aboutsummaryrefslogblamecommitdiff
path: root/xplane.mjs
blob: c7aaebcfefb246afd1768e01fafff2b2c978ca99 (plain) (tree)
1
2
3
4
5
6
7
8
9
                               
 
                     





                                 

                                                 
















                                                                                                             

                                     
                                                         



                                                           
                                           











                                                                            
                                                                         
                                                      



                            
 

                                                       






                                                                 







                               











                                                             
                                                                            

                                    
                                                                      
 















                                                           








                                                          
 
import dgram from "node:dgram";

export class XPlane {
    constructor(
        xplaneAddr = "localhost",
        xplanePort = 49000,
        statusCheck = 5000,
        statusTimeout = 1000,
    ) {
        this.socket = dgram.createSocket("udp4");
        this.subscribed = [];
        this.lastReceived = null;
        const xplane = this;
        this.statusChecker = setTimeout(async function statusChecker() {
            let active = false;
            if (xplane.lastReceived) {
                if (new Date() - xplane.lastReceived < statusTimeout) {
                    active = true;
                }
            }
            if (!active) {
                //console.info(
                //    `x-plane data not detected, will try again in ${(statusCheck / 1000).toFixed(2)} secs`,
                //);
                await xplane._subscribeAll();
            }
            xplane.statusChecker = setTimeout(statusChecker, statusCheck);
        }, statusCheck);
        this.xplaneAddr = xplaneAddr;
        this.xplanePort = xplanePort;
        this.socket.on("message", async (msg, rinfo) => {
            if (msg.subarray(0, 5).toString() != "RREF,") {
                console.info("dropping unrelated message");
                return;
            }
            this.lastReceived = new Date();
            let num = (msg.length - 5) / 8;
            for (let i = 0; i < num; i++) {
                const idx = msg.readInt32LE(5 + i * 8);
                if (idx < 0) {
                    console.info(`sender index ${idx} should be >= 0`);
                    return;
                }
                if (idx >= this.subscribed.length) {
                    console.info(`sender index ${idx} > subscribed.length`);
                    return;
                }
                const v = msg.readFloatLE(9 + i * 8);
                //console.info(`${this.subscribed[idx].dataRef} = ${v}`);
                await this.subscribed[idx].handler(v);
            }
        });
        this.socket.bind(0);
    }

    async _subscribeDataRef(idx) {
        const { dataRef, freq } = this.subscribed[idx];
        let buffer = Buffer.alloc(4 + 1 + 4 * 2 + 400);
        let off = buffer.write("RREF");
        off = buffer.writeUInt8(0, off); // null terminated
        off = buffer.writeInt32LE(freq, off); // xint frequency
        off = buffer.writeInt32LE(idx, off); // xint sender index
        off += buffer.write(dataRef, off); // char[400] dataref
        off = buffer.writeUInt8(0, off); // null terminated
        await this.socket.send(
            buffer,
            0,
            buffer.length,
            this.xplanePort,
            this.xplaneAddr,
        );
    }

    async _subscribeAll() {
        for (let i = 0; i < this.subscribed.length; i++) {
            await this._subscribeDataRef(i);
        }
    }

    async subscribeDataRef(dataRef, freq, handler) {
        const idx = this.subscribed.length;
        if (handler) {
            this.subscribed.push({ dataRef, handler, freq });
        }
        console.info(`x-plane subscribed[${idx}] => ${dataRef} @${freq}Hz`);
        this._subscribeDataRef(idx);
    }
    //subscribeDataRef("sim/flightmodel/position/indicated_airspeed");

    async sendCommand(cmd) {
        let buffer = Buffer.alloc(4 + 1 + cmd.length + 1);
        let off = buffer.write("CMND");
        off = buffer.writeUInt8(0, off); // null terminated
        off += buffer.write(cmd, off); // command
        off = buffer.writeUInt8(0, off); // null terminated
        console.info(`x-plane cmd: ${cmd}`);
        await this.socket.send(
            buffer,
            0,
            buffer.length,
            this.xplanePort,
            this.xplaneAddr,
        );
    }
    //sendCommand("sim/GPS/g1000n1_hdg_down");

    async close() {
        for (let i = 0; i < this.subscribed.length; i++) {
            this.subscribed[i].freq = 0;
            await this._subscribeDataRef(i);
        }
        this.subscribed = [];
        clearTimeout(this.statusChecker);
    }
}