Career Maintenance Tool (for consoles)

Hi,

Here’s a handy little tool for automatically performing maintenance on your aircraft in career mode. The principle is the same as with @StarDreamer05’s mod (see here: Career Maintenance Tool): We’ll send key sequences, simulating maintenance actions in the career mode menus.

The difference is that this is a hardware solution and you don’t need a PC to run the game (of course, you’ll need a basic PC to program the module with the Arduino IDE). Since this is a workaround not intended to be permanent, I won’t publish the source code on GitHub, but directly here.

I’m using an ESP32-S3 “DevKitC-1-N16R8” module, which can be found commercially (Amazon, AliExpress) for less than €10. No soldering is required; all you need is a USB-C cable (often included with the module).

This module features an ESP32-S3 SoC with a native USB port. It has two USB connectors: one for programming (serial, via a CH343) and the other for native USB. We’ll program this module to function as a USB keyboard. This way, it will be universally recognized by FS2024, whether you’re on a PC or console.

On this picture, the blue cable is connected to a USB-Hub (to XBOX). The red cable is used only for programming (connected to PC), and can be removed after. The module is powered by either by the blue or red cable, no need of soldering jumpers.

On Xbox, the keyboard is recognized without needing a TPM chip and RSA key (as is the case with joysticks). I haven’t tested it on PS5, but it should work there as well.

Using it is very simple:
Plug the module into a USB port on the console or a USB hub. You can even use two keyboards (a physical one and the ESP32-S3 module) simultaneously.

Navigate to the main menu, with the first tile, “World Photographer,” selected (use the standard keyboard and arrow keys). On the module, press the “BOOT” button… and that’s it!

To interrupt the process, press the “RST” button.

Before starting, make sure you have enough money to maintain all the aircraft! :money_bag:

On the modules supplied, the “LED” jumper is often not connected (and therefore the WS2812 LED doesn’t light up). You’ll need to add a solder bridge at this point (see module documentation), but it’s not essential for operation, although it’s still nice to have the LED indicating progress.

Here’s the source code, happy tinkering!

/*
  Maintenance automatisée des avions
  Avec module ESP32-S3 (devkit)

  Ne pas oublier de mettre l'option -> USB-Mode : "USB-OTG (TinyUSB)"
  lors de la compilation.
*/

#ifndef ARDUINO_USB_MODE
#error L'ESP32 utilisé ne posséde pas d'interface USB native 
#elif ARDUINO_USB_MODE == 1
#warning Activer USB-OTG dans les options de compilation de l'ESP32
void setup() {}
void loop() {}
#else

#include "USB.h"
#include "USBHIDKeyboard.h"
#include <Adafruit_NeoPixel.h>

#define BTN_BOOT    0  // Bouton BOOT sur entrée 0
#define LED_WS2812 48  // Port LED WS2812

#define Couleur_Off      0,   0,   0
#define Couleur_Bleu     0,   0, 255
#define Couleur_Jaune  255, 255,   0
#define Couleur_Violet 255,   0, 255
#define Couleur_Cyan     0, 255, 255
#define Couleur_Rouge  255,   0,   0
#define Couleur_Vert     0, 255,   0

#define NB_COMP    10  // Nombre de compagnies

int Avions[NB_COMP] = { 63, 6, 4, 10, 9, 29, 6, 3, 18, 83 } ; // Nombre d'avions par compagnie

#define Delay_A   500   // Délai court pour naviguer (parcours avion ou compagnie)
#define Delay_B  2000   // Délai long pour la prise en compte des selections
#define Delay_C 10000   // Délai très long pour laisser le temps au GUI FS2024 de réagir
#define Delay_D 15000   // Délai ultra long pour laisser le temps d'afficher les pages extrémement lentes

Adafruit_NeoPixel LED(1, LED_WS2812, NEO_GRB + NEO_KHZ800) ;
USBHIDKeyboard Keyboard ;

// ----------------------------------------------------------

void Clavier(uint8_t k)
{
  // Génération de l'appui d'une touche
  // avec temporisation adaptée pour fonctionner sur XBOX

  Keyboard.press(k) ;     // --> appui sur la touche
  delay(50) ;             // --> délai de maintien 50 ms (ni trop court pour être bien pris en compte, ni trop long pour ne pas déclencher la répétition)
  Keyboard.releaseAll() ; // --> Relâche toute les touches qui étaient appuyées
  delay(Delay_A) ;        // Délai minimal entre deux touches
}

// ----------------------------------------------------------

void setup()
{
  // Initialisations

  pinMode(BTN_BOOT, INPUT_PULLUP) ;
  pinMode(LED_BUILTIN, OUTPUT) ;

  Keyboard.begin() ;
  USB.begin() ;
  LED.begin() ;
  
  LED.setBrightness(20) ;
  LED.setPixelColor(0, LED.Color(Couleur_Bleu)) ; // LED bleue (allumée une seconde, indique que l'interface a démarrée)
  LED.show() ;
  delay (1000) ;
  LED.setPixelColor(0, LED.Color(Couleur_Off)) ; // LED éteinte
  LED.show() ;
}

// ----------------------------------------------------------

void loop() 
{
  int c, a, i ;

  // Attente de l'appui sur le bouton "boot"

  while (digitalRead(BTN_BOOT) == HIGH)
  {
    delay(200) ;
  }

  LED.setPixelColor(0, LED.Color(Couleur_Jaune)) ; // LED jaune
  LED.show() ;

  // Attente relachement du bouton "boot"

  while (digitalRead(BTN_BOOT) == LOW)
  {
    delay(200) ;
  }
  
  LED.setPixelColor(0, LED.Color(Couleur_Cyan)) ; // LED Cyan (positionnement initial en cours)
  LED.show() ;

  // Au début, on doit être positionné sur tuile du menu principal ("World Photographer")
  // (active en se positionnant dessus avec le second clavier, mais sans cliquer !)

  Clavier(KEY_UP_ARROW) ;    // --> Up (bidon ! sert à activer le parcours avec le clavier - on reste donc sur la tuile "World Photographer")
  delay(Delay_B) ;
  
  // Ici, on est donc sur la position "home" du menu principal, autrement dit, la tuile "World Photographer"

  Clavier(KEY_DOWN_ARROW) ;  // --> Down (-> passe à la tuile "Challenge League")
  delay(Delay_B) ;

  Clavier(KEY_RIGHT_ARROW) ; // --> Right (-> passe à la tuile "Career")
  delay(Delay_B) ;
  
  Clavier(KEY_SPACE) ;       // --> Sélectionne "Career" et attente que le "Headquarters" soit chargé
  delay(Delay_D) ;

  LED.setPixelColor(0, LED.Color(Couleur_Violet)) ; // LED Violet (positionnement initial dans le "Headquarters")
  LED.show() ;

  // Par défaut on est positionné à la fin, sur "Missions"
  // on revient en arrière sur le choix "Companies" et on le selectionne

  Clavier(KEY_LEFT_ARROW) ;  // --> Left
  delay(Delay_B) ;

  Clavier(KEY_SPACE) ;       // --> Espace, sélectionne "Companies"
  delay(Delay_B) ;

  LED.setPixelColor(0, LED.Color(Couleur_Off)) ; // LED éteinte - on a atteint la page "Headquaters - My companies"
  LED.show() ;

  // Par défaut, "Overview" est sélectionné
  // On remonte sur le choix du dessus, "All companies" et on le sélectionne

  Clavier(KEY_UP_ARROW) ;    // --> Un petit coup de "UP"
  delay(Delay_B) ;

  Clavier(KEY_SPACE) ;       // --> Espace, sélectionne "Companies"
  delay(Delay_C) ;

  // Là, c'est bon, on est enfin sur la première compagnie !

  // C'est parti...
  
  for (c = 0 ; c < NB_COMP ; c++)
  {
    LED.setPixelColor(0, LED.Color(Couleur_Rouge)) ; // LED rouge
    LED.show() ;

    // Positionne sur la compagnie (sauf pour la première, où on est déjà dessus)

    if (c != 0)
    {
      for (i = 0 ; i < c ; i++)
      {
        Clavier(KEY_DOWN_ARROW) ; // --> Passe à la compagnie suivante
      }
    }

    Clavier(KEY_SPACE) ; // --> Sélectionne la compagnie
    delay(Delay_B) ;

    // Boucle sur le nombre d'avions à traiter pour cette compagnie

    for (a = 0 ; a < Avions[c] ; a++)
    {
      // Positionne sur l'avion (sauf pour le tout premier, où on est déjà dessus)

      if (a != 0)
      {
        for (i = 0 ; i < a ; i++)
        {
          Clavier(KEY_RIGHT_ARROW) ; // --> Passe à l'avion suivant
        }
      }

      Clavier(KEY_SPACE) ; // --> Ouvre la liste déroulante pour l'avion(on sera positionné sur le premier choix "Manage aircraft")
      delay(Delay_B) ;

      Clavier(KEY_SPACE) ; // --> Sélectionne "Manage aircraft"
      delay(Delay_B) ;

      // Là, on est dans le menu maintenance de l'avion, on doit être sur la tuile "Update Check-up"

      Clavier(KEY_SPACE) ; // --> Sélectionne "Update Check-up"
      delay(Delay_B) ;

      Clavier(KEY_SPACE) ; // --> Valide "Check-up ?"
      delay(Delay_B) ;

      Clavier(KEY_RIGHT_ARROW) ; // --> Passe à la tuile "Delegated maintenance"
      delay(Delay_B) ;

      Clavier(KEY_SPACE) ; // --> Sélectionne "Delegated maintenance"
      delay(Delay_B) ;

      Clavier(KEY_SPACE) ; // --> Valide "Pay ?"
      delay(Delay_B) ;

      Clavier(KEY_ESC) ; // --> Ressort et revient au premier avion de la liste
      delay(Delay_B) ;
    }

    LED.setPixelColor(0, LED.Color(Couleur_Off)) ; // LED éteinte
    LED.show() ;

    Clavier(KEY_ESC) ; // --> Ressort de la compagnie
    delay(Delay_C) ;
  }

  LED.setPixelColor(0, LED.Color(Couleur_Vert)) ; // LED vert
  LED.show() ;
  delay(5000) ;
  LED.setPixelColor(0, LED.Color(Couleur_Off)) ; // LED éteinte
  LED.show() ;

}
#endif

The code must be adapted to suit individual needs. Initially, you will find the number of companies (here, 10) and a table showing the number of aircraft per company.

Sometimes, certain timings will need to be adjusted. Keystroke sequences are generated blindly, without knowing the result. Sometimes FS2024’s response times can vary, and there may be some glitches.

Of course, it’s still a “DIY” tool, without warranty or support. It works fairly well for me, but I hope we’ll soon be able to do without it.

I appreciate things like this, but the fact that the Career maintenance UI and process is in a state that users see the value in this is sad. Fingers crossed for improvements to this in future sim updates.

3 Likes