aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDeterminant <[email protected]>2024-08-29 00:04:11 -0700
committerDeterminant <[email protected]>2024-08-29 00:04:11 -0700
commitea672a5d4c7d781ca986f5e0df3b5cb2e1acd135 (patch)
treebe4f3776e2969ec90ab12519df981040fc44e993
parent0cddfa1953a516342b44568bf214ac7103ffe6bb (diff)
add HSI
-rwxr-xr-xapp.mjs323
-rw-r--r--profile.yaml105
2 files changed, 314 insertions, 114 deletions
diff --git a/app.mjs b/app.mjs
index b3caf0f..ae82d55 100755
--- a/app.mjs
+++ b/app.mjs
@@ -8,6 +8,8 @@ import { discover, HAPTIC } from "loupedeck";
import { readFile } from "fs/promises";
import { parse } from "yaml";
import { XPlane } from "./xplane.mjs";
+import { isArray } from "util";
+import { platform } from "process";
const labelFont = "OCR A Extended";
const labelSize = 22;
@@ -66,35 +68,48 @@ const getKeyConf = (i) => {
const rectifyLabel = (conf) => {
// conf must be non-null
- let text, text2, font, font2;
- let color_bg, color_fg;
- let color_bg2, color_fg2;
+ let text, font;
+ let color_bg = [],
+ color_fg = [];
- let size = labelSize;
if (isObject(conf)) {
- if (conf.size != null) {
- size = conf.size;
+ text = [conf.text];
+ color_bg = [conf.color_bg];
+ color_fg = [conf.color_fg];
+ if (conf.text2 != null) {
+ text.push(conf.text2);
+ }
+ if (conf.text3 != null) {
+ text.push(conf.text3);
+ }
+ if (conf.color_bg2) {
+ color_bg.push(conf.color_bg2);
+ }
+ if (conf.color_fg2) {
+ color_fg.push(conf.color_fg2);
+ }
+ if (conf.color_bg3) {
+ color_bg.push(conf.color_bg3);
+ }
+ if (conf.color_fg3) {
+ color_fg.push(conf.color_fg3);
+ }
+ let size = [conf.size != null ? conf.size : labelSize];
+ size.push(conf.size2 != null ? conf.size2 : size[0] * 0.9);
+ size.push(conf.size3 != null ? conf.size3 : size[1]);
+ font = [];
+ for (let i = 0; i < size.length; i++) {
+ font.push(`${size[i]}px '${labelFont}'`);
}
- text = conf.text;
- text2 = conf.text2;
- color_bg = conf.color_bg;
- color_fg = conf.color_fg;
- color_bg2 = conf.color_bg2;
- color_fg2 = conf.color_fg2;
- font2 = `${size * 0.9}px '${labelFont}'`;
} else {
- text = conf.toString();
+ text = [conf.toString()];
+ font = [`${labelSize}px '${labelFont}'`];
}
- font = `${size}px '${labelFont}'`;
return {
text,
- text2,
font,
- font2,
color_bg,
color_fg,
- color_bg2,
- color_fg2,
};
};
@@ -120,7 +135,7 @@ const drawKey = async (id, conf, pressed) => {
c.strokeRect(padding, padding, w - padding * 2, h - padding * 2);
if (conf != null) {
- drawDoubleLineText(c, conf);
+ drawMultiLineText(c, conf);
}
// otherwise the empty key style is still drawn
});
@@ -158,8 +173,8 @@ const drawSideKnobs = async (side, confs, highlight) => {
const { text, font, color_bg, color_fg } = rectifyLabel(
confs[i],
);
- if (color_bg) {
- c.fillStyle = color_bg;
+ if (color_bg[0]) {
+ c.fillStyle = color_bg[0];
c.fillRect(
x_padding + 2,
y_padding + y_offset + 2,
@@ -167,75 +182,85 @@ const drawSideKnobs = async (side, confs, highlight) => {
h - y_padding * 2 - 2,
);
}
- c.font = font;
+ c.font = font[0];
const {
width,
actualBoundingBoxAscent,
actualBoundingBoxDescent,
- } = c.measureText(text);
+ } = c.measureText(text[0]);
const x_axis = (h - width) / 2;
const y_axis =
w / 2 +
(actualBoundingBoxAscent - actualBoundingBoxDescent) / 2;
c.rotate((90 * Math.PI) / 180);
c.fillStyle = hl ? "black" : "white";
- c.fillText(text, x_axis + y_offset, -(w - y_axis));
+ c.fillText(text[0], x_axis + y_offset, -(w - y_axis));
c.resetTransform();
}
}
});
};
-const drawDoubleLineText = (c, conf) => {
+const drawMultiLineText = (c, conf) => {
const w = c.canvas.width;
const h = c.canvas.height;
- const { text, text2, font, font2, color_fg2 } = rectifyLabel(conf);
-
- c.font = font;
- const m1 = c.measureText(text);
- const x1 = (w - m1.width) / 2;
- if (text2 != null) {
- const m2 = c.measureText(text2);
- const h1 = m1.actualBoundingBoxAscent - m1.actualBoundingBoxDescent;
- const h2 = m2.actualBoundingBoxAscent - m2.actualBoundingBoxDescent;
- const sep = h1;
- const y1 = h / 2 + h1 / 2 - sep;
- const x2 = (w - m2.width) / 2;
- const y2 = y1 + h1 / 2 + sep + h2 / 2;
- c.fillText(text, x1, y1);
-
- if (color_fg2 != null) {
- c.fillStyle = color_fg2;
+ const { text, font, color_fg } = rectifyLabel(conf);
+
+ c.save();
+ c.font = font[0];
+ let ms = [];
+ let text_h = 0;
+ const mx = c.measureText("x");
+ const sep = conf.sep
+ ? conf.sep
+ : mx.actualBoundingBoxAscent - mx.actualBoundingBoxDescent;
+ for (let i = 0; i < text.length; i++) {
+ c.font = font[i];
+ const m = c.measureText(text[i]);
+ ms.push(m);
+ text_h += m.actualBoundingBoxAscent - m.actualBoundingBoxDescent;
+ }
+ text_h += (text.length - 1) * sep;
+ let y0 = (h - text_h) / 2;
+ for (let i = 0; i < text.length; i++) {
+ const x =
+ Math.max(
+ 0,
+ w -
+ (ms[i].actualBoundingBoxRight -
+ ms[i].actualBoundingBoxLeft),
+ ) / 2;
+ const hh =
+ ms[i].actualBoundingBoxAscent - ms[i].actualBoundingBoxDescent;
+ const y = y0 + hh;
+ c.font = font[i];
+ if (color_fg[i]) {
+ c.fillStyle = color_fg[i];
}
- c.font = font2;
- c.fillText(text2, x2, y2);
- } else {
- const y1 =
- h / 2 +
- (m1.actualBoundingBoxAscent - m1.actualBoundingBoxDescent) / 2;
- c.fillText(text, x1, y1);
+ c.fillText(text[i], x, y);
+ y0 += hh + sep;
}
+ c.restore();
};
-const formatDisplayText = (formatter, value) => {
- if (isNaN(value)) {
- return "X";
- }
+const formatDisplayText = (formatter, values) => {
if (formatter) {
return Function(
- "$value",
+ "$d",
`"use strict"; return(\`${formatter}\`);`,
- )(value);
- } else {
- return value.toFixed(0).toString();
+ )(values);
}
+ if (isNaN(values[0])) {
+ return "X";
+ }
+ return values[0].toFixed(0).toString();
};
const renderAttitudeIndicator = (c, display, values) => {
const pitch = values[0];
const roll = values[1];
- const raw = values[2];
+ const cdi = values[2];
const bg = "black";
const fg = "white";
const w = c.canvas.width;
@@ -257,16 +282,17 @@ const renderAttitudeIndicator = (c, display, values) => {
c.translate(x0, y0);
c.rotate((-roll * Math.PI) / 180);
c.translate(0, (pitch / 10) * longSep);
+
c.fillStyle = "#0077b6";
c.fillRect(-w, -2 * h, 2 * w, 4 * h);
c.fillStyle = "#99582a";
c.fillRect(-w, 0, 2 * w, 4 * h);
+
c.lineWidth = 1;
c.strokeStyle = fg;
c.beginPath();
c.moveTo(-0.75 * w, 0);
c.lineTo(0.75 * w, 0);
-
c.fillStyle = fg;
c.font = `10px ${labelFont}`;
const drawMark = (i) => {
@@ -292,13 +318,42 @@ const renderAttitudeIndicator = (c, display, values) => {
c.moveTo(x0 - 30, y0);
c.lineTo(x0 - 10, y0);
c.lineTo(x0 - 10, y0 + 8);
- c.stroke();
- c.beginPath();
c.moveTo(x0 + 30, y0);
c.lineTo(x0 + 10, y0);
c.lineTo(x0 + 10, y0 + 8);
c.stroke();
+
+ // draw vertical deflection
+ const pi2 = 2 * Math.PI;
+ const vdef_x = w - 10;
+ const vdef_r = 3;
+
+ c.strokeStyle = "white";
+ c.lineWidth = 1;
+ c.beginPath();
+ for (let i = -2; i <= 2; i++) {
+ if (i != 0) {
+ const vdef_y = y0 + 13 * i;
+ c.moveTo(vdef_x + vdef_r, vdef_y);
+ c.arc(vdef_x, vdef_y, vdef_r, 0, pi2);
+ }
+ }
+ c.stroke();
+
+ // draw CDI diamond
+ const cdi_y = y0 + 13 * cdi;
+ const cdi_h = 7;
+ const cdi_w = 4;
+ c.fillStyle = "lightgreen";
+ c.strokeStyle = "black";
+ c.beginPath();
+ c.moveTo(vdef_x, cdi_y + cdi_h);
+ c.lineTo(vdef_x - cdi_w, cdi_y);
+ c.lineTo(vdef_x, cdi_y - cdi_h);
+ c.lineTo(vdef_x + cdi_w, cdi_y);
+ c.stroke();
+ c.fill();
};
const renderTextGauge = (c, display, values) => {
@@ -308,9 +363,6 @@ const renderTextGauge = (c, display, values) => {
const w = c.canvas.width;
const h = c.canvas.height;
- const text = formatDisplayText(display.formatter, value);
- const m = c.measureText(text);
-
// draw background
c.fillStyle = bg;
c.fillRect(0, 0, w, h);
@@ -318,21 +370,24 @@ const renderTextGauge = (c, display, values) => {
c.strokeStyle = fg;
c.lineWidth = 1;
- drawDoubleLineText(c, {
- text,
- text2:
- display.tag != null
- ? display.tag
- : formatDisplayText(
- display.formatter2 || display.formatter,
- values[1],
- ),
+ drawMultiLineText(c, {
+ text: formatDisplayText(display.formatter, values),
+ text2: display.formatter2
+ ? formatDisplayText(display.formatter2, values)
+ : undefined,
+ text3: display.formatter3
+ ? formatDisplayText(display.formatter3, values)
+ : undefined,
+ size: display.size,
+ size2: display.size2,
+ size3: display.size3,
+ color_fg: display.color_fg,
color_fg2: display.color_fg2,
+ color_fg3: display.color_fg3,
});
};
const renderMeterGauge = (c, display, values) => {
- const value = values[0];
const bg = "black";
const fg = "white";
const w = c.canvas.width;
@@ -344,12 +399,12 @@ const renderMeterGauge = (c, display, values) => {
return;
}
- let reading = (value - min) / (max - min);
+ let reading = (values[0] - min) / (max - min);
if (isNaN(reading)) {
reading = min;
}
- const text = formatDisplayText(display.formatter, value);
+ const text = formatDisplayText(display.formatter, values);
// draw background
c.fillStyle = bg;
@@ -582,6 +637,115 @@ const renderAltimeter = (c, display, values) => {
c.font = `12px '${labelFont}'`;
c.fillText(Math.trunc(vsi / 10) * 10, vsi_x + 2, vsi_y + vsi_h * 0.8);
}
+ const selected = values[2];
+ if (isNumber(selected)) {
+ c.fillStyle = "#6697ff";
+ c.font = `14px '${labelFont}'`;
+ c.fillText(selected, 15, 18);
+ }
+};
+
+const renderHSI = (c, display, values) => {
+ const bg = "black";
+ const fg = "white";
+ const w = c.canvas.width;
+ const h = c.canvas.height;
+
+ // draw background
+ c.fillStyle = bg;
+ c.fillRect(0, 0, w, h);
+ c.fillStyle = fg;
+ c.strokeStyle = fg;
+ c.lineWidth = 1;
+
+ const x0 = w / 2;
+ const y0 = h / 2;
+ const r = w / 2 - 5;
+ const f1 = 0.8;
+ const f2 = 0.9;
+ const cdi_r = 0.4 * r;
+ const vdef_r = 3;
+ const deg2Rad = (x) => (x / 180) * Math.PI;
+ const hdg = deg2Rad(values[0]);
+ const hdg_bug = deg2Rad(values[1]);
+ const src = display.navs[values[2]];
+ const crs = src ? deg2Rad(values[src.crs]) : null;
+ let def = src ? Math.min(Math.max(values[src.def], -3), 3) : null;
+ if (isNaN(def)) {
+ def = 0;
+ }
+ const polarXY = (theta, r) => {
+ const t = -theta - Math.PI / 2;
+ const dx = r * Math.cos(t);
+ const dy = -r * Math.sin(t);
+ return { dx, dy };
+ };
+ const pi2 = Math.PI * 2;
+
+ c.translate(x0, y0);
+ c.rotate(-hdg);
+ c.beginPath();
+ for (let i = 0; i < 36; i++) {
+ const { dx, dy } = polarXY(deg2Rad(i * 10), r);
+ const f = (i & 1) == 0 ? f1 : f2;
+ c.moveTo(dx, dy);
+ c.lineTo(dx * f, dy * f);
+ }
+
+ c.font = `14px '${labelFont}'`;
+ c.fillText("N", -5, -0.5 * r);
+
+ if (isNumber(hdg_bug)) {
+ const bug_w = 4;
+ const bug_y1 = -(r - 3);
+ const bug_y0 = -(r - 8);
+ c.stroke();
+ c.rotate(hdg_bug);
+ c.fillStyle = "blue";
+ c.beginPath();
+ c.moveTo(0, bug_y1);
+ c.lineTo(-bug_w, -(r + 1));
+ c.lineTo(-bug_w, bug_y0);
+ c.lineTo(bug_w, bug_y0);
+ c.lineTo(bug_w, -(r + 1));
+ c.lineTo(0, bug_y1);
+ c.fill();
+ c.rotate(-hdg_bug);
+ }
+
+ if (crs != null) {
+ c.rotate(crs);
+
+ for (let i = -2; i <= 2; i++) {
+ if (i != 0) {
+ const x = 13 * i;
+ c.moveTo(x + vdef_r, 0);
+ c.arc(x, 0, vdef_r, 0, pi2);
+ }
+ }
+ c.stroke();
+
+ c.beginPath();
+ const cdi_x = 13 * def;
+ c.lineWidth = 3;
+ c.strokeStyle = src.color ? src.color : "magenta";
+
+ c.moveTo(cdi_x, -(cdi_r - 1));
+ c.lineTo(cdi_x, cdi_r - 1);
+
+ c.moveTo(0, -r);
+ c.lineTo(0, -(cdi_r + 1));
+ c.moveTo(0, -r);
+
+ // crs arrowhead
+ c.lineTo(-5, -0.8 * r);
+ c.lineTo(5, -0.8 * r);
+ c.lineTo(0, -r);
+
+ c.moveTo(0, r);
+ c.lineTo(0, cdi_r + 1);
+ }
+ c.stroke();
};
const drawGauge = async (key, label, values) => {
@@ -591,6 +755,7 @@ const drawGauge = async (key, label, values) => {
attitude: renderAttitudeIndicator,
ias: renderIAS,
alt: renderAltimeter,
+ hsi: renderHSI,
};
await device.drawKey(key, (c) => {
const display = label.display;
@@ -613,7 +778,7 @@ const loadPage = async (page) => {
const conf = Array.isArray(keys) && keys.length > i ? keys[i] : null;
pms.push(drawKey(i, conf, false));
if (isObject(conf) && conf.display != null) {
- drawGauge(i, conf, NaN);
+ drawGauge(i, conf, []);
}
}
await Promise.all(pms);
@@ -625,7 +790,7 @@ device.on("connect", async () => {
/*
for (let i = 3600; i > 1000; i -= 0.1) {
await device.drawKey(0, (c) => {
- renderAltimeter(c, null, [i]);
+ renderAltimeter(c, null, [i, 500]);
});
await new Promise((res) => setTimeout(res, 10));
}
diff --git a/profile.yaml b/profile.yaml
index 6415849..8b97e75 100644
--- a/profile.yaml
+++ b/profile.yaml
@@ -1,4 +1,5 @@
---
+# This is the profile that I created for my own setup (good for DA40 with G1000 in X-Plane).
# for xplane_cmd, refer to /Resources/plugins/Commands.txt under your X-Plane installation
# for xplane_dataref, refer to /Resources/plugins/DataRefs.txt
#
@@ -48,19 +49,37 @@
keys:
- display:
type: meter
- freq: 6,
+ freq: 6
+ source:
+ - xplane_dataref: sim/flightmodel/controls/flaprat
+ min: 0
+ max: 1
+ stops:
+ - color: 'gray'
+ value_begin: 0
+ value_end: 0.2
+ - color: 'gray'
+ value_begin: 0.4
+ value_end: 0.6
+ - color: 'gray'
+ value_begin: 0.8
+ value_end: 1
+ formatter: '${$d[0] != null ? ($d[0] * 100).toFixed(0) : "X"}%'
+ - display:
+ type: meter
+ freq: 1,
source:
- xplane_dataref: sim/cockpit2/engine/indicators/MPR_in_hg[0]
min: 10
max: 30
- formatter: '${$value.toFixed(2)}'
+ formatter: '${$d[0] != null ? $d[0].toFixed(2) : "X"}'
stops:
- color: 'green'
value_begin: 13
value_end: 30
- display:
type: meter
- freq: 6,
+ freq: 1,
source:
- xplane_dataref: sim/cockpit2/engine/indicators/engine_speed_rpm[0]
min: 0
@@ -75,25 +94,7 @@
- color: 'red'
value_begin: 2700
value_end: 3000
- - display:
- type: meter
- freq: 6
- source:
- - xplane_dataref: sim/flightmodel/controls/flaprat
- min: 0
- max: 1
- stops:
- - color: 'gray'
- value_begin: 0
- value_end: 0.2
- - color: 'gray'
- value_begin: 0.4
- value_end: 0.6
- - color: 'gray'
- value_begin: 0.8
- value_end: 1
- formatter: '${($value * 100).toFixed(0)}%'
- - text: Flap
+ - text: Flaps
text2: Up
pressed:
xplane_cmd: sim/flight_controls/flaps_up
@@ -108,37 +109,76 @@
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/indicators/gps_hdef_dots_pilot
+
- display:
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
- - text: Flap
+ - xplane_dataref: sim/cockpit/autopilot/altitude
+ - text: Flaps
text2: Down
pressed:
xplane_cmd: sim/flight_controls/flaps_down
- -
- display:
type: text
source:
+ - xplane_dataref: sim/cockpit/autopilot/autopilot_state
+ - xplane_dataref: sim/cockpit2/radios/actuators/HSI_source_select_pilot
+ formatter: '${($d[0] & 2) ? "HDG" : (($d[0] & 0x200) ? "NAV" : "ROL")} ${($d[0] & 0x10) ? "VS" : (($d[0] & 0x40) ? "FLC" : (($d[0] & 0x4000) ? "ALT" : "PIT"))}'
+ formatter2: '${$d[1] == 0 ? "NAV1" : ($d[1] == 1 ? "NAV2" : ($d[1] == 2 ? "GPS" : "X"))}'
+ size: 16
+ size2: 16
+ - display:
+ 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
- formatter: '${$value.toFixed(0).padStart(3, "0")}'
- color_fg2: '#1a62fd'
+ - xplane_dataref: sim/cockpit2/radios/actuators/HSI_source_select_pilot
+ - xplane_dataref: sim/cockpit/radios/gps_course_degtm
+ - xplane_dataref: sim/cockpit2/radios/indicators/gps_hdef_dots_pilot
+ - xplane_dataref: sim/cockpit2/radios/actuators/nav1_obs_deg_mag_pilot
+ - xplane_dataref: sim/cockpit2/radios/indicators/nav1_hdef_dots_pilot
+ - xplane_dataref: sim/cockpit2/radios/actuators/nav2_obs_deg_mag_pilot
+ - xplane_dataref: sim/cockpit2/radios/indicators/nav2_hdef_dots_pilot
+ hdg: 0
+ src: 1
+ navs:
+ 2:
+ crs: 3
+ def: 4
+ color: magenta
+ 0:
+ crs: 5
+ def: 6
+ color: green
+ 1:
+ crs: 7
+ def: 8
+ color: green
- display:
type: text
+ freq: 6
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/cockpit/radios/gps_course_degtm
- tag: CRS
- formatter: '${$value.toFixed(0).padStart(3, "0")}'
- color_fg2: 'magenta'
+ formatter: '${$d[0] != null ? $d[0].toFixed(0).padStart(3, "0") : "xxx"}'
+ formatter2: 'HDG ${$d[1] != null ? $d[1].toFixed(0).padStart(3, "0") : "xxx"}'
+ formatter3: 'CRS ${$d[2] != null ? $d[2].toFixed(0).padStart(3, "0") : "xxx"}'
+ color_fg2: '#1a62fd'
+ color_fg3: 'magenta'
+ size2: 16
+ size3: 16
- text: Brake
pressed:
xplane_cmd: sim/flight_controls/brakes_toggle_regular
# Page 1
-- left:
+- right:
- text: HDG
inc:
xplane_cmd: sim/GPS/g1000n1_hdg_up
@@ -158,11 +198,6 @@
xplane_cmd: sim/GPS/g1000n1_crs_down
pressed:
xplane_cmd: sim/GPS/g1000n1_crs_sync
-
- right:
- - ''
- - ''
- - ''
keys:
- text: AP
pressed: