From c55235e1ccc8de6bc03b7d19f4f66aea5660316e Mon Sep 17 00:00:00 2001 From: Determinant Date: Fri, 30 Aug 2024 13:39:09 -0700 Subject: ... --- README.rst | 2 +- app.mjs | 93 ++++++++++++++++++++++++++++++++++++------------------------ profile.yaml | 61 ++++++++++++++++++++++----------------- 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' -- cgit v1.2.3-70-g09d2