├── Cessna 414AW ├── C414_Mixture_Controller.wasm ├── panel.GTN750 │ └── PANEL.CFG ├── panel.GTN750XI │ └── PANEL.CFG ├── panel │ └── PANEL.CFG └── sound.xml ├── LICENSE.txt ├── README.md ├── Seneca V └── SenecaV_Mixture_Controller.wasm ├── Turbo Arrow ├── TurboArrow_Mixture_Controller.wasm └── panel.cfg ├── Turbo Bonanza ├── Bonanza_Mixture_Controller.wasm └── panel.cfg ├── instructions.pdf └── src ├── C414AW ├── C414_Mixture_Controller.cpp ├── FdController.h ├── FdGauge.h ├── Mixture_Controller.sln ├── Mixture_Controller.vcxproj ├── Mixture_Controller.vcxproj.user ├── PidController.h ├── SimConnectDefs.h ├── common.h └── turbocharger.h ├── SenecaV ├── FdController.h ├── FdGauge.h ├── Mixture_Controller.sln ├── Mixture_Controller.vcxproj ├── Mixture_Controller.vcxproj.user ├── PidController.h ├── SenecaV_Mixture_Controller.cpp ├── SimConnectDefs.h ├── common.h └── turbocharger.h ├── TurboArrow ├── FdController.h ├── FdGauge.h ├── Mixture_Controller.sln ├── Mixture_Controller.vcxproj ├── Mixture_Controller.vcxproj.user ├── PidController.h ├── SimConnectDefs.h ├── TurboArrow_Mixture_Controller.cpp ├── common.h └── turbocharger.h └── TurboBonanza ├── Bonanza_Mixture_Controller.cpp ├── FdController.h ├── FdGauge.h ├── Mixture_Controller.sln ├── Mixture_Controller.vcxproj ├── Mixture_Controller.vcxproj.user ├── PidController.h ├── SimConnectDefs.h ├── common.h └── turbocharger.h /Cessna 414AW/C414_Mixture_Controller.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WxMarc/TurboEngineMixtureController/12a9b40bd4ee75bda1be05fdef31debdbec8cf7f/Cessna 414AW/C414_Mixture_Controller.wasm -------------------------------------------------------------------------------- /Cessna 414AW/panel.GTN750/PANEL.CFG: -------------------------------------------------------------------------------- 1 | //--------------------------Designed by FLYSIMWARE! 2022 ------------------------------ 2 | 3 | 4 | [Vcockpit01] 5 | size_mm = 650,768 6 | pixel_size = 650,768 7 | texture = SCREEN_PMSGTN750U1 8 | htmlgauge00=NavSystems/flysimware_Cessna_414A/pms50_gtn750_int/gtn750_int.html, 0, 0, 650,768 9 | 10 | [Vcockpit02] 11 | size_mm = 650,768 12 | pixel_size = 650,768 13 | texture = SCREEN_PMSGTN750U2 14 | htmlgauge00=NavSystems/flysimware_Cessna_414A/pms50_gtn750_int/gtn750_int.html?index=2, 0, 0, 650,768 15 | 16 | 17 | [Vcockpit03] 18 | size_mm = 512,512 19 | pixel_size = 512,512 20 | texture = RNAVNAV2 21 | htmlgauge00=Generic/Radios/RNAV_NAV2/RNAVNAV.html, 0,0,512,512 22 | 23 | 24 | [Vcockpit04] 25 | size_mm = 512,512 26 | pixel_size = 512,512 27 | texture = CFS1000A 28 | htmlgauge00=Generic/Radios/CFS1000A/CFS1000A.html, 0,0,512,512 29 | 30 | 31 | 32 | [Vcockpit05] 33 | size_mm = 512,512 34 | pixel_size = 512,512 35 | texture = DMEC1077B 36 | htmlgauge00=Generic/Radios/DME_C1077B/DMEC1077B.html, 0,0,512,512 37 | 38 | 39 | [Vcockpit06] 40 | size_mm = 512,512 41 | pixel_size = 512,512 42 | texture = COMM2 43 | htmlgauge00=Generic/Radios/COMM2_C1038A/C1038A.html, 0,0,512,512 44 | 45 | 46 | [Vcockpit07] 47 | size_mm = 512,512 48 | pixel_size = 512,512 49 | texture = ADF1 50 | htmlgauge00=Generic/Radios/ADF1_C1046A/C1046A.html, 0,0,512,512 51 | 52 | 53 | [Vcockpit08] 54 | size_mm = 512,512 55 | pixel_size = 512,512 56 | texture = M811B 57 | htmlgauge00=Generic/Radios/DAVTRON_M811B/M811B.html, 0,0,512,512 58 | 59 | 60 | [Vcockpit09] 61 | size_mm = 2048,2048 62 | pixel_size = 2048,2048 63 | texture = EFB_TABLET_CLICKABLE 64 | htmlgauge00=Generic/Radios/EFB_TABLET/EFBTABLET.html, 0,0,2048,2048 65 | 66 | 67 | [VCockpit10] 68 | size_mm = 440,100 69 | pixel_size = 440,100 70 | texture = GTX345_LCD_SCREEN 71 | htmlgauge00=Generic/Radios/AS330/AS330.html, 0,0,440, 100 72 | 73 | 74 | [Vcockpit11] 75 | size_mm = 2000,2000 76 | pixel_size = 2000,2000 77 | texture = GARMIN_AP_LCD_SCREEN 78 | gauge00=FLYSIMWARE_GARMIN_605_AP!GARMIN_605_AutoPilot, 0,0, 2000, 526 79 | 80 | 81 | [Vcockpit12] 82 | size_mm = 512,512 83 | pixel_size = 512,512 84 | texture = ALTSET 85 | htmlgauge00=Generic/Radios/ALT_AL300/AL300.html, 0,0,512,512 86 | 87 | 88 | [Vcockpit13] 89 | size_mm=0,0 90 | pixel_size=0,0 91 | texture=$PFD 92 | background_color=0,0,0 93 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=C414_Mixture_Controller.wasm&wasm_gauge=FdGauge,0,0,1,1 94 | 95 | 96 | [VPainting01] 97 | size_mm = 2048,512 98 | texture = $RegistrationNumber 99 | location = exterior 100 | 101 | painting00=Registration/Registration.html?font_color=black, 0, 0, 2048, 512 102 | 103 | [VPainting02] 104 | size_mm = 512,128 105 | texture = $RegistrationNumber 106 | location = interior 107 | 108 | painting00=Registration/Registration.html?font_color=white, 0, 0, 512, 128 -------------------------------------------------------------------------------- /Cessna 414AW/panel.GTN750XI/PANEL.CFG: -------------------------------------------------------------------------------- 1 | //--------------------------Designed by FLYSIMWARE! 2022 ------------------------------ 2 | 3 | 4 | [Vcockpit01] 5 | size_mm = 650,768 6 | pixel_size = 650,768 7 | texture = SCREEN_TDSGTNXI750U1 8 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=Gauge/TDSGTNXiGaugeModule.wasm&wasm_gauge=GTNXI750U1, 0,0,650,768 9 | 10 | 11 | [VCockpit02] 12 | size_mm = 650,768 13 | pixel_size = 650,768 14 | texture = SCREEN_TDSGTNXI750U2 15 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=Gauge/TDSGTNXiGaugeModule.wasm&wasm_gauge=GTNXI750U2, 0,0,650,768 16 | 17 | 18 | [Vcockpit03] 19 | size_mm = 512,512 20 | pixel_size = 512,512 21 | texture = RNAVNAV2 22 | htmlgauge00=Generic/Radios/RNAV_NAV2/RNAVNAV.html, 0,0,512,512 23 | 24 | 25 | [Vcockpit04] 26 | size_mm = 512,512 27 | pixel_size = 512,512 28 | texture = CFS1000A 29 | htmlgauge00=Generic/Radios/CFS1000A/CFS1000A.html, 0,0,512,512 30 | 31 | 32 | 33 | [Vcockpit05] 34 | size_mm = 512,512 35 | pixel_size = 512,512 36 | texture = DMEC1077B 37 | htmlgauge00=Generic/Radios/DME_C1077B/DMEC1077B.html, 0,0,512,512 38 | 39 | 40 | [Vcockpit06] 41 | size_mm = 512,512 42 | pixel_size = 512,512 43 | texture = COMM2 44 | htmlgauge00=Generic/Radios/COMM2_C1038A/C1038A.html, 0,0,512,512 45 | 46 | 47 | [Vcockpit07] 48 | size_mm = 512,512 49 | pixel_size = 512,512 50 | texture = ADF1 51 | htmlgauge00=Generic/Radios/ADF1_C1046A/C1046A.html, 0,0,512,512 52 | 53 | 54 | [Vcockpit08] 55 | size_mm = 512,512 56 | pixel_size = 512,512 57 | texture = M811B 58 | htmlgauge00=Generic/Radios/DAVTRON_M811B/M811B.html, 0,0,512,512 59 | 60 | 61 | [Vcockpit09] 62 | size_mm = 2048,2048 63 | pixel_size = 2048,2048 64 | texture = EFB_TABLET_CLICKABLE 65 | htmlgauge00=Generic/Radios/EFB_TABLET/EFBTABLET.html, 0,0,2048,2048 66 | 67 | 68 | [VCockpit10] 69 | size_mm = 440,100 70 | pixel_size = 440,100 71 | texture = GTX345_LCD_SCREEN 72 | htmlgauge00=Generic/Radios/AS330/AS330.html, 0,0,440, 100 73 | 74 | 75 | [Vcockpit11] 76 | size_mm = 2000,2000 77 | pixel_size = 2000,2000 78 | texture = GARMIN_AP_LCD_SCREEN 79 | gauge00=FLYSIMWARE_GARMIN_605_AP!GARMIN_605_AutoPilot, 0,0, 2000, 526 80 | 81 | 82 | [Vcockpit12] 83 | size_mm = 512,512 84 | pixel_size = 512,512 85 | texture = ALTSET 86 | htmlgauge00=Generic/Radios/ALT_AL300/AL300.html, 0,0,512,512 87 | 88 | 89 | [Vcockpit13] 90 | size_mm=0,0 91 | pixel_size=0,0 92 | texture=$PFD 93 | background_color=0,0,0 94 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=C414_Mixture_Controller.wasm&wasm_gauge=FdGauge,0,0,1,1 95 | 96 | 97 | 98 | 99 | [VPainting01] 100 | size_mm = 2048,512 101 | texture = $RegistrationNumber 102 | location = exterior 103 | 104 | painting00=Registration/Registration.html?font_color=black, 0, 0, 2048, 512 105 | 106 | [VPainting02] 107 | size_mm = 512,128 108 | texture = $RegistrationNumber 109 | location = interior 110 | 111 | painting00=Registration/Registration.html?font_color=white, 0, 0, 512, 128 -------------------------------------------------------------------------------- /Cessna 414AW/panel/PANEL.CFG: -------------------------------------------------------------------------------- 1 | //--------------------------Designed by FLYSIMWARE! 2021 ------------------------------ 2 | 3 | 4 | 5 | 6 | [VCockpit01] 7 | size_mm = 320,234 8 | pixel_size = 320,234 9 | texture = AS530_Screen 10 | htmlgauge00=NavSystems/GPS/AS530/AS530.html, 0, 0, 320,234 11 | 12 | 13 | [Vcockpit02] 14 | size_mm = 512,512 15 | pixel_size = 512,512 16 | texture = RNAVNAV2 17 | htmlgauge00=Generic/Radios/RNAV_NAV2/RNAVNAV.html, 0,0,512,512 18 | 19 | 20 | [Vcockpit03] 21 | size_mm = 512,512 22 | pixel_size = 512,512 23 | texture = CFS1000A 24 | htmlgauge00=Generic/Radios/CFS1000A/CFS1000A.html, 0,0,512,512 25 | 26 | 27 | 28 | [Vcockpit04] 29 | size_mm = 512,512 30 | pixel_size = 512,512 31 | texture = DMEC1077B 32 | htmlgauge00=Generic/Radios/DME_C1077B/DMEC1077B.html, 0,0,512,512 33 | 34 | 35 | [Vcockpit05] 36 | size_mm = 512,512 37 | pixel_size = 512,512 38 | texture = COMM2 39 | htmlgauge00=Generic/Radios/COMM2_C1038A/C1038A.html, 0,0,512,512 40 | 41 | 42 | [Vcockpit06] 43 | size_mm = 512,512 44 | pixel_size = 512,512 45 | texture = ADF1 46 | htmlgauge00=Generic/Radios/ADF1_C1046A/C1046A.html, 0,0,512,512 47 | 48 | 49 | [Vcockpit07] 50 | size_mm = 512,512 51 | pixel_size = 512,512 52 | texture = M811B 53 | htmlgauge00=Generic/Radios/DAVTRON_M811B/M811B.html, 0,0,512,512 54 | 55 | 56 | [Vcockpit08] 57 | size_mm = 2048,2048 58 | pixel_size = 2048,2048 59 | texture = EFB_TABLET_CLICKABLE 60 | htmlgauge00=Generic/Radios/EFB_TABLET/EFBTABLET.html, 0,0,2048,2048 61 | 62 | [Vcockpit09] 63 | size_mm=0,0 64 | pixel_size=0,0 65 | texture=$PFD 66 | background_color=0,0,0 67 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=C414_Mixture_Controller.wasm&wasm_gauge=FdGauge,0,0,1,1 68 | 69 | 70 | 71 | [VPainting01] 72 | size_mm = 2048,512 73 | texture = $RegistrationNumber 74 | location = exterior 75 | 76 | painting00=Registration/Registration.html?font_color=black, 0, 0, 2048, 512 77 | 78 | [VPainting02] 79 | size_mm = 512,128 80 | texture = $RegistrationNumber 81 | location = interior 82 | 83 | painting00=Registration/Registration.html?font_color=white, 0, 0, 512, 128 -------------------------------------------------------------------------------- /Cessna 414AW/sound.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TurboEngineMixtureController 2 | Mixture Controller for Turbocharged Piston Engines in MSFS 2020 3 | 4 | NOTE: If using the Working Title Technology add-on for the PMS50 GTN750 in the Carenado Seneca V, the mixture controller 5 | WASM file and the panel.cfg must be edited in the "pms50-gtn750wtt-aircraft-carenado-seneca" folder in your community 6 | folder, rather than following the procedure in the included PDF instructions. The PMS50 WTT add-on replaces the default 7 | panel in the Seneca V, and it will override the mixture controller if it is located in the official Carenado installation 8 | directory. 9 | 10 | Microsoft Flight Simulator's fuel mixture logic for turbocharged piston engines produces excessively rich fuel mixtures 11 | and loss of power at high altitudes. The default logic appears to calculate fuel/air mixture based on ambient density. 12 | In a real-world turbocharged engine, the turbocharger compresses air before it enters the cylinders. This increases the 13 | density of air in the engine significantly above ambient density. As such, the default logic in MSFS does not properly 14 | represent the behavior of a turbocharged engine. A more detailed description of the problem, with data extracted from 15 | MSFS, is available here: 16 | 17 | https://forums.flightsimulator.com/t/inaccurate-mixture-behavior-on-turbocharged-piston-engines/411095 18 | 19 | This package includes mixture controllers for four turbocharged piston aircraft that are currently available for 20 | MSFS: 21 | 22 | - Robert Young's Turbonormalized Bonanza 23 | - Just Flight Turbo Arrow 24 | - Carenado Seneca V 25 | - Flysimware 414AW 26 | 27 | The controllers calculate a target fuel/air mixture based on the user's hardware mixture lever setting and the air density 28 | in the intake manifold (rather than the ambient density). This allows the mixture to be managed according to the POH, 29 | setting mixture at full-rich throughout the climb instead of leaning the mixture to maintain proper power. 30 | 31 | Please see the included instructions.pdf for detailed instructions on how to install the controllers for any of the 32 | aircraft mentioned above. 33 | 34 | DEPENDENCIES: 35 | Successful installtion of the mixture controller requires updating the layout.json file for the airplane to include a 36 | reference to the .wasm file which is added to the airplane's panel folder. I recommend using the MSFS Layout Generator for 37 | this. It is a quick drag-and-drop utility that updates layout files painlessly. See the link below for more information. 38 | 39 | https://github.com/HughesMDflyer4/MSFSLayoutGenerator 40 | 41 | CAUTION: 42 | Successfully installing these controllers will require copying and pasting the files to the proper folders and updating 43 | each airplane's panel.cfg and layout.json files. If you are not comfortable taking these steps, do not use this code. 44 | 45 | These controllers are designed to work with a hardware axis (throttle quadrant, HOTAS with mixture axis, etc). If you do 46 | not have a hardware mixture axis, or if you have automixture enabled in MSFS settings, this controller will not add much 47 | value to your experience. 48 | 49 | KNOWN ISSUES: 50 | The mixture lever displayed in the virtual cockpit will not reflect the mixture setting on your hardware axis. The virtual 51 | cockpit lever displays the mixture setting in the sim, which the controller code calculates based on your hardware axis 52 | position and the density in the intake manifold. At high altitudes, it is quite possible to see the virtual cockpit lever 53 | set to 25% mixture while the hardware axis is set to full-rich. This is an artifact of the incorrect mixture logic in the 54 | core sim. It is possible to map the virtual cockpit mixture lever to the hardware axis, but this controller currently does 55 | not implement that feature. 56 | -------------------------------------------------------------------------------- /Seneca V/SenecaV_Mixture_Controller.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WxMarc/TurboEngineMixtureController/12a9b40bd4ee75bda1be05fdef31debdbec8cf7f/Seneca V/SenecaV_Mixture_Controller.wasm -------------------------------------------------------------------------------- /Turbo Arrow/TurboArrow_Mixture_Controller.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WxMarc/TurboEngineMixtureController/12a9b40bd4ee75bda1be05fdef31debdbec8cf7f/Turbo Arrow/TurboArrow_Mixture_Controller.wasm -------------------------------------------------------------------------------- /Turbo Arrow/panel.cfg: -------------------------------------------------------------------------------- 1 | // Panel Configuration file 2 | // JF_PA28_TurboArrow 3 | 4 | [VCockpit01] 5 | size_mm=1024,768 6 | pixel_size=1024,768 7 | texture=$TEST 8 | background_color=0,0,255 9 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=JF_PA28_TurboArrow_WASM.wasm&wasm_gauge=SystemWatch, 0,0,1024,768 10 | 11 | [VCockpit02] 12 | size_mm=347,95 13 | pixel_size=347,95 14 | texture=SCREEN_GPS 15 | background_color=0,0,255 16 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=JF_PA28_TurboArrow_WASM.wasm&wasm_gauge=GPS100, 0,0,347,95 17 | 18 | [VCockpit03] 19 | size_mm=421,88 20 | pixel_size=421,88 21 | texture=SCREEN_KN62 22 | background_color=0,0,255 23 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=JF_PA28_TurboArrow_WASM.wasm&wasm_gauge=KN62, 0,0,421,88 24 | 25 | [VCockpit04] 26 | size_mm=256,90 27 | pixel_size=256,90 28 | texture=SCREEN_TIMER 29 | background_color=0,0,255 30 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=JF_PA28_TurboArrow_WASM.wasm&wasm_gauge=AstroTechTimer, 0,0,256,90 31 | 32 | [VCockpit05] 33 | size_mm = 350,190 34 | pixel_size = 350,190 35 | texture = SCREEN_GNS430 36 | htmlgauge00=NavSystems/GPS/AS430/AS430.html, 4, 3, 342, 183 37 | 38 | [VCockpit06] 39 | size_mm = 320,234 40 | pixel_size = 320,234 41 | texture = SCREEN_GNS530 42 | htmlgauge00=NavSystems/GPS/AS530/AS530.html, 4, 3, 316, 231 43 | 44 | [VCockpit07] 45 | size_mm=1024,1024 46 | pixel_size=1024,1024 47 | texture=SCREEN_EFB 48 | background_color=0,0,255 49 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=JF_PA28_TurboArrow_WASM.wasm&wasm_gauge=EFB, 0,0,1024,1024 50 | 51 | [VCockpit08] 52 | Background_color = 0,0,0 53 | size_mm = 650,768 54 | visible = 0 55 | pixel_size = 650,768 56 | texture=SCREEN_GTN 57 | 58 | htmlgauge00= NavSystems/justflight-aircraft-pa28-turboarrow/pms50_gtn750_int/gtn750_int.html, 0,0,650,768 59 | 60 | [VCockpit09] 61 | Background_color = 0,0,0 62 | size_mm = 650,290 63 | visible = 0 64 | pixel_size = 650,290 65 | texture = SCREEN_GTN650 66 | 67 | htmlgauge00= NavSystems/justflight-aircraft-pa28-turboarrow/pms50_gtn750_int/gtn650_int.html?index=2, 0, 0, 650,290 68 | 69 | [Vcockpit10] 70 | size_mm = 650,768 71 | pixel_size = 650,768 72 | texture = SCREEN_TDSGTNXI750 73 | background_color=0,0,0 74 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=Gauge/TDSGTNXiGaugeModule.wasm&wasm_gauge=GTNXI750U1, 0,0,650,768 75 | 76 | [Vcockpit11] 77 | size_mm = 650,287 78 | pixel_size = 650,287 79 | texture = SCREEN_TDSGTNXI650 80 | background_color=0,0,0 81 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=Gauge/TDSGTNXiGaugeModule.wasm&wasm_gauge=GTNXI650U2, 0,0,650,287 82 | 83 | [Vcockpit12] 84 | size_mm=0,0 85 | pixel_size=0,0 86 | texture=$PFD 87 | background_color=0,0,0 88 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=TurboArrow_Mixture_Controller.wasm&wasm_gauge=FdGauge,0,0,1,1 89 | 90 | [VIEWS] 91 | VIEW_FORWARD_DIR=2.000, 0.000, 0.000 92 | 93 | [Color] 94 | Day=255,255,255 95 | Night=255,255,255 96 | Luminous=201,64,64 97 | 98 | 99 | [VPainting01] 100 | size_mm = 512,128 101 | texture = $RegistrationNumberInt 102 | location = interior 103 | 104 | painting00=Registration/Registration.html?font_color=white, 0, 0, 512, 128 105 | 106 | [VPainting02] 107 | size_mm = 2048,512 108 | texture = $RegistrationNumbersExt 109 | location = interior 110 | 111 | painting00=Registration/Registration.html?font_color=black&font_style=bold-italic, 0, 0, 2048, 512 112 | 113 | [VPainting03] 114 | size_mm = 2048,512 115 | texture = $RegistrationNumbersExt 116 | location = exterior 117 | 118 | painting00=Registration/Registration.html?font_color=black&font_style=bold-italic, 0, 0, 2048, 512 -------------------------------------------------------------------------------- /Turbo Bonanza/Bonanza_Mixture_Controller.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WxMarc/TurboEngineMixtureController/12a9b40bd4ee75bda1be05fdef31debdbec8cf7f/Turbo Bonanza/Bonanza_Mixture_Controller.wasm -------------------------------------------------------------------------------- /Turbo Bonanza/panel.cfg: -------------------------------------------------------------------------------- 1 | // Panel Configuration file 2 | // Asobo BONANZA_G36 3 | 4 | [VCockpit01] 5 | size_mm=1024,768 6 | pixel_size=1024,768 7 | texture=$AS1000_PFD_1 8 | background_color=42,42,40 9 | htmlgauge00=NavSystems/AS1000/PFD/AS1000_PFD.html, 0,0,1024,768 10 | 11 | [VCockpit02] 12 | size_mm=1024,768 13 | pixel_size=1024,768 14 | texture=$AS1000_PFD_2 15 | background_color=42,42,40 16 | htmlgauge00=NavSystems/AS1000/MFD/AS1000_MFD.html, 0,0,1024,768 17 | 18 | [VCockpit03] 19 | size_mm=317,109 20 | pixel_size=317,109 21 | texture=$OAT_Screen 22 | htmlgauge00=Generic/Misc/DigitalOAT/DigitalOAT.html, 0,0,317,109 23 | 24 | [Vcockpit04] 25 | size_mm=0,0 26 | pixel_size=0,0 27 | texture=$PFD 28 | background_color=0,0,0 29 | htmlgauge00=WasmInstrument/WasmInstrument.html?wasm_module=Bonanza_Mixture_Controller.wasm&wasm_gauge=FdGauge,0,0,1,1 30 | 31 | [VPainting01] 32 | size_mm = 256, 64 33 | texture = $RegistrationNumber 34 | location = interior 35 | painting00=Registration/Registration.html?font_color=white, 0, 0, 256, 64 36 | 37 | [VPainting02] 38 | size_mm = 1024, 256 39 | texture = $RegistrationNumber 40 | location = exterior 41 | painting00=Registration/Registration.html?font_color=white, 0, 0, 1024, 256 -------------------------------------------------------------------------------- /instructions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WxMarc/TurboEngineMixtureController/12a9b40bd4ee75bda1be05fdef31debdbec8cf7f/instructions.pdf -------------------------------------------------------------------------------- /src/C414AW/C414_Mixture_Controller.cpp: -------------------------------------------------------------------------------- 1 |  2 | #ifdef _MSC_VER 3 | #define snprintf _snprintf_s 4 | #elif !defined(__MINGW32__) 5 | #include 6 | #endif 7 | 8 | #include "FdGauge.h" 9 | 10 | FdGauge FD_GAUGE; 11 | 12 | 13 | // ------------------------ 14 | // Callbacks 15 | extern "C" { 16 | 17 | MSFS_CALLBACK bool FdGauge_gauge_callback(FsContext ctx, int service_id, void* pData) 18 | { 19 | switch (service_id) 20 | { 21 | case PANEL_SERVICE_PRE_INSTALL: 22 | { 23 | return true; 24 | } 25 | break; 26 | case PANEL_SERVICE_POST_INSTALL: 27 | { 28 | return FD_GAUGE.InitializeFD(); 29 | } 30 | break; 31 | case PANEL_SERVICE_PRE_DRAW: 32 | { 33 | sGaugeDrawData* drawData = static_cast(pData); 34 | return FD_GAUGE.OnUpdate(drawData->dt); 35 | } 36 | break; 37 | case PANEL_SERVICE_PRE_KILL: 38 | { 39 | FD_GAUGE.KillFD(); 40 | return true; 41 | } 42 | break; 43 | } 44 | return false; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/C414AW/FdController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef FDCTRL 4 | #define FDCTRL 5 | 6 | #include "common.h" 7 | #include "PidController.h" 8 | 9 | class FdController 10 | { 11 | private: 12 | SimVars* simVars; 13 | 14 | /// 15 | /// An instance of the mixture PID controller. 16 | /// 17 | PidController* mixtureController[2]; 18 | 19 | /// 20 | /// The current mixture control axis, from -16384 to 16384. 21 | /// 22 | int mixtureAxis[2] = { 16384, 16384 }; 23 | 24 | bool enabled = true; 25 | 26 | /// 27 | /// Calculates and updates mixture according to target and PIDs. 28 | /// 29 | /// 30 | void updateMixture(double deltaTime) { 31 | EngineControlData controls; 32 | controls.mixtureLeft = this->getDesiredMixture(0, deltaTime); 33 | controls.mixtureRight = this->getDesiredMixture(1, deltaTime); 34 | SimConnect_SetDataOnSimObject(hSimConnect, DataTypes::EngineControls, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(EngineControlData), &controls); 35 | } 36 | 37 | /// 38 | /// Gets the mixture lever position to achieve the desired fuel/air mixture ratio 39 | /// 40 | /// Desired mixture setting 41 | double getDesiredMixture(int idx, double deltaTime) { 42 | double mixtureLeverPerc = 100.0 * (this->mixtureAxis[idx] + 16384) / 32768.0; 43 | 44 | if (!enabled) { 45 | return mixtureLeverPerc; 46 | } 47 | 48 | double targetFuelAirMixture = Turbocharger::getTargetFuelAirRatio(mixtureLeverPerc); 49 | double simFuelAirMixture = this->simVars->getFuelAirRatio(idx + 1); 50 | //printf("Target Mixture: %1.3f\nSim Mixture: %1.3f\n", targetFuelAirMixture, simFuelAirMixture); 51 | 52 | double error = targetFuelAirMixture - simFuelAirMixture; 53 | double pidOut = 0.0; 54 | pidOut = this->mixtureController[idx]->GetOutput(error, deltaTime); 55 | //printf("Output from PID Controller: %2.3f\n", error); 56 | return max(0, min(100, this->simVars->getMixtureLeverPosition(idx + 1) + pidOut)); 57 | } 58 | 59 | void updateVisibleMixture(int idx) { 60 | int targetMixture = this->mixtureAxis[idx]; 61 | double mixtureLeverPerc = 100.0 * (targetMixture + 16384) / 32768.0; 62 | if (idx == 0) { 63 | this->simVars->setMixture1Pos(mixtureLeverPerc); 64 | } 65 | else { 66 | this->simVars->setMixture2Pos(mixtureLeverPerc); 67 | } 68 | } 69 | 70 | public: 71 | void init() 72 | { 73 | printf("FdController init"); 74 | 75 | this->simVars = new SimVars(); 76 | 77 | float p = 150.0; 78 | float i = 580.0; 79 | float d = 0.0; 80 | this->mixtureController[0] = new PidController(p, i, d, -5, 5); 81 | this->mixtureController[1] = new PidController(p, i, d, -5, 5); 82 | } 83 | 84 | void update(int mixtureAxis[], double deltaTime) 85 | { 86 | this->mixtureAxis[0] = mixtureAxis[0]; 87 | this->mixtureAxis[1] = mixtureAxis[1]; 88 | this->updateMixture(deltaTime); 89 | this->updateVisibleMixture(0); 90 | this->updateVisibleMixture(1); 91 | this->simVars->setFadecActiveFlag(); 92 | } 93 | }; 94 | 95 | FdController FdCtrlInstance; 96 | 97 | #endif // !FDCTRL 98 | -------------------------------------------------------------------------------- /src/C414AW/FdGauge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifndef FDGAUGE 5 | #define FDGAUGE 6 | 7 | #ifndef __INTELLISENSE__ 8 | # define MODULE_EXPORT __attribute__( ( visibility( "default" ) ) ) 9 | # define MODULE_WASM_MODNAME(mod) __attribute__((import_module(mod))) 10 | #else 11 | # define MODULE_EXPORT 12 | # define MODULE_WASM_MODNAME(mod) 13 | # define __attribute__(x) 14 | # define __restrict__ 15 | #endif 16 | 17 | #include "common.h" 18 | #include "FdController.h" 19 | 20 | const int MIN_MIX = -16384; 21 | const int MAX_MIX = 16384; 22 | const int MIX_STEP = 256; 23 | 24 | int globalMixtureAxis[2] = { MAX_MIX, MAX_MIX }; 25 | 26 | class FdGauge 27 | { 28 | private: 29 | 30 | bool isConnected = false; 31 | 32 | /// 33 | /// Registers all the mixture SimConnect client events. 34 | /// 35 | void RegisterMixtureClientEvents() 36 | { 37 | printf("Registering mixture events...\r\n"); 38 | 39 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixtureSet, "AXIS_MIXTURE_SET"); 40 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixture1Set, "AXIS_MIXTURE1_SET"); 41 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixture2Set, "AXIS_MIXTURE2_SET"); 42 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSetBest, "MIXTURE_SET_BEST"); 43 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSet, "MIXTURE_SET"); 44 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureRich, "MIXTURE_RICH"); 45 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncr, "MIXTURE_INCR"); 46 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncrSmall, "MIXTURE_INCR_SMALL"); 47 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecrSmall, "MIXTURE_DECR_SMALL"); 48 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecr, "MIXTURE_DECR"); 49 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureLean, "MIXTURE_LEAN"); 50 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Set, "MIXTURE1_SET"); 51 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Rich, "MIXTURE1_RICH"); 52 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Incr, "MIXTURE1_INCR"); 53 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1IncrSmall, "MIXTURE1_INCR_SMALL"); 54 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1DecrSmall, "MIXTURE1_DECR_SMALL"); 55 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Decr, "MIXTURE1_DECR"); 56 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Lean, "MIXTURE1_LEAN"); 57 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Set, "MIXTURE2_SET"); 58 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Rich, "MIXTURE2_RICH"); 59 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Incr, "MIXTURE2_INCR"); 60 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2IncrSmall, "MIXTURE2_INCR_SMALL"); 61 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2DecrSmall, "MIXTURE2_DECR_SMALL"); 62 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Decr, "MIXTURE2_DECR"); 63 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Lean, "MIXTURE2_LEAN"); 64 | } 65 | 66 | /// 67 | /// Registers the SimConnect mixture event group for capture. 68 | /// 69 | void RegisterMixtureEventGroup() 70 | { 71 | printf("Registering mixture event group...\r\n"); 72 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixtureSet, TRUE); 73 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixture1Set, TRUE); 74 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixture2Set, TRUE); 75 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSetBest, TRUE); 76 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSet, TRUE); 77 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureRich, TRUE); 78 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncr, TRUE); 79 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncrSmall, TRUE); 80 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecrSmall, TRUE); 81 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecr, TRUE); 82 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureLean, TRUE); 83 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Set, TRUE); 84 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Rich, TRUE); 85 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Incr, TRUE); 86 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1IncrSmall, TRUE); 87 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1DecrSmall, TRUE); 88 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Decr, TRUE); 89 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Lean, TRUE); 90 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Set, TRUE); 91 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Rich, TRUE); 92 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Incr, TRUE); 93 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2IncrSmall, TRUE); 94 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2DecrSmall, TRUE); 95 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Decr, TRUE); 96 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Lean, TRUE); 97 | 98 | SimConnect_SetNotificationGroupPriority(hSimConnect, EventGroups::Mixture, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE); 99 | } 100 | 101 | /// 102 | /// Initializes the connection to SimConnect. 103 | /// 104 | /// True if successful, false otherwise. 105 | bool InitializeSimConnect() 106 | { 107 | printf("Connecting to SimConnect...\r\n"); 108 | if (SUCCEEDED(SimConnect_Open(&hSimConnect, "FdGauge", nullptr, 0, 0, 0))) 109 | { 110 | printf("SimConnect connected.\r\n"); 111 | 112 | this->RegisterMixtureClientEvents(); 113 | this->RegisterMixtureEventGroup(); 114 | 115 | SimConnect_AddToDataDefinition(hSimConnect, DataTypes::EngineControls, "GENERAL ENG MIXTURE LEVER POSITION:1", "Percent"); 116 | SimConnect_AddToDataDefinition(hSimConnect, DataTypes::EngineControls, "GENERAL ENG MIXTURE LEVER POSITION:2", "Percent"); 117 | 118 | printf("SimConnect registrations complete.\r\n"); 119 | return true; 120 | } 121 | 122 | printf("SimConnect failed.\r\n"); 123 | 124 | return false; 125 | } 126 | 127 | /// 128 | /// A callback used for handling SimConnect updates. 129 | /// 130 | /// The update data sent by SimConnect. 131 | /// The size of the SimConnect data structure. 132 | /// A pointer specified by the client. 133 | static void CALLBACK HandleAxisEvent(SIMCONNECT_RECV* pData, DWORD cbData, void* pContext) 134 | { 135 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EVENT) 136 | { 137 | SIMCONNECT_RECV_EVENT* evt = static_cast(pData); 138 | if (evt->uGroupID == EventGroups::Mixture) 139 | { 140 | FdGauge* fd = static_cast(pContext); 141 | if (fd == 0) 142 | { 143 | printf("FD pointer was null processing SimConnect event.\r\n"); 144 | } 145 | else 146 | { 147 | HandleMixtureAxis(evt); 148 | } 149 | } 150 | } 151 | 152 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EXCEPTION) 153 | { 154 | SIMCONNECT_RECV_EXCEPTION* ex = static_cast(pData); 155 | printf("SimConnect Exception: %d \r\n", ex->dwException); 156 | } 157 | } 158 | 159 | /// 160 | /// Handles throttle axis updates received from SimConnect. 161 | /// 162 | /// A pointer to the SimConnect event structure. 163 | static void HandleMixtureAxis(SIMCONNECT_RECV_EVENT* evt) 164 | { 165 | switch (evt->uEventID) 166 | { 167 | case MixtureEventIDs::AxisMixtureSet: 168 | globalMixtureAxis[0] = static_cast(evt->dwData); 169 | globalMixtureAxis[1] = static_cast(evt->dwData); 170 | break; 171 | case MixtureEventIDs::AxisMixture1Set: 172 | globalMixtureAxis[0] = static_cast(evt->dwData); 173 | break; 174 | case MixtureEventIDs::AxisMixture2Set: 175 | globalMixtureAxis[1] = static_cast(evt->dwData); 176 | break; 177 | case MixtureEventIDs::MixtureSetBest: 178 | case MixtureEventIDs::MixtureRich: 179 | globalMixtureAxis[0] = MAX_MIX; 180 | globalMixtureAxis[1] = MAX_MIX; 181 | break; 182 | case MixtureEventIDs::Mixture1Rich: 183 | globalMixtureAxis[0] = MAX_MIX; 184 | break; 185 | case MixtureEventIDs::Mixture2Rich: 186 | globalMixtureAxis[1] = MAX_MIX; 187 | break; 188 | case MixtureEventIDs::MixtureSet: 189 | globalMixtureAxis[0] = (static_cast(evt->dwData) * 2) - MAX_MIX; 190 | globalMixtureAxis[1] = (static_cast(evt->dwData) * 2) - MAX_MIX; 191 | break; 192 | case MixtureEventIDs::Mixture1Set: 193 | globalMixtureAxis[0] = (static_cast(evt->dwData) * 2) - MAX_MIX; 194 | break; 195 | case MixtureEventIDs::Mixture2Set: 196 | globalMixtureAxis[1] = (static_cast(evt->dwData) * 2) - MAX_MIX; 197 | break; 198 | case MixtureEventIDs::MixtureLean: 199 | globalMixtureAxis[0] = MIN_MIX; 200 | globalMixtureAxis[1] = MIN_MIX; 201 | break; 202 | case MixtureEventIDs::Mixture1Lean: 203 | globalMixtureAxis[0] = MIN_MIX; 204 | break; 205 | case MixtureEventIDs::Mixture2Lean: 206 | globalMixtureAxis[1] = MIN_MIX; 207 | break; 208 | case MixtureEventIDs::MixtureIncr: 209 | case MixtureEventIDs::MixtureIncrSmall: 210 | globalMixtureAxis[0] += MIX_STEP; // TODO: CLAMP ALL INCR/DECR EVENTS 211 | globalMixtureAxis[1] += MIX_STEP; 212 | break; 213 | case MixtureEventIDs::Mixture1Incr: 214 | case MixtureEventIDs::Mixture1IncrSmall: 215 | globalMixtureAxis[0] += MIX_STEP; 216 | break; 217 | case MixtureEventIDs::Mixture2Incr: 218 | case MixtureEventIDs::Mixture2IncrSmall: 219 | globalMixtureAxis[1] += MIX_STEP; 220 | break; 221 | case MixtureEventIDs::MixtureDecr: 222 | case MixtureEventIDs::MixtureDecrSmall: 223 | globalMixtureAxis[0] -= MIX_STEP; 224 | globalMixtureAxis[1] -= MIX_STEP; 225 | break; 226 | case MixtureEventIDs::Mixture1Decr: 227 | case MixtureEventIDs::Mixture1DecrSmall: 228 | globalMixtureAxis[0] -= MIX_STEP; 229 | break; 230 | case MixtureEventIDs::Mixture2Decr: 231 | case MixtureEventIDs::Mixture2DecrSmall: 232 | globalMixtureAxis[1] -= MIX_STEP; 233 | break; 234 | } 235 | 236 | globalMixtureAxis[0] = clamp(globalMixtureAxis[0], MIN_MIX, MAX_MIX); 237 | globalMixtureAxis[1] = clamp(globalMixtureAxis[1], MIN_MIX, MAX_MIX); 238 | } 239 | 240 | public: 241 | 242 | /// 243 | /// Initializes the FD. 244 | /// 245 | /// True if successful, false otherwise. 246 | bool InitializeFD() 247 | { 248 | if (!this->InitializeSimConnect()) { 249 | printf("Init SimConnect failed"); 250 | return false; 251 | } 252 | 253 | FdCtrlInstance.init(); 254 | isConnected = true; 255 | SimConnect_CallDispatch(hSimConnect, HandleAxisEvent, this); 256 | 257 | return true; 258 | } 259 | 260 | /// 261 | /// A callback used to update the FD at each tick. 262 | /// 263 | /// The time since the previous update. 264 | /// True if successful, false otherwise. 265 | bool OnUpdate(double deltaTime) 266 | { 267 | if (isConnected == true) { 268 | FdCtrlInstance.update(globalMixtureAxis, deltaTime); 269 | } 270 | 271 | return true; 272 | } 273 | 274 | /// 275 | /// Kill. 276 | /// 277 | /// True if succesful, false otherwise. 278 | bool KillFD() 279 | { 280 | isConnected = false; 281 | unregister_all_named_vars(); 282 | return SUCCEEDED(SimConnect_Close(hSimConnect)); 283 | } 284 | }; 285 | 286 | #endif // !FDGAUGE 287 | -------------------------------------------------------------------------------- /src/C414AW/Mixture_Controller.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31410.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mixture_Controller", "Mixture_Controller.vcxproj", "{A5468B35-BBBD-4C55-97ED-81BFE343B0E4}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|MSFS = Debug|MSFS 11 | Release|MSFS = Release|MSFS 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.ActiveCfg = Debug|MSFS 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.Build.0 = Debug|MSFS 16 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.ActiveCfg = Release|MSFS 17 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.Build.0 = Release|MSFS 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9129C4E4-B566-46E3-81AE-74CCDE9CA599} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/C414AW/Mixture_Controller.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | MSFS 7 | 8 | 9 | Release 10 | MSFS 11 | 12 | 13 | 14 | 16.0 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4} 16 | Module 17 | 10.0 18 | C414_Mixture_Controller 19 | 20 | 21 | 22 | Application 23 | true 24 | MSFS 25 | MultiByte 26 | 27 | 28 | Application 29 | false 30 | MSFS 31 | true 32 | MultiByte 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | .wasm 48 | 49 | 50 | $(MSFS_IncludePath) 51 | 52 | 53 | .wasm 54 | $(MSFS_IncludePath) 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | false 67 | 68 | 69 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 70 | false 71 | 72 | 73 | 74 | 75 | 76 | 77 | ProgramDatabase 78 | 79 | 80 | stdcpp14 81 | 82 | 83 | 84 | 85 | %(AdditionalDependencies) 86 | 87 | 88 | true 89 | $(OutDir)$(TargetName)$(TargetExt) 90 | 91 | 92 | 93 | 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | $(OutDir) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | 117 | 118 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 119 | false 120 | false 121 | false 122 | 123 | 124 | 125 | 126 | 127 | 128 | %(AdditionalDependencies) 129 | 130 | 131 | true 132 | $(OutDir)$(TargetName)$(TargetExt) 133 | 134 | 135 | 136 | 137 | false 138 | 139 | 140 | 141 | 142 | 143 | 144 | $(OutDir) 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/C414AW/Mixture_Controller.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/C414AW/PidController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef PIDC 4 | #define PIDC 5 | 6 | /// 7 | /// A class for controlling values via a PID. 8 | /// 9 | class PidController 10 | { 11 | private: 12 | /// 13 | /// The gain of the proportional term. 14 | /// 15 | double gainProportion; 16 | 17 | /// 18 | /// The gain of the integral term. 19 | /// 20 | double gainIntegral; 21 | 22 | /// 23 | /// The gain of the derivative term. 24 | /// 25 | double gainDerivative; 26 | 27 | /// 28 | /// The minimum output allowed. 29 | /// 30 | double minOutput; 31 | 32 | /// 33 | /// The maximum output allowed. 34 | /// 35 | double maxOutput; 36 | 37 | /// 38 | /// The previous error amount. 39 | /// 40 | double prevError; 41 | 42 | /// 43 | /// The previous PID output. 44 | /// 45 | double prevOutput; 46 | 47 | /// 48 | /// The current integral term. 49 | /// 50 | double integral; 51 | 52 | /// 53 | /// Clamps a value to the PID min and max outputs. 54 | /// 55 | /// The value to clamp. 56 | /// The clamped value. 57 | double Clamp(double value, double max, double min) 58 | { 59 | if (value > max) 60 | { 61 | return max; 62 | } 63 | 64 | if (value < min) 65 | { 66 | return min; 67 | } 68 | 69 | return value; 70 | } 71 | 72 | public: 73 | /// 74 | /// Creates an instance of a PidController. 75 | /// 76 | /// The gain of the proportional term. 77 | /// The gain of the integral term. 78 | /// The gain of the derivative term. 79 | /// The maximum output. 80 | /// The minimum output. 81 | PidController(double gainProportion, double gainIntegral, double gainDerivative, double minOutput, double maxOutput) 82 | : gainProportion(gainProportion), gainIntegral(gainIntegral), gainDerivative(gainDerivative), 83 | minOutput(minOutput), maxOutput(maxOutput), prevError(0), prevOutput(0), integral(0) { } 84 | 85 | template int sgn(T val) { 86 | return (T(0) < val) - (val < T(0)); 87 | } 88 | 89 | /// 90 | /// Gets the output of the PID for a given error and timespan. 91 | /// 92 | /// The error vs the target value. 93 | /// The delta time vs the previous observation. 94 | /// The PID output. 95 | double GetOutput(double error, double deltaTime) 96 | { 97 | auto proportion = gainProportion * error; 98 | //if ((gainIntegral * integral) >= maxOutput) { 99 | //integral -= (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 100 | //} 101 | //else if ((gainIntegral * integral) <= minOutput) { 102 | integral += (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 103 | //} 104 | 105 | 106 | if (sgn(error) != sgn(prevError)) { 107 | integral = 0; 108 | } 109 | 110 | auto derivative = gainDerivative * ((error - prevError) / deltaTime); 111 | derivative = this->Clamp(derivative, 20.0, -20.0); 112 | 113 | auto output = this->Clamp(proportion + (gainIntegral * integral) + (derivative), this->maxOutput, this->minOutput); 114 | 115 | //printf("P: %.2f I: %.2f D %.2f \r\n", proportion, (gainIntegral * integral), (derivative)); 116 | 117 | prevError = error; 118 | prevOutput = output; 119 | 120 | return output; 121 | } 122 | }; 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /src/C414AW/SimConnectDefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SCDEFS 4 | #define SCDEFS 5 | 6 | #include "common.h" 7 | 8 | /// 9 | /// SimConnect client event IDs for the mixture group. 10 | /// 11 | enum MixtureEventIDs 12 | { 13 | AxisMixtureSet, 14 | AxisMixture1Set, 15 | AxisMixture2Set, 16 | MixtureSetBest, 17 | MixtureSet, 18 | MixtureRich, 19 | MixtureIncr, 20 | MixtureIncrSmall, 21 | MixtureDecrSmall, 22 | MixtureDecr, 23 | MixtureLean, 24 | Mixture1Set, 25 | Mixture1Rich, 26 | Mixture1Incr, 27 | Mixture1IncrSmall, 28 | Mixture1DecrSmall, 29 | Mixture1Decr, 30 | Mixture1Lean, 31 | Mixture2Set, 32 | Mixture2Rich, 33 | Mixture2Incr, 34 | Mixture2IncrSmall, 35 | Mixture2DecrSmall, 36 | Mixture2Decr, 37 | Mixture2Lean 38 | }; 39 | 40 | /// 41 | /// SimConnect event groups. 42 | /// 43 | enum EventGroups 44 | { 45 | /// 46 | /// The client event group ID to use when any events from the mixture axis group 47 | /// are received. 48 | /// 49 | Mixture = 0, 50 | }; 51 | 52 | /// 53 | /// SimConnect data types for sending the sim updates. 54 | /// 55 | enum DataTypes 56 | { 57 | /// 58 | /// The data type ID to use when sending engine controls data. 59 | /// 60 | EngineControls = 0 61 | }; 62 | /// 63 | /// Engine controls. 64 | /// 65 | struct EngineControlData 66 | { 67 | /// 68 | /// The mixture of the engine, expressed in a 100s base percent. 69 | /// 70 | double mixtureLeft; 71 | double mixtureRight; 72 | }; 73 | 74 | /// 75 | /// A collection of SimVar unit enums. 76 | /// 77 | class Units 78 | { 79 | public: 80 | /// 81 | /// The Percent SimVar unit. 82 | /// 83 | ENUM Percent = get_units_enum("Percent"); 84 | 85 | /// 86 | /// The Ratio SimVar unit. 87 | /// 88 | ENUM Ratio = get_units_enum("Ratio"); 89 | 90 | ENUM Celsius = get_units_enum("Celsius"); 91 | 92 | ENUM Number = get_units_enum("Number"); 93 | 94 | ENUM inHg = get_units_enum("inches of mercury"); 95 | }; 96 | 97 | 98 | /// 99 | /// A collection of SimVar enums. 100 | /// 101 | class SimVars 102 | { 103 | public: 104 | Units* m_Units; 105 | 106 | /// 107 | /// The GENERAL ENGINE MIXTURE LEVER POSITION SimVar. 108 | /// 109 | ENUM Mixture = get_aircraft_var_enum("GENERAL ENG MIXTURE LEVER POSITION"); 110 | 111 | ENUM FuelAirRatio = get_aircraft_var_enum("RECIP MIXTURE RATIO"); 112 | 113 | ENUM AmbientTemperature = get_aircraft_var_enum("AMBIENT TEMPERATURE"); 114 | 115 | ENUM AmbientPressure = get_aircraft_var_enum("AMBIENT PRESSURE"); 116 | 117 | ID FadecActive; 118 | 119 | /// 120 | /// The local variable for the visible mixture position 121 | /// 122 | ID MixturePos1; 123 | ID MixturePos2; 124 | 125 | SimVars() 126 | { 127 | this->initializeVars(); 128 | } 129 | 130 | void initializeVars() { 131 | FadecActive = register_named_variable("FADEC_ACTIVE"); 132 | MixturePos1 = register_named_variable("Mixture1_Pos"); 133 | MixturePos2 = register_named_variable("Mixture2_Pos"); 134 | m_Units = new Units(); 135 | } 136 | 137 | void setFadecActiveFlag() { 138 | set_named_variable_value(FadecActive, 1); 139 | } 140 | 141 | void setMixture1Pos(double value) { 142 | set_named_variable_value(MixturePos1, value); 143 | } 144 | 145 | void setMixture2Pos(double value) { 146 | set_named_variable_value(MixturePos2, value); 147 | } 148 | 149 | FLOAT64 getMixtureLeverPosition(int index) { 150 | return aircraft_varget(Mixture, m_Units->Percent, index); 151 | } 152 | 153 | FLOAT64 getFuelAirRatio(int index) { 154 | return aircraft_varget(FuelAirRatio, m_Units->Ratio, index); 155 | } 156 | 157 | FLOAT64 getAmbientTemperature() { 158 | return aircraft_varget(AmbientTemperature, m_Units->Celsius, 0); 159 | } 160 | 161 | FLOAT64 getAmbientPressure() { 162 | return aircraft_varget(AmbientPressure, m_Units->inHg, 0); 163 | } 164 | }; 165 | 166 | #endif 167 | -------------------------------------------------------------------------------- /src/C414AW/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _COMMON_H_ 4 | #define _COMMON_H_ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "SimConnectDefs.h" 16 | 17 | #include "turbocharger.h" 18 | #include 19 | #include 20 | 21 | 22 | /// 23 | /// The handle to the SimConnect instance. 24 | /// 25 | HANDLE hSimConnect; 26 | 27 | double clamp(double v, double lo, double hi) 28 | { 29 | assert(!(hi < lo)); 30 | return (v < lo) ? lo : (hi < v) ? hi : v; 31 | } 32 | 33 | 34 | #endif -------------------------------------------------------------------------------- /src/C414AW/turbocharger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef TURBOCHARGER 4 | #define TURBOCHARGER 5 | 6 | #include "common.h" 7 | 8 | class Turbocharger 9 | { 10 | public: 11 | static double getTargetFuelAirRatio(double mixturePercentage) { 12 | // The proportionality constant between Fuel/Air mixture and ambient specific volume measured in the sim: 13 | double fullRichSpecificVolumeConstant = 0.110236; 14 | double seaLevelSpecificVolume = 1.0 / 1.225; 15 | // Slope of the relationship between mixture lever position (in percent) and the ratio of fuel/air mixture to ambient specific volume -- measured from sim data. 16 | double slope_90_100 = 0.000487531; 17 | double slope_75_90 = 0.000243697; 18 | double slope_60_75 = 0.000409167; 19 | double slope_20_60 = 0.000954534; 20 | 21 | if (mixturePercentage == 100.0) { 22 | double targetFuelAirRatio = seaLevelSpecificVolume * fullRichSpecificVolumeConstant; 23 | return targetFuelAirRatio; 24 | } 25 | else if (mixturePercentage >= 90.0) { 26 | double targetFuelAirRatio = seaLevelSpecificVolume * (fullRichSpecificVolumeConstant + (mixturePercentage - 100.0) * slope_90_100); 27 | return targetFuelAirRatio; 28 | } 29 | else if (mixturePercentage >= 75.0) { 30 | double targetFuelAirRatio = seaLevelSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (mixturePercentage - 90.0) * slope_75_90); 31 | return targetFuelAirRatio; 32 | } 33 | else if (mixturePercentage >= 60.0) { 34 | double targetFuelAirRatio = seaLevelSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (mixturePercentage - 75.0) * slope_60_75); 35 | return targetFuelAirRatio; 36 | } 37 | else if (mixturePercentage >= 20.0) { 38 | double targetFuelAirRatio = seaLevelSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (mixturePercentage - 60.0) * slope_20_60); 39 | return targetFuelAirRatio; 40 | } 41 | else { 42 | double slope_0_20 = (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60) / 20.0; 43 | double targetFuelAirRatio = seaLevelSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60 + (mixturePercentage - 20.0) * slope_0_20); 44 | return targetFuelAirRatio; 45 | } 46 | } 47 | }; 48 | 49 | #endif // !TURBOCHARGER 50 | -------------------------------------------------------------------------------- /src/SenecaV/FdController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef FDCTRL 4 | #define FDCTRL 5 | 6 | #include "common.h" 7 | #include "PidController.h" 8 | 9 | class FdController 10 | { 11 | private: 12 | SimVars* simVars; 13 | 14 | /// 15 | /// An instance of the mixture PID controller. 16 | /// 17 | PidController* mixtureController[2]; 18 | 19 | /// 20 | /// The current mixture control axis, from -16384 to 16384. 21 | /// 22 | int mixtureAxis[2] = { 16384, 16384 }; 23 | 24 | bool enabled = true; 25 | 26 | /// 27 | /// Calculates and updates mixture according to target and PIDs. 28 | /// 29 | /// 30 | void updateMixture(double deltaTime) { 31 | EngineControlData controls; 32 | controls.mixtureLeft = this->getDesiredMixture(0, deltaTime); 33 | controls.mixtureRight = this->getDesiredMixture(1, deltaTime); 34 | SimConnect_SetDataOnSimObject(hSimConnect, DataTypes::EngineControls, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(EngineControlData), &controls); 35 | } 36 | 37 | /// 38 | /// Gets the mixture lever position to achieve the desired fuel/air mixture ratio 39 | /// 40 | /// Desired mixture setting 41 | double getDesiredMixture(int idx, double deltaTime) { 42 | double mixtureLeverPerc = 100.0 * (this->mixtureAxis[idx] + 16384) / 32768.0; 43 | 44 | if (!enabled) { 45 | return mixtureLeverPerc; 46 | } 47 | 48 | double targetFuelAirMixture = Turbocharger::getTargetFuelAirRatio(this->simVars->getAmbientTemperature(), this->simVars->getAmbientPressure(), mixtureLeverPerc); 49 | double simFuelAirMixture = this->simVars->getFuelAirRatio(idx + 1); 50 | //printf("Target Mixture: %1.3f\nSim Mixture: %1.3f\n", targetFuelAirMixture, simFuelAirMixture); 51 | 52 | double error = targetFuelAirMixture - simFuelAirMixture; 53 | double pidOut = 0.0; 54 | pidOut = this->mixtureController[idx]->GetOutput(error, deltaTime); 55 | //printf("Output from PID Controller: %2.3f\n", error); 56 | return max(0, min(100, this->simVars->getMixtureLeverPosition(idx + 1) + pidOut)); 57 | } 58 | 59 | void updateVisibleMixture(int idx) { 60 | int targetMixture = this->mixtureAxis[idx]; 61 | double mixtureLeverPerc = 100.0 * (targetMixture + 16384) / 32768.0; 62 | if (idx == 0) { 63 | this->simVars->setMixture1Pos(mixtureLeverPerc); 64 | } 65 | else { 66 | this->simVars->setMixture2Pos(mixtureLeverPerc); 67 | } 68 | } 69 | 70 | public: 71 | void init() 72 | { 73 | printf("FdController init"); 74 | 75 | this->simVars = new SimVars(); 76 | 77 | float p = 350.0; 78 | float i = 380.0; 79 | float d = 0.0; 80 | this->mixtureController[0] = new PidController(p, i, d, -5, 5); 81 | this->mixtureController[1] = new PidController(p, i, d, -5, 5); 82 | } 83 | 84 | void update(int mixtureAxis[], double deltaTime) 85 | { 86 | this->mixtureAxis[0] = mixtureAxis[0]; 87 | this->mixtureAxis[1] = mixtureAxis[1]; 88 | this->updateMixture(deltaTime); 89 | this->updateVisibleMixture(0); 90 | this->updateVisibleMixture(1); 91 | this->simVars->setFadecActiveFlag(); 92 | } 93 | }; 94 | 95 | FdController FdCtrlInstance; 96 | 97 | #endif // !FDCTRL 98 | -------------------------------------------------------------------------------- /src/SenecaV/FdGauge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifndef FDGAUGE 5 | #define FDGAUGE 6 | 7 | #ifndef __INTELLISENSE__ 8 | # define MODULE_EXPORT __attribute__( ( visibility( "default" ) ) ) 9 | # define MODULE_WASM_MODNAME(mod) __attribute__((import_module(mod))) 10 | #else 11 | # define MODULE_EXPORT 12 | # define MODULE_WASM_MODNAME(mod) 13 | # define __attribute__(x) 14 | # define __restrict__ 15 | #endif 16 | 17 | #include "common.h" 18 | #include "FdController.h" 19 | 20 | const int MIN_MIX = -16384; 21 | const int MAX_MIX = 16384; 22 | const int MIX_STEP = 256; 23 | 24 | int globalMixtureAxis[2] = { MAX_MIX, MAX_MIX }; 25 | 26 | class FdGauge 27 | { 28 | private: 29 | 30 | bool isConnected = false; 31 | 32 | /// 33 | /// Registers all the mixture SimConnect client events. 34 | /// 35 | void RegisterMixtureClientEvents() 36 | { 37 | printf("Registering mixture events...\r\n"); 38 | 39 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixtureSet, "AXIS_MIXTURE_SET"); 40 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixture1Set, "AXIS_MIXTURE1_SET"); 41 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixture2Set, "AXIS_MIXTURE2_SET"); 42 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSetBest, "MIXTURE_SET_BEST"); 43 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSet, "MIXTURE_SET"); 44 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureRich, "MIXTURE_RICH"); 45 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncr, "MIXTURE_INCR"); 46 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncrSmall, "MIXTURE_INCR_SMALL"); 47 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecrSmall, "MIXTURE_DECR_SMALL"); 48 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecr, "MIXTURE_DECR"); 49 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureLean, "MIXTURE_LEAN"); 50 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Set, "MIXTURE1_SET"); 51 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Rich, "MIXTURE1_RICH"); 52 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Incr, "MIXTURE1_INCR"); 53 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1IncrSmall, "MIXTURE1_INCR_SMALL"); 54 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1DecrSmall, "MIXTURE1_DECR_SMALL"); 55 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Decr, "MIXTURE1_DECR"); 56 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Lean, "MIXTURE1_LEAN"); 57 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Set, "MIXTURE2_SET"); 58 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Rich, "MIXTURE2_RICH"); 59 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Incr, "MIXTURE2_INCR"); 60 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2IncrSmall, "MIXTURE2_INCR_SMALL"); 61 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2DecrSmall, "MIXTURE2_DECR_SMALL"); 62 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Decr, "MIXTURE2_DECR"); 63 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture2Lean, "MIXTURE2_LEAN"); 64 | } 65 | 66 | /// 67 | /// Registers the SimConnect mixture event group for capture. 68 | /// 69 | void RegisterMixtureEventGroup() 70 | { 71 | printf("Registering mixture event group...\r\n"); 72 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixtureSet, TRUE); 73 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixture1Set, TRUE); 74 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixture2Set, TRUE); 75 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSetBest, TRUE); 76 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSet, TRUE); 77 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureRich, TRUE); 78 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncr, TRUE); 79 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncrSmall, TRUE); 80 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecrSmall, TRUE); 81 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecr, TRUE); 82 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureLean, TRUE); 83 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Set, TRUE); 84 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Rich, TRUE); 85 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Incr, TRUE); 86 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1IncrSmall, TRUE); 87 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1DecrSmall, TRUE); 88 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Decr, TRUE); 89 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Lean, TRUE); 90 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Set, TRUE); 91 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Rich, TRUE); 92 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Incr, TRUE); 93 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2IncrSmall, TRUE); 94 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2DecrSmall, TRUE); 95 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Decr, TRUE); 96 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture2Lean, TRUE); 97 | 98 | SimConnect_SetNotificationGroupPriority(hSimConnect, EventGroups::Mixture, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE); 99 | } 100 | 101 | /// 102 | /// Initializes the connection to SimConnect. 103 | /// 104 | /// True if successful, false otherwise. 105 | bool InitializeSimConnect() 106 | { 107 | printf("Connecting to SimConnect...\r\n"); 108 | if (SUCCEEDED(SimConnect_Open(&hSimConnect, "FdGauge", nullptr, 0, 0, 0))) 109 | { 110 | printf("SimConnect connected.\r\n"); 111 | 112 | this->RegisterMixtureClientEvents(); 113 | this->RegisterMixtureEventGroup(); 114 | 115 | SimConnect_AddToDataDefinition(hSimConnect, DataTypes::EngineControls, "GENERAL ENG MIXTURE LEVER POSITION:1", "Percent"); 116 | SimConnect_AddToDataDefinition(hSimConnect, DataTypes::EngineControls, "GENERAL ENG MIXTURE LEVER POSITION:2", "Percent"); 117 | 118 | printf("SimConnect registrations complete.\r\n"); 119 | return true; 120 | } 121 | 122 | printf("SimConnect failed.\r\n"); 123 | 124 | return false; 125 | } 126 | 127 | /// 128 | /// A callback used for handling SimConnect updates. 129 | /// 130 | /// The update data sent by SimConnect. 131 | /// The size of the SimConnect data structure. 132 | /// A pointer specified by the client. 133 | static void CALLBACK HandleAxisEvent(SIMCONNECT_RECV* pData, DWORD cbData, void* pContext) 134 | { 135 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EVENT) 136 | { 137 | SIMCONNECT_RECV_EVENT* evt = static_cast(pData); 138 | if (evt->uGroupID == EventGroups::Mixture) 139 | { 140 | FdGauge* fd = static_cast(pContext); 141 | if (fd == 0) 142 | { 143 | printf("FD pointer was null processing SimConnect event.\r\n"); 144 | } 145 | else 146 | { 147 | HandleMixtureAxis(evt); 148 | } 149 | } 150 | } 151 | 152 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EXCEPTION) 153 | { 154 | SIMCONNECT_RECV_EXCEPTION* ex = static_cast(pData); 155 | printf("SimConnect Exception: %d \r\n", ex->dwException); 156 | } 157 | } 158 | 159 | /// 160 | /// Handles throttle axis updates received from SimConnect. 161 | /// 162 | /// A pointer to the SimConnect event structure. 163 | static void HandleMixtureAxis(SIMCONNECT_RECV_EVENT* evt) 164 | { 165 | switch (evt->uEventID) 166 | { 167 | case MixtureEventIDs::AxisMixtureSet: 168 | globalMixtureAxis[0] = static_cast(evt->dwData); 169 | globalMixtureAxis[1] = static_cast(evt->dwData); 170 | break; 171 | case MixtureEventIDs::AxisMixture1Set: 172 | globalMixtureAxis[0] = static_cast(evt->dwData); 173 | break; 174 | case MixtureEventIDs::AxisMixture2Set: 175 | globalMixtureAxis[1] = static_cast(evt->dwData); 176 | break; 177 | case MixtureEventIDs::MixtureSetBest: 178 | case MixtureEventIDs::MixtureRich: 179 | globalMixtureAxis[0] = MAX_MIX; 180 | globalMixtureAxis[1] = MAX_MIX; 181 | break; 182 | case MixtureEventIDs::Mixture1Rich: 183 | globalMixtureAxis[0] = MAX_MIX; 184 | break; 185 | case MixtureEventIDs::Mixture2Rich: 186 | globalMixtureAxis[1] = MAX_MIX; 187 | break; 188 | case MixtureEventIDs::MixtureSet: 189 | globalMixtureAxis[0] = (static_cast(evt->dwData) * 2) - MAX_MIX; 190 | globalMixtureAxis[1] = (static_cast(evt->dwData) * 2) - MAX_MIX; 191 | break; 192 | case MixtureEventIDs::Mixture1Set: 193 | globalMixtureAxis[0] = (static_cast(evt->dwData) * 2) - MAX_MIX; 194 | break; 195 | case MixtureEventIDs::Mixture2Set: 196 | globalMixtureAxis[1] = (static_cast(evt->dwData) * 2) - MAX_MIX; 197 | break; 198 | case MixtureEventIDs::MixtureLean: 199 | globalMixtureAxis[0] = MIN_MIX; 200 | globalMixtureAxis[1] = MIN_MIX; 201 | break; 202 | case MixtureEventIDs::Mixture1Lean: 203 | globalMixtureAxis[0] = MIN_MIX; 204 | break; 205 | case MixtureEventIDs::Mixture2Lean: 206 | globalMixtureAxis[1] = MIN_MIX; 207 | break; 208 | case MixtureEventIDs::MixtureIncr: 209 | case MixtureEventIDs::MixtureIncrSmall: 210 | globalMixtureAxis[0] += MIX_STEP; // TODO: CLAMP ALL INCR/DECR EVENTS 211 | globalMixtureAxis[1] += MIX_STEP; 212 | break; 213 | case MixtureEventIDs::Mixture1Incr: 214 | case MixtureEventIDs::Mixture1IncrSmall: 215 | globalMixtureAxis[0] += MIX_STEP; 216 | break; 217 | case MixtureEventIDs::Mixture2Incr: 218 | case MixtureEventIDs::Mixture2IncrSmall: 219 | globalMixtureAxis[1] += MIX_STEP; 220 | break; 221 | case MixtureEventIDs::MixtureDecr: 222 | case MixtureEventIDs::MixtureDecrSmall: 223 | globalMixtureAxis[0] -= MIX_STEP; 224 | globalMixtureAxis[1] -= MIX_STEP; 225 | break; 226 | case MixtureEventIDs::Mixture1Decr: 227 | case MixtureEventIDs::Mixture1DecrSmall: 228 | globalMixtureAxis[0] -= MIX_STEP; 229 | break; 230 | case MixtureEventIDs::Mixture2Decr: 231 | case MixtureEventIDs::Mixture2DecrSmall: 232 | globalMixtureAxis[1] -= MIX_STEP; 233 | break; 234 | } 235 | 236 | globalMixtureAxis[0] = clamp(globalMixtureAxis[0], MIN_MIX, MAX_MIX); 237 | globalMixtureAxis[1] = clamp(globalMixtureAxis[1], MIN_MIX, MAX_MIX); 238 | } 239 | 240 | public: 241 | 242 | /// 243 | /// Initializes the FD. 244 | /// 245 | /// True if successful, false otherwise. 246 | bool InitializeFD() 247 | { 248 | if (!this->InitializeSimConnect()) { 249 | printf("Init SimConnect failed"); 250 | return false; 251 | } 252 | 253 | FdCtrlInstance.init(); 254 | isConnected = true; 255 | SimConnect_CallDispatch(hSimConnect, HandleAxisEvent, this); 256 | 257 | return true; 258 | } 259 | 260 | /// 261 | /// A callback used to update the FD at each tick. 262 | /// 263 | /// The time since the previous update. 264 | /// True if successful, false otherwise. 265 | bool OnUpdate(double deltaTime) 266 | { 267 | if (isConnected == true) { 268 | FdCtrlInstance.update(globalMixtureAxis, deltaTime); 269 | } 270 | 271 | return true; 272 | } 273 | 274 | /// 275 | /// Kill. 276 | /// 277 | /// True if succesful, false otherwise. 278 | bool KillFD() 279 | { 280 | isConnected = false; 281 | unregister_all_named_vars(); 282 | return SUCCEEDED(SimConnect_Close(hSimConnect)); 283 | } 284 | }; 285 | 286 | #endif // !FDGAUGE 287 | -------------------------------------------------------------------------------- /src/SenecaV/Mixture_Controller.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31410.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mixture_Controller", "Mixture_Controller.vcxproj", "{A5468B35-BBBD-4C55-97ED-81BFE343B0E4}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|MSFS = Debug|MSFS 11 | Release|MSFS = Release|MSFS 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.ActiveCfg = Debug|MSFS 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.Build.0 = Debug|MSFS 16 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.ActiveCfg = Release|MSFS 17 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.Build.0 = Release|MSFS 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9129C4E4-B566-46E3-81AE-74CCDE9CA599} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/SenecaV/Mixture_Controller.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | MSFS 7 | 8 | 9 | Release 10 | MSFS 11 | 12 | 13 | 14 | 16.0 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4} 16 | Module 17 | 10.0 18 | SenecaV_Mixture_Controller 19 | 20 | 21 | 22 | Application 23 | true 24 | MSFS 25 | MultiByte 26 | 27 | 28 | Application 29 | false 30 | MSFS 31 | true 32 | MultiByte 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | .wasm 48 | 49 | 50 | $(MSFS_IncludePath) 51 | 52 | 53 | .wasm 54 | $(MSFS_IncludePath) 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | false 67 | 68 | 69 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 70 | false 71 | 72 | 73 | 74 | 75 | 76 | 77 | ProgramDatabase 78 | 79 | 80 | stdcpp14 81 | 82 | 83 | 84 | 85 | %(AdditionalDependencies) 86 | 87 | 88 | true 89 | $(OutDir)$(TargetName)$(TargetExt) 90 | 91 | 92 | 93 | 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | $(OutDir) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | 117 | 118 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 119 | false 120 | false 121 | false 122 | 123 | 124 | 125 | 126 | 127 | 128 | %(AdditionalDependencies) 129 | 130 | 131 | true 132 | $(OutDir)$(TargetName)$(TargetExt) 133 | 134 | 135 | 136 | 137 | false 138 | 139 | 140 | 141 | 142 | 143 | 144 | $(OutDir) 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/SenecaV/Mixture_Controller.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/SenecaV/PidController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef PIDC 4 | #define PIDC 5 | 6 | /// 7 | /// A class for controlling values via a PID. 8 | /// 9 | class PidController 10 | { 11 | private: 12 | /// 13 | /// The gain of the proportional term. 14 | /// 15 | double gainProportion; 16 | 17 | /// 18 | /// The gain of the integral term. 19 | /// 20 | double gainIntegral; 21 | 22 | /// 23 | /// The gain of the derivative term. 24 | /// 25 | double gainDerivative; 26 | 27 | /// 28 | /// The minimum output allowed. 29 | /// 30 | double minOutput; 31 | 32 | /// 33 | /// The maximum output allowed. 34 | /// 35 | double maxOutput; 36 | 37 | /// 38 | /// The previous error amount. 39 | /// 40 | double prevError; 41 | 42 | /// 43 | /// The previous PID output. 44 | /// 45 | double prevOutput; 46 | 47 | /// 48 | /// The current integral term. 49 | /// 50 | double integral; 51 | 52 | /// 53 | /// Clamps a value to the PID min and max outputs. 54 | /// 55 | /// The value to clamp. 56 | /// The clamped value. 57 | double Clamp(double value, double max, double min) 58 | { 59 | if (value > max) 60 | { 61 | return max; 62 | } 63 | 64 | if (value < min) 65 | { 66 | return min; 67 | } 68 | 69 | return value; 70 | } 71 | 72 | public: 73 | /// 74 | /// Creates an instance of a PidController. 75 | /// 76 | /// The gain of the proportional term. 77 | /// The gain of the integral term. 78 | /// The gain of the derivative term. 79 | /// The maximum output. 80 | /// The minimum output. 81 | PidController(double gainProportion, double gainIntegral, double gainDerivative, double minOutput, double maxOutput) 82 | : gainProportion(gainProportion), gainIntegral(gainIntegral), gainDerivative(gainDerivative), 83 | minOutput(minOutput), maxOutput(maxOutput), prevError(0), prevOutput(0), integral(0) { } 84 | 85 | template int sgn(T val) { 86 | return (T(0) < val) - (val < T(0)); 87 | } 88 | 89 | /// 90 | /// Gets the output of the PID for a given error and timespan. 91 | /// 92 | /// The error vs the target value. 93 | /// The delta time vs the previous observation. 94 | /// The PID output. 95 | double GetOutput(double error, double deltaTime) 96 | { 97 | auto proportion = gainProportion * error; 98 | //if ((gainIntegral * integral) >= maxOutput) { 99 | //integral -= (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 100 | //} 101 | //else if ((gainIntegral * integral) <= minOutput) { 102 | integral += (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 103 | //} 104 | 105 | 106 | if (sgn(error) != sgn(prevError)) { 107 | integral = 0; 108 | } 109 | 110 | auto derivative = gainDerivative * ((error - prevError) / deltaTime); 111 | derivative = this->Clamp(derivative, 20.0, -20.0); 112 | 113 | auto output = this->Clamp(proportion + (gainIntegral * integral) + (derivative), this->maxOutput, this->minOutput); 114 | 115 | //printf("P: %.2f I: %.2f D %.2f \r\n", proportion, (gainIntegral * integral), (derivative)); 116 | 117 | prevError = error; 118 | prevOutput = output; 119 | 120 | return output; 121 | } 122 | }; 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /src/SenecaV/SenecaV_Mixture_Controller.cpp: -------------------------------------------------------------------------------- 1 |  2 | #ifdef _MSC_VER 3 | #define snprintf _snprintf_s 4 | #elif !defined(__MINGW32__) 5 | #include 6 | #endif 7 | 8 | #include "FdGauge.h" 9 | 10 | FdGauge FD_GAUGE; 11 | 12 | 13 | // ------------------------ 14 | // Callbacks 15 | extern "C" { 16 | 17 | MSFS_CALLBACK bool FdGauge_gauge_callback(FsContext ctx, int service_id, void* pData) 18 | { 19 | switch (service_id) 20 | { 21 | case PANEL_SERVICE_PRE_INSTALL: 22 | { 23 | return true; 24 | } 25 | break; 26 | case PANEL_SERVICE_POST_INSTALL: 27 | { 28 | return FD_GAUGE.InitializeFD(); 29 | } 30 | break; 31 | case PANEL_SERVICE_PRE_DRAW: 32 | { 33 | sGaugeDrawData* drawData = static_cast(pData); 34 | return FD_GAUGE.OnUpdate(drawData->dt); 35 | } 36 | break; 37 | case PANEL_SERVICE_PRE_KILL: 38 | { 39 | FD_GAUGE.KillFD(); 40 | return true; 41 | } 42 | break; 43 | } 44 | return false; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/SenecaV/SimConnectDefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SCDEFS 4 | #define SCDEFS 5 | 6 | #include "common.h" 7 | 8 | /// 9 | /// SimConnect client event IDs for the mixture group. 10 | /// 11 | enum MixtureEventIDs 12 | { 13 | AxisMixtureSet, 14 | AxisMixture1Set, 15 | AxisMixture2Set, 16 | MixtureSetBest, 17 | MixtureSet, 18 | MixtureRich, 19 | MixtureIncr, 20 | MixtureIncrSmall, 21 | MixtureDecrSmall, 22 | MixtureDecr, 23 | MixtureLean, 24 | Mixture1Set, 25 | Mixture1Rich, 26 | Mixture1Incr, 27 | Mixture1IncrSmall, 28 | Mixture1DecrSmall, 29 | Mixture1Decr, 30 | Mixture1Lean, 31 | Mixture2Set, 32 | Mixture2Rich, 33 | Mixture2Incr, 34 | Mixture2IncrSmall, 35 | Mixture2DecrSmall, 36 | Mixture2Decr, 37 | Mixture2Lean 38 | }; 39 | 40 | /// 41 | /// SimConnect event groups. 42 | /// 43 | enum EventGroups 44 | { 45 | /// 46 | /// The client event group ID to use when any events from the mixture axis group 47 | /// are received. 48 | /// 49 | Mixture = 0, 50 | }; 51 | 52 | /// 53 | /// SimConnect data types for sending the sim updates. 54 | /// 55 | enum DataTypes 56 | { 57 | /// 58 | /// The data type ID to use when sending engine controls data. 59 | /// 60 | EngineControls = 0 61 | }; 62 | /// 63 | /// Engine controls. 64 | /// 65 | struct EngineControlData 66 | { 67 | /// 68 | /// The mixture of the engine, expressed in a 100s base percent. 69 | /// 70 | double mixtureLeft; 71 | double mixtureRight; 72 | }; 73 | 74 | /// 75 | /// A collection of SimVar unit enums. 76 | /// 77 | class Units 78 | { 79 | public: 80 | /// 81 | /// The Percent SimVar unit. 82 | /// 83 | ENUM Percent = get_units_enum("Percent"); 84 | 85 | /// 86 | /// The Ratio SimVar unit. 87 | /// 88 | ENUM Ratio = get_units_enum("Ratio"); 89 | 90 | ENUM Celsius = get_units_enum("Celsius"); 91 | 92 | ENUM Number = get_units_enum("Number"); 93 | 94 | ENUM inHg = get_units_enum("inches of mercury"); 95 | }; 96 | 97 | 98 | /// 99 | /// A collection of SimVar enums. 100 | /// 101 | class SimVars 102 | { 103 | public: 104 | Units* m_Units; 105 | 106 | /// 107 | /// The GENERAL ENGINE MIXTURE LEVER POSITION SimVar. 108 | /// 109 | ENUM Mixture = get_aircraft_var_enum("GENERAL ENG MIXTURE LEVER POSITION"); 110 | 111 | ENUM FuelAirRatio = get_aircraft_var_enum("RECIP MIXTURE RATIO"); 112 | 113 | ENUM AmbientTemperature = get_aircraft_var_enum("AMBIENT TEMPERATURE"); 114 | 115 | ENUM AmbientPressure = get_aircraft_var_enum("AMBIENT PRESSURE"); 116 | 117 | ID FadecActive; 118 | 119 | /// 120 | /// The local variable for the visible mixture position 121 | /// 122 | ID MixturePos1; 123 | ID MixturePos2; 124 | 125 | SimVars() 126 | { 127 | this->initializeVars(); 128 | } 129 | 130 | void initializeVars() { 131 | FadecActive = register_named_variable("FADEC_ACTIVE"); 132 | MixturePos1 = register_named_variable("Mixture1_Pos"); 133 | MixturePos2 = register_named_variable("Mixture2_Pos"); 134 | m_Units = new Units(); 135 | } 136 | 137 | void setFadecActiveFlag() { 138 | set_named_variable_value(FadecActive, 1); 139 | } 140 | 141 | void setMixture1Pos(double value) { 142 | set_named_variable_value(MixturePos1, value); 143 | } 144 | 145 | void setMixture2Pos(double value) { 146 | set_named_variable_value(MixturePos2, value); 147 | } 148 | 149 | FLOAT64 getMixtureLeverPosition(int index) { 150 | return aircraft_varget(Mixture, m_Units->Percent, index); 151 | } 152 | 153 | FLOAT64 getFuelAirRatio(int index) { 154 | return aircraft_varget(FuelAirRatio, m_Units->Ratio, index); 155 | } 156 | 157 | FLOAT64 getAmbientTemperature() { 158 | return aircraft_varget(AmbientTemperature, m_Units->Celsius, 0); 159 | } 160 | 161 | FLOAT64 getAmbientPressure() { 162 | return aircraft_varget(AmbientPressure, m_Units->inHg, 0); 163 | } 164 | }; 165 | 166 | #endif 167 | -------------------------------------------------------------------------------- /src/SenecaV/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _COMMON_H_ 4 | #define _COMMON_H_ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "SimConnectDefs.h" 16 | 17 | #include "turbocharger.h" 18 | #include 19 | #include 20 | 21 | 22 | /// 23 | /// The handle to the SimConnect instance. 24 | /// 25 | HANDLE hSimConnect; 26 | 27 | double clamp(double v, double lo, double hi) 28 | { 29 | assert(!(hi < lo)); 30 | return (v < lo) ? lo : (hi < v) ? hi : v; 31 | } 32 | 33 | 34 | #endif -------------------------------------------------------------------------------- /src/SenecaV/turbocharger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef TURBOCHARGER 4 | #define TURBOCHARGER 5 | 6 | #include "common.h" 7 | 8 | class Turbocharger 9 | { 10 | public: 11 | static double getTargetFuelAirRatio(FLOAT64 ambientTemperature, FLOAT64 ambientPressure, double mixturePercentage) { 12 | double maxPowerManifoldPressure = 38.0; 13 | double criticalAltitude = 19000.0; 14 | double icaoStandardPressureAtCriticalAltitude = 14.3532959; 15 | double maxTurboBoostPressure = maxPowerManifoldPressure - icaoStandardPressureAtCriticalAltitude; 16 | // The proportionality constant between Fuel/Air mixture and ambient specific volume measured in the sim: 17 | double fullRichSpecificVolumeConstant = 0.110236; 18 | // The ratio between ICAO standard atmosphere specific volume at sea-level and the specific volume of air in the intake manifold (at maximum safe power setting -- calculated using the adiabatic process equation) 19 | double turboBoostCorrectionFactor = 1.18614538; 20 | double turboSpecificVolumeConstant = fullRichSpecificVolumeConstant * turboBoostCorrectionFactor; 21 | // Slope of the relationship between mixture lever position (in percent) and the ratio of fuel/air mixture to ambient specific volume -- measured from sim data. 22 | double slope_90_100 = 0.000487531; 23 | double slope_75_90 = 0.000243697; 24 | double slope_60_75 = 0.000409167; 25 | double slope_20_60 = 0.000954534; 26 | double ambientPressurePa = ambientPressure * 3386.39; 27 | double maxManifoldPressurePa = maxPowerManifoldPressure * 3386.39; 28 | double ambientTemperatureK = ambientTemperature + 273.15; 29 | double maxManifoldDensity = 0.0; 30 | if (ambientPressure >= icaoStandardPressureAtCriticalAltitude) { 31 | maxManifoldDensity = maxManifoldPressurePa / (287.058 * pow(pow(ambientPressurePa, (1.0 - 1.4)) * pow(ambientTemperatureK, 1.4) / pow(maxManifoldPressurePa, (1.0 - 1.4)), (1.0 / 1.40))); 32 | } 33 | else { 34 | maxManifoldDensity = ((ambientPressure + maxTurboBoostPressure) * 3386.39) / (287.058 * pow(pow(ambientPressurePa, (1.0 - 1.4)) * pow(ambientTemperatureK, 1.4) / pow(((ambientPressure + maxTurboBoostPressure) * 3386.39), (1.0 - 1.4)), (1.0 / 1.40))); 35 | } 36 | double fullRichSpecificVolume = 1.0 / maxManifoldDensity; 37 | if (mixturePercentage == 100.0) { 38 | double targetFuelAirRatio = fullRichSpecificVolume * turboSpecificVolumeConstant; 39 | return targetFuelAirRatio; 40 | } 41 | else if (mixturePercentage >= 90.0) { 42 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (mixturePercentage - 100.0) * slope_90_100); 43 | return targetFuelAirRatio; 44 | } 45 | else if (mixturePercentage >= 75.0) { 46 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (mixturePercentage - 90.0) * slope_75_90); 47 | return targetFuelAirRatio; 48 | } 49 | else if (mixturePercentage >= 60.0) { 50 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (mixturePercentage - 75.0) * slope_60_75); 51 | return targetFuelAirRatio; 52 | } 53 | else if (mixturePercentage >= 20.0) { 54 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (mixturePercentage - 60.0) * slope_20_60); 55 | return targetFuelAirRatio; 56 | } 57 | else { 58 | double slope_0_20 = (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60) / 20.0; 59 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60 + (mixturePercentage - 20.0) * slope_0_20); 60 | return targetFuelAirRatio; 61 | } 62 | } 63 | }; 64 | 65 | #endif // !TURBOCHARGER 66 | -------------------------------------------------------------------------------- /src/TurboArrow/FdController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef FDCTRL 4 | #define FDCTRL 5 | 6 | #include "common.h" 7 | #include "PidController.h" 8 | 9 | class FdController 10 | { 11 | private: 12 | SimVars* simVars; 13 | 14 | /// 15 | /// An instance of the mixture PID controller. 16 | /// 17 | PidController* mixtureController; 18 | 19 | /// 20 | /// The current mixture control axis, from -16384 to 16384. 21 | /// 22 | int mixtureAxis = 16384; 23 | 24 | bool enabled = true; 25 | 26 | /// 27 | /// Calculates and updates mixture according to target and PIDs. 28 | /// 29 | /// 30 | void updateMixture(double deltaTime) { 31 | EngineControlData controls; 32 | controls.mixtureone = this->getDesiredMixture(deltaTime); 33 | SimConnect_SetDataOnSimObject(hSimConnect, DataTypes::EngineControls, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(EngineControlData), &controls); 34 | } 35 | 36 | /// 37 | /// Gets the mixture lever position to achieve the desired fuel/air mixture ratio 38 | /// 39 | /// Desired mixture setting 40 | double getDesiredMixture(double deltaTime) { 41 | double mixtureLeverPerc = 100.0 * (this->mixtureAxis + 16384) / 32768.0; 42 | 43 | if (!enabled) { 44 | return mixtureLeverPerc; 45 | } 46 | 47 | double targetFuelAirMixture = Turbocharger::getTargetFuelAirRatio(this->simVars->getAmbientTemperature(), this->simVars->getAmbientPressure(), mixtureLeverPerc); 48 | double simFuelAirMixture = this->simVars->getFuelAirRatio(1); 49 | printf("Target Mixture: %1.3f\nSim Mixture: %1.3f\n", targetFuelAirMixture, simFuelAirMixture); 50 | 51 | double error = targetFuelAirMixture - simFuelAirMixture; 52 | double pidOut = 0.0; 53 | pidOut = this->mixtureController->GetOutput(error, deltaTime); 54 | printf("Output from PID Controller: %2.3f\n", error); 55 | return max(0, min(100, this->simVars->getMixtureLeverPosition(1) + pidOut)); 56 | } 57 | 58 | void updateVisibleMixture() { 59 | int targetMixture = this->mixtureAxis; 60 | double mixtureLeverPerc = 100.0 * (targetMixture + 16384) / 32768.0; 61 | this->simVars->setMixturePos(mixtureLeverPerc); 62 | } 63 | 64 | public: 65 | void init() 66 | { 67 | printf("FdController init"); 68 | 69 | this->simVars = new SimVars(); 70 | 71 | float p = 350.0; 72 | float i = 380.0; 73 | float d = 0.0; 74 | this->mixtureController = new PidController(p, i, d, -5, 5); 75 | } 76 | 77 | void update(int mixtureAxis, double deltaTime) 78 | { 79 | this->mixtureAxis = mixtureAxis; 80 | this->updateMixture(deltaTime); 81 | this->updateVisibleMixture(); 82 | this->simVars->setFadecActiveFlag(); 83 | } 84 | }; 85 | 86 | FdController FdCtrlInstance; 87 | 88 | #endif // !FDCTRL 89 | -------------------------------------------------------------------------------- /src/TurboArrow/FdGauge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifndef FDGAUGE 5 | #define FDGAUGE 6 | 7 | #ifndef __INTELLISENSE__ 8 | # define MODULE_EXPORT __attribute__( ( visibility( "default" ) ) ) 9 | # define MODULE_WASM_MODNAME(mod) __attribute__((import_module(mod))) 10 | #else 11 | # define MODULE_EXPORT 12 | # define MODULE_WASM_MODNAME(mod) 13 | # define __attribute__(x) 14 | # define __restrict__ 15 | #endif 16 | 17 | #include "common.h" 18 | #include "FdController.h" 19 | 20 | const int MIN_MIX = -16384; 21 | const int MAX_MIX = 16384; 22 | const int MIX_STEP = 256; 23 | 24 | int globalMixtureAxis = MAX_MIX; 25 | 26 | class FdGauge 27 | { 28 | private: 29 | 30 | bool isConnected = false; 31 | 32 | /// 33 | /// Registers all the mixture SimConnect client events. 34 | /// 35 | void RegisterMixtureClientEvents() 36 | { 37 | printf("Registering mixture events...\r\n"); 38 | 39 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixtureSet, "AXIS_MIXTURE_SET"); 40 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixture1Set, "AXIS_MIXTURE1_SET"); 41 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSetBest, "MIXTURE_SET_BEST"); 42 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSet, "MIXTURE_SET"); 43 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureRich, "MIXTURE_RICH"); 44 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncr, "MIXTURE_INCR"); 45 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncrSmall, "MIXTURE_INCR_SMALL"); 46 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecrSmall, "MIXTURE_DECR_SMALL"); 47 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecr, "MIXTURE_DECR"); 48 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureLean, "MIXTURE_LEAN"); 49 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Set, "MIXTURE1_SET"); 50 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Rich, "MIXTURE1_RICH"); 51 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Incr, "MIXTURE1_INCR"); 52 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1IncrSmall, "MIXTURE1_INCR_SMALL"); 53 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1DecrSmall, "MIXTURE1_DECR_SMALL"); 54 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Decr, "MIXTURE1_DECR"); 55 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Lean, "MIXTURE1_LEAN"); 56 | } 57 | 58 | /// 59 | /// Registers the SimConnect mixture event group for capture. 60 | /// 61 | void RegisterMixtureEventGroup() 62 | { 63 | printf("Registering mixture event group...\r\n"); 64 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixtureSet, TRUE); 65 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixture1Set, TRUE); 66 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSetBest, TRUE); 67 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSet, TRUE); 68 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureRich, TRUE); 69 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncr, TRUE); 70 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncrSmall, TRUE); 71 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecrSmall, TRUE); 72 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecr, TRUE); 73 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureLean, TRUE); 74 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Set, TRUE); 75 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Rich, TRUE); 76 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Incr, TRUE); 77 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1IncrSmall, TRUE); 78 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1DecrSmall, TRUE); 79 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Decr, TRUE); 80 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Lean, TRUE); 81 | 82 | SimConnect_SetNotificationGroupPriority(hSimConnect, EventGroups::Mixture, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE); 83 | } 84 | 85 | /// 86 | /// Initializes the connection to SimConnect. 87 | /// 88 | /// True if successful, false otherwise. 89 | bool InitializeSimConnect() 90 | { 91 | printf("Connecting to SimConnect...\r\n"); 92 | if (SUCCEEDED(SimConnect_Open(&hSimConnect, "FdGauge", nullptr, 0, 0, 0))) 93 | { 94 | printf("SimConnect connected.\r\n"); 95 | 96 | this->RegisterMixtureClientEvents(); 97 | this->RegisterMixtureEventGroup(); 98 | 99 | SimConnect_AddToDataDefinition(hSimConnect, DataTypes::EngineControls, "GENERAL ENG MIXTURE LEVER POSITION:1", "Percent"); 100 | 101 | printf("SimConnect registrations complete.\r\n"); 102 | return true; 103 | } 104 | 105 | printf("SimConnect failed.\r\n"); 106 | 107 | return false; 108 | } 109 | 110 | /// 111 | /// A callback used for handling SimConnect updates. 112 | /// 113 | /// The update data sent by SimConnect. 114 | /// The size of the SimConnect data structure. 115 | /// A pointer specified by the client. 116 | static void CALLBACK HandleAxisEvent(SIMCONNECT_RECV* pData, DWORD cbData, void* pContext) 117 | { 118 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EVENT) 119 | { 120 | SIMCONNECT_RECV_EVENT* evt = static_cast(pData); 121 | if (evt->uGroupID == EventGroups::Mixture) 122 | { 123 | FdGauge* fd = static_cast(pContext); 124 | if (fd == 0) 125 | { 126 | printf("FD pointer was null processing SimConnect event.\r\n"); 127 | } 128 | else 129 | { 130 | HandleMixtureAxis(evt); 131 | } 132 | } 133 | } 134 | 135 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EXCEPTION) 136 | { 137 | SIMCONNECT_RECV_EXCEPTION* ex = static_cast(pData); 138 | printf("SimConnect Exception: %d \r\n", ex->dwException); 139 | } 140 | } 141 | 142 | /// 143 | /// Handles throttle axis updates received from SimConnect. 144 | /// 145 | /// A pointer to the SimConnect event structure. 146 | static void HandleMixtureAxis(SIMCONNECT_RECV_EVENT* evt) 147 | { 148 | switch (evt->uEventID) 149 | { 150 | case MixtureEventIDs::AxisMixtureSet: 151 | case MixtureEventIDs::AxisMixture1Set: 152 | globalMixtureAxis = static_cast(evt->dwData); 153 | break; 154 | case MixtureEventIDs::MixtureSetBest: 155 | case MixtureEventIDs::MixtureRich: 156 | case MixtureEventIDs::Mixture1Rich: 157 | globalMixtureAxis = MAX_MIX; 158 | break; 159 | case MixtureEventIDs::MixtureSet: 160 | globalMixtureAxis = (static_cast(evt->dwData) * 2) - MAX_MIX; 161 | break; 162 | case MixtureEventIDs::Mixture1Set: 163 | globalMixtureAxis = (static_cast(evt->dwData) * 2) - MAX_MIX; 164 | break; 165 | case MixtureEventIDs::MixtureLean: 166 | case MixtureEventIDs::Mixture1Lean: 167 | globalMixtureAxis = MIN_MIX; 168 | break; 169 | case MixtureEventIDs::MixtureIncr: 170 | case MixtureEventIDs::MixtureIncrSmall: 171 | case MixtureEventIDs::Mixture1Incr: 172 | case MixtureEventIDs::Mixture1IncrSmall: 173 | globalMixtureAxis += MIX_STEP; // TODO: CLAMP ALL INCR/DECR EVENTS 174 | break; 175 | case MixtureEventIDs::MixtureDecr: 176 | case MixtureEventIDs::MixtureDecrSmall: 177 | case MixtureEventIDs::Mixture1Decr: 178 | case MixtureEventIDs::Mixture1DecrSmall: 179 | globalMixtureAxis -= MIX_STEP; // TODO: CLAMP ALL INCR/DECR EVENTS 180 | break; 181 | } 182 | 183 | globalMixtureAxis = clamp(globalMixtureAxis, MIN_MIX, MAX_MIX); 184 | } 185 | 186 | public: 187 | 188 | /// 189 | /// Initializes the FD. 190 | /// 191 | /// True if successful, false otherwise. 192 | bool InitializeFD() 193 | { 194 | if (!this->InitializeSimConnect()) { 195 | printf("Init SimConnect failed"); 196 | return false; 197 | } 198 | 199 | FdCtrlInstance.init(); 200 | isConnected = true; 201 | SimConnect_CallDispatch(hSimConnect, HandleAxisEvent, this); 202 | 203 | return true; 204 | } 205 | 206 | /// 207 | /// A callback used to update the FD at each tick. 208 | /// 209 | /// The time since the previous update. 210 | /// True if successful, false otherwise. 211 | bool OnUpdate(double deltaTime) 212 | { 213 | if (isConnected == true) { 214 | FdCtrlInstance.update(globalMixtureAxis, deltaTime); 215 | } 216 | 217 | return true; 218 | } 219 | 220 | /// 221 | /// Kill. 222 | /// 223 | /// True if succesful, false otherwise. 224 | bool KillFD() 225 | { 226 | isConnected = false; 227 | unregister_all_named_vars(); 228 | return SUCCEEDED(SimConnect_Close(hSimConnect)); 229 | } 230 | }; 231 | 232 | #endif // !FDGAUGE 233 | -------------------------------------------------------------------------------- /src/TurboArrow/Mixture_Controller.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31410.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mixture_Controller", "Mixture_Controller.vcxproj", "{A5468B35-BBBD-4C55-97ED-81BFE343B0E4}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|MSFS = Debug|MSFS 11 | Release|MSFS = Release|MSFS 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.ActiveCfg = Debug|MSFS 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.Build.0 = Debug|MSFS 16 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.ActiveCfg = Release|MSFS 17 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.Build.0 = Release|MSFS 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9129C4E4-B566-46E3-81AE-74CCDE9CA599} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/TurboArrow/Mixture_Controller.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | MSFS 7 | 8 | 9 | Release 10 | MSFS 11 | 12 | 13 | 14 | 16.0 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4} 16 | Module 17 | 10.0 18 | TurboArrow_Mixture_Controller 19 | 20 | 21 | 22 | Application 23 | true 24 | MSFS 25 | MultiByte 26 | 27 | 28 | Application 29 | false 30 | MSFS 31 | true 32 | MultiByte 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | .wasm 48 | 49 | 50 | $(MSFS_IncludePath) 51 | 52 | 53 | .wasm 54 | $(MSFS_IncludePath) 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | false 67 | 68 | 69 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 70 | false 71 | 72 | 73 | 74 | 75 | 76 | 77 | ProgramDatabase 78 | 79 | 80 | stdcpp14 81 | 82 | 83 | 84 | 85 | %(AdditionalDependencies) 86 | 87 | 88 | true 89 | $(OutDir)$(TargetName)$(TargetExt) 90 | 91 | 92 | 93 | 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | $(OutDir) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | 117 | 118 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 119 | false 120 | false 121 | false 122 | 123 | 124 | 125 | 126 | 127 | 128 | %(AdditionalDependencies) 129 | 130 | 131 | true 132 | $(OutDir)$(TargetName)$(TargetExt) 133 | 134 | 135 | 136 | 137 | false 138 | 139 | 140 | 141 | 142 | 143 | 144 | $(OutDir) 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/TurboArrow/Mixture_Controller.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/TurboArrow/PidController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef PIDC 4 | #define PIDC 5 | 6 | /// 7 | /// A class for controlling values via a PID. 8 | /// 9 | class PidController 10 | { 11 | private: 12 | /// 13 | /// The gain of the proportional term. 14 | /// 15 | double gainProportion; 16 | 17 | /// 18 | /// The gain of the integral term. 19 | /// 20 | double gainIntegral; 21 | 22 | /// 23 | /// The gain of the derivative term. 24 | /// 25 | double gainDerivative; 26 | 27 | /// 28 | /// The minimum output allowed. 29 | /// 30 | double minOutput; 31 | 32 | /// 33 | /// The maximum output allowed. 34 | /// 35 | double maxOutput; 36 | 37 | /// 38 | /// The previous error amount. 39 | /// 40 | double prevError; 41 | 42 | /// 43 | /// The previous PID output. 44 | /// 45 | double prevOutput; 46 | 47 | /// 48 | /// The current integral term. 49 | /// 50 | double integral; 51 | 52 | /// 53 | /// Clamps a value to the PID min and max outputs. 54 | /// 55 | /// The value to clamp. 56 | /// The clamped value. 57 | double Clamp(double value, double max, double min) 58 | { 59 | if (value > max) 60 | { 61 | return max; 62 | } 63 | 64 | if (value < min) 65 | { 66 | return min; 67 | } 68 | 69 | return value; 70 | } 71 | 72 | public: 73 | /// 74 | /// Creates an instance of a PidController. 75 | /// 76 | /// The gain of the proportional term. 77 | /// The gain of the integral term. 78 | /// The gain of the derivative term. 79 | /// The maximum output. 80 | /// The minimum output. 81 | PidController(double gainProportion, double gainIntegral, double gainDerivative, double minOutput, double maxOutput) 82 | : gainProportion(gainProportion), gainIntegral(gainIntegral), gainDerivative(gainDerivative), 83 | minOutput(minOutput), maxOutput(maxOutput), prevError(0), prevOutput(0), integral(0) { } 84 | 85 | template int sgn(T val) { 86 | return (T(0) < val) - (val < T(0)); 87 | } 88 | 89 | /// 90 | /// Gets the output of the PID for a given error and timespan. 91 | /// 92 | /// The error vs the target value. 93 | /// The delta time vs the previous observation. 94 | /// The PID output. 95 | double GetOutput(double error, double deltaTime) 96 | { 97 | auto proportion = gainProportion * error; 98 | //if ((gainIntegral * integral) >= maxOutput) { 99 | //integral -= (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 100 | //} 101 | //else if ((gainIntegral * integral) <= minOutput) { 102 | integral += (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 103 | //} 104 | 105 | 106 | if (sgn(error) != sgn(prevError)) { 107 | integral = 0; 108 | } 109 | 110 | auto derivative = gainDerivative * ((error - prevError) / deltaTime); 111 | derivative = this->Clamp(derivative, 20.0, -20.0); 112 | 113 | auto output = this->Clamp(proportion + (gainIntegral * integral) + (derivative), this->maxOutput, this->minOutput); 114 | 115 | //printf("P: %.2f I: %.2f D %.2f \r\n", proportion, (gainIntegral * integral), (derivative)); 116 | 117 | prevError = error; 118 | prevOutput = output; 119 | 120 | return output; 121 | } 122 | }; 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /src/TurboArrow/SimConnectDefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SCDEFS 4 | #define SCDEFS 5 | 6 | #include "common.h" 7 | 8 | /// 9 | /// SimConnect client event IDs for the mixture group. 10 | /// 11 | enum MixtureEventIDs 12 | { 13 | AxisMixtureSet, 14 | AxisMixture1Set, 15 | MixtureSetBest, 16 | MixtureSet, 17 | MixtureRich, 18 | MixtureIncr, 19 | MixtureIncrSmall, 20 | MixtureDecrSmall, 21 | MixtureDecr, 22 | MixtureLean, 23 | Mixture1Set, 24 | Mixture1Rich, 25 | Mixture1Incr, 26 | Mixture1IncrSmall, 27 | Mixture1DecrSmall, 28 | Mixture1Decr, 29 | Mixture1Lean 30 | }; 31 | 32 | /// 33 | /// SimConnect event groups. 34 | /// 35 | enum EventGroups 36 | { 37 | /// 38 | /// The client event group ID to use when any events from the mixture axis group 39 | /// are received. 40 | /// 41 | Mixture = 0, 42 | }; 43 | 44 | /// 45 | /// SimConnect data types for sending the sim updates. 46 | /// 47 | enum DataTypes 48 | { 49 | /// 50 | /// The data type ID to use when sending engine controls data. 51 | /// 52 | EngineControls = 0 53 | }; 54 | /// 55 | /// Engine controls. 56 | /// 57 | struct EngineControlData 58 | { 59 | /// 60 | /// The mixture of the engine, expressed in a 100s base percent. 61 | /// 62 | double mixtureone; 63 | }; 64 | 65 | /// 66 | /// A collection of SimVar unit enums. 67 | /// 68 | class Units 69 | { 70 | public: 71 | /// 72 | /// The Percent SimVar unit. 73 | /// 74 | ENUM Percent = get_units_enum("Percent"); 75 | 76 | /// 77 | /// The Ratio SimVar unit. 78 | /// 79 | ENUM Ratio = get_units_enum("Ratio"); 80 | 81 | ENUM Celsius = get_units_enum("Celsius"); 82 | 83 | ENUM Number = get_units_enum("Number"); 84 | 85 | ENUM inHg = get_units_enum("inches of mercury"); 86 | }; 87 | 88 | 89 | /// 90 | /// A collection of SimVar enums. 91 | /// 92 | class SimVars 93 | { 94 | public: 95 | Units* m_Units; 96 | 97 | /// 98 | /// The GENERAL ENGINE MIXTURE LEVER POSITION SimVar. 99 | /// 100 | ENUM Mixture = get_aircraft_var_enum("GENERAL ENG MIXTURE LEVER POSITION"); 101 | 102 | ENUM FuelAirRatio = get_aircraft_var_enum("RECIP MIXTURE RATIO"); 103 | 104 | ENUM AmbientTemperature = get_aircraft_var_enum("AMBIENT TEMPERATURE"); 105 | 106 | ENUM AmbientPressure = get_aircraft_var_enum("AMBIENT PRESSURE"); 107 | 108 | ID FadecActive; 109 | 110 | /// 111 | /// The local variable for the visible mixture position 112 | /// 113 | ID MixturePos; 114 | 115 | SimVars() 116 | { 117 | this->initializeVars(); 118 | } 119 | 120 | void initializeVars() { 121 | FadecActive = register_named_variable("FADEC_ACTIVE"); 122 | MixturePos = register_named_variable("Mixture_Pos"); 123 | m_Units = new Units(); 124 | } 125 | 126 | void setFadecActiveFlag() { 127 | set_named_variable_value(FadecActive, 1); 128 | } 129 | 130 | void setMixturePos(double value) { 131 | set_named_variable_value(MixturePos, value); 132 | } 133 | 134 | FLOAT64 getMixtureLeverPosition(int index) { 135 | return aircraft_varget(Mixture, m_Units->Percent, index); 136 | } 137 | 138 | FLOAT64 getFuelAirRatio(int index) { 139 | return aircraft_varget(FuelAirRatio, m_Units->Ratio, index); 140 | } 141 | 142 | FLOAT64 getAmbientTemperature() { 143 | return aircraft_varget(AmbientTemperature, m_Units->Celsius, 0); 144 | } 145 | 146 | FLOAT64 getAmbientPressure() { 147 | return aircraft_varget(AmbientPressure, m_Units->inHg, 0); 148 | } 149 | }; 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /src/TurboArrow/TurboArrow_Mixture_Controller.cpp: -------------------------------------------------------------------------------- 1 |  2 | #ifdef _MSC_VER 3 | #define snprintf _snprintf_s 4 | #elif !defined(__MINGW32__) 5 | #include 6 | #endif 7 | 8 | #include "FdGauge.h" 9 | 10 | FdGauge FD_GAUGE; 11 | 12 | 13 | // ------------------------ 14 | // Callbacks 15 | extern "C" { 16 | 17 | MSFS_CALLBACK bool FdGauge_gauge_callback(FsContext ctx, int service_id, void* pData) 18 | { 19 | switch (service_id) 20 | { 21 | case PANEL_SERVICE_PRE_INSTALL: 22 | { 23 | return true; 24 | } 25 | break; 26 | case PANEL_SERVICE_POST_INSTALL: 27 | { 28 | return FD_GAUGE.InitializeFD(); 29 | } 30 | break; 31 | case PANEL_SERVICE_PRE_DRAW: 32 | { 33 | sGaugeDrawData* drawData = static_cast(pData); 34 | return FD_GAUGE.OnUpdate(drawData->dt); 35 | } 36 | break; 37 | case PANEL_SERVICE_PRE_KILL: 38 | { 39 | FD_GAUGE.KillFD(); 40 | return true; 41 | } 42 | break; 43 | } 44 | return false; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/TurboArrow/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _COMMON_H_ 4 | #define _COMMON_H_ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "SimConnectDefs.h" 16 | 17 | #include "turbocharger.h" 18 | #include 19 | #include 20 | 21 | 22 | /// 23 | /// The handle to the SimConnect instance. 24 | /// 25 | HANDLE hSimConnect; 26 | 27 | double clamp(double v, double lo, double hi) 28 | { 29 | assert(!(hi < lo)); 30 | return (v < lo) ? lo : (hi < v) ? hi : v; 31 | } 32 | 33 | 34 | #endif -------------------------------------------------------------------------------- /src/TurboArrow/turbocharger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef TURBOCHARGER 4 | #define TURBOCHARGER 5 | 6 | #include "common.h" 7 | 8 | class Turbocharger 9 | { 10 | public: 11 | static double getTargetFuelAirRatio(FLOAT64 ambientTemperature, FLOAT64 ambientPressure, double mixturePercentage) { 12 | double maxPowerManifoldPressure = 41.0; 13 | double criticalAltitude = 12000.0; 14 | double icaoStandardPressureAtCriticalAltitude = 19.03455; 15 | double maxTurboBoostPressure = maxPowerManifoldPressure - icaoStandardPressureAtCriticalAltitude; 16 | double fullRichSpecificVolumeConstant = 0.110236; 17 | double turboBoostCorrectionFactor = 1.252303; 18 | double turboSpecificVolumeConstant = fullRichSpecificVolumeConstant * turboBoostCorrectionFactor; 19 | double slope_90_100 = 0.000487531; 20 | double slope_75_90 = 0.000243697; 21 | double slope_60_75 = 0.000409167; 22 | double slope_20_60 = 0.000954534; 23 | double ambientPressurePa = ambientPressure * 3386.39; 24 | double maxManifoldPressurePa = maxPowerManifoldPressure * 3386.39; 25 | double ambientTemperatureK = ambientTemperature + 273.15; 26 | double maxManifoldDensity = 0.0; 27 | if (ambientPressure >= icaoStandardPressureAtCriticalAltitude) { 28 | maxManifoldDensity = maxManifoldPressurePa / (287.058 * pow(pow(ambientPressurePa, (1.0 - 1.4)) * pow(ambientTemperatureK, 1.4) / pow(maxManifoldPressurePa, (1.0 - 1.4)), (1.0 / 1.40))); 29 | } 30 | else { 31 | maxManifoldDensity = ((ambientPressure + maxTurboBoostPressure) * 3386.39) / (287.058 * pow(pow(ambientPressurePa, (1.0 - 1.4)) * pow(ambientTemperatureK, 1.4) / pow(((ambientPressure + maxTurboBoostPressure) * 3386.39), (1.0 - 1.4)), (1.0 / 1.40))); 32 | } 33 | double fullRichSpecificVolume = 1.0 / maxManifoldDensity; 34 | if (mixturePercentage == 100.0) { 35 | double targetFuelAirRatio = fullRichSpecificVolume * turboSpecificVolumeConstant; 36 | return targetFuelAirRatio; 37 | } 38 | else if (mixturePercentage >= 90.0) { 39 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (mixturePercentage - 100.0) * slope_90_100); 40 | return targetFuelAirRatio; 41 | } 42 | else if (mixturePercentage >= 75.0) { 43 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (mixturePercentage - 90.0) * slope_75_90); 44 | return targetFuelAirRatio; 45 | } 46 | else if (mixturePercentage >= 60.0) { 47 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (mixturePercentage - 75.0) * slope_60_75); 48 | return targetFuelAirRatio; 49 | } 50 | else if (mixturePercentage >= 20.0) { 51 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (mixturePercentage - 60.0) * slope_20_60); 52 | return targetFuelAirRatio; 53 | } 54 | else { 55 | double slope_0_20 = (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60) / 20.0; 56 | double targetFuelAirRatio = fullRichSpecificVolume * (turboSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60 + (mixturePercentage - 20.0) * slope_0_20); 57 | return targetFuelAirRatio; 58 | } 59 | } 60 | }; 61 | 62 | #endif // !TURBOCHARGER 63 | -------------------------------------------------------------------------------- /src/TurboBonanza/Bonanza_Mixture_Controller.cpp: -------------------------------------------------------------------------------- 1 |  2 | #ifdef _MSC_VER 3 | #define snprintf _snprintf_s 4 | #elif !defined(__MINGW32__) 5 | #include 6 | #endif 7 | 8 | #include "FdGauge.h" 9 | 10 | FdGauge FD_GAUGE; 11 | 12 | 13 | // ------------------------ 14 | // Callbacks 15 | extern "C" { 16 | 17 | MSFS_CALLBACK bool FdGauge_gauge_callback(FsContext ctx, int service_id, void* pData) 18 | { 19 | switch (service_id) 20 | { 21 | case PANEL_SERVICE_PRE_INSTALL: 22 | { 23 | return true; 24 | } 25 | break; 26 | case PANEL_SERVICE_POST_INSTALL: 27 | { 28 | return FD_GAUGE.InitializeFD(); 29 | } 30 | break; 31 | case PANEL_SERVICE_PRE_DRAW: 32 | { 33 | sGaugeDrawData* drawData = static_cast(pData); 34 | return FD_GAUGE.OnUpdate(drawData->dt); 35 | } 36 | break; 37 | case PANEL_SERVICE_PRE_KILL: 38 | { 39 | FD_GAUGE.KillFD(); 40 | return true; 41 | } 42 | break; 43 | } 44 | return false; 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/TurboBonanza/FdController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef FDCTRL 4 | #define FDCTRL 5 | 6 | #include "common.h" 7 | #include "PidController.h" 8 | 9 | class FdController 10 | { 11 | private: 12 | SimVars* simVars; 13 | 14 | /// 15 | /// An instance of the mixture PID controller. 16 | /// 17 | PidController* mixtureController; 18 | 19 | /// 20 | /// The current mixture control axis, from -16384 to 16384. 21 | /// 22 | int mixtureAxis = 16384; 23 | 24 | bool enabled = true; 25 | 26 | /// 27 | /// Calculates and updates mixture according to target and PIDs. 28 | /// 29 | /// 30 | void updateMixture(double deltaTime) { 31 | EngineControlData controls; 32 | controls.mixtureone = this->getDesiredMixture(deltaTime); 33 | SimConnect_SetDataOnSimObject(hSimConnect, DataTypes::EngineControls, SIMCONNECT_OBJECT_ID_USER, 0, 0, sizeof(EngineControlData), &controls); 34 | } 35 | 36 | /// 37 | /// Gets the mixture lever position to achieve the desired fuel/air mixture ratio 38 | /// 39 | /// Desired mixture setting 40 | double getDesiredMixture(double deltaTime) { 41 | double mixtureLeverPerc = 100.0 * (this->mixtureAxis + 16384) / 32768.0; 42 | 43 | if (!enabled) { 44 | return mixtureLeverPerc; 45 | } 46 | 47 | double targetFuelAirMixture = Turbocharger::getTargetFuelAirRatio(this->simVars->getAmbientTemperature(), this->simVars->getAmbientPressure(), mixtureLeverPerc); 48 | double simFuelAirMixture = this->simVars->getFuelAirRatio(1); 49 | printf("Target Mixture: %1.3f\nSim Mixture: %1.3f\n", targetFuelAirMixture, simFuelAirMixture); 50 | 51 | double error = targetFuelAirMixture - simFuelAirMixture; 52 | double pidOut = 0.0; 53 | pidOut = this->mixtureController->GetOutput(error, deltaTime); 54 | printf("Output from PID Controller: %2.3f\n", error); 55 | return max(0, min(100, this->simVars->getMixtureLeverPosition(1) + pidOut)); 56 | } 57 | 58 | void updateVisibleMixture() { 59 | int targetMixture = this->mixtureAxis; 60 | double mixtureLeverPerc = 100.0 * (targetMixture + 16384) / 32768.0; 61 | this->simVars->setMixturePos(mixtureLeverPerc); 62 | } 63 | 64 | public: 65 | void init() 66 | { 67 | printf("FdController init"); 68 | 69 | this->simVars = new SimVars(); 70 | 71 | float p = 350.0; 72 | float i = 380.0; 73 | float d = 0.0; 74 | this->mixtureController = new PidController(p, i, d, -5, 5); 75 | } 76 | 77 | void update(int mixtureAxis, double deltaTime) 78 | { 79 | this->mixtureAxis = mixtureAxis; 80 | this->updateMixture(deltaTime); 81 | this->updateVisibleMixture(); 82 | this->simVars->setFadecActiveFlag(); 83 | } 84 | }; 85 | 86 | FdController FdCtrlInstance; 87 | 88 | #endif // !FDCTRL 89 | -------------------------------------------------------------------------------- /src/TurboBonanza/FdGauge.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #ifndef FDGAUGE 5 | #define FDGAUGE 6 | 7 | #ifndef __INTELLISENSE__ 8 | # define MODULE_EXPORT __attribute__( ( visibility( "default" ) ) ) 9 | # define MODULE_WASM_MODNAME(mod) __attribute__((import_module(mod))) 10 | #else 11 | # define MODULE_EXPORT 12 | # define MODULE_WASM_MODNAME(mod) 13 | # define __attribute__(x) 14 | # define __restrict__ 15 | #endif 16 | 17 | #include "common.h" 18 | #include "FdController.h" 19 | 20 | const int MIN_MIX = -16384; 21 | const int MAX_MIX = 16384; 22 | const int MIX_STEP = 256; 23 | 24 | int globalMixtureAxis = MAX_MIX; 25 | 26 | class FdGauge 27 | { 28 | private: 29 | 30 | bool isConnected = false; 31 | 32 | /// 33 | /// Registers all the mixture SimConnect client events. 34 | /// 35 | void RegisterMixtureClientEvents() 36 | { 37 | printf("Registering mixture events...\r\n"); 38 | 39 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixtureSet, "AXIS_MIXTURE_SET"); 40 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::AxisMixture1Set, "AXIS_MIXTURE1_SET"); 41 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSetBest, "MIXTURE_SET_BEST"); 42 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureSet, "MIXTURE_SET"); 43 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureRich, "MIXTURE_RICH"); 44 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncr, "MIXTURE_INCR"); 45 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureIncrSmall, "MIXTURE_INCR_SMALL"); 46 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecrSmall, "MIXTURE_DECR_SMALL"); 47 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureDecr, "MIXTURE_DECR"); 48 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::MixtureLean, "MIXTURE_LEAN"); 49 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Set, "MIXTURE1_SET"); 50 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Rich, "MIXTURE1_RICH"); 51 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Incr, "MIXTURE1_INCR"); 52 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1IncrSmall, "MIXTURE1_INCR_SMALL"); 53 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1DecrSmall, "MIXTURE1_DECR_SMALL"); 54 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Decr, "MIXTURE1_DECR"); 55 | SimConnect_MapClientEventToSimEvent(hSimConnect, MixtureEventIDs::Mixture1Lean, "MIXTURE1_LEAN"); 56 | } 57 | 58 | /// 59 | /// Registers the SimConnect mixture event group for capture. 60 | /// 61 | void RegisterMixtureEventGroup() 62 | { 63 | printf("Registering mixture event group...\r\n"); 64 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixtureSet, TRUE); 65 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::AxisMixture1Set, TRUE); 66 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSetBest, TRUE); 67 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureSet, TRUE); 68 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureRich, TRUE); 69 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncr, TRUE); 70 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureIncrSmall, TRUE); 71 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecrSmall, TRUE); 72 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureDecr, TRUE); 73 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::MixtureLean, TRUE); 74 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Set, TRUE); 75 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Rich, TRUE); 76 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Incr, TRUE); 77 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1IncrSmall, TRUE); 78 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1DecrSmall, TRUE); 79 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Decr, TRUE); 80 | SimConnect_AddClientEventToNotificationGroup(hSimConnect, EventGroups::Mixture, MixtureEventIDs::Mixture1Lean, TRUE); 81 | 82 | SimConnect_SetNotificationGroupPriority(hSimConnect, EventGroups::Mixture, SIMCONNECT_GROUP_PRIORITY_HIGHEST_MASKABLE); 83 | } 84 | 85 | /// 86 | /// Initializes the connection to SimConnect. 87 | /// 88 | /// True if successful, false otherwise. 89 | bool InitializeSimConnect() 90 | { 91 | printf("Connecting to SimConnect...\r\n"); 92 | if (SUCCEEDED(SimConnect_Open(&hSimConnect, "FdGauge", nullptr, 0, 0, 0))) 93 | { 94 | printf("SimConnect connected.\r\n"); 95 | 96 | this->RegisterMixtureClientEvents(); 97 | this->RegisterMixtureEventGroup(); 98 | 99 | SimConnect_AddToDataDefinition(hSimConnect, DataTypes::EngineControls, "GENERAL ENG MIXTURE LEVER POSITION:1", "Percent"); 100 | 101 | printf("SimConnect registrations complete.\r\n"); 102 | return true; 103 | } 104 | 105 | printf("SimConnect failed.\r\n"); 106 | 107 | return false; 108 | } 109 | 110 | /// 111 | /// A callback used for handling SimConnect updates. 112 | /// 113 | /// The update data sent by SimConnect. 114 | /// The size of the SimConnect data structure. 115 | /// A pointer specified by the client. 116 | static void CALLBACK HandleAxisEvent(SIMCONNECT_RECV* pData, DWORD cbData, void* pContext) 117 | { 118 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EVENT) 119 | { 120 | SIMCONNECT_RECV_EVENT* evt = static_cast(pData); 121 | if (evt->uGroupID == EventGroups::Mixture) 122 | { 123 | FdGauge* fd = static_cast(pContext); 124 | if (fd == 0) 125 | { 126 | printf("FD pointer was null processing SimConnect event.\r\n"); 127 | } 128 | else 129 | { 130 | HandleMixtureAxis(evt); 131 | } 132 | } 133 | } 134 | 135 | if (pData->dwID == SIMCONNECT_RECV_ID::SIMCONNECT_RECV_ID_EXCEPTION) 136 | { 137 | SIMCONNECT_RECV_EXCEPTION* ex = static_cast(pData); 138 | printf("SimConnect Exception: %d \r\n", ex->dwException); 139 | } 140 | } 141 | 142 | /// 143 | /// Handles throttle axis updates received from SimConnect. 144 | /// 145 | /// A pointer to the SimConnect event structure. 146 | static void HandleMixtureAxis(SIMCONNECT_RECV_EVENT* evt) 147 | { 148 | switch (evt->uEventID) 149 | { 150 | case MixtureEventIDs::AxisMixtureSet: 151 | case MixtureEventIDs::AxisMixture1Set: 152 | globalMixtureAxis = static_cast(evt->dwData); 153 | break; 154 | case MixtureEventIDs::MixtureSetBest: 155 | case MixtureEventIDs::MixtureRich: 156 | case MixtureEventIDs::Mixture1Rich: 157 | globalMixtureAxis = MAX_MIX; 158 | break; 159 | case MixtureEventIDs::MixtureSet: 160 | globalMixtureAxis = (static_cast(evt->dwData) * 2) - MAX_MIX; 161 | break; 162 | case MixtureEventIDs::Mixture1Set: 163 | globalMixtureAxis = (static_cast(evt->dwData) * 2) - MAX_MIX; 164 | break; 165 | case MixtureEventIDs::MixtureLean: 166 | case MixtureEventIDs::Mixture1Lean: 167 | globalMixtureAxis = MIN_MIX; 168 | break; 169 | case MixtureEventIDs::MixtureIncr: 170 | case MixtureEventIDs::MixtureIncrSmall: 171 | case MixtureEventIDs::Mixture1Incr: 172 | case MixtureEventIDs::Mixture1IncrSmall: 173 | globalMixtureAxis += MIX_STEP; // TODO: CLAMP ALL INCR/DECR EVENTS 174 | break; 175 | case MixtureEventIDs::MixtureDecr: 176 | case MixtureEventIDs::MixtureDecrSmall: 177 | case MixtureEventIDs::Mixture1Decr: 178 | case MixtureEventIDs::Mixture1DecrSmall: 179 | globalMixtureAxis -= MIX_STEP; // TODO: CLAMP ALL INCR/DECR EVENTS 180 | break; 181 | } 182 | 183 | globalMixtureAxis = clamp(globalMixtureAxis, MIN_MIX, MAX_MIX); 184 | } 185 | 186 | public: 187 | 188 | /// 189 | /// Initializes the FD. 190 | /// 191 | /// True if successful, false otherwise. 192 | bool InitializeFD() 193 | { 194 | if (!this->InitializeSimConnect()) { 195 | printf("Init SimConnect failed"); 196 | return false; 197 | } 198 | 199 | FdCtrlInstance.init(); 200 | isConnected = true; 201 | SimConnect_CallDispatch(hSimConnect, HandleAxisEvent, this); 202 | 203 | return true; 204 | } 205 | 206 | /// 207 | /// A callback used to update the FD at each tick. 208 | /// 209 | /// The time since the previous update. 210 | /// True if successful, false otherwise. 211 | bool OnUpdate(double deltaTime) 212 | { 213 | if (isConnected == true) { 214 | FdCtrlInstance.update(globalMixtureAxis, deltaTime); 215 | } 216 | 217 | return true; 218 | } 219 | 220 | /// 221 | /// Kill. 222 | /// 223 | /// True if succesful, false otherwise. 224 | bool KillFD() 225 | { 226 | isConnected = false; 227 | unregister_all_named_vars(); 228 | return SUCCEEDED(SimConnect_Close(hSimConnect)); 229 | } 230 | }; 231 | 232 | #endif // !FDGAUGE 233 | -------------------------------------------------------------------------------- /src/TurboBonanza/Mixture_Controller.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31410.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mixture_Controller", "Mixture_Controller.vcxproj", "{A5468B35-BBBD-4C55-97ED-81BFE343B0E4}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|MSFS = Debug|MSFS 11 | Release|MSFS = Release|MSFS 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.ActiveCfg = Debug|MSFS 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Debug|MSFS.Build.0 = Debug|MSFS 16 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.ActiveCfg = Release|MSFS 17 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4}.Release|MSFS.Build.0 = Release|MSFS 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {9129C4E4-B566-46E3-81AE-74CCDE9CA599} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /src/TurboBonanza/Mixture_Controller.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | MSFS 7 | 8 | 9 | Release 10 | MSFS 11 | 12 | 13 | 14 | 16.0 15 | {A5468B35-BBBD-4C55-97ED-81BFE343B0E4} 16 | Module 17 | 10.0 18 | Bonanza_Mixture_Controller 19 | 20 | 21 | 22 | Application 23 | true 24 | MSFS 25 | MultiByte 26 | 27 | 28 | Application 29 | false 30 | MSFS 31 | true 32 | MultiByte 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | .wasm 48 | 49 | 50 | $(MSFS_IncludePath) 51 | 52 | 53 | .wasm 54 | $(MSFS_IncludePath) 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | false 67 | 68 | 69 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 70 | false 71 | 72 | 73 | 74 | 75 | 76 | 77 | ProgramDatabase 78 | 79 | 80 | stdcpp14 81 | 82 | 83 | 84 | 85 | %(AdditionalDependencies) 86 | 87 | 88 | true 89 | $(OutDir)$(TargetName)$(TargetExt) 90 | 91 | 92 | 93 | 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | $(OutDir) 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Level3 111 | MaxSpeed 112 | true 113 | true 114 | true 115 | true 116 | 117 | 118 | __wasi__;_STRING_H_CPLUSPLUS_98_CONFORMANCE_;_WCHAR_H_CPLUSPLUS_98_CONFORMANCE_;_LIBCPP_HAS_NO_THREADS;_WINDLL;%(PreprocessorDefinitions) 119 | false 120 | false 121 | false 122 | 123 | 124 | 125 | 126 | 127 | 128 | %(AdditionalDependencies) 129 | 130 | 131 | true 132 | $(OutDir)$(TargetName)$(TargetExt) 133 | 134 | 135 | 136 | 137 | false 138 | 139 | 140 | 141 | 142 | 143 | 144 | $(OutDir) 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | -------------------------------------------------------------------------------- /src/TurboBonanza/Mixture_Controller.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/TurboBonanza/PidController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef PIDC 4 | #define PIDC 5 | 6 | /// 7 | /// A class for controlling values via a PID. 8 | /// 9 | class PidController 10 | { 11 | private: 12 | /// 13 | /// The gain of the proportional term. 14 | /// 15 | double gainProportion; 16 | 17 | /// 18 | /// The gain of the integral term. 19 | /// 20 | double gainIntegral; 21 | 22 | /// 23 | /// The gain of the derivative term. 24 | /// 25 | double gainDerivative; 26 | 27 | /// 28 | /// The minimum output allowed. 29 | /// 30 | double minOutput; 31 | 32 | /// 33 | /// The maximum output allowed. 34 | /// 35 | double maxOutput; 36 | 37 | /// 38 | /// The previous error amount. 39 | /// 40 | double prevError; 41 | 42 | /// 43 | /// The previous PID output. 44 | /// 45 | double prevOutput; 46 | 47 | /// 48 | /// The current integral term. 49 | /// 50 | double integral; 51 | 52 | /// 53 | /// Clamps a value to the PID min and max outputs. 54 | /// 55 | /// The value to clamp. 56 | /// The clamped value. 57 | double Clamp(double value, double max, double min) 58 | { 59 | if (value > max) 60 | { 61 | return max; 62 | } 63 | 64 | if (value < min) 65 | { 66 | return min; 67 | } 68 | 69 | return value; 70 | } 71 | 72 | public: 73 | /// 74 | /// Creates an instance of a PidController. 75 | /// 76 | /// The gain of the proportional term. 77 | /// The gain of the integral term. 78 | /// The gain of the derivative term. 79 | /// The maximum output. 80 | /// The minimum output. 81 | PidController(double gainProportion, double gainIntegral, double gainDerivative, double minOutput, double maxOutput) 82 | : gainProportion(gainProportion), gainIntegral(gainIntegral), gainDerivative(gainDerivative), 83 | minOutput(minOutput), maxOutput(maxOutput), prevError(0), prevOutput(0), integral(0) { } 84 | 85 | template int sgn(T val) { 86 | return (T(0) < val) - (val < T(0)); 87 | } 88 | 89 | /// 90 | /// Gets the output of the PID for a given error and timespan. 91 | /// 92 | /// The error vs the target value. 93 | /// The delta time vs the previous observation. 94 | /// The PID output. 95 | double GetOutput(double error, double deltaTime) 96 | { 97 | auto proportion = gainProportion * error; 98 | //if ((gainIntegral * integral) >= maxOutput) { 99 | //integral -= (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 100 | //} 101 | //else if ((gainIntegral * integral) <= minOutput) { 102 | integral += (error * deltaTime) + ((deltaTime * (error - prevError)) / 2); 103 | //} 104 | 105 | 106 | if (sgn(error) != sgn(prevError)) { 107 | integral = 0; 108 | } 109 | 110 | auto derivative = gainDerivative * ((error - prevError) / deltaTime); 111 | derivative = this->Clamp(derivative, 20.0, -20.0); 112 | 113 | auto output = this->Clamp(proportion + (gainIntegral * integral) + (derivative), this->maxOutput, this->minOutput); 114 | 115 | //printf("P: %.2f I: %.2f D %.2f \r\n", proportion, (gainIntegral * integral), (derivative)); 116 | 117 | prevError = error; 118 | prevOutput = output; 119 | 120 | return output; 121 | } 122 | }; 123 | 124 | #endif 125 | -------------------------------------------------------------------------------- /src/TurboBonanza/SimConnectDefs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef SCDEFS 4 | #define SCDEFS 5 | 6 | #include "common.h" 7 | 8 | /// 9 | /// SimConnect client event IDs for the mixture group. 10 | /// 11 | enum MixtureEventIDs 12 | { 13 | AxisMixtureSet, 14 | AxisMixture1Set, 15 | MixtureSetBest, 16 | MixtureSet, 17 | MixtureRich, 18 | MixtureIncr, 19 | MixtureIncrSmall, 20 | MixtureDecrSmall, 21 | MixtureDecr, 22 | MixtureLean, 23 | Mixture1Set, 24 | Mixture1Rich, 25 | Mixture1Incr, 26 | Mixture1IncrSmall, 27 | Mixture1DecrSmall, 28 | Mixture1Decr, 29 | Mixture1Lean 30 | }; 31 | 32 | /// 33 | /// SimConnect event groups. 34 | /// 35 | enum EventGroups 36 | { 37 | /// 38 | /// The client event group ID to use when any events from the mixture axis group 39 | /// are received. 40 | /// 41 | Mixture = 0, 42 | }; 43 | 44 | /// 45 | /// SimConnect data types for sending the sim updates. 46 | /// 47 | enum DataTypes 48 | { 49 | /// 50 | /// The data type ID to use when sending engine controls data. 51 | /// 52 | EngineControls = 0 53 | }; 54 | /// 55 | /// Engine controls. 56 | /// 57 | struct EngineControlData 58 | { 59 | /// 60 | /// The mixture of the engine, expressed in a 100s base percent. 61 | /// 62 | double mixtureone; 63 | }; 64 | 65 | /// 66 | /// A collection of SimVar unit enums. 67 | /// 68 | class Units 69 | { 70 | public: 71 | /// 72 | /// The Percent SimVar unit. 73 | /// 74 | ENUM Percent = get_units_enum("Percent"); 75 | 76 | /// 77 | /// The Ratio SimVar unit. 78 | /// 79 | ENUM Ratio = get_units_enum("Ratio"); 80 | 81 | ENUM Celsius = get_units_enum("Celsius"); 82 | 83 | ENUM Number = get_units_enum("Number"); 84 | 85 | ENUM inHg = get_units_enum("inches of mercury"); 86 | }; 87 | 88 | 89 | /// 90 | /// A collection of SimVar enums. 91 | /// 92 | class SimVars 93 | { 94 | public: 95 | Units* m_Units; 96 | 97 | /// 98 | /// The GENERAL ENGINE MIXTURE LEVER POSITION SimVar. 99 | /// 100 | ENUM Mixture = get_aircraft_var_enum("GENERAL ENG MIXTURE LEVER POSITION"); 101 | 102 | ENUM FuelAirRatio = get_aircraft_var_enum("RECIP MIXTURE RATIO"); 103 | 104 | ENUM AmbientTemperature = get_aircraft_var_enum("AMBIENT TEMPERATURE"); 105 | 106 | ENUM AmbientPressure = get_aircraft_var_enum("AMBIENT PRESSURE"); 107 | 108 | ID FadecActive; 109 | 110 | /// 111 | /// The local variable for the visible mixture position 112 | /// 113 | ID MixturePos; 114 | 115 | SimVars() 116 | { 117 | this->initializeVars(); 118 | } 119 | 120 | void initializeVars() { 121 | FadecActive = register_named_variable("FADEC_ACTIVE"); 122 | MixturePos = register_named_variable("Mixture_Pos"); 123 | m_Units = new Units(); 124 | } 125 | 126 | void setFadecActiveFlag() { 127 | set_named_variable_value(FadecActive, 1); 128 | } 129 | 130 | void setMixturePos(double value) { 131 | set_named_variable_value(MixturePos, value); 132 | } 133 | 134 | FLOAT64 getMixtureLeverPosition(int index) { 135 | return aircraft_varget(Mixture, m_Units->Percent, index); 136 | } 137 | 138 | FLOAT64 getFuelAirRatio(int index) { 139 | return aircraft_varget(FuelAirRatio, m_Units->Ratio, index); 140 | } 141 | 142 | FLOAT64 getAmbientTemperature() { 143 | return aircraft_varget(AmbientTemperature, m_Units->Celsius, 0); 144 | } 145 | 146 | FLOAT64 getAmbientPressure() { 147 | return aircraft_varget(AmbientPressure, m_Units->inHg, 0); 148 | } 149 | }; 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /src/TurboBonanza/common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef _COMMON_H_ 4 | #define _COMMON_H_ 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "SimConnectDefs.h" 16 | 17 | #include "turbocharger.h" 18 | #include 19 | #include 20 | 21 | 22 | /// 23 | /// The handle to the SimConnect instance. 24 | /// 25 | HANDLE hSimConnect; 26 | 27 | double clamp(double v, double lo, double hi) 28 | { 29 | assert(!(hi < lo)); 30 | return (v < lo) ? lo : (hi < v) ? hi : v; 31 | } 32 | 33 | 34 | #endif -------------------------------------------------------------------------------- /src/TurboBonanza/turbocharger.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifndef TURBOCHARGER 4 | #define TURBOCHARGER 5 | 6 | #include "common.h" 7 | 8 | class Turbocharger 9 | { 10 | public: 11 | static double getTargetFuelAirRatio(FLOAT64 ambientTemperature, FLOAT64 ambientPressure, double mixturePercentage) { 12 | double maxPowerManifoldPressure = 29.6; 13 | double criticalAltitude = 18000.0; 14 | double icaoStandardPressureAtCriticalAltitude = 14.95168; 15 | double maxTurboBoostPressure = maxPowerManifoldPressure - icaoStandardPressureAtCriticalAltitude; 16 | double fullRichSpecificVolumeConstant = 0.110236; 17 | double slope_90_100 = 0.000487531; 18 | double slope_75_90 = 0.000243697; 19 | double slope_60_75 = 0.000409167; 20 | double slope_20_60 = 0.000954534; 21 | double ambientPressurePa = ambientPressure * 3386.39; 22 | double maxManifoldPressurePa = maxPowerManifoldPressure * 3386.39; 23 | double ambientTemperatureK = ambientTemperature + 273.15; 24 | double maxManifoldDensity = 0.0; 25 | if (ambientPressure >= icaoStandardPressureAtCriticalAltitude) { 26 | maxManifoldDensity = maxManifoldPressurePa / (287.058 * pow(pow(ambientPressurePa, (1.0 - 1.4)) * pow(ambientTemperatureK, 1.4) / pow(maxManifoldPressurePa, (1.0 - 1.4)), (1.0 / 1.40))); 27 | } 28 | else { 29 | maxManifoldDensity = ((ambientPressure + maxTurboBoostPressure) * 3386.39) / (287.058 * pow(pow(ambientPressurePa, (1.0 - 1.4)) * pow(ambientTemperatureK, 1.4) / pow(((ambientPressure + maxTurboBoostPressure) * 3386.39), (1.0 - 1.4)), (1.0 / 1.40))); 30 | } 31 | double fullRichSpecificVolume = 1.0 / maxManifoldDensity; 32 | if (mixturePercentage == 100.0) { 33 | double targetFuelAirRatio = fullRichSpecificVolume * fullRichSpecificVolumeConstant; 34 | return targetFuelAirRatio; 35 | } 36 | else if (mixturePercentage >= 90.0) { 37 | double targetFuelAirRatio = fullRichSpecificVolume * (fullRichSpecificVolumeConstant + (mixturePercentage - 100.0) * slope_90_100); 38 | return targetFuelAirRatio; 39 | } 40 | else if (mixturePercentage >= 75.0) { 41 | double targetFuelAirRatio = fullRichSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (mixturePercentage - 90.0) * slope_75_90); 42 | return targetFuelAirRatio; 43 | } 44 | else if (mixturePercentage >= 60.0) { 45 | double targetFuelAirRatio = fullRichSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (mixturePercentage - 75.0) * slope_60_75); 46 | return targetFuelAirRatio; 47 | } 48 | else if (mixturePercentage >= 20.0) { 49 | double targetFuelAirRatio = fullRichSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (mixturePercentage - 60.0) * slope_20_60); 50 | return targetFuelAirRatio; 51 | } 52 | else { 53 | double slope_0_20 = (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60) / 20.0; 54 | double targetFuelAirRatio = fullRichSpecificVolume * (fullRichSpecificVolumeConstant + (90.0 - 100.0) * slope_90_100 + (75.0 - 90.0) * slope_75_90 + (60.0 - 75.0) * slope_60_75 + (20.0 - 60.0) * slope_20_60 + (mixturePercentage - 20.0) * slope_0_20); 55 | return targetFuelAirRatio; 56 | } 57 | } 58 | }; 59 | 60 | #endif // !TURBOCHARGER 61 | --------------------------------------------------------------------------------