MobiFlight Connector

repository·main·Indexed 43 days ago

https://github.com/mobiflight/mobiflight-connector

An open-source Windows application that bridges flight simulators (MSFS, X-Plane, FSX, P3D) with custom hardware such as Arduino, MIDI, and USB HID. It provides a graphical user interface for creating peripherals without programming, includes a WebSocket API for CDU displays, and supports custom device integration and aircraft-specific configuration profiles.

Tokens
10K
Snippets
25
Records
45
Agent score
57%

What's inside MobiFlight Connector

  1. Understand event logging and RawValue behavior

    main

    When debugging virtual button events, the logs and the UI RawValue follow specific rules:

    Logging

    • Physical Events: PRESS and RELEASE are logged as real hardware transitions.
    • Synthetic Events: HOLD, REPEAT, and LONG_RELEASE are logged with their configured threshold. For example, a log might show ButtonName:300ms to indicate that the 300ms HoldDelay threshold was the trigger.
    • Skip Reasons: If a configuration is inactive or preconditions are not met, the log will explicitly state why (e.g., "Skipping inactive config", "Preconditions not satisfied").

    RawValue (UI)

    • Physical Events: A physical PRESS or RELEASE always updates the RawValue in the UI, even if no configuration is currently bound to that specific event. This confirms the hardware is communicating.
    • Synthetic Events: HOLD or REPEAT events only update the RawValue if there is a configuration specifically defining an onHold action. This prevents the UI from showing synthetic states that the user has no active control over.
  2. How virtual button events (Hold, Repeat, LongRelease) are processed

    main

    MobiFlight uses a synthetic button event generator to handle complex button behaviors like holding and repeating. Instead of tracking state within individual configuration items (which could lead to duplicate timers for the same physical button), the state is managed at the controller layer (MobiFlightCache, JoystickManager, or MidiBoardManager).

    Event Types

    • HOLD: Triggered when a button is held for a specified HoldDelay.
    • REPEAT: Triggered repeatedly at a cadence defined by RepeatDelay while the button remains pressed. Note that REPEAT events resolve to the same onHold action as HOLD, but are distinguished in logs/UI to show they are part of a repeat cycle.
    • LONG_RELEASE: Triggered if a button is released after the LongReleaseDelay has elapsed.
    • RELEASE: A standard button release.

    Processing Stages

    The system uses a chain of three processing shapes:

    1. Inject (Time-triggered): Generates HOLD and REPEAT events based on elapsed time.
    2. Reclassify (Event-triggered): Relabels existing events (e.g., turning a standard turn into a LEFT_FAST encoder event).
    3. Aggregate (Event-triggered): Combines multiple raw events into one (e.g., turning two physical buttons into a single Encoder event).
  3. How Virtual Button Events (Hold, Repeat, LongRelease) work

    main

    MobiFlight Connector supports synthetic button events derived from physical button presses. This allows a single physical button to trigger different actions based on how long it is held.

    Key Concepts

    • Hold/Repeat: Triggered when a button is held for a specific HoldDelay and subsequently repeats at a RepeatDelay interval.
    • LongRelease: A RELEASE event that is reclassified as a LONG_RELEASE if the button was held longer than the configured LongReleaseDelay.
    • Multi-mode Support: You can bind multiple configurations to the same physical button with different timing requirements. For example, one config can trigger an onHold after 300ms, while another triggers an onHold after 1000ms. The system resolves these independently.
    • Dispatch-time Decision: LONG_RELEASE is not a separate hardware event; it is a decision made by each individual configuration during the dispatch phase. The hardware only ever sends a RELEASE, and each config checks if the HeldDurationMs exceeds its own LongReleaseDelay to decide whether to execute its onLongRelease action or its standard onRelease action.
  4. Understand the Message envelope & serialization format

    main

    Messages sent between the Frontend and Backend use a Message<T> envelope. This structure ensures that the receiver can identify the type of payload before full deserialization occurs.

    Envelope Structure:

    • key: A string representing the message type. By default, this is the name of the payload type (payload.GetType().Name).
    • payload: The actual data object of type T.

    Serialization Details:

    • Messages are serialized using JsonConvert.SerializeObject.
    • Polymorphism is handled via [JsonConverter] attributes on the model types themselves (e.g., Incoming/Converter/InputActionConverter.cs).
    • The backend uses a two-stage deserialization process: first as Message<object> to extract the key, then looking up the registered Type for that key to deserialize the payload into its concrete type. Unknown keys are logged as warnings and dropped.
    {
      "key": "string",
      "payload": {}
    }
  5. How joystick encoders are synthesized from button pairs

    main

    Some joystick hardware reports rotary encoders as two separate momentary buttons (e.g., an INC pulse and a DEC pulse). MobiFlight handles this via an Aggregate stage within the JoystickManager.

    Aggregation Logic

    • Two physical button inputs are consumed and transformed into a single Encoder device output.
    • Ordering: This aggregation happens before the Hold/LongRelease chain. This ensures that once two buttons are paired as an encoder, they no longer surface as ordinary buttons, preventing accidental onHold or onRelease triggers from encoder pulses.

    Fast-Turn Detection

    After aggregation, a Reclassify stage can detect rapid movements. For example, if 5 consecutive turns occur in under 1 second, the event is relabeled as a LEFT_FAST or RIGHT_FAST event.

  6. Understanding Virtual Button Events (Hold, Repeat, LongRelease)

    main

    MobiFlight Connector supports 'virtual' button events that are synthesized by the software rather than reported directly by physical hardware. These events allow you to trigger actions based on how a button is interacted with over time.

    Supported Virtual Events

    • Hold: Triggered when a button is pressed and held for a specific duration.
    • Repeat: Triggered repeatedly while a button remains in the 'Hold' state, following a specific delay.
    • LongRelease: Triggered when a button is released after having been held for a specific duration (distinguishable from a standard RELEASE).

    Implementation Details

    These events are managed by the SyntheticButtonEventGenerator and are integrated into the MobiFlightCache, JoystickManager, and MidiBoardManager. They are designed to follow the same execution pipeline as real PRESS and RELEASE events, ensuring that Preconditions, Modifiers, Status reporting, and UI updates behave consistently regardless of whether the event was physical or virtual.

  7. Understand the Frontend ↔ Backend messaging architecture

    main

    The MobiFlight Connector uses a symmetric, non-blocking messaging architecture to communicate between the .NET WinForms application (backend) and the React frontend (browser/WebView2).

    Instead of using the WebView2 postMessage bridge for all traffic, the .NET app hosts an in-process WebSocket server built using the BCL (System.Net.Sockets.TcpListener and System.Net.WebSockets.WebSocket.CreateFromStream). This allows for a scalable design where the frontend is one of potentially many clients (e.g., future mobile companion apps).

    Key Architectural Components:

    • Backend (WinForms): Uses MessageExchange.Publish<T> to send messages. An IMessagePublisher (specifically WebSocketServerPublisher) broadcasts messages to all connected WebSocket sessions.
    • Frontend (React): Uses a messageClient that maps message keys to handlers. It communicates via a MessageTransport layer, which abstracts whether the underlying transport is WebSocketTransport or PostMessageTransport (used for E2E testing).
    • Message Format: Both sides exchange identical envelopes containing a { key, payload } structure (defined in MobiFlight/BrowserMessages/Message.cs).
      .NET (WinForms)                                 Browser (WebView2 / dev browser)
      ---------------                                 --------------------------------
      producers ──► MessageExchange.Publish<T>        components ──► publish(CommandMessage)
                          │                                                │
                          ▼                                                ▼
                 IMessagePublisher                                   messageClient
            (WebSocketServerPublisher)                     (key→handlers map, single dispatch)
                          │                                                │
                          ▼                                                ▼
                   MessageServer (BCL)     ◄══ ws://127.0.0.1:PORT ══►  MessageTransport
                 broadcast to all sessions                       (WebSocketTransport | PostMessageTransport)
                          │                                                ▲
                          ▼                                                │
            MessageExchange.PublishReceivedMessage            useAppMessage(key, cb)
  8. How the MobiFlight Architecture works

    main

    MobiFlight is a hybrid application consisting of a C# backend and a React/TypeScript frontend:

    • C# Backend: Handles flight simulator integration (MSFS, FSX/P3D, X-Plane), hardware communication (Arduino, MIDI, USB HID), and the execution engine that polls simulator variables to drive output devices.
    • Frontend: A React + TypeScript application (using Vite, Tailwind, and shadcn/ui) that provides the configuration UI. It runs inside a WebView2 control embedded in the C# backend.
    • Communication: The frontend and backend communicate via a JSON message bridge. The React side uses PostMessage to send messages, which the C# side processes via handlers in BrowserMessages.
  9. Limitations of Virtual Button Events

    main

    While Hold, Repeat, and LongRelease are implemented, certain advanced features related to button state are currently deferred:

    • Encoder-from-button-pairs: The ability to treat button pairs as encoders is currently in the design discussion phase and is not yet implemented.
    • Precondition-facing button state: You cannot currently use a button's current state (e.g., "is this button currently being held") directly as a precondition. Currently, the only workaround is to relay the button state through a MobiFlight Variable.
    • Encoder/Analog distinction: The system currently uses a shared InputEventArgs for Button, Encoder, and Analog events. While this works for most consumers, it means the system does not yet have strictly typed delegates for each specific input type.
  10. How WinCtrl CDU scripts work

    main

    WinCtrl CDU scripts mirror a simulated aircraft's CDU (Control Display Unit) screen to physical WinCtrl CDU hardware.

    When MobiFlight detects a WinCTRL CDU device, it performs the following workflow:

    1. It identifies the currently loaded aircraft.
    2. It checks ScriptMappings.json to find a matching AircraftMatchPattern.
    3. If a match is found, it executes the corresponding Python script (ScriptName).
    4. The Python script fetches display data from the simulator (via SimConnect, X-Plane datarefs, etc.) and sends it to the MobiFlight WebSocket API.

    Matching Logic:

    • MSFS: The pattern is compared against the path component of the aircraft's aircraft.cfg.
    • X-Plane: The pattern is compared against the sim/aircraft/view/acf_ui_name dataref.
    • Patterns can use plain substrings or regular expressions. Ensure patterns are unique so only one script matches any given aircraft.
    {
      "VendorId": "0x4098",
      "ProductIds": [ "WinwingCDUs" ],
      "AircraftMatchPattern": "pmdg 737",
      "ScriptName": "pmdg_737_winwing_cdu.py"
    }
  11. Exception: Using postMessage for UserAuthenticationWebView

    main

    The UserAuthenticationWebView is a deliberate exception to the WebSocket architecture. Because it navigates to foreign origins (Identity Providers) during the login process, it cannot use the local WebSocket server due to origin security restrictions.

    Implementation Details:

    • Outbound: The auth WebView uses window.chrome.webview.postMessage to send commands (e.g., CommandUserAuthentication{state:'cancelled'}) back to the app.
    • Inbound: The FrontendPanel does not inject the WebSocket URL into this WebView. Instead, WebViewMessageReceiver provides a receive-only bridge that routes messages into MessageExchange.PublishReceivedMessage.