Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

High level API #84

Draft
wants to merge 12 commits into
base: master
Choose a base branch
from
Prev Previous commit
Next Next commit
Add better error handling to api-helper
  • Loading branch information
EvenAR committed Jul 21, 2024
commit 60eb01272883707c2e7e6b854a917584aa5f2637
103 changes: 56 additions & 47 deletions samples/typescript/apiHelper.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,71 @@
import { ApiHelper } from '../../dist/helper/ApiHelper';
import { open, Protocol, SimConnectDataType, SimObjectType } from '../../dist';
import { ApiHelper } from '../../dist/apiHelper';
import { open, Protocol, SimConnectDataType } from '../../dist';

open('Testing', Protocol.KittyHawk, { host: '192.168.0.21', port: 1337 })
open('API-helper example', Protocol.KittyHawk, { host: '192.168.0.21', port: 1337 })
.then(async ({ recvOpen, handle }) => {
console.log('Yay, connected', recvOpen);
handle.on('exception', ex => console.log(ex));
console.log('Yay, connected!', recvOpen);
await doStuff(new ApiHelper(handle));
})
.catch(e => {
console.log('Unhandled error', e);
});

const { systemEvents, simulationVariables } = new ApiHelper(handle);
async function doStuff(apiHelper: ApiHelper) {
const { systemEvents, simulationVariables } = apiHelper;

systemEvents.addEventListener('Pause', data => {
console.log(data === 0 ? 'UnPaused' : 'Paused');
});
/** Subscribe to a system event */
systemEvents.addEventListener('Pause', data => {
console.log(data === 0 ? 'UnPaused' : 'Paused');
});

const pos = await simulationVariables.readValues({
/** Get a set of simulation variables once */
simulationVariables
.readValues({
acTitle: {
simulationVariable: 'TITLE',
units: null,
dataType: SimConnectDataType.STRING128,
},
cat: {
simulationVariable: 'CATEGORY',
units: null,
dataType: SimConnectDataType.STRINGV,
dataType: SimConnectDataType.STRING128,
},
fuel3: {
simulationVariable: 'FUEL TOTAL QUANTItTY',
fuelOnBoard: {
simulationVariable: 'FUEL TOTA L QUANTITY',
units: 'liters',
dataType: SimConnectDataType.INT32,
},
});
})
.then(data => {
console.log(
`Current aircraft is '${data.acTitle}'. It has ${data.fuelOnBoard} liters of fuel on board`
);
})
.catch(err => console.log(err));

simulationVariables.monitorSimulationObjects(
SimObjectType.AIRCRAFT,
10000,
{
lat: {
simulationVariable: 'PLANE LATITUDE',
units: 'degrees',
dataType: SimConnectDataType.FLOAT64,
},
lng: {
simulationVariable: 'PLANE LONGITUDE',
units: 'degrees',
dataType: SimConnectDataType.FLOAT64,
},
cat: {
simulationVariable: 'TITLE',
units: null,
dataType: SimConnectDataType.STRING128,
},
ias: {
simulationVariable: 'AIRSPEED INDICATED',
units: 'knots',
dataType: SimConnectDataType.INT32,
},
/** Get simulation variables whenever they changes */
simulationVariables.monitorValues(
{
airspeed: {
simulationVariable: 'AIRSPEED INDICATED',
units: 'knots',
dataType: SimConnectDataType.INT32,
},
values => {
console.log('nearby aircraft', values);
position: {
simulationVariable: 'STRUCT LATLONALT',
units: null,
dataType: SimConnectDataType.LATLONALT,
},
},
(err, data) => {
if (err) {
console.log(err);
} else if (data) {
console.log('Airspeed:', data.airspeed);
console.log('Position:', data.position);
}
);

console.log(pos);
})
.catch(e => {
console.log('Failed to connect', e);
});
},
{ onlyOnChange: true }
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,17 @@ import {
XYZ,
} from '../dto';
import { SimObjectType } from '../enums/SimObjectType';
import { RecvException } from '../recv';
import { checkForExceptions } from './utils';

export type SimvarCallback<T extends RequestedVariables> = (
err: SimConnectError | null,
data: VariablesResponse<T> | null
) => void;

export type SimConnectError = {
message: string;
exception: string;
};

class SimulationVariablesHelper {
private _handle: SimConnectConnection;
Expand All @@ -41,15 +51,20 @@ class SimulationVariablesHelper {
requestStructure: T,
simObjectId: number = SimConnectConstants.OBJECT_ID_USER
): Promise<VariablesResponse<T>> {
const sub = this._makeSubscription({
requestStructure,
simObjectId,
period: SimConnectPeriod.ONCE,
});

return new Promise(resolve => {
return new Promise((resolve, reject) => {
let hasFailed = false;
const sub = this._makeSubscription({
requestStructure,
simObjectId,
period: SimConnectPeriod.ONCE,
errorHandler: error => {
hasFailed = true;
reject(error);
},
});
this._handle.once('simObjectData', recvSimObjectData => {
if (
!hasFailed &&
sub.requestId === recvSimObjectData.requestID &&
sub.defineId === recvSimObjectData.defineID
) {
Expand All @@ -72,26 +87,33 @@ class SimulationVariablesHelper {
*/
monitorValues<T extends RequestedVariables>(
simulationVariables: T,
callback: (values: VariablesResponse<T>) => void,
callback: SimvarCallback<T>,
options?: {
onlyOnChange?: boolean;
simObjectId?: number;
interval?: SimConnectPeriod;
}
) {
let hasFailed = false;
const sub = this._makeSubscription({
requestStructure: simulationVariables,
simObjectId: options?.simObjectId || SimConnectConstants.OBJECT_ID_USER,
period: options?.interval || SimConnectPeriod.SIM_FRAME,
flags: options?.onlyOnChange ? DataRequestFlag.DATA_REQUEST_FLAG_CHANGED : 0,
errorHandler: err => {
hasFailed = true;
callback(err, null);
},
});

this._handle.on('simObjectData', recvSimObjectData => {
if (
!hasFailed &&
sub.requestId === recvSimObjectData.requestID &&
sub.defineId === recvSimObjectData.defineID
) {
callback(
null,
extractDataStructureFromBuffer(simulationVariables, recvSimObjectData.data)
);
}
Expand All @@ -102,12 +124,13 @@ class SimulationVariablesHelper {
type: SimObjectType,
radiusMeters: number,
simulationVariables: T,
callback: (values: VariablesResponse<T>) => void
callback: SimvarCallback<T>
) {
const sub = this._makeSubscriptionByType({
requestStructure: simulationVariables,
radiusMeters,
type,
errorHandler: err => callback(err, null),
});

this._handle.on('simObjectDataByType', recvSimObjectData => {
Expand All @@ -116,6 +139,7 @@ class SimulationVariablesHelper {
sub.defineId === recvSimObjectData.defineID
) {
callback(
null,
extractDataStructureFromBuffer(simulationVariables, recvSimObjectData.data)
);
}
Expand All @@ -127,8 +151,9 @@ class SimulationVariablesHelper {
period: SimConnectPeriod;
simObjectId: number;
flags?: number;
errorHandler: (error: SimConnectError) => void;
}): { defineId: number; requestId: number } {
const defineId = this._defineData(params.requestStructure);
const defineId = this._defineData(params.requestStructure, params.errorHandler);
const requestId = this._nextDataRequestId++;

const sendId = this._handle.requestDataOnSimObject(
Expand All @@ -139,7 +164,12 @@ class SimulationVariablesHelper {
DataRequestFlag.DATA_REQUEST_FLAG_DEFAULT | (params.flags || 0)
);

this._checkForExceptions(sendId, 'Failed to subscribe');
checkForExceptions(this._handle, sendId, ex =>
params.errorHandler({
message: 'Failed to request data for sim object',
exception: ex,
})
);

return { requestId, defineId };
}
Expand All @@ -148,10 +178,11 @@ class SimulationVariablesHelper {
requestStructure: T;
radiusMeters: number;
type: SimObjectType;
errorHandler: (error: SimConnectError) => void;
}): { defineId: number; requestId: number } {
const requestId = this._nextDataRequestId++;

const defineId = this._defineData(params.requestStructure);
const defineId = this._defineData(params.requestStructure, params.errorHandler);

const sendId = this._handle.requestDataOnSimObjectType(
requestId,
Expand All @@ -160,12 +191,20 @@ class SimulationVariablesHelper {
params.type
);

this._checkForExceptions(sendId, 'Failed to subscribe');
checkForExceptions(this._handle, sendId, ex =>
params.errorHandler({
message: 'Failed to request data for sim object type',
exception: ex,
})
);

return { requestId, defineId };
}

private _defineData<T extends RequestedVariables>(requestStructure: T): number {
private _defineData<T extends RequestedVariables>(
requestStructure: T,
errorHandler: (error: SimConnectError) => void
): number {
const defineId = this._nextDataDefinitionId++;

/**
Expand All @@ -183,31 +222,16 @@ class SimulationVariablesHelper {
requestedValue.dataType,
requestedValue.epsilon
);
this._checkForExceptions(
sendId,
`Failed to register simulation variable '${userDefinedNames[index]}'`
checkForExceptions(this._handle, sendId, ex =>
errorHandler({
message: `Something is wrong with the requested value '${userDefinedNames[index]}'`,
exception: ex,
})
);
});

return defineId;
}

private _checkForExceptions(sendId: number, message: string) {
function exceptionHandler(recvException: RecvException) {
if (recvException.sendId === sendId) {
throw Error(
`SimConnectException - sendId=${recvException.sendId}, exception=${recvException.exception}, message="${message}"`
);
}
}

this._handle.on('exception', exceptionHandler);

// Give SimConnect server some time to throw the exception, then remove the listener
setTimeout(() => {
this._handle.off('exception', exceptionHandler);
}, 1000);
}
}

function extractDataStructureFromBuffer<T extends RequestedVariables>(
Expand All @@ -216,13 +240,13 @@ function extractDataStructureFromBuffer<T extends RequestedVariables>(
) {
return Object.keys(requestStructure)
.reverse() // Reverse to get the same order as requested order
.reduce((result, propName) => {
return {
.reduce(
(result, propName) => ({
[propName]: extractSimConnectValue(rawBuffer, requestStructure[propName].dataType),
...result,
};
return result;
}, {} as VariablesResponse<T>);
}),
{} as VariablesResponse<T>
);
}

function extractSimConnectValue<T extends SimConnectDataType>(
Expand Down Expand Up @@ -288,6 +312,7 @@ type VariablesResponse<R extends RequestedVariables> = {
[K in keyof R]: OutputVariableType[R[K]['dataType']];
};

/** Maps the SimConnect data types to JS data types */
type OutputVariableType = {
[T in SimConnectDataType]: {
[SimConnectDataType.INVALID]: undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SimConnectConnection } from '../SimConnectConnection';
import { checkForExceptions } from './utils';

export class SystemEventsHelper {
_nextClientEventId;
Expand Down Expand Up @@ -32,21 +33,30 @@ export class SystemEventsHelper {
clientEventId: this._nextClientEventId,
eventHandlers: [eventHandler],
};
this._handle.subscribeToSystemEvent(this._nextClientEventId, systemEventName);
const sendId = this._handle.subscribeToSystemEvent(
this._nextClientEventId,
systemEventName
);
this._nextClientEventId++;
checkForExceptions(this._handle, sendId, ex => {
throw Error(`Subscription for system event '${systemEventName}' failed: ${ex}`);
});
}
}

removeEventListener(systemEventName: string, eventHandler?: SystemEventHandler) {
const sub = this._subscriptions[systemEventName];
if (!sub) {
throw Error(`No subscriptions exists for ${systemEventName}`);
throw Error(`No subscription exists for system event '${systemEventName}'`);
}

sub.eventHandlers = eventHandler ? sub.eventHandlers.filter(cb => eventHandler !== cb) : [];
if (sub.eventHandlers.length === 0) {
this._handle.unsubscribeFromSystemEvent(sub.clientEventId);
const sendId = this._handle.unsubscribeFromSystemEvent(sub.clientEventId);
delete this._subscriptions[systemEventName];
checkForExceptions(this._handle, sendId, ex => {
throw Error(`Unsubscription for system event '${systemEventName}' failed: ${ex}`);
});
}
}
}
Expand Down
File renamed without changes.
Loading