aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeterminant <[email protected]>2024-08-14 22:13:43 -0700
committerDeterminant <[email protected]>2024-08-14 22:13:43 -0700
commit2e301c67e21618bf3fd6d4bc1c70e569ba599cee (patch)
tree4533e51952031304fb364e5947a9074f7a88e6d4
parent21c23c6cf4b8fe12ffa32030e70c0f71f18feae7 (diff)
get loupedeck to work with X-Plane
-rwxr-xr-x[-rw-r--r--]ctrl.mjs (renamed from index.mjs)69
-rw-r--r--profile.yaml221
-rw-r--r--xplane.mjs37
3 files changed, 278 insertions, 49 deletions
diff --git a/index.mjs b/ctrl.mjs
index 193b8cd..bcab1c5 100644..100755
--- a/index.mjs
+++ b/ctrl.mjs
@@ -1,3 +1,4 @@
+#!/usr/bin/env node
import { registerFont } from 'canvas';
registerFont('./ocr-a-ext.ttf', { family: 'ocr' });
@@ -6,25 +7,48 @@ import { discover } from 'loupedeck'
import { readFile } from 'fs/promises';
import { parse } from 'yaml'
+import { sendCommand } from './xplane.mjs';
+import { isNumberObject } from 'util/types';
const pages = parse(await readFile("./profile.yaml", "utf8"));
+// state of the controller
+let currentPage;
+let pressed = new Set();
+let highlighted = new Set();
+
// Detects and opens first connected device
const device = await discover()
+const isNumber = (x) => { return !isNaN(x); }
+
const isObject = (obj) => {
return obj != null && obj.constructor.name === "Object"
};
+const doAction = (labeled, type) => {
+ if (!isObject(labeled)) {
+ return;
+ }
+ let actionSpec = labeled[type];
+ if (actionSpec === undefined) {
+ return;
+ }
+ if (actionSpec.hasOwnProperty('xplane_cmd')) {
+ sendCommand(actionSpec.xplane_cmd);
+ }
+};
+
const rectifyLabel = (label) => {
let text;
- let font;
+ let font = "22px ocr";
if (isObject(label)) {
text = label.text;
- font = `${label.size}px ocr`;
+ if (label.hasOwnProperty('size')) {
+ font = `${label.size}px ocr`;
+ }
} else {
text = label.toString();
- font = "24px ocr";
}
return { text, font }
}
@@ -95,10 +119,6 @@ const loadPage = (page) => {
}
};
-let currentPage;
-let pressed = new Set();
-let highlighted = new Set();
-
// Observe connect events
device.on('connect', () => {
console.info('Connection successful!')
@@ -108,12 +128,32 @@ device.on('connect', () => {
// React to button presses
device.on('down', ({ id }) => {
- console.info(`switch to page: ${id}`)
- if (id == 0) {
- return
+ if (isNumber(id)) {
+ console.info(`switch to page: ${id}`)
+ if (id == 0) {
+ return
+ }
+ currentPage = id;
+ loadPage(pages[currentPage]);
+ } else {
+ const { left, right, keys } = pages[currentPage] || {};
+ if (!left) {
+ return
+ }
+ let pos = {"T": 0, "C": 1, "B": 2}[id.substring(4, 5)];
+ let side = {"L": ['left', left], "R": ['right', right]}[id.substring(5, 6)];
+ let mask = [false, false, false];
+ mask[pos] = true;
+ drawSideKnobs(side[0], side[1], mask);
+ if (!highlighted.has(id)) {
+ highlighted.add(id);
+ setTimeout(() => {
+ drawSideKnobs(side[0], side[1], [false, false, false]);
+ highlighted.delete(id);
+ }, 200);
+ }
+ doAction(side[1][pos], 'pressed');
}
- currentPage = id;
- loadPage(pages[currentPage]);
})
// React to knob turns
@@ -134,6 +174,7 @@ device.on('rotate', ({ id, delta }) => {
highlighted.delete(id);
}, 200);
}
+ doAction(side[1][pos], delta > 0 ? 'inc' : 'dec');
})
const clearStaleButton = (touches) => {
@@ -153,7 +194,9 @@ device.on('touchstart', ({ changedTouches, touches }) => {
return
}
pressed.add(target.key);
- drawKey(target.key, pages[currentPage].keys[target.key], true);
+ const key = pages[currentPage].keys[target.key];
+ drawKey(target.key, key, true);
+ doAction(key, 'pressed');
//device.vibrate()
})
diff --git a/profile.yaml b/profile.yaml
index a7f3210..07f1e33 100644
--- a/profile.yaml
+++ b/profile.yaml
@@ -3,49 +3,198 @@
# page 1
- left:
# left knob labels
- - HDG
- - ALT
- - CRS
- right:
- # right knob labels
+ - text: FMS out
+ size: 20
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_fms_outer_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_fms_outer_down
- text: FMS in
size: 20
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_fms_inner_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_fms_inner_down
+ - text: BARO
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_baro_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_baro_down
+ right:
+ # right knob labels
- text: FMS out
size: 20
- - BARO
+ inc:
+ xplane_cmd: sim/GPS/g1000n2_fms_outer_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n2_fms_outer_down
+ - text: FMS in
+ size: 20
+ inc:
+ xplane_cmd: sim/GPS/g1000n2_fms_inner_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n2_fms_inner_down
+ - ''
keys:
# 12 touchable keys from left to right, top to bottom
- - A
- - B
- - C
- - D
- - E
- - F
- - G
- - H
- - I
- - J
- - K
- - L
+ - text: -D->
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_direct
+ - text: MENU
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_menu
+ - text: Direct
+ pressed:
+ xplane_cmd: sim/GPS/g1000n2_direct
+ - text: MENU
+ pressed:
+ xplane_cmd: sim/GPS/g1000n2_menu
+ - text: FPL
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_fpl
+ - text: PROC
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_proc
+ - text: FPL
+ pressed:
+ xplane_cmd: sim/GPS/g1000n2_fpl
+ - text: PROC
+ pressed:
+ xplane_cmd: sim/GPS/g1000n2_proc
+ - text: CLR
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_clr
+ - text: ENT
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_ent
+ - text: CLR
+ pressed:
+ xplane_cmd: sim/GPS/g1000n2_clr
+ - text: ENT
+ pressed:
+ xplane_cmd: sim/GPS/g1000n2_ent
+
# page 2
- left:
- - A
- - B
- - C
+ - text: HDG
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_hdg_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_hdg_down
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_hdg_sync
+ - text: ALT
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_alt_inner_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_alt_inner_down
+ - text: CRS
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_crs_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_crs_down
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_crs_sync
+
right:
- - X
- - Y
- - Z
+ - ''
+ - ''
+ - ''
+ keys:
+ - text: AP
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_ap
+ - text: FD
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_fd
+ - text: HDG
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_hdg
+ - text: ALT
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_alt
+ - text: NAV
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_nav
+ - text: VNV
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_vnv
+ - text: APR
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_apr
+ - text: BC
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_bc
+ - text: VS
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_vs
+ - text: UP
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_nose_up
+ - text: FLC
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_flc
+ - text: DN
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_nose_down
+
+# page 3
+- left:
+ - text: COM in
+ size: 20
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_com_inner_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_com_inner_down
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_com12
+ - text: COM out
+ size: 20
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_com_outer_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_com_outer_down
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_com12
+ - ''
+ right:
+ - text: NAV in
+ size: 20
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_nav_inner_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_nav_inner_down
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_nav12
+ - text: NAV out
+ size: 20
+ inc:
+ xplane_cmd: sim/GPS/g1000n1_nav_outer_up
+ dec:
+ xplane_cmd: sim/GPS/g1000n1_nav_outer_down
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_nav12
+ - ''
keys:
- - 1
- - 2
- - 3
- - 4
- - 5
- - 6
- - 7
- - 8
- - 9
- - 10
- - 11
- - 12
+ - text: <-COM
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_com_ff
+ - text: <-NAV
+ pressed:
+ xplane_cmd: sim/GPS/g1000n1_nav_ff
+ - ''
+ - ''
+ - text: COM1 M
+ size: 16
+ - text: COM1
+ pressed:
+ xplane_cmd: sim/GPS/monitor_audio_com1
+ - ''
+ - ''
+ - text: COM2 M
+ size: 16
+ - text: COM2
+ pressed:
+ xplane_cmd: sim/GPS/monitor_audio_com2
+ - ''
+ - ''
diff --git a/xplane.mjs b/xplane.mjs
new file mode 100644
index 0000000..54190c9
--- /dev/null
+++ b/xplane.mjs
@@ -0,0 +1,37 @@
+import dgram from 'node:dgram';
+
+const xplaneAddr = "localhost";
+const xplanePort = 49000;
+const socket = dgram.createSocket('udp4');
+
+//socket.on('message', (msg, rinfo) => {
+// console.log(msg, rinfo)
+//});
+//
+//socket.bind(10080);
+
+const subscribeDataRef = async (dataRef) => {
+ //const dataRef = "sim/flightmodel/position/indicated_airspeed";
+ let buffer = Buffer.alloc(4 + 1 + 4 * 2 + 400);
+ let off = buffer.write("RREF");
+ off = buffer.writeUInt8(0, off); // null terminated
+ off = buffer.writeInt32LE(1, off); // xint frequency
+ off = buffer.writeInt32LE(0, off); // xint client
+ off += buffer.write(dataRef, off); // char[400] dataref
+ off = buffer.writeUInt8(0, off); // null terminated
+ console.log(Array.from(buffer));
+ await socket.send(buffer, 0, buffer.length, xplanePort, xplaneAddr);
+}
+
+
+export const sendCommand = async (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(`sent command: ${cmd}`)
+ await socket.send(buffer, 0, buffer.length, xplanePort, xplaneAddr);
+};
+
+//command("sim/GPS/g1000n1_hdg_down");