• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

MSFS20 Object creation via SimConnect, wasm & GPS tracking map

Messages
7
Country
france
Hello everyone,

I’m working on a project within "MSFS 2020" where the goal is to allow users to create an object (here I try to create a firetruck) at specific locations on the map by clicking on it. The project consists of two main components: a **JavaScript front-end** that manages map interactions and GPS tracking, and a **WASM module** that is supposed to handle communication with **SimConnect** for firetruck creation. No external server like **Node.js** or any local servers are used; the JavaScript communicates directly with the MSFS WASM module.

### Functionality:

1. **GPS Tracking**:
* The map shows the current position of the user's plane in real-time by polling **SimVars** like latitude, longitude, and heading. This part is functioning as expected, and the plane icon updates regularly.
* There’s a toggle button that enables or disables auto-follow mode. When enabled, the map centers on the plane as it moves.
2. **Map Layers**:
* There is a **layer switcher** allowing users to toggle between different map styles (e.g., OpenTopoMap, OpenStreetMap, Thunderforest). This is also working correctly, with the map layers switching and updating without any issues.
3. **Firetruck Creation**:
* When the user clicks on the map, the **latitude and longitude** of the click are sent to the WASM module, where it should trigger the creation of a firetruck at that location using **SimConnect**. The coordinates are correctly passed to the WASM module, and the click event seems to be processed fine in the JavaScript.

### The Problem:

Despite having no apparent errors in the console and with the WASM debugger showing that the module is functioning, **SimConnect is not properly connecting**. Even though the WASM module is supposed to send requests to SimConnect to create a firetruck object, nothing appears in the simulator.

**SimConnect Inspector** does not detect any connection from the WASM module, and there is no evidence that any request is reaching the simulator. The SimConnect functionality is supposed to trigger the creation of a firetruck using `SimConnect_AICreateSimulatedObject`, but it seems SimConnect isn't initialized properly, even though the code runs without immediate errors.

### Debugging Notes:

* **WASM Ready Status**: In the JavaScript code, we check if the WASM module is ready using `Module.ccall('isSimConnectReady')`, and it returns `true`. However, **SimConnect Inspector** shows no active connection or events related to the module, indicating SimConnect may not be correctly set up inside the WASM module.
* **Firetruck Request Fails Silently**: Even though the JavaScript is passing the correct coordinates to the WASM module, the firetruck doesn't appear, and there’s no indication that the `SimConnect_AICreateSimulatedObject` request is reaching MSFS.

### Question:

Does anyone have experience dealing with SimConnect and WASM modules in MSFS? Specifically, why might the **WASM module not be establishing a connection** with SimConnect, despite the code indicating it is ready? Also, how can I further debug or troubleshoot why **SimConnect Inspector** doesn’t recognize my module?

Below is the code for both the JavaScript and WASM modules. Any guidance would be appreciated!

---

**PS**: An explanation or example of how to resolve this issue would be greatly appreciated, as I am open to corrections or suggestions that will help fix this problem.

here my JavaScript & cpp (to be compilated into wasm) codes :

**JS**
JavaScript:
let simvarLoaded = false;
let wasmReady = false;
let map;
let planeMarker;
let followPlane = false;
let tileLayer;
let currentLayer = 1;

function logMessage(message, type = "info") {
    const messageArea = document.getElementById('messageArea');
    const logEntry = document.createElement('div');
    logEntry.className = `log-entry log-${type}`;
    logEntry.innerHTML = message;

    if (messageArea) {
        messageArea.appendChild(logEntry);
        messageArea.scrollTop = messageArea.scrollHeight;
    }

    console.log(message);
}

// Fonction pour enregistrer le callback de log côté WASM
function registerWasmLogCallback() {
    if (typeof Module !== "undefined" && typeof Module._registerLogCallback === "function") {
        Module._registerLogCallback((message) => {
            logMessage(message, "wasm-log");  // Enregistre les logs du WASM
        });
    }
}

// Chargement de SimVar et initialisation de la carte
function loadSimVar() {
    if (typeof SimVar === "undefined") {
        logMessage("Loading SimVar...", "info");
        Include.addScript("/JS/simvar.js", () => {
            setTimeout(() => {
                if (typeof SimVar !== "undefined") {
                    logMessage("[OK] SimVar loaded.", "success");
                    simvarLoaded = true;
                    initializeMap();
                    checkWasmReady();
                } else {
                    logMessage("[ERROR] Failed to load SimVar.", "error");
                }
            }, 1000);
        });
    } else {
        logMessage("[OK] SimVar already loaded.", "success");
        simvarLoaded = true;
        initializeMap();
        checkWasmReady();
    }
}

function checkWasmReady() {
    if (typeof Module !== "undefined" && typeof Module.ccall === "function") {
        try {
            wasmReady = Module.ccall('isSimConnectReady', 'boolean', [], []);
            if (wasmReady) {
                logMessage("[OK] WASM module and SimConnect are ready.", "success");
                registerWasmLogCallback();  // Enregistre le callback WASM pour recevoir les logs
            } else {
                logMessage("[INFO] SimConnect not ready yet. Retrying in 5 seconds...", "info");
                setTimeout(checkWasmReady, 5000);
            }
        } catch (error) {
            logMessage("[ERROR] Error checking WASM readiness: " + error, "error");
            setTimeout(checkWasmReady, 5000);
        }
    } else {
        logMessage("[INFO] WASM module not loaded yet. Retrying in 5 seconds...", "info");
        setTimeout(checkWasmReady, 5000);
    }
}

function updatePlanePosition() {
    if (!simvarLoaded) return;

    try {
        const lat = SimVar.GetSimVarValue("GPS POSITION LAT", "degree latitude");
        const lon = SimVar.GetSimVarValue("GPS POSITION LON", "degree longitude");
        const heading = SimVar.GetSimVarValue("PLANE HEADING DEGREES TRUE", "degrees");

        if (planeMarker) {
            planeMarker.setLatLng([lat, lon]);
            const iconWrapper = planeMarker.getElement().querySelector('.plane-icon-wrapper');
            if (iconWrapper) {
                iconWrapper.style.transform = `rotate(${heading}deg)`;
            }
            if (followPlane) {
                map.setView([lat, lon]);
            }
        }
    } catch (error) {
        logMessage("[ERROR] Error updating plane position: " + error, "error");
    }
}

function initializeMap() {
    const initialLat = SimVar.GetSimVarValue("GPS POSITION LAT", "degree latitude");
    const initialLon = SimVar.GetSimVarValue("GPS POSITION LON", "degree longitude");

    map = L.map('map', {
        minZoom: 2,
        maxZoom: 19,
        zoomControl: true,
        dragging: true
    }).setView([initialLat, initialLon], 14);

    tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
        attribution: 'Map data: © OpenStreetMap contributors, OpenTopoMap'
    }).addTo(map);

    const planeIcon = L.divIcon({
        className: 'plane-icon',
        html: '<div class="plane-icon-wrapper"><img src="/icons/toolbar/ICON_MAP_PLANE_WILDFIRE.svg" alt="Plane"></div>',
        iconSize: [80, 80],
        iconAnchor: [40, 40]
    });
    planeMarker = L.marker([initialLat, initialLon], { icon: planeIcon }).addTo(map);

    map.on('click', function (e) {
        triggerPulseMarker(e.latlng.lat, e.latlng.lng);
        triggerFireTruckCreation(e.latlng.lat, e.latlng.lng);
    });

    initializeToggleButton();
    initializeLayerSwitcher();
    setInterval(updatePlanePosition, 1000);
}

function triggerPulseMarker(lat, lon) {
    const pulseIcon = L.divIcon({
        className: 'vfx-marker',
        html: '<div class="pulse"></div>',
        iconSize: [30, 30],
        iconAnchor: [15, 15]
    });
    const pulseMarker = L.marker([lat, lon], { icon: pulseIcon }).addTo(map);

    setTimeout(() => map.removeLayer(pulseMarker), 2000);
}

function triggerFireTruckCreation(lat, lon) {
    if (!wasmReady) {
        logMessage("[ERROR] WASM module not ready. Cannot create firetruck.", "error");
        return;
    }

    try {
        Module.ccall('updateClickCoordinates', null, ['number', 'number'], [lat, lon]);
        const success = Module.ccall('requestFireTruckCreation', 'boolean', [], []);
      
        if (success) {
            logMessage("[OK] Firetruck creation requested successfully.", "success");
        } else {
            logMessage("[ERROR] Failed to create firetruck.", "error");
        }
    } catch (error) {
        logMessage("[ERROR] Error creating firetruck via WASM: " + error, "error");
    }
}

function initializeToggleButton() {
    const toggleButton = document.getElementById('toggle-follow');
    if (toggleButton) {
        toggleButton.setAttribute('on', "false");
        toggleButton.addEventListener('click', function () {
            followPlane = !followPlane;
            this.setAttribute('on', followPlane);
            this.setAttribute('title', followPlane ? "Désactiver Suivi" : "Activer Suivi");
            logMessage(followPlane ? "[OK] Suivi activé." : "[OK] Suivi désactivé.", "success");
        });
    } else {
        logMessage("[ERREUR] Bouton suivi non trouvé.", "error");
    }
}

function initializeLayerSwitcher() {
    const layerSwitchButton = document.getElementById('layer-switch');
    if (layerSwitchButton) {
        layerSwitchButton.addEventListener('click', function () {
            currentLayer = (currentLayer % 3) + 1;
            switch (currentLayer) {
                case 1:
                    map.removeLayer(tileLayer);
                    tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
                        attribution: 'Map data: © OpenStreetMap contributors, OpenTopoMap'
                    }).addTo(map);
                    break;
                case 2:
                    map.removeLayer(tileLayer);
                    tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                        attribution: 'Map data: © OpenStreetMap contributors'
                    }).addTo(map);
                    break;
                case 3:
                    map.removeLayer(tileLayer);
                    tileLayer = L.tileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', {
                        attribution: 'Map data: © Thunderforest'
                    }).addTo(map);
                    break;
            }
            logMessage(`Couches de carte : ${currentLayer}`, "info");
        });
    } else {
        logMessage("[ERREUR] Bouton switch non trouvé.", "error");
    }
}

window.onload = function () {
    logMessage("Loading...", "info");
    loadSimVar();
};

**wasm**
C:
#include <MSFS/MSFS.h>
#include <MSFS/MSFS_WindowsTypes.h>
#include <MSFS/Legacy/gauges.h>
#include <SimConnect.h>
#include <stdio.h>
#include <string.h>
#include <WASMModule.h>

// Déclaration d'un pointeur de fonction pour le callback vers JavaScript
typedef void (*LogCallbackType)(const char* message);
LogCallbackType logCallback = nullptr;

HANDLE hSimConnect = NULL;
bool simConnectConnected = false;
static double clickLat = 0.0;
static double clickLon = 0.0;

// Fonction pour initialiser la connexion à SimConnect
void initSimConnect() {
    HRESULT hr = SimConnect_Open(&hSimConnect, "wildfirePanel", NULL, 0, 0, 0);
    if (SUCCEEDED(hr)) {
        simConnectConnected = true;
        if (logCallback) logCallback("[WASM] Connexion à SimConnect réussie.");
    }
    else {
        simConnectConnected = false;
        if (logCallback) logCallback("[WASM] Échec de la connexion à SimConnect.");
    }
}

// Fonction pour vérifier si SimConnect est prêt
extern "C" bool isSimConnectReady() {
    if (logCallback) logCallback("[WASM] Vérification de la connexion à SimConnect.");
    return simConnectConnected;
}

// Fonction appelée pour créer un camion de pompier aux coordonnées (lat, lon)
extern "C" bool requestFireTruckCreation() {
    if (logCallback) logCallback("[WASM] Demande de création du camion de pompier...");

    if (!simConnectConnected) {
        if (logCallback) logCallback("[WASM] SimConnect non connecté. Impossible de créer le camion.");
        return false;
    }

    // Initialisation de la position
    SIMCONNECT_DATA_INITPOSITION init;
    init.Latitude = clickLat;
    init.Longitude = clickLon;
    init.Altitude = 0;  // Par défaut au sol
    init.Pitch = 0;
    init.Bank = 0;
    init.Heading = 0;
    init.OnGround = 1;  // Assurez-vous que l'objet est sur le sol
    init.Airspeed = 0;

    // Création du camion de pompier via SimConnect
    HRESULT hr = SimConnect_AICreateSimulatedObject(
        hSimConnect,
        "ASO_Firetruck01",   // Nom de l'objet à créer
        init,                // Données de position initiale
        0                    // Aucun flag supplémentaire
    );

    if (SUCCEEDED(hr)) {
        if (logCallback) logCallback("[WASM] Camion de pompier créé avec succès.");
        return true;
    }
    else {
        if (logCallback) logCallback("[WASM] Échec de la création du camion de pompier.");
        return false;
    }
}

// Fonction pour mettre à jour les coordonnées du clic depuis JavaScript
extern "C" void updateClickCoordinates(double lat, double lon) {
    clickLat = lat;
    clickLon = lon;
    if (logCallback) logCallback("[WASM] Coordonnées de clic mises à jour.");
}

// Fonction pour enregistrer le callback de log
extern "C" void registerLogCallback(LogCallbackType callback) {
    logCallback = callback;
}

// Fonction d'initialisation appelée lorsque WASM est chargé
extern "C" void module_init() {
    if (logCallback) logCallback("[WASM] Initialisation du module WASM.");
    initSimConnect();  // Initialisation de SimConnect à ce moment-là
}

// Fonction de nettoyage appelée lorsque le module est déchargé
extern "C" void module_deinit() {
    if (logCallback) logCallback("[WASM] Déchargement du module WASM.");
    if (simConnectConnected) {
        SimConnect_Close(hSimConnect);
        if (logCallback) logCallback("[WASM] Connexion SimConnect fermée.");
    }
}

**Ingame pictures**
 
Last edited:
Hello Cheyenne,

I think there's some confusion here... maybe it's on my part, but I suspect not at this point.. :)

The WASM modules that run inside MSFS itself (such as you have there) cannot be called from JavaScript like that... they're running in a different process altogether. In fact Asobo had to invent a new communication protocol for sending messages between JS and these "back end" WASM modules: Communication API

In the JavaScript code, we check if the WASM module is ready using `Module.ccall('isSimConnectReady')`, and it returns `true`.

From looking at what you posted, I suspect that checkWasmReady() never gets past the first condition of

if (typeof Module !== "undefined" && typeof Module.ccall === "function")

Otherwise the output area would show something else besides "[ERROR] WASM module not ready. Cannot create firetruck."

Specifically, why might the **WASM module not be establishing a connection** with SimConnect

As for why SimConnect isn't connecting, I suspect it is because you're missing the `MSFS_CALLBACK` macro prefix on the functions which are supposed to be exported, like module_init(). So that never gets called, though otherwise the module is loading fine.

C++:
extern "C" MSFS_CALLBACK void module_init(void) { ... }

(MSFS_CALLBACK is from MSFS/MSFS.h)

You should get some logging going within the module code itself (not relying on the JS bridge), by printing to stdout (using std::cout << calls or whatever) -- these will show up in the MSFS Dev View Console window (just make sure they're not filtered out in the view).

Hope that helps,
-Max
 
Thank you MAX 👍 for the information and the codes. Now WASM makes sense to me :), and I can diagnose more; however, no fire truck is created, and JavaScript continues to return error messages. Yet, SimConnect is well connected, according to the console log. I will continue to investigate. I still do not wish to use a local network for such a small number of requested functions. SimConnect Inspector displays the proxy, but no information appears in the tabs—neither DefineID in the data definition nor anything elsewhere. So I'll keep digging to pinpoint the issue.

look at screenshots :
 
Hi Cheyenne, glad that was helpful!

So what does your code look like now? Did you set up that "Communication API" stuff?

AFAIK there's no other way to communicate with a WASM module from JS/in-game panel. You'll need to set up a basic message exchange "protocol."

Of course if there was a JS API for creating objects, you could just use that directly perhaps... but I don't know that there is. 🤷‍♂️

Cheers,
-Max
 
Hello Max,

Thank you so much for your help, it’s greatly appreciated!

I've set up the "Communication API" as you suggested, but I still encounter issues. My main goal is to create a VFX effect, with the fire truck "ASO_Firetruck01" being just a reference object for testing. I've also tried using the VFX API, but I’m struggling with whether to use the object name or the GUID to create it.

Could you provide more guidance? 🥺 Here's the updated code and some screenshots for reference.



Project folder organisation :
| layout.json
| manifest.json
| package.xml
|
+---PackageDefinitions
| | cheyennedesign-wildfires.xml
| |
| \---cheyennedesign-wildfires
| \---ContentInfo
| Thumbnail.jpg
|
\---PackageSources
+---html_UI
| +---icons
| | \---toolbar
| | ICON_MAP_PLANE_WILDFIRE.svg
| | ICON_TOOLBAR_WILDFIRE.svg
| |
| \---InGamePanels
| \---wildfirePanel
| wildfirePanel.css
| wildfirePanel.html
| wildfirePanel.js
|
+---InGamePanels
| wildfirePanel.xml
|
\---modules
wildfirePanel.wasm


New JS :

JavaScript:
let simvarLoaded = false;
let wasmReady = false;
let map;
let planeMarker;
let followPlane = false;
let tileLayer;
let currentLayer = 1;

// Fonction pour afficher des messages de journalisation
function logMessage(message, type = "info") {
    const messageArea = document.getElementById('messageArea');
    const logEntry = document.createElement('div');
    logEntry.className = `log-entry log-${type}`;
    logEntry.innerHTML = message;

    if (messageArea) {
        messageArea.appendChild(logEntry);
        messageArea.scrollTop = messageArea.scrollHeight;
    }

    console.log(message);
}

// Chargement de SimVar si non encore chargé
function loadSimVar() {
    if (typeof SimVar === "undefined") {
        logMessage("Loading SimVar...", "info");
        Include.addScript("/JS/simvar.js", () => {
            setTimeout(() => {
                if (typeof SimVar !== "undefined") {
                    logMessage("[OK] SimVar loaded.", "success");
                    simvarLoaded = true;
                    initializeMap();
                    checkWasmReady();
                } else {
                    logMessage("[ERROR] Failed to load SimVar.", "error");
                }
            }, 1000);
        });
    } else {
        logMessage("[OK] SimVar already loaded.", "success");
        simvarLoaded = true;
        initializeMap();
        checkWasmReady();
    }
}

// Vérification si le module WASM est prêt
function checkWasmReady() {
    if (typeof Module !== "undefined" && typeof Module.ccall === "function") {
        try {
            wasmReady = Module.ccall('isSimConnectReady', 'boolean', [], []);
            if (wasmReady) {
                logMessage("[OK] WASM module and SimConnect are ready.", "success");
            } else {
                logMessage("[INFO] SimConnect not ready yet. Retrying in 5 seconds...", "info");
                setTimeout(checkWasmReady, 5000);
            }
        } catch (error) {
            logMessage("[ERROR] Error checking WASM readiness: " + error, "error");
            setTimeout(checkWasmReady, 5000);
        }
    } else {
        logMessage("[INFO] WASM module not fully loaded yet. Retrying in 5 seconds...", "info");
        setTimeout(checkWasmReady, 5000);
    }
}

// Mise à jour de la position de l'avion sur la carte
function updatePlanePosition() {
    if (!simvarLoaded) return;

    try {
        const lat = SimVar.GetSimVarValue("GPS POSITION LAT", "degree latitude");
        const lon = SimVar.GetSimVarValue("GPS POSITION LON", "degree longitude");
        const heading = SimVar.GetSimVarValue("PLANE HEADING DEGREES TRUE", "degrees");

        if (planeMarker) {
            planeMarker.setLatLng([lat, lon]);
            const iconWrapper = planeMarker.getElement().querySelector('.plane-icon-wrapper');
            if (iconWrapper) {
                iconWrapper.style.transform = `rotate(${heading}deg)`;
            }
            if (followPlane) {
                map.setView([lat, lon]);
            }
        }
    } catch (error) {
        logMessage("[ERROR] Error updating plane position: " + error, "error");
    }
}

// Initialisation de la carte avec Leaflet
function initializeMap() {
    const initialLat = SimVar.GetSimVarValue("GPS POSITION LAT", "degree latitude");
    const initialLon = SimVar.GetSimVarValue("GPS POSITION LON", "degree longitude");

    map = L.map('map', {
        minZoom: 2,
        maxZoom: 19,
        zoomControl: true,
        dragging: true
    }).setView([initialLat, initialLon], 14);

    tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
        attribution: 'Map data: © OpenStreetMap contributors, OpenTopoMap'
    }).addTo(map);

    const planeIcon = L.divIcon({
        className: 'plane-icon',
        html: '<div class="plane-icon-wrapper"><img src="/icons/toolbar/ICON_MAP_PLANE_WILDFIRE.svg" alt="Plane"></div>',
        iconSize: [80, 80],
        iconAnchor: [40, 40]
    });
    planeMarker = L.marker([initialLat, initialLon], { icon: planeIcon }).addTo(map);

    map.on('click', function (e) {
        triggerPulseMarker(e.latlng.lat, e.latlng.lng);
        triggerFireTruckCreation(e.latlng.lat, e.latlng.lng);
    });

    initializeToggleButton();
    initializeLayerSwitcher();
    setInterval(updatePlanePosition, 1000);
}

// Affichage d'un marqueur d'impulsion sur la carte
function triggerPulseMarker(lat, lon) {
    const pulseIcon = L.divIcon({
        className: 'vfx-marker',
        html: '<div class="pulse"></div>',
        iconSize: [30, 30],
        iconAnchor: [15, 15]
    });
    const pulseMarker = L.marker([lat, lon], { icon: pulseIcon }).addTo(map);

    setTimeout(() => map.removeLayer(pulseMarker), 2000);
}

// Création d'un camion de pompier à la position cliquée
function triggerFireTruckCreation(lat, lon) {
    if (!wasmReady) {
        logMessage("[ERROR] WASM module not ready. Cannot create firetruck.", "error");
        return;
    }

    try {
        Module.ccall('updateClickCoordinates', null, ['number', 'number'], [lat, lon]);
        const success = Module.ccall('requestFireTruckCreation', 'boolean', [], []);
       
        if (success) {
            logMessage("[OK] Firetruck creation requested successfully.", "success");
        } else {
            logMessage("[ERROR] Failed to create firetruck.", "error");
        }
    } catch (error) {
        logMessage("[ERROR] Error creating firetruck via WASM: " + error, "error");
    }
}

// Initialisation du bouton de suivi de l'avion
function initializeToggleButton() {
    const toggleButton = document.getElementById('toggle-follow');
    if (toggleButton) {
        toggleButton.setAttribute('on', "false");
        toggleButton.addEventListener('click', function () {
            followPlane = !followPlane;
            this.setAttribute('on', followPlane);
            this.setAttribute('title', followPlane ? "Désactiver Suivi" : "Activer Suivi");
            logMessage(followPlane ? "[OK] Suivi activé." : "[OK] Suivi désactivé.", "success");
        });
    } else {
        logMessage("[ERREUR] Bouton suivi non trouvé.", "error");
    }
}

// Initialisation du switcher de couches de carte
function initializeLayerSwitcher() {
    const layerSwitchButton = document.getElementById('layer-switch');
    if (layerSwitchButton) {
        layerSwitchButton.addEventListener('click', function () {
            currentLayer = (currentLayer % 3) + 1;
            switch (currentLayer) {
                case 1:
                    map.removeLayer(tileLayer);
                    tileLayer = L.tileLayer('https://{s}.tile.opentopomap.org/{z}/{x}/{y}.png', {
                        attribution: 'Map data: © OpenStreetMap contributors, OpenTopoMap'
                    }).addTo(map);
                    break;
                case 2:
                    map.removeLayer(tileLayer);
                    tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                        attribution: 'Map data: © OpenStreetMap contributors'
                    }).addTo(map);
                    break;
                case 3:
                    map.removeLayer(tileLayer);
                    tileLayer = L.tileLayer('https://{s}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', {
                        attribution: 'Map data: © Thunderforest'
                    }).addTo(map);
                    break;
            }
            logMessage(`Couches de carte : ${currentLayer}`, "info");
        });
    } else {
        logMessage("[ERREUR] Bouton switch non trouvé.", "error");
    }
}

// Démarrage du script
window.onload = function () {
    logMessage("Loading...", "info");
    loadSimVar();
};

New cpp (actual wasm test version) :
C:
#include <MSFS/MSFS.h>
#include <MSFS/MSFS_WindowsTypes.h>
#include <MSFS/Legacy/gauges.h>
#include <SimConnect.h>
#include <stdio.h>
#include <string.h>
#include <WASMModule.h>
#include <iostream>

HANDLE hSimConnect = NULL;
bool simConnectConnected = false;
static double clickLat = 0.0;
static double clickLon = 0.0;
static int callCounter = 0;

// Fonction pour initialiser la connexion à SimConnect
MSFS_CALLBACK void initSimConnect() {
    HRESULT hr = SimConnect_Open(&hSimConnect, "wildfirePanel", NULL, 0, 0, 0);
    if (SUCCEEDED(hr)) {
        simConnectConnected = true;
        std::cout << "[wildfire WASM] Connexion à SimConnect réussie." << std::endl;
    }
    else {
        simConnectConnected = false;
        std::cout << "[wildfire WASM] Échec de la connexion à SimConnect. HRESULT: 0x" << std::hex << hr << std::endl;
    }
}

// Fonction pour vérifier si SimConnect est prêt
extern "C" MSFS_CALLBACK bool isSimConnectReady() {
    callCounter++;
    std::cout << "[wildfire WASM] isSimConnectReady appelé " << callCounter << " fois. Statut: "
        << (simConnectConnected ? "Connecté" : "Non connecté") << std::endl;
    return simConnectConnected;
}

// Fonction appelée pour créer un camion de pompier aux coordonnées (lat, lon)
extern "C" MSFS_CALLBACK bool requestFireTruckCreation() {
    std::cout << "[wildfire WASM] Début de requestFireTruckCreation" << std::endl;
    if (!simConnectConnected) {
        std::cout << "[wildfire WASM] SimConnect non connecté. Impossible de créer le camion de pompier." << std::endl;
        return false;
    }

    // Initialisation de la position
    SIMCONNECT_DATA_INITPOSITION initPos;
    initPos.Latitude = clickLat;
    initPos.Longitude = clickLon;
    initPos.Altitude = 0;  // Par défaut au sol
    initPos.Pitch = 0;
    initPos.Bank = 0;
    initPos.Heading = 0;
    initPos.OnGround = 1;  // L'objet doit être sur le sol
    initPos.Airspeed = 0;

    // Utilisation d'un ID de requête unique
    HRESULT hr = SimConnect_AICreateSimulatedObject(
        hSimConnect,
        "ASO_Firetruck01",   // Nom de l'objet
        initPos,             // Structure d'initialisation de la position
        SIMCONNECT_UNUSED    // Aucun ID de requête spécifique (utilisation de SIMCONNECT_UNUSED)
    );

    if (SUCCEEDED(hr)) {
        std::cout << "[wildfire WASM] Demande de création du camion de pompier envoyée avec succès." << std::endl;
        return true;
    }
    else {
        std::cout << "[wildfire WASM] Échec de la création du camion de pompier, HRESULT: 0x" << std::hex << hr << std::endl;
        return false;
    }
}

// Fonction pour mettre à jour les coordonnées du clic depuis JavaScript
extern "C" MSFS_CALLBACK void updateClickCoordinates(double lat, double lon) {
    std::cout << "[wildfire WASM] updateClickCoordinates appelé avec Lat=" << lat << ", Lon=" << lon << std::endl;
    clickLat = lat;
    clickLon = lon;
}

// Fonction d'initialisation appelée lorsque WASM est chargé
extern "C" MSFS_CALLBACK void module_init() {
    std::cout << "[wildfire WASM] Début de l'initialisation du module WASM." << std::endl;
    initSimConnect();
    std::cout << "[wildfire WASM] Fin de l'initialisation du module WASM." << std::endl;
}

// Fonction de nettoyage appelée lorsque le module est déchargé
extern "C" MSFS_CALLBACK void module_deinit() {
    std::cout << "[wildfire WASM] Début du déchargement du module WASM." << std::endl;
    if (simConnectConnected) {
        SimConnect_Close(hSimConnect);
        std::cout << "[wildfire WASM] Connexion SimConnect fermée." << std::endl;
    }
    std::cout << "[wildfire WASM] Module WASM complètement déchargé." << std::endl;
}
 
Hi Cheyenne,

I'm not seeing any Communication API code in here... 🧐

For example to set the JS value wasmReady from within the WASM module you'd need:

In JS:
JavaScript:
function wasmReadyCallback() {
    wasmReady = true;
}

const wasmListener = RegisterViewListener('JS_LISTENER_COMM_BUS');
wasmListener.on("WASMReady", wasmReadyCallback);

In WASM C++:
C++:
#include <MSFS/MSFS_CommBus.h>

fsCommBusCall("WASMReady", null, 0, FsCommBusBroadcastFlags::FsCommBusBroadcast_JS);


To trigger the truck creation from within the JS panel, you could use something like this:

WASM/C++:
C++:
#include "rapidjson/document.h"

void  createFireTruck(const char* args, unsigned int /* size */, void* /* ctx */) {
    // parse JSON argument
    Document d;
    d.Parse(args);
    const double lat = d["lat"].GetDouble();
    const double lon = d["lon"].GetDouble();
 
    // proceed with truck object creation at given position
    // ....
}

extern "C" MSFS_CALLBACK module_init() {
    // .... other initi stuff
 
    // register callback
    fsCommBusRegister("CreateFireTruck", createFireTruck);
}
JavaScript:
// register a listener
const wasmListener = RegisterViewListener('JS_LISTENER_COMM_BUS');

// call the listener callback
Coherent.call("COMM_BUS_WASM_CALLBACK", "CreateFireTruck", '{"lat": 42.222, "lon": 23.123}');
// or
wasmListener.call("COMM_BUS_WASM_CALLBACK", "CreateFireTruck", '{"lat": 42.222, "lon": 23.123}');

The examples are simplified and completely untested or linted (and I've never even used RapidJSON).


HTH,
-Max
 
Last edited:
PS. I just checked out the "CommBusAircraft" example from the latest MSFS SDK and it seems the JS API side has changed from what is currently documented at https://docs.flightsimulator.com/html/Programming_Tools/WASM/Communication_API/Communication_API.htm
Or maybe that also still works, but looking at their examples on the docs page again made me wonder about RegisterViewListener() usage in my 2nd example (what's the point of calling it there?).

There is now a new function in the JS SDK, RegisterCommBusListener(). For which I can't find any docs. Seems like it returns an instance of a "listener," like RegisterViewListener() does/did. But its (optional) argument is a callback function. (Why a callback? No idea... if it's an async process to create a listener, then what is RegisterCommBusListener() returning? Unclear.)

Anyway, apparently you can then use that listener instance to both register JS event listeners (for handling WASM events) and call WASM functions.

Adapting my previous examples in combination with examples from the CommBusAircraft:

JavaScript:
// connect listener
const commBusListener = RegisterCommBusListener(() => {
    console.log("JS_LISTENER_COMM_BUS register");
});


// function to invoke CreateFireTruck on WASM side
sendToWasm() {
    var data = JSON.stringify({ lat: 42.222, lon: 23.123 });
    commBusListener.callWasm("CreateFireTruck", data);
}

// example callback to be invoked from WASM side
wasmReadyCallback() {
    wasmReady = true;
}

// connect listener for "WASMReady" WASM side event
commBusListener.on("WASMReady", wasmReadyCallback);

The WASM/C++ side is unchanged.
 
Hi Max,

I’m currently working on a test platform to establish two-way communication between a WASM module and JavaScript in Microsoft Flight Simulator, using the CommBus system ; To obviously eventually return to my initial project, because I need to know how to resolve this problem of transverse communication.

My Objective:
WASM Module: I've created a WASM module that registers an event using "fsCommBusRegister" to listen for messages from JavaScript.
JavaScript Side: JavaScript should be able to send messages to the WASM module, and the WASM should respond via "fsCommBusCall".

The Challenges I’m Facing:
Callback Not Triggering: Although the event is registered correctly, the callback function ("JsToWasmCallback") in the WASM module does not seem to be triggered when JavaScript sends messages.
JavaScript Can't Receive Messages: On the JavaScript side, I'm unable to properly capture or listen to events sent from the WASM module using CommBus.
... and I still can't solve this problem of decommited memory on the wasm (see wasm debugger)

What I’ve Tried:
I’ve reviewed the official MSFS SDK documentation, especially the sections on WASM and JavaScript communication via CommBus. I’m confident that I’m using the correct function signatures ("fsCommBusRegister", "fsCommBusCall"), and I’ve added extensive logging to track the events.

I’m not using Emscripten, which seems to be the focus in some community discussions, so I might be missing something specific to the setup I have.

Could you offer any guidance on how to troubleshoot or better implement this communication? I’d appreciate any advice on getting the CommBus system to work reliably between JavaScript and WASM in MSFS.

here my codes :

cpp (wasm)

C:
#include <MSFS/MSFS.h>
#include <MSFS/MSFS_WindowsTypes.h>
#include <MSFS/Legacy/gauges.h>
#include <SimConnect.h>
#include <MSFS/MSFS_CommBus.h>
#include <iostream>
#include <string>
#include <cstring>

// Structure globale de l'application
struct AppState {
    HANDLE hSimConnect = INVALID_HANDLE_VALUE;
    bool simConnectConnected = false;
    bool wasmReady = false;
    bool panelActive = false;  // Nouvel état pour suivre l'activité du panneau
} g_state;

// Prototypes des fonctions
void InitializeSimConnect();
void SendMessageToJS(const std::string& message);
void HandleJsToWasmEvent(const char* buf, unsigned int bufSize);

// Callback pour les événements de JS vers WASM
extern "C" void MSFS_CALLBACK JsToWasmCallback(const char* buf, unsigned int bufSize, void* ctx) {
    HandleJsToWasmEvent(buf, bufSize);
}

// Fonction d'initialisation du module WASM
extern "C" MSFS_CALLBACK void module_init() {
    std::cout << "[WASM] Module initialized. Registering communication events..." << std::endl;
    InitializeSimConnect();

    bool success = fsCommBusRegister("JsToWasmEvent", JsToWasmCallback, nullptr);
    if (success) {
        std::cout << "[WASM] CommBus registration successful." << std::endl;
    }
    else {
        std::cout << "[WASM] CommBus registration failed." << std::endl;
    }

    g_state.wasmReady = true;
    std::cout << "[WASM] WASM is ready. Waiting for JS messages..." << std::endl;
    SendMessageToJS("WASM module initialized and ready");
}

// Initialisation de SimConnect
void InitializeSimConnect() {
    HRESULT hr = SimConnect_Open(&g_state.hSimConnect, "WASM Module", nullptr, 0, 0, 0);
    if (SUCCEEDED(hr)) {
        g_state.simConnectConnected = true;
        std::cout << "[WASM] SimConnect connected successfully." << std::endl;
    }
    else {
        g_state.simConnectConnected = false;
        std::cout << "[WASM] Failed to connect to SimConnect. Error code: " << hr << std::endl;
    }
}

// Fonction pour envoyer un message à JavaScript
void SendMessageToJS(const std::string& message) {
    std::cout << "[WASM] Sending message to JS: " << message << std::endl;
    bool success = fsCommBusCall("WasmToJsEvent", message.c_str(), message.length(), FsCommBusBroadcastFlags::FsCommBusBroadcast_JS);
    std::cout << "[WASM] Message sent successfully: " << (success ? "Yes" : "No") << std::endl;
}

// Gestion des messages JS vers WASM
void HandleJsToWasmEvent(const char* buf, unsigned int bufSize) {
    std::string message(buf, bufSize);
    std::cout << "[WASM] Received message from JS: " << message << std::endl;

    // Vérifier si le message concerne l'état du panneau
    if (message == "PanelOpened") {
        g_state.panelActive = true;
        std::cout << "[WASM] Panel is now active." << std::endl;
    }
    else if (message == "PanelClosed") {
        g_state.panelActive = false;
        std::cout << "[WASM] Panel is now inactive." << std::endl;
    }

    // Répondre à JS
    SendMessageToJS("Message received by WASM: " + message);
}

// Fonction de mise à jour du module WASM (pour SimConnect)
extern "C" MSFS_CALLBACK void module_update() {
    if (g_state.simConnectConnected) {
        SIMCONNECT_RECV* pData;
        DWORD cbData;
        while (SUCCEEDED(SimConnect_GetNextDispatch(g_state.hSimConnect, &pData, &cbData))) {
            // Traiter les messages SimConnect si nécessaire
        }
    }

    // Vérifier périodiquement l'état du panneau et le forcer à rester ouvert si nécessaire
    if (g_state.panelActive) {
        SendMessageToJS("KeepPanelOpen");
    }
}

// Fonction de nettoyage du module WASM
extern "C" MSFS_CALLBACK void module_deinit() {
    if (g_state.hSimConnect != INVALID_HANDLE_VALUE) {
        SimConnect_Close(g_state.hSimConnect);
        g_state.hSimConnect = INVALID_HANDLE_VALUE;
        g_state.simConnectConnected = false;
        std::cout << "[WASM] SimConnect disconnected." << std::endl;
    }
    g_state.wasmReady = false;
    g_state.panelActive = false;
    SendMessageToJS("WASM module deinitialized");
}

JS

JavaScript:
let simvarLoaded = false;
let simConnectConnected = false;
let wasmReady = false;
let testLoopInterval = 3000; // Intervalle de 3 secondes pour mettre à jour les tests

// Fonction pour afficher les messages dans la zone de texte dynamique et la console MSFS
function logMessage(message, type = "info", source = "JS") {
    const messageArea = document.getElementById('messageArea');
    const logEntry = document.createElement('div');
    logEntry.className = `log-entry log-${type}`;
    logEntry.innerHTML = `[${source}]: ${message}`;

    if (messageArea) {
        messageArea.appendChild(logEntry);
        messageArea.scrollTop = messageArea.scrollHeight;
    }

    console.log(`[${source}]: ${message}`);  // Log dans la console MSFS
}

// Fonction de test périodique des connexions et communications
function startTestLoop() {
    setInterval(() => {
        // Test SimConnect
        testSimConnect();

        // Test SimVar
        testSimVar();

        // Test communication JS vers WASM
        testJS2WASM();

        // Test communication WASM vers JS
        testWASM2JS();
    }, testLoopInterval);
}

// Test de la connexion SimConnect
function testSimConnect() {
    if (simConnectConnected) {
        logMessage("SimConnect connected successfully.", "success", "JS");
    } else {
        logMessage("SimConnect not connected.", "error", "JS");
    }
}

// Test de la connexion SimVar
function testSimVar() {
    if (simvarLoaded) {
        logMessage("SimVar loaded successfully.", "success", "JS");
    } else {
        logMessage("SimVar not loaded.", "error", "JS");
    }
}

// Test de la communication JS vers WASM
function testJS2WASM() {
    if (wasmReady) {
        logMessage("JS to WASM communication working.", "success", "JS");
        // Envoi d'un message au module WASM via le CommBus
        SendMessageToWASM("Hello from JS");
    } else {
        logMessage("JS to WASM communication failed.", "error", "JS");
    }
}

// Fonction pour envoyer un message à WASM
function SendMessageToWASM(message) {
    logMessage(`Sending message to WASM: ${message}`, "info", "JS");
    // Utilisation de fsCommBusCall pour envoyer un message à WASM
    fsCommBusCall('JsToWasmEvent', message, message.length, FsCommBusBroadcastFlags.FsCommBusBroadcast_WASM);
}

// Test de la communication WASM vers JS
function testWASM2JS() {
    if (window.CommBus) {
        CommBus.on("WasmToJsEvent", (message) => {
            logMessage(`Received message from WASM: ${message}`, "info", "WASM");
        });
    } else {
        logMessage("WASM to JS communication not available.", "error", "JS");
    }
}

// Lancer la boucle de test après le chargement de la page
window.onload = function () {
    logMessage("Page loaded. Waiting for WASM communication...", "info", "JS");
    startTestLoop(); // Démarrer la boucle de tests périodiques
};

Here my documentation :
SDK Communication_API page
Crate MSFS

Some project screenshots :
 
Hi Cheyenne,

Looks like you made good progress!

OK, the WASM side looks pretty good, with just a couple minor comments. I'll tackle that first.
(I'm just picking out what I see offhand... not actually testing any code.)

extern "C" void MSFS_CALLBACK
You only need those on module_init() and module_deinit().

bool success = fsCommBusCall("WasmToJsEvent", message.c_str(), message.length(), FsCommBusBroadcastFlags::FsCommBusBroadcast_JS);
You want to message.length() + 1 for the size argument to account for the resulting char array null terminator.

extern "C" MSFS_CALLBACK void module_update()
I'm not familiar with this callback.... do you have more info about this?
To process incoming SimConnect messages in a WASM modules you'd typically use a different way...
C++:
// set up a message processing function
void CALLBACK dispatchMessage(SIMCONNECT_RECV* pData, DWORD cbData, void*)
{
    switch (pData->dwID)
    {
        case SIMCONNECT_RECV_ID_OPEN:
            g_state.simConnectConnected) = true;
            break;
           
        case SIMCONNECT_RECV_ID_EXCEPTION: {
            SIMCONNECT_RECV_EXCEPTION *data = reinterpret_cast<SIMCONNECT_RECV_EXCEPTION *>(pData);
            std::cout << "SimConnect exception: " << data->dwException << std::endl;
            break;
        }

       // handle other message types.... 
       // if you need an "event loop" you could SubscribeToSystemEvent() for one of the recurring events like "frame"
    }
}

void InitializeSimConnect(() {
    // SimConnect_Open(), etc...
    // After connecting to SimConnect, call the message dispatcher function once.
    // The callback will get invoked automatically any time there is a message ready.
    // There is no need to put it inside a loop when used in a WASM module.
    // https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/General/SimConnect_CallDispatch.htm
    SimConnect_CallDispatch(g_state.hSimConnect, dispatchMessage, nullptr)
}

void module_deinit() { ....
SendMessageToJS("WASM module deinitialized");
Careful sending anything out after the module has be de-initialized... in a totally different situation I've seen it hang up the whole sim, preventing it from quitting. A sim bug, obviously, but... 🤷‍♂️

OK, on the JS side.... I'm not really sure what's going on there :) And what does the Rust crate have to do with it? :p (Funny though, I was just working on a Rust project, which I don't typically do.)

But you're not too far off... I'm just showing the changed bits here:

JavaScript:
// Add a global for the comm bus listener/sender (assumes the CommBus JS libs are already loaded by the panel page)
const commBusListener = RegisterCommBusListener(() => {
    console.log("commBusListener registered");
});

function startTestLoop() {
    // ...
    // remove testWASM2JS();
}

function testWASM2JS(message) {
    if (message.startsWith("WASM module initialized"))
        wasmReady = true;
    else if (message == "SimConnectReady")  // hypothetical, not in current WASM code
        simConnectConnected = = true;
    logMessage(`Received message from WASM: ${message}`, "info", "WASM");
}

function SendMessageToWASM(message) {
    logMessage(`Sending message to WASM: ${message}`, "info", "JS");
    // Utilisation de CommBusListener pour envoyer un message à WASM
    commBusListener.callWasm('JsToWasmEvent', message);
}

window.onload = function () {
    logMessage("Page loaded. Waiting for WASM communication...", "info", "JS");

    // connect listener for "WasmToJsEvent" WASM-side event
    commBusListener.on("WasmToJsEvent", testWASM2JS);
    
    startTestLoop(); // Démarrer la boucle de tests périodiques
};

Again, not tested nor linted... just going by what's documented, basically. If you had a whole test project zipped up, that would help testing.... though I can't promise anything! :)

Kinda peaked my interest with this to see if I could add a "bridge" to the JS APIs via my WASimCommander system (invoking JS APIs features remotely from outside the sim).

Anyway, hope that helps!
-Max
 
and I still can't solve this problem of decommited memory on the wasm

PS. I have no idea what that memory usage is showing you... and I don't see any issues in the code. Your g_state struct is 64B and that would be on the stack anyway... what's on the heap? Probably some MSFS internals? SimConnect? Does the memory usage change during runtime (probably more interesting once you have the comm stuff working)? What if you don't init SimConnect at all?

Now I'll have to go look at what my own module reports there... thanks a lot! :-P
 
Back
Top