Skip to content

Commit

Permalink
prep
Browse files Browse the repository at this point in the history
  • Loading branch information
Pomax committed Sep 6, 2024
1 parent 329b907 commit 2b37a61
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 23 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ This library _strictly_ adheres to [semver](https://semver.org)'s major.minor.pa

# Version history

v4.6.0 (6 September 2024)

- Updated airports based on MSFS v1.37.19.0
- added the `.periodic()` function for scheduling event updates using MSFS's built in interval identifiers rather than specifying your own interval in milliseconds.

v4.5.0 (16 February 2024)

- Updated runway start and end points with an altitude value in the third array position.
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,34 @@ Approaches gave the following shape:
Sets up a periodic call to `handler` every `interval` milliseconds with the result of `get(...propNames)`. Returns an arg-less `off()` to end the scheduled call.
#### `periodic(handler, period, ...propNames)`
similar to `schedule`, but using MSFS's own timing constants rather than specifying a certain number of milliseconds. These can be found over on [the official documentation page](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Structures_And_Enumerations/SIMCONNECT_PERIOD.htm) but are reproduced here (always check the website to make sure the information here isn't out of date though!). Note that the offical SDK versions have a `SIMCONNECT_PERIOD_` prefix which is not used in this library:
Constant | Description
--- | ---
`NEVER` | Specifies that the data is not to be sent.
`ONCE` | Specifies that the data should be sent once only. Note that this is not an efficient way of receiving data frequently, use one of the other periods if there is a regular frequency to the data request.
`VISUAL_FRAME` | Specifies that the data should be sent every visual (rendered) frame.
`SIM_FRAME` | Specifies that the data should be sent every simulated frame, whether that frame is rendered or not.
`SECOND` | Specifies that the data should be sent once every second.
You can access these constants through the `SimConnectPeriod` export:
```js
import { MSFS_API, SimConnectPeriod } from "msfs-simconnect-api-wrapper";
const api = new MSFS_API();
api.connect({
...
onConnect: () => {
api.period(..., SimConnectPeriod.VISUAL_FRAME, ...);
}
});
```
#### `set(propName, value)`
Accepts a single simvar and the value its should be set to. This will throw "SimVar ... is not settable" when attempting to set the value for a read-only variable.
Expand Down
Binary file modified airport.db.gz
Binary file not shown.
File renamed without changes.
52 changes: 49 additions & 3 deletions msfs-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
Protocol,
} from "node-simconnect";

export { SimConnectPeriod };

// imports used by the API
import { SimVars } from "./simvars/index.js";
import { SystemEvents as SysEvents } from "./system-events/index.js";
import { SIMCONNECT_EXCEPTION } from "./exceptions.js";
import { SIMCONNECT_EXCEPTION } from "./exceptions/exceptions.js";

// Special import for working with airport data
import { AirportEvents, getAirportHandler } from "./special/airports.js";
Expand Down Expand Up @@ -224,7 +226,13 @@ export class MSFS_API {
* @param {*} defs
* @returns
*/
generateGetPromise(DATA_ID, REQUEST_ID, propNames, defs) {
generateGetPromise(
DATA_ID,
REQUEST_ID,
propNames,
defs,
period = SimConnectPeriod.ONCE
) {
const { handle } = this;
return new Promise((resolve, _reject) => {
const handleDataRequest = ({ requestID, data }) => {
Expand All @@ -244,7 +252,7 @@ export class MSFS_API {
REQUEST_ID,
DATA_ID,
SimConnectConstants.OBJECT_ID_USER,
SimConnectPeriod.ONCE,
period,
...[0, 0, 0, 0]
);
});
Expand Down Expand Up @@ -330,4 +338,42 @@ export class MSFS_API {
run();
return () => (running = false);
}

/**
* similar to schedule, but using https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/API_Reference/Structures_And_Enumerations/SIMCONNECT_PERIOD.htm
*/
periodic(handler, period = SimConnectPeriod.SIM_FRAME, ...propNames) {
if (!this.connected) throw new Error(MSFS_NOT_CONNECTED);

// Stolen from `get`, DRY this out later.
const DATA_ID = this.nextId();
const REQUEST_ID = DATA_ID;
propNames = propNames.map((s) => s.replaceAll(`_`, ` `));

const defs = propNames.map((propName) => SimVars[propName]);
this.addDataDefinitions(DATA_ID, propNames, defs);

const { handle } = this;

const handleDataRequest = ({ requestID, data }) => {
if (requestID === REQUEST_ID) {
const result = {};
propNames.forEach((propName, pos) => {
result[codeSafe(propName)] = defs[pos].read(data);
});
handler(result);
}
};

handle.on("simObjectData", handleDataRequest);
const role = SimConnectConstants.OBJECT_ID_USER;
handle.requestDataOnSimObject(REQUEST_ID, DATA_ID, role, period);

return () => {
const never = SimConnectPeriod.NEVER;
handle.requestDataOnSimObject(REQUEST_ID, DATA_ID, role, never);
handle.clearDataDefinition(DATA_ID);
this.releaseId(DATA_ID);
};
}
}
1 change: 1 addition & 0 deletions special/airports.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export function loadAirportDB(location = AIRPORT_DB_LOCATION) {
console.log(`JSON parse error:`, e);
}
}

return cachedAirportData;
}

Expand Down
78 changes: 78 additions & 0 deletions test/pid-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { SimConnectPeriod, MSFS_API } from "../msfs-api.js";
import { watch } from "./util/reload-watcher.js";

let updateElevator, updateAileron;
await watch(import.meta.dirname, "util/pid-code.js", (lib) => {
updateElevator = lib.updateElevator;
updateAileron = lib.updateAileron;
});

let stop = () => {};
const api = new MSFS_API();

(async function tryConnect() {
console.log(`Testing call prevention prior to connection`);
console.log(`Awaiting connection`);
api.connect({
autoReconnect: true,
retries: Infinity,
retryInterval: 5,
onConnect: connect,
onRetry: (_retries, interval) =>
console.log(`Connection failed: retrying in ${interval} seconds.`),
});
})();

async function connect() {
console.log(`MSFS connected`);

let time = Date.now();

// Ask simconnect to update us on the state of
// a small set of vars every frame.
stop = api.periodic(
(data) => {
// But let's only run code (at most) 30 times a second.
const now = Date.now();
if (now - time < 30) return;
time = now;

console.log(`.`);

// Also, don't run if the AP is on.
if (data.AUTOPILOT_MASTER === 1) return;

// VS0 mode
{
const elevator = data.AILERON_POSITION;
const VS = data.VERTICAL_SPEED * 60; // fpm;
updateElevator(api, elevator, VS);
}

// LVL mode
{
const aileron = data.AILERON_POSITION;
const bank = (data.PLANE_BANK_DEGREES * 180) / Math.PI;
const turnRate = (data.TURN_INDICATOR_RATE * 180) / Math.PI;
const heading = (data.PLANE_HEADING_DEGREES_MAGNETIC * 180) / Math.PI;
updateAileron(api, aileron, bank, turnRate, heading);
}
},
SimConnectPeriod.SIM_FRAME,
`AUTOPILOT_MASTER`,
`AILERON_POSITION`,
`ELEVATOR_POSITION`,
`PLANE_BANK_DEGREES`,
`TURN_INDICATOR_RATE`,
`PLANE_HEADING_DEGREES_MAGNETIC`,
`VERTICAL_SPEED`
);
}

process.on("SIGINT", function () {
console.log(`running stop first...`);
stop();
setInterval(() => {
process.exit();
}, 1000);
});
40 changes: 20 additions & 20 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,6 @@ const api = new MSFS_API();
console.log(`Testing call prevention prior to connection`);
await testAPriori();

if (true) {
console.log(`testing direct airport access`);
const airports = loadAirportDB();
console.log(`${airports.length} airports loaded directly`);

// find all airports near Denver
const [lat, long] = [39.7642219, -105.0202604];
const start = performance.now();
const denver = airports.filter((a) => {
const { latitude: y, longitude: x } = a;
return lat - 0.5 < y && y < lat + 0.5 && long - 0.5 < x && x < long + 0.5;
});
const end = performance.now();
console.log(
`found ${
denver.length
} airports found in a 1 arc degree rect around Denver in ${end - start}ms`
);
}

console.log(`Awaiting connection`);
api.connect({
autoReconnect: true,
Expand Down Expand Up @@ -75,6 +55,26 @@ async function testAPriori() {
async function connect(handle) {
console.log(`MSFS connected`);

if (true) {
console.log(`testing direct airport access`);
const airports = loadAirportDB();
console.log(`${airports.length} airports loaded directly`);

// find all airports near Denver
const [lat, long] = [39.7642219, -105.0202604];
const start = performance.now();
const denver = airports.filter((a) => {
const { latitude: y, longitude: x } = a;
return lat - 0.5 < y && y < lat + 0.5 && long - 0.5 < x && x < long + 0.5;
});
const end = performance.now();
console.log(
`found ${
denver.length
} airports found in a 1 arc degree rect around Denver in ${end - start}ms`
);
}

const { CAMERA_STATE: camera } = await api.get(`CAMERA_STATE`);
if (camera > 10) {
throw new Error(`MSFS needs to be "in game" for the tests to run\n`);
Expand Down
39 changes: 39 additions & 0 deletions test/util/pid-code.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { PIDController } from "./pid-controller.js";

let elevatorPID = new PIDController(1, 0.5, 0);
elevatorPID.setTarget(0);

let aileronPID = new PIDController(1, 0, 1);
aileronPID.setTarget(0);

export function updateElevator(api, elevator, VS) {
return;
const recommendation = elevatorPID.update(VS);
const correction = constrainMap(recommendation, -1000, 1000, -1, 1);
api.set(`ELEVATOR_POSITION`, constrain(elevator + correction, -1, 1));
}

export function updateAileron(api, aileron, bank, turnRate, heading) {
const hdiff = heading - 340;
console.log(bank.toFixed(4), turnRate.toFixed(4), heading.toFixed(4), hdiff.toFixed(4));
if ((hdiff < 0 && turnRate >= 0) || (hdiff > 0 && turnRate <= 0)) {
const recommendation = aileronPID.update(hdiff);
const correction = constrainMap(recommendation, -3, 3, -0.01, 0.01);
api.set(`AILERON_POSITION`, constrain(aileron + correction, -1, 1));
}
}

function map(v, ds, de, ts, te) {
const d = de - ds;
if (d === 0) return ts;
return ts + ((v - ds) * (te - ts)) / d;
}

function constrain(v, m, M) {
if (m > M) return constrain(v, M, m);
return v > M ? M : v < m ? m : v;
}

function constrainMap(v, ds, de, ts, te) {
return constrain(map(v, ds, de, ts, te), ts, te);
}
Loading

0 comments on commit 2b37a61

Please sign in to comment.