diff --git a/.github/workflows/companion.yml b/.github/workflows/companion.yml
index ee8969d06b0..7e8837cab1d 100644
--- a/.github/workflows/companion.yml
+++ b/.github/workflows/companion.yml
@@ -8,13 +8,17 @@ on:
- v*
paths:
- '.github/workflows/companion.yml'
+ - '.github/workflows/wasm_modules.yml'
- '.github/actions/python_dependencies/**'
- '.github/actions/wasi_sdk/**'
- '.github/actions/build_companion/**'
- 'companion/**'
+ - 'fw.json'
- 'pyproject.toml'
- 'tools/build-companion.sh'
- 'tools/build-wasm-modules.sh'
+ - 'radio/src/targets/simu/CMakeLists.txt'
+ - 'radio/src/targets/simu/pch_std.h'
- 'cmake/wasm/CMakeLists.txt'
- 'cmake/FetchWamr.cmake'
- 'cmake/wamr-*.patch'
@@ -22,13 +26,17 @@ on:
pull_request:
paths:
- '.github/workflows/companion.yml'
+ - '.github/workflows/wasm_modules.yml'
- '.github/actions/python_dependencies/**'
- '.github/actions/wasi_sdk/**'
- '.github/actions/build_companion/**'
- 'companion/**'
+ - 'fw.json'
- 'pyproject.toml'
- 'tools/build-companion.sh'
- 'tools/build-wasm-modules.sh'
+ - 'radio/src/targets/simu/CMakeLists.txt'
+ - 'radio/src/targets/simu/pch_std.h'
- 'cmake/wasm/CMakeLists.txt'
- 'cmake/FetchWamr.cmake'
- 'cmake/wamr-*.patch'
@@ -49,58 +57,8 @@ on:
jobs:
modules:
- name: WASM Modules (${{ matrix.group.id }})
- runs-on: macos-15
- strategy:
- matrix:
- group:
- - id: color-1
- target: [nv14, el18, pl18, pl18ev]
- - id: color-2
- target: [pl18u, t15, t16, t18, t22]
- - id: bw-1
- target: [zorro, pocket, commando8, tprov2, tpros, bumblebee, t14]
- - id: bw-2
- target: [mt12, gx12, t20, t20v2, t12max, tx12mk2, boxer]
- - id: color-3
- target: [tx15, tx16s, x12s, f16]
- - id: mixed-1
- target: [v12, v14, v14lcd, v15, v16, x10, x10express]
- - id: bw-3
- target: [x7access, x9dp2019, x9e]
- - id: color-4
- target: [nb4p, st16, pa01, c14]
- - id: color-5
- target: [t15pro, tx16smk3, gx15, h17, t15h7]
-
- steps:
- - name: Check out the repo
- uses: actions/checkout@v7
- with:
- submodules: true
-
- - name: Setup Python Dependencies
- uses: ./.github/actions/python_dependencies
-
- - name: Setup WASI SDK
- uses: ./.github/actions/wasi_sdk
-
- - name: Build modules
- shell: bash
- env:
- FLAVOR: ${{ join(matrix.group.target, ';') }}
- CMAKE_BUILD_TYPE: 'Release'
- run: |
- mkdir output && \
- tools/build-wasm-modules.sh "$(pwd)" "$(pwd)/output/"
-
- - name: Archive production artifacts
- uses: actions/upload-artifact@v7
- with:
- name: wasm-modules-${{ matrix.group.id }}
- path: ${{github.workspace}}/output
- if-no-files-found: error
- retention-days: 15
+ name: Modules
+ uses: ./.github/workflows/wasm_modules.yml
build-linux:
name: Linux Companion
diff --git a/.github/workflows/validate_includes.yml b/.github/workflows/validate_includes.yml
new file mode 100644
index 00000000000..2e79c285afd
--- /dev/null
+++ b/.github/workflows/validate_includes.yml
@@ -0,0 +1,24 @@
+---
+name: Validate includes
+on:
+ push:
+ branches:
+ - 'main'
+ pull_request:
+ branches:
+ - 'main'
+ workflow_dispatch:
+
+# No 'paths' filter: any source edit can add an include. Pure stdlib, runs in
+# about a second.
+
+jobs:
+ validate:
+ name: Includes
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v7
+
+ - name: Check that edgetx.h does not spread
+ run: python3 tools/check_includes.py
diff --git a/.github/workflows/wasm_modules.yml b/.github/workflows/wasm_modules.yml
new file mode 100644
index 00000000000..ac91c39e0c0
--- /dev/null
+++ b/.github/workflows/wasm_modules.yml
@@ -0,0 +1,72 @@
+name: WASM Modules
+
+# Builds one simulator module per target in fw.json and uploads them as
+# wasm-modules-* artifacts. Shared by the Companion and web simulator workflows.
+on:
+ workflow_call:
+
+env:
+ # Each group takes one runner
+ GROUPS_COUNT: 9
+
+jobs:
+ plan:
+ name: Plan WASM module groups
+ runs-on: ubuntu-latest
+ outputs:
+ groups: ${{ steps.groups.outputs.groups }}
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v7
+
+ - name: Split fw.json targets into groups
+ id: groups
+ shell: bash
+ run: |
+ groups=$(jq -c --argjson n "${GROUPS_COUNT}" '
+ [.targets[][1] | rtrimstr("-")] as $t
+ | [range($n) as $i
+ | {id: "group-\($i + 1)",
+ target: [range($i; $t | length; $n) as $j | $t[$j]]}]
+ | map(select(.target | length > 0))
+ ' fw.json)
+ echo "groups=${groups}" >> "$GITHUB_OUTPUT"
+
+ modules:
+ name: WASM Modules (${{ matrix.group.id }})
+ needs: plan
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ group: ${{ fromJSON(needs.plan.outputs.groups) }}
+
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v7
+ with:
+ submodules: true
+
+ - name: Setup Python Dependencies
+ uses: ./.github/actions/python_dependencies
+
+ - name: Setup WASI SDK
+ uses: ./.github/actions/wasi_sdk
+ with:
+ arch: x86_64-linux
+
+ - name: Build modules
+ shell: bash
+ env:
+ FLAVOR: ${{ join(matrix.group.target, ';') }}
+ CMAKE_BUILD_TYPE: 'Release'
+ run: |
+ mkdir output && \
+ tools/build-wasm-modules.sh "$(pwd)" "$(pwd)/output/"
+
+ - name: Archive production artifacts
+ uses: actions/upload-artifact@v7
+ with:
+ name: wasm-modules-${{ matrix.group.id }}
+ path: ${{github.workspace}}/output
+ if-no-files-found: error
+ retention-days: 15
diff --git a/.github/workflows/web_simulator.yml b/.github/workflows/web_simulator.yml
new file mode 100644
index 00000000000..dc947e0b749
--- /dev/null
+++ b/.github/workflows/web_simulator.yml
@@ -0,0 +1,156 @@
+name: Web Simulator
+
+on:
+ schedule:
+ - cron: '0 3 * * *' # run at 3 AM UTC
+ pull_request:
+ paths:
+ - '.github/workflows/web_simulator.yml'
+ - 'web/**'
+ workflow_dispatch:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
+
+env:
+ NODE_VERSION: '22'
+
+jobs:
+ check:
+ name: Check web build
+ if: github.event_name == 'pull_request'
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v7
+
+ - name: Setup Node
+ uses: actions/setup-node@v7
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: npm
+ cache-dependency-path: web/package-lock.json
+
+ - name: Build
+ working-directory: web
+ run: |
+ npm ci
+ npm run build
+
+ modules:
+ name: Modules
+ # Repositories synced from this one get the schedule too
+ if: github.event_name != 'pull_request' && github.repository == 'EdgeTX/edgetx'
+ uses: ./.github/workflows/wasm_modules.yml
+
+ build:
+ name: Build site
+ needs: modules
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v7
+
+ - name: Setup Node
+ uses: actions/setup-node@v7
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: npm
+ cache-dependency-path: web/package-lock.json
+
+ - name: Download WASM modules
+ uses: actions/download-artifact@v8
+ with:
+ pattern: wasm-modules-*
+ path: wasm-modules
+ merge-multiple: true
+
+ # The artifacts also contain build logs, which must not be published
+ - name: Copy WASM modules
+ run: cp wasm-modules/*.wasm web/public/
+
+ - name: Check that every radio has a module
+ shell: bash
+ run: |
+ missing=0
+ while read -r module; do
+ if [[ ! -f "web/public/${module}" ]]; then
+ echo "::error::Missing module ${module}"
+ missing=1
+ fi
+ done < <(jq -r '.[].wasm' web/public/radios.json)
+ exit "${missing}"
+
+ - name: Build
+ working-directory: web
+ run: |
+ npm ci
+ npm run build
+
+ # Before the deploy job, which only runs if this one succeeds
+ - name: Start every radio
+ working-directory: web
+ run: |
+ npx playwright-core install --with-deps chromium
+ npm run smoke-test
+
+ - name: Archive site
+ uses: actions/upload-artifact@v7
+ with:
+ name: web-simulator-dist
+ path: web/dist
+ if-no-files-found: error
+ retention-days: 3
+
+ # Separate from the build, so that the Cloudflare token is only available to
+ # wrangler. The environment only accepts runs from the deployed branches.
+ deploy:
+ name: Deploy site
+ needs: build
+ runs-on: ubuntu-latest
+ environment: web-simulator
+ steps:
+ - name: Check out the repo
+ uses: actions/checkout@v7
+ with:
+ persist-credentials: false
+
+ - name: Setup Node
+ uses: actions/setup-node@v7
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Install wrangler
+ working-directory: web
+ run: npm ci --ignore-scripts
+
+ - name: Download site
+ uses: actions/download-artifact@v8
+ with:
+ name: web-simulator-dist
+ path: web/dist
+
+ - name: Deploy
+ id: deploy
+ working-directory: web
+ shell: bash
+ env:
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ run: |
+ npx wrangler deploy | tee "${RUNNER_TEMP}/deploy.log"
+ url=$(grep -oE 'https://[^ ]+\.workers\.dev' "${RUNNER_TEMP}/deploy.log" | head -n 1)
+ if [[ -z "${url}" ]]; then
+ echo "::error::Could not find the deployed URL in the wrangler output"
+ exit 1
+ fi
+ echo "url=${url}" >> "$GITHUB_OUTPUT"
+
+ - name: Check deployed site
+ env:
+ SITE_URL: ${{ steps.deploy.outputs.url }}
+ run: node web/scripts/check-deployment.js "${SITE_URL}"
diff --git a/companion/src/CMakeLists.txt b/companion/src/CMakeLists.txt
index 09986227741..0b21b7d7d08 100644
--- a/companion/src/CMakeLists.txt
+++ b/companion/src/CMakeLists.txt
@@ -136,10 +136,19 @@ endif()
############# Import radio hardware definitions ###############
set(HWDEFS_DIR "${CMAKE_SOURCE_DIR}/radio/src/boards/hw_defs")
-set(HWDEFS_TMPL "${COMPANION_SRC_DIRECTORY}/hwdefs.qrc.in")
-set(HWDEFS_PHDR "HWDEF_JSON_LIST")
set(HWDEFS_QRC "${CMAKE_CURRENT_BINARY_DIR}/hwdefs.qrc")
-set(HWDEFS_CMD "${COMPANION_SRC_DIRECTORY}/../util/generate_hwdefs_qrc.py")
+
+# CONFIGURE_DEPENDS re-runs the glob on each build, so adding or removing
+# a definition regenerates the qrc in an existing build folder
+file(GLOB HWDEF_JSON_FILES CONFIGURE_DEPENDS "${HWDEFS_DIR}/*.json")
+list(SORT HWDEF_JSON_FILES)
+set(HWDEF_JSON_ENTRIES "")
+foreach(json_file ${HWDEF_JSON_FILES})
+ get_filename_component(json_file_name ${json_file} NAME)
+ list(APPEND HWDEF_JSON_ENTRIES " ${json_file}")
+endforeach()
+list(JOIN HWDEF_JSON_ENTRIES "\n" HWDEF_JSON_LIST)
+configure_file(${COMPANION_SRC_DIRECTORY}/hwdefs.qrc.in ${HWDEFS_QRC} @ONLY)
############# Common lib ###############
@@ -158,16 +167,6 @@ set(common_MOC_HDRS
qt_wrap_cpp(common_SRCS ${common_MOC_HDRS})
-# qrc used by Companion and Simulator
-add_custom_command(OUTPUT ${HWDEFS_QRC}
- COMMAND ${PYTHON_EXECUTABLE} ${HWDEFS_CMD}
- -d ${HWDEFS_DIR}
- -t ${HWDEFS_TMPL}
- -p ${HWDEFS_PHDR}
- -o ${HWDEFS_QRC}
- DEPENDS ${HWDEFS_TMPL}
-)
-
set(common_RESOURCES
companion.qrc
${TRANSLATIONS_QRC}
diff --git a/companion/src/firmwares/boardjson.cpp b/companion/src/firmwares/boardjson.cpp
index a0327585725..4eedfaf0af8 100644
--- a/companion/src/firmwares/boardjson.cpp
+++ b/companion/src/firmwares/boardjson.cpp
@@ -238,6 +238,9 @@ const int BoardJson::getCapability(const Board::Capability capability) const
case Board::StatusLedColors:
return m_hardware.status_led_colors;
+ case Board::HasTrainerJackCrossover:
+ return m_hardware.trainer_jack_crossover;
+
case Board::HasStatusLedPwm:
return m_hardware.status_led_pwm;
@@ -1342,6 +1345,12 @@ bool BoardJson::loadFile(Board::Type board, QString hwdefn, InputsTable * inputs
hardware.cpu_type = o.value("cpu_type").toString().toStdString();
}
+ if (obj.value("trainer").isObject()) {
+ const QJsonObject &o = obj.value("trainer").toObject();
+
+ hardware.trainer_jack_crossover = o.value("trainer_jack_crossover").toBool();
+ }
+
if (obj.value("identity").isObject()) {
const QJsonObject &o = obj.value("identity").toObject();
diff --git a/companion/src/firmwares/boardjson.h b/companion/src/firmwares/boardjson.h
index 55535c345cc..0677334a7d3 100644
--- a/companion/src/firmwares/boardjson.h
+++ b/companion/src/firmwares/boardjson.h
@@ -133,6 +133,7 @@ class BoardJson
int has_int_module_support = 0;
int sport_max_baudrate = 0;
int surface = 0;
+ int trainer_jack_crossover = 0;
std::string cpu = "";
std::string cpu_type = "";
};
diff --git a/companion/src/firmwares/boards.h b/companion/src/firmwares/boards.h
index c5fb0921ae5..3a341f13a94 100644
--- a/companion/src/firmwares/boards.h
+++ b/companion/src/firmwares/boards.h
@@ -254,6 +254,7 @@ namespace Board {
HasRTC,
HasSDCard,
HasSwitchableJack,
+ HasTrainerJackCrossover,
HasTrainerModuleCPPM,
HasTrainerModuleSBUS,
HasVBat,
diff --git a/companion/src/firmwares/edgetx/yaml_generalsettings.cpp b/companion/src/firmwares/edgetx/yaml_generalsettings.cpp
index 60e1c7f1e78..23beb578dc4 100644
--- a/companion/src/firmwares/edgetx/yaml_generalsettings.cpp
+++ b/companion/src/firmwares/edgetx/yaml_generalsettings.cpp
@@ -263,6 +263,7 @@ Node convert::encode(const GeneralSettings& rhs)
node["disableTrainerPoweroffAlarm"] = (int)rhs.disableTrainerPoweroffAlarm;
node["USBMode"] = rhs.usbMode;
node["usbChargeDisabled"] = (int)rhs.usbChargeDisabled;
+ node["trainerTipOnly"] = rhs.trainerTipOnly;
node["hatsMode"] = hatsModeLut << rhs.hatsMode;
node["stickDeadZone"] = rhs.stickDeadZone;
node["jackMode"] = rhs.jackMode;
@@ -558,6 +559,7 @@ bool convert::decode(const Node& node, GeneralSettings& rhs)
node["disableTrainerPoweroffAlarm"] >> rhs.disableTrainerPoweroffAlarm;
node["USBMode"] >> rhs.usbMode;
node["usbChargeDisabled"] >> rhs.usbChargeDisabled;
+ node["trainerTipOnly"] >> rhs.trainerTipOnly;
node["hatsMode"] >> hatsModeLut >> rhs.hatsMode;
node["stickDeadZone"] >> rhs.stickDeadZone;
node["jackMode"] >> rhs.jackMode;
diff --git a/companion/src/firmwares/generalsettings.h b/companion/src/firmwares/generalsettings.h
index 4c0f34ddb91..dcb2cee69af 100644
--- a/companion/src/firmwares/generalsettings.h
+++ b/companion/src/firmwares/generalsettings.h
@@ -319,6 +319,7 @@ class GeneralSettings {
bool disableTrainerPoweroffAlarm;
unsigned int usbMode;
bool usbChargeDisabled;
+ unsigned int trainerTipOnly;
unsigned int hatsMode;
unsigned int stickDeadZone;
unsigned int jackMode;
diff --git a/companion/src/generaledit/hardware.cpp b/companion/src/generaledit/hardware.cpp
index d69f9dc4fc5..ec906689d3b 100644
--- a/companion/src/generaledit/hardware.cpp
+++ b/companion/src/generaledit/hardware.cpp
@@ -225,6 +225,16 @@ HardwarePanel::HardwarePanel(QWidget * parent, GeneralSettings & generalSettings
addParams();
}
+ if (Boards::getCapability(board, Board::HasTrainerJackCrossover)) {
+ addLabel(tr("Trainer lead"));
+ AutoComboBox *trainerLead = new AutoComboBox(this);
+ trainerLead->addItem(tr("Crossover"), 0);
+ trainerLead->addItem(tr("Standard"), 1);
+ trainerLead->setField(generalSettings.trainerTipOnly, this);
+ params->append(trainerLead);
+ addParams();
+ }
+
if (firmware->getCapability(BluetoothEnabled)) {
addLabel(tr("Bluetooth"));
diff --git a/companion/src/simulation/wasmsimulatorinterface.cpp b/companion/src/simulation/wasmsimulatorinterface.cpp
index d9081741e50..44746a2cfe5 100644
--- a/companion/src/simulation/wasmsimulatorinterface.cpp
+++ b/companion/src/simulation/wasmsimulatorinterface.cpp
@@ -444,9 +444,11 @@ void WasmSimulatorInterface::init()
m_stopRequested = false;
+ for (auto & value : m_analogValues)
+ value.store(0, std::memory_order_relaxed);
+
// Call simuInit
QMutexLocker lckr(&m_mutex);
- memset(m_analogValues, 0, sizeof(m_analogValues));
wasm_runtime_call_wasm(m_execEnv, m_fnInit, 0, nullptr);
// Query LCD dimensions
@@ -584,13 +586,16 @@ void WasmSimulatorInterface::stop()
return;
m_stopRequested = true;
- deinitAudio();
{
QMutexLocker lckr(&m_mutex);
wasm_runtime_call_wasm(m_execEnv, m_fnStop, 0, nullptr);
}
+ // Close audio only once simuStop() has joined the firmware threads, so the
+ // audio task cannot be inside SDL_QueueAudio() while the device closes.
+ deinitAudio();
+
// Fully destroy the WASM module so the next init()/start() gets a clean
// instance. WASI threads created by the firmware's RTOS layer cannot be
// reliably restarted within the same module instance.
@@ -614,16 +619,15 @@ void WasmSimulatorInterface::setVolumeGain(const int value)
void WasmSimulatorInterface::setAnalogValue(uint8_t index, int16_t value)
{
- QMutexLocker lckr(&m_mutex);
if (index < MAX_ANALOGS)
- m_analogValues[index] = value;
+ m_analogValues[index].store(value, std::memory_order_relaxed);
}
+// Called from firmware threads; must not take m_mutex (see header).
int16_t WasmSimulatorInterface::getAnalogValue(uint8_t index)
{
- QMutexLocker lckr(&m_mutex);
if (index < MAX_ANALOGS)
- return m_analogValues[index];
+ return m_analogValues[index].load(std::memory_order_relaxed);
return 0;
}
diff --git a/companion/src/simulation/wasmsimulatorinterface.h b/companion/src/simulation/wasmsimulatorinterface.h
index 6daebe8918f..1d8596f6606 100644
--- a/companion/src/simulation/wasmsimulatorinterface.h
+++ b/companion/src/simulation/wasmsimulatorinterface.h
@@ -126,7 +126,9 @@ class WasmSimulatorInterface : public SimulatorInterface
// Host-side analog values (polled by WASM via simuGetAnalog)
static constexpr int MAX_ANALOGS = 32;
- int16_t m_analogValues[MAX_ANALOGS] = {};
+ // Read by firmware threads through host_simuGetAnalog, so kept out of
+ // m_mutex: stop() holds that across simuStop(), which joins those threads.
+ std::atomic m_analogValues[MAX_ANALOGS] = {};
// Cached output values for change detection
TxOutputs m_lastOutputs;
diff --git a/companion/targets/linux/CPackLinuxDeploy.cmake.in b/companion/targets/linux/CPackLinuxDeploy.cmake.in
index 0a1e115d32f..2df9f7e27b8 100644
--- a/companion/targets/linux/CPackLinuxDeploy.cmake.in
+++ b/companion/targets/linux/CPackLinuxDeploy.cmake.in
@@ -1,6 +1,12 @@
execute_process(COMMAND @CMAKE_MAKE_PROGRAM@ DESTDIR=@APPIMAGE_DIR@ install
WORKING_DIRECTORY @CMAKE_BINARY_DIR@)
+# appimagetool creates .DirIcon from a PNG or XPM at the top of the AppDir, and
+# fails without one. linuxdeploy only puts the scalable SVG there.
+file(COPY_FILE
+ "@APPIMAGE_DIR@@CMAKE_INSTALL_PREFIX@/share/icons/hicolor/256x256/apps/@COMPANION_NAME@.png"
+ "@APPIMAGE_DIR@/@COMPANION_NAME@.png")
+
# linuxdeploy-plugin-qt only bundles the xcb platform plugin by default, so also
# bundle the Wayland platform plugins and their shell/decoration integrations.
# Set via ENV as the lists contain ';' which execute_process would split.
diff --git a/companion/util/generate_hwdefs_qrc.py b/companion/util/generate_hwdefs_qrc.py
deleted file mode 100755
index 7d9085430e3..00000000000
--- a/companion/util/generate_hwdefs_qrc.py
+++ /dev/null
@@ -1,51 +0,0 @@
-#!/usr/bin/env python3
-
-import argparse
-import glob
-from os import path
-
-def main(jsondir, template, placeholder, output):
- if not path.isdir(jsondir):
- raise Exception(jsondir + " is not a directory")
-
- try:
- tmplt = open(template, "r")
- except:
- raise Exception("Unable to open template file " + template)
-
- try:
- qrc = open(output, "w")
- except:
- raise Exception("Unable to open " + output + " for writing")
-
- line = tmplt.readline()
-
- while placeholder not in line and line != '':
- qrc.write(line)
- line = tmplt.readline()
-
- for f in sorted(glob.iglob(path.join(jsondir, '*.json'), recursive = False)):
- fname = path.basename(f)
- qrc.write(' ' + f + '' + '\n')
-
- line = tmplt.readline()
-
- while line != '':
- qrc.write(line)
- line = tmplt.readline()
-
- tmplt.close()
- qrc.close()
-
-
-if __name__ == "__main__":
-
- parser = argparse.ArgumentParser(description='Generate hardware definitions resource file')
- parser.add_argument('-d', metavar='json files directory', required=True)
- parser.add_argument('-t', metavar='template file', required=True)
- parser.add_argument('-p', metavar='placeholder', required=True)
- parser.add_argument('-o', metavar='qrc file', required=True)
-
- args = parser.parse_args()
-
- main(args.d, args.t, args.p, args.o)
diff --git a/docs/development/header-guidelines.md b/docs/development/header-guidelines.md
new file mode 100644
index 00000000000..84c4ea7b650
--- /dev/null
+++ b/docs/development/header-guidelines.md
@@ -0,0 +1,73 @@
+# Header Guidelines
+
+A trivial file like `maths.cpp` used to expand to more than 54,000 lines, because `edgetx.h` includes nearly everything. This has three costs:
+
+- **Compile time:** about 84% of compile time is spent in the front end, mostly parsing headers.
+- **Rebuilds:** a change to `edgetx.h`, or to anything it includes, rebuilds most of the tree.
+- **Coupling:** code can reach anything without saying so, which blurs the separation between hardware and software.
+
+These rules apply to new code. Existing code moves towards them gradually, in small pull requests.
+
+## Rules
+
+### 1. Depend downwards only
+
+From lowest to highest:
+
+1. `os`
+2. `hal`, `drivers`
+3. `boards`, `targets`
+4. Core: data model, mixer, telemetry, storage
+5. `gui`
+
+A layer only includes headers from its own level or below. In particular, the data model must not include GUI headers.
+
+### 2. Do not include `edgetx.h` in headers or hardware code
+
+Headers and everything under `os`, `hal`, `drivers`, `boards` and `targets` must not include `edgetx.h`. `tools/check_includes.py` enforces this in CI.
+
+Other new files should avoid it too. If a file only needs a few declarations, move them to a small header, as `maths.h` does.
+
+### 3. Include what you use
+
+Each file includes the headers for what it uses directly, and does not rely on getting them through another header.
+
+### 4. Declare unconditionally
+
+Use `#if` around definitions and struct layouts, not function declarations. A conditional declaration makes every file that sees it differ between radios, even when the file never calls the function, which prevents reusing compiled output.
+
+```cpp
+// Avoid
+#if defined(FUNCTION_SWITCHES)
+void evalFunctionSwitches();
+#endif
+
+// Prefer
+void evalFunctionSwitches();
+```
+
+### 5. Keep widely included headers light
+
+In a header included by many files:
+
+- forward-declare types used only by pointer or reference;
+- avoid ``, ``, `` and similar if they can stay in the `.cpp`.
+
+### 6. Do not add global `-D` flags
+
+Every file sees a global define. For a new option, use a config header that only the files concerned include, or runtime data from the hardware definitions.
+
+## The `edgetx.h` baseline
+
+`tools/check_includes_baseline.txt` lists the headers and hardware files that still include `edgetx.h`. It can only shrink:
+
+- a new include in a header or in hardware code fails the check;
+- once a file is fixed, its entry must be removed. `python3 tools/check_includes.py --update` does this.
+
+Do not use `--update` to add entries.
+
+## Precompiled standard library headers
+
+WASM builds precompile the headers in `radio/src/targets/simu/pch_std.h`, and force-include them in every C++ file. A file that forgets `#include ` can therefore still compile for WASM, but the firmware build will catch it.
+
+Only add standard library headers to that file. `USE_PCH=OFF` turns it off.
diff --git a/mkdocs.yml b/mkdocs.yml
index 99d4a8d409a..035142afbbf 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -98,6 +98,7 @@ nav:
- External Module Protocols: development/external-module-protocols.md
- YAML Parser/Generator: development/yaml-parser-generator.md
- Mixer Synchronisation: development/mixer-synchronisation.md
+ - Header Guidelines: development/header-guidelines.md
exclude_docs: |
README.md
diff --git a/radio/src/boards/generic_stm32/trainer_ports.cpp b/radio/src/boards/generic_stm32/trainer_ports.cpp
index c0373921f4a..07eb10c1060 100644
--- a/radio/src/boards/generic_stm32/trainer_ports.cpp
+++ b/radio/src/boards/generic_stm32/trainer_ports.cpp
@@ -29,12 +29,13 @@
#include "dataconstants.h"
#include "hal.h"
+#include "edgetx.h"
__WEAK void board_trainer_init()
{
#if defined(TRAINER_DETECT_GPIO)
gpio_init(TRAINER_DETECT_GPIO, GPIO_IN_PU, GPIO_PIN_SPEED_LOW);
-#endif
+#endif
trainer_init();
}
@@ -63,11 +64,6 @@ static const stm32_pulse_timer_t trainerOutputTimer = {
.DMA_TC_CallbackPtr = nullptr,
};
-void trainer_init_dsc_out()
-{
- trainer_init_output(&trainerOutputTimer);
-}
-
static const stm32_pulse_timer_t trainerInputTimer = {
.GPIO = TRAINER_IN_GPIO,
.GPIO_Alternate = TRAINER_GPIO_AF,
@@ -82,6 +78,25 @@ static const stm32_pulse_timer_t trainerInputTimer = {
.DMA_TC_CallbackPtr = nullptr,
};
+#if defined(TRAINER_JACK_CROSSOVER)
+// Trainer in is on the jack tip and out on the ring, for the crossover lead
+// the radio ships with. A mono lead grounds the ring, so optionally send on
+// the tip (the input pin) as well.
+static_assert(__IS_TRAINER_TIMER_OUT_CHANNEL_SUPPORTED(TRAINER_IN_TIMER_Channel),
+ "Unsupported trainer timer input channel for tip-only output");
+#endif
+
+void trainer_init_dsc_out()
+{
+#if defined(TRAINER_JACK_CROSSOVER)
+ if (g_eeGeneral.trainerTipOnly) {
+ trainer_init_output(&trainerInputTimer);
+ return;
+ }
+#endif
+ trainer_init_output(&trainerOutputTimer);
+}
+
void trainer_init_dsc_in()
{
trainer_init_capture(&trainerInputTimer);
diff --git a/radio/src/boards/hw_defs/el18.json b/radio/src/boards/hw_defs/el18.json
index 1001ca99c3c..b30b87fd22f 100644
--- a/radio/src/boards/hw_defs/el18.json
+++ b/radio/src/boards/hw_defs/el18.json
@@ -404,6 +404,7 @@
"trainer_timer_irqn": "TIM4_IRQn",
"trainer_timer_irqhandler": "TIM4_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_2",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
}
}
diff --git a/radio/src/boards/hw_defs/nb4p.json b/radio/src/boards/hw_defs/nb4p.json
index 09154e6d46c..37192a06563 100644
--- a/radio/src/boards/hw_defs/nb4p.json
+++ b/radio/src/boards/hw_defs/nb4p.json
@@ -267,6 +267,7 @@
"trainer_timer_irqn": "TIM4_IRQn",
"trainer_timer_irqhandler": "TIM4_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_2",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
}
}
diff --git a/radio/src/boards/hw_defs/nv14.json b/radio/src/boards/hw_defs/nv14.json
index 045f71ba681..706c95dd323 100644
--- a/radio/src/boards/hw_defs/nv14.json
+++ b/radio/src/boards/hw_defs/nv14.json
@@ -404,6 +404,7 @@
"trainer_timer_irqn": "TIM4_IRQn",
"trainer_timer_irqhandler": "TIM4_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_2",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
}
}
diff --git a/radio/src/boards/hw_defs/pa01.json b/radio/src/boards/hw_defs/pa01.json
index 1c04a5c14cd..0f763794af6 100644
--- a/radio/src/boards/hw_defs/pa01.json
+++ b/radio/src/boards/hw_defs/pa01.json
@@ -392,6 +392,7 @@
"trainer_timer_irqn": "TIM1_CC_IRQn",
"trainer_timer_irqhandler": "TIM1_CC_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_1",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
}
}
diff --git a/radio/src/boards/hw_defs/pl18.json b/radio/src/boards/hw_defs/pl18.json
index da43303620a..bab36edf8b4 100644
--- a/radio/src/boards/hw_defs/pl18.json
+++ b/radio/src/boards/hw_defs/pl18.json
@@ -390,6 +390,7 @@
"trainer_timer_irqn": "TIM4_IRQn",
"trainer_timer_irqhandler": "TIM4_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_2",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
}
}
diff --git a/radio/src/boards/hw_defs/pl18ev.json b/radio/src/boards/hw_defs/pl18ev.json
index 0be17d570e7..b4ea1e0c1e0 100644
--- a/radio/src/boards/hw_defs/pl18ev.json
+++ b/radio/src/boards/hw_defs/pl18ev.json
@@ -434,6 +434,7 @@
"trainer_timer_irqn": "TIM4_IRQn",
"trainer_timer_irqhandler": "TIM4_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_2",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
}
}
diff --git a/radio/src/boards/hw_defs/pl18u.json b/radio/src/boards/hw_defs/pl18u.json
index 171ef41e0b7..579ef426393 100644
--- a/radio/src/boards/hw_defs/pl18u.json
+++ b/radio/src/boards/hw_defs/pl18u.json
@@ -407,6 +407,7 @@
"trainer_timer_irqn": "TIM4_IRQn",
"trainer_timer_irqhandler": "TIM4_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_2",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
}
}
diff --git a/radio/src/boards/hw_defs/st16.json b/radio/src/boards/hw_defs/st16.json
index 7b31d781175..ad078c9adc9 100644
--- a/radio/src/boards/hw_defs/st16.json
+++ b/radio/src/boards/hw_defs/st16.json
@@ -511,15 +511,16 @@
"usb_gpio_af": "GPIO_AF10"
},
"trainer": {
- "trainer_in_gpio": "GPIO_PIN(GPIOE, 5)",
- "trainer_in_timer_channel": "LL_TIM_CHANNEL_CH1",
- "trainer_out_gpio": "GPIO_PIN(GPIOE, 6)",
- "trainer_out_timer_channel": "LL_TIM_CHANNEL_CH2",
+ "trainer_in_gpio": "GPIO_PIN(GPIOE, 6)",
+ "trainer_in_timer_channel": "LL_TIM_CHANNEL_CH2",
+ "trainer_out_gpio": "GPIO_PIN(GPIOE, 5)",
+ "trainer_out_timer_channel": "LL_TIM_CHANNEL_CH1",
"trainer_timer": "TIM15",
"trainer_timer_irqn": "TIM15_IRQn",
"trainer_timer_irqhandler": "TIM15_IRQHandler",
"trainer_gpio_af": "LL_GPIO_AF_4",
- "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)"
+ "trainer_timer_freq": "(PERI1_FREQUENCY * TIMER_MULT_APB1)",
+ "trainer_jack_crossover": true
},
"key_lock_combo": [
"KEY_EXIT",
diff --git a/radio/src/datastructs_private.h b/radio/src/datastructs_private.h
index 18c9ca68c1e..6a15235c8f7 100644
--- a/radio/src/datastructs_private.h
+++ b/radio/src/datastructs_private.h
@@ -1219,13 +1219,16 @@ PACK(struct RadioData {
// Radio level tabs control (global settings)
NOBACKUP(uint8_t modelSelectLayout:2);
NOBACKUP(uint8_t radioThemesDisabled:1);
+ // 0 = trainer in/out on their own jack contacts (crossover lead, default),
+ // 1 = both on the tip (mono / straight lead). TRAINER_JACK_CROSSOVER only
+ NOBACKUP(uint8_t trainerTipOnly:1);
#if defined(USB_CHARGE_CONTROL)
// 0 = charge while USB active (default), 1 = hold the charger off while USB
// is plugged in SD/Joystick/VCP mode
NOBACKUP(uint8_t usbChargeDisabled:1);
- NOBACKUP(uint8_t spare:6 SKIP);
+ NOBACKUP(uint8_t spare:5 SKIP);
#else
- NOBACKUP(uint8_t spare:7 SKIP);
+ NOBACKUP(uint8_t spare:6 SKIP);
#endif
#elif LCD_W == 128
uint8_t invertLCD:1; // Invert B&W LCD display
diff --git a/radio/src/edgetx.h b/radio/src/edgetx.h
index 33b4c4d407c..f7038665aa8 100644
--- a/radio/src/edgetx.h
+++ b/radio/src/edgetx.h
@@ -353,15 +353,13 @@ uint8_t statusLedPhaseColor(uint8_t phase);
bool statusLedColorAvailable(int color);
#endif
-uint16_t isqrt32(uint32_t n);
+#include "maths.h"
void setDefaultOwnerId();
void generalDefault();
void generalDefaultSwitches();
void generalDefaultUILanguage();
-uint32_t hash(const void * ptr, uint32_t size);
-
#define calc100to256_16Bits(x) calc100to256(x)
#define calc100toRESX_16Bits(x) calc100toRESX(x)
diff --git a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp
index a44f9e4ac66..0654513ea47 100644
--- a/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp
+++ b/radio/src/gui/colorlcd/libui/etx_lv_theme.cpp
@@ -430,7 +430,7 @@ void etx_solid_bg(lv_obj_t* obj, LcdColorIndex bg_color,
void etx_font(lv_obj_t* obj, FontIndex fontIdx, lv_style_selector_t selector)
{
// Remove old style first
- for (int i = 0; i < TOTAL_COLOR_COUNT; i += 1)
+ for (int i = 0; i < FONTS_COUNT; i += 1)
lv_obj_remove_style(obj, &styles->font[i], selector);
etx_obj_add_style(obj, styles->font[fontIdx], selector);
}
diff --git a/radio/src/gui/colorlcd/radio/radio_hardware.cpp b/radio/src/gui/colorlcd/radio/radio_hardware.cpp
index fc57e4c3af6..e941fb7f173 100644
--- a/radio/src/gui/colorlcd/radio/radio_hardware.cpp
+++ b/radio/src/gui/colorlcd/radio/radio_hardware.cpp
@@ -22,6 +22,7 @@
#include "radio_hardware.h"
#include "edgetx.h"
+#include "choice.h"
#include "getset_helpers.h"
#include "hal/adc_driver.h"
#include "hw_extmodule.h"
@@ -165,6 +166,21 @@ const static SetupLineDef setupLines[] = {
new ToggleSwitch(parent, {x, y, 0, 0}, GET_SET_DEFAULT(g_eeGeneral.audioMuteEnable));
}
},
+#endif
+#if defined(TRAINER_JACK_CROSSOVER)
+ {
+ // Trainer jack wiring: crossover lead vs mono / straight lead
+ STR_DEF(STR_TRAINER_JACK_MODE),
+ [](Window* parent, coord_t x, coord_t y) {
+ new Choice(parent, {x, y, 0, 0}, STR_TRAINER_JACK_MODES, 0, 1,
+ GET_DEFAULT(g_eeGeneral.trainerTipOnly), [=](int32_t newValue) {
+ g_eeGeneral.trainerTipOnly = newValue;
+ SET_DIRTY();
+ // restarted on the new contacts by checkTrainerSettings()
+ stopTrainer();
+ });
+ }
+ },
#endif
{nullptr, nullptr},
};
diff --git a/radio/src/maths.cpp b/radio/src/maths.cpp
index 592dd1c1873..b171fbd5ff9 100644
--- a/radio/src/maths.cpp
+++ b/radio/src/maths.cpp
@@ -19,7 +19,7 @@
* GNU General Public License for more details.
*/
-#include "edgetx.h"
+#include "maths.h"
uint16_t isqrt32(uint32_t n)
{
diff --git a/radio/src/maths.h b/radio/src/maths.h
new file mode 100644
index 00000000000..779433f7da2
--- /dev/null
+++ b/radio/src/maths.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Based on code named
+ * opentx - https://github.com/opentx/opentx
+ * th9x - http://code.google.com/p/th9x
+ * er9x - http://code.google.com/p/er9x
+ * gruvin9x - http://code.google.com/p/gruvin9x
+ *
+ * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+
+#include
+
+uint16_t isqrt32(uint32_t n);
+uint32_t hash(const void * ptr, uint32_t size);
diff --git a/radio/src/storage/yaml/yaml_datastructs_c14.cpp b/radio/src/storage/yaml/yaml_datastructs_c14.cpp
index 81079dac127..7a12f97092c 100644
--- a/radio/src/storage/yaml/yaml_datastructs_c14.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_c14.cpp
@@ -474,7 +474,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_f16.cpp b/radio/src/storage/yaml/yaml_datastructs_f16.cpp
index deca61c1a1b..1dbd5d91bca 100644
--- a/radio/src/storage/yaml/yaml_datastructs_f16.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_f16.cpp
@@ -475,7 +475,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_gx15.cpp b/radio/src/storage/yaml/yaml_datastructs_gx15.cpp
index 5373309f211..2983abac9ea 100644
--- a/radio/src/storage/yaml/yaml_datastructs_gx15.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_gx15.cpp
@@ -498,8 +498,9 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
YAML_UNSIGNED( "usbChargeDisabled", 1 ),
- YAML_PADDING( 6 ),
+ YAML_PADDING( 5 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_h17.cpp b/radio/src/storage/yaml/yaml_datastructs_h17.cpp
index 7cad23b569c..de5cb26d87d 100644
--- a/radio/src/storage/yaml/yaml_datastructs_h17.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_h17.cpp
@@ -469,7 +469,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp b/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp
index 41e83bb5be6..03e807226be 100644
--- a/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_nb4p.cpp
@@ -465,7 +465,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp
index 06b9f866951..36672f520b7 100644
--- a/radio/src/storage/yaml/yaml_datastructs_nv14.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_nv14.cpp
@@ -465,7 +465,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ARRAY("keyShortcuts", 8, 6, struct_KeyShortcut, NULL),
YAML_ARRAY("qmFavorites", 8, 12, struct_QMFavorite, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_pa01.cpp b/radio/src/storage/yaml/yaml_datastructs_pa01.cpp
index 96643126a95..4f0ac605118 100644
--- a/radio/src/storage/yaml/yaml_datastructs_pa01.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_pa01.cpp
@@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp
index e41b4810019..dcbe900758b 100644
--- a/radio/src/storage/yaml/yaml_datastructs_pl18.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_pl18.cpp
@@ -472,7 +472,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp b/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp
index cfddee0632a..2def7ffbe9a 100644
--- a/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_pl18u.cpp
@@ -465,7 +465,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_st16.cpp b/radio/src/storage/yaml/yaml_datastructs_st16.cpp
index eca99be6f0d..d1536f51f6d 100644
--- a/radio/src/storage/yaml/yaml_datastructs_st16.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_st16.cpp
@@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_t15.cpp b/radio/src/storage/yaml/yaml_datastructs_t15.cpp
index 8e25a6ef249..1579cbf38ce 100644
--- a/radio/src/storage/yaml/yaml_datastructs_t15.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_t15.cpp
@@ -483,7 +483,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp b/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp
index efecdd65629..7513972850a 100644
--- a/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_t15h7.cpp
@@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp b/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp
index efecdd65629..7513972850a 100644
--- a/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_t15pro.cpp
@@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp b/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp
index 38114b7a477..ab639771abc 100644
--- a/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_t18h7.cpp
@@ -493,7 +493,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_t22.cpp b/radio/src/storage/yaml/yaml_datastructs_t22.cpp
index efecdd65629..7513972850a 100644
--- a/radio/src/storage/yaml/yaml_datastructs_t22.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_t22.cpp
@@ -498,7 +498,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_tx15.cpp b/radio/src/storage/yaml/yaml_datastructs_tx15.cpp
index 5373309f211..2983abac9ea 100644
--- a/radio/src/storage/yaml/yaml_datastructs_tx15.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_tx15.cpp
@@ -498,8 +498,9 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
YAML_UNSIGNED( "usbChargeDisabled", 1 ),
- YAML_PADDING( 6 ),
+ YAML_PADDING( 5 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp b/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp
index 88ee740d5b7..e62517f3eaa 100644
--- a/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_tx16smk3.cpp
@@ -499,8 +499,9 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
YAML_UNSIGNED( "usbChargeDisabled", 1 ),
- YAML_PADDING( 6 ),
+ YAML_PADDING( 5 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_v12.cpp b/radio/src/storage/yaml/yaml_datastructs_v12.cpp
index 3c886a9d22a..64e63fe2f59 100644
--- a/radio/src/storage/yaml/yaml_datastructs_v12.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_v12.cpp
@@ -493,7 +493,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_v15.cpp b/radio/src/storage/yaml/yaml_datastructs_v15.cpp
index 192f60d3926..4dd823e075c 100644
--- a/radio/src/storage/yaml/yaml_datastructs_v15.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_v15.cpp
@@ -500,7 +500,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/storage/yaml/yaml_datastructs_x10.cpp b/radio/src/storage/yaml/yaml_datastructs_x10.cpp
index f3c773b8e0b..920a8cf6a40 100644
--- a/radio/src/storage/yaml/yaml_datastructs_x10.cpp
+++ b/radio/src/storage/yaml/yaml_datastructs_x10.cpp
@@ -474,7 +474,8 @@ static const struct YamlNode struct_RadioData[] = {
YAML_UNSIGNED( "favMultiMode", 1 ),
YAML_UNSIGNED( "modelSelectLayout", 2 ),
YAML_UNSIGNED( "radioThemesDisabled", 1 ),
- YAML_PADDING( 7 ),
+ YAML_UNSIGNED( "trainerTipOnly", 1 ),
+ YAML_PADDING( 6 ),
YAML_UNSIGNED( "pwrOffIfInactive", 8 ),
YAML_ENUM("statusLedError", 2, enum_StatusLedColor, NULL),
YAML_ENUM("statusLedReady", 2, enum_StatusLedColor, NULL),
diff --git a/radio/src/targets/common/arm/CMakeLists.txt b/radio/src/targets/common/arm/CMakeLists.txt
index 564ace9c2c0..4e5bebf2bae 100644
--- a/radio/src/targets/common/arm/CMakeLists.txt
+++ b/radio/src/targets/common/arm/CMakeLists.txt
@@ -275,7 +275,10 @@ if(GHOST)
)
endif()
-add_definitions(-g)
+# The WASM modules are stripped at link time, so this would be wasted work
+if(NOT WASI)
+ add_definitions(-g)
+endif()
add_definitions(-DCPUARM)
add_definitions(-DGPS)
diff --git a/radio/src/targets/simu/CMakeLists.txt b/radio/src/targets/simu/CMakeLists.txt
index 4a7d2394e34..e53dba1bfa4 100644
--- a/radio/src/targets/simu/CMakeLists.txt
+++ b/radio/src/targets/simu/CMakeLists.txt
@@ -95,6 +95,14 @@ if(WASI)
target_compile_options(radiolib_native PUBLIC ${WASI_COMPILE_OPTIONS})
target_compile_options(simu_drivers PUBLIC ${WASI_COMPILE_OPTIONS})
+ option(USE_PCH "Precompile the standard library headers" ON)
+ if(USE_PCH)
+ target_precompile_headers(radiolib_native PRIVATE
+ "$<$:${CMAKE_CURRENT_SOURCE_DIR}/pch_std.h>"
+ )
+ target_precompile_headers(simu_drivers REUSE_FROM radiolib_native)
+ endif()
+
add_executable(wasi-module EXCLUDE_FROM_ALL
${SIMU_SRC}
)
diff --git a/radio/src/targets/simu/pch_std.h b/radio/src/targets/simu/pch_std.h
new file mode 100644
index 00000000000..f20bd4f313d
--- /dev/null
+++ b/radio/src/targets/simu/pch_std.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) EdgeTX
+ *
+ * Based on code named
+ * opentx - https://github.com/opentx/opentx
+ * th9x - http://code.google.com/p/th9x
+ * er9x - http://code.google.com/p/er9x
+ * gruvin9x - http://code.google.com/p/gruvin9x
+ *
+ * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+// Precompiled for the WASM builds, where parsing these headers takes about half
+// of the compile time. Standard library headers only: including EdgeTX headers
+// here would make every file depend on them.
+#pragma once
+
+#include
+#include
+#include
+#include