feat: WASM modules based simulation in Companion (#6435)

This commit is contained in:
Raphael Coeffic
2026-03-20 12:19:17 +10:00
committed by GitHub
parent 5f8a38bb18
commit 04a2231aee
80 changed files with 3126 additions and 2220 deletions
@@ -0,0 +1,64 @@
name: 'Build Companion'
inputs:
os:
description: 'OS / Architecture'
required: true
qt-version:
description: 'Qt version'
default: '6.9.3'
qt-arch:
description: 'Qt architecture'
required: runner.os != 'Linux'
sdl2-version:
description: 'SDL2 version'
default: '2.32.8'
runs:
using: "composite"
steps:
- name: Install Qt
if: runner.os != 'Linux'
uses: jdpurcell/install-qt-action@v5
env:
AQT_CONFIG: ${{ github.workspace }}/tools/aqt-settings.ini
with:
version: ${{ inputs.qt-version }}
modules: 'qtmultimedia qtserialport'
arch: ${{ inputs.qt-arch }}
cache: true
cache-key-prefix: 'install-qt-action-${{ inputs.qt-arch }}'
- name: Setup SDL2
if: runner.os != 'Linux'
shell: bash
env:
ACTION_PATH: ${{ github.action_path }}
SDL2_VERSION: ${{ inputs.sdl2-version }}
run: |
"${ACTION_PATH}/setup-sdl2.sh" ${SDL2_VERSION}
- name: Setup MSVC
if: runner.os == 'Windows'
shell: pwsh
run: ${{ github.action_path }}/setup-msvc.ps1
- name: Build
shell: bash
env:
CMAKE_BUILD_TYPE: 'Release'
run: |
mkdir output && \
CMAKE_PREFIX_PATH="$QT_ROOT_DIR;$SDL2_ROOT" \
tools/build-companion.sh "$(pwd)" "$(pwd)/output/"
- name: Compose release filename
run: echo "artifact_name=edgetx-cpn-${{ inputs.os }}-${GITHUB_REF##*/}" >> $GITHUB_ENV
shell: bash
- name: Archive production artifacts
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}"
path: ${{github.workspace}}/output
retention-days: 15
@@ -0,0 +1,39 @@
# setup-msvc.ps1
# Runs vcvarsall.bat and exports only the new/changed environment
# variables to GitHub Actions, avoiding clobbering existing ones.
param(
[string]$Arch = "x64"
)
# Find VS installation
$vsWhere = "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe"
$vsPath = & $vsWhere -latest -property installationPath
$vcvars = Join-Path $vsPath "VC\Auxiliary\Build\vcvarsall.bat"
if (-not (Test-Path $vcvars)) {
Write-Error "vcvarsall.bat not found at: $vcvars"
exit 1
}
# Snapshot environment before
$before = @{}
Get-ChildItem env: | ForEach-Object { $before[$_.Name] = $_.Value }
# Run vcvarsall.bat and import its environment into this process
cmd /c "`"$vcvars`" $Arch && set" | ForEach-Object {
if ($_ -match '^([^=]+)=(.*)$') {
[System.Environment]::SetEnvironmentVariable($Matches[1], $Matches[2])
}
}
# Export only changed/new variables to GitHub Actions
$count = 0
Get-ChildItem env: | ForEach-Object {
if ($before[$_.Name] -ne $_.Value) {
"$($_.Name)=$($_.Value)" | Out-File -Append -Encoding utf8 $env:GITHUB_ENV
$count++
}
}
Write-Host "Exported $count environment variables to GITHUB_ENV"
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
# setup-sdl2.sh
# Downloads/installs SDL2 for Windows and macOS.
# Linux is handled separately via a Docker container.
set -euo pipefail
VERSION="${1:?Usage: setup-sdl2.sh <version>}"
case "$RUNNER_OS" in
Windows)
ARCHIVE="SDL2-devel-${VERSION}-VC.zip"
URL="https://github.com/libsdl-org/SDL/releases/download/release-${VERSION}/${ARCHIVE}"
DEST="${RUNNER_TEMP}/SDL2"
echo "Downloading SDL2 ${VERSION} (Windows/MSVC)..."
curl -fsSL -o "${RUNNER_TEMP}/${ARCHIVE}" "${URL}"
unzip -q "${RUNNER_TEMP}/${ARCHIVE}" -d "${DEST}"
SDL2_ROOT="${DEST}/SDL2-${VERSION}"
echo "SDL2_ROOT=${SDL2_ROOT}" >> "${GITHUB_ENV}"
;;
macOS)
ARCHIVE="SDL2-${VERSION}.dmg"
URL="https://github.com/libsdl-org/SDL/releases/download/release-${VERSION}/${ARCHIVE}"
echo "Downloading SDL2 ${VERSION} (macOS framework)..."
curl -fsSL -o "${RUNNER_TEMP}/${ARCHIVE}" "${URL}"
hdiutil attach "${RUNNER_TEMP}/${ARCHIVE}" -quiet -mountpoint "${RUNNER_TEMP}/sdl2-mnt"
sudo cp -R "${RUNNER_TEMP}/sdl2-mnt/SDL2.framework" /Library/Frameworks/
hdiutil detach "${RUNNER_TEMP}/sdl2-mnt" -quiet
;;
Linux)
echo "Skipping — SDL2 is provided by the Docker container"
;;
*)
echo "::error::Unsupported RUNNER_OS: ${RUNNER_OS}"
exit 1
;;
esac
echo "SDL2 ${VERSION} setup complete for ${RUNNER_OS}"
@@ -0,0 +1,17 @@
name: 'Setup WASI SDK'
inputs:
python-version:
description: 'Python version'
default: '3.13'
runs:
using: "composite"
steps:
- uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python-version }}
cache: 'pip' # caching pip dependencies
- run: python3 -m pip install -r requirements.txt
shell: bash
+20
View File
@@ -0,0 +1,20 @@
name: 'Setup WASI SDK'
inputs:
arch:
description: 'OS / Architecture (ex: arm64-macos)'
default: 'arm64-macos'
version:
description: 'SDK version'
default: '25'
runs:
using: "composite"
steps:
- name: Install WASI SDK
if: steps.cache-wasi-sdk.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir /tmp/wasi-sdk
curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-${{ inputs.version }}/wasi-sdk-${{ inputs.version }}.0-${{ inputs.arch }}.tar.gz | tar xz --strip-components 1 -C /tmp/wasi-sdk
sudo mv /tmp/wasi-sdk /opt/wasi-sdk
+16 -34
View File
@@ -15,9 +15,6 @@ on:
- '.gitmodules'
- 'CMakeLists.txt'
pull_request:
branches:
- 'main'
- '[0-9]+.[0-9]+'
paths: *trigger-paths
jobs:
@@ -30,7 +27,7 @@ jobs:
strategy:
matrix:
target:
- x9dp2019
- x9dp2019;x9e
- tx15
- tx16s
- tx16smk3
@@ -38,20 +35,14 @@ jobs:
- el18
- pl18
- pl18ev
- t12max
- t12max;t14
- t15
- t16
- t18
- tlite #STM32F2
- t20
- t14
- t20v2
- tx12 #STM32F2
- t20;t20v2
- x10
- x12s
- x9e
- mt12
- gx12
- mt12;gx12
- nb4p
- st16
- t15pro
@@ -65,7 +56,6 @@ jobs:
uses: actions/checkout@v6
with:
submodules: recursive
# fetch-depth: 0 # https://github.com/actions/checkout#Fetch-all-history-for-all-tags-and-branches
- name: Test ${{ matrix.target }}
env:
@@ -86,28 +76,21 @@ jobs:
matrix:
target:
- nv14;el18
- pl18;pl18ev;pl18u
- pa01
- t12max
- t15;t16;t18
- zorro;pocket;mt12;commando8
- pl18;pl18ev
- pl18u;pa01
- t15;t16
- t14;t18;mt12
- zorro;pocket;commando8
- tprov2;tpros;bumblebee
- t20;t20v2;t14
- tx12mk2;boxer;gx12
- tx15
- tx16s
- tx16smk3
- f16
- mt12;gx12;t20;t20v2
- t12max;tx12mk2;boxer
- tx15;tx16s
- x12s;f16
- v12;v14;v16
- x10;x10express
- x12s
- x7access
- x9dp2019
- x9e;x9e-hall
- mt12;gx12
- nb4p
- st16
- t15pro
- x7access;x9dp2019;x9e;x9e-hall
- nb4p;st16
- tx16smk3;t14;t15pro
container:
image: ghcr.io/edgetx/edgetx-dev:latest
volumes:
@@ -144,7 +127,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Compose release filename
# https://stackoverflow.com/questions/58033366/how-to-get-current-branch-within-github-actions
run: echo "artifact_name=edgetx-firmware-${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Merge firmware artifact packages
+164
View File
@@ -0,0 +1,164 @@
name: Companion
on:
push:
branches:
- 'main'
tags:
- v*
paths:
- '.github/workflows/companion.yml'
- 'companion/**'
- 'tools/build-companion.sh'
pull_request:
paths:
- '.github/workflows/companion.yml'
- 'companion/**'
- 'tools/build-companion.sh'
workflow_dispatch:
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]
- id: bw-1
target: [zorro, pocket, mt12, 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, v16, x10, x10express]
- id: bw-3
target: [x7access, x9dp2019, x9e, x9e-hall]
- id: color-4
target: [nb4p, st16, pa01]
- id: color-5
target: [t15pro, tx16smk3]
steps:
- name: Check out the repo
uses: actions/checkout@v5
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
build-linux:
name: Linux Companion
runs-on: ubuntu-latest
needs: modules
container:
image: ghcr.io/edgetx/edgetx-dev:latest
volumes:
- ${{ github.workspace }}:/src
steps:
- name: Check out the repo
uses: actions/checkout@v5
with:
submodules: recursive
- name: Download WASM modules
uses: actions/download-artifact@v7
with:
pattern: wasm-modules-*
path: wasm-modules
merge-multiple: true
- name: Setup and Build
uses: ./.github/actions/build_companion
with:
os: 'linux'
build-macos:
name: macOS Companion
runs-on: macos-15
needs: modules
env:
CMAKE_OSX_ARCHITECTURES: 'x86_64;arm64'
CMAKE_OSX_DEPLOYMENT_TARGET: '11.0'
steps:
- name: Check out the repo
uses: actions/checkout@v5
with:
submodules: recursive
- name: Download WASM modules
uses: actions/download-artifact@v7
with:
pattern: wasm-modules-*
path: wasm-modules
merge-multiple: true
- name: Setup and Build
uses: ./.github/actions/build_companion
with:
os: 'macos'
qt-arch: 'clang_64'
build-win64:
name: Windows Companion
runs-on: windows-2022
needs: modules
env:
CMAKE_GENERATOR: 'Ninja'
CXX: 'clang++'
CC: 'clang'
defaults:
run:
shell: bash
steps:
- name: Check out the repo
uses: actions/checkout@v5
with:
submodules: recursive
- name: Download WASM modules
uses: actions/download-artifact@v7
with:
pattern: wasm-modules-*
path: wasm-modules
merge-multiple: true
- name: Setup and Build
uses: ./.github/actions/build_companion
with:
os: 'win64'
qt-arch: 'win64_msvc2022_64'
-86
View File
@@ -1,86 +0,0 @@
name: Linux Companion
on:
push:
branches:
- 'main'
- '[0-9]+.[0-9]+'
tags:
- v*
paths: &trigger-paths
- '.github/workflows/linux_cpn.yml'
- 'companion/**'
- 'tools/build-companion.sh'
pull_request:
branches:
- 'main'
- '[0-9]+.[0-9]+'
paths: *trigger-paths
workflow_dispatch:
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
jobs:
build:
runs-on: ubuntu-latest
if: |
github.event_name != 'pull_request' ||
!contains(github.event.pull_request.labels.*.name, 'ci: skip-cpn-linux')
container:
image: ghcr.io/edgetx/edgetx-dev:latest
volumes:
- ${{ github.workspace }}:/src
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
working-directory: ${{github.workspace}}
shell: bash
env:
CMAKE_BUILD_PARALLEL_LEVEL: 4
run: |
mkdir output && mkdir logs && \
tools/build-companion.sh "$(pwd)" "$(pwd)/output/" 2>&1 | tee logs/build-main.log
- name: Collect build logs
if: always()
shell: bash
run: |
# Create logs directory if it doesn't exist
mkdir -p logs
# Copy all build logs from the build directory
if [ -d "build" ]; then
find build -name "*.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
find build -name "CMakeOutput.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
find build -name "CMakeError.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
fi
- name: Compose release filename
if: always()
# https://stackoverflow.com/questions/58033366/how-to-get-current-branch-within-github-actions
run: echo "artifact_name=edgetx-cpn-linux-${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Archive production artifacts
if: success()
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}"
path: ${{github.workspace}}/output
retention-days: 15
- name: Archive build logs
if: always()
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}-logs"
path: ${{github.workspace}}/logs
retention-days: 30
-114
View File
@@ -1,114 +0,0 @@
name: MacOSX Companion
on:
push:
branches:
- 'main'
- '[0-9]+.[0-9]+'
tags:
- v*
paths: &trigger-paths
- '.github/workflows/macosx_cpn.yml'
- 'companion/**'
- 'tools/build-companion.sh'
pull_request:
branches:
- 'main'
- '[0-9]+.[0-9]+'
paths: *trigger-paths
workflow_dispatch:
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
BUILD_TYPE: Release
QT_VERSION: "6.9.3"
jobs:
build:
runs-on: macos-15-intel
if: |
github.event_name != 'pull_request' ||
!contains(github.event.pull_request.labels.*.name, 'ci: skip-cpn-macos')
steps:
- name: Select XCode version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.0'
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Setup Python 3.12
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Create Build Environment
# Some projects don't allow in-source building, so create a separate build directory
# We'll use this as our working directory for all subsequent commands
run: cmake -E make_directory ${{github.workspace}}/build
- name: Install Qt
uses: jdpurcell/install-qt-action@v5
env:
AQT_CONFIG: ${{ github.workspace }}/tools/aqt-settings.ini
with:
version: ${{ env.QT_VERSION }}
modules: 'qtmultimedia qtserialport'
setup-python: 'false'
cache: true
cache-key-prefix: 'install-qt-action-macOS'
- name: Install dependencies
run: python3 -m pip install --upgrade pip Pillow lz4 clang jinja2 pydantic
- name: Install SDL2
run: brew install SDL2
- name: Build
working-directory: ${{github.workspace}}
shell: bash
env:
CMAKE_BUILD_PARALLEL_LEVEL: 3
run: |
mkdir output && mkdir logs && \
tools/build-companion.sh "$(pwd)" "$(pwd)/output/" 2>&1 | tee logs/build-main.log
- name: Collect build logs
if: always()
shell: bash
run: |
# Create logs directory if it doesn't exist
mkdir -p logs
# Copy all build logs from the build directory
if [ -d "build" ]; then
find build -name "*.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
find build -name "CMakeOutput.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
find build -name "CMakeError.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
fi
- name: Compose release filename
if: always()
# https://stackoverflow.com/questions/58033366/how-to-get-current-branch-within-github-actions
run: echo "artifact_name=edgetx-cpn-osx-${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Archive production artifacts
if: success()
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}"
path: ${{github.workspace}}/output
retention-days: 15
- name: Archive build logs
if: always()
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}-logs"
path: ${{github.workspace}}/logs
retention-days: 30
-116
View File
@@ -1,116 +0,0 @@
name: Windows Companion 64-bit
on:
push:
branches:
- 'main'
- '[0-9]+.[0-9]+'
tags:
- v*
paths: &trigger-paths
- '.github/workflows/win_cpn-64.yml'
- 'companion/**'
- 'tools/build-companion.sh'
pull_request:
branches:
- 'main'
- '[0-9]+.[0-9]+'
paths: *trigger-paths
workflow_dispatch:
env:
# Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
CMAKE_BUILD_TYPE: "Release"
CMAKE_GENERATOR: "Ninja"
QT_VERSION: "6.9.0"
QT_ARCH: "win64_msvc2022_64"
SDL_VERSION: "2.32.8"
CXX: "clang++"
CC: "clang"
jobs:
build:
runs-on: windows-2022
if: |
github.event_name != 'pull_request' ||
!contains(github.event.pull_request.labels.*.name, 'ci: skip-cpn-win')
defaults:
run:
shell: bash
steps:
- name: Check out the repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Qt
uses: jdpurcell/install-qt-action@v5
env:
AQT_CONFIG: ${{ github.workspace }}/tools/aqt-settings.ini
with:
cache: true
cache-key-prefix: 'install-qt-action-${{ env.QT_ARCH }}'
version: ${{ env.QT_VERSION }}
arch: ${{ env.QT_ARCH }}
modules: 'qtmultimedia qtserialport'
- name: Setup SDL2
id: setup-sdl2
uses: libsdl-org/setup-sdl@v1
with:
version: ${{ env.SDL_VERSION }}
build-type: "Release"
- name: Install Python Dependencies
run: |
echo $PATH
$Python3_ROOT_DIR/python3.exe -m pip install clang jinja2 lz4 pillow pydantic
- name: Setup MSVC
uses: ilammy/msvc-dev-cmd@v1
- name: Build
env:
CMAKE_BUILD_PARALLEL_LEVEL: 4
run: |
mkdir output && mkdir logs && \
CMAKE_PREFIX_PATH="$QT_ROOT_DIR;$SDL2_ROOT" \
tools/build-companion.sh "$(pwd)" "$(pwd)/output/" 2>&1 | tee logs/build-main.log
- name: Collect build logs
if: always()
shell: bash
run: |
# Create logs directory if it doesn't exist
mkdir -p logs
# Copy all build logs from the build directory
if [ -d "build" ]; then
find build -name "*.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
find build -name "CMakeOutput.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
find build -name "CMakeError.log" -type f -exec cp {} logs/ \; 2>/dev/null || true
fi
- name: Compose release filename
if: always()
# https://stackoverflow.com/questions/58033366/how-to-get-current-branch-within-github-actions
run: echo "artifact_name=edgetx-cpn-win64-${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Archive production artifacts
if: success()
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}"
path: ${{github.workspace}}/output
retention-days: 15
- name: Archive build logs
if: always()
uses: actions/upload-artifact@v7
with:
name: "${{ env.artifact_name }}-logs"
path: ${{github.workspace}}/logs
retention-days: 30
+36 -4
View File
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.13)
cmake_minimum_required(VERSION 3.21)
project(EdgeTX)
set(VERSION_MAJOR "3")
@@ -8,6 +8,8 @@ set(VERSION_REVISION "0")
set(CODENAME "dev")
string(TIMESTAMP BUILD_YEAR "%Y")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
if(MINGW OR WIN32)
set(CMAKE_OBJECT_PATH_MAX 200)
endif()
@@ -86,6 +88,28 @@ if(EdgeTX_SUPERBUILD)
set(PARALLEL_FLAG --parallel)
endif()
# Webasm targets
if(ENABLE_WASM)
include(FetchWasiSDK)
find_package(WasiSDK REQUIRED)
ExternalProject_Add(wasm
SOURCE_DIR ${CMAKE_SOURCE_DIR}
BINARY_DIR ${CMAKE_BINARY_DIR}/wasm
CMAKE_ARGS
${CMAKE_ARGS}
-Wno-dev
-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
-DCMAKE_TOOLCHAIN_FILE=${WASI_SDK_PATH}/share/cmake/wasi-sdk-pthread.cmake
CMAKE_CACHE_ARGS
-DEdgeTX_SUPERBUILD:BOOL=0
-DNATIVE_BUILD:BOOL=1
-DDISABLE_COMPANION:BOOL=1
INSTALL_COMMAND ""
EXCLUDE_FROM_ALL TRUE
)
endif()
add_custom_target(configure
DEPENDS native-configure arm-none-eabi-configure)
@@ -103,6 +127,13 @@ if(EdgeTX_SUPERBUILD)
COMMAND ${CMAKE_COMMAND} --build native --target simu ${PARALLEL_FLAG}
)
if(ENABLE_WASM)
add_custom_target(wasi-module
COMMAND ${CMAKE_COMMAND} --build wasm --target wasi-module --parallel
DEPENDS wasm-configure
)
endif()
add_custom_target(companion
COMMAND ${CMAKE_COMMAND} --build native --target companion ${PARALLEL_FLAG}
DEPENDS native-configure
@@ -137,8 +168,6 @@ if(EdgeTX_SUPERBUILD)
return()
endif()
enable_language(ASM)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake)
include(GenericDefinitions)
@@ -148,11 +177,14 @@ else()
message(STATUS "EdgeTX ${VERSION_PREFIX}${VERSION}${VERSION_SUFFIX} @ ${GIT_STR}")
endif()
if(NATIVE_BUILD)
if(NATIVE_BUILD AND NOT WASI)
include(NativeTargets)
else()
# Prevent CMake warnings
set(IGNORE "${LIBUSB1_ROOT_DIR}" "${OPENSSL_ROOT_DIR}")
endif()
# Prevent CMake warnings
set(IGNORE "${ENABLE_WASM}")
add_subdirectory(${RADIO_SRC_DIR})
+25 -1
View File
@@ -9,6 +9,22 @@
"cacheVariables": {
"EdgeTX_SUPERBUILD": false,
"NATIVE_BUILD": false
},
"warnings": {
"dev": false
}
},
{
"name": "companion",
"displayName": "Companion",
"binaryDir": "build/companion",
"toolchainFile": "cmake/toolchain/native.cmake",
"cacheVariables": {
"EdgeTX_SUPERBUILD": false,
"NATIVE_BUILD": true
},
"warnings": {
"dev": false
}
},
{
@@ -34,6 +50,14 @@
"firmware"
]
},
{
"name": "companion",
"displayName": "Companion",
"configurePreset": "companion",
"targets": [
"companion"
]
},
{
"name": "simu",
"displayName": "SDL Simu",
@@ -51,4 +75,4 @@
]
}
]
}
}
+5
View File
@@ -0,0 +1,5 @@
# Copy all WASM simulator modules from SRC_DIR to DST_DIR at build time.
# Invoked via cmake -P so the pattern is evaluated on every build.
file(INSTALL "${SRC_DIR}/" DESTINATION "${DST_DIR}"
FILES_MATCHING PATTERN "edgetx-*-simulator.wasm"
MESSAGE_NEVER)
+3 -1
View File
@@ -14,7 +14,9 @@ else()
message(FATAL_ERROR "Unsupported platform: ${CMAKE_SYSTEM_NAME}")
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
if(APPLE)
set(RS_DFU_ARCH "universal")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
set(RS_DFU_ARCH "x86_64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(RS_DFU_ARCH "aarch64")
+63
View File
@@ -0,0 +1,63 @@
# Fetch and configure WAMR (WebAssembly Micro Runtime)
cmake_minimum_required(VERSION 3.14)
include(FetchContent)
# WAMR build configuration
set(WAMR_BUILD_INTERP 1)
set(WAMR_BUILD_FAST_INTERP 0)
set(WAMR_BUILD_AOT 0)
set(WAMR_BUILD_JIT 0)
set(WAMR_BUILD_LIBC_BUILTIN 1)
set(WAMR_BUILD_LIBC_WASI 1)
set(WAMR_BUILD_LIB_WASI_THREADS 1)
set(WAMR_BUILD_SHARED_MEMORY 1)
set(WAMR_BUILD_BULK_MEMORY 1)
set(WAMR_BUILD_REF_TYPES 1)
set(WAMR_BUILD_SIMD 0)
set(WAMR_BUILD_EXCE_HANDLING 1)
set(WAMR_DISABLE_HW_BOUND_CHECK 1)
FetchContent_Declare(
wamr
GIT_REPOSITORY https://github.com/bytecodealliance/wasm-micro-runtime
GIT_TAG WAMR-2.4.4
GIT_SHALLOW TRUE
)
# Populate separately so we can patch before building
FetchContent_GetProperties(wamr)
if(NOT wamr_POPULATED)
FetchContent_Populate(wamr)
# WAMR 2.4.4 win_file.c has trailing backslashes in comments that
# clang treats as line continuations, causing compile errors.
execute_process(
COMMAND git apply "${CMAKE_CURRENT_LIST_DIR}/wamr-fix-win-backslash-comments.patch"
WORKING_DIRECTORY "${wamr_SOURCE_DIR}"
RESULT_VARIABLE _patch_result
)
if(_patch_result)
message(WARNING "WAMR win_file.c patch failed (may already be applied)")
endif()
add_subdirectory("${wamr_SOURCE_DIR}" "${wamr_BINARY_DIR}" EXCLUDE_FROM_ALL)
if(WIN32)
# WAMR unconditionally adds -lm and -ldl as PUBLIC link libraries,
# which don't exist on Windows.
get_target_property(_vmlib_libs vmlib INTERFACE_LINK_LIBRARIES)
if(_vmlib_libs)
list(REMOVE_ITEM _vmlib_libs "-lm" "-ldl")
set_target_properties(vmlib PROPERTIES INTERFACE_LINK_LIBRARIES "${_vmlib_libs}")
endif()
# WAMR headers use __declspec(dllimport/dllexport) on MSVC-compatible
# compilers, but we link vmlib statically. Override both API macros to
# empty for vmlib itself (PUBLIC = PRIVATE + INTERFACE).
target_compile_definitions(vmlib PUBLIC
WASM_RUNTIME_API_EXTERN=
WASM_API_EXTERN=
)
endif()
endif()
+41
View File
@@ -0,0 +1,41 @@
cmake_minimum_required(VERSION 3.14)
set(WASI_SDK_VERSION "25")
set(WASI_SDK_FULL_VERSION "${WASI_SDK_VERSION}.0")
# Determine platform and architecture
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
set(WASI_SDK_PLATFORM "linux")
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
set(WASI_SDK_PLATFORM "macos")
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
set(WASI_SDK_PLATFORM "windows")
else()
message(FATAL_ERROR "Unsupported platform: ${CMAKE_HOST_SYSTEM_NAME}")
endif()
if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64")
set(WASI_SDK_ARCH "x86_64")
elseif(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
set(WASI_SDK_ARCH "arm64")
else()
message(FATAL_ERROR "Unsupported Linux architecture: ${CMAKE_HOST_SYSTEM_PROCESSOR}")
endif()
set(WASI_SDK_BASE_URL "https://github.com/WebAssembly/wasi-sdk/releases/download")
set(WASI_SDK_FILENAME "wasi-sdk-${WASI_SDK_VERSION}.0-${WASI_SDK_ARCH}-${WASI_SDK_PLATFORM}")
set(WASI_SDK_URL "${WASI_SDK_BASE_URL}/wasi-sdk-${WASI_SDK_VERSION}/${WASI_SDK_FILENAME}.tar.gz")
set(FETCHCONTENT_QUIET FALSE)
include(FetchContent)
FetchContent_Declare(
WasiSDK
URL ${WASI_SDK_URL}
DOWNLOAD_EXTRACT_TIMESTAMP true
FIND_PACKAGE_ARGS
)
FetchContent_MakeAvailable(WasiSDK)
set(WASI_SDK_PATH ${wasisdk_SOURCE_DIR})
+47
View File
@@ -0,0 +1,47 @@
# FindWasiSDK.cmake
# Module to find WASI SDK installation
#
# This module defines the following variables:
# WasiSDK_FOUND - True if WASI SDK is found
# WasiSDK_ROOT_DIR - Root directory of WASI SDK installation
#
# Usage:
# set(WASI_SDK_PATH "/path/to/wasi-sdk")
# find_package(WasiSDK REQUIRED)
cmake_minimum_required(VERSION 3.10)
# Use WASI_SDK_PATH if provided, otherwise try to find it
if(WASI_SDK_PATH)
set(_wasi_sdk_search_paths "${WASI_SDK_PATH}")
else()
# Default search paths for common WASI SDK installations
set(_wasi_sdk_search_paths
"$ENV{WASI_SDK_PATH}"
"/opt/wasi-sdk"
)
endif()
# Find the WASI SDK root directory by looking for the toolchain file
find_path(WasiSDK_ROOT_DIR
NAMES share/cmake/wasi-sdk.cmake
PATHS ${_wasi_sdk_search_paths}
DOC "WASI SDK root directory"
NO_DEFAULT_PATH
)
# Handle find_package arguments
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(WasiSDK
DEFAULT_MSG
WasiSDK_ROOT_DIR
)
# Mark variables as advanced
mark_as_advanced(
WasiSDK_ROOT_DIR
)
if(WasiSDK_FOUND)
set(WASI_SDK_PATH ${WasiSDK_ROOT_DIR})
endif()
+4 -4
View File
@@ -39,24 +39,24 @@ include(FetchGtest)
add_custom_target(tests-radio
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gtests-radio
DEPENDS gtests-radio
)
)
if(Qt6Core_FOUND AND NOT DISABLE_COMPANION)
add_subdirectory(${COMPANION_SRC_DIRECTORY})
add_custom_target(tests-companion
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/gtests-companion
DEPENDS gtests-companion
)
)
add_custom_target(gtests
DEPENDS gtests-radio gtests-companion
)
)
add_custom_target(tests
DEPENDS tests-radio tests-companion
)
else()
add_custom_target(gtests
DEPENDS gtests-radio
)
)
add_custom_target(tests
DEPENDS tests-radio
)
@@ -0,0 +1,29 @@
diff --git a/core/shared/platform/windows/win_file.c b/core/shared/platform/windows/win_file.c
index 55ea77a..df67705 100644
--- a/core/shared/platform/windows/win_file.c
+++ b/core/shared/platform/windows/win_file.c
@@ -1295,13 +1295,13 @@ os_readlinkat(os_file_handle handle, const char *path, char *buf,
if (wbufsize >= 4 && wbuf[0] == L'\\' && wbuf[1] == L'?'
&& wbuf[2] == L'?' && wbuf[3] == L'\\') {
- // Starts with \??\
+ // Has \??\ prefix
if (wbufsize >= 6
&& ((wbuf[4] >= L'A' && wbuf[4] <= L'Z')
|| (wbuf[4] >= L'a' && wbuf[4] <= L'z'))
&& wbuf[5] == L':' && (wbufsize == 6 || wbuf[6] == L'\\'))
{
- // \??\<drive>:\
+ // \??\<drive>:\ path
wbuf += 4;
wbufsize -= 4;
}
@@ -1310,7 +1310,7 @@ os_readlinkat(os_file_handle handle, const char *path, char *buf,
&& (wbuf[6] == L'C' || wbuf[6] == L'c')
&& wbuf[7] == L'\\')
{
- // \??\UNC\<server>\<share>\ - make sure the final path looks like \\<server>\<share>\
+ // \??\UNC\<server>\<share>\ - make sure the final path looks like \\<server>\<share>\ path
wbuf += 6;
wbuf[0] = L'\\';
wbufsize -= 6;
+38 -17
View File
@@ -1,4 +1,5 @@
set(SIMULATOR_INSTALL_PREFIX "" CACHE STRING "Alternative simulator library search path")
set(SIMULATOR_WASM_DIR "" CACHE PATH "Directory containing .wasm simulator modules to bundle")
set(COMPANION_NAME "companion")
set(SIMULATOR_NAME "simulator")
@@ -271,6 +272,19 @@ target_link_libraries(${COMPANION_NAME} PRIVATE
PrintTargetReport("${COMPANION_NAME}")
# Copy WASM simulator modules next to executables for local dev
if(SIMULATOR_WASM_DIR)
foreach(_target ${COMPANION_NAME} ${SIMULATOR_NAME})
add_custom_command(TARGET ${_target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DSRC_DIR=${SIMULATOR_WASM_DIR}
-DDST_DIR=$<TARGET_FILE_DIR:${_target}>
-P ${CMAKE_SOURCE_DIR}/cmake/CopyWasmModules.cmake
COMMENT "Copying WASM simulator modules to ${_target} output"
)
endforeach()
endif()
############# Standalone simulator ###############
set(simu_SRCS simulator.cpp )
@@ -399,8 +413,8 @@ elseif(WIN32)
endif(OPENSSL_FOUND)
if(TARGET SDL2::SDL2)
install(FILES $<TARGET_FILE:SDL2::SDL2>
DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2
RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR})
endif()
include(FindMSVCRedist)
@@ -414,23 +428,23 @@ elseif(APPLE)
# Qt + Cmake + Mac is poorly documented. A lot of this is guesswork
# and trial and error. Do not hesitate to fix it for the better
set(COMPANION_DISPLAY_NAME "EdgeTX Companion")
set_target_properties(${COMPANION_NAME} PROPERTIES
MACOSX_RPATH TRUE
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME "${COMPANION_DISPLAY_NAME}"
MACOSX_BUNDLE_INFO_PLIST ${COMPANION_TARGETS_DIR}/MacOSXBundleInfo.plist.in
INSTALL_RPATH "@executable_path/../Frameworks"
BUILD_WITH_INSTALL_RPATH TRUE
)
# This the name that the user will see in the generated DMG and what the application
# will be called under /Applications. We include the version string to make installing
# different versions side-by-side
set(COMPANION_OSX_APP_BUNDLE_NAME "${COMPANION_DISPLAY_NAME} ${VERSION_FAMILY}")
set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.edgetx.companion")
set(companion_app_dir "companion.app")
set_target_properties(${COMPANION_NAME} PROPERTIES
OUTPUT_NAME "${COMPANION_OSX_APP_BUNDLE_NAME}"
MACOSX_RPATH TRUE
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_BUNDLE_NAME "${COMPANION_DISPLAY_NAME}"
MACOSX_BUNDLE_INFO_PLIST ${COMPANION_TARGETS_DIR}/MacOSXBundleInfo.plist.in
INSTALL_RPATH "@executable_path/../Frameworks"
BUILD_WITH_INSTALL_RPATH TRUE
)
set(companion_app_dir "${COMPANION_OSX_APP_BUNDLE_NAME}.app")
set(companion_res_dir "${companion_app_dir}/Contents/Resources")
# Only Companion is distributed for macOS
@@ -457,7 +471,7 @@ elseif(APPLE)
)
install(FILES ${MAC_ICON_FILE}
DESTINATION ${companion_res_dir}
DESTINATION "${companion_res_dir}"
COMPONENT Runtime)
endif()
@@ -545,7 +559,7 @@ elseif(APPLE)
OUTPUT_SCRIPT qt_deploy_companion
CONTENT "
qt_deploy_runtime_dependencies(
EXECUTABLE ${companion_app_dir}
EXECUTABLE \"${companion_app_dir}\"
NO_TRANSLATIONS
NO_COMPILER_RUNTIME
GENERATE_QT_CONF
@@ -556,7 +570,15 @@ elseif(APPLE)
# Run macdeployqt
install(SCRIPT ${qt_deploy_companion} COMPONENT Runtime)
# Fix Code Signing and rename bundle
# Bundle SDL2
if(TARGET SDL2::SDL2)
install(IMPORTED_RUNTIME_ARTIFACTS SDL2::SDL2
FRAMEWORK DESTINATION "${companion_app_dir}/Contents/Frameworks"
LIBRARY DESTINATION "${companion_app_dir}/Contents/Frameworks"
COMPONENT Runtime)
endif()
# Re-sign bundle after all contents are installed
install(CODE "
set(app_dir \"\${CMAKE_INSTALL_PREFIX}/${companion_app_dir}\")
execute_process(COMMAND \${CMAKE_COMMAND} -E echo \"Re-signing: \${app_dir}\")
@@ -564,7 +586,6 @@ elseif(APPLE)
COMMAND codesign --remove-signature \${app_dir}
COMMAND codesign --force --deep -s - \${app_dir}
)
file(RENAME \"\${app_dir}\" \"\${CMAKE_INSTALL_PREFIX}/${COMPANION_OSX_APP_BUNDLE_NAME}.app\")
" COMPONENT Runtime)
else()
+8 -12
View File
@@ -31,6 +31,7 @@ static const StringTagMappingTable inputTypesLookupTable = {
{std::to_string(Board::AIT_FLEX), "FLEX"},
{std::to_string(Board::AIT_VBAT), "VBAT"},
{std::to_string(Board::AIT_RTC_BAT), "RTC_BAT"},
{std::to_string(Board::AIT_LUX), "LUX"},
{std::to_string(Board::AIT_SWITCH), "SWITCH"},
};
@@ -119,18 +120,13 @@ void BoardJson::afterLoadFixups(Board::Type board, InputsTable * inputs, Switche
}
}
if (IS_RADIOMASTER_TX16SMK3(board)) {
if (getInputIndex(inputs, "LIGHT", Board::LVT_TAG) < 0) {
InputDefn defn;
defn.type = AIT_FLEX;
defn.tag = "LIGHT";
defn.name = "Ambient light";
defn.shortName = "Light";
defn.flexType = FLEX_POT;
defn.inverted = false;
defn.cfgYaml = Board::LVT_TAG;
defn.refYaml = Board::LVT_TAG; // non-default
inputs->insert(inputs->end(), defn);
// Set default labels for LUX inputs if not provided by JSON
for (auto &defn : *inputs) {
if (defn.type == AIT_LUX) {
if (defn.name.empty())
defn.name = "Ambient light";
if (defn.shortName.empty())
defn.shortName = "Light";
}
}
+1
View File
@@ -307,6 +307,7 @@ namespace Board {
AIT_FLEX,
AIT_VBAT,
AIT_RTC_BAT,
AIT_LUX,
AIT_SWITCH,
};
+5 -3
View File
@@ -92,6 +92,7 @@ bool GeneralSettings::isInputAvailable(int index) const
const InputConfig &config = inputConfig[index];
return (config.type == Board::AIT_STICK ||
config.type == Board::AIT_LUX ||
(config.type == Board::AIT_FLEX && config.flexType != Board::FLEX_NONE));
}
@@ -124,10 +125,11 @@ bool GeneralSettings::isInputPot(int index) const
if (isInputAvailable(index)) {
const InputConfig &config = inputConfig[index];
return (config.type == Board::AIT_FLEX &&
return (config.type == Board::AIT_LUX ||
(config.type == Board::AIT_FLEX &&
(config.flexType == Board::FLEX_POT ||
config.flexType == Board::FLEX_POT_CENTER ||
config.flexType == Board::FLEX_MULTIPOS));
config.flexType == Board::FLEX_MULTIPOS)));
}
return false;
@@ -227,7 +229,7 @@ void GeneralSettings::init()
backlightOffBright = IS_FAMILY_HORUS_OR_T16(board) ? 20 : 0;
backgroundVolume = 1;
speakerVolume = 12;
speakerVolume = 0;
wavVolume = 2;
hatsMode = HATSMODE_SWITCHABLE;
+4
View File
@@ -22,6 +22,7 @@ set(${PROJECT_NAME}_NAMES
serialportsdialog
hostserialconnector
simulateduiwidgetGeneric
wasmsimulatorinterface
)
if(TARGET SDL2::SDL2)
@@ -54,10 +55,13 @@ add_library(${PROJECT_NAME}
${${PROJECT_NAME}_SRCS}
)
include(FetchWamr)
target_link_libraries(${PROJECT_NAME}
PRIVATE
${CPN_COMMON_LIB}
Qt::Svg
vmlib
)
if(TARGET SDL2::SDL2)
-12
View File
@@ -118,21 +118,9 @@ class RadioUiAction : public QObject
// Note: Qt::KeypadModifier is required for arrow keys as they are considered part of the keypad
if (((keyEvent->modifiers() == Qt::NoModifier) || (keyEvent->modifiers() & Qt::ShiftModifier) || (keyEvent->modifiers() & Qt::KeypadModifier)) &&
m_keys.contains(keyEvent->key())) {
// qDebug() << "Event:" << event->type() << "Key:" << QString("0x%1").arg(keyEvent->key(), 8, 16, QLatin1Char( '0' )) << "Text:" << keyEvent->text() << "found";
trigger(event->type() == QEvent::KeyPress);
return true;
}
else {
// qDebug() << "Key not found";
// qDebug() << "Event:" << event->type() <<
// "Key:" << QString("0x%1").arg(keyEvent->key(), 8, 16, QLatin1Char( '0' )) <<
// "Text:" << keyEvent->text() <<
// "Shift:" << (bool)(keyEvent->modifiers() & Qt::ShiftModifier) <<
// "Keypad:" << (bool)(keyEvent->modifiers() & Qt::KeypadModifier) <<
// "Ctrl:" << (bool)(keyEvent->modifiers() & Qt::ControlModifier) <<
// "Alt:" << (bool)(keyEvent->modifiers() & Qt::AltModifier) <<
// "Meta:" << (bool)(keyEvent->modifiers() & Qt::MetaModifier);
}
}
return QObject::eventFilter(obj, event);
+10 -3
View File
@@ -174,9 +174,16 @@ void SimulatedUIWidget::wheelEvent(QWheelEvent * event)
{
if (event->angleDelta().isNull())
return;
// steps can be negative or positive to determine direction (negative is UP/LEFT scroll)
QPoint numSteps = event->angleDelta() / 8 / 15 * -1; // one step per 15deg
emit simulatorWheelEvent(numSteps.y());
// Accumulate sub-step deltas (macOS trackpad sends pixel-level values)
static constexpr int STEP_THRESHOLD = 120;
m_wheelAccumulator += event->angleDelta().y() * -1;
int steps = m_wheelAccumulator / STEP_THRESHOLD;
if (steps != 0) {
m_wheelAccumulator -= steps * STEP_THRESHOLD;
emit simulatorWheelEvent(steps);
}
event->accept();
}
@@ -149,6 +149,7 @@ class SimulatedUIWidget : public QWidget
unsigned int m_backLight;
int m_beepShow;
int m_beepVal;
int m_wheelAccumulator = 0;
static int strKeyToInt(std::string key);
+1 -1
View File
@@ -155,7 +155,7 @@ struct SimulatorOptions
if (o.m_version >= 3)
in >> o.simulatorId;
if (o.m_version >= 4)
in >> o.simulatorId;
in >> o.flags;
}
else {
qWarning() << "Error loading SimulatorOptions, saved version not valid:" << o.m_version << "Expected <=" << SIMULATOR_OPTIONS_VERSION;
+50 -89
View File
@@ -20,17 +20,15 @@
*/
#include "simulatorinterface.h"
#include "wasmsimulatorinterface.h"
#include "customdebug.h"
#include "version.h"
#include "firmwares/eeprominterface.h"
#include <QDebug>
#include <QLibraryInfo>
#if defined _MSC_VER || !defined __GNUC__
#include <windows.h>
#endif
QMap<QString, QPair<QString, QLibrary *>> SimulatorLoader::registeredSimulators;
QMap<QString, SimulatorFactory *> SimulatorLoader::registeredSimulators;
QStringList SimulatorLoader::getAvailableSimulators()
{
@@ -39,29 +37,40 @@ QStringList SimulatorLoader::getAvailableSimulators()
int SimulatorLoader::registerSimulators(const QDir & dir)
{
QStringList filters;
#if defined(__APPLE__)
filters << "libedgetx-*-simulator.dylib";
#elif defined(WIN32) || defined(__CYGWIN__)
filters << "libedgetx-*-simulator.dll";
#else
filters << "libedgetx-*-simulator.so";
#endif
registeredSimulators.clear();
QStringList wasmFilters;
wasmFilters << "edgetx-*-simulator.wasm";
qCDebug(simulatorInterfaceLoader) << "Searching for simulators in" << dir.path() << "matching pattern" << filters;
qCDebug(simulatorInterfaceLoader) << "Searching for WASM simulators in"
<< dir.path();
foreach(QString filename, dir.entryList(filters, QDir::Files)) {
QString simuName(filename.mid(3, filename.lastIndexOf('-') - 3));
QString libPath(dir.path() + "/" + filename);
foreach (QString filename, dir.entryList(wasmFilters, QDir::Files)) {
// Extract board name: "edgetx-<name>-simulator.wasm"
QString simuName = filename;
simuName.remove(0, 7); // remove "edgetx-"
simuName.truncate(simuName.lastIndexOf("-simulator"));
if (getAvailableSimulators().contains(simuName))
if (registeredSimulators.contains(simuName))
continue;
registeredSimulators.insert(simuName, {libPath, nullptr});
QString wasmPath = dir.path() + "/" + filename;
// Resolve board type from name
Board::Type boardType = Board::BOARD_UNKNOWN;
Firmware * fw = Firmware::getFirmwareForId(QString("edgetx-") + simuName);
if (fw)
boardType = fw->getBoard();
auto * factory = new WasmSimulatorFactory(wasmPath, simuName, boardType);
registeredSimulators.insert(simuName, factory);
qCDebug(simulatorInterfaceLoader) << "Registered WASM simulator:"
<< simuName;
}
qCDebug(simulatorInterfaceLoader) << "Found libraries:" << (registeredSimulators.size() ? registeredSimulators.keys() : QStringList() << "none");
qCDebug(simulatorInterfaceLoader)
<< "Found WASM modules:"
<< (registeredSimulators.size() ? registeredSimulators.keys()
: QStringList() << "none");
return registeredSimulators.size();
}
@@ -78,7 +87,8 @@ void SimulatorLoader::registerSimulators()
if (QDir::isAbsolutePath(SIMULATOR_LIB_SEARCH_PATH)) {
dir.setPath(SIMULATOR_LIB_SEARCH_PATH);
} else {
dir.setPath(QCoreApplication::applicationDirPath() + "/" SIMULATOR_LIB_SEARCH_PATH);
dir.setPath(QCoreApplication::applicationDirPath() + "/"
SIMULATOR_LIB_SEARCH_PATH);
}
#endif
registerSimulators(dir.absolutePath());
@@ -86,98 +96,49 @@ void SimulatorLoader::registerSimulators()
void SimulatorLoader::unregisterSimulators()
{
for(QPair<QString, QLibrary *> lib : registeredSimulators) {
if (lib.second)
delete lib.second;
}
qDeleteAll(registeredSimulators);
registeredSimulators.clear();
}
QString SimulatorLoader::findSimulatorByName(const QString & name)
{
int pos;
QString ret;
QString simuName = name;
while(1) {
qCDebug(simulatorInterfaceLoader) << "searching" << simuName << "simulator";
if (registeredSimulators.contains(simuName)) {
ret = simuName;
break;
}
// Strip "edgetx-" prefix if present (registered names don't have it)
if (simuName.startsWith("edgetx-"))
simuName.remove(0, 7);
while (1) {
qCDebug(simulatorInterfaceLoader) << "searching" << simuName
<< "simulator";
if (registeredSimulators.contains(simuName))
return simuName;
if ((pos = simuName.lastIndexOf('-')) <= 0)
break;
simuName = simuName.mid(0, pos);
if (simuName.count('-') == 0)
break;
}
return ret;
return QString();
}
SimulatorInterface * SimulatorLoader::loadSimulator(const QString & name)
{
SimulatorInterface * si = nullptr;
QString simuName = findSimulatorByName(name);
if (simuName.isEmpty()) {
qWarning() << "Simulator" << name << "not found.";
return si;
return nullptr;
}
QPair<QString, QLibrary *> libInfo = registeredSimulators.value(simuName, {QString(), nullptr});
QString libPath = libInfo.first;
QLibrary *lib = libInfo.second;
qCDebug(simulatorInterfaceLoader) << "Trying to load simulator in " << libPath;
if (!lib) {
lib = new QLibrary(libPath);
if (lib)
registeredSimulators.insert(simuName, {libPath, lib});
else {
qWarning() << "Unable to load library";
return si;
}
}
SimulatorFactory * factory;
RegisterSimulator registerFunc = (RegisterSimulator)lib->resolve("registerSimu");
if (registerFunc && (factory = registerFunc()) && (si = factory->create())) {
quint8 instance = lib->property("instances_used").toUInt();
lib->setProperty("instances_used", ++instance);
qCDebug(simulatorInterfaceLoader) << "Loaded" << factory->name() << "simulator instance" << instance;
delete factory;
}
else {
qWarning() << "Library error" << libPath << lib->errorString();
}
SimulatorInterface * si = registeredSimulators[simuName]->create();
if (si)
qCDebug(simulatorInterfaceLoader) << "Loaded simulator:" << simuName;
return si;
}
bool SimulatorLoader::unloadSimulator(const QString & name)
{
bool ret = false;
QString simuName = findSimulatorByName(name);
if (simuName.isEmpty())
return ret;
QLibrary * lib = registeredSimulators.value(simuName).second;
if (lib && lib->isLoaded()) {
quint8 instance = lib->property("instances_used").toUInt();
lib->setProperty("instances_used", --instance);
if (!instance) {
ret = lib->unload();
qCDebug(simulatorInterfaceLoader) << "Unloading" << simuName << "(" << lib->fileName() << ")" << "result:" << ret;
}
else {
ret = true;
qCDebug(simulatorInterfaceLoader) << "Simulator" << simuName << "instances remaining:" << instance;
}
}
else {
qCDebug(simulatorInterfaceLoader) << "Simulator library for " << simuName << "already unloaded.";
}
return ret;
Q_UNUSED(name);
return true;
}
@@ -31,7 +31,6 @@
#include <QString>
#include <QByteArray>
#include <QDir>
#include <QLibrary>
#include <QMap>
#include <QSerialPort>
@@ -177,7 +176,6 @@ class SimulatorInterface : public QObject
void started();
void stopped();
void heartbeat(qint32 loops, qint64 timestamp);
void runtimeError(const QString & error);
void lcdChange(bool backlightEnable);
void phaseChanged(qint8 phase, const QString & name);
void channelOutValueChange(quint8 index, qint32 value, qint32 limit);
@@ -216,8 +214,6 @@ class SimulatorLoader
static bool unloadSimulator(const QString & name);
protected:
typedef SimulatorFactory * (*RegisterSimulator)();
static int registerSimulators(const QDir & dir);
static QMap<QString, QPair<QString, QLibrary *>> registeredSimulators;
static QMap<QString, SimulatorFactory *> registeredSimulators;
};
@@ -123,11 +123,16 @@ SimulatorMainWindow::SimulatorMainWindow(QWidget *parent, const QString & simula
ui->menuView->insertSeparator(ui->actionToggleMenuBar);
ui->menuView->insertAction(ui->actionToggleMenuBar, ui->toolBar->toggleViewAction());
// Hide some actions based on simulator capabilities.
if(!m_simulator->getCapability(SimulatorInterface::CAP_LUA))
ui->actionReloadLua->setDisabled(true);
if(!m_simulator->getCapability(SimulatorInterface::CAP_TELEM_FRSKY_SPORT))
m_telemetryDockWidget->toggleViewAction()->setDisabled(true);
// Disable capability-dependent actions until the simulator starts and
// the WASM module is loaded (getCapability returns 0 before that).
ui->actionReloadLua->setDisabled(true);
m_telemetryDockWidget->toggleViewAction()->setDisabled(true);
connect(m_simulator, &SimulatorInterface::started, this, [this]() {
if (m_simulator->getCapability(SimulatorInterface::CAP_LUA))
ui->actionReloadLua->setEnabled(true);
if (m_simulator->getCapability(SimulatorInterface::CAP_TELEM_FRSKY_SPORT))
m_telemetryDockWidget->toggleViewAction()->setEnabled(true);
});
#ifndef USE_SDL
ui->actionJoystickSettings->setDisabled(true);
#endif
@@ -210,7 +210,7 @@ void SimulatorStartupDialog::loadRadioProfile(int id)
*m_options = g.profile[id].simulatorOptions();
if (m_options->firmwareId.isEmpty() && !g.profile[id].fwType().isEmpty())
if (!g.profile[id].fwType().isEmpty())
m_options->firmwareId = g.profile[id].fwType();
m_options->firmwareId = findRadioId(m_options->firmwareId);
@@ -222,11 +222,11 @@ void SimulatorStartupDialog::loadRadioProfile(int id)
setGlobalFirmware(m_options->firmwareId);
// always refresh as linked simulatorId as could change over time
m_options->simulatorId = getCurrentFirmware()->getSimulatorId();
m_options->simulatorId = SimulatorLoader::findSimulatorByName(getCurrentFirmware()->getSimulatorId());
m_simProxy->setFilterFixedString(m_options->simulatorId);
i = ui->cbSimulator->findText(findRadioId(m_options->simulatorId), Qt::MatchContains);
i = ui->cbSimulator->findText(m_options->simulatorId, Qt::MatchContains);
if (i > -1)
ui->cbSimulator->setCurrentIndex(i);
@@ -293,11 +293,11 @@ void SimulatorStartupDialog::onRadioTypeChanged(int index)
setGlobalFirmware(id);
// always refresh as linked simulatorId as could change over time
id = getCurrentFirmware()->getSimulatorId();
id = SimulatorLoader::findSimulatorByName(getCurrentFirmware()->getSimulatorId());
m_simProxy->setFilterFixedString(id);
const int i = ui->cbSimulator->findText(findRadioId(id), Qt::MatchContains);
const int i = ui->cbSimulator->findText(id, Qt::MatchContains);
if (i > -1)
ui->cbSimulator->setCurrentIndex(i);
}
@@ -305,8 +305,8 @@ void SimulatorStartupDialog::onRadioTypeChanged(int index)
void SimulatorStartupDialog::onDataFileSelect(bool)
{
QString filter = SIMU_FILES_FILTER;
QString file = QFileDialog::getSaveFileName(this, tr("Select a data file"), ui->dataFile->text(),
filter, NULL, QFileDialog::DontConfirmOverwrite);
QString file = QFileDialog::getOpenFileName(this, tr("Select a data file"), ui->dataFile->text(),
filter);
if (!file.isEmpty()) {
ui->dataFile->setText(file);
ui->optFile->setChecked(true);
@@ -104,7 +104,6 @@ SimulatorWidget::SimulatorWidget(QWidget * parent, SimulatorInterface * simulato
connect(simulator, &SimulatorInterface::started, this, &SimulatorWidget::onSimulatorStarted);
connect(simulator, &SimulatorInterface::heartbeat, this, &SimulatorWidget::onSimulatorHeartbeat);
connect(simulator, &SimulatorInterface::runtimeError, this, &SimulatorWidget::onSimulatorError);
connect(simulator, &SimulatorInterface::phaseChanged, this, &SimulatorWidget::onPhaseChanged);
connect((SimulatorMainWindow *)parent, &SimulatorMainWindow::txBatteryVoltageChanged, this, &SimulatorWidget::onTxBatteryVoltageChanged);
@@ -862,8 +861,6 @@ void SimulatorWidget::onjoystickAxisValueChanged(int axis, int value)
GeneralSettings radioSettings = GeneralSettings();
if (radioSettings.isInputAvailable(stick)) {
if (radioSettings.isInputPot(stick)) {
if (radioSettings.inputConfig[stick].flexType == Board::FlexType::FLEX_MULTIPOS)
stickval += 1024;
emit widgetValueChange(RadioWidget::RADIO_WIDGET_KNOB, stick, stickval);
} else if (radioSettings.isInputSlider(stick)) {
emit widgetValueChange(RadioWidget::RADIO_WIDGET_FADER, stick, stickval);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,217 @@
/*
* Copyright (C) EdgeTX
*
* 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 "simulatorinterface.h"
#include <QMutex>
#include <QTimer>
#include <QVector>
#include <atomic>
#include "wasm_export.h"
#include <SDL.h>
class WasmSimulatorInterface : public SimulatorInterface
{
Q_OBJECT
public:
WasmSimulatorInterface(const QString & wasmPath, const QString & boardName,
Board::Type boardType);
virtual ~WasmSimulatorInterface();
QString name() override;
bool isRunning() override;
void readRadioData(QByteArray & dest) override {}
uint8_t * getLcd() override;
uint8_t getSensorInstance(uint16_t id, uint8_t defaultValue = 0) override;
uint16_t getSensorRatio(uint16_t id) override;
const int getCapability(Capability cap) override;
public slots:
void init() override;
void start(const char * filename = nullptr, bool tests = true) override;
void stop() override;
void setSdPath(const QString & sdPath = "",
const QString & settingsPath = "") override;
void setVolumeGain(const int value) override;
void setRadioData(const QByteArray & data) override {}
void setAnalogValue(uint8_t index, int16_t value) override;
void setKey(uint8_t key, bool state) override;
void setSwitch(uint8_t swtch, int8_t state) override;
void setTrim(unsigned int idx, int value) override;
void setTrimSwitch(uint8_t trim, bool state) override;
void setTrainerInput(unsigned int inputNumber, int16_t value) override;
void setInputValue(int type, uint8_t index, int16_t value) override;
void rotaryEncoderEvent(int steps) override;
void touchEvent(int type, int x, int y) override;
void lcdFlushed() override;
void setTrainerTimeout(uint16_t ms) override;
void sendInternalModuleTelemetry(const quint8 protocol,
const QByteArray data) override;
void sendExternalModuleTelemetry(const quint8 protocol,
const QByteArray data) override;
void setLuaStateReloadPermanentScripts() override;
void addTracebackDevice(QIODevice * device) override;
void removeTracebackDevice(QIODevice * device) override;
void receiveAuxSerialData(const quint8 port_num,
const QByteArray & data) override;
// Called by WASM import simuGetAnalog
int16_t getAnalogValue(uint8_t index);
// Called by WASM import simuTrace
void writeTrace(const char * text);
// Called by WASM import simuQueueAudio
void queueAudio(const uint8_t * buf, uint32_t len);
// Called by WASM import simuLcdNotify (from WAMR thread)
void notifyLcdReady();
protected slots:
void run();
void onLcdNotify();
protected:
bool loadModule();
void unloadModule();
bool resolveExports();
void refreshLcd();
void checkOutputsChanged();
void initAudio();
void deinitAudio();
QString m_wasmPath;
QString m_boardName;
Board::Type m_boardType;
QString m_sdPath;
QString m_settingsPath;
QTimer * m_timer10ms = nullptr;
std::atomic<bool> m_lcdNotified{false};
QMutex m_mutex;
QMutex m_mtxTbDevices;
QVector<QIODevice *> m_tracebackDevices;
bool m_stopRequested = false;
// Audio
SDL_AudioDeviceID m_audioDevice = 0;
int m_volumeGain = SDL_MIX_MAXVOLUME;
// Host-side analog values (polled by WASM via simuGetAnalog)
static constexpr int MAX_ANALOGS = 32;
int16_t m_analogValues[MAX_ANALOGS] = {};
// Cached output values for change detection
TxOutputs m_lastOutputs;
bool m_resetOutputsData = true;
// Persistent WASM buffer for bulk copies (allocated once in init)
uint32_t m_wasmScratchBuf = 0;
uint32_t m_wasmScratchSize = 0;
// Host-side LCD buffer + persistent WASM-side copy buffer
uint8_t * m_lcdBuffer = nullptr;
uint32_t m_lcdBufferSize = 0;
uint32_t m_wasmLcdBuf = 0;
uint32_t m_lcdWidth = 0;
uint32_t m_lcdHeight = 0;
uint32_t m_lcdDepth = 0;
// WAMR handles
wasm_module_t m_module = nullptr;
wasm_module_inst_t m_moduleInst = nullptr;
wasm_exec_env_t m_execEnv = nullptr;
QByteArray m_wasmBinary;
// Cached WASM function references
wasm_function_inst_t m_fnInit = nullptr;
wasm_function_inst_t m_fnStart = nullptr;
wasm_function_inst_t m_fnStop = nullptr;
wasm_function_inst_t m_fnIsRunning = nullptr;
wasm_function_inst_t m_fnSetKey = nullptr;
wasm_function_inst_t m_fnSetTrim = nullptr;
wasm_function_inst_t m_fnSetSwitch = nullptr;
wasm_function_inst_t m_fnLcdChanged = nullptr;
wasm_function_inst_t m_fnLcdCopy = nullptr;
wasm_function_inst_t m_fnLcdGetWidth = nullptr;
wasm_function_inst_t m_fnLcdGetHeight = nullptr;
wasm_function_inst_t m_fnLcdGetDepth = nullptr;
wasm_function_inst_t m_fnTouchDown = nullptr;
wasm_function_inst_t m_fnTouchUp = nullptr;
wasm_function_inst_t m_fnFatfsSetPaths = nullptr;
wasm_function_inst_t m_fnRotaryEncoderEvent = nullptr;
wasm_function_inst_t m_fnGetCapability = nullptr;
// Output value exports (bulk copy)
wasm_function_inst_t m_fnGetNumChannels = nullptr;
wasm_function_inst_t m_fnCopyChannelOutputs = nullptr;
wasm_function_inst_t m_fnCopyMixOutputs = nullptr;
wasm_function_inst_t m_fnGetNumLogicalSwitches = nullptr;
wasm_function_inst_t m_fnCopyLogicalSwitches = nullptr;
wasm_function_inst_t m_fnGetTrimValue = nullptr;
wasm_function_inst_t m_fnGetTrimRange = nullptr;
wasm_function_inst_t m_fnGetFlightMode = nullptr;
wasm_function_inst_t m_fnGetNumGVars = nullptr;
wasm_function_inst_t m_fnGetNumFlightModes = nullptr;
wasm_function_inst_t m_fnGetGVar = nullptr;
// Phase 4: telemetry, trim, lua, lcd, trainer
wasm_function_inst_t m_fnSetTrimValue = nullptr;
wasm_function_inst_t m_fnSendTelemetry = nullptr;
wasm_function_inst_t m_fnLuaReloadPermanentScripts = nullptr;
wasm_function_inst_t m_fnLcdFlushed = nullptr;
wasm_function_inst_t m_fnGetMaxTrainerChannels = nullptr;
wasm_function_inst_t m_fnCopyTrainerInput = nullptr;
wasm_function_inst_t m_fnSetTrainerTimeout = nullptr;
// Backlight and function switch LEDs
wasm_function_inst_t m_fnGetBacklightState = nullptr;
wasm_function_inst_t m_fnGetNumCustomSwitches = nullptr;
wasm_function_inst_t m_fnGetCustomSwitchColor = nullptr;
wasm_function_inst_t m_fnGetCustomSwitchIndex = nullptr;
// Trainer input buffer (flushed in bulk via run())
static constexpr int MAX_TRAINER_CH = 16;
int16_t m_trainerValues[MAX_TRAINER_CH] = {};
bool m_trainerDirty = false;
// Cached function switch LED colors for change detection
static constexpr int MAX_FS_LEDS = 8;
uint32_t m_lastFSLedColors[MAX_FS_LEDS] = {};
wasm_function_inst_t m_fnMalloc = nullptr;
wasm_function_inst_t m_fnFree = nullptr;
};
class WasmSimulatorFactory : public SimulatorFactory
{
public:
WasmSimulatorFactory(const QString & wasmPath, const QString & boardName,
Board::Type boardType);
QString name() override;
Board::Type type() override;
SimulatorInterface * create() override;
private:
QString m_wasmPath;
QString m_boardName;
Board::Type m_boardType;
};
@@ -71,18 +71,16 @@ class RadioKnobWidget : public RadioWidget
m_dial->setFixedSize(QSize(42, 42));
m_dial->setNotchesVisible(true);
m_dial->setMinimum(-1024);
m_dial->setMaximum(1024);
if (m_stepSize > 1) {
m_dial->setMinimum(0);
m_dial->setMaximum(2048);
// this is a bit of a hack to get the notch markers to display correctly
// the actual notches/value are constrained in setValue()
// notch markers for multipos — actual values are constrained in setValueFromDial()
m_dial->setSingleStep(m_stepSize / 10);
m_dial->setPageStep(m_stepSize);
m_dial->setNotchTarget(5.7);
}
else {
m_dial->setMinimum(-1024);
m_dial->setMaximum(1024);
m_dial->setPageStep(128);
m_dial->setNotchTarget(64);
}
@@ -99,14 +97,15 @@ class RadioKnobWidget : public RadioWidget
{
int v = value;
if (m_stepSize > 1) {
v = ((v + m_stepSize / 2) / m_stepSize) * m_stepSize;
// Fix values to account for lack of precision from using integer step size
// This makes the values more symmetrical around the center point
// Note: this is specific to the 6 position switch which is currently the only use case here
if (v > 1024) v += 3;
// Round to nearest step position relative to minimum
int offset = v - m_dial->minimum();
offset = ((offset + m_stepSize / 2) / m_stepSize) * m_stepSize;
v = m_dial->minimum() + offset;
// Fix values to account for lack of precision from integer step size
// (2048 / 5 = 409 leaves a remainder of 3). Add correction to the
// upper half so positions are symmetric around center.
if (v > 0) v += 3;
if (v != value) {
// If the desired value is different then update the slider position
// Note: this will trigger another value changed event call back into this function, at which time setValue will be called below
m_dial->setValue(v);
return;
}
+4 -4
View File
@@ -312,14 +312,14 @@ int main(int argc, char *argv[])
if (cliResult == CommandLineExitErr)
return finish(1);
// TODO : defaults should be set in Profile::init()
if (simOptions.firmwareId.isEmpty()) {
// Always use profile's firmware type and SD path as authoritative source —
// the copies in simulatorOptions can become stale.
if (!g.profile[profileId].fwType().isEmpty())
simOptions.firmwareId = g.profile[profileId].fwType();
}
if (simOptions.dataFolder.isEmpty())
simOptions.dataFolder = g.eepromDir();
if (simOptions.sdPath.isEmpty())
if (!g.profile[profileId].sdPath().isEmpty())
simOptions.sdPath = g.profile[profileId].sdPath();
// DO NOT use saved simulatorId as could be changed in later releases
+6 -2
View File
@@ -34,7 +34,6 @@ option(SIMU_LUA_COMPILER "Pre-compile and save Lua scripts in simulator." ON)
option(FAS_PROTOTYPE "Support of old FAS prototypes (different resistors)" OFF)
option(RAS "RAS (SWR) enabled" ON)
option(TEMPLATES "Model templates menu" OFF)
option(TRACE_SIMPGMSPACE "Turn on traces in simpgmspace.cpp" ON)
option(TRACE_LUA_INTERNALS "Turn on traces for Lua internals" OFF)
option(TRACE_AUDIO "Traces audio enabled" OFF)
option(DEBUG_SEGGER_RTT "Debug output to Segger RTT" OFF)
@@ -575,7 +574,10 @@ if(NATIVE_BUILD)
set_property(TARGET radiolib_native PROPERTY POSITION_INDEPENDENT_CODE ON)
add_subdirectory(targets/simu)
add_subdirectory(tests)
if(NOT WASI)
add_subdirectory(tests)
endif()
endif()
set(SRC ${SRC} ${FIRMWARE_SRC})
@@ -595,6 +597,8 @@ if(NOT USE_UNSUPPORTED_TOOLCHAIN AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_EQUA
message(FATAL_ERROR "Only Arm GNU Toolchain version 14.2.rel1 is supported!!")
endif()
enable_language(ASM)
set(CMAKE_C_FLAGS "${FIRMWARE_C_FLAGS}")
set(CMAKE_C_FLAGS_DEBUG "${FIRMWARE_C_FLAGS_DEBUG}")
set(CMAKE_C_FLAGS_RELEASE "${FIRMWARE_C_FLAGS}")
-7
View File
@@ -730,13 +730,6 @@ void AudioQueue::playTone(uint16_t freq, uint16_t len, uint16_t pause, uint8_t f
void AudioQueue::playFile(const char * filename, uint8_t flags, uint8_t id, int8_t fragmentVolume)
{
#if defined(SIMU)
TRACE("playFile(\"%s\", flags=%x, id=%d fragmentVolume=%d ee_general=%d)", filename, flags, id, fragmentVolume, g_eeGeneral.wavVolume);
if (strlen(filename) > AUDIO_FILENAME_MAXLEN) {
TRACE("file name too long! maximum length is %d characters", AUDIO_FILENAME_MAXLEN);
}
#endif
if (!sdMounted())
return;
+1 -1
View File
@@ -1536,7 +1536,7 @@ void edgeTxInit()
#if defined(AUDIO)
currentSpeakerVolume = requiredSpeakerVolume =
g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF;
limit<int>(0, g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF, VOLUME_LEVEL_MAX);
#if !defined(SOFTWARE_VOLUME)
audioSetVolume(currentSpeakerVolume);
#endif
-5
View File
@@ -27,11 +27,6 @@
#include "edgetx_helpers.h"
#include "touch.h"
#include "switches.h"
#if defined(SIMU)
#include "targets/simu/simpgmspace.h"
#endif
#include "board.h"
#if !defined(SIMU)
+5 -5
View File
@@ -23,8 +23,7 @@
#include "switches.h"
#include "hal/audio_driver.h"
#include "boards/generic_stm32/rgb_leds.h"
#include "os/time.h"
#if defined(COLORLCD)
void setRequestedMainView(uint8_t view);
@@ -380,9 +379,10 @@ void evalFunctions(CustomFunctionData * functions, CustomFunctionsContext & func
case FUNC_PUSH_CUST_SWITCH:
if (CFN_PARAM(cfn)) { // Duration is set
if (! CFN_VAL2(cfn) ) { // Duration not started yet
CFN_VAL2(cfn) = timersGetMsTick() + CFN_PARAM(cfn) * 100;
CFN_VAL2(cfn) = time_get_ms() + CFN_PARAM(cfn) * 100;
g_model.cfsSetSFState(CFN_CS_INDEX(cfn), 1);
} else if (timersGetMsTick() < (uint32_t)CFN_VAL2(cfn) ) { // Still within push duration
}
else if (time_get_ms() < (uint32_t)CFN_VAL2(cfn) ) { // Still within push duration
g_model.cfsSetSFState(CFN_CS_INDEX(cfn), 1);
}
} else { // No duration set
@@ -457,7 +457,7 @@ void evalFunctions(CustomFunctionData * functions, CustomFunctionsContext & func
#if defined(FUNCTION_SWITCHES)
if (CFN_FUNC(cfn) == FUNC_PUSH_CUST_SWITCH) {
// Handling duration after function is active
if (timersGetMsTick() < (uint32_t)CFN_VAL2(cfn)) {
if (time_get_ms() < (uint32_t)CFN_VAL2(cfn)) {
g_model.cfsSetSFState(CFN_CS_INDEX(cfn), 1);
}
else {
-2
View File
@@ -256,11 +256,9 @@ bool gpsNewFrameNMEA(char c)
gpsData.numSat = gps_Msg.numSat;
gpsData.hdop = gps_Msg.hdop;
if (gps_Msg.fix) {
__disable_irq(); // do the atomic update of lat/lon
gpsData.latitude = gps_Msg.latitude;
gpsData.longitude = gps_Msg.longitude;
gpsData.altitude = gps_Msg.altitude;
__enable_irq();
}
break;
case FRAME_RMC:
-2
View File
@@ -158,11 +158,9 @@ static void gpsProcessMessage(uint16_t msg_type, uint16_t msg_len,
gpsData.speed = pvt->gSpeed / 100; // speed in 0.1m/s
gpsData.groundCourse = pvt->headMot / 10000; // degrees * 10
if (gpsData.fix) {
__disable_irq(); // do the atomic update of lat/lon
gpsData.longitude = pvt->lon / 10; // degrees * 1.000.000
gpsData.latitude = pvt->lat / 10; // degrees * 1.000.000
gpsData.altitude = pvt->hMSL / 100; // altitude in 0.1m
__enable_irq();
}
#if defined(RTCLOCK)
@@ -529,6 +529,12 @@ coord_t BitmapBuffer::drawSizedText(coord_t x, coord_t y, const char *s,
return ((flags & RIGHT) ? orig_pos : pos) - offsetX;
}
coord_t BitmapBuffer::drawText(coord_t x, coord_t y, const char* s, LcdFlags flags)
{
if (!s) return x;
return drawSizedText(x, y, s, strlen(s), flags);
}
// Resize and convert to LVGL image data format with alpha blending to
// background color
void BitmapBuffer::resizeToLVGL(coord_t w, coord_t h)
+1 -5
View File
@@ -132,11 +132,7 @@ class BitmapBuffer
coord_t drawSizedText(coord_t x, coord_t y, const char* s, uint8_t len,
LcdFlags flags = 0);
coord_t drawText(coord_t x, coord_t y, const char* s, LcdFlags flags = 0)
{
if (!s) return x;
return drawSizedText(x, y, s, strlen(s), flags);
}
coord_t drawText(coord_t x, coord_t y, const char* s, LcdFlags flags = 0);
void drawBitmap(coord_t x, coord_t y, const BitmapBuffer* bmp,
coord_t srcx = 0, coord_t srcy = 0, coord_t srcw = 0,
+1 -3
View File
@@ -176,9 +176,7 @@ LabelDialog::LabelDialog(const char *label, int length, const char* title,
std::function<void(std::string)> _saveHandler) :
ModalWindow(false), saveHandler(std::move(_saveHandler))
{
assert(length <= MAX_LABEL_LENGTH);
strncpy(this->label, label, length);
strncpy(this->label, label, std::min(length, MAX_LABEL_LENGTH));
this->label[length] = '\0';
auto form = new Window(this, rect_t{});
+5 -6
View File
@@ -25,12 +25,11 @@
#include "layout.h"
#include "LvglWrapper.h"
#include "etx_lv_theme.h"
#include "os/sleep.h"
#include "sdcard.h"
#include "view_main.h"
// timers_driver.h
uint32_t timersGetMsTick();
#include "os/sleep.h"
#include "os/time.h"
#include "sdcard.h"
MainWindow* MainWindow::_instance = nullptr;
@@ -63,7 +62,7 @@ void MainWindow::run(bool trash)
LvglWrapper::instance()->run();
#if defined(DEBUG_WINDOWS)
auto start = timersGetMsTick();
auto start = time_get_ms();
#endif
if (widgetRefreshEnable)
@@ -85,7 +84,7 @@ void MainWindow::run(bool trash)
emptyTrash();
#if defined(DEBUG_WINDOWS)
auto delta = timersGetMsTick() - start;
auto delta = time_get_ms() - start;
if (delta > 10) {
TRACE_WINDOWS("MainWindow::run took %dms", delta);
}
@@ -27,6 +27,7 @@
#define SET_DIRTY() storageDirty(EE_MODEL)
#if defined(EXTERNAL_ANTENNA)
PXX1AntennaSettings::PXX1AntennaSettings(Window* parent,
const FlexGridLayout& g,
uint8_t moduleIdx) :
@@ -62,3 +63,4 @@ PXX1AntennaSettings::PXX1AntennaSettings(Window* parent,
antennaChoice->setAvailableHandler(
[=](int8_t mode) { return mode != ANTENNA_MODE_PER_MODEL; });
}
#endif
+2 -3
View File
@@ -30,8 +30,8 @@ uint8_t menuLevel = 0;
void popMenu()
{
killEvents(KEY_EXIT);
if (menuLevel == 0) return;
assert(menuLevel > 0);
menuLevel = menuLevel - 1;
menuEvent = EVT_ENTRY_UP;
// TODO ? AUDIO_KEY_PRESS();
@@ -57,6 +57,7 @@ void chainMenu(MenuHandlerFunc newMenu)
void pushMenu(MenuHandlerFunc newMenu)
{
killAllEvents();
if (menuLevel == DIM(menuHandlers) - 1) return;
if (menuLevel == 0) {
if (newMenu == menuTabGeneral[0].menuFunc)
@@ -71,8 +72,6 @@ void pushMenu(MenuHandlerFunc newMenu)
menuLevel++;
assert(menuLevel < DIM(menuHandlers));
menuHandlers[menuLevel] = newMenu;
menuEvent = EVT_ENTRY;
// ? AUDIO_KEY_PRESS();
+29 -9
View File
@@ -366,8 +366,6 @@ static uint32_t apply_low_pass_filter(uint32_t v, uint32_t v_prev,
static uint32_t apply_calibration(const CalibData* calib, uint32_t v)
{
// Simu uses normed inputs
#if !defined(SIMU)
// Apply calibration relative to mid-point
int32_t s = v - 2 * calib->mid;
s = s * (int32_t)RESX /
@@ -383,10 +381,7 @@ static uint32_t apply_calibration(const CalibData* calib, uint32_t v)
s = 4 * RESX;
}
v = s;
#endif
return v;
return s;
}
static uint32_t apply_multipos(const StepsCalibData* calib, uint32_t v)
@@ -407,6 +402,24 @@ static uint32_t apply_multipos(const StepsCalibData* calib, uint32_t v)
return ANAFILT_MAX;
}
#if defined(SIMU)
// In simulation, assume equally spaced positions across full ADC range
static uint32_t apply_multipos_simu(uint32_t v)
{
constexpr uint32_t ALPHA_MULT = JITTER_ALPHA * ANALOG_MULTIPLIER;
constexpr uint32_t ANAFILT_MAX = 2 * RESX * ALPHA_MULT;
constexpr uint32_t N = XPOTS_MULTIPOS_COUNT;
constexpr uint32_t COUNT = N - 1;
uint32_t pos = v * N / (ANAFILT_MAX + 1);
if (pos > COUNT) pos = COUNT;
return (pos < COUNT)
? (pos * (ANAFILT_MAX + ALPHA_MULT)) / COUNT
: ANAFILT_MAX;
}
#endif
void getADC()
{
auto max_analogs = adcGetMaxInputs(ADC_INPUT_ALL);
@@ -435,14 +448,17 @@ void getADC()
bool is_flex_input = (x >= pot_offset) && (x < pot_offset + max_pots);
bool is_multipos = is_flex_input && IS_POT_MULTIPOS(x - pot_offset);
// 1st: apply calibration
uint32_t v = getAnalogValue(x);
#if !defined(SIMU)
// Apply hardware calibration (not needed in simulation:
// the host already provides normalized ADC-range values)
if (x < max_calib_analogs && !is_multipos) {
v = apply_calibration(&g_eeGeneral.calib[x], v);
}
#endif
// 2nd: apply inversion
// Apply inversion
if (x < pot_offset && getStickInversion(inputMappingConvertMode(x))) {
v = 4 * RESX - v;
}
@@ -451,14 +467,18 @@ void getADC()
v = 4 * RESX - v;
}
// 3rd: apply filtering
// Apply filtering
s_anaFilt[x] = apply_low_pass_filter(v, s_anaFilt[x], x < max_mains);
if (is_multipos) {
#if defined(SIMU)
s_anaFilt[x] = apply_multipos_simu(s_anaFilt[x]);
#else
const auto* calib = (const StepsCalibData*)&g_eeGeneral.calib[x];
if (IS_MULTIPOS_CALIBRATED(calib)) {
s_anaFilt[x] = apply_multipos(calib, s_anaFilt[x]);
}
#endif
}
#if defined(JITTER_MEASURE)
+2 -2
View File
@@ -1200,7 +1200,6 @@ void evalMixes(uint8_t tick10ms)
weight += fp_act[p];
}
}
assert(weight);
mixerCurrentFlightMode = fm;
}
else {
@@ -1235,7 +1234,8 @@ void evalMixes(uint8_t tick10ms)
if (g_eeGeneral.volumeSrc) {
calcVolumeValue(g_eeGeneral.volumeSrc);
} else {
requiredSpeakerVolume = g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF;
requiredSpeakerVolume =
limit<int>(0, g_eeGeneral.speakerVolume + VOLUME_LEVEL_DEF, VOLUME_LEVEL_MAX);
}
}
#endif
+4 -3
View File
@@ -23,11 +23,12 @@
#include <chrono>
extern uint64_t simuTimerMicros(void);
uint32_t time_get_ms()
{
return simuTimerMicros() / 1000;
static auto _start = std::chrono::steady_clock::now();;
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - _start);
return duration.count();
}
time_point_t time_point_now()
+14 -2
View File
@@ -403,8 +403,14 @@ void getSwitchesPosition(bool startup)
if (IS_POT_MULTIPOS(i)) {
auto analog_idx = offset + i;
StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[analog_idx];
#if defined(SIMU)
{
uint8_t count = XPOTS_MULTIPOS_COUNT - 1;
#else
if (IS_MULTIPOS_CALIBRATED(calib)) {
uint8_t pos = anaIn(analog_idx) / (2 * RESX / calib->count);
uint8_t count = calib->count;
#endif
uint8_t pos = anaIn(analog_idx) / (2 * RESX / count);
uint8_t previousPos = potsPos[i] >> 4;
uint8_t previousStoredPos = potsPos[i] & 0x0F;
if (startup) {
@@ -839,9 +845,15 @@ swsrc_t getMovedSwitch()
for (int i = 0; i < MAX_POTS; i++) {
if (IS_POT_MULTIPOS(i)) {
StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[MAX_STICKS + i];
#if defined(SIMU)
{
uint8_t count = XPOTS_MULTIPOS_COUNT - 1;
#else
if (IS_MULTIPOS_CALIBRATED(calib)) {
uint8_t count = calib->count;
#endif
uint8_t prev = potsPos[i] & 0x0F;
uint8_t next = anaIn(MAX_STICKS + i) / (2 * RESX / calib->count);
uint8_t next = anaIn(MAX_STICKS + i) / (2 * RESX / count);
if (prev != next) {
result = SWSRC_FIRST_MULTIPOS_SWITCH + i * XPOTS_MULTIPOS_COUNT + next;
}
@@ -38,9 +38,4 @@
#endif
#endif
#if defined(SIMU)
#include "../simu/simpgmspace.h"
#endif
#include "delays_driver.h"
+79 -111
View File
@@ -5,8 +5,27 @@ if(NOT SIMU_TARGET)
return()
endif()
if(MINGW)
# struct packing breaks on MinGW w/out -mno-ms-bitfields:
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991
# http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw
#
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields")
endif()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
if(ASAN AND NOT WASI)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
set(SIMU_DRIVERS
simpgmspace.cpp
simulib.cpp
simufatfs.cpp
simudisk.cpp
simulcd.cpp
@@ -20,10 +39,9 @@ set(SIMU_DRIVERS
bt_driver.cpp
timers_driver.cpp
abnormal_reboot.cpp
)
)
set(HW_DESC_JSON ${FLAVOUR}.json)
# AddHardwareDefTarget(${HW_DESC_JSON})
AddHWGenTarget(${HW_DESC_JSON} simu_keys simu_keys.inc)
AddHWGenTarget(${HW_DESC_JSON} simu_switches simu_switches.inc)
AddHWGenTarget(${HW_DESC_JSON} hal_adc_inputs hal_adc_inputs.inc)
@@ -32,35 +50,21 @@ set(SIMU_DRIVERS ${SIMU_DRIVERS}
${CMAKE_CURRENT_BINARY_DIR}/simu_keys.inc
${CMAKE_CURRENT_BINARY_DIR}/simu_switches.inc
${CMAKE_CURRENT_BINARY_DIR}/hal_adc_inputs.inc
${RADIO_SRC_DIR}/boards/hw_defs/${HW_DESC_JSON}
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
if(SIMU_LUA_COMPILER)
add_definitions(-DLUA_COMPILER)
endif()
if(SIMU_BOOTLOADER)
add_definitions(-DSIMU_BOOTLOADER)
set(SIMU_DRIVERS ${SIMU_DRIVERS}
../../bootloader/bin_files.cpp
../../bootloader/boot.cpp
# TODO - how to handle this for B&W (not currently used)?
../../gui/colorlcd/boot_menu.cpp
../../thirdparty/lz4/lz4.c
)
endif()
# Pack all simu driver sources into an object lib
add_library(simu_drivers OBJECT EXCLUDE_FROM_ALL
${SIMU_DRIVERS})
${SIMU_DRIVERS}
)
get_property(SIMU_SRC_OPTIONS
TARGET radiolib_native
PROPERTY INTERFACE_COMPILE_OPTIONS)
PROPERTY INTERFACE_COMPILE_OPTIONS
)
target_compile_options(simu_drivers PRIVATE ${SIMU_SRC_OPTIONS})
set_property(TARGET simu_drivers PROPERTY POSITION_INDEPENDENT_CODE ON)
if(SIMU_AUX)
@@ -71,101 +75,64 @@ endif()
set(SIMU_SRC
$<TARGET_OBJECTS:radiolib_native>
$<TARGET_OBJECTS:simu_drivers>
)
)
set(SIMU_SRC ${SIMU_SRC} PARENT_SCOPE)
# Set the options as well in parent scope to be used by unit tests
set(SIMU_SRC_OPTIONS ${SIMU_SRC_OPTIONS} PARENT_SCOPE)
if(Qt6Widgets_FOUND)
set(SIMULATOR_FLAVOUR edgetx-${FLAVOUR})
set(SIMULATOR_TARGET ${SIMULATOR_FLAVOUR}-simulator)
add_definitions(-DSIMULATOR_FLAVOUR="${SIMULATOR_FLAVOUR}")
include_directories(${COMPANION_SRC_DIRECTORY} ${COMPANION_SRC_DIRECTORY}/simulation)
qt_wrap_cpp(SIMULATOR_SRC
${COMPANION_SRC_DIRECTORY}/simulation/simulatorinterface.h
opentxsimulator.h
)
list(APPEND SIMULATOR_SRC ${SIMU_SRC} opentxsimulator.cpp)
add_library(${SIMULATOR_TARGET} SHARED ${SIMULATOR_SRC})
set_target_properties(${SIMULATOR_TARGET} PROPERTIES PREFIX "lib")
if(TARGET SDL2::SDL2)
set(SDL2_LIBRARIES SDL2::SDL2)
target_sources(${SIMULATOR_TARGET} PRIVATE simuaudio.cpp)
else()
target_sources(${SIMULATOR_TARGET} PRIVATE no_audio.cpp)
endif()
target_compile_options(${SIMULATOR_TARGET} PRIVATE ${SIMU_SRC_OPTIONS})
target_compile_definitions(${SIMULATOR_TARGET} PUBLIC ${APP_COMMON_DEFINES})
target_link_libraries(${SIMULATOR_TARGET} ${SDL2_LIBRARIES} Qt::Core Qt::SerialPort)
# Remove debug symbols on release builds
if("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
add_custom_command(TARGET ${SIMULATOR_TARGET} POST_BUILD
COMMAND ${CMAKE_STRIP} -x $<TARGET_FILE:${SIMULATOR_TARGET}>)
endif()
add_custom_command(
TARGET ${SIMULATOR_TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/plugins/
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:${SIMULATOR_TARGET}> ${CMAKE_BINARY_DIR}/plugins/
if(WASI)
# strip debug info in Release builds
list(APPEND CMAKE_EXE_LINKER_FLAGS_RELEASE
-Wl,--strip-debug
)
# Introduced at 2.5 - TODO replace with a code change
# When compiled shared simulators using libopenui and Companion have classes named MainWindow
# When the simulator is launched from Companion the runtime dynamic linking can result in Companion overiding the simulator
# and crashing the simulator under some OSes
# The flag forces the runtime linker to always look for references in the simulator first
if (NOT CLANG)
target_link_options(${SIMULATOR_TARGET} PRIVATE "LINKER:-Bsymbolic")
set(WASI_COMPILE_OPTIONS
-Os
-fno-exceptions
-fno-rtti
-ffunction-sections
-fdata-sections
# setjmp / longjmp handling
-mllvm -wasm-enable-sjlj
)
if(USE_LTO)
list(APPEND WASI_COMPILE_OPTIONS -flto=thin)
endif()
#
add_custom_target(libsimulator DEPENDS ${SIMULATOR_TARGET})
# Prepare the "all-simu-libs" target to build simulator libraries for *every* supported PCB type (PCB_TYPES list)
# (a fast build machine or corresponding amount of patience is recommended for this target).
if(${CMAKE_GENERATOR} MATCHES ".*Unix Makefiles$")
# Ensure parallel builds with GCC make (try with MAKEFLAGS but this seems to be getting clobbered somewhere).
# otherwise 'make' may complain: "warning: jobserver unavailable: using -j1..." and build single-threaded. (Ninja always rocks this.)
set(mkflags "$ENV{MAKEFLAGS}")
if (mkflags STREQUAL "")
set(mkflags "-j")
endif()
target_compile_options(radiolib_native PUBLIC ${WASI_COMPILE_OPTIONS})
target_compile_options(simu_drivers PUBLIC ${WASI_COMPILE_OPTIONS})
add_executable(wasi-module EXCLUDE_FROM_ALL
${SIMU_SRC}
)
set_target_properties(wasi-module PROPERTIES
OUTPUT_NAME "edgetx-${FLAVOUR}-simulator"
SUFFIX ".wasm"
)
target_compile_options(wasi-module PUBLIC ${WASI_COMPILE_OPTIONS})
target_link_options(wasi-module PRIVATE
-Wl,--import-memory
-Wl,--export-memory
-Wl,--max-memory=2147483648
-Wl,--gc-sections
# enable runtime to check stack / heap
-Wl,--export=__heap_base,--export=__data_end
-Wl,--export=malloc -Wl,--export=free
# no main()
-mexec-model=reactor
# setjmp / longjmp
-Wl,-mllvm,-wasm-enable-sjlj
-lsetjmp
)
if(USE_LTO)
target_link_options(wasi-module PRIVATE -Wl,--thinlto-jobs=all)
endif()
set(all_libs_cmd ${CMAKE_COMMAND} -E echo "Building all simulator libraries")
foreach(pcb ${PCB_TYPES})
set(pcbrev "")
if(pcb STREQUAL X12S)
set(pcbrev "-DPCBREV=13")
endif()
set(all_libs_cmd ${all_libs_cmd} && ${CMAKE_COMMAND} -DPCB=${pcb} ${pcbrev} -Wno-dev ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} && ${CMAKE_COMMAND} --build . --target libsimulator -- ${mkflags})
endforeach()
add_custom_target(all-simu-libs COMMAND ${all_libs_cmd} USES_TERMINAL WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endif()
if(MINGW)
# struct packing breaks on MinGW w/out -mno-ms-bitfields: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52991 & http://stackoverflow.com/questions/24015852/struct-packing-and-alignment-with-mingw
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mno-ms-bitfields")
endif()
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
if(ASAN)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS}")
if(TARGET SDL2::SDL2)
else()
add_executable(simu
EXCLUDE_FROM_ALL
${SIMU_SRC}
@@ -177,15 +144,16 @@ if(TARGET SDL2::SDL2)
sdl_simu.cpp
)
include(FetchImgui)
target_compile_options(simu PUBLIC -DSIMU)
add_png_target(image_assets "assets/images/*.png")
add_dependencies(simu image_assets)
# STB
set(STB_DIR ${RADIO_SRC_DIR}/thirdparty/stb)
target_include_directories(simu PUBLIC ${STB_DIR})
target_link_libraries(simu PUBLIC imgui)
endif()
PrintTargetReport("simu/libsimulator")
# ImGui
include(FetchImgui)
target_link_libraries(simu PRIVATE imgui)
endif()
+15 -8
View File
@@ -19,20 +19,24 @@
* GNU General Public License for more details.
*/
#include "simulib.h"
#include "hal/adc_driver.h"
#include "definitions.h"
#include "definitions.h"
#include "myeeprom.h"
#include "board.h"
#include "edgetx.h"
#include "translations/translations.h"
#include "hal_adc_inputs.inc"
void enableVBatBridge(){}
void disableVBatBridge(){}
bool isVBatBridgeEnabled(){ return false; }
uint16_t getLuxSensorValue() { return 1024; }
uint16_t getLuxSensorValue()
{
if (adcGetMaxInputs(ADC_INPUT_LUX) < 1) return 0;
return anaIn(adcGetInputOffset(ADC_INPUT_LUX));
}
uint16_t getBatteryVoltage()
{
@@ -45,19 +49,22 @@ uint16_t getRTCBatteryVoltage()
return 300;
}
extern uint16_t simu_get_analog(uint8_t idx);
extern uint16_t simuGetAnalog(uint8_t idx);
static bool simu_start_conversion()
{
int max_input = adcGetInputOffset(ADC_INPUT_VBAT);
int max_input = adcGetMaxInputs(ADC_INPUT_ALL);
for (int i = 0; i < max_input; i++) {
setAnalogValue(i, simu_get_analog(i));
setAnalogValue(i, simuGetAnalog(i));
}
// set batteries default voltages
int i = adcGetInputOffset(ADC_INPUT_VBAT);
if (i > 0) {
uint16_t volts = (uint16_t)((g_eeGeneral.vBatWarn > 0 ? g_eeGeneral.vBatWarn : BATTERY_WARN) + 5) * 10; // +0.5V and prec2
// +0.5V and prec2
uint16_t vBatWarn = BATTERY_WARN;
if (g_eeGeneral.vBatWarn > 0) vBatWarn = g_eeGeneral.vBatWarn;
uint16_t volts = (vBatWarn + 5) * 10;
setAnalogValue(i, volts * 2);
}
+1 -3
View File
@@ -20,7 +20,7 @@
*/
#include "audio.h"
#include "simuaudio.h"
#include "simulib.h"
#if defined(AUDIO_HP_DETECT_PIN)
bool audioHeadphoneDetect()
@@ -51,8 +51,6 @@ int simuAudioGetVolume()
#endif
}
void simuQueueAudio(const uint8_t* data, uint32_t len);
void audioConsumeCurrentBuffer()
{
auto& fifo = audioQueue.buffersFifo;
+47
View File
@@ -0,0 +1,47 @@
# Simulator Plugin Interface
## Methods
```c
/**
* Simu module functions
*/
/* Simulator plugin name */
const char* simu_get_name();
/* Start/stop */
void simuInit();
void simuStop(); // should be async but isn't
bool simuIsRunning();
/* Live data */
unsigned int simu_get_num_outputs();
const int16_t* simu_get_outputs();
unsigned int simu_get_num_channels();
const int16_t* simu_get_channels();
unsigned int simu_get_num_logical_switches();
uint32_t* simu_get_logical_switches();
uint8_t simu_get_flight_mode();
uint8_t simu_get_trim_value(uint8_t flight_mode, uint8_t axis);
uint8_t simu_get_stick_mode();
uint16_t simu_get_trim_range();
void simu_get_flight_mode_name(uint8_t flight_mode, char* buffer, int buflen);
// TODO: get GVar definitions + values
/**
* Callbacks / native functions
*/
void simu_read_analogs(uint16_t* analogs, int num_analogs);
uint32_t simu_read_keys();
uint32_t simu_read_trims();
void simu_flush_display(bool backlight, uint8_t* buffer, int buflen);
```
-945
View File
@@ -1,945 +0,0 @@
/*
* 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.
*/
#include "opentxsimulator.h"
#include "edgetx.h"
#include "simulcd.h"
#include "simuaudio.h"
#include "switches.h"
#include "serial.h"
#include "myeeprom.h"
#include "hal/adc_driver.h"
#include "hal/rotary_encoder.h"
#include "os/time.h"
#include "boards/generic_stm32/rgb_leds.h"
#include <QDebug>
#include <QElapsedTimer>
#if !defined(MAX_LOGICAL_SWITCHES) && defined(NUM_CSW)
#define MAX_LOGICAL_SWITCHES NUM_CSW
#endif
#define GET_SWITCH_BOOL(sw__) getSwitch((sw__), 0);
#define ETXS_DBG qDebug() << "(" << simuTimerMicros() << "us)"
int16_t g_anas[MAX_ANALOG_INPUTS];
QVector<QIODevice *> OpenTxSimulator::tracebackDevices;
typedef struct {
uint8_t index;
QMutex mutex;
QQueue<uint8_t> receiveBuffer;
OpenTxSimulator * simulator;
} simulated_serial_port_t;
simulated_serial_port_t simulatedSerialPorts[MAX_AUX_SERIAL];
extern etx_serial_port_t * serialPorts[MAX_AUX_SERIAL];
#if defined(HARDWARE_TOUCH)
tmr10ms_t downTime = 0;
tmr10ms_t tapTime = 0;
short tapCount = 0;
#define TAP_TIME 25
#endif
uint16_t simu_get_analog(uint8_t idx)
{
// TODO: return raw values for ADC_INPUT_VBAT and ADC_INPUT_RTC_BAT
// 6POS simu mechanism use a different scale, so needs specific offset
if (IS_POT_MULTIPOS(idx - adcGetInputOffset(ADC_INPUT_FLEX))) {
// Use radio calibration data to determine conversion factor
StepsCalibData * calib = (StepsCalibData *) &g_eeGeneral.calib[idx];
int range6POS = 2048; // Default if calibration is not valid
if (calib->count != 0) {
// calculate 6POS switch range from calibration data
int c1 = calib->steps[calib->count - 1] * 32; // last calibration value
int c2 = calib->steps[calib->count - 2] * 32; // 2nd last calibration value
range6POS = c1 + (c1 - c2) / 2;
}
return (g_anas[idx] * range6POS / 2048);
}
return (g_anas[idx] * 2) + 2048;
}
void firmwareTraceCb(const char * text)
{
foreach (QIODevice * dev, OpenTxSimulator::tracebackDevices) {
if (dev)
dev->write(text);
}
}
// Serial port handling needs to know about OpenTxSimulator, so we we
// need to update what's in simpgmspace.cpp when we have a simulator
// to point at.
static void* simulator_host_drv_init(void* hw_def, const etx_serial_init* dev)
{
if (hw_def == nullptr)
return nullptr;
simulated_serial_port_t *port = (simulated_serial_port_t *)hw_def;
port->simulator->drv_auxSerialInit(port->index, dev);
// Return the port definition as the context
return (void *)port;
}
static void simulator_host_drv_deinit(void* ctx)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialDeinit(port->index);
}
static void simulator_host_drv_send_byte(void* ctx, uint8_t b)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialSendByte(port->index, b);
}
static void simulator_host_drv_send_buffer(void* ctx, const uint8_t* b, uint32_t l)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialSendBuffer(port->index, b, l);
}
static int simulator_host_drv_get_byte(void* ctx, uint8_t* b)
{
if (ctx == nullptr)
return 0;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
return port->simulator->drv_auxSerialGetByte(port->index, b);
}
static void simulator_host_drv_set_baudrate(void* ctx, uint32_t baudrate)
{
if (ctx == nullptr)
return;
simulated_serial_port_t *port = (simulated_serial_port_t *)ctx;
port->simulator->drv_auxSerialSetBaudrate(port->index, baudrate);
}
static bool simulator_host_drv_tx_completed(void* ctx) { return true; }
static const etx_serial_driver_t simulator_host_drv = {
.init = simulator_host_drv_init,
.deinit = simulator_host_drv_deinit,
.sendByte = simulator_host_drv_send_byte,
.sendBuffer = simulator_host_drv_send_buffer,
.txCompleted = simulator_host_drv_tx_completed,
.waitForTxCompleted = nullptr,
.enableRx = nullptr,
.getByte = simulator_host_drv_get_byte,
.getLastByte = nullptr,
.getBufferedBytes = nullptr,
.copyRxBuffer = nullptr,
.clearRxBuffer = nullptr,
.getBaudrate = nullptr,
.setBaudrate = simulator_host_drv_set_baudrate,
.setPolarity = nullptr,
.setHWOption = nullptr,
.setReceiveCb = nullptr,
.setIdleCb = nullptr,
.setBaudrateCb = nullptr,
};
OpenTxSimulator::OpenTxSimulator() :
SimulatorInterface(),
m_timer10ms(nullptr),
m_resetOutputsData(true),
m_stopRequested(false)
{
tracebackDevices.clear();
traceCallback = firmwareTraceCb;
// When we create the simulator, we change the UART driver
for (int i = 0; i < MAX_AUX_SERIAL; i++) {
etx_serial_port_t * port = serialPorts[i];
if (port != nullptr) {
port->uart = &simulator_host_drv;
port->hw_def = &(simulatedSerialPorts[i]);
simulatedSerialPorts[i].index = i;
simulatedSerialPorts[i].simulator = this;
}
}
}
OpenTxSimulator::~OpenTxSimulator()
{
traceCallback = nullptr;
tracebackDevices.clear();
for (int i = 0; i < MAX_AUX_SERIAL; i++) {
etx_serial_port_t * port = serialPorts[i];
if (port != nullptr) {
port->hw_def = nullptr;
}
}
if (m_timer10ms)
delete m_timer10ms;
if (isRunning()) {
stop();
QElapsedTimer tmout;
tmout.start();
while (isRunning() && !tmout.hasExpired(1000))
;
}
//qDebug() << "Deleting OpenTxSimulator";
}
QString OpenTxSimulator::name()
{
return QString(SIMULATOR_FLAVOUR);
}
bool OpenTxSimulator::isRunning()
{
QMutexLocker lckr(&m_mtxSimuMain);
return simuIsRunning();
}
void OpenTxSimulator::init()
{
if (isRunning())
return;
ETXS_DBG;
if (!m_timer10ms) {
// make sure we create & control the timer from current thread
m_timer10ms = new QTimer();
m_timer10ms->setInterval(10);
connect(m_timer10ms, &QTimer::timeout, this, &OpenTxSimulator::run);
connect(this, SIGNAL(started()), m_timer10ms, SLOT(start()));
connect(this, SIGNAL(stopped()), m_timer10ms, SLOT(stop()));
}
m_resetOutputsData = true;
setStopRequested(false);
QMutexLocker lckr(&m_mtxSimuMain);
memset(g_anas, 0, sizeof(g_anas));
simuInit();
if (adcGetMaxInputs(ADC_INPUT_RTC_BAT) > 0) {
auto idx = adcGetInputOffset(ADC_INPUT_RTC_BAT);
setAnalogValue(idx, 800);
}
}
void OpenTxSimulator::start(const char * filename, bool tests)
{
if (isRunning()) return;
ETXS_DBG << "file:" << filename << "tests:" << tests;
QMutexLocker lckr(&m_mtxSimuMain);
QMutexLocker slckr(&m_mtxSettings);
simuAudioInit();
simuStart(tests, simuSdDirectory.toLatin1().constData(),
simuSettingsDirectory.toLatin1().constData());
emit started();
QTimer::singleShot(0, this, SLOT(run())); // old style for Qt < 5.4
}
void OpenTxSimulator::stop()
{
if (!isRunning())
return;
ETXS_DBG;
setStopRequested(true);
QMutexLocker lckr(&m_mtxSimuMain);
simuStop();
simuAudioDeInit();
emit stopped();
}
void OpenTxSimulator::setSdPath(const QString & sdPath, const QString & settingsPath)
{
QMutexLocker lckr(&m_mtxSettings);
simuSdDirectory = sdPath;
simuSettingsDirectory = settingsPath;
}
void OpenTxSimulator::setVolumeGain(const int value)
{
QMutexLocker lckr(&m_mtxSettings);
volumeGain = value;
}
void OpenTxSimulator::setRadioData(const QByteArray & data)
{
// #if defined(EEPROM_SIZE)
// QMutexLocker lckr(&m_mtxRadioData);
// eeprom = (uint8_t *)malloc(qMin<int>(EEPROM_SIZE, data.size()));
// memcpy(eeprom, data.data(), qMin<int>(EEPROM_SIZE, data.size()));
// #endif
}
void OpenTxSimulator::readRadioData(QByteArray & dest)
{
// #if defined(EEPROM_SIZE)
// QMutexLocker lckr(&m_mtxRadioData);
// if (eeprom)
// memcpy(dest.data(), eeprom, qMin<int>(EEPROM_SIZE, dest.size()));
// #endif
}
uint8_t * OpenTxSimulator::getLcd()
{
return (uint8_t *)simuLcdBuf;
}
void OpenTxSimulator::setAnalogValue(uint8_t index, int16_t value)
{
static int dim = DIM(g_anas);
if (index < dim)
g_anas[index] = value;
}
void OpenTxSimulator::setSwitch(uint8_t swtch, int8_t state)
{
simuSetSwitch(swtch, state);
}
void OpenTxSimulator::setKey(uint8_t key, bool state)
{
simuSetKey(key, state);
}
void OpenTxSimulator::setTrimSwitch(uint8_t trim, bool state)
{
simuSetTrim(trim, state);
}
void OpenTxSimulator::setTrim(unsigned int idx, int value)
{
unsigned i = inputMappingConvertMode(idx);
uint8_t phase = getTrimFlightMode(getFlightMode(), i);
setTrimValue(phase, i, value);
}
void OpenTxSimulator::setTrainerInput(unsigned int inputNumber, int16_t value)
{
static unsigned dim = DIM(trainerInput);
if (inputNumber < dim)
trainerInput[inputNumber] = qMin(qMax((int16_t)-512, value), (int16_t)512);
}
void OpenTxSimulator::setInputValue(int type, uint8_t index, int16_t value)
{
//qDebug() << type << index << value << this->thread();
switch (type) {
case INPUT_SRC_ANALOG :
case INPUT_SRC_STICK :
case INPUT_SRC_KNOB :
case INPUT_SRC_SLIDER :
setAnalogValue(index, value);
break;
case INPUT_SRC_TXVIN :
if (adcGetMaxInputs(ADC_INPUT_VBAT) > 0) {
auto idx = adcGetInputOffset(ADC_INPUT_VBAT);
setAnalogValue(idx, value);
emit txBatteryVoltageChanged((unsigned int)value);
}
break;
case INPUT_SRC_SWITCH :
setSwitch(index, (int8_t)value);
break;
case INPUT_SRC_TRIM_SW :
setTrimSwitch(index, (bool)value);
break;
case INPUT_SRC_TRIM :
setTrim(index, value);
break;
case INPUT_SRC_KEY :
setKey(index, (bool)value);
break;
case INPUT_SRC_TRAINER :
setTrainerInput(index, value);
break;
case INPUT_SRC_ROTENC : // TODO
default:
return;
}
}
extern volatile rotenc_t rotencValue;
extern volatile uint32_t rotencDt;
void OpenTxSimulator::rotaryEncoderEvent(int steps)
{
if (steps == 0) return;
#if defined(ROTARY_ENCODER_NAVIGATION) && !defined(USE_HATS_AS_KEYS)
static uint32_t last_tick = 0;
if (steps != 0) {
if (g_eeGeneral.rotEncMode == ROTARY_ENCODER_MODE_INVERT_BOTH)
steps *= -1;
rotencValue += steps * ROTARY_ENCODER_GRANULARITY;
// TODO: set rotencDt
uint32_t now = time_get_ms();
uint32_t dt = now - last_tick;
rotencDt += dt;
last_tick = now;
}
#else
int key;
if (keyIsSupported(KEY_UP) | keyIsSupported(KEY_DOWN)) {
key = steps > 0 ? KEY_DOWN : KEY_UP;
} else if (keyIsSupported(KEY_PLUS) | keyIsSupported(KEY_MINUS)) {
key = steps > 0 ? KEY_MINUS : KEY_PLUS;
} else {
return; // not supposed to happen???
}
setKey(key, 1);
QTimer::singleShot(10, [this, key]() { setKey(key, 0); });
#endif // defined(ROTARY_ENCODER_NAVIGATION)
}
void OpenTxSimulator::touchEvent(int type, int x, int y)
{
#if defined(HARDWARE_TOUCH)
tmr10ms_t now = get_tmr10ms();
simTouchState.tapCount = 0;
#endif
switch (type) {
case TouchDown:
TRACE_WINDOWS("[Mouse Press] %d %d", x, y);
#if defined(HARDWARE_TOUCH)
simTouchState.event = TE_DOWN;
simTouchState.startX = simTouchState.x = x;
simTouchState.startY = simTouchState.y = y;
downTime = now;
#endif
break;
case TouchUp:
TRACE_WINDOWS("[Mouse Release] %d %d", x, y);
#if defined(HARDWARE_TOUCH)
if (simTouchState.event == TE_DOWN) {
simTouchState.event = TE_UP;
simTouchState.x = simTouchState.startX;
simTouchState.y = simTouchState.startY;
if (now - downTime <= TAP_TIME) {
if (now - tapTime > TAP_TIME)
tapCount = 1;
else
tapCount++;
simTouchState.tapCount = tapCount;
tapTime = now;
}
} else {
simTouchState.event = TE_SLIDE_END;
}
#endif
break;
case TouchSlide:
TRACE_WINDOWS("[Mouse Move] %d %d", x, y);
#if defined(HARDWARE_TOUCH)
simTouchState.deltaX += x - simTouchState.x;
simTouchState.deltaY += y - simTouchState.y;
if (simTouchState.event == TE_SLIDE ||
abs(simTouchState.deltaX) >= SLIDE_RANGE ||
abs(simTouchState.deltaY) >= SLIDE_RANGE) {
simTouchState.event = TE_SLIDE;
simTouchState.x = x;
simTouchState.y = y;
}
#endif
break;
}
#if defined(HARDWARE_TOUCH)
simTouchOccured=true;
#endif
}
void OpenTxSimulator::lcdFlushed()
{
::lcdFlushed();
}
void OpenTxSimulator::setTrainerTimeout(uint16_t ms)
{
trainerSetTimer(ms);
}
void OpenTxSimulator::sendTelemetry(const uint8_t module, const uint8_t protocol, const QByteArray data)
{
//ETXS_DBG << data;
switch (protocol) {
case SIMU_TELEMETRY_PROTOCOL_FRSKY_SPORT:
sportProcessTelemetryPacket(module,
(uint8_t *)data.constData(),
data.size());
break;
case SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB:
frskyDProcessPacket(module,
(uint8_t *)data.constData(),
data.size());
break;
case SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB_OOB:
// FrSky D telemetry is a stream which can span multiple
// packets. The telemetry parser _could_ be in the middle of a
// user packet when we want to inject telemetry, so we can't just
// call frskyDProcessPacket() with a USRPKT (at least not safely!)
// Instead we will bypass the telemetry stream parser and inject
// out of band into processHubPacket(). This way we also don't
// have to mess with byte stuffing and variable length packets.
//
// Note this doesn't take into account which module it's from, but
// that's how it works in the radio too if you have two frsky D
// modules running at once, so ¯\_(ツ)_/¯
{
uint8_t id = data[0];
uint16_t value = ((uint8_t)(data[2]) << 8) + (uint8_t)(data[1]);
processHubPacket(id, value);
}
break;
case SIMU_TELEMETRY_PROTOCOL_CROSSFIRE:
processCrossfireTelemetryFrame(module,
(uint8_t *)data.constData(),
data.size());
break;
default:
// Do nothing
break;
}
}
void OpenTxSimulator::sendInternalModuleTelemetry(const uint8_t protocol, const QByteArray data)
{
sendTelemetry(INTERNAL_MODULE, protocol, data);
}
void OpenTxSimulator::sendExternalModuleTelemetry(const uint8_t protocol, const QByteArray data)
{
sendTelemetry(EXTERNAL_MODULE, protocol, data);
}
uint8_t OpenTxSimulator::getSensorInstance(uint16_t id, uint8_t defaultValue)
{
for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) {
if (isTelemetryFieldAvailable(i)) {
TelemetrySensor * sensor = &g_model.telemetrySensors[i];
if (sensor->id == id) {
return sensor->frskyInstance.physID;
}
}
}
return defaultValue;
}
uint16_t OpenTxSimulator::getSensorRatio(uint16_t id)
{
for (int i = 0; i < MAX_TELEMETRY_SENSORS; i++) {
if (isTelemetryFieldAvailable(i)) {
TelemetrySensor * sensor = &g_model.telemetrySensors[i];
if (sensor->id == id) {
return sensor->custom.ratio;
}
}
}
return 0;
}
const int OpenTxSimulator::getCapability(Capability cap)
{
int ret = 0;
switch(cap) {
case CAP_LUA :
#ifdef LUA
ret = 1;
#endif
break;
case CAP_ROTARY_ENC :
break;
case CAP_ROTARY_ENC_NAV :
#ifdef ROTARY_ENCODER_NAVIGATION
ret = 1;
#endif
break;
case CAP_TELEM_FRSKY_SPORT :
ret = 1;
break;
case CAP_SERIAL_AUX1:
ret = (auxSerialGetPort(SP_AUX1) != nullptr);
break;
case CAP_SERIAL_AUX2:
ret = (auxSerialGetPort(SP_AUX2) != nullptr);
break;
}
return ret;
}
void OpenTxSimulator::setLuaStateReloadPermanentScripts()
{
#if defined(LUA)
luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
#endif
}
void OpenTxSimulator::addTracebackDevice(QIODevice * device)
{
QMutexLocker lckr(&m_mtxTbDevices);
if (device && !tracebackDevices.contains(device))
tracebackDevices.append(device);
}
void OpenTxSimulator::removeTracebackDevice(QIODevice * device)
{
if (device) {
QMutexLocker lckr(&m_mtxTbDevices);
// no QVector::removeAll() in Qt < 5.4
int i = 0;
foreach (QIODevice * d, tracebackDevices) {
if (d == device)
tracebackDevices.remove(i);
++i;
}
}
}
void OpenTxSimulator::receiveAuxSerialData(quint8 port_nr, const QByteArray & data)
{
if (port_nr >= MAX_AUX_SERIAL)
return;
QMutexLocker locker(&(simulatedSerialPorts[port_nr].mutex));
for (uint8_t byte : data)
simulatedSerialPorts[port_nr].receiveBuffer.enqueue(byte);
}
void OpenTxSimulator::drv_auxSerialSetBaudrate(quint8 port_nr, quint32 baudrate)
{
emit auxSerialSetBaudrate(port_nr, baudrate);
}
void OpenTxSimulator::drv_auxSerialInit(quint8 port_nr, const etx_serial_init* dev)
{
switch(dev->encoding) {
case ETX_Encoding_8N1:
emit auxSerialSetEncoding(port_nr, SERIAL_ENCODING_8N1);
break;
case ETX_Encoding_8E2:
emit auxSerialSetEncoding(port_nr, SERIAL_ENCODING_8E2);
break;
default:
// Do nothing, host hardware can't do SERIAL_ENCODING_PXX1_PWM
break;
}
if (dev->baudrate != 0)
emit auxSerialSetBaudrate(port_nr, dev->baudrate);
emit auxSerialStart(port_nr);
}
void OpenTxSimulator::drv_auxSerialDeinit(quint8 port_nr)
{
emit auxSerialStop(port_nr);
}
void OpenTxSimulator::drv_auxSerialSendByte(quint8 port_nr, uint8_t b)
{
QByteArray data = QByteArray((const char *)&b, 1);
emit auxSerialSendData(port_nr, data);
}
void OpenTxSimulator::drv_auxSerialSendBuffer(quint8 port_nr, const uint8_t* b, uint32_t l)
{
QByteArray data = QByteArray((const char *)b, l);
emit auxSerialSendData(port_nr, data);
}
int OpenTxSimulator::drv_auxSerialGetByte(quint8 port_nr, uint8_t *b)
{
// Obtain the port's mutex before messing with the buffer
QMutexLocker locker(&(simulatedSerialPorts[port_nr].mutex));
if (simulatedSerialPorts[port_nr].receiveBuffer.isEmpty())
return 0;
uint8_t byte = simulatedSerialPorts[port_nr].receiveBuffer.dequeue();
*b = byte;
return 1;
}
/*** Protected functions ***/
void OpenTxSimulator::run()
{
static uint32_t loops = 0;
static QElapsedTimer ts;
if (!loops)
ts.start();
if (isStopRequested()) {
return;
}
if (!isRunning()) {
QString err(getError());
emit runtimeError(err);
emit stopped();
return;
}
++loops;
checkLcdChanged();
checkFuncSwitchChanged();
if (!(loops % 5)) {
checkOutputsChanged();
}
if (!(loops % (SIMULATOR_INTERFACE_HEARTBEAT_PERIOD / 10))) {
emit heartbeat(loops, simuTimerMicros() / 1000);
}
}
bool OpenTxSimulator::isStopRequested()
{
QMutexLocker lckr(&m_mtxStopReq);
return m_stopRequested;
}
void OpenTxSimulator::setStopRequested(bool stop)
{
QMutexLocker lckr(&m_mtxStopReq);
m_stopRequested = stop;
}
bool OpenTxSimulator::checkLcdChanged()
{
if (simuLcdRefresh) {
simuLcdRefresh = false;
emit lcdChange(isBacklightEnabled());
return true;
}
return false;
}
void OpenTxSimulator::checkFuncSwitchChanged()
{
#if defined(FUNCTION_SWITCHES)
for (int i = 0; i < CPN_MAX_SWITCHES; i += 1) {
if (switchIsCustomSwitch(i)) {
uint8_t cfs = switchGetCustomSwitchIdx(i);
uint32_t c = rgbGetLedColor(cfs);
emit fsColorChange(i, c);
}
}
#endif
}
void OpenTxSimulator::checkOutputsChanged()
{
static TxOutputs lastOutputs;
static size_t chansDim = DIM(channelOutputs);
const static int16_t limit = 512 * 2;
qint32 tmpVal;
uint8_t i, idx;
const uint8_t phase = getFlightMode(); // edgetx.cpp
for (i=0; i < chansDim; i++) {
if (lastOutputs.chans[i] != channelOutputs[i] || m_resetOutputsData) {
emit channelOutValueChange(i, channelOutputs[i], (g_model.extendedLimits ? limit * LIMIT_EXT_PERCENT / 100 : limit));
emit outputValueChange(OUTPUT_SRC_CHAN_OUT, i, channelOutputs[i]);
lastOutputs.chans[i] = channelOutputs[i];
}
if (lastOutputs.ex_chans[i] != ex_chans[i] || m_resetOutputsData) {
emit channelMixValueChange(i, ex_chans[i], limit * 2);
emit outputValueChange(OUTPUT_SRC_CHAN_MIX, i, ex_chans[i]);
lastOutputs.ex_chans[i] = ex_chans[i];
}
}
for (i=0; i < MAX_LOGICAL_SWITCHES; i++) {
tmpVal = (qint32)GET_SWITCH_BOOL(SWSRC_FIRST_LOGICAL_SWITCH+i);
if (lastOutputs.vsw[i] != (bool)tmpVal || m_resetOutputsData) {
emit virtualSwValueChange(i, tmpVal);
emit outputValueChange(OUTPUT_SRC_VIRTUAL_SW, i, tmpVal);
lastOutputs.vsw[i] = tmpVal;
}
}
for (i=0; i < Board::TRIM_AXIS_COUNT; i++) {
idx = inputMappingConvertMode(i);
tmpVal = getTrimValue(getTrimFlightMode(phase, idx), idx);
if (lastOutputs.trims[i] != tmpVal || m_resetOutputsData) {
emit trimValueChange(i, tmpVal);
emit outputValueChange(OUTPUT_SRC_TRIM_VALUE, i, tmpVal);
lastOutputs.trims[i] = tmpVal;
}
}
tmpVal = g_model.extendedTrims ? TRIM_EXTENDED_MAX : TRIM_MAX;
if (lastOutputs.trimRange != tmpVal || m_resetOutputsData) {
emit trimRangeChange(Board::TRIM_AXIS_COUNT, -tmpVal, tmpVal);
emit outputValueChange(OUTPUT_SRC_TRIM_RANGE, Board::TRIM_AXIS_COUNT, tmpVal);
lastOutputs.trimRange = tmpVal;
}
if (lastOutputs.phase != phase || m_resetOutputsData) {
emit phaseChanged(phase, getCurrentPhaseName());
emit outputValueChange(OUTPUT_SRC_PHASE, 0, qint16(phase));
lastOutputs.phase = phase;
}
#if defined(GVAR_VALUE) && defined(GVARS)
gVarMode_t gvar;
for (uint8_t gv=0; gv < MAX_GVARS; gv++) {
gvar.prec = g_model.gvars[gv].prec;
gvar.unit = g_model.gvars[gv].unit;
for (uint8_t fm=0; fm < MAX_FLIGHT_MODES; fm++) {
gvar.mode = fm;
gvar.value = (int16_t)GVAR_VALUE(gv, getGVarFlightMode(fm, gv));
tmpVal = gvar;
if (lastOutputs.gvars[fm][gv] != tmpVal || m_resetOutputsData) {
lastOutputs.gvars[fm][gv] = tmpVal;
emit gVarValueChange(gv, tmpVal);
emit outputValueChange(OUTPUT_SRC_GVAR, gv, tmpVal);
}
}
}
#endif
m_resetOutputsData = false;
}
uint8_t OpenTxSimulator::getStickMode()
{
return limit<uint8_t>(0, g_eeGeneral.stickMode, 3);
}
const char * OpenTxSimulator::getPhaseName(unsigned int phase)
{
static char buff[LEN_FLIGHT_MODE_NAME+1];
strAppend(buff, g_model.flightModeData[phase].name, LEN_FLIGHT_MODE_NAME);
return buff;
}
const QString OpenTxSimulator::getCurrentPhaseName()
{
unsigned phase = getFlightMode();
QString name(getPhaseName(phase));
if (name.isEmpty())
name = QString::number(phase);
return name;
}
const char * OpenTxSimulator::getError()
{
return main_thread_error;
}
/*
* OpenTxSimulatorFactory
*/
class OpenTxSimulatorFactory: public SimulatorFactory
{
public:
OpenTxSimulatorFactory()
{
}
virtual SimulatorInterface * create()
{
return new OpenTxSimulator();
}
virtual QString name()
{
return QString(SIMULATOR_FLAVOUR);
}
virtual Board::Type type()
{
#if defined(PCBX12S)
return Board::BOARD_HORUS_X12S;
#elif defined(PCBX10)
return Board::BOARD_X10;
#elif defined(PCBX7ACCESS)
return Board::BOARD_TARANIS_X7_ACCESS;
#elif defined(PCBX7)
return Board::BOARD_TARANIS_X7;
#elif defined(PCBX9LITES)
return Board::BOARD_TARANIS_X9LITES;
#elif defined(PCBX9LITE)
return Board::BOARD_TARANIS_X9LITE;
#elif defined(PCBPL18)
return Board::BOARD_FLYSKY_PL18;
#else
return Board::BOARD_TARANIS_X9D;
#endif
}
};
extern "C" DLLEXPORT SimulatorFactory * registerSimu()
{
return new OpenTxSimulatorFactory();
}
-123
View File
@@ -1,123 +0,0 @@
/*
* 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 "simulatorinterface.h"
#include "dataconstants.h"
#include <QMutex>
#include <QObject>
#include <QTimer>
#if defined __GNUC__
#define DLLEXPORT
#else
#define DLLEXPORT __declspec(dllexport)
#endif
class DLLEXPORT OpenTxSimulator : public SimulatorInterface
{
Q_OBJECT
public:
enum TouchEvent {
TouchUp=0,
TouchDown,
TouchSlide
};
OpenTxSimulator();
virtual ~OpenTxSimulator();
virtual QString name();
virtual bool isRunning();
virtual void readRadioData(QByteArray & dest);
virtual uint8_t * getLcd();
virtual uint8_t getSensorInstance(uint16_t id, uint8_t defaultValue = 0);
virtual uint16_t getSensorRatio(uint16_t id);
virtual const int getCapability(Capability cap);
static QVector<QIODevice *> tracebackDevices;
void drv_auxSerialInit(quint8 port_num, const etx_serial_init* dev);
void drv_auxSerialDeinit(quint8 port_num);
void drv_auxSerialSetBaudrate(quint8 port_num, uint32_t baudrate);
void drv_auxSerialSendByte(quint8 port_num, uint8_t byte);
void drv_auxSerialSendBuffer(quint8 port_num, const uint8_t* data, uint32_t size);
int drv_auxSerialGetByte(quint8 port_num, uint8_t* data);
public slots:
virtual void init();
virtual void start(const char * filename = nullptr, bool tests = true);
virtual void stop();
virtual void setSdPath(const QString & sdPath = "", const QString & settingsPath = "");
virtual void setVolumeGain(const int value);
virtual void setRadioData(const QByteArray & data);
virtual void setAnalogValue(uint8_t index, int16_t value);
virtual void setKey(uint8_t key, bool state);
virtual void setSwitch(uint8_t swtch, int8_t state);
virtual void setTrim(unsigned int idx, int value);
virtual void setTrimSwitch(uint8_t trim, bool state);
virtual void setTrainerInput(unsigned int inputNumber, int16_t value);
virtual void setInputValue(int type, uint8_t index, int16_t value);
virtual void rotaryEncoderEvent(int steps);
virtual void touchEvent(int type, int x, int y);
virtual void lcdFlushed();
virtual void setTrainerTimeout(uint16_t ms);
virtual void sendTelemetry(const uint8_t module, const uint8_t protocol, const QByteArray data);
virtual void sendInternalModuleTelemetry(const uint8_t protocol, const QByteArray data);
virtual void sendExternalModuleTelemetry(const uint8_t protocol, const QByteArray data);
virtual void setLuaStateReloadPermanentScripts();
virtual void addTracebackDevice(QIODevice * device);
virtual void removeTracebackDevice(QIODevice * device);
virtual void receiveAuxSerialData(const quint8 port_num, const QByteArray & data);
protected slots:
void run();
protected:
bool isStopRequested();
void setStopRequested(bool stop);
bool checkLcdChanged();
void checkOutputsChanged();
void checkFuncSwitchChanged();
uint8_t getStickMode();
const char * getPhaseName(unsigned int phase);
const QString getCurrentPhaseName();
const char * getError();
QString simuSdDirectory;
QString simuSettingsDirectory;
QTimer * m_timer10ms;
QMutex m_mtxStopReq;
QMutex m_mtxSimuMain;
QMutex m_mtxRadioData;
QMutex m_mtxSettings;
QMutex m_mtxTbDevices;
int volumeGain;
bool m_resetOutputsData;
bool m_stopRequested;
};
+22 -5
View File
@@ -38,6 +38,10 @@
#include "hal/rotary_encoder.h"
#include "hal/switch_driver.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#if !SDL_VERSION_ATLEAST(2,0,19)
#error This backend requires SDL 2.0.19+ because of SDL_RenderGeometryRaw() function
#endif
@@ -62,7 +66,7 @@
#include "display.h"
#include "simuaudio.h"
#include "simpgmspace.h"
#include "simulib.h"
#include "hal/key_driver.h"
#include "hal/switch_driver.h"
@@ -83,9 +87,11 @@ static SDL_Texture* screen_frame_buffer;
static GimbalState stick_left = {{0.5f, 0.5f}, false};
static GimbalState stick_right = {{0.5f, 0.5f}, false};
#if !defined(__EMSCRIPTEN__)
static const unsigned char _icon_png[] = {
#include "icon.lbm"
};
#endif
#if defined(ROTARY_ENCODER_NAVIGATION)
extern volatile rotenc_t rotencValue;
@@ -292,7 +298,6 @@ static SDL_Texture* LoadTexture(SDL_Renderer* renderer, const unsigned char* pix
return texture;
}
static float switches_width()
{
const int max_sw_line = MAX_SWITCHES / 2;
@@ -731,10 +736,12 @@ int main(int argc, char* argv[])
SDL_GetRendererInfo(renderer, &info);
SDL_Log("Current SDL_Renderer: %s", info.name);
#if !defined(__EMSCRIPTEN__)
SDL_Surface* icon = LoadImage(_icon_png, sizeof(_icon_png));
if (window && icon) {
SDL_SetWindowIcon(window, icon);
}
#endif
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
@@ -779,8 +786,9 @@ int main(int argc, char* argv[])
// Init simulation
simuInit();
simuStart(true, args.getStoragePath().c_str(),
args.getSettingsPath().c_str());
simuFatfsSetPaths(args.getStoragePath().c_str(),
args.getSettingsPath().c_str());
simuStart();
// Main loop
SDL_SetEventFilter([](void*, SDL_Event* event){
@@ -792,6 +800,9 @@ int main(int argc, char* argv[])
return 1;
}, NULL);
#if defined(__EMSCRIPTEN__)
emscripten_set_main_loop([]() { handleEvents(); }, 0, true);
#else
do {
Uint64 start_ts = SDL_GetPerformanceCounter();
if (!handleEvents()) break;
@@ -804,6 +815,7 @@ int main(int argc, char* argv[])
SDL_Delay(std::max(0,(int32_t)floor(16.666f - elapsedMS)));
} while(true);
#endif
// App cleanup
simuStop();
@@ -816,14 +828,16 @@ int main(int argc, char* argv[])
SDL_DestroyTexture(screen_frame_buffer);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
#if !defined(__EMSCRIPTEN__)
SDL_FreeSurface(icon);
#endif
SDL_CloseAudio();
SDL_Quit();
return 0;
}
uint16_t simu_get_analog(uint8_t idx)
uint16_t simuGetAnalog(uint8_t idx)
{
auto max_sticks = adcGetMaxInputs(ADC_INPUT_MAIN);
if (idx < max_sticks) {
@@ -857,3 +871,6 @@ uint16_t simu_get_analog(uint8_t idx)
// probably RTC_BAT
return 0;
}
void simuTrace(const char* text) {}
void simuLcdNotify() {}
-69
View File
@@ -1,69 +0,0 @@
/*
* 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 <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stddef.h>
#include <errno.h>
#include <string>
#define __disable_irq()
#define __enable_irq()
extern char * main_thread_error;
uint64_t simuTimerMicros(void);
void simuSetKey(uint8_t key, bool state);
void simuSetTrim(uint8_t trim, bool state);
void simuSetSwitch(uint8_t swtch, int8_t state);
#if defined(__cplusplus)
void simuInit();
void simuStart(bool tests = true, const char * sdPath = nullptr, const char * settingsPath = nullptr);
void simuStop();
bool simuIsRunning();
void startEepromThread(const char * filename = "eeprom.bin");
void stopEepromThread();
#endif
void simuMain();
void simuFatfsSetPaths(const char * sdPath, const char * settingsPath);
std::string simuFatfsGetCurrentPath();
std::string simuFatfsGetRealPath(const std::string &p);
#if defined(TRACE_SIMPGMSPACE)
#undef TRACE_SIMPGMSPACE
#define TRACE_SIMPGMSPACE TRACE
#else
#define TRACE_SIMPGMSPACE(...)
#endif
#if defined(HARDWARE_TOUCH)
extern struct TouchState simTouchState;
extern bool simTouchOccured;
#endif
+1
View File
@@ -21,6 +21,7 @@
#include "audio.h"
#include "simuaudio.h"
#include "simulib.h"
#include <stdint.h>
#include <SDL.h>
-3
View File
@@ -21,9 +21,6 @@
#pragma once
#include <stdint.h>
bool simuAudioInit();
void simuAudioDeInit();
void simuQueueAudio(const uint8_t* data, uint32_t len);
int simuAudioGetVolume();
+38 -66
View File
@@ -19,7 +19,7 @@
* GNU General Public License for more details.
*/
#include "simpgmspace.h"
#include "simulib.h"
#if !defined(SIMU_DISKIO)
@@ -31,6 +31,7 @@
#include <filesystem>
#include <fstream>
#include <memory>
#include <optional>
#include <string>
namespace fs = std::filesystem;
@@ -200,22 +201,17 @@ FRESULT file_stat(const std::string& realPath, FILINFO* fno)
// Get last write time
auto ftime = fs::last_write_time(fsPath, ec);
if (!ec) {
try {
auto systime = ftime_to_systime(ftime);
auto time_t_val = std::chrono::system_clock::to_time_t(systime);
struct tm* ltime = localtime(&time_t_val);
auto systime = ftime_to_systime(ftime);
auto time_t_val = std::chrono::system_clock::to_time_t(systime);
struct tm* ltime = localtime(&time_t_val);
if (ltime) {
// Convert to FatFs format
fno->fdate = ((ltime->tm_year - 80) << 9) |
((ltime->tm_mon + 1) << 5) | ltime->tm_mday;
fno->ftime = (ltime->tm_hour << 11) | (ltime->tm_min << 5) |
(ltime->tm_sec / 2);
} else {
fno->fdate = 0;
fno->ftime = 0;
}
} catch (...) {
if (ltime) {
// Convert to FatFs format
fno->fdate = ((ltime->tm_year - 80) << 9) | ((ltime->tm_mon + 1) << 5) |
ltime->tm_mday;
fno->ftime =
(ltime->tm_hour << 11) | (ltime->tm_min << 5) | (ltime->tm_sec / 2);
} else {
fno->fdate = 0;
fno->ftime = 0;
}
@@ -286,16 +282,12 @@ FRESULT f_open(FIL* fil, const TCHAR* name, BYTE flag)
}
}
try {
auto simuFil = new _simu_FIL(realPath, mode);
if (simuFil->stream->is_open()) {
fil->obj.fs = reinterpret_cast<FATFS*>(simuFil);
return FR_OK;
} else {
delete simuFil;
return FR_INVALID_NAME;
}
} catch (...) {
auto simuFil = new _simu_FIL(realPath, mode);
if (simuFil->stream->is_open()) {
fil->obj.fs = reinterpret_cast<FATFS*>(simuFil);
return FR_OK;
} else {
delete simuFil;
return FR_INVALID_NAME;
}
}
@@ -408,35 +400,20 @@ struct _simu_DIR {
if (ec) {
iter = end_iter; // Set to end if error
}
skipDotEntries(); // TODO: check if necessary
}
private:
void skipDotEntries()
{
while (iter != end_iter) {
std::string filename = iter->path().filename().string();
if (filename != "." && filename != "..") {
break;
}
std::error_code ec;
++iter;
}
}
public:
bool hasNext() const { return iter != end_iter; }
fs::directory_entry getNext()
std::optional<fs::directory_entry> getNext()
{
if (iter == end_iter) {
throw std::runtime_error("No more entries");
return std::nullopt;
}
auto current = *iter;
std::error_code ec;
++iter;
skipDotEntries();
return current;
}
@@ -452,13 +429,8 @@ FRESULT f_opendir(DIR* rep, const TCHAR* name)
return FR_NO_PATH;
}
try {
rep->obj.fs = reinterpret_cast<FATFS*>(new _simu_DIR(path));
return FR_OK;
} catch (...) {
rep->obj.fs = nullptr;
return FR_NO_PATH;
}
rep->obj.fs = reinterpret_cast<FATFS*>(new _simu_DIR(path));
return FR_OK;
}
FRESULT f_closedir(DIR* rep)
@@ -479,24 +451,24 @@ FRESULT f_readdir(DIR* rep, FILINFO* fil)
return FR_NO_FILE;
}
try {
auto entry = sd->getNext();
if (fil != nullptr) {
memset(fil->fname, 0, FF_MAX_LFN);
std::string filename = entry.path().filename().string();
size_t copyLen = std::min(filename.length(), size_t(FF_MAX_LFN - 1));
strncpy(fil->fname, filename.c_str(), copyLen);
std::string fullPath = entry.path().string();
return file_stat(fullPath, fil);
}
return FR_OK;
} catch (...) {
auto maybe_entry = sd->getNext();
if (!maybe_entry.has_value()) {
return FR_NO_FILE;
}
auto entry = maybe_entry.value();
if (fil != nullptr) {
memset(fil->fname, 0, FF_MAX_LFN);
std::string filename = entry.path().filename().string();
size_t copyLen = std::min(filename.length(), size_t(FF_MAX_LFN - 1));
strncpy(fil->fname, filename.c_str(), copyLen);
std::string fullPath = entry.path().string();
return file_stat(fullPath, fil);
}
return FR_OK;
}
FRESULT f_mkfs(const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len)
+9 -5
View File
@@ -21,6 +21,7 @@
#include "lcd.h"
#include "simulcd.h"
#include "simulib.h"
#include "rtos.h"
#include <string.h>
#include <utility>
@@ -41,10 +42,11 @@ void lcdInit() {}
void lcdRefresh()
{
// Mark screen dirty for async refresh
simuLcdRefresh = true;
memcpy(simuLcdBuf, displayBuf, DISPLAY_BUFFER_SIZE * sizeof(pixel_t));
// Mark screen dirty and notify host for async refresh
simuLcdRefresh = true;
simuLcdNotify();
}
#else
@@ -102,8 +104,9 @@ static void simuRefreshLcd(lv_disp_drv_t * disp_drv, uint16_t *buffer, const rec
// simply set LVGL's buffer as our current frame buffer
simuLcdBuf = buffer;
// Trigger async refresh
// Trigger async refresh and notify host
simuLcdRefresh = true;
simuLcdNotify();
#else
_copy_area(simuLcdBackBuf, buffer, copy_area);
@@ -118,8 +121,9 @@ static void simuRefreshLcd(lv_disp_drv_t * disp_drv, uint16_t *buffer, const rec
simuLcdBackBuf = _LCD_BUF2;
}
// Trigger async refresh
// Trigger async refresh and notify host
simuLcdRefresh = true;
simuLcdNotify();
// Copy refreshed & rotated areas into new back buffer
uint16_t* src = simuLcdBuf;
@@ -19,29 +19,31 @@
* GNU General Public License for more details.
*/
#include "board.h"
#define SIMPGMSPC_USE_QT 0
#include "edgetx.h"
#include "simulib.h"
#include "simulcd.h"
#include "hal/adc_driver.h"
#include "hal/rotary_encoder.h"
#include "hal/usb_driver.h"
#include "hal/audio_driver.h"
#include "os/sleep.h"
#include "os/task.h"
#include "os/timer_native_impl.h"
#include <errno.h>
#include <stdarg.h>
#include <string>
#if !defined (_MSC_VER) || defined (__GNUC__)
#include <chrono>
#include <sys/time.h>
#include "edgetx.h"
#include "debug.h"
#include "switches.h"
#include "input_mapping.h"
#if defined(GVARS)
#include "gvars.h"
#endif
#include "trainer.h"
#include "telemetry/frsky.h"
#include "telemetry/crossfire.h"
#if defined(LUA)
#include "lua/lua_api.h"
#endif
#include <assert.h>
int g_snapshot_idx = 0;
@@ -61,32 +63,10 @@ rotenc_t rotaryEncoderGetValue()
return rotencValue / ROTARY_ENCODER_GRANULARITY;
}
// TODO: remove all STM32 defs
extern const etx_hal_adc_driver_t simu_adc_driver;
void lcdCopy(void * dest, void * src);
uint64_t simuTimerMicros(void)
{
auto now = std::chrono::steady_clock::now();
return (uint64_t) std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()).count();
}
uint16_t getTmr16KHz()
{
return simuTimerMicros() * 2 / 125;
}
uint16_t getTmr2MHz()
{
return simuTimerMicros() * 2;
}
uint32_t timersGetMsTick(void)
{
return simuTimerMicros() / 1000;
}
void lcdFlushed();
void simuInit()
{
@@ -94,6 +74,9 @@ void simuInit()
rotencValue = 0;
#endif
// Route firmware TRACE() output to host via WASM import
traceCallback = simuTrace;
// Init ADC driver callback
adcInit(&simu_adc_driver);
// Switches
@@ -103,7 +86,6 @@ void simuInit()
bool keysStates[MAX_KEYS] = { false };
void simuSetKey(uint8_t key, bool state)
{
// TRACE("simuSetKey(%d, %d)", key, state);
assert(key < DIM(keysStates));
keysStates[key] = state;
}
@@ -111,7 +93,6 @@ void simuSetKey(uint8_t key, bool state)
bool trimsStates[MAX_TRIMS * 2] = { false };
void simuSetTrim(uint8_t trim, bool state)
{
// TRACE("simuSetTrim(%d, %d)", trim, state);
assert(trim < DIM(trimsStates));
trimsStates[trim] = state;
}
@@ -125,7 +106,7 @@ static void* bootloaderThread(void*)
}
#endif
void simuStart(bool tests, const char * sdPath, const char * settingsPath)
void simuStart(bool tests)
{
if (simu_running)
return;
@@ -137,8 +118,6 @@ void simuStart(bool tests, const char * sdPath, const char * settingsPath)
startOptions = (tests ? 0 : OPENTX_START_NO_SPLASH | OPENTX_START_NO_CALIBRATION | OPENTX_START_NO_CHECKS);
simu_shutdown = false;
simuFatfsSetPaths(sdPath, settingsPath);
/*
g_tmr10ms must be non-zero otherwise some SF functions (that use this timer as a marker when it was last executed)
will be executed twice on startup. Normal radio does not see this issue because g_tmr10ms is already a big number
@@ -217,6 +196,42 @@ bool simuIsRunning()
return simu_running;
}
bool simuLcdChanged()
{
bool changed = simuLcdRefresh;
simuLcdRefresh = false;
return changed;
}
uint32_t simuLcdCopy(uint8_t* buf, uint32_t maxLen)
{
uint32_t size = DISPLAY_BUFFER_SIZE * sizeof(pixel_t);
if (size > maxLen) size = maxLen;
memcpy(buf, simuLcdBuf, size);
return size;
}
uint32_t simuLcdGetWidth()
{
return LCD_W;
}
uint32_t simuLcdGetHeight()
{
return LCD_H;
}
uint32_t simuLcdGetDepth()
{
#if defined(COLORLCD)
return 16;
#elif LCD_W == 212
return 4;
#else
return 1;
#endif
}
#if !defined(COLORLCD)
void lcdSetRefVolt(uint8_t val)
{
@@ -278,7 +293,6 @@ uint32_t readKeys()
for (int i = 0; i < MAX_KEYS; i++) {
if (keysStates[i]) {
// TRACE("key pressed %d", i);
result |= 1 << i;
}
}
@@ -292,7 +306,6 @@ uint32_t readTrims()
for (int i = 0; i < keysGetMaxTrims() * 2; i++) {
if (trimsStates[i]) {
// TRACE("trim pressed %d", i);
trims |= 1 << i;
}
}
@@ -508,3 +521,238 @@ struct TouchState getInternalTouchState()
return simTouchState;
}
#endif
void simuTouchDown(int16_t x, int16_t y)
{
#if defined(HARDWARE_TOUCH)
touchPanelDown(x, y);
#endif
}
void simuTouchUp()
{
#if defined(HARDWARE_TOUCH)
touchPanelUp();
#endif
}
void simuRotaryEncoderEvent(int32_t steps)
{
#if defined(ROTARY_ENCODER_NAVIGATION)
rotencValue += steps * ROTARY_ENCODER_GRANULARITY;
#endif
}
int32_t simuGetCapability(uint8_t cap)
{
switch (cap) {
case 0: // CAP_LUA
#ifdef LUA
return 1;
#else
return 0;
#endif
case 1: // CAP_ROTARY_ENC
return 0;
case 2: // CAP_ROTARY_ENC_NAV
#ifdef ROTARY_ENCODER_NAVIGATION
return 1;
#else
return 0;
#endif
case 3: // CAP_TELEM_FRSKY_SPORT
return 1;
case 4: // CAP_SERIAL_AUX1
return (auxSerialGetPort(SP_AUX1) != nullptr) ? 1 : 0;
case 5: // CAP_SERIAL_AUX2
return (auxSerialGetPort(SP_AUX2) != nullptr) ? 1 : 0;
default:
return 0;
}
}
void simuSetTrimValue(uint8_t idx, int32_t value)
{
unsigned i = inputMappingConvertMode(idx);
uint8_t phase = getTrimFlightMode(getFlightMode(), i);
setTrimValue(phase, i, value);
}
void simuSendTelemetry(uint8_t module, uint8_t protocol,
const uint8_t* data, uint32_t len)
{
switch (protocol) {
case 0: // SIMU_TELEMETRY_PROTOCOL_FRSKY_SPORT
sportProcessTelemetryPacket(module, data, len);
break;
case 1: // SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB
frskyDProcessPacket(module, data, len);
break;
case 2: // SIMU_TELEMETRY_PROTOCOL_CROSSFIRE
processCrossfireTelemetryFrame(module, (uint8_t*)data, len);
break;
case 3: // SIMU_TELEMETRY_PROTOCOL_FRSKY_HUB_OOB
if (len >= 3) {
uint8_t id = data[0];
int16_t value = ((uint8_t)(data[2]) << 8) + (uint8_t)(data[1]);
processHubPacket(id, value);
}
break;
default:
break;
}
}
void simuLuaReloadPermanentScripts()
{
#if defined(LUA)
luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
#endif
}
void simuLcdFlushed()
{
::lcdFlushed();
}
uint8_t simuGetMaxTrainerChannels()
{
return MAX_TRAINER_CHANNELS;
}
void simuCopyTrainerInput(const int16_t* buf, uint8_t count)
{
if (count > MAX_TRAINER_CHANNELS)
count = MAX_TRAINER_CHANNELS;
for (uint8_t i = 0; i < count; i++) {
int16_t v = buf[i];
if (v < -512) v = -512;
if (v > 512) v = 512;
trainerInput[i] = v;
}
}
void simuSetTrainerTimeout(uint16_t ms)
{
trainerSetTimer(ms / 10);
}
// -- Output values --
uint8_t simuGetNumChannels()
{
return MAX_OUTPUT_CHANNELS;
}
uint8_t simuCopyChannelOutputs(int16_t* buf, uint8_t maxCount)
{
uint8_t n = MAX_OUTPUT_CHANNELS < maxCount ? MAX_OUTPUT_CHANNELS : maxCount;
memcpy(buf, channelOutputs, n * sizeof(int16_t));
return n;
}
uint8_t simuCopyMixOutputs(int16_t* buf, uint8_t maxCount)
{
uint8_t n = MAX_OUTPUT_CHANNELS < maxCount ? MAX_OUTPUT_CHANNELS : maxCount;
memcpy(buf, ex_chans, n * sizeof(int16_t));
return n;
}
uint8_t simuGetNumLogicalSwitches()
{
return MAX_LOGICAL_SWITCHES;
}
uint8_t simuCopyLogicalSwitches(uint8_t* buf, uint8_t maxCount)
{
uint8_t n = MAX_LOGICAL_SWITCHES < maxCount ? MAX_LOGICAL_SWITCHES : maxCount;
for (uint8_t i = 0; i < n; i++)
buf[i] = getSwitch(SWSRC_FIRST_LOGICAL_SWITCH + i, 0) ? 1 : 0;
return n;
}
int32_t simuGetTrimValue(uint8_t idx)
{
uint8_t phase = getFlightMode();
uint8_t mapped = inputMappingConvertMode(idx);
return getTrimValue(getTrimFlightMode(phase, mapped), mapped);
}
int16_t simuGetTrimRange()
{
return g_model.extendedTrims ? TRIM_EXTENDED_MAX : TRIM_MAX;
}
int32_t simuGetFlightMode()
{
return getFlightMode();
}
uint8_t simuGetNumGVars()
{
#if defined(GVARS)
return MAX_GVARS;
#else
return 0;
#endif
}
uint8_t simuGetNumFlightModes()
{
return MAX_FLIGHT_MODES;
}
int32_t simuGetGVar(uint8_t gv, uint8_t fm)
{
#if defined(GVARS)
if (gv < MAX_GVARS && fm < MAX_FLIGHT_MODES) {
uint8_t prec = g_model.gvars[gv].prec;
uint8_t unit = g_model.gvars[gv].unit;
int16_t value = (int16_t)GVAR_VALUE(gv, getGVarFlightMode(fm, gv));
// Encode as gVarMode_t: value[15:0] | mode[23:16] | prec[25:24] | unit[27:26]
return (((unit & 0x3) << 26) | ((prec & 0x3) << 24) |
((fm & 0xFF) << 16) | (value & 0xFFFF));
}
#endif
return 0;
}
bool simuGetBacklightState()
{
return isBacklightEnabled();
}
// -- Custom (function) switches --
uint8_t simuGetNumCustomSwitches()
{
return NUM_FUNCTIONS_SWITCHES;
}
uint8_t simuGetCustomSwitchIndex(uint8_t cfsIdx)
{
#if defined(FUNCTION_SWITCHES)
return switchGetSwitchFromCustomIdx(cfsIdx);
#else
(void)cfsIdx;
return 0;
#endif
}
bool simuGetCustomSwitchState(uint8_t idx)
{
#if defined(FUNCTION_SWITCHES)
if (idx < NUM_FUNCTIONS_SWITCHES)
return fsLedState(idx);
#endif
return false;
}
uint32_t simuGetCustomSwitchColor(uint8_t idx)
{
#if defined(FUNCTION_SWITCHES)
if (idx < NUM_FUNCTIONS_SWITCHES)
return fsGetLedRGB(idx);
#endif
return 0;
}
+168
View File
@@ -0,0 +1,168 @@
/*
* 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 <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <string>
#if __wasm__
#define WASM_EXPORT_AS(name) __attribute__((export_name(name)))
#define WASM_EXPORT(symbol) WASM_EXPORT_AS(#symbol) symbol
#define WASM_IMPORT_AS(name) __attribute__((import_name(name)))
#define WASM_IMPORT(symbol) WASM_IMPORT_AS(#symbol) symbol
#else
#define WASM_EXPORT_AS(name)
#define WASM_EXPORT(symbol) symbol
#define WASM_IMPORT_AS(name)
#define WASM_IMPORT(symbol) symbol
#endif
// -- WASM exports (called by host) --
// Lifecycle: call simuInit() once, then simuFatfsSetPaths() + simuStart().
// Poll simuIsRunning() periodically. Call simuStop() to shut down.
void WASM_EXPORT(simuInit)();
void WASM_EXPORT(simuStart)(bool tests = true);
void WASM_EXPORT(simuStop)();
bool WASM_EXPORT(simuIsRunning)();
// Set SD card and settings paths before simuStart() to avoid STORAGE WARNING.
void WASM_EXPORT(simuFatfsSetPaths)(const char * sdPath, const char * settingsPath);
// Input: keys use Board::Keys enum, switches use Board switch indices,
// trims use Board::TrimSwitches enum (momentary press, not value).
void WASM_EXPORT(simuSetKey)(uint8_t key, bool state);
void WASM_EXPORT(simuSetTrim)(uint8_t trim, bool state);
void WASM_EXPORT(simuSetSwitch)(uint8_t swtch, int8_t state);
// Touch: call simuTouchDown(x,y) on press and repeatedly during drag
// (continuous position updates), then simuTouchUp() on release.
// The firmware detects slides from successive position changes.
void WASM_EXPORT(simuTouchDown)(int16_t x, int16_t y);
void WASM_EXPORT(simuTouchUp)();
// LCD: the firmware calls simuLcdNotify() (import) when a new frame is ready.
// The host waits for this notification, then calls simuLcdCopy() to read the
// framebuffer and simuLcdFlushed() to signal that the buffer can be reused.
// simuLcdChanged() is kept for backward compatibility (Companion polling).
// Depth is bits per pixel (1, 4, or 16).
bool WASM_EXPORT(simuLcdChanged)();
uint32_t WASM_EXPORT(simuLcdCopy)(uint8_t* buf, uint32_t maxLen);
uint32_t WASM_EXPORT(simuLcdGetWidth)();
uint32_t WASM_EXPORT(simuLcdGetHeight)();
uint32_t WASM_EXPORT(simuLcdGetDepth)();
// Rotary encoder: positive steps = clockwise. The firmware handles
// mode inversion and key translation internally.
void WASM_EXPORT(simuRotaryEncoderEvent)(int32_t steps);
// Capabilities: cap values match SimulatorInterface::Capability enum
// (0=LUA, 1=ROTARY_ENC, 2=ROTARY_ENC_NAV, 3=TELEM_FRSKY_SPORT,
// 4=SERIAL_AUX1, 5=SERIAL_AUX2). Returns 0 or 1.
int32_t WASM_EXPORT(simuGetCapability)(uint8_t cap);
int WASM_EXPORT(simuAudioGetVolume)();
// Backlight state: returns true if backlight is currently enabled.
bool WASM_EXPORT(simuGetBacklightState)();
// Custom (function) switches: LED state and RGB color.
// idx is the custom switch index (0-based), not the global switch index.
// Use simuGetNumCustomSwitches() to get the count.
uint8_t WASM_EXPORT(simuGetNumCustomSwitches)();
uint8_t WASM_EXPORT(simuGetCustomSwitchIndex)(uint8_t cfsIdx);
bool WASM_EXPORT(simuGetCustomSwitchState)(uint8_t idx);
uint32_t WASM_EXPORT(simuGetCustomSwitchColor)(uint8_t idx);
// Value-based trim setting: idx is mapped through inputMappingConvertMode.
void WASM_EXPORT(simuSetTrimValue)(uint8_t idx, int32_t value);
// Telemetry injection: protocol matches SIMU_TELEMETRY_PROTOCOL_* enum.
// module: 0=internal, 1=external.
void WASM_EXPORT(simuSendTelemetry)(uint8_t module, uint8_t protocol,
const uint8_t* data, uint32_t len);
// Lua: trigger reload of permanent scripts.
void WASM_EXPORT(simuLuaReloadPermanentScripts)();
// LCD: notify firmware that host has consumed the LCD buffer.
void WASM_EXPORT(simuLcdFlushed)();
// Trainer input: bulk copy values (-512..512) into trainerInput[].
// Returns the max number of channels.
uint8_t WASM_EXPORT(simuGetMaxTrainerChannels)();
void WASM_EXPORT(simuCopyTrainerInput)(const int16_t* buf, uint8_t count);
void WASM_EXPORT(simuSetTrainerTimeout)(uint16_t ms);
// Output values: polled periodically by host (every ~50ms).
// Bulk copy channel outputs into buf (int16_t[]). Returns channel count.
uint8_t WASM_EXPORT(simuGetNumChannels)();
uint8_t WASM_EXPORT(simuCopyChannelOutputs)(int16_t* buf, uint8_t maxCount);
uint8_t WASM_EXPORT(simuCopyMixOutputs)(int16_t* buf, uint8_t maxCount);
// Bulk copy logical switch states into buf (uint8_t[], 0 or 1). Returns count.
uint8_t WASM_EXPORT(simuGetNumLogicalSwitches)();
uint8_t WASM_EXPORT(simuCopyLogicalSwitches)(uint8_t* buf, uint8_t maxCount);
// Trim values. idx 0..TRIM_AXIS_COUNT-1 (typically 8).
// Returns the trim value for the current flight mode.
int32_t WASM_EXPORT(simuGetTrimValue)(uint8_t idx);
int16_t WASM_EXPORT(simuGetTrimRange)();
// Flight mode: returns current flight mode index (0-based).
int32_t WASM_EXPORT(simuGetFlightMode)();
// GVars: gv 0..N-1, fm 0..M-1. Returns encoded gVarMode_t.
uint8_t WASM_EXPORT(simuGetNumGVars)();
uint8_t WASM_EXPORT(simuGetNumFlightModes)();
int32_t WASM_EXPORT(simuGetGVar)(uint8_t gv, uint8_t fm);
// -- WASM imports (provided by host) --
// simuGetAnalog: return ADC-range value for input at index idx.
// All inputs use 0..4096 range (center=2048), same as SDL simulator.
uint16_t WASM_IMPORT(simuGetAnalog)(uint8_t idx);
void WASM_IMPORT(simuQueueAudio)(const uint8_t* buf, uint32_t len);
// simuTrace: send debug/trace output text to the host for display.
// Called via traceCallback from debugPrintf().
void WASM_IMPORT(simuTrace)(const char* text);
// simuLcdNotify: signal the host that the LCD framebuffer has been updated
// and is ready to be copied. Called from lcdRefresh() (mono) and the LVGL
// flush callback (color). On the host side this wakes an Atomics.waitAsync
// listener so the frame can be rendered without polling.
void WASM_IMPORT(simuLcdNotify)();
// -- Internal (not exported) --
void simuMain();
std::string simuFatfsGetCurrentPath();
std::string simuFatfsGetRealPath(const std::string& p);
#if defined(HARDWARE_TOUCH)
extern struct TouchState simTouchState;
extern bool simTouchOccured;
#endif
+2
View File
@@ -19,6 +19,8 @@
* GNU General Public License for more details.
*/
#include "simulib.h"
#include "hal/switch_driver.h"
#include "definitions.h"
#include "myeeprom.h"
-1
View File
@@ -18,7 +18,6 @@ file(GLOB TEST_SRC_FILES ${RADIO_SRC_DIR}/tests/*.cpp
CONFIGURE_DEPENDS "${RADIO_SRC_DIR}/tests/*.cpp")
set(TEST_SRC_FILES ${TEST_SRC_FILES}
${CMAKE_CURRENT_SOURCE_DIR}/location.h
${SIMU_SRC}
)
+4 -6
View File
@@ -122,12 +122,10 @@ class TersePrinter : public EmptyTestEventListener {
int32_t lastAct = 0;
uint16_t simu_get_analog(uint8_t idx)
{
return 0;
}
void simuQueueAudio(const uint8_t*, uint32_t) {}
uint16_t simuGetAnalog(uint8_t) { return 0; }
void simuQueueAudio(const uint8_t *, uint32_t) {}
void simuTrace(const char* text) {}
void simuLcdNotify() {}
static char _stringResult[200];
+1
View File
@@ -25,6 +25,7 @@
#include <gtest/gtest.h>
#include "edgetx.h"
#include "simulib.h"
#include "model_init.h"
#include "switches.h"
#include "hal/switch_driver.h"
+1 -1
View File
@@ -25,7 +25,7 @@
#include <gtest/gtest.h>
#if !defined(COLORLCD)
#include "simpgmspace.h"
#include "simulib.h"
#include "simulcd.h"
#include "common/stdlcd/draw_functions.h"
+1 -1
View File
@@ -24,7 +24,7 @@
#if defined(COLORLCD)
#include "simpgmspace.h"
#include "simulib.h"
#include "bitmaps.h"
#include "debug.h"
+4
View File
@@ -20,6 +20,10 @@
// #include "user_config.h"
#if __wasm__
#define l_signalT long
#endif
// prevent localeconv()
#define getlocaledecpoint() ('.')
+5
View File
@@ -0,0 +1,5 @@
clang
jinja2
pillow
lz4
pydantic
+44 -229
View File
@@ -1,7 +1,7 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/build-common.sh"
. "$SCRIPT_DIR/build-common.sh"
SRCDIR=$1
OUTDIR=$2
@@ -14,23 +14,24 @@ if [[ -z ${OUTDIR} ]]; then
OUTDIR="$(pwd)/output"
fi
if [[ ! -d "${OUTDIR}" ]]; then
mkdir -p "${OUTDIR}"
fi
# Determine parallel jobs
determine_max_jobs
# Create a master log file for the entire build process
MASTER_LOG="build-summary.log"
QUIET_FLAGS=""
if [[ "$CMAKE_GENERATOR" == "Ninja" ]]; then
QUIET_FLAGS="-- --quiet"
QUIET_FLAGS="-- --quiet"
else
# Assume Makefile generator for non-Ninja builds
COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_RULE_MESSAGES=OFF"
# Assume Makefile generator for non-Ninja builds
COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_RULE_MESSAGES=OFF"
fi
COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_BUILD_TYPE=Release -DCMAKE_MESSAGE_LOG_LEVEL=WARNING -Wno-dev -DGVARS=YES -DHELI=YES -DLUA=YES"
COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_BUILD_TYPE=Release -DCMAKE_MESSAGE_LOG_LEVEL=WARNING -Wno-dev"
if [ "$(uname)" = "Darwin" ]; then
COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_OSX_DEPLOYMENT_TARGET='10.15'"
COMMON_OPTIONS="${COMMON_OPTIONS} -DCMAKE_OSX_DEPLOYMENT_TARGET='11.0'"
fi
# Generate EDGETX_VERSION_SUFFIX if not already set
@@ -53,103 +54,6 @@ fi
rm -rf build && mkdir build && cd build
# Function to output error logs (works in both GitHub Actions and terminal)
output_error_log() {
local log_file="$1"
local context="$2"
if [[ -f "$log_file" ]]; then
echo "------------------------------------------"
echo " Full error output from $log_file ($context):"
echo "------------------------------------------"
cat "$log_file"
echo "------------------------------------------"
# Also append to master log for aggregation
{
echo "=== Error from $log_file ($context) ==="
cat "$log_file"
echo "=== End of $log_file ==="
} >> "$MASTER_LOG"
else
echo "⚠️ Warning: Log file $log_file not found for $context"
echo "⚠️ Warning: Log file $log_file not found for $context" >> "$MASTER_LOG"
fi
}
# Function to show last N lines of a log file for warnings
show_log_summary() {
local log_file="$1"
local lines="${2:-50}"
local context="$3"
if [[ -f "$log_file" ]]; then
echo "------------------------------------------"
echo "Last $lines lines from $log_file:"
echo "------------------------------------------"
tail -n "$lines" "$log_file"
fi
}
run_pipeline() {
local pipeline_type="$1"
local log_file="${2:-/dev/null}"
local context="$3"
local show_details="${4:-false}"
local cmake_opts="${QUIET_FLAGS}"
case "$pipeline_type" in
"plugin")
clean_build
if ! execute_with_output "⚙️ Configuring" "cmake ${BUILD_OPTIONS} ${SRCDIR}" "$log_file" "$show_details"; then
output_error_log "$log_file" "$context (Configuration)"
return 1
fi
if ! execute_with_output "🔧 Native config" "cmake_build_parallel . --target native-configure ${cmake_opts}" "$log_file" "$show_details"; then
output_error_log "$log_file" "$context (Native Configure)"
return 1
fi
if ! execute_with_output "📦 Building lib" "cmake_build_parallel native --target libsimulator ${cmake_opts}" "$log_file" "$show_details"; then
output_error_log "$log_file" "$context (Library Build)"
return 1
fi
;;
"final")
if ! execute_with_output "🔧 Final config" "cmake_build_parallel . --target native-configure ${cmake_opts}" "$log_file" "$show_details"; then
output_error_log "$log_file" "Final Configuration"
return 1
fi
if ! execute_with_output "📦 Building companion" "cmake_build_parallel native --target companion ${cmake_opts}" "$log_file" "$show_details"; then
output_error_log "$log_file" "$context (Companion Build)"
return 1
fi
if ! execute_with_output "📦 Packaging" "cmake_build_parallel native --target ${PACKAGE_TARGET}" "$log_file" "true"; then
output_error_log "$log_file" "Final Packaging"
return 1
fi
;;
esac
return 0
}
execute_with_output() {
local description="$1"
local command="$2"
local log_file="$3"
local show_output="${4:-false}"
if [[ "$show_output" == "true" && "$log_file" != "/dev/null" ]]; then
echo " $description..."
fi
eval "$command" >> "$log_file" 2>&1
}
clean_build() {
rm -f CMakeCache.txt native/CMakeCache.txt
}
get_platform_config() {
local platform=$(uname)
case "$platform" in
@@ -174,139 +78,50 @@ get_platform_config() {
esac
}
# Enhanced plugin builder with better error handling
build_plugin() {
local plugin="$1"
local log_file="build_${plugin}.log"
local verbose="${2:-false}"
BUILD_OPTIONS="${COMMON_OPTIONS} "
if ! get_target_build_options "$plugin" >> "$log_file" 2>&1; then
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::error::Failed to get build options for $plugin"
fi
output_error_log "$log_file" "$plugin (Build Options)"
return 1
fi
# Only show detailed output in GitHub Actions or if verbose is requested
local show_details="false"
if [[ -n "$GITHUB_ACTIONS" || "$verbose" == "true" ]]; then
show_details="true"
fi
if ! run_pipeline "plugin" "$log_file" "$plugin" "$show_details"; then
return 1
fi
# Check for warnings and show summary if found
if grep -q -i "warning" "$log_file"; then
echo " ⚠️ $plugin completed with warnings"
if [[ "$show_details" == "true" ]]; then
show_log_summary "$log_file" 50 "$plugin (Warnings)"
fi
return 0
fi
echo " ✅ $plugin completed successfully"
return 0
}
declare -a simulator_plugins=(
x9lite x9lites x9d x9dp x9dp2019 x9e
x7 x7access
t8 t12 t12max tx12 tx12mk2 t15 t15pro t16 t18 t20 t20v2
xlite xlites
x10 x10express x12s
zorro tx16s tx16smk3 tx15
commando8 boxer pocket mt12 gx12
tlite tpro tprov2 tpros bumblebee lr3pro t14
nv14 el18 pl18 pl18ev pl18u st16 pa01
f16 v14 v16
)
get_platform_config
TOTAL=${#simulator_plugins[@]}
FAILED_PLUGINS=()
echo "🔨 Building $TOTAL Plugins for $PACKAGE_NAME"
for i in "${!simulator_plugins[@]}"; do
plugin="${simulator_plugins[$i]}"
current=$((i + 1))
percent=$((current * 100 / TOTAL))
# For terminal output, put each plugin on its own line for cleaner display
if [[ -n "$GITHUB_ACTIONS" ]]; then
printf "::group::%s %-12s [%2d/%2d] %3d%%\n" "$PACKAGE_EMOJI" "$plugin" "$current" "$TOTAL" "$percent"
else
echo "🔨 [$current/$TOTAL] ($percent%) Building $plugin..."
fi
error_status=0
if ! build_plugin "$plugin"; then
FAILED_PLUGINS+=("$plugin")
error_status=1
fi
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::endgroup::"
fi
if [ $error_status -ne 0 ]; then
# TODO: if not '--build-all' used
exit 1
fi
done
# Show summary of any failures (if using continue-on-error approach)
if [ ${#FAILED_PLUGINS[@]} -gt 0 ]; then
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::group::❌ Build Failures Summary"
else
echo "❌ Build Failures Summary"
echo "=========================================="
fi
echo "The following plugins failed to build:"
for failed_plugin in "${FAILED_PLUGINS[@]}"; do
echo " ❌ $failed_plugin"
done
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::endgroup::"
else
echo "=========================================="
fi
exit 1
fi
BUILD_OPTIONS="${COMMON_OPTIONS} -DEdgeTX_SUPERBUILD:BOOL=0 -DNATIVE_BUILD:BOOL=1"
LOG_FILE="build-companion.log"
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::group::📦 Final $PACKAGE_NAME Package"
echo "::group::📦 Building $PACKAGE_NAME"
else
echo "📦 Final $PACKAGE_NAME Package"
echo "📦 Building $PACKAGE_NAME"
echo "=========================================="
fi
error_status=0
if run_pipeline "final" "final.log" "companion" "true"; then
if cp native/$PACKAGE_FILES "${OUTDIR}" 2>/dev/null; then
echo " ✅ All builds completed successfully!"
echo " 📁 Package saved to: ${OUTDIR}"
else
echo " ❌ Failed to copy package files to output directory"
echo " 📁 Directory Contents:"
echo " ----------------------"
clean_build() {
rm -f CMakeCache.txt native/CMakeCache.txt
}
echo "Contents of native/ directory:"
ls -la native/ || echo "native/ directory not found"
echo "Looking for files matching: $PACKAGE_FILES"
find native/ -name "$PACKAGE_FILES" 2>/dev/null || echo "No matching files found"
error_status=1
fi
clean_build && mkdir -p native/plugins
# Copy WASM simulator modules if available
if [[ -d "${SRCDIR}/wasm-modules" ]]; then
cp "${SRCDIR}"/wasm-modules/*.wasm native/plugins/ 2>/dev/null && \
echo " 🔌 Copied WASM modules to plugins/" || true
fi
error_status=0
if ! cmake -B native -S "${SRCDIR}" --toolchain cmake/toolchain/native.cmake ${BUILD_OPTIONS} >> "$LOG_FILE" 2>&1; then
echo " ❌ CMake configuration failed"
cat "$LOG_FILE"
error_status=1
elif ! cmake_build_parallel native --target companion ${QUIET_FLAGS} >> "$LOG_FILE" 2>&1; then
echo " ❌ Companion build failed"
cat "$LOG_FILE"
error_status=1
elif ! cmake_build_parallel native --target ${PACKAGE_TARGET} >> "$LOG_FILE" 2>&1; then
echo " ❌ Packaging failed"
cat "$LOG_FILE"
error_status=1
elif cp native/$PACKAGE_FILES "${OUTDIR}" 2>/dev/null; then
echo " ✅ Build completed successfully!"
echo " 📁 Package saved to: ${OUTDIR}"
else
echo " ❌ Final packaging failed"
echo " ❌ Failed to copy package files to output directory"
ls -la native/ || echo "native/ directory not found"
error_status=1
fi
+243
View File
@@ -0,0 +1,243 @@
#!/bin/bash
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/build-common.sh"
SRCDIR=$1
OUTDIR=$2
if [[ -z ${SRCDIR} ]]; then
SRCDIR="$(pwd)"
fi
if [[ -z ${OUTDIR} ]]; then
OUTDIR="$(pwd)/output"
fi
if [[ ! -d "${OUTDIR}" ]]; then
mkdir -p "${OUTDIR}"
fi
QUIET_FLAGS=""
if [[ "$CMAKE_GENERATOR" == "Ninja" ]]; then
QUIET_FLAGS="-- --quiet"
else
# Assume Makefile generator for non-Ninja builds
COMMON_OPTIONS="-DCMAKE_RULE_MESSAGES=OFF"
fi
COMMON_OPTIONS+=" -DCMAKE_BUILD_TYPE=Release -DCMAKE_MESSAGE_LOG_LEVEL=WARNING -Wno-dev"
COMMON_OPTIONS+=" -DCMAKE_MODULE_PATH=/opt/wasi-sdk/share/cmake/"
COMMON_OPTIONS+=" -DEdgeTX_SUPERBUILD:BOOL=0 -DNATIVE_BUILD:BOOL=1"
# Generate EDGETX_VERSION_SUFFIX if not already set
if [[ -z ${EDGETX_VERSION_SUFFIX} ]]; then
gh_type=$(echo "$GITHUB_REF" | awk -F / '{print $2}') #heads|tags|pull
if [[ $gh_type = "tags" ]]; then
# tags: refs/tags/<tag_name>
gh_tag=${GITHUB_REF##*/}
export EDGETX_VERSION_TAG=$gh_tag
elif [[ $gh_type = "pull" ]]; then
# pull: refs/pull/<pr_number>/merge
gh_pull_number=PR$(echo "$GITHUB_REF" | awk -F / '{print $3}')
export EDGETX_VERSION_SUFFIX=$gh_pull_number
elif [[ $gh_type = "heads" ]]; then
# heads: refs/heads/<branch_name>
gh_branch=${GITHUB_REF##*/}
export EDGETX_VERSION_SUFFIX=$gh_branch
fi
fi
if [[ -n "$GITHUB_ACTIONS" ]]; then
MAX_JOBS=${MAX_JOBS:-3}
fi
BUILD_DIR="build/wasm"
# Function to output error logs (works in both GitHub Actions and terminal)
output_error_log() {
local log_file="$1"
local context="$2"
if [[ -f "$log_file" ]]; then
echo "------------------------------------------"
echo " Full error output from $log_file:"
echo "------------------------------------------"
cat "$log_file"
else
echo "⚠️ Warning: Log file $log_file not found for $context"
fi
}
# Function to show last N lines of a log file for warnings
show_log_summary() {
local log_file="$1"
local lines="${2:-50}"
local context="$3"
if [[ -f "$log_file" ]]; then
echo "------------------------------------------"
echo "Last $lines lines from $log_file:"
echo "------------------------------------------"
tail -n "$lines" "$log_file"
fi
}
run_pipeline() {
local log_file="${1:-/dev/null}"
local context="$2"
local show_details="${3:-false}"
local cmake_opts="--parallel ${MAX_JOBS} ${QUIET_FLAGS}"
local wasi_toolchain="/opt/wasi-sdk/share/cmake/wasi-sdk-pthread.cmake"
clean_build
if ! execute_with_output "🔧 CMake config" "cmake -S ${SRCDIR} -B ${BUILD_DIR} --toolchain ${wasi_toolchain} ${BUILD_OPTIONS}" "$log_file" "$show_details"; then
output_error_log "$log_file" "$context (Configuration)"
return 1
fi
if ! execute_with_output "📦 Building WASM module" "cmake --build ${BUILD_DIR} --target wasi-module ${cmake_opts}" "$log_file" "$show_details"; then
output_error_log "$log_file" "$context (Library Build)"
return 1
fi
return 0
}
execute_with_output() {
local description="$1"
local command="$2"
local log_file="$3"
local show_output="${4:-false}"
if [[ "$show_output" == "true" && "$log_file" != "/dev/null" ]]; then
echo " $description..."
fi
eval "$command" >> "$log_file" 2>&1
}
clean_build() {
rm -f "${BUILD_DIR}/CMakeCache.txt"
}
# Enhanced plugin builder with better error handling
build_plugin() {
local plugin="$1"
local log_file="${OUTDIR}/build_${plugin}.log"
local verbose="${2:-false}"
BUILD_OPTIONS="${COMMON_OPTIONS} "
if ! get_target_build_options "$plugin" >> "$log_file" 2>&1; then
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::error::Failed to get build options for $plugin"
fi
output_error_log "$log_file" "$plugin (Build Options)"
return 1
fi
# Only show detailed output in GitHub Actions or if verbose is requested
local show_details="false"
if [[ -n "$GITHUB_ACTIONS" || "$verbose" == "true" ]]; then
show_details="true"
fi
if ! run_pipeline "$log_file" "$plugin" "$show_details"; then
return 1
fi
# FLAVOUR may differ from target name (e.g. x9dp2019 -> x9d+2019),
# so copy whatever .wasm was produced.
cp ${BUILD_DIR}/edgetx-*-simulator.wasm "${OUTDIR}/" 2>/dev/null
# Check for warnings and show summary if found
if grep -q -i "warning" "$log_file"; then
echo " ⚠️ $plugin completed with warnings"
if [[ "$show_details" == "true" ]]; then
show_log_summary "$log_file" 50 "$plugin (Warnings)"
fi
return 0
fi
echo " ✅ $plugin completed successfully"
return 0
}
if [[ -n "$FLAVOR" ]]; then
# Convert semicolon-separated string to array
IFS=';' read -ra temp_array <<< "$FLAVOR"
plugins=()
for item in "${temp_array[@]}"; do
plugins+=($(echo "$item" | tr '[:upper:]' '[:lower:]'))
done
else
declare -a plugins=(
# monochrome
boxer bumblebee commando8
gx12 mt12 pocket t12max
t14 t20 t20v2 tpros tprov2
tx12mk2 zorro v12 v14
x7access x9dp2019 x9e
# colour
el18 nb4p nv14 st16 pa01
pl18 pl18ev pl18u
t15 t15pro t16 t18
tx15 tx16s tx16smk3 f16 v16
x10 x10express x12s
)
fi
TOTAL=${#plugins[@]}
FAILED_PLUGINS=()
echo "🔨 Building $TOTAL Plugins"
for i in "${!plugins[@]}"; do
plugin="${plugins[$i]}"
current=$((i + 1))
percent=$((current * 100 / TOTAL))
# For terminal output, put each plugin on its own line for cleaner display
if [[ -n "$GITHUB_ACTIONS" ]]; then
printf "::group::📦 %-12s [%2d/%2d] %3d%%\n" "$plugin" "$current" "$TOTAL" "$percent"
else
echo "🔨 [$current/$TOTAL] ($percent%) Building $plugin..."
fi
error_status=0
if ! build_plugin "$plugin"; then
FAILED_PLUGINS+=("$plugin")
error_status=1
fi
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::endgroup::"
fi
if [ $error_status -ne 0 ]; then
# TODO: if not '--build-all' used
exit 1
fi
done
# Show summary of any failures (if using continue-on-error approach)
if [ ${#FAILED_PLUGINS[@]} -gt 0 ]; then
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::group::❌ Build Failures Summary"
else
echo "❌ Build Failures Summary"
echo "=========================================="
fi
echo "The following plugins failed to build:"
for failed_plugin in "${FAILED_PLUGINS[@]}"; do
echo " ❌ $failed_plugin"
done
if [[ -n "$GITHUB_ACTIONS" ]]; then
echo "::endgroup::"
else
echo "=========================================="
fi
exit 1
fi
+1 -5
View File
@@ -59,12 +59,8 @@ do
cmake ${BUILD_OPTIONS} "${SRCDIR}"
cmake_build_parallel . --target arm-none-eabi-configure
cmake_build_parallel arm-none-eabi --target ${FIRMARE_TARGET}
cmake_build_parallel . --target native-configure
cmake_build_parallel native --target libsimulator
cmake_build_parallel native --target tests-radio
rm -f CMakeCache.txt native/CMakeCache.txt arm-none-eabi/CMakeCache.txt
rm -f CMakeCache.txt native/CMakeCache.txt
done