aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.rst2
-rwxr-xr-xapp.mjs93
-rw-r--r--profile.yaml61
3 files changed, 92 insertions, 64 deletions
diff --git a/README.rst b/README.rst
index 3717afb..02754dd 100644
--- a/README.rst
+++ b/README.rst
@@ -29,4 +29,4 @@ Resources
- Videos: https://photos.app.goo.gl/1hAQ19DZQRo4RRr9A
- Profile is currently in ``profile.yaml``.
-- Linux permission issue: copy ``50-loupedeck.rules`` to be under ``/etc/udev/rules.d`` and then ``sudo udevadm control --reload-rules && sudo udevadm trigger``.
+- Linux permission issue: copy ``50-loupedeck.rules`` to be under ``/etc/udev/rules.d`` and then ``sudo udevadm control --reload-rules && sudo udevadm trigger``. In Linux, install the font ``ocr-a-ext.ttf`` to your system.
diff --git a/app.mjs b/app.mjs
index acb4bf5..1455f7b 100755
--- a/app.mjs
+++ b/app.mjs
@@ -1,15 +1,23 @@
#!/usr/bin/env node
-//import { registerFont } from "canvas";
-//registerFont("./ocr-a-ext.ttf", { family: "OCR A Extended" });
+import { registerFont } from "canvas";
+if (process.platform == "linux") {
+ console.warn(
+ "node-canvas does not support directly using font file in Linux (see https://github.com/Automattic/node-canvas/issues/2097#issuecomment-1803950952), please copy ./ocr-a-ext.ttf in this folder to your local font folder (~/.fonts/) or install it system-wide.",
+ );
+} else {
+ registerFont(`${import.meta.dirname}/ocr-a-ext.ttf`, {
+ family: "OCR A Extended",
+ });
+}
import { discover, HAPTIC } from "loupedeck";
import { readFile } from "fs/promises";
import { parse } from "yaml";
import { XPlane } from "./xplane.mjs";
-const labelFont = "OCR A Extended";
-const labelSize = 22;
+const defaultFont = "OCR A Extended";
+const defaultTextSize = 22;
const xplane = new XPlane();
if (process.argv.length > 3) {
@@ -78,10 +86,12 @@ const getTextStyles = (conf) => {
? conf.color_fg
: [conf.color_fg];
for (let i = 0; i < size.length; i++) {
- font.push(`${size[i] ? size[i] : labelSize}px '${labelFont}'`);
+ font.push(
+ `${size[i] ? size[i] : defaultTextSize}px '${defaultFont}'`,
+ );
}
} else {
- font.push(`${labelSize}px '${labelFont}'`);
+ font.push(`${defaultTextSize}px '${defaultFont}'`);
}
return {
font,
@@ -113,9 +123,7 @@ const formatValues = (conf, values, n = 1) => {
let last;
let text = [];
- const formatter = Array.isArray(conf.formatter)
- ? conf.formatter
- : [conf.formatter];
+ const formatter = Array.isArray(conf.values) ? conf.values : [conf.values];
for (let i = 0; i < n; i++) {
let fmt = formatter[i] || last;
text.push(f(fmt));
@@ -289,7 +297,7 @@ const renderTextGauge = (c, display, values) => {
c.fillStyle = bg;
c.fillRect(0, 0, w, h);
- const text = formatValues(display, values, display.formatter.length);
+ const text = formatValues(display, values, display.values.length);
// TODO: cache this
const styles = getTextStyles({
size: display.size,
@@ -297,7 +305,7 @@ const renderTextGauge = (c, display, values) => {
"color_fg",
display,
values,
- display.formatter.length,
+ display.values.length,
),
});
renderMultiLineText(c, 0, 0, w, h, text, styles, {});
@@ -364,8 +372,8 @@ const renderMeterGauge = (c, display, values) => {
// show the value text
const text = formatValues(display, values);
- const size = display.font ? display.font : labelSize;
- c.font = `${size * 0.9}px '${labelFont}'`;
+ const { font } = getTextStyles(display);
+ c.font = font[0];
c.fillStyle = fg;
const m = c.measureText(text);
c.fillText(text, (w - m.width) / 2, h / 2 + 25);
@@ -381,9 +389,12 @@ const renderAttitudeIndicator = (c, display, values) => {
c.fillStyle = bg;
c.fillRect(0, 0, w, h);
- const pitch = values[0];
- const roll = values[1];
- const src = display.src[values[2]];
+ const pitch = values[0] || 0;
+ const roll = values[1] || 0;
+ let src = isObject(display.navs) ? display.navs[values[2]] : null;
+ if (!isObject(src)) {
+ src = null;
+ }
const cdi = src ? values[src.def] : null;
const received = src ? values[src.received] : null;
@@ -412,7 +423,7 @@ const renderAttitudeIndicator = (c, display, values) => {
c.moveTo(-0.75 * w, 0);
c.lineTo(0.75 * w, 0);
c.fillStyle = fg;
- c.font = `10px ${labelFont}`;
+ c.font = `10px ${defaultFont}`;
const drawMark = (i) => {
const y = longSep * i;
const sign = i < 0 ? -1 : 1;
@@ -460,7 +471,7 @@ const renderAttitudeIndicator = (c, display, values) => {
}
c.stroke();
- if (received == 0) {
+ if (isNumber(received) && received != 0) {
// draw CDI diamond
const cdi_y = 13 * cdi;
const cdi_h = 7;
@@ -523,13 +534,13 @@ const renderMechanicalDisplay = (
right = true,
wideWinWidth = 2,
lowDigitStep = 1,
- size = labelSize,
+ size = defaultTextSize,
) => {
const bg = "black";
const fg = "white";
c.save();
- c.font = `${size}px '${labelFont}'`;
+ c.font = `${size}px '${defaultFont}'`;
const m = c.measureText("x");
const y0 =
h / 2 + (m.actualBoundingBoxAscent - m.actualBoundingBoxDescent) / 2;
@@ -636,7 +647,7 @@ const renderAltimeter = (c, display, values) => {
renderMechanicalDisplay(c, w, h, values[0], 5, false, 2, 20, 18);
// draw floating vsi window
- const vsi = values[1];
+ const vs = values[1];
const vsiBgX = w / 2 + 4;
c.fillRect(vsiBgX, 0, w - vsiBgX, h);
c.fillStyle = "#000";
@@ -644,20 +655,20 @@ const renderAltimeter = (c, display, values) => {
const vsiX = vsiBgX + 2;
const vsiY =
(1 -
- (Math.min(Math.max(isNumber(vsi) ? vsi : 0, -2000), 2000) + 2000) /
+ (Math.min(Math.max(isNumber(vs) ? vs : 0, -2000), 2000) + 2000) /
4000) *
(h - vsiH);
c.fillRect(vsiX, vsiY, w - vsiX, vsiH);
c.fillStyle = fg;
- if (isNumber(vsi)) {
- c.font = `12px '${labelFont}'`;
- c.fillText(Math.trunc(vsi / 10) * 10, vsiX + 2, vsiY + vsiH * 0.8);
+ if (isNumber(vs)) {
+ c.font = `12px '${defaultFont}'`;
+ c.fillText(Math.trunc(vs / 10) * 10, vsiX + 2, vsiY + vsiH * 0.8);
}
- const selected = values[2];
- if (isNumber(selected)) {
- c.fillStyle = "#6697ff";
- c.font = `14px '${labelFont}'`;
- c.fillText(selected, 15, 18);
+ const altB = values[2];
+ if (isNumber(altB)) {
+ c.fillStyle = "cyan";
+ c.font = `14px '${defaultFont}'`;
+ c.fillText(altB, 15, 18);
}
};
@@ -682,14 +693,16 @@ const renderHSI = (c, display, values) => {
const hdg = deg2Rad(values[0]);
const hdgB = deg2Rad(values[1]);
- const src = display.navs[values[2]];
+ let src = isObject(display.navs) ? display.navs[values[2]] : null;
+ if (!isObject(src)) {
+ src = null;
+ }
const crs = src ? deg2Rad(values[src.crs]) : null;
let def = src ? Math.min(Math.max(values[src.def], -3), 3) : null;
- const received = src ? values[src.received] : null;
-
if (isNaN(def)) {
def = 0;
}
+ const received = src ? values[src.received] : null;
const polarXY = (theta, r) => {
const t = -theta - Math.PI / 2;
const dx = r * Math.cos(t);
@@ -711,7 +724,7 @@ const renderHSI = (c, display, values) => {
}
c.fillStyle = fg;
- c.font = `16px '${labelFont}'`;
+ c.font = `16px '${defaultFont}'`;
c.fillText("N", -5, -0.5 * r);
if (isNumber(hdgB)) {
@@ -748,7 +761,8 @@ const renderHSI = (c, display, values) => {
c.lineWidth = 3;
c.strokeStyle = src.color ? src.color : "magenta";
- if (received != 0) {
+ if (isNumber(received) && received != 0) {
+ // draw CDI needle
const cdiX = 13 * def;
c.moveTo(cdiX, -(cdiR - 1));
c.lineTo(cdiX, cdiR - 1);
@@ -783,12 +797,17 @@ const renderBarGauge = (c, display, values) => {
const slotHeight = 60;
const barWidth = slotWidth * 0.6;
- const text = formatValues(display, values, 4);
+ const text = formatValues(display, values, display.values.length);
const label = getLabels(display);
// TODO: cache this
const { font, color_fg } = getTextStyles({
size: display.size,
- color_fg: formatColors("color_fg", display, values, 4),
+ color_fg: formatColors(
+ "color_fg",
+ display,
+ values,
+ display.values.length,
+ ),
});
c.rotate(Math.PI / 2);
diff --git a/profile.yaml b/profile.yaml
index cd64d8c..63567cf 100644
--- a/profile.yaml
+++ b/profile.yaml
@@ -5,6 +5,7 @@
#
# Page 0
- left:
+ # left knob labels from top to bottom
- label: Thr.
inc:
xplane_cmd: sim/engines/throttle_up
@@ -27,6 +28,7 @@
xplane_cmd: sim/engines/mixture_max
color_bg: '#ff0000'
right:
+ # right knob labels
- label: HDG
inc:
xplane_cmd: sim/GPS/g1000n1_hdg_up
@@ -47,8 +49,13 @@
pressed:
xplane_cmd: sim/GPS/g1000n1_crs_sync
keys:
+ # 12 touchable keys from left to right, top to bottom
- display:
+ # a "display" key only shows some live data, rather than functioning as a pressable key
+ # "type" indicates which template for the display to use (one of meter/text/bar/attitude/ias/alt/hsi)
+ # here, "bar" is a bar chart that draws several (practically <= 4) bars from left to right showing some stats
type: bar
+ # update frequency of the display
freq: 6
source:
- xplane_dataref: sim/cockpit2/engine/actuators/throttle_ratio[0]
@@ -60,29 +67,36 @@
- P
- M
- F
+ # font size for each bar
size:
- 12
- 12
- 12
- 12
- formatter:
+ # value expression written in a JS template literal (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)
+ # for each bar, should result in [0, 1]. Note $d is the array that
+ # contains the values from "source"
+ values:
- '${$d[0] != null ? ($d[0] * 100).toFixed(0) : "X"}%'
- '${$d[1] != null ? ($d[1] * 100).toFixed(0) : "X"}%'
- '${$d[2] != null ? ($d[2] * 100).toFixed(0) : "X"}%'
- '${$d[3] != null ? ($d[3] * 100).toFixed(0) : "X"}%'
+ # bar colors
color_fg:
- white
- blue
- red
- gray
- display:
+ # meter is a arc-style display that could be used to show RPM/power/speed, etc.
type: meter
freq: 6
source:
- xplane_dataref: sim/cockpit2/engine/indicators/MPR_in_hg[0]
min: 10
max: 30
- formatter: '${$d[0] != null ? $d[0].toFixed(1) : "X"}'
+ values: '${$d[0] != null ? $d[0].toFixed(1) : "X"}'
+ # stops define the segmented arcs that the needle can point to at different angles
stops:
- color: 'green'
value_begin: 13
@@ -120,11 +134,11 @@
source:
- xplane_dataref: sim/cockpit/gyros/the_ind_ahars_pilot_deg # pitch
- xplane_dataref: sim/cockpit/gyros/phi_ind_ahars_pilot_deg # roll
- - xplane_dataref: sim/cockpit2/radios/actuators/HSI_source_select_pilot
+ - xplane_dataref: sim/cockpit2/radios/actuators/HSI_source_select_pilot # navigation source for HSI
- xplane_dataref: sim/cockpit2/radios/indicators/hsi_vdef_dots_pilot
- xplane_dataref: sim/cockpit2/radios/indicators/hsi_flag_glideslope_pilot
-
- src:
+ # navs contains the mapping from the HSI source value (source[2]) to the index in source[] array
+ navs:
2:
def: 3
received: 4
@@ -138,9 +152,9 @@
type: alt
freq: 24
source:
- - xplane_dataref: sim/cockpit2/gauges/indicators/altitude_ft_pilot # alt
- - xplane_dataref: sim/cockpit2/gauges/indicators/vvi_fpm_pilot # vs
- - xplane_dataref: sim/cockpit/autopilot/altitude
+ - xplane_dataref: sim/cockpit2/gauges/indicators/altitude_ft_pilot # altitude
+ - xplane_dataref: sim/cockpit2/gauges/indicators/vvi_fpm_pilot # vertical speed
+ - xplane_dataref: sim/cockpit/autopilot/altitude # altitude bug
- label:
- Flaps
- Down
@@ -151,7 +165,7 @@
source:
- xplane_dataref: sim/cockpit/autopilot/autopilot_state
- xplane_dataref: sim/cockpit2/radios/actuators/HSI_source_select_pilot
- formatter:
+ values:
- '${($d[0] & 2) ? "HDG" : (($d[0] & 0x200) ? "NAV" : "ROL")} ${($d[0] & 0x10) ? "VS" : (($d[0] & 0x40) ? "FLC" : (($d[0] & 0x4000) ? "ALT" : "PIT"))}'
- '${$d[1] == 0 ? "NAV1" : ($d[1] == 1 ? "NAV2" : ($d[1] == 2 ? "GPS" : "X"))}'
size:
@@ -161,16 +175,14 @@
type: hsi
freq: 12
source:
- - xplane_dataref: sim/cockpit2/gauges/indicators/heading_AHARS_deg_mag_pilot
- - xplane_dataref: sim/cockpit2/autopilot/heading_dial_deg_mag_pilot
- - xplane_dataref: sim/cockpit2/radios/actuators/HSI_source_select_pilot
+ - xplane_dataref: sim/cockpit2/gauges/indicators/heading_AHARS_deg_mag_pilot # current heading
+ - xplane_dataref: sim/cockpit2/autopilot/heading_dial_deg_mag_pilot # heading bug
+ - xplane_dataref: sim/cockpit2/radios/actuators/HSI_source_select_pilot # navigation source for HSI
- xplane_dataref: sim/cockpit2/radios/indicators/hsi_hdef_dots_pilot
- xplane_dataref: sim/cockpit2/radios/indicators/hsi_display_horizontal_pilot
- xplane_dataref: sim/cockpit/radios/gps_course_degtm
- xplane_dataref: sim/cockpit2/radios/actuators/nav1_obs_deg_mag_pilot
- xplane_dataref: sim/cockpit2/radios/actuators/nav2_obs_deg_mag_pilot
- hdg: 0
- src: 1
navs:
2:
crs: 5
@@ -197,7 +209,7 @@
- xplane_dataref: sim/cockpit/radios/gps_course_degtm
- xplane_dataref: sim/cockpit2/radios/actuators/nav1_obs_deg_mag_pilot
- xplane_dataref: sim/cockpit2/radios/actuators/nav2_obs_deg_mag_pilot
- formatter:
+ values:
- '${$d[0] != null ? $d[0].toFixed(0).padStart(3, "0") : "000"}'
- 'HDG ${$d[1] != null ? $d[1].toFixed(0).padStart(3, "0") : "000"}'
- 'CRS ${($d[2] == 2 ? $d[3] : ($d[2] == 0 ? $d[4] : ($d[2] == 1 ? $d[5] : 0))).toFixed(0).padStart(3, "0")}'
@@ -275,7 +287,6 @@
# Page 2
- left:
- # left knob labels
- label: FMS out
size: 20
inc:
@@ -298,7 +309,6 @@
dec:
xplane_cmd: sim/GPS/g1000n1_baro_down
right:
- # right knob labels
- label: FMS out
size: 20
inc:
@@ -317,7 +327,6 @@
xplane_cmd: sim/GPS/g1000n3_cursor
-
keys:
- # 12 touchable keys from left to right, top to bottom
- label: -D->
pressed:
xplane_cmd: sim/GPS/g1000n1_direct
@@ -374,7 +383,7 @@
xplane_cmd: sim/GPS/g1000n1_com_outer_down
pressed:
xplane_cmd: sim/GPS/g1000n1_com12
- - ''
+ -
right:
- label: NAV in
size: 20
@@ -392,7 +401,7 @@
xplane_cmd: sim/GPS/g1000n1_nav_outer_down
pressed:
xplane_cmd: sim/GPS/g1000n1_nav12
- - ''
+ -
keys:
- label: <-COM
pressed:
@@ -400,8 +409,8 @@
- label: <-NAV
pressed:
xplane_cmd: sim/GPS/g1000n1_nav_ff
- - ''
- - ''
+ -
+ -
- label:
- COM1
- Mic
@@ -410,8 +419,8 @@
- label: COM1
pressed:
xplane_cmd: sim/audio_panel/monitor_audio_com1
- - ''
- - ''
+ -
+ -
- label:
- COM2
- Mic
@@ -420,6 +429,6 @@
- label: COM2
pressed:
xplane_cmd: sim/audio_panel/monitor_audio_com2
- - ''
- - ''
+ -
+ -
color: 'white'