diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml deleted file mode 100644 index 9ac12115..00000000 --- a/.github/workflows/build-release.yml +++ /dev/null @@ -1,41 +0,0 @@ -## This is triggered by the publishing of a release, and will build the assets and attach them. - -name: On Release - -on: - release: - types: [published] - -jobs: - ci: - name: CI - uses: ./.github/workflows/ci.yml - with: - debug_build: false - - release: - name: Attach Release Artifacts - needs: ci - runs-on: ubuntu-22.04 - steps: - - name: Code Checkout - uses: actions/checkout@v2 - - - name: Fetch build artifacts - uses: actions/download-artifact@v2 - - - name: List assets - run: ls -al Betaflight-*/* - - - name: Attach assets to release - run: | - set -x - assets=() - for asset in Betaflight-*/*; do - assets+=("-a" "$asset") - echo "$asset" - done - tag_name="${GITHUB_REF##*/}" - hub release edit "${assets[@]}" -m "" "$tag_name" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 400ecb38..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,62 +0,0 @@ -# Builds Betaflight Blackbox Explorer on Windows, Linux and macOS platforms. -# -# After building, artifacts are released to a seperate repository. - -name: CI - -on: - workflow_call: - inputs: - debug_build: - description: 'Specifies if it is a debug build or a release build' - default: true - required: false - type: boolean - -jobs: - build: - name: Build (${{ matrix.name }}) - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - name: Linux - os: ubuntu-20.04 - releaseArgs: --linux64 - - - name: macOS - os: macos-11 - releaseArgs: --osx64 - - - name: Windows - os: windows-2022 - releaseArgs: --win64 - steps: - - uses: actions/checkout@v2 - - - name: Cache NW.js - uses: actions/cache@v2 - with: - path: cache/ - key: ${{ runner.os }}-${{ hashFiles('gulpfile.js') }} - - - name: Install Node.js - uses: actions/setup-node@v2 - with: - node-version-file: '.nvmrc' - cache: yarn - - - run: yarn install --immutable --immutable-cache --check-cache - - - run: yarn gulp release ${{ matrix.releaseArgs }} - if: ${{ !inputs.debug_build }} - - - run: yarn gulp debug-release ${{ matrix.releaseArgs }} - if: ${{ inputs.debug_build }} - - - name: Publish build artifacts - uses: actions/upload-artifact@v2 - with: - name: Betaflight-Blackbox-Explorer${{ inputs.debug_build == 'true' && '-Debug' || '' }}-${{ matrix.name }} - path: release/ - retention-days: 90 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml deleted file mode 100644 index a890e01e..00000000 --- a/.github/workflows/nightly.yml +++ /dev/null @@ -1,71 +0,0 @@ -# You'll need to setup the follwing environment variables: -# env.repoNightly - The repository to release nightly builds to e.g. betaflight-configurator-nightly -# env.releaseNotes - The release notes to be published as part of the github release -# env.debugReleaseNotes - The release notes to be published as part of the github debug release -# secrets.REPO_TOKEN - A GitHub token with permissions to push and publish releases to the nightly repo - -env: - repoNightly: betaflight/blackbox-log-viewer-nightlies - debugReleaseNotes: > - This is an automated development build. - It may be unstable and result in corrupted configurations or data loss. - **Use only for testing.** - releaseNotes: This is a release build. It does not contain the debug console. - -name: Nightly - -on: - push: - branches: - - master - - '*-maintenance' - -jobs: - ci: - name: CI - uses: ./.github/workflows/ci.yml - - release: - name: Release - needs: ci - runs-on: ubuntu-20.04 - steps: - - name: Fetch build artifacts - uses: actions/download-artifact@v2 - with: - path: release-assets/ - - - name: Select release notes - id: notes - run: | - set -- release-assets/Betaflight-Blackbox-Explorer-Debug-* - echo "::set-output name=notes::$(test -e "$1" && echo '${{ env.debugReleaseNotes }}' || echo '${{ env.releaseNotes }}')" - - - name: Get current date - id: date - run: echo "::set-output name=today::$(date '+%Y%m%d')" - - - name: Release - uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v0.1.14 - with: - token: ${{ secrets.REPO_TOKEN }} - repository: ${{ env.repoNightly }} - tag_name: v${{ steps.date.outputs.today }}.${{ github.run_number }} - files: release-assets/Betaflight-Blackbox-Explorer-*/** - draft: false - prerelease: false - fail_on_unmatched_files: true - body: | - ${{ steps.notes.outputs.notes }} - - ### Repository: - ${{ github.repository }} ([link](${{ github.event.repository.html_url }})) - - ### Branch: - ${{ github.ref_name }} ([link](${{ github.event.repository.html_url }}/tree/${{ github.ref_name }})) - - ### Latest changeset: - ${{ github.event.head_commit.id }} ([link](${{ github.event.head_commit.url }})) - - ### Changes: - ${{ github.event.head_commit.message }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml deleted file mode 100644 index b298655b..00000000 --- a/.github/workflows/pr.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: PR - -on: - pull_request: - branches: - - master - - '*-maintenance' - -jobs: - ci: - name: CI - uses: ./.github/workflows/ci.yml \ No newline at end of file diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 74c4c2cd..00e45462 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -9,7 +9,7 @@ jobs: name: 'Check and close stale issues' runs-on: ubuntu-latest steps: - - uses: actions/stale@v3 + - uses: actions/stale@v8 with: repo-token: ${{ secrets.GITHUB_TOKEN }} operations-per-run: 30 diff --git a/.gitignore b/.gitignore index a1f91c14..58b012d5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,7 @@ debug/ release/ # artefacts for Visual Studio Code -/.vscode/ \ No newline at end of file +/.vscode/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* \ No newline at end of file diff --git a/.jshintrc b/.jshintrc index c91cbad0..378b0a7a 100644 --- a/.jshintrc +++ b/.jshintrc @@ -9,23 +9,6 @@ "$" : false, "jQuery" : false, "THREE" : false, - "Modernizr" : false, - - "ArrayDataStream" : true, - "Craft2D" : true, - "Craft3D" : true, - "ExpoCurve" : true, - "FIFOCache" : true, - "FlightLog" : true, - "FlightLogEvent" : true, - "FlightLogGrapher" : true, - "FlightLogParser" : true, - "FlightLogFieldPresenter" : true, - "FlightLogIndex" : true, - "GraphConfig" : true, - "GraphLegend" : true, - "WorkspaceSelection": true, - "IMU" : true, - "SeekBar" : true + "Modernizr" : false } } \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 7fd02374..790e1105 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v16.15.0 +v20.10.0 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 690978bc..00000000 --- a/.travis.yml +++ /dev/null @@ -1,43 +0,0 @@ -os: - - linux - - osx - -sudo: false - -dist: trusty - -addons: - apt: - packages: - - rpm - -git: - depth: 5 - -language: node_js - -node_js: - - 16.15.0 - -before_install: - - npm i -g npm@6.0.1 - -script: - - yarn gulp release - -cache: - directories: - - node_modules - -# Notifications are encrypted to betaflight/betaflight to avoid spam from forks -# Command: `travis encrypt "" --com -r betaflight/blackbox-log-viewer` -notifications: - slack: - rooms: - - secure: lg53G5eeNucPlQos/RUA/PxkewvJwcWU7vxqMQJ8/Ba6VEcK4Yd6jGOhb837suafyZXR4HPqrOxBY8GNfcf8ZL258vLjfNy1164KL1ErUIUGAFKtevsV2BIX+G8LbEKkJDimKmzTZ4mZ4mnPY9fmRffTZY4Ioj+tj+DeESEUjo1AGWLWynRsBz3BondtE/7R8p+Ab3GPWv/JuL8Q4t3fJH8ZRSkdPC5rHuZKYge6EhrwCRlUzQP5iDC6Fw9ZOuSbsAcNLFh1ClaXDps9EPKSL5M7UMDYk1IgVrci0jcVo9afI7IZf5/SuR/Pn9BGSgSIV1+subarc0pew6PKG+Nz6dy9edhlTU/w7M1pi0/aOiUVEmo5nSsgJRwJg3lroonaAZpn9KWtNTRRMZDUK1wrge/QFKLydsrGrmxVo+QkjUf82MGQBAe5yDMfPZzkl502AwBp/ZnE6AT6Fj60A2sPsUgredK70f85Yv9r7FOX/mm+MU4MBAsDgl7LHHnC4Q9mH4kjnu2QBMf+1c7YxlOz8Y/a0b1+F72+e25wRrz/NWZfJLv6KVb7bjAFA9B8bSn9DKSauwloM/Bn2vQz64tmEJIEXm734nQx/fwii8RuaUJ/t8OJRiXiAF1IDBdKZNtE7jsMDBFNP/ct30Mm9Cqpad9xDFjRH5CgUYsMMLqphyU= - webhooks: - urls: - - secure: qyk5ou1nFHaFJTeR5tUWqJdCKW6UPSrcedzhhcDpcT4seKtSycChzD6OJCJfdtI/9GbSw79H2E4chj49FzG9Qlilmq88TCIU61EKrxTmkUudXyxNKxBcUQ5S5N0y3GOABCUUxJpccDnLUHvD3MZ0ppQ43N4FdMygiq5yHwx1zXhBdnE6ZuwQGAwh/TPYOhPBbvB3W2xVXcQExWKCkhk8xGCA5vfvR0m82jkrwMSlfKLsWxmUNM6l5YDcHtCwE0F68WCtcTUiv6WVQ60SvVe1EVGiz0O43ee8K7Y/Ays23GSb839+N2NcAF0NY8BygPes3phbsw+zgJZB9PT3GmcY2A3U6n6SREtEy+7Zxc3m5ntI9FVrgepQqCnUHgu/sxV+eC356OXi1XIHK3Yv7ouh+gQTF3p3TVpMShesdW+zRzprt2x+PDFXv8ESu0DM6d7w0PU0YqcztON5fZQSvLytm+uXObIxDsT6oafdfR5J+hRHwEuBsPlMi7GaONQsMuxUC0nYBFq1VCHpVGLY2kbCxeqC35GmRH39Ei52U4Yr0fcgwa1Kh/2qTrjNtahrJhOcr+pE17XerPfWKCTfNARBI/AE274ykTNsKfhCQy6hETnw8xJm2GzARWmWK1Q3Ein0+C8Xs9LhQSrCpCGcKXsbsQZuqFY7gRrsbRGX9mzD7DI= - on_success: always # options: [always|never|change] default: always - on_failure: always # options: [always|never|change] default: always - on_start: always # options: [always|never|change] default: always diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bad6bbd9..8d009dd4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Please remember the issue tracker on github is _not_ for user support. Please a Issues created without steps to repeat are likely to be closed. E-mail requests for support will go un-answered; All support needs to be public so that other people can read the problems and solutions. -Remember that issues that are due to mis-configuration, wiring or failure to read documentation just takes time away from the developers and can often be solved without developer interaction by other users. +Remember that issues that are due to misconfiguration, wiring or failure to read documentation just takes time away from the developers and can often be solved without developer interaction by other users. Please search for existing issues *before* creating new ones. diff --git a/README.md b/README.md index dcf7f129..42184b3e 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,131 @@ # Betaflight Blackbox Explorer -[![Latest version](https://img.shields.io/github/v/release/betaflight/blackbox-log-viewer)](https://github.com/betaflight/blackbox-log-viewer/releases) [![Build](https://img.shields.io/github/workflow/status/betaflight/blackbox-log-viewer/Nightly)](https://github.com/betaflight/blackbox-log-viewer/actions/workflows/nightly.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=betaflight_blackbox-log-viewer&metric=alert_status)](https://sonarcloud.io/dashboard?id=betaflight_blackbox-log-viewer) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) +[![Latest version](https://img.shields.io/github/v/release/betaflight/blackbox-log-viewer)](https://github.com/betaflight/blackbox-log-viewer/releases) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=betaflight_blackbox-log-viewer&metric=alert_status)](https://sonarcloud.io/dashboard?id=betaflight_blackbox-log-viewer) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) ![Main explorer interface](screenshots/main-interface.jpg) -This tool allows you to open logs recorded by Betaflight's Blackbox feature in your web browser. You can seek through -the log to examine graphed values at each timestep. If you have a flight video, you can load that in as well and it'll -be played behind the log. You can export the graphs as a WebM video to share with others. +This tool allows you to open logs recorded by Betaflight's Blackbox feature in +your web browser. You can seek through the log to examine graphed values at each +timestep. If you have a flight video, you can load that in as well and it'll be +played behind the log. You can export the graphs as a WebM video to share with +others. ## Installation -### Standalone - -Betaflight 4.3 users should use the [latest nightly release](https://github.com/betaflight/blackbox-log-viewer-nightlies). -Users of earlier Betaflight firmware should first try the nightly release, but if some fields don't render properly, try an earlier version from [Releases](https://github.com/betaflight/blackbox-log-viewer/releases). -### Notes - -#### Windows users - -The minimum required version of Windows is Windows 8. +Current blackbox explorer version is built as +[PWA](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/What_is_a_progressive_web_app). +Meaning it can work in both online and offline modes as regular desktop app +would. -#### MacOS X users +### Web -Changes to the security model used in the latest versions of MacOS X 10.14 (Mojave) and 10.15 (Catalina) mean that the operating system will show an error message ('"Betaflight\ Blackbox\ Explorer.app" is damaged and can’t be opened. You should move it to the Trash.') when trying to install the application. To work around this, run the following command in a terminal before installing: `sudo xattr -rd com.apple.quarantine /Applications/Betaflight\ Blackbox\ Explorer.app`. +1. Visit https://blackbox.betaflight.com/ +2. Use the app -### Unstable Testing Versions - -Unstable testing versions of the lates builds of the Betaflight Blackbox Explorer for most platforms can be downloaded from [here](https://github.com/betaflight/blackbox-log-viewer-nightlies/releases). +### Standalone -**Be aware that these versions are intended for testing / feedback only, and may be buggy or broken.** +1. Visit https://blackbox.betaflight.com/ +2. Follow the procedure to install PWA on your platform. On MacOS chrome: +![Url bar PWA install](screenshots/url-bar.webp) +![PWA install dialog](screenshots/pwa-install-dialog.webp) ## Usage -Click the "Open log file/video" button at the top right and select your log file and your flight video (if you recorded one). -You can scroll through the log by clicking or dragging on the seek bar that appears underneath the main graph. The -current time is represented by the vertical red bar in the center of the graph. You can also click and drag left and -right on the graph area to scrub backwards and forwards. +Click the "Open log file/video" button at the top right and select your log file +and your flight video (if you recorded one). + +You can scroll through the log by clicking or dragging on the seek bar that +appears underneath the main graph. The current time is represented by the +vertical red bar in the center of the graph. You can also click and drag left +and right on the graph area to scrub backwards and forwards. ### Syncing your log to your flight video -The blackbox plays a short beep on the buzzer when arming, and this corresponds with the start of the logged data. -You can sync your log against your flight video by pressing the "start log here" button when you hear the beep in the -video. You can tune the alignment of the log manually by pressing the nudge left and nudge right buttons in the log -sync section, or by editing the value in the "log sync" box. Positive values move the log toward the end of the video, +The blackbox plays a short beep on the buzzer when arming, and this corresponds +with the start of the logged data. You can sync your log against your flight +video by pressing the "start log here" button when you hear the beep in the +video. You can tune the alignment of the log manually by pressing the nudge left +and nudge right buttons in the log sync section, or by editing the value in the +"log sync" box. Positive values move the log toward the end of the video, negative values move it towards the beginning. ### Customizing the graph display -Click the "Graph Setup" button on the right side of the display in order to choose which fields should be plotted on -the graph. You may, for example, want to remove the default gyro plot and add separate gyro plots for each rotation axis. -Or you may want to plot vbat against throttle to examine your battery's performance. +Click the "Graph Setup" button on the right side of the display in order to +choose which fields should be plotted on the graph. You may, for example, want +to remove the default gyro plot and add separate gyro plots for each rotation +axis. Or you may want to plot vbat against throttle to examine your battery's +performance. -## Native app build via NW.js +## Developing -### Development +### Node setup -1. Install node.js -2. Install yarn: `npm install yarn -g`. -3. Change to project folder and run `yarn install`. -4. Run `yarn start` to build & run the debug flavor. +We are using [nvm](https://github.com/nvm-sh/nvm) to manage the correct node +vesion, follow the install instruction there. After which from blackbox directory +just run: -### App build and release - -The tasks are defined in `gulpfile.js` and can be run through yarn: -``` -yarn gulp [[platform] [platform] ...] +```bash +nvm use ``` -List of possible values of ``: -* **dist** copies all the JS and CSS files in the `./dist` folder. -* **apps** builds the apps in the `./apps` folder [1]. -* **debug** builds debug version of the apps in the `./debug` folder [1]. -* **release** zips up the apps into individual archives in the `./release` folder [1]. +### Yarn -[1] Running this task on macOS or Linux requires Wine, since it's needed to set the icon for the Windows app (build for specific platform to avoid errors). +For dependency management we are using [yarn](https://yarnpkg.com/), follow the +instruction there to install it. -#### Setting up and building on a Mac +### Development mode -- Install GitHub desktop application from https://desktop.github.com and open the GitHub Desktop application. -- At https://github.com/betaflight/betaflight-configurator, select Clone or Download > Open in Desktop +We are using [vite](https://vitejs.dev/) for development setup. It provides +bundling and various optimisations like hot module reloading. -(The GitHub Desktop application should come to the front and create a repository (not necessarily where you want it). The blackbox-log-viewer repository (folder) should appear under the list of local repositories. You can find your local repository location on your mac using the 'Locate in Finder' command GitHub Desktop It can be moved somewhere more else, but you'll then need to tell Github where you're moved it to.) +With `node` and `yarn` setup, to start developing run: -Open Terminal.app and install or update homebrew: -``` -/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" -``` -install node 8.x and yarn, if already installed, agree to update them -``` -brew install node@8 yarn -``` -Change Terminal's working directory wherever you put blackbox-log-viewer folder; easiest way is to type 'cd ' in Terminal then drag the blackbox-log-viewer folder from the Finder to the terminal window. Or use a terminal command like -``` -cd ~/mydirectorypath/blackbox-log-viewer +```bash +yarn start ``` -install dependencies into that folder (ignoring many confusing messages) with: -``` -yarn install -``` +This will start development server on http://localhost:5173/. -finally build the DMG itself, which will end up in blackbox-log-viewer/release/, with: -``` -yarn gulp release -``` -or just: -``` -gulp release +## Installing Dev Build locally + +If you want to have latest and greatest version installed on your machine from +the tip of the repository: +1. First need to build the application: +```bash +yarn build ``` -For a build with debugging capabilities (use F12 to open the debug console): +2. Start the application in `preview` mode +```bash +yarn preview ``` -yarn start``` - - - -#### Build or release app for one specific platform -To build or release only for one specific platform you can append the plaform after the `task-name`. -If no platform is provided, only for the platform you are builing from will be build. - -* **MacOS X** use `yarn gulp --osx64` -* **Linux** use `yarn gulp --linux64` -* **Windows** use `yarn gulp --win64` - -`` would typically be `release`. You can also use multiple platforms e.g. `yarn gulp --osx64 --linux64`. Other platforms like `--win32` and `--linux32` can be used too, but they are not officially supported, so use them at your own risk. - -#### macOS DMG installation background image - -The release distribution for macOS uses a DMG file to install the application. -The PSD source for the DMG backgound image can be found in the root (`dmg-background.png`). After changing the source, export the image to PNG format in folder `./images/`. +3. Visit http://localhost:4173/ +4. Follow the steps from [Standalone](#standalone) ## Flight video won't load, or jumpy flight video upon export -Some flight video formats aren't supported by Chrome, so the viewer can't open them. You can fix this by re-encoding -your video using the free tool [Handbrake][]. Open your original video using Handbrake. In the output settings, choose -MP4 as the format, and H.264 as the video codec. +Some flight video formats aren't supported by Chrome, so the viewer can't open +them. You can fix this by re-encoding your video using the free tool +[Handbrake][]. Open your original video using Handbrake. In the output settings, +choose MP4 as the format, and H.264 as the video codec. -Because of [Google Bug #66631][], Chrome is unable to accurately seek within H.264 videos that use B-frames. This is -mostly fine when viewing the flight video inside Blackbox Explorer. However, if you use the "export video" feature, this -bug will cause the flight video in the background of the exported video to occasionally jump backwards in time for a -couple of frames, causing a very glitchy appearance. +Because of [Google Bug #66631][], Chrome is unable to accurately seek within +H.264 videos that use B-frames. This is mostly fine when viewing the flight +video inside Blackbox Explorer. However, if you use the "export video" feature, +this bug will cause the flight video in the background of the exported video to +occasionally jump backwards in time for a couple of frames, causing a very +glitchy appearance. -To fix that issue, you need to tell Handbrake to render every frame as an intraframe, which will avoid any problematic -B-frames. Do that by adding "keyint=1" into the Additional Options box: +To fix that issue, you need to tell Handbrake to render every frame as an +intraframe, which will avoid any problematic B-frames. Do that by adding +"keyint=1" into the Additional Options box: ![Handbrake settings](screenshots/handbrake.png) -Hit start to begin re-encoding your video. Once it finishes, you should be able to load the new video into the Blackbox -Explorer. +Hit start to begin re-encoding your video. Once it finishes, you should be able +to load the new video into the Blackbox Explorer. [Handbrake]: https://handbrake.fr/ [Google Bug #66631]: http://code.google.com/p/chromium/issues/detail?id=66631 diff --git a/_locales/en/messages.json b/_locales/en/messages.json deleted file mode 100644 index c31e8d6b..00000000 --- a/_locales/en/messages.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "translation_version": { - "message": "0" - }, - "noticeTitle": { - "message": "Notice" - }, - "checkForUnstableVersions": { - "message": "Show update notifications for unstable versions of the configurator" - }, - "updateNotice": { - "message": "You are using an outdated version of the Betaflight Blackbox Explorer.
Version $1 is available online, please visit the release page to download and install the latest version with fixes and improvements.

Please close the app before updating." - }, - "updateWebsite": { - "message": "Go to Release Website" - }, - "close": { - "message": "Close" - } -} \ No newline at end of file diff --git a/assets/linux/betaflight-blackbox-explorer.desktop b/assets/linux/betaflight-blackbox-explorer.desktop deleted file mode 100644 index a5fc4e8f..00000000 --- a/assets/linux/betaflight-blackbox-explorer.desktop +++ /dev/null @@ -1,8 +0,0 @@ -[Desktop Entry] -Name=Betaflight Blackbox Explorer -Comment=Crossplatform blackbox analitics tool for Betaflight flight control system -Exec=/opt/betaflight/betaflight-blackbox-explorer/betaflight-blackbox-explorer -Icon=/opt/betaflight/betaflight-blackbox-explorer/icon/bf_icon_128.png -Terminal=false -Type=Application -MimeType=application/x-blackboxlog diff --git a/assets/linux/copyright b/assets/linux/copyright deleted file mode 100644 index d525e019..00000000 --- a/assets/linux/copyright +++ /dev/null @@ -1,4 +0,0 @@ -This package was created by the Betaflight open source flight controller firmware project (https://github.com/betaflight/betaflight). - -All of the code is covered under the terms of the GPL version 3. See the -file /usr/share/common-licenses/GPL-3 for more information. diff --git a/assets/linux/mime/betaflight-blackbox-explorer.xml b/assets/linux/mime/betaflight-blackbox-explorer.xml deleted file mode 100644 index f026f7fd..00000000 --- a/assets/linux/mime/betaflight-blackbox-explorer.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - Betaflight Blackbox log file - - - - diff --git a/assets/windows/bf_installer.bmp b/assets/windows/bf_installer.bmp deleted file mode 100644 index 36dc9111..00000000 Binary files a/assets/windows/bf_installer.bmp and /dev/null differ diff --git a/assets/windows/bf_installer_icon.ico b/assets/windows/bf_installer_icon.ico deleted file mode 100644 index e5d4fc8a..00000000 Binary files a/assets/windows/bf_installer_icon.ico and /dev/null differ diff --git a/assets/windows/bf_installer_small.bmp b/assets/windows/bf_installer_small.bmp deleted file mode 100644 index dea19e9e..00000000 Binary files a/assets/windows/bf_installer_small.bmp and /dev/null differ diff --git a/assets/windows/installer.iss b/assets/windows/installer.iss deleted file mode 100644 index 03cf7aff..00000000 --- a/assets/windows/installer.iss +++ /dev/null @@ -1,119 +0,0 @@ -; ------------------------------------------ -; Installer for Betaflight Blackbox Viewer -; ------------------------------------------ -; It receives from the command line with /D the parameters: -; version -; archName -; archAllowed -; archInstallIn64bit -; sourceFolder -; targetFolder - -#define ApplicationName "Betaflight Blackbox Explorer" -#define CompanyName "The Betaflight open source project" -#define CompanyUrl "https://betaflight.com/" -#define ExecutableFileName "betaflight-blackbox-explorer.exe" -#define GroupName "Betaflight" -#define InstallerFileName "betaflight-blackbox-explorer-installer_" + version + "_" + archName -#define InstallerFileName "betaflight-blackbox-explorer_" + version + "_" + archName + "-installer" -#define SourcePath "..\..\" + sourceFolder + "\betaflight-blackbox-explorer\" + archName -#define TargetFolderName "Betaflight-Blackbox-Explorer" -#define UpdatesUrl "https://github.com/betaflight/blackbox-log-viewer/releases/" - -[CustomMessages] -LaunchProgram=Start %1 - -[Files] -Source: "{#SourcePath}\*"; DestDir: "{app}"; Flags: recursesubdirs - -[Icons] -; Programs group -Name: "{group}\{#ApplicationName}"; Filename: "{app}\{#ExecutableFileName}"; -; Desktop icon -Name: "{autodesktop}\{#ApplicationName}"; Filename: "{app}\{#ExecutableFileName}"; -; Non admin users, uninstall icon -Name: "{group}\Uninstall {#ApplicationName}"; Filename: "{uninstallexe}"; Check: not IsAdminInstallMode - -[Registry] -; File associations -Root: HKA; Subkey: "Software\Classes\.bbl"; ValueType: string; ValueName: ""; ValueData: "BetaflightBlackboxFile"; Flags: uninsdeletevalue -Root: HKA; Subkey: "Software\Classes\.bfl"; ValueType: string; ValueName: ""; ValueData: "BetaflightBlackboxFile"; Flags: uninsdeletevalue -Root: HKA; Subkey: "Software\Classes\BetaflightBlackboxFile"; ValueType: string; ValueName: ""; ValueData: "Betaflight Blackbox Explorer log file"; Flags: uninsdeletekey -Root: HKA; Subkey: "Software\Classes\BetaflightBlackboxFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#ExecutableFileName}" -Root: HKA; Subkey: "Software\Classes\BetaflightBlackboxFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExecutableFileName}"" ""%1""" - -; App registration -Root: HKA; Subkey: "Software\Classes\Applications\{#ExecutableFileName}"; ValueType: string; ValueName: "FriendlyAppName"; ValueData: "{#ApplicationName}"; Flags: uninsdeletekey - -[Run] -; Add a checkbox to start the app after installed -Filename: {app}\{#ExecutableFileName}; Description: {cm:LaunchProgram, {#ApplicationName}}; Flags: nowait postinstall skipifsilent - -[Setup] -AppId=610b3d74-ca89-4533-9490-128c40143493 -AppName={#ApplicationName} -AppPublisher={#CompanyName} -AppPublisherURL={#CompanyUrl} -AppUpdatesURL={#UpdatesUrl} -AppVersion={#version} -ArchitecturesAllowed={#archAllowed} -ArchitecturesInstallIn64BitMode={#archInstallIn64bit} -ChangesAssociations=True -Compression=lzma2 -DefaultDirName={autopf}\{#GroupName}\{#TargetFolderName} -DefaultGroupName={#GroupName}\{#ApplicationName} -LicenseFile=..\..\LICENSE -MinVersion=6.2 -OutputBaseFilename={#InstallerFileName} -OutputDir=..\..\{#targetFolder}\ -PrivilegesRequiredOverridesAllowed=commandline dialog -SetupIconFile=bf_installer_icon.ico -SolidCompression=yes -UninstallDisplayIcon={app}\{#ExecutableFileName} -UninstallDisplayName={#ApplicationName} -WizardImageFile=bf_installer.bmp -WizardSmallImageFile=bf_installer_small.bmp -WizardStyle=modern - -[Code] -function InitializeSetup(): Boolean; -var - ResultCode: Integer; - ResultStr: String; - ParameterStr : String; -begin - - Result := True; - - // Check if the application is already installed by the old NSIS installer, and uninstall it - // Look into the different registry entries: win32, win64 and without user rights - if not RegQueryStringValue(HKLM, 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Betaflight Blackbox Explorer', 'UninstallString', ResultStr) then - begin - if not RegQueryStringValue(HKLM, 'SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Betaflight Blackbox Explorer', 'UninstallString', ResultStr) then - begin - RegQueryStringValue(HKCU, 'SOFTWARE\Betaflight\Betaflight Blackbox Explorer', 'UninstallString', ResultStr) - end; - end; - - // Found, start uninstall - if ResultStr <> '' then - begin - - ResultStr:=RemoveQuotes(ResultStr); - - // Add this parameter to not return until uninstall finished. The drawback is that the uninstaller file is not deleted - ParameterStr := '_?=' + ExtractFilePath(ResultStr); - - if Exec(ResultStr, ParameterStr, '', SW_SHOW, ewWaitUntilTerminated, ResultCode) then - begin - // Delete the unistaller file and empty folders. Not deleting the files. - DeleteFile(ResultStr); - DelTree(ExtractFilePath(ResultStr), True, False, True); - end - else begin - Result := False; - MsgBox('Error uninstalling old Blackbox ' + SysErrorMessage(ResultCode) + '.', mbError, MB_OK); - end; - end; - -end; \ No newline at end of file diff --git a/changelog.html b/changelog.html index 2e8ce260..cc54cde3 100644 --- a/changelog.html +++ b/changelog.html @@ -184,7 +184,7 @@

Notes

Log file extension

BB Viewer attempts to determine the file type you are opening by examining the file extension first. Where no file extension is used in the name, - then BB Viewer will guess the type of file; which is usually fine for small log files (<10MB).

+ then BB Viewer will guess the type of file; which is usually fine for small log files (<10MB).

For log files larger than 10MB, then BB Viewer guesses they are video files and tries to load it (and fails because it is not a video)

So as a tip, it's best to use a file extension when you save your logs.

The file extensions that are automatically recognised are: -

@@ -194,7 +194,7 @@

Log file extension

  • .JSON as workspace backups
  • -

    If it is not one of those extensions, it takes a guess on the type by file size (if file-size < 10MB then Log file else its a video);

    +

    If it is not one of those extensions, it takes a guess on the type by file size (if file-size < 10MB then Log file else its a video);

    Mousewheel Improvements

    @@ -310,7 +310,7 @@

    2.5.0 - Grids, INAV Header Support and Stick Trails.

  • Gyro chart range increased to +/-2000deg/s was a minor calculation error that limited the gyro chart to approx +/-1500deg/s
  • Header update for Betaflight v3.0; added fields gyro_notch_hz, gyro_notch_q, rc_smooth_interval and "Craft name"
  • Craft Name is now shown on status bar (if entered in CLI) and also at top of header dialog (Betaflight v3.0 only).
  • -
  • PID Contoller type names on header updated for Betaflight v3.0.
  • +
  • PID Controller type names on header updated for Betaflight v3.0.
  • Stick Trails; This feature can be turned on from the User Settings dialog, will show a phosphor trail on the stick display that shows the movement of the sticks for the last 500ms.
  • Add tooltips to primary logger controls.
  • Thanks to BorisB, Added New Event names for Betaflight Twitch Testing mode.
  • diff --git a/gulp-appdmg.js b/gulp-appdmg.js deleted file mode 100644 index def0222e..00000000 --- a/gulp-appdmg.js +++ /dev/null @@ -1,31 +0,0 @@ - -const appdmg = require('appdmg'); -const through = require('through2'); -const gutil = require('gulp-util'); - -const PluginError = gutil.PluginError; -const PLUGIN_NAME = 'gulp-appdmg'; - -module.exports = function(options) { - const stream = through.obj(function(file, encoding, next) { - next(); - }, function(callback) { - const self = this; - const ee = appdmg(options); - - ee.on('progress', function(info) { - gutil.log(info.current + '/' + info.total + ' ' + info.type + ' ' + (info.title || info.status)); - }); - - ee.on('error', function(err) { - self.emit('error', new PluginError(PLUGIN_NAME, err)); - callback(); - }); - - ee.on('finish', callback); - }); - - // returning the file stream - stream.resume(); - return stream; -}; diff --git a/gulpfile.js b/gulpfile.js deleted file mode 100644 index 34e10b6b..00000000 --- a/gulpfile.js +++ /dev/null @@ -1,639 +0,0 @@ -'use strict'; - -const pkg = require('./package.json'); - -const fs = require('fs'); -const path = require('path'); - -const zip = require('gulp-zip'); -const del = require('del'); -const innoSetup = require('@quanle94/innosetup'); -const NwBuilder = require('nw-builder'); -const deb = require('gulp-debian'); -const buildRpm = require('rpm-builder') -const commandExistsSync = require('command-exists').sync; - -const gulp = require('gulp'); -const yarn = require("gulp-yarn"); -const rename = require('gulp-rename'); -const os = require('os'); - -const DIST_DIR = './dist/'; -const APPS_DIR = './apps/'; -const DEBUG_DIR = './debug/'; -const RELEASE_DIR = './release/'; - -const LINUX_INSTALL_DIR = '/opt/betaflight'; - -var nwBuilderOptions = { - version: '0.60.0', - files: './dist/**/*', - macIcns: './images/bf_icon.icns', - macPlist: { 'CFBundleDisplayName': 'Betaflight Blackbox Explorer'}, - winIco: './images/bf_icon.ico', -}; - -//----------------- -//Pre tasks operations -//----------------- -const SELECTED_PLATFORMS = getInputPlatforms(); - -//----------------- -//Tasks -//----------------- - -gulp.task('clean', gulp.parallel(clean_dist, clean_apps, clean_debug, clean_release)); - -gulp.task('clean-dist', clean_dist); - -gulp.task('clean-apps', clean_apps); - -gulp.task('clean-debug', clean_debug); - -gulp.task('clean-release', clean_release); - -gulp.task('clean-cache', clean_cache); - -const distRebuild = gulp.series(clean_dist, dist); -gulp.task('dist', distRebuild); - -const appsBuild = gulp.series(gulp.parallel(clean_apps, distRebuild), apps, gulp.parallel(listPostBuildTasks(APPS_DIR))); -gulp.task('apps', appsBuild); - -const debugAppsBuild = gulp.series(gulp.parallel(clean_debug, distRebuild), debug, gulp.parallel(listPostBuildTasks(DEBUG_DIR))); - -const debugBuild = gulp.series(dist, debug, gulp.parallel(listPostBuildTasks(DEBUG_DIR)), start_debug); -gulp.task('debug', debugBuild); - -const releaseBuild = gulp.series(gulp.parallel(clean_release, appsBuild), gulp.parallel(listReleaseTasks(APPS_DIR))); -gulp.task('release', releaseBuild); - -const debugReleaseBuild = gulp.series(gulp.parallel(clean_release, debugAppsBuild), gulp.parallel(listReleaseTasks(DEBUG_DIR))); -gulp.task('debug-release', debugReleaseBuild); - -gulp.task('default', debugBuild); - -// ----------------- -// Helper functions -// ----------------- - -// Get platform from commandline args -// # -// # gulp []+ Run only for platform(s) (with one of --linux64, --linux32, --osx64, --win32 or --win64) -// # -function getInputPlatforms() { - var supportedPlatforms = ['linux64', 'linux32', 'osx64', 'win32', 'win64']; - var platforms = []; - var regEx = /--(\w+)/; - for (var i = 3; i < process.argv.length; i++) { - var arg = process.argv[i].match(regEx)[1]; - if (supportedPlatforms.indexOf(arg) > -1) { - platforms.push(arg); - } else { - console.log('Unknown platform: ' + arg); - process.exit(); - } - } - - if (platforms.length === 0) { - var defaultPlatform = getDefaultPlatform(); - if (supportedPlatforms.indexOf(defaultPlatform) > -1) { - platforms.push(defaultPlatform); - } else { - console.error(`Your current platform (${os.platform()}) is not a supported build platform. Please specify platform to build for on the command line.`); - process.exit(); - } - } - - if (platforms.length > 0) { - console.log('Building for platform(s): ' + platforms + '.'); - } else { - console.error('No suitables platforms found.'); - process.exit(); - } - - return platforms; -} - -// Gets the default platform to be used -function getDefaultPlatform() { - let defaultPlatform; - switch (os.platform()) { - case 'darwin': - defaultPlatform = 'osx64'; - - break; - case 'linux': - defaultPlatform = 'linux64'; - - break; - case 'win32': - defaultPlatform = 'win64'; - - break; - - default: - defaultPlatform = ''; - - break; - } - return defaultPlatform; -} - -function getPlatforms() { - return SELECTED_PLATFORMS.slice(); -} - -function removeItem(platforms, item) { - const index = platforms.indexOf(item); - if (index >= 0) { - platforms.splice(index, 1); - } -} - -function getRunDebugAppCommand(arch) { - - let command; - - switch (arch) { - case 'osx64': - const pkgName = `${pkg.name}.app`; - command = `open ${path.join(DEBUG_DIR, pkg.name, arch, pkgName)}`; - - break; - - case 'linux64': - case 'linux32': - command = path.join(DEBUG_DIR, pkg.name, arch, pkg.name); - - break; - - case 'win32': - case 'win64': - command = path.join(DEBUG_DIR, pkg.name, arch, pkg.name + '.exe'); - - break; - - default: - command = ''; - - break; - } - - return command; -} - -function getReleaseFilename(platform, ext, portable = false) { - return `${pkg.name}_${pkg.version}_${platform}${portable ? "-portable" : ""}.${ext}`; -} - -function clean_dist() { - return del([DIST_DIR + '**'], { force: true }); -}; - -function clean_apps() { - return del([APPS_DIR + '**'], { force: true }); -}; - -function clean_debug() { - return del([DEBUG_DIR + '**'], { force: true }); -}; - -function clean_release() { - return del([RELEASE_DIR + '**'], { force: true }); -}; - -function clean_cache() { - return del(['./cache/**'], { force: true }); -}; - -// Real work for dist task. Done in another task to call it via -// run-sequence. -function dist() { - var distSources = [ - // CSS files - './css/**/*', - - // JavaScript - './*.js', - './js/**/*', - - // everything else - './package.json', // For NW.js - './*.html', - './images/**/*', - './_locales/**/*', - ]; - return gulp.src(distSources, { base: '.' }) - .pipe(gulp.dest(DIST_DIR)) - .pipe(yarn({ - production: true, - ignoreScripts: true - }));; -}; - -// Create runable app directories in ./apps -function apps(done) { - var platforms = getPlatforms(); - - buildNWApps(platforms, 'normal', APPS_DIR, done); -}; - -function listPostBuildTasks(folder, done) { - - var platforms = getPlatforms(); - - var postBuildTasks = []; - - if (platforms.indexOf('win32') != -1) { - postBuildTasks.push(function post_build_win32(done){ return post_build('win32', folder, done) }); - } - - if (platforms.indexOf('win64') != -1) { - postBuildTasks.push(function post_build_win64(done){ return post_build('win64', folder, done) }); - } - - if (platforms.indexOf('linux32') != -1) { - postBuildTasks.push(function post_build_linux32(done){ return post_build('linux32', folder, done) }); - } - - if (platforms.indexOf('linux64') != -1) { - postBuildTasks.push(function post_build_linux64(done){ return post_build('linux64', folder, done) }); - } - - if (platforms.indexOf('osx64') != -1) { - postBuildTasks.push(function post_build_osx64(done){ return post_build('osx64', folder, done) }); - } - - // We need to return at least one task, if not gulp will throw an error - if (postBuildTasks.length == 0) { - postBuildTasks.push(function post_build_none(done){ done() }); - } - return postBuildTasks; -} - -function post_build(arch, folder, done) { - - if ((arch == 'win32') || (arch == 'win64')) { - // Copy ffmpeg codec library into Windows app - var libSrc = './library/' + arch + '/ffmpeg.dll' - var libDest = path.join(folder, pkg.name, arch); - console.log('Copy ffmpeg library to Windows app (' + libSrc + ' to ' + libDest + ')'); - return gulp.src(libSrc) - .pipe(gulp.dest(libDest)); - } - - if ((arch == 'linux32') || (arch == 'linux64')) { - - // Copy Ubuntu launcher scripts to destination dir - var launcherDir = path.join(folder, pkg.name, arch); - - // Copy ffmpeg codec library into Linux app - var libSrc = './library/' + arch + '/libffmpeg.so' - var libDest = path.join(launcherDir, 'lib'); - - console.log('Copy Ubuntu launcher scripts to ' + launcherDir); - gulp.src('assets/linux/**') - .pipe(gulp.dest(launcherDir)) - .on('end', function() { - - console.log('Copy ffmpeg library to Linux app (' + libSrc + ' to ' + libDest + ')'); - gulp.src(libSrc) - .pipe(gulp.dest(libDest)) - .on('end', function() {done()}); - - }); - return; - } - - if (arch == 'osx64') { - // Determine the WebKit version distributed in nw.js - var pathToVersions = path.join(folder, pkg.name, 'osx64', pkg.name + '.app', 'Contents', 'Frameworks', 'nwjs Framework.framework', 'Versions'); - var files = fs.readdirSync(pathToVersions); - if (files.length >= 1) { - var webKitVersion = files[0]; - console.log('Found nwjs version: ' + webKitVersion) - // Copy ffmpeg codec library into macOS app - var libSrc = './library/osx64/libffmpeg.dylib' - var libDest = path.join(pathToVersions, webKitVersion) + '/'; - console.log('Copy ffmpeg library to macOS app (' + libSrc + ' to ' + libDest + ')'); - return gulp.src(libSrc) - .pipe(gulp.dest(libDest)); - } else { - console.log('Error: could not find the Version folder.'); - } - } - - return done(); -} - -// Create debug app directories in ./debug -function debug(done) { - var platforms = getPlatforms(); - - buildNWApps(platforms, 'sdk', DEBUG_DIR, done); -} - -function buildNWApps(platforms, flavor, dir, done) { - - if (platforms.length > 0) { - var builder = new NwBuilder(Object.assign({ - buildDir: dir, - platforms: platforms, - flavor: flavor - }, nwBuilderOptions)); - builder.on('log', console.log); - builder.build(function (err) { - if (err) { - console.log('Error building NW apps: ' + err); - clean_debug(); - process.exit(1); - } - done(); - }); - } else { - console.log('No platform suitable for NW Build') - done(); - } -} - - -function start_debug(done) { - - var platforms = getPlatforms(); - - var exec = require('child_process').exec; - if (platforms.length === 1) { - var run = getRunDebugAppCommand(platforms[0]); - console.log('Starting debug app (' + run + ')...'); - exec(run); - } else { - console.log('More than one platform specified, not starting debug app'); - } - done(); -} - -// Create installer package for windows platforms -function release_win(arch, appDirectory, done) { - - // Parameters passed to the installer script - const parameters = []; - - // Extra parameters to replace inside the iss file - parameters.push(`/Dversion=${pkg.version}`); - parameters.push(`/DarchName=${arch}`); - parameters.push(`/DarchAllowed=${(arch === 'win32') ? 'x86 x64' : 'x64'}`); - parameters.push(`/DarchInstallIn64bit=${(arch === 'win32') ? '' : 'x64'}`); - parameters.push(`/DsourceFolder=${appDirectory}`); - parameters.push(`/DtargetFolder=${RELEASE_DIR}`); - - // Show only errors in console - parameters.push(`/Q`); - - // Script file to execute - parameters.push("assets/windows/installer.iss"); - - innoSetup(parameters, {}, - function(error) { - if (error != null) { - console.error(`Installer for platform ${arch} finished with error ${error}`); - } else { - console.log(`Installer for platform ${arch} finished`); - } - done(); - }); -} - -// Create distribution package (zip) for windows and linux platforms -function release_zip(arch, appDirectory) { - const src = path.join(appDirectory, pkg.name, arch, '**'); - const output = getReleaseFilename(arch, 'zip', true); - const base = path.join(appDirectory, pkg.name, arch); - - return compressFiles(src, base, output, 'Betaflight Blackbox Explorer'); -} - -// Compress files from srcPath, using basePath, to outputFile in the RELEASE_DIR -function compressFiles(srcPath, basePath, outputFile, zipFolder) { - return gulp.src(srcPath, { base: basePath }) - .pipe(rename(function(actualPath){ actualPath.dirname = path.join(zipFolder, actualPath.dirname) })) - .pipe(zip(outputFile)) - .pipe(gulp.dest(RELEASE_DIR)); -} - -function release_deb(arch, appDirectory, done) { - // Check if dpkg-deb exists - if (!commandExistsSync('dpkg-deb')) { - console.warn('dpkg-deb command not found, not generating deb package for ' + arch); - return done(); - } - - var debArch; - - switch (arch) { - case 'linux32': - debArch = 'i386'; - break; - case 'linux64': - debArch = 'amd64'; - break; - default: - console.error("Deb package error, arch: " + arch); - process.exit(1); - break; - } - - return gulp.src([path.join(appDirectory, pkg.name, arch, '*')]) - .pipe(deb({ - package: pkg.name, - version: pkg.version, - section: 'base', - priority: 'optional', - architecture: debArch, - maintainer: pkg.author, - description: pkg.description, - preinst: [`rm -rf ${LINUX_INSTALL_DIR}/${pkg.name}`], - postinst: [`chown root:root ${LINUX_INSTALL_DIR}`, `chown -R root:root ${LINUX_INSTALL_DIR}/${pkg.name}`, `cp ${LINUX_INSTALL_DIR}/${pkg.name}/mime/${pkg.name}.xml /usr/share/mime/packages/`, 'update-mime-database /usr/share/mime', - `cp ${LINUX_INSTALL_DIR}/${pkg.name}/icon/bf_icon_128.png /usr/share/icons/hicolor/128x128/mimetypes/application-x-blackboxlog.png`, 'gtk-update-icon-cache /usr/share/icons/hicolor -f', - `xdg-desktop-menu install ${LINUX_INSTALL_DIR}/${pkg.name}/${pkg.name}.desktop`], - prerm: [`rm /usr/share/mime/packages/${pkg.name}.xml`, 'update-mime-database /usr/share/mime', - 'rm /usr/share/icons/hicolor/128x128/mimetypes/application-x-blackboxlog.png', 'gtk-update-icon-cache /usr/share/icons/hicolor -f', - `xdg-desktop-menu uninstall ${pkg.name}.desktop`], - depends: 'libgconf-2-4', - changelog: [], - _target: `${LINUX_INSTALL_DIR}/${pkg.name}`, - _out: RELEASE_DIR, - _copyright: 'assets/linux/copyright', - _clean: true - })); -} - -function release_rpm(arch, appDirectory, done) { - - // Check if dpkg-deb exists - if (!commandExistsSync('rpmbuild')) { - console.warn('rpmbuild command not found, not generating rpm package for ' + arch); - done(); - } - - // The buildRpm does not generate the folder correctly, manually - createDirIfNotExists(RELEASE_DIR); - - var options = { - name: pkg.name, - version: pkg.version, - buildArch: getLinuxPackageArch('rpm', arch), - vendor: pkg.author, - summary: pkg.description, - license: 'GNU General Public License v3.0', - requires: 'libgconf-2-4', - prefix: '/opt', - files: - [ { cwd: path.join(appDirectory, pkg.name, arch), - src: '*', - dest: `${LINUX_INSTALL_DIR}/${pkg.name}` } ], - postInstallScript: [`cp ${LINUX_INSTALL_DIR}/${pkg.name}/mime/${pkg.name}.xml /usr/share/mime/packages/`, 'update-mime-database /usr/share/mime', - `cp ${LINUX_INSTALL_DIR}/${pkg.name}/icon/bf_icon_128.png /usr/share/icons/hicolor/128x128/mimetypes/application-x-blackboxlog.png`, 'gtk-update-icon-cache /usr/share/icons/hicolor -f', - `xdg-desktop-menu install ${LINUX_INSTALL_DIR}/${pkg.name}/${pkg.name}.desktop`, - 'xdg-desktop-menu forceupdate'], - preUninstallScript: [`rm /usr/share/mime/packages/${pkg.name}.xml`, 'update-mime-database /usr/share/mime', - 'rm /usr/share/icons/hicolor/128x128/mimetypes/application-x-blackboxlog.png', 'gtk-update-icon-cache /usr/share/icons/hicolor -f', - `xdg-desktop-menu uninstall ${pkg.name}.desktop`], - tempDir: path.join(RELEASE_DIR,'tmp-rpm-build-' + arch), - keepTemp: false, - verbose: false, - rpmDest: RELEASE_DIR - }; - - buildRpm(options, function(err, rpm) { - if (err) { - console.error("Error generating rpm package: " + err); - } - done(); - }); -} - -// Create distribution package for macOS platform -function release_osx64(appDirectory) { - var appdmg = require('./gulp-appdmg'); - - // The appdmg does not generate the folder correctly, manually - createDirIfNotExists(RELEASE_DIR); - - // The src pipe is not used - return gulp.src(['.']) - .pipe(appdmg({ - target: path.join(RELEASE_DIR, getReleaseFilename('macOS', 'dmg')), - basepath: path.join(appDirectory, pkg.name, 'osx64'), - specification: { - title: 'BF Blackbox Explorer', // <= volume name; should be smaller than 27 chars. - contents: [ - { 'x': 448, 'y': 342, 'type': 'link', 'path': '/Applications' }, - { 'x': 192, 'y': 344, 'type': 'file', 'path': pkg.name + '.app', 'name': 'Betaflight Blackbox Explorer.app' } - ], - background: path.join(__dirname, 'images/dmg-background.png'), - format: 'UDZO', - window: { - size: { - width: 638, - height: 479 - } - } - }, - }) - ); -} - -function getLinuxPackageArch(type, arch) { - var packArch; - - switch (arch) { - case 'linux32': - packArch = 'i386'; - break; - case 'linux64': - if (type == 'rpm') { - packArch = 'x86_64'; - } else { - packArch = 'amd64'; - } - break; - default: - console.error("Package error, arch: " + arch); - process.exit(1); - break; - } - - return packArch; -} - -// Create the dir directory, with write permissions -function createDirIfNotExists(dir) { - fs.mkdir(dir, '0775', function(err) { - if (err) { - if (err.code !== 'EEXIST') { - throw err; - } - } - }); -} - -// Create a list of the gulp tasks to execute for release -function listReleaseTasks(appDirectory) { - - createDirIfNotExists(RELEASE_DIR); - - var platforms = getPlatforms(); - - var releaseTasks = []; - - if (platforms.indexOf('linux64') !== -1) { - releaseTasks.push(function release_linux64_zip(){ - return release_zip('linux64', appDirectory); - }); - releaseTasks.push(function release_linux64_deb(done){ - return release_deb('linux64', appDirectory, done); - }); - releaseTasks.push(function release_linux64_rpm(done){ - return release_rpm('linux64', appDirectory, done); - }); - } - - if (platforms.indexOf('linux32') !== -1) { - releaseTasks.push(function release_linux32_zip(){ - return release_zip('linux32', appDirectory); - }); - releaseTasks.push(function release_linux32_deb(done){ - return release_deb('linux32', appDirectory, done); - }); - releaseTasks.push(function release_linux32_rpm(done){ - return release_rpm('linux32', appDirectory, done); - }); - } - - if (platforms.indexOf('osx64') !== -1) { - releaseTasks.push(function () { - return release_osx64(appDirectory); - }); - } - - if (platforms.indexOf('win32') !== -1) { - releaseTasks.push(function release_win32_zip() { - return release_zip('win32', appDirectory); - }); - releaseTasks.push(function release_win32(done) { - return release_win('win32', appDirectory, done); - }); - } - - if (platforms.indexOf('win64') !== -1) { - releaseTasks.push(function release_win64_zip() { - return release_zip('win64', appDirectory); - }); - releaseTasks.push(function release_win64(done) { - return release_win('win64', appDirectory, done); - }); - } - - return releaseTasks; -} diff --git a/index.html b/index.html index 32cc44cf..db27284c 100644 --- a/index.html +++ b/index.html @@ -9,15 +9,13 @@ - - - - - - + + + + - + @@ -167,8 +165,8 @@

    Welcome to the Enhanced Blackbox Explorer!

    @@ -181,6 +179,7 @@

    Welcome to the Enhanced Blackbox Explorer!

    Export Workspaces... Export CSV... + Export GPX... Open log file/video @@ -252,6 +251,11 @@

    View

    A +
  • + + Map + +
  • @@ -285,6 +289,9 @@

    Overlay

    +
    @@ -454,6 +461,7 @@

    Workspace

    @@ -490,6 +498,7 @@

    Workspace

    +
    @@ -550,14 +559,38 @@

    Field values

    + + +

    Statistics

    +
    + + + + + + + + + + + + + + + +
    Min/max values from this log
     MinMaxMean
    -
    +
    + +
    +
    +
    @@ -573,6 +606,9 @@

    Field values

    -
    +
    + - +
    -
    @@ -645,7 +681,7 @@ + title="Close and save changes">Apply changes
    @@ -684,7 +720,7 @@ - + +
    - + @@ -903,7 +939,7 @@
    - + @@ -938,9 +974,9 @@ - + @@ -991,9 +1027,9 @@
    + - + @@ -1029,6 +1065,14 @@ + + + + + + @@ -1260,7 +1304,7 @@
    - + @@ -2019,6 +2063,26 @@
    + + + + + + + + + +
    + + + + + + + + +
    + @@ -2816,6 +2880,39 @@ +
    +
    +
    Measurement System
    +
    +
    +
    +
    + + + +
    + + m/s +
    + kph +
    + mph +
    + +
    + + + + +
    + + meters +
    + feet +
    +
    + +
    @@ -2893,6 +2990,44 @@
    +
    +
    +
    Map Settings
    +
    +
    +
    + +
    + + + + + + + +
    + + + + +

    %

    +
    + + +

    %

    +
    + + +

    %

    +
    +
    +
    Watermark Settings
    @@ -3060,69 +3195,27 @@
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + -

    +

    Notice

    diff --git a/index.js b/index.js deleted file mode 100644 index 48d5fd0d..00000000 --- a/index.js +++ /dev/null @@ -1,74 +0,0 @@ - -openLinksInExternalBrowserByDefault(); - -$(document).ready(function () { - // translate to user-selected language - localize(); -}); - -function checkForConfiguratorUpdates() { - var releaseChecker = new ReleaseChecker('configurator', 'https://api.github.com/repos/betaflight/blackbox-log-viewer/releases'); - - releaseChecker.loadReleaseData(notifyOutdatedVersion); -} - -function notifyOutdatedVersion(releaseData) { - chrome.storage.local.get('checkForUnstableVersions', function (result) { - var showUnstableReleases = false; - if (result.checkForConfiguratorUnstableVersions) { - showUnstableReleases = true; - } - var versions = releaseData.filter(function (version) { - var semVerVersion = semver.parse(version.tag_name); - if (semVerVersion && (showUnstableReleases || semVerVersion.prerelease.length === 0)) { - return version; - } - }).sort(function (v1, v2) { - try { - return semver.compare(v2.tag_name, v1.tag_name); - } catch (e) { - return false; - } - }); - - if (versions.length > 0 && semver.lt(getManifestVersion(), versions[0].tag_name)) { - GUI.log(chrome.i18n.getMessage('updateNotice', [versions[0].tag_name, versions[0].html_url])); - - var dialog = $('.dialogUpdate')[0]; - - $('.dialogUpdate-content').html(chrome.i18n.getMessage('updateNotice', [versions[0].tag_name, versions[0].html_url])); - - $('.dialogUpdate-closebtn').click(function () { - dialog.close(); - }); - - $('.dialogUpdate-websitebtn').click(function () { - dialog.close(); - - window.open(versions[0].html_url); - }); - - dialog.showModal(); - } - }); -} - -checkForConfiguratorUpdates(); - -function openLinksInExternalBrowserByDefault() { - - const gui = require('nw.gui'); - - //Get the current window - const win = gui.Window.get(); - - win.on('new-win-policy', function(frame, url, policy) { - // do not open the window - policy.ignore(); - // and open it in external browser - gui.Shell.openExternal(url); - }); - -} - - diff --git a/js/datastream.js b/js/datastream.js deleted file mode 100644 index a24011d8..00000000 --- a/js/datastream.js +++ /dev/null @@ -1,172 +0,0 @@ -"use strict"; - -var ArrayDataStream; - -(function(){ - var EOF = -1; - - /* - * Take an array of unsigned byte data and present it as a stream with various methods - * for reading data in different formats. - */ - ArrayDataStream = function(data, start, end) { - this.data = data; - this.eof = false; - this.start = start === undefined ? 0 : start; - this.end = end === undefined ? data.length : end; - this.pos = this.start; - }; - - /** - * Read a single byte from the string and turn it into a JavaScript string (assuming ASCII). - * - * @returns String containing one character, or EOF if the end of file was reached (eof flag - * is set). - */ - ArrayDataStream.prototype.readChar = function() { - if (this.pos < this.end) - return String.fromCharCode(this.data[this.pos++]); - - this.eof = true; - return EOF; - }; - - /** - * Read one unsigned byte from the stream - * - * @returns Unsigned byte, or EOF if the end of file was reached (eof flag is set). - */ - ArrayDataStream.prototype.readByte = function() { - if (this.pos < this.end) - return this.data[this.pos++]; - - this.eof = true; - return EOF; - }; - - //Synonym: - ArrayDataStream.prototype.readU8 = ArrayDataStream.prototype.readByte; - - ArrayDataStream.prototype.readS8 = function() { - return signExtend8Bit(this.readByte()); - }; - - ArrayDataStream.prototype.unreadChar = function(c) { - this.pos--; - }; - - ArrayDataStream.prototype.peekChar = function() { - if (this.pos < this.end) - return String.fromCharCode(this.data[this.pos]); - - this.eof = true; - return EOF; - }; - - /** - * Read a (maximally 32-bit) unsigned integer from the stream which was encoded in Variable Byte format. - * - * @returns the unsigned integer, or 0 if a valid integer could not be read (EOF was reached or integer format - * was invalid). - */ - ArrayDataStream.prototype.readUnsignedVB = function() { - var - i, b, - shift = 0, result = 0; - - // 5 bytes is enough to encode 32-bit unsigned quantities - for (i = 0; i < 5; i++) { - b = this.readByte(); - - if (b == EOF) - return 0; - - result = result | ((b & ~0x80) << shift); - - // Final byte? - if (b < 128) { - /* - * Force the 32-bit integer to be reinterpreted as unsigned by doing an unsigned right shift, so that - * the top bit being set doesn't cause it to interpreted as a negative number. - */ - return result >>> 0; - } - - shift += 7; - } - - // This VB-encoded int is too long! - return 0; - }; - - ArrayDataStream.prototype.readSignedVB = function() { - var unsigned = this.readUnsignedVB(); - - // Apply ZigZag decoding to recover the signed value - return (unsigned >>> 1) ^ -(unsigned & 1); - }; - - ArrayDataStream.prototype.readString = function(length) { - var - chars = new Array(length), - i; - - for (i = 0; i < length; i++) { - chars[i] = this.readChar(); - } - - return chars.join(""); - }; - - ArrayDataStream.prototype.readS16 = function() { - var - b1 = this.readByte(), - b2 = this.readByte(); - - return signExtend16Bit(b1 | (b2 << 8)); - }; - - ArrayDataStream.prototype.readU16 = function() { - var - b1 = this.readByte(), - b2 = this.readByte(); - - return b1 | (b2 << 8); - }; - - ArrayDataStream.prototype.readU32 = function() { - var - b1 = this.readByte(), - b2 = this.readByte(), - b3 = this.readByte(), - b4 = this.readByte(); - return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); - }; - - /** - * Search for the string 'needle' beginning from the current stream position up - * to the end position. Return the offset of the first occurance found. - * - * @param needle - * String to search for - * @returns Position of the start of needle in the stream, or -1 if it wasn't - * found - */ - ArrayDataStream.prototype.nextOffsetOf = function(needle) { - var i, j; - - for (i = this.pos; i <= this.end - needle.length; i++) { - if (this.data[i] == needle[0]) { - for (j = 1; j < needle.length && this.data[i + j] == needle[j]; j++) - ; - - if (j == needle.length) - return i; - } - } - - return -1; - }; - - ArrayDataStream.prototype.EOF = EOF; -}()); \ No newline at end of file diff --git a/js/flightlog_fielddefs.js b/js/flightlog_fielddefs.js deleted file mode 100644 index 09d82586..00000000 --- a/js/flightlog_fielddefs.js +++ /dev/null @@ -1,554 +0,0 @@ -"use strict"; - -function makeReadOnly(x) { - // Make read-only if browser supports it: - if (Object.freeze) { - return Object.freeze(x); - } - - // Otherwise a no-op - return x; -} - -// Some constants used at different places -const MAX_MOTOR_NUMBER = 8; -const DSHOT_MIN_VALUE = 48; -const DSHOT_MAX_VALUE = 2047; -const DSHOT_RANGE = DSHOT_MAX_VALUE - DSHOT_MIN_VALUE; -const ANALOG_MIN_VALUE = 1000; - -// Fields definitions for lists -var - FlightLogEvent = makeReadOnly({ - SYNC_BEEP: 0, - - AUTOTUNE_CYCLE_START: 10, - AUTOTUNE_CYCLE_RESULT: 11, - AUTOTUNE_TARGETS: 12, - INFLIGHT_ADJUSTMENT: 13, - LOGGING_RESUME: 14, - DISARM: 15, - - GTUNE_CYCLE_RESULT: 20, - FLIGHT_MODE: 30, - TWITCH_TEST: 40, // Feature for latency testing - - CUSTOM : 250, // Virtual Event Code - Never part of Log File. - CUSTOM_BLANK : 251, // Virtual Event Code - Never part of Log File. - No line shown - LOG_END: 255 - }), - - // Add a general axis index. - AXIS = makeReadOnly({ - ROLL: 0, - PITCH: 1, - YAW: 2 - }), - - FLIGHT_LOG_FLIGHT_MODE_NAME = [], - - FLIGHT_LOG_FLIGHT_MODE_NAME_PRE_3_3 = makeReadOnly([ - 'ARM', - 'ANGLE', - 'HORIZON', - 'BARO', - 'ANTIGRAVITY', - 'MAG', - 'HEADFREE', - 'HEADADJ', - 'CAMSTAB', - 'CAMTRIG', - 'GPSHOME', - 'GPSHOLD', - 'PASSTHRU', - 'BEEPER', - 'LEDMAX', - 'LEDLOW', - 'LLIGHTS', - 'CALIB', - 'GOV', - 'OSD', - 'TELEMETRY', - 'GTUNE', - 'SONAR', - 'SERVO1', - 'SERVO2', - 'SERVO3', - 'BLACKBOX', - 'FAILSAFE', - 'AIRMODE', - '3DDISABLE', - 'FPVANGLEMIX', - 'BLACKBOXERASE', - 'CAMERA1', - 'CAMERA2', - 'CAMERA3', - 'FLIPOVERAFTERCRASH', - 'PREARM', - ]), - - FLIGHT_LOG_FLIGHT_MODE_NAME_POST_3_3 = makeReadOnly([ - 'ARM', - 'ANGLE', - 'HORIZON', - 'MAG', - 'BARO', - 'GPSHOME', - 'GPSHOLD', - 'HEADFREE', - 'PASSTHRU', - 'RANGEFINDER', - 'FAILSAFE', - 'GPSRESCUE', - 'ANTIGRAVITY', - 'HEADADJ', - 'CAMSTAB', - 'CAMTRIG', - 'BEEPER', - 'LEDMAX', - 'LEDLOW', - 'LLIGHTS', - 'CALIB', - 'GOV', - 'OSD', - 'TELEMETRY', - 'GTUNE', - 'SERVO1', - 'SERVO2', - 'SERVO3', - 'BLACKBOX', - 'AIRMODE', - '3D', - 'FPVANGLEMIX', - 'BLACKBOXERASE', - 'CAMERA1', - 'CAMERA2', - 'CAMERA3', - 'FLIPOVERAFTERCRASH', - 'PREARM', - 'BEEPGPSCOUNT', - 'VTXPITMODE', - 'USER1', - 'USER2', - 'USER3', - 'USER4', - 'PIDAUDIO', - 'ACROTRAINER', - 'VTXCONTROLDISABLE', - 'LAUNCHCONTROL', - ]), - - FLIGHT_LOG_FEATURES = makeReadOnly([ - 'RX_PPM', - 'VBAT', - 'INFLIGHT_ACC_CAL', - 'RX_SERIAL', - 'MOTOR_STOP', - 'SERVO_TILT', - 'SOFTSERIAL', - 'GPS', - 'FAILSAFE', - 'SONAR', - 'TELEMETRY', - 'CURRENT_METER', - '3D', - 'RX_PARALLEL_PWM', - 'RX_MSP', - 'RSSI_ADC', - 'LED_STRIP', - 'DISPLAY', - 'ONESHOT125', - 'BLACKBOX', - 'CHANNEL_FORWARDING', - 'TRANSPONDER', - 'AIRMODE', - 'SUPEREXPO_RATES' - ]), - - PID_CONTROLLER_TYPE = ([ - 'UNUSED', - 'MWREWRITE', - 'LUXFLOAT' - ]), - - PID_DELTA_TYPE = makeReadOnly([ - 'ERROR', - 'MEASUREMENT' - ]), - - OFF_ON = makeReadOnly([ - "OFF", - "ON" - ]), - - FAST_PROTOCOL = makeReadOnly([ - "PWM", - "ONESHOT125", - "ONESHOT42", - "MULTISHOT", - "BRUSHED", - "DSHOT150", - "DSHOT300", - "DSHOT600", - "DSHOT1200", - "PROSHOT1000", - ]), - - MOTOR_SYNC = makeReadOnly([ - "SYNCED", - "UNSYNCED" - ]), - - SERIALRX_PROVIDER = makeReadOnly([ - "SPEK1024", - "SPEK2048", - "SBUS", - "SUMD", - "SUMH", - "XB-B", - "XB-B-RJ01", - "IBUS", - "JETIEXBUS", - "CRSF", - "SRXL", - "CUSTOM", - "FPORT", - ]), - - ANTI_GRAVITY_MODE = makeReadOnly([ - "SMOOTH", - "STEP" - ]), - - RC_SMOOTHING_TYPE = makeReadOnly([ - "INTERPOLATION", - "FILTER" - ]), - - RC_SMOOTHING_INPUT_TYPE = makeReadOnly([ - "PT1", - "BIQUAD" - ]), - - RC_SMOOTHING_DERIVATIVE_TYPE = makeReadOnly([ - "PT1", - "BIQUAD" - ]), - - RC_SMOOTHING_MODE = makeReadOnly([ - "OFF", - "ON" - ]), - - RC_SMOOTHING_DEBUG_AXIS = makeReadOnly([ - "ROLL", - "PITCH", - "YAW", - "THROTTLE" - ]), - - RC_INTERPOLATION = makeReadOnly([ - "OFF", - "DEFAULT", - "AUTO", - "MANUAL" - ]), - - FILTER_TYPE = makeReadOnly([ - "PT1", - "BIQUAD", - "PT2", - "PT3", - ]), - - DEBUG_MODE = [], - - DEBUG_MODE_COMPLETE = makeReadOnly([ - "NONE", - "CYCLETIME", - "BATTERY", - "GYRO", - "ACCELEROMETER", - "MIXER", - "AIRMODE", - "PIDLOOP", - "NOTCH", - "RC_INTERPOLATION", - "VELOCITY", - "DTERM_FILTER", - "ANGLERATE", - "ESC_SENSOR", - "SCHEDULER", - "STACK", - "ESC_SENSOR_RPM", - "ESC_SENSOR_TMP", - "ALTITUDE", - "FFT", - "FFT_TIME", - "FFT_FREQ", - "RX_FRSKY_SPI", - "GYRO_RAW", - "DUAL_GYRO", - "DUAL_GYRO_RAW", - "DUAL_GYRO_COMBINED", - "DUAL_GYRO_DIFF", - "MAX7456_SIGNAL", - "MAX7456_SPICLOCK", - "SBUS", - "FPORT", - "RANGEFINDER", - "RANGEFINDER_QUALITY", - "LIDAR_TF", - "ADC_INTERNAL", - "RUNAWAY_TAKEOFF", - "SDIO", - "CURRENT_SENSOR", - "USB", - "SMARTAUDIO", - "RTH", - "ITERM_RELAX", - "ACRO_TRAINER", - "RC_SMOOTHING", - "RX_SIGNAL_LOSS", - "RC_SMOOTHING_RATE", - "ANTI_GRAVITY", - "DYN_LPF", - "RX_SPECTRUM_SPI", - "DSHOT_RPM_TELEMETRY", - "RPM_FILTER", - "D_MIN", - "AC_CORRECTION", - "AC_ERROR", - "DUAL_GYRO_SCALED", - "DSHOT_RPM_ERRORS", - "CRSF_LINK_STATISTICS_UPLINK", - "CRSF_LINK_STATISTICS_PWR", - "CRSF_LINK_STATISTICS_DOWN", - "BARO", - "GPS_RESCUE_THROTTLE_PID", - "DYN_IDLE", - "FF_LIMIT", - "FF_INTERPOLATED", - "BLACKBOX_OUTPUT", - "GYRO_SAMPLE", - "RX_TIMING", - "D_LPF", - "VTX_TRAMP", - "GHST", - "SCHEDULER_DETERMINISM", - "TIMING_ACCURACY", - "RX_EXPRESSLRS_SPI", - "RX_EXPRESSLRS_PHASELOCK", - ]), - - SUPER_EXPO_YAW = makeReadOnly([ - "OFF", - "ON", - "ALWAYS" - ]), - - DTERM_DIFFERENTIATOR = makeReadOnly([ - "STANDARD", - "ENHANCED" - ]), - - GYRO_LPF = makeReadOnly([ - "OFF", - "188HZ", - "98HZ", - "42HZ", - "20HZ", - "10HZ", - "5HZ", - "EXPERIMENTAL" - ]), - - GYRO_HARDWARE_LPF = makeReadOnly([ - "NORMAL", - "EXPERIMENTAL", - "1KHZ_SAMPLING" - ]), - - GYRO_32KHZ_HARDWARE_LPF = makeReadOnly([ - "NORMAL", - "EXPERIMENTAL" - ]), - - ACC_HARDWARE = makeReadOnly([ - "AUTO", - "NONE", - "ADXL345", - "MPU6050", - "MMA8452", - "BMA280", - "LSM303DLHC", - "MPU6000", - "MPU6500", - "FAKE" - ]), - - BARO_HARDWARE = makeReadOnly([ - "AUTO", - "NONE", - "BMP085", - "MS5611", - "BMP280" - ]), - - MAG_HARDWARE = makeReadOnly([ - "AUTO", - "NONE", - "HMC5883", - "AK8975", - "AK8963" - ]), - - FLIGHT_LOG_FLIGHT_STATE_NAME = makeReadOnly([ - "GPS_FIX_HOME", - "GPS_FIX", - "CALIBRATE_MAG", - "SMALL_ANGLE", - "FIXED_WING" - ]), - - FLIGHT_LOG_FAILSAFE_PHASE_NAME = makeReadOnly([ - "IDLE", - "RX_LOSS_DETECTED", - "LANDING", - "LANDED" - ]), - - FFT_CALC_STEPS = makeReadOnly([ - "ARM_CFFT_F32", - "BITREVERSAL", - "STAGE_RFFT_F32", - "ARM_CMPLX_MAG_F32", - "CALC_FREQUENCIES", - "UPDATE_FILTERS", - "HANNING" - ]), - - ITERM_RELAX = makeReadOnly([ - "OFF", - "RP", - "RPY", - "RP_INC", - "RPY_INC", - ]), - - ITERM_RELAX_TYPE = makeReadOnly([ - "GYRO", - "SETPOINT", - ]), - - DYN_NOTCH_RANGE = makeReadOnly([ - "HIGH", - "MEDIUM", - "LOW", - "AUTO", - ]), - - FLIGHT_LOG_DISARM_REASON = makeReadOnly([ - "ARMING_DISABLED", - "FAILSAFE", - "THROTTLE_TIMEOUT", - "STICKS", - "SWITCH", - "CRASH_PROTECTION", - "RUNAWAY_TAKEOFF", - "GPS_RESCUE", - "SERIAL_IO", - ]), - - RATES_TYPE = makeReadOnly([ - "BETAFLIGHT", - "RACEFLIGHT", - "KISS", - "ACTUAL", - "QUICK", - ]), - - GYRO_TO_USE = makeReadOnly([ - "FIRST", - "SECOND", - "BOTH", - ]), - - FF_AVERAGING = makeReadOnly([ - "OFF", - "2_POINT", - "3_POINT", - "4_POINT", - ]), - - SIMPLIFIED_PIDS_MODE = makeReadOnly([ - "OFF", - "ON - RP", - "ON - RPY", - ]), - - THROTTLE_LIMIT_TYPE = makeReadOnly([ - "OFF", - "SCALE", - "CLIP", - ]); - -function adjustFieldDefsList(firmwareType, firmwareVersion) { - if((firmwareType == FIRMWARE_TYPE_BETAFLIGHT) && semver.gte(firmwareVersion, '3.3.0')) { - - // Debug names - DEBUG_MODE = DEBUG_MODE_COMPLETE.slice(0); - DEBUG_MODE.splice(DEBUG_MODE.indexOf('MIXER'), 1); - DEBUG_MODE.splice(DEBUG_MODE.indexOf('AIRMODE'), 1); - DEBUG_MODE.splice(DEBUG_MODE.indexOf('VELOCITY'), 1); - DEBUG_MODE.splice(DEBUG_MODE.indexOf('DTERM_FILTER'), 1); - - if(semver.gte(firmwareVersion, '3.4.0')) { - DEBUG_MODE.splice(DEBUG_MODE.indexOf('GYRO'), 1, 'GYRO_FILTERED'); - DEBUG_MODE.splice(DEBUG_MODE.indexOf('NOTCH'), 1, 'GYRO_SCALED'); - } - if(semver.gte(firmwareVersion, '4.0.0')) { - DEBUG_MODE.splice(DEBUG_MODE.indexOf('GYRO_RAW'), 0, 'RX_SFHSS_SPI'); - } - if(semver.gte(firmwareVersion, '4.1.0')) { - DEBUG_MODE.splice(DEBUG_MODE.indexOf('DUAL_GYRO'), 1); - DEBUG_MODE.splice(DEBUG_MODE.indexOf('DUAL_GYRO_COMBINED'), 1); - } - if(semver.gte(firmwareVersion, '4.3.0')) { - DEBUG_MODE.splice(DEBUG_MODE.indexOf('FF_INTERPOLATED'), 1, 'FEEDFORWARD'); - DEBUG_MODE.splice(DEBUG_MODE.indexOf('FF_LIMIT'), 1, 'FEEDFORWARD_LIMIT'); - } - DEBUG_MODE = makeReadOnly(DEBUG_MODE); - - // Flight mode names - FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_POST_3_3.slice(0); - if (semver.lt(firmwareVersion, '3.4.0')) { - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSRESCUE'), 1); - } - if (semver.gte(firmwareVersion, '3.5.0')) { - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('RANGEFINDER'), 1); - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('CAMTRIG'), 1); - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('LEDMAX'), 1); - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('LLIGHTS'), 1); - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GOV'), 1); - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GTUNE'), 1); - } - if (semver.gte(firmwareVersion, '4.0.0')) { - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('BARO'), 1); - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSHOME'), 1); - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSHOLD'), 1); - } - FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME); - - } else { - DEBUG_MODE = DEBUG_MODE_COMPLETE; - - FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_PRE_3_3.slice(0); - - if((firmwareType == FIRMWARE_TYPE_BETAFLIGHT) && semver.lte(firmwareVersion, '3.1.6')) { - FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('ANTIGRAVITY'), 1); - } - - FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME); - } -} diff --git a/js/flightlog_fields_presenter.js b/js/flightlog_fields_presenter.js deleted file mode 100644 index 6493035a..00000000 --- a/js/flightlog_fields_presenter.js +++ /dev/null @@ -1,1159 +0,0 @@ -"use strict"; - -function FlightLogFieldPresenter() { -} - -(function() { - const FRIENDLY_FIELD_NAMES = { - - 'axisP[all]': 'PID P', - 'axisP[0]': 'PID P [roll]', - 'axisP[1]': 'PID P [pitch]', - 'axisP[2]': 'PID P [yaw]', - - 'axisI[all]': 'PID I', - 'axisI[0]': 'PID I [roll]', - 'axisI[1]': 'PID I [pitch]', - 'axisI[2]': 'PID I [yaw]', - - 'axisD[all]': 'PID D', - 'axisD[0]': 'PID D [roll]', - 'axisD[1]': 'PID D [pitch]', - 'axisD[2]': 'PID D [yaw]', - - 'axisF[all]': 'PID Feedforward', - 'axisF[0]': 'PID Feedforward [roll]', - 'axisF[1]': 'PID Feedforward [pitch]', - 'axisF[2]': 'PID Feedforward [yaw]', - - //Virtual field - 'axisSum[all]': 'PID Sum', - 'axisSum[0]' : 'PID Sum [roll]', - 'axisSum[1]' : 'PID Sum [pitch]', - 'axisSum[2]' : 'PID Sum [yaw]', - - //Virtual field - 'axisError[all]': 'PID Error', - 'axisError[0]' : 'PID Error [roll]', - 'axisError[1]' : 'PID Error [pitch]', - 'axisError[2]' : 'PID Error [yaw]', - - //Virtual field - 'rcCommands[all]': 'Setpoints', - 'rcCommands[0]' : 'Setpoint [roll]', - 'rcCommands[1]' : 'Setpoint [pitch]', - 'rcCommands[2]' : 'Setpoint [yaw]', - 'rcCommands[3]' : 'Setpoint [throttle]', - - 'rcCommand[all]': 'RC Commands', - 'rcCommand[0]': 'RC Command [roll]', - 'rcCommand[1]': 'RC Command [pitch]', - 'rcCommand[2]': 'RC Command [yaw]', - 'rcCommand[3]': 'RC Command [throttle]', - - 'gyroADC[all]': 'Gyros', - 'gyroADC[0]': 'Gyro [roll]', - 'gyroADC[1]': 'Gyro [pitch]', - 'gyroADC[2]': 'Gyro [yaw]', - - //End-users prefer 1-based indexing - 'motor[all]': 'Motors', - 'motor[0]': 'Motor [1]', - 'motor[1]': 'Motor [2]', - 'motor[2]': 'Motor [3]', - 'motor[3]': 'Motor [4]', - 'motor[4]': 'Motor [5]', - 'motor[5]': 'Motor [6]', - 'motor[6]': 'Motor [7]', - 'motor[7]': 'Motor [8]', - - 'motorLegacy[all]': 'Motors (Legacy)', - 'motorLegacy[0]': 'Motor (Legacy) [1]', - 'motorLegacy[1]': 'Motor (Legacy) [2]', - 'motorLegacy[2]': 'Motor (Legacy) [3]', - 'motorLegacy[3]': 'Motor (Legacy) [4]', - 'motorLegacy[4]': 'Motor (Legacy) [5]', - 'motorLegacy[5]': 'Motor (Legacy) [6]', - 'motorLegacy[6]': 'Motor (Legacy) [7]', - 'motorLegacy[7]': 'Motor (Legacy) [8]', - - 'servo[all]': 'Servos', - 'servo[5]': 'Servo Tail', - - 'vbatLatest': 'Battery volt.', - 'amperageLatest': 'Amperage', - 'BaroAlt': 'Barometer', - - 'heading[all]': 'Heading', - 'heading[0]': 'Heading [roll]', - 'heading[1]': 'Heading [pitch]', - 'heading[2]': 'Heading [yaw]', - - 'accSmooth[all]': 'Accel.', - 'accSmooth[0]': 'Accel. [X]', - 'accSmooth[1]': 'Accel. [Y]', - 'accSmooth[2]': 'Accel. [Z]', - - 'magADC[all]': 'Compass', - 'magADC[0]': 'Compass [X]', - 'magADC[1]': 'Compass [Y]', - 'magADC[2]': 'Compass [Z]', - - 'flightModeFlags': 'Flight Mode Flags', - 'stateFlags': 'State Flags', - 'failsafePhase': 'Failsafe Phase', - 'rxSignalReceived': 'RX Signal Received', - 'rxFlightChannelsValid': 'RX Flight Ch. Valid', - 'rssi': 'RSSI', - }; - - const DEBUG_FRIENDLY_FIELD_NAMES_INITIAL = { - 'NONE' : { - 'debug[all]':'Debug [all]', - 'debug[0]':'Debug [0]', - 'debug[1]':'Debug [1]', - 'debug[2]':'Debug [2]', - 'debug[3]':'Debug [3]', - }, - 'CYCLETIME' : { - 'debug[all]':'Debug Cycle Time', - 'debug[0]':'Cycle Time', - 'debug[1]':'CPU Load', - 'debug[2]':'Motor Update', - 'debug[3]':'Motor Deviation', - }, - 'BATTERY' : { - 'debug[all]':'Debug Battery', - 'debug[0]':'Battery Volt ADC', - 'debug[1]':'Battery Volt', - 'debug[2]':'Not Used', - 'debug[3]':'Not Used', - }, - 'GYRO' : { - 'debug[all]':'Debug Gyro', - 'debug[0]':'Gyro Raw [X]', - 'debug[1]':'Gyro Raw [Y]', - 'debug[2]':'Gyro Raw [Z]', - 'debug[3]':'Not Used', - }, - 'GYRO_FILTERED' : { - 'debug[all]':'Debug Gyro Filtered', - 'debug[0]':'Gyro Filtered [X]', - 'debug[1]':'Gyro Filtered [Y]', - 'debug[2]':'Gyro Filtered [Z]', - 'debug[3]':'Not Used', - }, - 'ACCELEROMETER' : { - 'debug[all]':'Debug Accel.', - 'debug[0]':'Accel. Raw [X]', - 'debug[1]':'Accel. Raw [Y]', - 'debug[2]':'Accel. Raw [Z]', - 'debug[3]':'Not Used', - }, - 'MIXER' : { - 'debug[all]':'Debug Mixer', - 'debug[0]':'Roll-Pitch-Yaw Mix [0]', - 'debug[1]':'Roll-Pitch-Yaw Mix [1]', - 'debug[2]':'Roll-Pitch-Yaw Mix [2]', - 'debug[3]':'Roll-Pitch-Yaw Mix [3]', - }, - 'PIDLOOP' : { - 'debug[all]':'Debug PID', - 'debug[0]':'Wait Time', - 'debug[1]':'Sub Update Time', - 'debug[2]':'PID Update Time', - 'debug[3]':'Motor Update Time', - }, - 'NOTCH' : { - 'debug[all]':'Debug Notch', - 'debug[0]':'Gyro Pre-Notch [roll]', - 'debug[1]':'Gyro Pre-Notch [pitch]', - 'debug[2]':'Gyro Pre-Notch [yaw]', - 'debug[3]':'Not Used', - }, - 'GYRO_SCALED' : { - 'debug[all]':'Debug Gyro Scaled', - 'debug[0]':'Gyro Scaled [roll]', - 'debug[1]':'Gyro Scaled [pitch]', - 'debug[2]':'Gyro Scaled [yaw]', - 'debug[3]':'Not Used', - }, - 'RC_INTERPOLATION' : { - 'debug[all]':'Debug RC Interpolation', - 'debug[0]':'Raw RC Command [roll]', - 'debug[1]':'Current RX Refresh Rate', - 'debug[2]':'Interpolation Step Count', - 'debug[3]':'RC Setpoint [roll]', - }, - 'DTERM_FILTER' : { - 'debug[all]':'Debug Filter', - 'debug[0]':'DTerm Filter [roll]', - 'debug[1]':'DTerm Filter [pitch]', - 'debug[2]':'Not Used', - 'debug[3]':'Not Used', - }, - 'ANGLERATE' : { - 'debug[all]':'Debug Angle Rate', - 'debug[0]':'Angle Rate[roll]', - 'debug[1]':'Angle Rate[pitch]', - 'debug[2]':'Angle Rate[yaw]', - 'debug[3]':'Not Used', - }, - 'ESC_SENSOR' : { - 'debug[all]':'ESC Sensor', - 'debug[0]':'Motor Index', - 'debug[1]':'Timeouts', - 'debug[2]':'CNC errors', - 'debug[3]':'Data age', - }, - 'SCHEDULER' : { - 'debug[all]':'Scheduler', - 'debug[0]':'Not Used', - 'debug[1]':'Not Used', - 'debug[2]':'Schedule Time', - 'debug[3]':'Function Exec Time', - }, - 'STACK' : { - 'debug[all]':'Stack', - 'debug[0]':'Stack High Mem', - 'debug[1]':'Stack Low Mem', - 'debug[2]':'Stack Current', - 'debug[3]':'Stack p', - }, - 'ESC_SENSOR_RPM' : { - 'debug[all]':'ESC Sensor RPM', - 'debug[0]':'Motor 1', - 'debug[1]':'Motor 2', - 'debug[2]':'Motor 3', - 'debug[3]':'Motor 4', - }, - 'ESC_SENSOR_TMP' : { - 'debug[all]':'ESC Sensor Temp', - 'debug[0]':'Motor 1', - 'debug[1]':'Motor 2', - 'debug[2]':'Motor 3', - 'debug[3]':'Motor 4', - }, - 'ALTITUDE' : { - 'debug[all]':'Altitude', - 'debug[0]':'GPS Trust * 100', - 'debug[1]':'Baro Altitude', - 'debug[2]':'GPS Altitude', - 'debug[3]':'Vario', - }, - 'FFT' : { - 'debug[all]':'Debug FFT', - 'debug[0]':'Gyro Scaled [dbg-axis]', - 'debug[1]':'Gyro Pre-Dyn [dbg-axis]', - 'debug[2]':'Gyro Downsampled [roll]', - 'debug[3]':'FFT Center Index [roll]', - }, - 'FFT_TIME' : { - 'debug[all]':'Debug FFT TIME', - 'debug[0]':'Active calc step', - 'debug[1]':'Step duration', - 'debug[2]':'Additional steps', - 'debug[3]':'Not used', - }, - 'FFT_FREQ' : { - 'debug[all]':'Debug FFT FREQ', - 'debug[0]':'Center Freq [roll]', - 'debug[1]':'Center Freq [pitch]', - 'debug[2]':'Gyro Pre-Dyn [dbg-axis]', - 'debug[3]':'Gyro Scaled [dbg-axis]', - }, - 'RX_FRSKY_SPI' : { - 'debug[all]':'FrSky SPI Rx', - 'debug[0]':'Looptime', - 'debug[1]':'Packet', - 'debug[2]':'Missing Packets', - 'debug[3]':'State', - }, - 'RX_SFHSS_SPI' : { - 'debug[all]':'SFHSS SPI Rx', - 'debug[0]':'State', - 'debug[1]':'Missing Frame', - 'debug[2]':'Offset Max', - 'debug[3]':'Offset Min', - }, - 'GYRO_RAW' : { - 'debug[all]':'Debug Gyro Raw', - 'debug[0]':'Gyro Raw [X]', - 'debug[1]':'Gyro Raw [Y]', - 'debug[2]':'Gyro Raw [Z]', - 'debug[3]':'Not Used', - }, - 'DUAL_GYRO' : { - 'debug[all]':'Debug Dual Gyro', - 'debug[0]':'Gyro 1 Filtered [roll]', - 'debug[1]':'Gyro 1 Filtered [pitch]', - 'debug[2]':'Gyro 2 Filtered [roll]', - 'debug[3]':'Gyro 2 Filtered [pitch]', - }, - 'DUAL_GYRO_RAW': { - 'debug[all]':'Debug Dual Gyro Raw', - 'debug[0]':'Gyro 1 Raw [roll]', - 'debug[1]':'Gyro 1 Raw [pitch]', - 'debug[2]':'Gyro 2 Raw [roll]', - 'debug[3]':'Gyro 2 Raw [pitch]', - }, - 'DUAL_GYRO_COMBINED': { - 'debug[all]':'Debug Dual Combined', - 'debug[0]':'Not Used', - 'debug[1]':'Gyro Filtered [roll]', - 'debug[2]':'Gyro Filtered [pitch]', - 'debug[3]':'Not Used', - }, - 'DUAL_GYRO_DIFF': { - 'debug[all]':'Debug Dual Gyro Diff', - 'debug[0]':'Gyro Diff [roll]', - 'debug[1]':'Gyro Diff [pitch]', - 'debug[2]':'Gyro Diff [yaw]', - 'debug[3]':'Not Used', - }, - 'MAX7456_SIGNAL' : { - 'debug[all]':'Max7456 Signal', - 'debug[0]':'Mode Reg', - 'debug[1]':'Sense', - 'debug[2]':'ReInit', - 'debug[3]':'Rows', - }, - 'MAX7456_SPICLOCK' : { - 'debug[all]':'Max7456 SPI Clock', - 'debug[0]':'Overclock', - 'debug[1]':'DevType', - 'debug[2]':'Divisor', - 'debug[3]':'not used', - }, - 'SBUS' : { - 'debug[all]':'SBus Rx', - 'debug[0]':'Frame Flags', - 'debug[1]':'State Flags', - 'debug[2]':'Frame Time', - 'debug[3]':'not used', - }, - 'FPORT' : { - 'debug[all]':'FPort Rx', - 'debug[0]':'Frame Interval', - 'debug[1]':'Frame Errors', - 'debug[2]':'Last Error', - 'debug[3]':'Telemetry Interval', - }, - 'RANGEFINDER' : { - 'debug[all]':'Rangefinder', - 'debug[0]':'not used', - 'debug[1]':'Raw Altitude', - 'debug[2]':'Calc Altituded', - 'debug[3]':'SNR', - }, - 'RANGEFINDER_QUALITY' : { - 'debug[all]':'Rangefinder Quality', - 'debug[0]':'Raw Altitude', - 'debug[1]':'SNR Threshold Reached', - 'debug[2]':'Dyn Distance Threshold', - 'debug[3]':'Is Surface Altitude Valid', - }, - 'LIDAR_TF' : { - 'debug[all]':'Lidar TF', - 'debug[0]':'Distance', - 'debug[1]':'Strength', - 'debug[2]':'TF Frame (4)', - 'debug[3]':'TF Frame (5)', - }, - 'ADC_INTERNAL' : { - 'debug[all]':'ADC Internal', - 'debug[0]':'Core Temp', - 'debug[1]':'VRef Internal Sample', - 'debug[2]':'Temp Sensor Sample', - 'debug[3]':'Vref mV', - }, - 'RUNAWAY_TAKEOFF' : { - 'debug[all]':'Runaway Takeoff', - 'debug[0]':'Enabled', - 'debug[1]':'Activating Delay', - 'debug[2]':'Deactivating Delay', - 'debug[3]':'Deactivating Time', - }, - 'CURRENT_SENSOR' : { - 'debug[all]':'Current Sensor', - 'debug[0]':'milliVolts', - 'debug[1]':'centiAmps', - 'debug[2]':'Amps Latest', - 'debug[3]':'mAh Drawn', - }, - 'USB' : { - 'debug[all]':'USB', - 'debug[0]':'Cable In', - 'debug[1]':'VCP Connected', - 'debug[2]':'not used', - 'debug[3]':'not used', - }, - 'SMART AUDIO' : { - 'debug[all]':'Smart Audio VTx', - 'debug[0]':'Device + Version', - 'debug[1]':'Channel', - 'debug[2]':'Frequency', - 'debug[3]':'Power', - }, - 'RTH' : { - 'debug[all]':'RTH', - 'debug[0]':'Rescue Throttle', - 'debug[1]':'Rescue Angle', - 'debug[2]':'Altitude Adjustment', - 'debug[3]':'Rescue State', - }, - 'ITERM_RELAX' : { - 'debug[all]':'I-term Relax', - 'debug[0]':'Setpoint HPF [roll]', - 'debug[1]':'I Relax Factor [roll]', - 'debug[2]':'Relaxed I Error [roll]', - 'debug[3]':'Axis Error [roll]', - }, - 'ACRO_TRAINER' : { - 'debug[all]':'Acro Trainer (a_t_axis)', - 'debug[0]':'Current Angle * 10 [deg]', - 'debug[1]':'Axis State', - 'debug[2]':'Correction amount', - 'debug[3]':'Projected Angle * 10 [deg]', - }, - 'RC_SMOOTHING' : { - 'debug[all]':'Debug RC Smoothing', - 'debug[0]':'Raw RC Command', - 'debug[1]':'Raw RC Derivative', - 'debug[2]':'Smoothed RC Derivative', - 'debug[3]':'RX Refresh Rate', - }, - 'RX_SIGNAL_LOSS' : { - 'debug[all]':'Rx Signal Loss', - 'debug[0]':'Signal Received', - 'debug[1]':'Failsafe', - 'debug[2]':'Not used', - 'debug[3]':'Throttle', - }, - 'RC_SMOOTHING_RATE' : { - 'debug[all]':'Debug RC Smoothing Rate', - 'debug[0]':'Current RX Refresh Rate', - 'debug[1]':'Training Step Count', - 'debug[2]':'Average RX Refresh Rate', - 'debug[3]':'Sampling State', - }, - 'ANTI_GRAVITY' : { - 'debug[all]':'I-term Relax', - 'debug[0]':'Base I gain * 1000', - 'debug[1]':'Final I gain * 1000', - 'debug[2]':'P gain [roll] * 1000', - 'debug[3]':'P gain [pitch] * 1000', - }, - 'DYN_LPF' : { - 'debug[all]':'Debug Dyn LPF', - 'debug[0]':'Gyro Scaled [dbg-axis]', - 'debug[1]':'Notch Center [roll]', - 'debug[2]':'Lowpass Cutoff', - 'debug[3]':'Gyro Pre-Dyn [dbg-axis]', - }, - 'DSHOT_RPM_TELEMETRY' : { - 'debug[all]':'DShot Telemetry RPM', - 'debug[0]':'Motor 1 - DShot', - 'debug[1]':'Motor 2 - DShot', - 'debug[2]':'Motor 3 - DShot', - 'debug[3]':'Motor 4 - DShot', - }, - 'RPM_FILTER' : { - 'debug[all]':'RPM Filter', - 'debug[0]':'Motor 1 - rpmFilter', - 'debug[1]':'Motor 2 - rpmFilter', - 'debug[2]':'Motor 3 - rpmFilter', - 'debug[3]':'Motor 4 - rpmFilter', - }, - 'D_MIN' : { - 'debug[all]':'D_MIN', - 'debug[0]':'Gyro Factor [roll]', - 'debug[1]':'Setpoint Factor [roll]', - 'debug[2]':'Actual D [roll]', - 'debug[3]':'Actual D [pitch]', - }, - 'AC_CORRECTION' : { - 'debug[all]':'AC Correction', - 'debug[0]':'AC Correction [roll]', - 'debug[1]':'AC Correction [pitch]', - 'debug[2]':'AC Correction [yaw]', - 'debug[3]':'Not Used', - }, - 'AC_ERROR' : { - 'debug[all]':'AC Error', - 'debug[0]':'AC Error [roll]', - 'debug[1]':'AC Error [pitch]', - 'debug[2]':'AC Error [yaw]', - 'debug[3]':'Not Used', - }, - 'DUAL_GYRO_SCALED' : { - 'debug[all]':'Dual Gyro Scaled', - 'debug[0]':'Gyro 1 [roll]', - 'debug[1]':'Gyro 1 [pitch]', - 'debug[2]':'Gyro 2 [roll]', - 'debug[3]':'Gyro 2 [pitch]', - }, - 'DSHOT_RPM_ERRORS' : { - 'debug[all]':'DSHOT RPM Error', - 'debug[0]':'DSHOT RPM Error [1]', - 'debug[1]':'DSHOT RPM Error [2]', - 'debug[2]':'DSHOT RPM Error [3]', - 'debug[3]':'DSHOT RPM Error [4]', - }, - 'CRSF_LINK_STATISTICS_UPLINK' : { - 'debug[all]':'CRSF Stats Uplink', - 'debug[0]':'Uplink RSSI 1', - 'debug[1]':'Uplink RSSI 2', - 'debug[2]':'Uplink Link Quality', - 'debug[3]':'RF Mode', - }, - 'CRSF_LINK_STATISTICS_PWR' : { - 'debug[all]':'CRSF Stats Power', - 'debug[0]':'Antenna', - 'debug[1]':'SNR', - 'debug[2]':'TX Power', - 'debug[3]':'Not Used', - }, - 'CRSF_LINK_STATISTICS_DOWN' : { - 'debug[all]':'CRSF Stats Downlink', - 'debug[0]':'Downlink RSSI', - 'debug[1]':'Downlink Link Quality', - 'debug[2]':'Downlink SNR', - 'debug[3]':'Not Used', - }, - 'BARO' : { - 'debug[all]':'Debug Barometer', - 'debug[0]':'Baro State', - 'debug[1]':'Baro Temperature', - 'debug[2]':'Baro Pressure', - 'debug[3]':'Baro Pressure Sum', - }, - 'GPS_RESCUE_THROTTLE_PID' : { - 'debug[all]':'GPS Rescue Throttle PID', - 'debug[0]':'Throttle P', - 'debug[1]':'Throttle I', - 'debug[2]':'Throttle D', - 'debug[3]':'Z Velocity', - }, - 'DYN_IDLE' : { - 'debug[all]':'Dyn Idle', - 'debug[0]':'Motor Range Min Inc', - 'debug[1]':'Target RPS Change Rate', - 'debug[2]':'Error', - 'debug[3]':'Min RPM', - }, - 'FF_LIMIT' : { - 'debug[all]':'FF Limit', - 'debug[0]':'FF input [roll]', - 'debug[1]':'FF input [pitch]', - 'debug[2]':'FF limited [roll]', - 'debug[3]':'Not Used', - }, - 'FF_INTERPOLATED' : { - 'debug[all]':'FF Interpolated [roll]', - 'debug[0]':'Setpoint Delta Impl [roll]', - 'debug[1]':'Boost amount [roll]', - 'debug[2]':'Boost amount, clipped [roll]', - 'debug[3]':'Clip amount [roll]', - }, - 'BLACKBOX_OUTPUT' : { - 'debug[all]':'Blackbox Output', - 'debug[0]':'Blackbox Rate', - 'debug[1]':'Blackbox Max Rate', - 'debug[2]':'Dropouts', - 'debug[3]':'Tx Bytes Free', - }, - 'GYRO_SAMPLE' : { - 'debug[all]':'Gyro Sample', - 'debug[0]':'Before downsampling', - 'debug[1]':'After downsampling', - 'debug[2]':'After RPM', - 'debug[3]':'After all but Dyn Notch', - }, - 'RX_TIMING' : { - 'debug[all]':'Receiver Timing (us)', - 'debug[0]':'Frame Delta', - 'debug[1]':'Frame Age', - 'debug[2]':'not used', - 'debug[3]':'not used', - }, - 'D_LPF' : { - 'debug[all]':'D-Term [D_LPF]', - 'debug[0]':'Unfiltered D [roll]', - 'debug[1]':'Unfiltered D [pitch]', - 'debug[2]':'Filtered, with DMax [roll]', - 'debug[3]':'Filtered, with DMax [pitch]', - }, - 'VTX_TRAMP' : { - 'debug[all]':'Tramp VTx', - 'debug[0]':'Status', - 'debug[1]':'Reply Code', - 'debug[2]':'Pit Mode', - 'debug[3]':'Retry Count', - }, - 'GHST' : { - 'debug[all]':'Ghost Rx', - 'debug[0]':'CRC Error Count', - 'debug[1]':'Unknown Frame Count', - 'debug[2]':'RSSI', - 'debug[3]':'Link Quality', - }, - 'SCHEDULER_DETERMINISM' : { - 'debug[all]':'Scheduler Determinism', - 'debug[0]':'Cycle Start time', - 'debug[1]':'ID of Late Task', - 'debug[2]':'Task Delay Time', - 'debug[3]':'Gyro Clock Skew', - }, - 'TIMING_ACCURACY' : { - 'debug[all]':'Timing Accuracy', - 'debug[0]':'CPU Busy', - 'debug[1]':'Late Tasks per second', - 'debug[2]':'Total delay in last second', - 'debug[3]':'Total Tasks per second', - }, - 'RX_EXPRESSLRS_SPI' : { - 'debug[all]':'ExpressLRS SPI Rx', - 'debug[0]':'Lost Connection Count', - 'debug[1]':'RSSI', - 'debug[2]':'SNR', - 'debug[3]':'Uplink LQ', - }, - 'RX_EXPRESSLRS_PHASELOCK' : { - 'debug[all]':'ExpressLRS SPI Phaselock', - 'debug[0]':'Phase offset', - 'debug[1]':'Filtered phase offset', - 'debug[2]':'Frequency Offset', - 'debug[3]':'Phase Shift', - }, - }; - - let DEBUG_FRIENDLY_FIELD_NAMES = null; - - FlightLogFieldPresenter.adjustDebugDefsList = function(firmwareType, firmwareVersion) { - - DEBUG_FRIENDLY_FIELD_NAMES = {...DEBUG_FRIENDLY_FIELD_NAMES_INITIAL}; - - if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT) { - if (semver.gte(firmwareVersion, '4.3.0')) { - DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD = { - 'debug[all]':'Feedforward [roll]', - 'debug[0]':'Setpoint, un-smoothed [roll]', - 'debug[1]':'Delta, smoothed [roll]', - 'debug[2]':'Boost, smoothed [roll]', - 'debug[3]':'rcCommand Delta [roll]', - }; - DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD_LIMIT = { - 'debug[all]':'Feedforward Limit [roll]', - 'debug[0]':'Feedforward input [roll]', - 'debug[1]':'Feedforward input [pitch]', - 'debug[2]':'Feedforward limited [roll]', - 'debug[3]':'Not Used', - }; - DEBUG_FRIENDLY_FIELD_NAMES.DYN_IDLE = { - 'debug[all]':'Dyn Idle', - 'debug[0]':'Dyn Idle P [roll]', - 'debug[1]':'Dyn Idle I [roll]', - 'debug[2]':'Dyn Idle D [roll]', - 'debug[3]':'Min RPM', - }; - DEBUG_FRIENDLY_FIELD_NAMES.FFT = { - 'debug[all]':'Debug FFT', - 'debug[0]':'Gyro Pre Dyn Notch [dbg-axis]', - 'debug[1]':'Gyro Post Dyn Notch [dbg-axis]', - 'debug[2]':'Gyro Downsampled [dbg-axis]', - 'debug[3]':'Not used', - }; - DEBUG_FRIENDLY_FIELD_NAMES.FFT_TIME = { - 'debug[all]':'Debug FFT TIME', - 'debug[0]':'Active calc step', - 'debug[1]':'Step duration', - 'debug[2]':'Not used', - 'debug[3]':'Not used', - }; - DEBUG_FRIENDLY_FIELD_NAMES.FFT_FREQ = { - 'debug[all]':'Debug FFT FREQ', - 'debug[0]':'Notch 1 Center Freq [dbg-axis]', - 'debug[1]':'Notch 2 Center Freq [dbg-axis]', - 'debug[2]':'Notch 3 Center Freq [dbg-axis]', - 'debug[3]':'Gyro Pre Dyn Notch [dbg-axis]', - }; - } else if (semver.gte(firmwareVersion, '4.2.0')) { - DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = { - 'debug[all]':'Feedforward [roll]', - 'debug[0]':'Setpoint Delta [roll]', - 'debug[1]':'Acceleration [roll]', - 'debug[2]':'Acceleration, clipped [roll]', - 'debug[3]':'Duplicate Counter [roll]', - }; - DEBUG_FRIENDLY_FIELD_NAMES.FF_LIMIT = { - 'debug[all]':'Feedforward Limit [roll]', - 'debug[0]':'FF limit input [roll]', - 'debug[1]':'FF limit input [pitch]', - 'debug[2]':'FF limited [roll]', - 'debug[3]':'Not Used', - }; - } else if (semver.gte(firmwareVersion, '4.1.0')) { - DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = { - 'debug[all]':'Feedforward [roll]', - 'debug[0]':'Setpoint Delta [roll]', - 'debug[1]':'Boost [roll]', - 'debug[2]':'Boost, clipped [roll]', - 'debug[3]':'Duplicate Counter [roll]', - }; - DEBUG_FRIENDLY_FIELD_NAMES.FF_LIMIT = { - 'debug[all]':'Feedforward Limit [roll]', - 'debug[0]':'FF limit input [roll]', - 'debug[1]':'FF limit input [pitch]', - 'debug[2]':'FF limited [roll]', - 'debug[3]':'Not Used', - }; - } - } - }; - - FlightLogFieldPresenter.presentFlags = function(flags, flagNames) { - var - printedFlag = false, - i, - result = ""; - - i = 0; - - while (flags > 0) { - if ((flags & 1) != 0) { - if (printedFlag) { - result += "|"; - } else { - printedFlag = true; - } - - result += flagNames[i]; - } - - flags >>= 1; - i++; - } - - if (printedFlag) { - return result; - } else { - return "0"; //No flags set - } - }; - - // Only list events that have changed, flag with eirer go ON or OFF. - FlightLogFieldPresenter.presentChangeEvent = function presentChangeEvent(flags, lastFlags, flagNames) { - var eventState = ''; - var found = false; - for(var i = 0; i < flagNames.length; i++) { - if((1< 0 to 128 - case 'debug[3]': // LQ 0-100 - return value.toFixed(0) + '%'; - default: - return value.toFixed(0); - } - case 'SCHEDULER_DETERMINISM': - switch (fieldName) { - case 'debug[0]': // cycle time in us*10 - case 'debug[2]': // task delay time in us*10 - case 'debug[3]': // task delay time in us*10 - return (value / 10).toFixed(1) + 'us'; - // debug 1 is task ID of late task - default: - return value.toFixed(0); - } - case 'TIMING_ACCURACY': - switch (fieldName) { - case 'debug[0]': // CPU Busy % - return value.toFixed(1) + '%'; - case 'debug[2]': // task delay time in us*10 - return (value / 10).toFixed(1) + 'us'; - default: - return value.toFixed(0); - } - case 'RX_EXPRESSLRS_SPI': - switch (fieldName) { - case 'debug[3]': // uplink LQ % - return value.toFixed(1) + '%'; - // debug 0 = Lost connection count - // debug 1 = RSSI - // debug 2 = SNR - default: - return value.toFixed(0); - } - case 'RX_EXPRESSLRS_PHASELOCK': - switch (fieldName) { - case 'debug[2]': // Frequency offset in ticks - return value.toFixed(0) + 'ticks'; - // debug 0 = Phase offset us - // debug 1 = Filtered phase offset us - // debug 3 = Pphase shift in us - default: - return value.toFixed(0) + 'us'; - } - } - return value.toFixed(0); - } - return ""; - }; - - FlightLogFieldPresenter.fieldNameToFriendly = function(fieldName, debugMode) { - if (debugMode) { - if (fieldName.includes('debug')) { - var debugModeName = DEBUG_MODE[debugMode]; - var debugFields; - if (debugModeName) { - debugFields = DEBUG_FRIENDLY_FIELD_NAMES[debugModeName]; - } - - if (!debugFields) { - if (fieldName === 'debug[all]') { - return 'Debug (' + (debugModeName || debugMode) + ')'; - } - debugFields = DEBUG_FRIENDLY_FIELD_NAMES[DEBUG_MODE[0]]; - } - - return debugFields[fieldName]; - } - } - if (FRIENDLY_FIELD_NAMES[fieldName]) { - return FRIENDLY_FIELD_NAMES[fieldName]; - } - - return fieldName; - }; -})(); diff --git a/js/graph_config.js b/js/graph_config.js deleted file mode 100644 index 06d959a0..00000000 --- a/js/graph_config.js +++ /dev/null @@ -1,834 +0,0 @@ -"use strict"; - -function GraphConfig(graphConfig) { - var - graphs = graphConfig ? graphConfig : [], - listeners = [], - that = this; - - function notifyListeners() { - for (var i = 0; i < listeners.length; i++) { - listeners[i](that); - } - } - - this.selectedFieldName = null; - this.selectedGraphIndex = 0; - this.selectedFieldIndex = 0; - - this.highlightGraphIndex = null; - this.highlightFieldIndex = null; - - this.getGraphs = function() { - return graphs; - }; - - /** - * newGraphs is an array of objects like {label: "graph label", height:, fields:[{name: curve:{offset:, power:, inputRange:, outputRange:, steps:}, color:, }, ...]} - */ - this.setGraphs = function(newGraphs) { - graphs = newGraphs; - - notifyListeners(); - }; - - /** - * Convert the given graph configs to make them appropriate for the given flight log. - */ - this.adaptGraphs = function(flightLog, graphs) { - var - logFieldNames = flightLog.getMainFieldNames(), - - // Make copies of graphs into here so we can modify them without wrecking caller's copy - newGraphs = []; - - for (var i = 0; i < graphs.length; i++) { - var - graph = graphs[i], - newGraph = $.extend( - // Default values for missing properties: - { - height: 1, - }, - // The old graph - graph, - // New fields to replace the old ones: - { - fields:[], - }, - ), - colorIndex = 0; - - for (var j = 0; j < graph.fields.length; j++) { - var - field = graph.fields[j], - matches; - - var adaptField = function(field, colorIndexOffset, forceNewCurve) { - const defaultCurve = GraphConfig.getDefaultCurveForField(flightLog, field.name); - - - if (field.curve === undefined || forceNewCurve) { - field.curve = defaultCurve; - } else { - /* The curve may have been originally created for a craft with different endpoints, so use the - * recommended offset and input range instead of the provided one. - */ - field.curve.offset = defaultCurve.offset; - field.curve.inputRange = defaultCurve.inputRange; - } - - if(colorIndexOffset!=null && field.color != undefined) { // auto offset the actual color (to expand [all] selections) - var index; - for(index=0; index < GraphConfig.PALETTE.length; index++) - { - if(GraphConfig.PALETTE[index].color == field.color) break; - } - field.color = GraphConfig.PALETTE[(index + colorIndexOffset) % GraphConfig.PALETTE.length].color - } - - if (field.color === undefined) { - field.color = GraphConfig.PALETTE[colorIndex % GraphConfig.PALETTE.length].color; - colorIndex++; - } - - if (field.smoothing === undefined) { - field.smoothing = GraphConfig.getDefaultSmoothingForField(flightLog, field.name); - } - - return field; - }; - - if ((matches = field.name.match(/^(.+)\[all\]$/))) { - var - nameRoot = matches[1], - nameRegex = new RegExp("^" + nameRoot + "\[[0-9]+\]$"), - colorIndexOffset = 0; - - for (var k = 0; k < logFieldNames.length; k++) { - if (logFieldNames[k].match(nameRegex)) { - // add special condition for rcCommands and debug as each of the fields requires a different scaling. - let forceNewCurve = (nameRoot=='rcCommand') || (nameRoot=='rcCommands') || (nameRoot=='debug'); - newGraph.fields.push(adaptField($.extend({}, field, {curve: $.extend({}, field.curve), name: logFieldNames[k], friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(logFieldNames[k], flightLog.getSysConfig().debug_mode)}), colorIndexOffset, forceNewCurve)); - colorIndexOffset++; - } - } - } else { - // Don't add fields if they don't exist in this log - if (flightLog.getMainFieldIndexByName(field.name) !== undefined) { - newGraph.fields.push(adaptField($.extend({}, field, {curve: $.extend({}, field.curve), friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(field.name, flightLog.getSysConfig().debug_mode)}))); - } - } - } - - newGraphs.push(newGraph); - } - - this.setGraphs(newGraphs); - }; - - this.addListener = function(listener) { - listeners.push(listener); - }; -} - -GraphConfig.PALETTE = [ - {color: "#fb8072", name: "Red" }, - {color: "#8dd3c7", name: "Cyan" }, - {color: "#ffffb3", name: "Yellow" }, - {color: "#bebada", name: "Purple" }, - {color: "#80b1d3", name: "Blue" }, - {color: "#fdb462", name: "Orange" }, - {color: "#b3de69", name: "Green" }, - {color: "#fccde5", name: "Pink" }, - {color: "#d9d9d9", name: "Grey" }, - {color: "#bc80bd", name: "Dark Purple" }, - {color: "#ccebc5", name: "Light Green" }, - {color: "#ffed6f", name: "Dark Yellow" } -]; - - -GraphConfig.load = function(config) { - // Upgrade legacy configs to suit the newer standard by translating field names - if (config) { - for (var i = 0; i < config.length; i++) { - var graph = config[i]; - - for (var j = 0; j < graph.fields.length; j++) { - var - field = graph.fields[j], - matches; - - if ((matches = field.name.match(/^gyroData(.+)$/))) { - field.name = "gyroADC" + matches[1]; - } - } - } - } else { - config = false; - } - - return config; -}; - -(function() { - GraphConfig.getDefaultSmoothingForField = function(flightLog, fieldName) { - try{ - if (fieldName.match(/^motor(Raw)?\[/)) { - return 5000; - } else if (fieldName.match(/^servo\[/)) { - return 5000; - } else if (fieldName.match(/^gyroADC.*\[/)) { - return 3000; - } else if (fieldName.match(/^accSmooth\[/)) { - return 3000; - } else if (fieldName.match(/^axis.+\[/)) { - return 3000; - } else { - return 0; - } - } catch (e) { return 0;} - }; - - - GraphConfig.getDefaultCurveForField = function(flightLog, fieldName) { - var - sysConfig = flightLog.getSysConfig(); - - var maxDegreesSecond = function(scale) { - switch(sysConfig["rates_type"]){ - case RATES_TYPE.indexOf('ACTUAL'): - case RATES_TYPE.indexOf('QUICK'): - return Math.max(sysConfig["rates"][0] * 10.0 * scale, - sysConfig["rates"][1] * 10.0 * scale, - sysConfig["rates"][2] * 10.0 * scale); - default: - return Math.max(flightLog.rcCommandRawToDegreesPerSecond(500,0) * scale, - flightLog.rcCommandRawToDegreesPerSecond(500,1) * scale, - flightLog.rcCommandRawToDegreesPerSecond(500,2) * scale); - } - } - - var getMinMaxForFields = function(/* fieldName1, fieldName2, ... */) { - // helper to make a curve scale based on the combined min/max of one or more fields - var - stats = flightLog.getStats(), - min = Number.MAX_VALUE, - max = Number.MIN_VALUE; - - for(var i in arguments) { - var - fieldIndex = flightLog.getMainFieldIndexByName(arguments[i]), - fieldStat = fieldIndex !== undefined ? stats.field[fieldIndex] : false; - - if (fieldStat) { - min = Math.min(min, fieldStat.min); - max = Math.max(max, fieldStat.max); - } - } - - if (min != Number.MAX_VALUE && max != Number.MIN_VALUE) { - return {min:min, max:max}; - } - - return {min:-500, max:500}; - } - - var getCurveForMinMaxFields = function(/* fieldName1, fieldName2, ... */) { - var mm = getMinMaxForFields.apply(null, arguments); - - return { - offset: -(mm.max + mm.min) / 2, - power: 1.0, - inputRange: Math.max((mm.max - mm.min) / 2, 1.0), - outputRange: 1.0 - }; - } - - var getCurveForMinMaxFieldsZeroOffset = function(/* fieldName1, fieldName2, ... */) { - var mm = getMinMaxForFields.apply(null, arguments); - - return { - offset: 0, - power: 1.0, - inputRange: Math.max(Math.max(Math.abs(mm.max), Math.abs(mm.min)), 1.0), - outputRange: 1.0 - }; - } - - const gyroScaleMargin = 1.20; // Give a 20% margin for gyro graphs - - try { - if (fieldName.match(/^motor\[/)) { - return { - offset: flightLog.isDigitalProtocol() ? - -(DSHOT_MIN_VALUE + DSHOT_RANGE / 2) : -(sysConfig.minthrottle + (sysConfig.maxthrottle - sysConfig.minthrottle) / 2), - power: 1.0, - inputRange: flightLog.isDigitalProtocol() ? - DSHOT_RANGE / 2 : (sysConfig.maxthrottle - sysConfig.minthrottle) / 2, - outputRange: 1.0, - }; - } else if (fieldName.match(/^motorLegacy\[/)) { - return { - offset: -(sysConfig.motorOutput[1] + sysConfig.motorOutput[0]) / 2, - power: 1.0, - inputRange: (sysConfig.motorOutput[1] - sysConfig.motorOutput[0]) / 2, - outputRange: 1.0, - }; - } else if (fieldName.match(/^servo\[/)) { - return { - offset: -1500, - power: 1.0, - inputRange: 500, - outputRange: 1.0 - }; - } else if (fieldName.match(/^accSmooth\[/)) { - return { - offset: 0, - power: 0.5, - inputRange: sysConfig.acc_1G * 16.0, /* Reasonable typical maximum for acc */ - outputRange: 1.0 - }; - } else if (fieldName == "rcCommands[3]") { // Throttle scaled - return { - offset: -50, - power: 1.0, /* Make this 1.0 to scale linearly */ - inputRange: 50, - outputRange: 1.0 - }; - } else if (fieldName.match(/^axisError\[/) || // Gyro, Gyro Scaled, RC Command Scaled and axisError - fieldName.match(/^rcCommands\[/) || // These use the same scaling as they are in the - fieldName.match(/^gyroADC\[/)) { // same range. - return { - offset: 0, - power: 0.25, /* Make this 1.0 to scale linearly */ - inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% - outputRange: 1.0 - }; - } else if (fieldName.match(/^axis.+\[/)) { - return { - offset: 0, - power: 0.3, - inputRange: 1000, // Was 400 ? - outputRange: 1.0 - }; - } else if (fieldName == "rcCommand[3]") { // Throttle - return { - offset: -1500, - power: 1.0, - inputRange: 500, - outputRange: 1.0 - }; - } else if (fieldName.match(/^rcCommand\[/)) { - return { - offset: 0, - power: 0.25, - inputRange: 500 * gyroScaleMargin, // +20% to let compare in the same scale with the rccommands - outputRange: 1.0 - }; - } else if (fieldName == "heading[2]") { - return { - offset: -Math.PI, - power: 1.0, - inputRange: Math.PI, - outputRange: 1.0 - }; - } else if (fieldName.match(/^heading\[/)) { - return { - offset: 0, - power: 1.0, - inputRange: Math.PI, - outputRange: 1.0 - }; - } else if (fieldName.match(/^sonar.*/)) { - return { - offset: -200, - power: 1.0, - inputRange: 200, - outputRange: 1.0 - }; - } else if (fieldName.match(/^rssi.*/)) { - return { - offset: -512, - power: 1.0, - inputRange: 512, - outputRange: 1.0 - }; - } else if (fieldName.match(/^debug.*/) && sysConfig.debug_mode!=null) { - - var debugModeName = DEBUG_MODE[sysConfig.debug_mode]; - switch (debugModeName) { - case 'CYCLETIME': - switch (fieldName) { - case 'debug[1]': //CPU Load - return { - offset: -50, - power: 1, - inputRange: 50, - outputRange: 1.0 - }; - default: - return { - offset: -1000, // zero offset - power: 1.0, - inputRange: 1000, // 0-2000uS - outputRange: 1.0 - }; - } - case 'PIDLOOP': - return { - offset: -250, // zero offset - power: 1.0, - inputRange: 250, // 0-500uS - outputRange: 1.0 - }; - case 'GYRO': - case 'GYRO_FILTERED': - case 'GYRO_SCALED': - case 'DUAL_GYRO': - case 'DUAL_GYRO_COMBINED': - case 'DUAL_GYRO_DIFF': - case 'DUAL_GYRO_RAW': - case 'DUAL_GYRO_SCALED': - case 'NOTCH': - case 'AC_CORRECTION': - case 'AC_ERROR': - return { - offset: 0, - power: 0.25, - inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% - outputRange: 1.0 - }; - case 'ACCELEROMETER': - return { - offset: 0, - power: 0.5, - inputRange: sysConfig.acc_1G * 16.0, /* Reasonable typical maximum for acc */ - outputRange: 1.0 - }; - case 'MIXER': - return { - offset: -(sysConfig.motorOutput[1] + sysConfig.motorOutput[0]) / 2, - power: 1.0, - inputRange: (sysConfig.motorOutput[1] - sysConfig.motorOutput[0]) / 2, - outputRange: 1.0 - }; - case 'BATTERY': - switch (fieldName) { - case 'debug[0]': //Raw Value (0-4095) - return { - offset: -2048, - power: 1, - inputRange: 2048, - outputRange: 1.0 - }; - default: - return { - offset: -130, - power: 1.0, - inputRange: 130, // 0-26.0v - outputRange: 1.0 - }; - } - case 'RC_INTERPOLATION': - switch (fieldName) { - case 'debug[0]': // Roll RC Command - case 'debug[3]': // refresh period - return getCurveForMinMaxFieldsZeroOffset(fieldName); - default: - return getCurveForMinMaxFields(fieldName); - } - case 'RC_SMOOTHING': - switch (fieldName) { - case 'debug[0]': // raw RC command - return { - offset: 0, - power: 0.25, - inputRange: 500 * gyroScaleMargin, // +20% to let compare in the same scale with the rccommands - outputRange: 1.0 - }; - case 'debug[1]': // raw RC command derivative - case 'debug[2]': // smoothed RC command derivative - return getCurveForMinMaxFieldsZeroOffset('debug[1]', 'debug[2]'); - default: - return getCurveForMinMaxFields(fieldName); - } - case 'RC_SMOOTHING_RATE': - switch (fieldName) { - case 'debug[0]': // current frame rate [us] - case 'debug[2]': // average frame rate [us] - return getCurveForMinMaxFields('debug[0]', 'debug[2]'); - default: - return getCurveForMinMaxFields(fieldName); - } - case 'ANGLERATE': - return { - offset: 0, - power: 0.25, /* Make this 1.0 to scale linearly */ - inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% - outputRange: 1.0 - }; - case 'FFT': - switch (fieldName) { - case 'debug[0]': // pre-dyn notch gyro [for gyro debug axis] - case 'debug[1]': // post-dyn notch gyro [for gyro debug axis] - case 'debug[2]': // pre-dyn notch gyro downsampled for FFT [for gyro debug axis] - return { - offset: 0, - power: 1.0, - inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% - outputRange: 1.0 - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'FFT_FREQ': - switch (fieldName) { - case 'debug[0]': // notch 1 center freq [for gyro debug axis] - case 'debug[1]': // notch 2 center freq [for gyro debug axis] - case 'debug[2]': // notch 3 center freq [for gyro debug axis] - return getCurveForMinMaxFields('debug[0]', 'debug[1]', 'debug[2]'); - case 'debug[3]': // pre-dyn notch gyro [for gyro debug axis] - return { - offset: 0, - power: 1.0, - inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% - outputRange: 1.0 - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'DYN_LPF': - switch (fieldName) { - case 'debug[1]': // Notch center - case 'debug[2]': // Lowpass Cutoff - return getCurveForMinMaxFields('debug[1]', 'debug[2]'); - case 'debug[0]': // gyro scaled [for selected axis] - case 'debug[3]': // pre-dyn notch gyro [for selected axis] - return { - offset: 0, - power: 0.25, - inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% - outputRange: 1.0 - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'FFT_TIME': - return { - offset: 0, - power: 1.0, - inputRange: 100, - outputRange: 1.0 - }; - case 'ESC_SENSOR_RPM': - case 'DSHOT_RPM_TELEMETRY': - case 'RPM_FILTER': - return getCurveForMinMaxFields('debug[0]', 'debug[1]', 'debug[2]', 'debug[3]'); - case 'D_MIN': - switch (fieldName) { - case 'debug[0]': // roll gyro factor - case 'debug[1]': // roll setpoint Factor - return getCurveForMinMaxFields('debug[0]', 'debug[1]'); - case 'debug[2]': // roll actual D - case 'debug[3]': // pitch actual D - return getCurveForMinMaxFields('debug[2]', 'debug[3]'); - default: - return getCurveForMinMaxFields(fieldName); - } - case 'ITERM_RELAX': - switch (fieldName) { - case 'debug[2]': // roll I relaxed error - case 'debug[3]': // roll absolute control axis error - return getCurveForMinMaxFieldsZeroOffset(fieldName); - default: - return getCurveForMinMaxFields(fieldName); - } - case 'FF_INTERPOLATED': - switch (fieldName) { - case 'debug[0]': // setpoint Delta - case 'debug[1]': // AccelerationModified - case 'debug[2]': // Acceleration - return { - offset: 0, - power: 1.0, - inputRange: 1000, - outputRange: 1.0, - }; - case 'debug[3]': // Clip or Count - return { - offset: -10, - power: 1.0, - inputRange: 10, - outputRange: 1.0, - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'FEEDFORWARD': // replaces FF_INTERPOLATED in 4.3 - switch (fieldName) { - case 'debug[0]': // in 4.3 is interpolated setpoint - return { - offset: 0, - power: 1.0, - inputRange: maxDegreesSecond(gyroScaleMargin), - outputRange: 1.0, - }; - case 'debug[1]': // feedforward delta element - case 'debug[2]': // feedforward boost element - return { - offset: 0, - power: 1.0, - inputRange: 1000, - outputRange: 1.0, - }; - case 'debug[3]': // rcCommand delta - return { - offset: 0, - power: 1.0, - inputRange: 10000, - outputRange: 1.0, - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'FF_LIMIT': - case 'FEEDFORWARD_LIMIT': - return { - offset: 0, - power: 1.0, - inputRange: 300, - outputRange: 1.0, - }; - case 'DYN_IDLE': - switch (fieldName) { - case 'debug[0]': // in 4.3 is dyn idle P - case 'debug[1]': // in 4.3 is dyn idle I - case 'debug[2]': // in 4.3 is dyn idle D - return { - offset: 0, - power: 1.0, - inputRange: 1000, - outputRange: 1.0, - }; - case 'debug[3]': // in 4.3 and 4.2 is minRPS - return { - offset: -1000, - power: 1.0, - inputRange: 1000, - outputRange: 1.0, - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'RX_TIMING': - switch (fieldName) { - case 'debug[0]': // CRC 0 to max int16_t - return { // start at bottom, scale up to 20ms - offset: -1000, - power: 1.0, - inputRange: 1000, - outputRange: 1.0, - }; - // debug 1 is Count of Unknown Frames - // debug 2 and 3 not used - default: - return getCurveForMinMaxFields(fieldName); - } - case 'GHST': - switch (fieldName) { - case 'debug[0]': // CRC 0 to max int16_t - case 'debug[1]': // Count of Unknown Frames - return getCurveForMinMaxFieldsZeroOffset(fieldName); - case 'debug[2]': // RSSI - return { - offset: 128, - power: 1.0, - inputRange: 128, - outputRange: 1.0, - }; - case 'debug[3]': // LQ percent - return { - offset: -50, - power: 1.0, - inputRange: 50, - outputRange: 1.0, - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'SCHEDULER_DETERMINISM': - switch (fieldName) { - case 'debug[0]': // Gyro task cycle us * 10 so 1250 = 125us - return { - offset: -5000, - power: 1.0, - inputRange: 5000, - outputRange: 1.0, - }; - case 'debug[1]': // ID of late task - case 'debug[2]': // task delay time 100us in middle - return { - offset: -1000, - power: 1.0, - inputRange: 1000, - outputRange: 1.0, - }; - case 'debug[3]': // gyro skew 100 = 10us - return { - offset: 0, - power: 1.0, - inputRange: 500, - outputRange: 1.0, - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'TIMING_ACCURACY': - switch (fieldName) { - case 'debug[0]': // % CPU Busy - case 'debug[1]': // late tasks per second - return { - offset: -50, - power: 1.0, - inputRange: 50, - outputRange: 1.0, - }; - case 'debug[2]': // total delay in last second - return { - offset: -500, - power: 1.0, - inputRange: 500, - outputRange: 1.0, - }; - case 'debug[3]': // total tasks per second - return { - offset: -5000, - power: 1.0, - inputRange: 5000, - outputRange: 1.0, - }; - default: - return getCurveForMinMaxFields(fieldName); - } - case 'RX_EXPRESSLRS_SPI': - switch (fieldName) { - case 'debug[2]': // Uplink LQ - return { - offset: -50, - power: 1.0, - inputRange: 50, - outputRange: 1.0, - }; - // debug 0 = Lost connection count - // debug 1 = RSSI - // debug 3 = SNR - default: - return getCurveForMinMaxFields(fieldName); - } - case 'RX_EXPRESSLRS_PHASELOCK': - switch (fieldName) { - case 'debug[2]': // Frequency offset in ticks - return getCurveForMinMaxFieldsZeroOffset(fieldName); - // debug 0 = Phase offset us - // debug 1 = Filtered phase offset us - // debug 3 = Phase shift in us - default: - return getCurveForMinMaxFields(fieldName); - } - } - } - // if not found above then - // Scale and center the field based on the whole-log observed ranges for that field - return getCurveForMinMaxFields(fieldName); - } catch(e) { - return { - offset: 0, - power: 1.0, - inputRange: 500, - outputRange: 1.0 - }; - } - }; - - /** - * Get an array of suggested graph configurations will be usable for the fields available in the given flightlog. - * - * Supply an array of strings `graphNames` to only fetch the graph with the given names. - */ - GraphConfig.getExampleGraphConfigs = function(flightLog, graphNames) { - var - result = [], - i, j; - - const EXAMPLE_GRAPHS = []; - - if (!flightLog.isFieldDisabled().MOTORS) { - EXAMPLE_GRAPHS.push({label: "Motors",fields: ["motor[all]", "servo[5]"]}); - EXAMPLE_GRAPHS.push({label: "Motors (Legacy)",fields: ["motorLegacy[all]", "servo[5]"]}); - } - if (!flightLog.isFieldDisabled().GYRO) { - EXAMPLE_GRAPHS.push({label: "Gyros",fields: ["gyroADC[all]"]}); - } - if (!flightLog.isFieldDisabled().SETPOINT) { - EXAMPLE_GRAPHS.push({label: "RC Rates",fields: ["rcCommands[all]"]}); - } - if (!flightLog.isFieldDisabled().RC_COMMANDS) { - EXAMPLE_GRAPHS.push({label: "RC Command",fields: ["rcCommand[all]"]}); - } - if (!flightLog.isFieldDisabled().PID) { - EXAMPLE_GRAPHS.push({label: "PIDs",fields: ["axisSum[all]"]}); - } - if (!(flightLog.isFieldDisabled().GYRO || flightLog.isFieldDisabled().PID)) { - EXAMPLE_GRAPHS.push({label: "PID Error",fields: ["axisError[all]"]}, - {label: "Gyro + PID roll",fields: ["axisP[0]", "axisI[0]", "axisD[0]", "axisF[0]", "gyroADC[0]"]}, - {label: "Gyro + PID pitch",fields: ["axisP[1]", "axisI[1]", "axisD[1]", "axisF[1]", "gyroADC[1]"]}, - {label: "Gyro + PID yaw",fields: ["axisP[2]", "axisI[2]", "axisD[2]", "axisF[2]", "gyroADC[2]"]}); - } - if (!flightLog.isFieldDisabled().ACC) { - EXAMPLE_GRAPHS.push({label: "Accelerometers",fields: ["accSmooth[all]"]}); - } - if (!flightLog.isFieldDisabled().DEBUG) { - EXAMPLE_GRAPHS.push({label: "Debug",fields: ["debug[all]"]}); - } - - for (i = 0; i < EXAMPLE_GRAPHS.length; i++) { - var - srcGraph = EXAMPLE_GRAPHS[i], - destGraph = { - label: srcGraph.label, - fields: [], - height: srcGraph.height || 1 - }, - found; - - if (graphNames !== undefined) { - found = false; - for (j = 0; j < graphNames.length; j++) { - if (srcGraph.label == graphNames[j]) { - found = true; - break; - } - } - - if (!found) { - continue; - } - } - - for (j = 0; j < srcGraph.fields.length; j++) { - var - srcFieldName = srcGraph.fields[j], - destField = { - name: srcFieldName - }; - - destGraph.fields.push(destField); - } - - result.push(destGraph); - } - - return result; - }; -})(); diff --git a/js/gui.js b/js/gui.js deleted file mode 100644 index 756a009f..00000000 --- a/js/gui.js +++ /dev/null @@ -1,172 +0,0 @@ - -'use strict'; - -var TABS = {}; // filled by individual tab js file - -var GUI_control = function () { - -}; - -// Timer managing methods - -// name = string -// code = function reference (code to be executed) -// interval = time interval in miliseconds -// first = true/false if code should be ran initially before next timer interval hits -GUI_control.prototype.interval_add = function (name, code, interval, first) { - var data = {'name': name, 'timer': null, 'code': code, 'interval': interval, 'fired': 0, 'paused': false}; - - if (first == true) { - code(); // execute code - - data.fired++; // increment counter - } - - data.timer = setInterval(function() { - code(); // execute code - - data.fired++; // increment counter - }, interval); - - this.interval_array.push(data); // push to primary interval array - - return data; -}; - -// name = string -GUI_control.prototype.interval_remove = function (name) { - for (var i = 0; i < this.interval_array.length; i++) { - if (this.interval_array[i].name == name) { - clearInterval(this.interval_array[i].timer); // stop timer - - this.interval_array.splice(i, 1); // remove element/object from array - - return true; - } - } - - return false; -}; - -// name = string -GUI_control.prototype.interval_pause = function (name) { - for (var i = 0; i < this.interval_array.length; i++) { - if (this.interval_array[i].name == name) { - clearInterval(this.interval_array[i].timer); - this.interval_array[i].paused = true; - - return true; - } - } - - return false; -}; - -// name = string -GUI_control.prototype.interval_resume = function (name) { - for (var i = 0; i < this.interval_array.length; i++) { - if (this.interval_array[i].name == name && this.interval_array[i].paused) { - var obj = this.interval_array[i]; - - obj.timer = setInterval(function() { - obj.code(); // execute code - - obj.fired++; // increment counter - }, obj.interval); - - obj.paused = false; - - return true; - } - } - - return false; -}; - -// input = array of timers thats meant to be kept, or nothing -// return = returns timers killed in last call -GUI_control.prototype.interval_kill_all = function (keep_array) { - var self = this; - var timers_killed = 0; - - for (var i = (this.interval_array.length - 1); i >= 0; i--) { // reverse iteration - var keep = false; - if (keep_array) { // only run through the array if it exists - keep_array.forEach(function (name) { - if (self.interval_array[i].name == name) { - keep = true; - } - }); - } - - if (!keep) { - clearInterval(this.interval_array[i].timer); // stop timer - - this.interval_array.splice(i, 1); // remove element/object from array - - timers_killed++; - } - } - - return timers_killed; -}; - -// name = string -// code = function reference (code to be executed) -// timeout = timeout in miliseconds -GUI_control.prototype.timeout_add = function (name, code, timeout) { - var self = this; - var data = {'name': name, 'timer': null, 'timeout': timeout}; - - // start timer with "cleaning" callback - data.timer = setTimeout(function() { - code(); // execute code - - // remove object from array - var index = self.timeout_array.indexOf(data); - if (index > -1) self.timeout_array.splice(index, 1); - }, timeout); - - this.timeout_array.push(data); // push to primary timeout array - - return data; -}; - -// name = string -GUI_control.prototype.timeout_remove = function (name) { - for (var i = 0; i < this.timeout_array.length; i++) { - if (this.timeout_array[i].name == name) { - clearTimeout(this.timeout_array[i].timer); // stop timer - - this.timeout_array.splice(i, 1); // remove element/object from array - - return true; - } - } - - return false; -}; - -// no input paremeters -// return = returns timers killed in last call -GUI_control.prototype.timeout_kill_all = function () { - var timers_killed = 0; - - for (var i = 0; i < this.timeout_array.length; i++) { - clearTimeout(this.timeout_array[i].timer); // stop timer - - timers_killed++; - } - - this.timeout_array = []; // drop objects - - return timers_killed; -}; - -// message = string -GUI_control.prototype.log = function (message) { - console.log(message); -}; - -// initialize object into GUI variable -var GUI = new GUI_control(); diff --git a/js/localization.js b/js/localization.js deleted file mode 100644 index d472aefa..00000000 --- a/js/localization.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -function localize() { - var localized = 0; - - var translate = function(messageID) { - localized++; - - return chrome.i18n.getMessage(messageID); - }; - - $('[i18n]:not(.i18n-replaced)').each(function() { - var element = $(this); - - element.html(translate(element.attr('i18n'))); - element.addClass('i18n-replaced'); - }); - - $('[i18n_title]:not(.i18n_title-replaced)').each(function() { - var element = $(this); - - element.attr('title', translate(element.attr('i18n_title'))); - element.addClass('i18n_title-replaced'); - }); - - $('[i18n_value]:not(.i18n_value-replaced)').each(function() { - var element = $(this); - - element.val(translate(element.attr('i18n_value'))); - element.addClass('i18n_value-replaced'); - }); - - $('[i18n_placeholder]:not(.i18n_placeholder-replaced)').each(function() { - var element = $(this); - - element.attr('placeholder', translate(element.attr('i18n_placeholder'))); - element.addClass('i18n_placeholder-replaced'); - }); - - return localized; -} \ No newline at end of file diff --git a/js/release_checker.js b/js/release_checker.js deleted file mode 100644 index 6e2076a8..00000000 --- a/js/release_checker.js +++ /dev/null @@ -1,58 +0,0 @@ - 'use strict;' - -var ReleaseChecker = function (releaseName, releaseUrl) { - var self = this; - - self._releaseName = releaseName; - self._releaseDataTag = `${self._releaseName}ReleaseData`; - self._releaseLastUpdateTag = `${self._releaseName}ReleaseLastUpdate` - self._releaseUrl = releaseUrl; - - -} - -ReleaseChecker.prototype.loadReleaseData = function (processFunction) { - var self = this; - chrome.storage.local.get([self._releaseLastUpdateTag, self._releaseDataTag], function (result) { - var releaseDataTimestamp = $.now(); - var cacheReleaseData = result[self._releaseDataTag]; - var cachedReleaseLastUpdate = result[self._releaseLastUpdateTag]; - if (!cacheReleaseData || !cachedReleaseLastUpdate || releaseDataTimestamp - cachedReleaseLastUpdate > 3600 * 1000) { - $.get(self._releaseUrl, function (releaseData) { - GUI.log(`Loaded release information for ${self._releaseName} from GitHub.`); - - var data = {}; - data[self._releaseDataTag] = releaseData - data[self._releaseLastUpdateTag] = releaseDataTimestamp - chrome.storage.local.set(data, function () {}); - - self._processReleaseData(releaseData, processFunction); - }).fail(function (data) { - var message = ''; - if (data['responseJSON']) { - message = data['responseJSON'].message; - } - GUI.log(`GitHub query for ${self._releaseName} releases failed, using cached information. Reason: ${message}`); - - self._processReleaseData(cacheReleaseData, processFunction); - }); - } else { - if (cacheReleaseData) { - GUI.log(`Using cached release information for ${self._releaseName} releases.`); - } - - self._processReleaseData(cacheReleaseData, processFunction); - } - }); -} - - -ReleaseChecker.prototype._processReleaseData = function (releaseData, processFunction) { - if (releaseData) { - processFunction(releaseData); - } else { - GUI.log(`No release information available for ${self._releaseName}.`); - - processFunction(); - } -} diff --git a/library/Readme.md b/library/Readme.md deleted file mode 100644 index 11152d8b..00000000 --- a/library/Readme.md +++ /dev/null @@ -1,7 +0,0 @@ -## Updating the libraries - -### ffmpeg - -Current version: 0.60.0 - -Updating: Download prebuilt libraries from https://github.com/iteufel/nwjs-ffmpeg-prebuilt/releases. diff --git a/library/linux32/libffmpeg.so b/library/linux32/libffmpeg.so deleted file mode 100755 index 2ff92fce..00000000 Binary files a/library/linux32/libffmpeg.so and /dev/null differ diff --git a/library/linux64/libffmpeg.so b/library/linux64/libffmpeg.so deleted file mode 100755 index 22d5c82b..00000000 Binary files a/library/linux64/libffmpeg.so and /dev/null differ diff --git a/library/osx64/libffmpeg.dylib b/library/osx64/libffmpeg.dylib deleted file mode 100755 index 5a6c6076..00000000 Binary files a/library/osx64/libffmpeg.dylib and /dev/null differ diff --git a/library/win32/ffmpeg.dll b/library/win32/ffmpeg.dll deleted file mode 100644 index 6e95e9c6..00000000 Binary files a/library/win32/ffmpeg.dll and /dev/null differ diff --git a/library/win64/ffmpeg.dll b/library/win64/ffmpeg.dll deleted file mode 100644 index 8ad3440a..00000000 Binary files a/library/win64/ffmpeg.dll and /dev/null differ diff --git a/package.json b/package.json index 51e3dd09..8e97138e 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,15 @@ { "name": "betaflight-blackbox-explorer", + "productName": "Blackbox Explorer", "displayName": "Betaflight - Blackbox Explorer", "description": "Crossplatform blackbox analitics tool for Betaflight flight control system.", - "version": "3.6.0", + "version": "4.0.0", "main": "index.html", - "chromium-args": "--disable-features=nw2", "default_locale": "en", "scripts": { - "start": "gulp debug", - "gulp": "gulp", - "release": "gulp release" - }, - "window": { - "id": "main", - "show": true, - "icon": "images/bf_icon_128.png", - "min_width": 930, - "min_height": 480 + "start": "vite", + "build": "vite build", + "preview": "vite preview" }, "repository": { "type": "git", @@ -25,35 +18,26 @@ "author": "The Betaflight open source project.", "license": "GPL-3.0", "dependencies": { + "Leaflet.MultiOptionsPolyline": "hgoebl/Leaflet.MultiOptionsPolyline", "bootstrap": "~3.4.1", "html2canvas": "^1.0.0-rc.5", - "lodash": "^4.17.21" + "jquery": "^3.7.1", + "jquery-ui": "^1.13.2", + "leaflet": "^1.9.3", + "leaflet-marker-rotation": "^0.4.0", + "lodash": "^4.17.21", + "throttle-debounce": "^5.0.0", + "vite": "^5.2.6", + "vite-plugin-pwa": "^0.19.7" }, "devDependencies": { - "@quanle94/innosetup": "^6.0.2", - "command-exists": "^1.2.8", - "del": "^5.0.0", - "gulp": "^4.0.1", - "gulp-debian": "~0.1.8", - "gulp-rename": "^2.0.0", - "gulp-util": "3.0.8", - "gulp-yarn": "^3.0.0", - "gulp-zip": "^5.0.0", "inflection": "1.12.0", - "nw-builder": "^3.5.7", - "os": "^0.1.1", - "rpm-builder": "^1.0.0", - "temp": "^0.9.0", - "through2": "4.0.2", "yarn": "^1.22.0" }, - "optionalDependencies": { - "appdmg": "^0.6.2" - }, "resolutions": { "**/**/lodash.template": "^4.5.0" }, "engines": { - "node": "16.x" + "node": "20.x" } } diff --git a/css/jquery.nouislider.min.css b/public/css/jquery.nouislider.min.css similarity index 100% rename from css/jquery.nouislider.min.css rename to public/css/jquery.nouislider.min.css diff --git a/images/bf_icon.icns b/public/images/bf_icon.icns similarity index 100% rename from images/bf_icon.icns rename to public/images/bf_icon.icns diff --git a/images/bf_icon.ico b/public/images/bf_icon.ico similarity index 100% rename from images/bf_icon.ico rename to public/images/bf_icon.ico diff --git a/assets/linux/icon/bf_icon_128.png b/public/images/bf_icon_128.png similarity index 100% rename from assets/linux/icon/bf_icon_128.png rename to public/images/bf_icon_128.png diff --git a/images/cf_logo_white.svg b/public/images/cf_logo_white.svg similarity index 100% rename from images/cf_logo_white.svg rename to public/images/cf_logo_white.svg diff --git a/images/dmg-background.png b/public/images/dmg-background.png similarity index 100% rename from images/dmg-background.png rename to public/images/dmg-background.png diff --git a/images/dmg-background@2x.png b/public/images/dmg-background@2x.png similarity index 100% rename from images/dmg-background@2x.png rename to public/images/dmg-background@2x.png diff --git a/images/glyphs/scrollwheel.svg b/public/images/glyphs/scrollwheel.svg similarity index 100% rename from images/glyphs/scrollwheel.svg rename to public/images/glyphs/scrollwheel.svg diff --git a/images/inav_logo_white.png b/public/images/inav_logo_white.png similarity index 100% rename from images/inav_logo_white.png rename to public/images/inav_logo_white.png diff --git a/images/light-wide-2.svg b/public/images/light-wide-2.svg similarity index 100% rename from images/light-wide-2.svg rename to public/images/light-wide-2.svg diff --git a/images/logo-background.png b/public/images/logo-background.png similarity index 100% rename from images/logo-background.png rename to public/images/logo-background.png diff --git a/images/logo.png b/public/images/logo.png similarity index 100% rename from images/logo.png rename to public/images/logo.png diff --git a/public/images/markers/craft.png b/public/images/markers/craft.png new file mode 100644 index 00000000..9c7e5eb0 Binary files /dev/null and b/public/images/markers/craft.png differ diff --git a/public/images/markers/home.png b/public/images/markers/home.png new file mode 100644 index 00000000..8bc8f701 Binary files /dev/null and b/public/images/markers/home.png differ diff --git a/images/motor_order/airplane.svg b/public/images/motor_order/airplane.svg similarity index 100% rename from images/motor_order/airplane.svg rename to public/images/motor_order/airplane.svg diff --git a/images/motor_order/atail_quad.svg b/public/images/motor_order/atail_quad.svg similarity index 100% rename from images/motor_order/atail_quad.svg rename to public/images/motor_order/atail_quad.svg diff --git a/images/motor_order/bicopter.svg b/public/images/motor_order/bicopter.svg similarity index 100% rename from images/motor_order/bicopter.svg rename to public/images/motor_order/bicopter.svg diff --git a/images/motor_order/custom.svg b/public/images/motor_order/custom.svg similarity index 100% rename from images/motor_order/custom.svg rename to public/images/motor_order/custom.svg diff --git a/images/motor_order/flying_wing.svg b/public/images/motor_order/flying_wing.svg similarity index 100% rename from images/motor_order/flying_wing.svg rename to public/images/motor_order/flying_wing.svg diff --git a/images/motor_order/hex_p.svg b/public/images/motor_order/hex_p.svg similarity index 100% rename from images/motor_order/hex_p.svg rename to public/images/motor_order/hex_p.svg diff --git a/images/motor_order/hex_x.svg b/public/images/motor_order/hex_x.svg similarity index 100% rename from images/motor_order/hex_x.svg rename to public/images/motor_order/hex_x.svg diff --git a/images/motor_order/octo_flat_p.svg b/public/images/motor_order/octo_flat_p.svg similarity index 100% rename from images/motor_order/octo_flat_p.svg rename to public/images/motor_order/octo_flat_p.svg diff --git a/images/motor_order/octo_flat_x.svg b/public/images/motor_order/octo_flat_x.svg similarity index 100% rename from images/motor_order/octo_flat_x.svg rename to public/images/motor_order/octo_flat_x.svg diff --git a/images/motor_order/octo_x8.svg b/public/images/motor_order/octo_x8.svg similarity index 100% rename from images/motor_order/octo_x8.svg rename to public/images/motor_order/octo_x8.svg diff --git a/images/motor_order/quad_p.svg b/public/images/motor_order/quad_p.svg similarity index 100% rename from images/motor_order/quad_p.svg rename to public/images/motor_order/quad_p.svg diff --git a/images/motor_order/quad_x.svg b/public/images/motor_order/quad_x.svg similarity index 100% rename from images/motor_order/quad_x.svg rename to public/images/motor_order/quad_x.svg diff --git a/images/motor_order/tri.svg b/public/images/motor_order/tri.svg similarity index 100% rename from images/motor_order/tri.svg rename to public/images/motor_order/tri.svg diff --git a/images/motor_order/vtail_quad.svg b/public/images/motor_order/vtail_quad.svg similarity index 100% rename from images/motor_order/vtail_quad.svg rename to public/images/motor_order/vtail_quad.svg diff --git a/images/motor_order/y4.svg b/public/images/motor_order/y4.svg similarity index 100% rename from images/motor_order/y4.svg rename to public/images/motor_order/y4.svg diff --git a/images/motor_order/y6.svg b/public/images/motor_order/y6.svg similarity index 100% rename from images/motor_order/y6.svg rename to public/images/motor_order/y6.svg diff --git a/images/bf_icon_128.png b/public/images/pwa/bf_icon_128.png similarity index 100% rename from images/bf_icon_128.png rename to public/images/pwa/bf_icon_128.png diff --git a/public/images/pwa/bf_icon_192.png b/public/images/pwa/bf_icon_192.png new file mode 100644 index 00000000..83c6e548 Binary files /dev/null and b/public/images/pwa/bf_icon_192.png differ diff --git a/public/images/pwa/bf_icon_256.png b/public/images/pwa/bf_icon_256.png new file mode 100644 index 00000000..c186cb44 Binary files /dev/null and b/public/images/pwa/bf_icon_256.png differ diff --git a/images/stick_modes/Mode_1.png b/public/images/stick_modes/Mode_1.png similarity index 100% rename from images/stick_modes/Mode_1.png rename to public/images/stick_modes/Mode_1.png diff --git a/images/stick_modes/Mode_2.png b/public/images/stick_modes/Mode_2.png similarity index 100% rename from images/stick_modes/Mode_2.png rename to public/images/stick_modes/Mode_2.png diff --git a/images/stick_modes/Mode_3.png b/public/images/stick_modes/Mode_3.png similarity index 100% rename from images/stick_modes/Mode_3.png rename to public/images/stick_modes/Mode_3.png diff --git a/images/stick_modes/Mode_4.png b/public/images/stick_modes/Mode_4.png similarity index 100% rename from images/stick_modes/Mode_4.png rename to public/images/stick_modes/Mode_4.png diff --git a/js/vendor/FileSaver.js b/public/js/FileSaver.js similarity index 100% rename from js/vendor/FileSaver.js rename to public/js/FileSaver.js diff --git a/js/complex.js b/public/js/complex.js similarity index 100% rename from js/complex.js rename to public/js/complex.js diff --git a/js/vendor/jquery-1.11.3.min.js b/public/js/jquery-1.11.3.min.js similarity index 100% rename from js/vendor/jquery-1.11.3.min.js rename to public/js/jquery-1.11.3.min.js diff --git a/js/vendor/jquery-ui-1.11.4.min.js b/public/js/jquery-ui-1.11.4.min.js similarity index 100% rename from js/vendor/jquery-ui-1.11.4.min.js rename to public/js/jquery-ui-1.11.4.min.js diff --git a/js/vendor/jquery.ba-throttle-debounce.js b/public/js/jquery.ba-throttle-debounce.js similarity index 100% rename from js/vendor/jquery.ba-throttle-debounce.js rename to public/js/jquery.ba-throttle-debounce.js diff --git a/public/js/lodash.min.js b/public/js/lodash.min.js new file mode 100644 index 00000000..4219da73 --- /dev/null +++ b/public/js/lodash.min.js @@ -0,0 +1,140 @@ +/** + * @license + * Lodash + * Copyright OpenJS Foundation and other contributors + * Released under MIT license + * Based on Underscore.js 1.8.3 + * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors + */ +(function(){function n(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function t(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u-1}function f(n,t,r){for(var e=-1,u=null==n?0:n.length;++e-1;);return r}function L(n,t){for(var r=n.length;r--&&y(t,n[r],0)>-1;);return r}function C(n,t){for(var r=n.length,e=0;r--;)n[r]===t&&++e; +return e}function U(n){return"\\"+Yr[n]}function B(n,t){return null==n?X:n[t]}function T(n){return Nr.test(n)}function $(n){return Pr.test(n)}function D(n){for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}function M(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function F(n,t){return function(r){return n(t(r))}}function N(n,t){for(var r=-1,e=n.length,u=0,i=[];++r>>1,$n=[["ary",mn],["bind",_n],["bindKey",vn],["curry",yn],["curryRight",dn],["flip",jn],["partial",bn],["partialRight",wn],["rearg",xn]],Dn="[object Arguments]",Mn="[object Array]",Fn="[object AsyncFunction]",Nn="[object Boolean]",Pn="[object Date]",qn="[object DOMException]",Zn="[object Error]",Kn="[object Function]",Vn="[object GeneratorFunction]",Gn="[object Map]",Hn="[object Number]",Jn="[object Null]",Yn="[object Object]",Qn="[object Promise]",Xn="[object Proxy]",nt="[object RegExp]",tt="[object Set]",rt="[object String]",et="[object Symbol]",ut="[object Undefined]",it="[object WeakMap]",ot="[object WeakSet]",ft="[object ArrayBuffer]",ct="[object DataView]",at="[object Float32Array]",lt="[object Float64Array]",st="[object Int8Array]",ht="[object Int16Array]",pt="[object Int32Array]",_t="[object Uint8Array]",vt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",dt=/\b__p \+= '';/g,bt=/\b(__p \+=) '' \+/g,wt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,mt=/&(?:amp|lt|gt|quot|#39);/g,xt=/[&<>"']/g,jt=RegExp(mt.source),At=RegExp(xt.source),kt=/<%-([\s\S]+?)%>/g,Ot=/<%([\s\S]+?)%>/g,It=/<%=([\s\S]+?)%>/g,Rt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,zt=/^\w*$/,Et=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,St=/[\\^$.*+?()[\]{}|]/g,Wt=RegExp(St.source),Lt=/^\s+/,Ct=/\s/,Ut=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Bt=/\{\n\/\* \[wrapped with (.+)\] \*/,Tt=/,? & /,$t=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,Dt=/[()=,{}\[\]\/\s]/,Mt=/\\(\\)?/g,Ft=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Nt=/\w*$/,Pt=/^[-+]0x[0-9a-f]+$/i,qt=/^0b[01]+$/i,Zt=/^\[object .+?Constructor\]$/,Kt=/^0o[0-7]+$/i,Vt=/^(?:0|[1-9]\d*)$/,Gt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Ht=/($^)/,Jt=/['\n\r\u2028\u2029\\]/g,Yt="\\ud800-\\udfff",Qt="\\u0300-\\u036f",Xt="\\ufe20-\\ufe2f",nr="\\u20d0-\\u20ff",tr=Qt+Xt+nr,rr="\\u2700-\\u27bf",er="a-z\\xdf-\\xf6\\xf8-\\xff",ur="\\xac\\xb1\\xd7\\xf7",ir="\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf",or="\\u2000-\\u206f",fr=" \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",cr="A-Z\\xc0-\\xd6\\xd8-\\xde",ar="\\ufe0e\\ufe0f",lr=ur+ir+or+fr,sr="['\u2019]",hr="["+Yt+"]",pr="["+lr+"]",_r="["+tr+"]",vr="\\d+",gr="["+rr+"]",yr="["+er+"]",dr="[^"+Yt+lr+vr+rr+er+cr+"]",br="\\ud83c[\\udffb-\\udfff]",wr="(?:"+_r+"|"+br+")",mr="[^"+Yt+"]",xr="(?:\\ud83c[\\udde6-\\uddff]){2}",jr="[\\ud800-\\udbff][\\udc00-\\udfff]",Ar="["+cr+"]",kr="\\u200d",Or="(?:"+yr+"|"+dr+")",Ir="(?:"+Ar+"|"+dr+")",Rr="(?:"+sr+"(?:d|ll|m|re|s|t|ve))?",zr="(?:"+sr+"(?:D|LL|M|RE|S|T|VE))?",Er=wr+"?",Sr="["+ar+"]?",Wr="(?:"+kr+"(?:"+[mr,xr,jr].join("|")+")"+Sr+Er+")*",Lr="\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",Cr="\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])",Ur=Sr+Er+Wr,Br="(?:"+[gr,xr,jr].join("|")+")"+Ur,Tr="(?:"+[mr+_r+"?",_r,xr,jr,hr].join("|")+")",$r=RegExp(sr,"g"),Dr=RegExp(_r,"g"),Mr=RegExp(br+"(?="+br+")|"+Tr+Ur,"g"),Fr=RegExp([Ar+"?"+yr+"+"+Rr+"(?="+[pr,Ar,"$"].join("|")+")",Ir+"+"+zr+"(?="+[pr,Ar+Or,"$"].join("|")+")",Ar+"?"+Or+"+"+Rr,Ar+"+"+zr,Cr,Lr,vr,Br].join("|"),"g"),Nr=RegExp("["+kr+Yt+tr+ar+"]"),Pr=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,qr=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],Zr=-1,Kr={}; +Kr[at]=Kr[lt]=Kr[st]=Kr[ht]=Kr[pt]=Kr[_t]=Kr[vt]=Kr[gt]=Kr[yt]=!0,Kr[Dn]=Kr[Mn]=Kr[ft]=Kr[Nn]=Kr[ct]=Kr[Pn]=Kr[Zn]=Kr[Kn]=Kr[Gn]=Kr[Hn]=Kr[Yn]=Kr[nt]=Kr[tt]=Kr[rt]=Kr[it]=!1;var Vr={};Vr[Dn]=Vr[Mn]=Vr[ft]=Vr[ct]=Vr[Nn]=Vr[Pn]=Vr[at]=Vr[lt]=Vr[st]=Vr[ht]=Vr[pt]=Vr[Gn]=Vr[Hn]=Vr[Yn]=Vr[nt]=Vr[tt]=Vr[rt]=Vr[et]=Vr[_t]=Vr[vt]=Vr[gt]=Vr[yt]=!0,Vr[Zn]=Vr[Kn]=Vr[it]=!1;var Gr={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a", +"\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae", +"\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C","\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g", +"\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i","\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O", +"\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S","\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w", +"\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe","\u0149":"'n","\u017f":"s"},Hr={"&":"&","<":"<",">":">",'"':""","'":"'"},Jr={"&":"&","<":"<",">":">",""":'"',"'":"'"},Yr={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Qr=parseFloat,Xr=parseInt,ne="object"==typeof global&&global&&global.Object===Object&&global,te="object"==typeof self&&self&&self.Object===Object&&self,re=ne||te||Function("return this")(),ee="object"==typeof exports&&exports&&!exports.nodeType&&exports,ue=ee&&"object"==typeof module&&module&&!module.nodeType&&module,ie=ue&&ue.exports===ee,oe=ie&&ne.process,fe=function(){ +try{var n=ue&&ue.require&&ue.require("util").types;return n?n:oe&&oe.binding&&oe.binding("util")}catch(n){}}(),ce=fe&&fe.isArrayBuffer,ae=fe&&fe.isDate,le=fe&&fe.isMap,se=fe&&fe.isRegExp,he=fe&&fe.isSet,pe=fe&&fe.isTypedArray,_e=m("length"),ve=x(Gr),ge=x(Hr),ye=x(Jr),de=function p(x){function Z(n){if(cc(n)&&!bh(n)&&!(n instanceof Ct)){if(n instanceof Y)return n;if(bl.call(n,"__wrapped__"))return eo(n)}return new Y(n)}function J(){}function Y(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t, +this.__index__=0,this.__values__=X}function Ct(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=Un,this.__views__=[]}function $t(){var n=new Ct(this.__wrapped__);return n.__actions__=Tu(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Tu(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Tu(this.__views__),n}function Yt(){if(this.__filtered__){var n=new Ct(this);n.__dir__=-1, +n.__filtered__=!0}else n=this.clone(),n.__dir__*=-1;return n}function Qt(){var n=this.__wrapped__.value(),t=this.__dir__,r=bh(n),e=t<0,u=r?n.length:0,i=Oi(0,u,this.__views__),o=i.start,f=i.end,c=f-o,a=e?f:o-1,l=this.__iteratees__,s=l.length,h=0,p=Hl(c,this.__takeCount__);if(!r||!e&&u==c&&p==c)return wu(n,this.__actions__);var _=[];n:for(;c--&&h-1}function lr(n,t){var r=this.__data__,e=Wr(r,n);return e<0?(++this.size,r.push([n,t])):r[e][1]=t,this}function sr(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t=t?n:t)),n}function Fr(n,t,e,u,i,o){var f,c=t&an,a=t&ln,l=t&sn;if(e&&(f=i?e(n,u,i,o):e(n)),f!==X)return f;if(!fc(n))return n;var s=bh(n);if(s){if(f=zi(n),!c)return Tu(n,f)}else{var h=zs(n),p=h==Kn||h==Vn;if(mh(n))return Iu(n,c);if(h==Yn||h==Dn||p&&!i){if(f=a||p?{}:Ei(n),!c)return a?Mu(n,Ur(f,n)):Du(n,Cr(f,n))}else{if(!Vr[h])return i?n:{};f=Si(n,h,c)}}o||(o=new wr);var _=o.get(n);if(_)return _;o.set(n,f),kh(n)?n.forEach(function(r){f.add(Fr(r,t,e,r,n,o))}):jh(n)&&n.forEach(function(r,u){ +f.set(u,Fr(r,t,e,u,n,o))});var v=l?a?di:yi:a?qc:Pc,g=s?X:v(n);return r(g||n,function(r,u){g&&(u=r,r=n[u]),Sr(f,u,Fr(r,t,e,u,n,o))}),f}function Nr(n){var t=Pc(n);return function(r){return Pr(r,n,t)}}function Pr(n,t,r){var e=r.length;if(null==n)return!e;for(n=ll(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===X&&!(u in n)||!i(o))return!1}return!0}function Gr(n,t,r){if("function"!=typeof n)throw new pl(en);return Ws(function(){n.apply(X,r)},t)}function Hr(n,t,r,e){var u=-1,i=o,a=!0,l=n.length,s=[],h=t.length; +if(!l)return s;r&&(t=c(t,z(r))),e?(i=f,a=!1):t.length>=tn&&(i=S,a=!1,t=new yr(t));n:for(;++uu?0:u+r), +e=e===X||e>u?u:kc(e),e<0&&(e+=u),e=r>e?0:Oc(e);r0&&r(f)?t>1?ee(f,t-1,r,e,u):a(u,f):e||(u[u.length]=f)}return u}function ue(n,t){return n&&bs(n,t,Pc)}function oe(n,t){return n&&ws(n,t,Pc)}function fe(n,t){return i(t,function(t){return uc(n[t])})}function _e(n,t){t=ku(t,n);for(var r=0,e=t.length;null!=n&&rt}function xe(n,t){return null!=n&&bl.call(n,t)}function je(n,t){return null!=n&&t in ll(n)}function Ae(n,t,r){return n>=Hl(t,r)&&n=120&&p.length>=120)?new yr(a&&p):X}p=n[0]; +var _=-1,v=l[0];n:for(;++_-1;)f!==n&&Ll.call(f,a,1),Ll.call(n,a,1);return n}function nu(n,t){for(var r=n?t.length:0,e=r-1;r--;){ +var u=t[r];if(r==e||u!==i){var i=u;Ci(u)?Ll.call(n,u,1):yu(n,u)}}return n}function tu(n,t){return n+Nl(Ql()*(t-n+1))}function ru(n,t,r,e){for(var u=-1,i=Gl(Fl((t-n)/(r||1)),0),o=il(i);i--;)o[e?i:++u]=n,n+=r;return o}function eu(n,t){var r="";if(!n||t<1||t>Wn)return r;do t%2&&(r+=n),t=Nl(t/2),t&&(n+=n);while(t);return r}function uu(n,t){return Ls(Vi(n,t,La),n+"")}function iu(n){return Ir(ra(n))}function ou(n,t){var r=ra(n);return Xi(r,Mr(t,0,r.length))}function fu(n,t,r,e){if(!fc(n))return n;t=ku(t,n); +for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++uu?0:u+t),r=r>u?u:r,r<0&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0;for(var i=il(u);++e>>1,o=n[i];null!==o&&!bc(o)&&(r?o<=t:o=tn){var s=t?null:ks(n);if(s)return P(s);c=!1,u=S,l=new yr}else l=t?[]:a;n:for(;++e=e?n:au(n,t,r)}function Iu(n,t){if(t)return n.slice();var r=n.length,e=zl?zl(r):new n.constructor(r); +return n.copy(e),e}function Ru(n){var t=new n.constructor(n.byteLength);return new Rl(t).set(new Rl(n)),t}function zu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.byteLength)}function Eu(n){var t=new n.constructor(n.source,Nt.exec(n));return t.lastIndex=n.lastIndex,t}function Su(n){return _s?ll(_s.call(n)):{}}function Wu(n,t){return new n.constructor(t?Ru(n.buffer):n.buffer,n.byteOffset,n.length)}function Lu(n,t){if(n!==t){var r=n!==X,e=null===n,u=n===n,i=bc(n),o=t!==X,f=null===t,c=t===t,a=bc(t); +if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n=f)return c;return c*("desc"==r[e]?-1:1)}}return n.index-t.index}function Uu(n,t,r,e){for(var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Gl(i-o,0),l=il(c+a),s=!e;++f1?r[u-1]:X,o=u>2?r[2]:X;for(i=n.length>3&&"function"==typeof i?(u--,i):X,o&&Ui(r[0],r[1],o)&&(i=u<3?X:i,u=1),t=ll(t);++e-1?u[i?t[o]:o]:X}}function Yu(n){return gi(function(t){var r=t.length,e=r,u=Y.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if("function"!=typeof i)throw new pl(en);if(u&&!o&&"wrapper"==bi(i))var o=new Y([],!0)}for(e=o?e:r;++e1&&d.reverse(),s&&cf))return!1;var a=i.get(n),l=i.get(t);if(a&&l)return a==t&&l==n;var s=-1,p=!0,_=r&pn?new yr:X;for(i.set(n,t),i.set(t,n);++s1?"& ":"")+t[e],t=t.join(r>2?", ":" "),n.replace(Ut,"{\n/* [wrapped with "+t+"] */\n")}function Li(n){return bh(n)||dh(n)||!!(Cl&&n&&n[Cl])}function Ci(n,t){var r=typeof n; +return t=null==t?Wn:t,!!t&&("number"==r||"symbol"!=r&&Vt.test(n))&&n>-1&&n%1==0&&n0){if(++t>=On)return arguments[0]}else t=0; +return n.apply(X,arguments)}}function Xi(n,t){var r=-1,e=n.length,u=e-1;for(t=t===X?e:t;++r=this.__values__.length;return{done:n,value:n?X:this.__values__[this.__index__++]}}function uf(){return this}function of(n){for(var t,r=this;r instanceof J;){var e=eo(r);e.__index__=0,e.__values__=X,t?u.__wrapped__=e:t=e;var u=e;r=r.__wrapped__}return u.__wrapped__=n,t}function ff(){var n=this.__wrapped__;if(n instanceof Ct){var t=n;return this.__actions__.length&&(t=new Ct(this)),t=t.reverse(),t.__actions__.push({func:nf,args:[Eo],thisArg:X}),new Y(t,this.__chain__)}return this.thru(Eo); +}function cf(){return wu(this.__wrapped__,this.__actions__)}function af(n,t,r){var e=bh(n)?u:Jr;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function lf(n,t){return(bh(n)?i:te)(n,mi(t,3))}function sf(n,t){return ee(yf(n,t),1)}function hf(n,t){return ee(yf(n,t),Sn)}function pf(n,t,r){return r=r===X?1:kc(r),ee(yf(n,t),r)}function _f(n,t){return(bh(n)?r:ys)(n,mi(t,3))}function vf(n,t){return(bh(n)?e:ds)(n,mi(t,3))}function gf(n,t,r,e){n=Hf(n)?n:ra(n),r=r&&!e?kc(r):0;var u=n.length;return r<0&&(r=Gl(u+r,0)), +dc(n)?r<=u&&n.indexOf(t,r)>-1:!!u&&y(n,t,r)>-1}function yf(n,t){return(bh(n)?c:Pe)(n,mi(t,3))}function df(n,t,r,e){return null==n?[]:(bh(t)||(t=null==t?[]:[t]),r=e?X:r,bh(r)||(r=null==r?[]:[r]),He(n,t,r))}function bf(n,t,r){var e=bh(n)?l:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ys)}function wf(n,t,r){var e=bh(n)?s:j,u=arguments.length<3;return e(n,mi(t,4),r,u,ds)}function mf(n,t){return(bh(n)?i:te)(n,Uf(mi(t,3)))}function xf(n){return(bh(n)?Ir:iu)(n)}function jf(n,t,r){return t=(r?Ui(n,t,r):t===X)?1:kc(t), +(bh(n)?Rr:ou)(n,t)}function Af(n){return(bh(n)?zr:cu)(n)}function kf(n){if(null==n)return 0;if(Hf(n))return dc(n)?V(n):n.length;var t=zs(n);return t==Gn||t==tt?n.size:Me(n).length}function Of(n,t,r){var e=bh(n)?h:lu;return r&&Ui(n,t,r)&&(t=X),e(n,mi(t,3))}function If(n,t){if("function"!=typeof t)throw new pl(en);return n=kc(n),function(){if(--n<1)return t.apply(this,arguments)}}function Rf(n,t,r){return t=r?X:t,t=n&&null==t?n.length:t,ai(n,mn,X,X,X,X,t)}function zf(n,t){var r;if("function"!=typeof t)throw new pl(en); +return n=kc(n),function(){return--n>0&&(r=t.apply(this,arguments)),n<=1&&(t=X),r}}function Ef(n,t,r){t=r?X:t;var e=ai(n,yn,X,X,X,X,X,t);return e.placeholder=Ef.placeholder,e}function Sf(n,t,r){t=r?X:t;var e=ai(n,dn,X,X,X,X,X,t);return e.placeholder=Sf.placeholder,e}function Wf(n,t,r){function e(t){var r=h,e=p;return h=p=X,d=t,v=n.apply(e,r)}function u(n){return d=n,g=Ws(f,t),b?e(n):v}function i(n){var r=n-y,e=n-d,u=t-r;return w?Hl(u,_-e):u}function o(n){var r=n-y,e=n-d;return y===X||r>=t||r<0||w&&e>=_; +}function f(){var n=fh();return o(n)?c(n):(g=Ws(f,i(n)),X)}function c(n){return g=X,m&&h?e(n):(h=p=X,v)}function a(){g!==X&&As(g),d=0,h=y=p=g=X}function l(){return g===X?v:c(fh())}function s(){var n=fh(),r=o(n);if(h=arguments,p=this,y=n,r){if(g===X)return u(y);if(w)return As(g),g=Ws(f,t),e(y)}return g===X&&(g=Ws(f,t)),v}var h,p,_,v,g,y,d=0,b=!1,w=!1,m=!0;if("function"!=typeof n)throw new pl(en);return t=Ic(t)||0,fc(r)&&(b=!!r.leading,w="maxWait"in r,_=w?Gl(Ic(r.maxWait)||0,t):_,m="trailing"in r?!!r.trailing:m), +s.cancel=a,s.flush=l,s}function Lf(n){return ai(n,jn)}function Cf(n,t){if("function"!=typeof n||null!=t&&"function"!=typeof t)throw new pl(en);var r=function(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;if(i.has(u))return i.get(u);var o=n.apply(this,e);return r.cache=i.set(u,o)||i,o};return r.cache=new(Cf.Cache||sr),r}function Uf(n){if("function"!=typeof n)throw new pl(en);return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2: +return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function Bf(n){return zf(2,n)}function Tf(n,t){if("function"!=typeof n)throw new pl(en);return t=t===X?t:kc(t),uu(n,t)}function $f(t,r){if("function"!=typeof t)throw new pl(en);return r=null==r?0:Gl(kc(r),0),uu(function(e){var u=e[r],i=Ou(e,0,r);return u&&a(i,u),n(t,this,i)})}function Df(n,t,r){var e=!0,u=!0;if("function"!=typeof n)throw new pl(en);return fc(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u), +Wf(n,t,{leading:e,maxWait:t,trailing:u})}function Mf(n){return Rf(n,1)}function Ff(n,t){return ph(Au(t),n)}function Nf(){if(!arguments.length)return[];var n=arguments[0];return bh(n)?n:[n]}function Pf(n){return Fr(n,sn)}function qf(n,t){return t="function"==typeof t?t:X,Fr(n,sn,t)}function Zf(n){return Fr(n,an|sn)}function Kf(n,t){return t="function"==typeof t?t:X,Fr(n,an|sn,t)}function Vf(n,t){return null==t||Pr(n,t,Pc(t))}function Gf(n,t){return n===t||n!==n&&t!==t}function Hf(n){return null!=n&&oc(n.length)&&!uc(n); +}function Jf(n){return cc(n)&&Hf(n)}function Yf(n){return n===!0||n===!1||cc(n)&&we(n)==Nn}function Qf(n){return cc(n)&&1===n.nodeType&&!gc(n)}function Xf(n){if(null==n)return!0;if(Hf(n)&&(bh(n)||"string"==typeof n||"function"==typeof n.splice||mh(n)||Oh(n)||dh(n)))return!n.length;var t=zs(n);if(t==Gn||t==tt)return!n.size;if(Mi(n))return!Me(n).length;for(var r in n)if(bl.call(n,r))return!1;return!0}function nc(n,t){return Se(n,t)}function tc(n,t,r){r="function"==typeof r?r:X;var e=r?r(n,t):X;return e===X?Se(n,t,X,r):!!e; +}function rc(n){if(!cc(n))return!1;var t=we(n);return t==Zn||t==qn||"string"==typeof n.message&&"string"==typeof n.name&&!gc(n)}function ec(n){return"number"==typeof n&&Zl(n)}function uc(n){if(!fc(n))return!1;var t=we(n);return t==Kn||t==Vn||t==Fn||t==Xn}function ic(n){return"number"==typeof n&&n==kc(n)}function oc(n){return"number"==typeof n&&n>-1&&n%1==0&&n<=Wn}function fc(n){var t=typeof n;return null!=n&&("object"==t||"function"==t)}function cc(n){return null!=n&&"object"==typeof n}function ac(n,t){ +return n===t||Ce(n,t,ji(t))}function lc(n,t,r){return r="function"==typeof r?r:X,Ce(n,t,ji(t),r)}function sc(n){return vc(n)&&n!=+n}function hc(n){if(Es(n))throw new fl(rn);return Ue(n)}function pc(n){return null===n}function _c(n){return null==n}function vc(n){return"number"==typeof n||cc(n)&&we(n)==Hn}function gc(n){if(!cc(n)||we(n)!=Yn)return!1;var t=El(n);if(null===t)return!0;var r=bl.call(t,"constructor")&&t.constructor;return"function"==typeof r&&r instanceof r&&dl.call(r)==jl}function yc(n){ +return ic(n)&&n>=-Wn&&n<=Wn}function dc(n){return"string"==typeof n||!bh(n)&&cc(n)&&we(n)==rt}function bc(n){return"symbol"==typeof n||cc(n)&&we(n)==et}function wc(n){return n===X}function mc(n){return cc(n)&&zs(n)==it}function xc(n){return cc(n)&&we(n)==ot}function jc(n){if(!n)return[];if(Hf(n))return dc(n)?G(n):Tu(n);if(Ul&&n[Ul])return D(n[Ul]());var t=zs(n);return(t==Gn?M:t==tt?P:ra)(n)}function Ac(n){if(!n)return 0===n?n:0;if(n=Ic(n),n===Sn||n===-Sn){return(n<0?-1:1)*Ln}return n===n?n:0}function kc(n){ +var t=Ac(n),r=t%1;return t===t?r?t-r:t:0}function Oc(n){return n?Mr(kc(n),0,Un):0}function Ic(n){if("number"==typeof n)return n;if(bc(n))return Cn;if(fc(n)){var t="function"==typeof n.valueOf?n.valueOf():n;n=fc(t)?t+"":t}if("string"!=typeof n)return 0===n?n:+n;n=R(n);var r=qt.test(n);return r||Kt.test(n)?Xr(n.slice(2),r?2:8):Pt.test(n)?Cn:+n}function Rc(n){return $u(n,qc(n))}function zc(n){return n?Mr(kc(n),-Wn,Wn):0===n?n:0}function Ec(n){return null==n?"":vu(n)}function Sc(n,t){var r=gs(n);return null==t?r:Cr(r,t); +}function Wc(n,t){return v(n,mi(t,3),ue)}function Lc(n,t){return v(n,mi(t,3),oe)}function Cc(n,t){return null==n?n:bs(n,mi(t,3),qc)}function Uc(n,t){return null==n?n:ws(n,mi(t,3),qc)}function Bc(n,t){return n&&ue(n,mi(t,3))}function Tc(n,t){return n&&oe(n,mi(t,3))}function $c(n){return null==n?[]:fe(n,Pc(n))}function Dc(n){return null==n?[]:fe(n,qc(n))}function Mc(n,t,r){var e=null==n?X:_e(n,t);return e===X?r:e}function Fc(n,t){return null!=n&&Ri(n,t,xe)}function Nc(n,t){return null!=n&&Ri(n,t,je); +}function Pc(n){return Hf(n)?Or(n):Me(n)}function qc(n){return Hf(n)?Or(n,!0):Fe(n)}function Zc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,t(n,e,u),n)}),r}function Kc(n,t){var r={};return t=mi(t,3),ue(n,function(n,e,u){Br(r,e,t(n,e,u))}),r}function Vc(n,t){return Gc(n,Uf(mi(t)))}function Gc(n,t){if(null==n)return{};var r=c(di(n),function(n){return[n]});return t=mi(t),Ye(n,r,function(n,r){return t(n,r[0])})}function Hc(n,t,r){t=ku(t,n);var e=-1,u=t.length;for(u||(u=1,n=X);++et){ +var e=n;n=t,t=e}if(r||n%1||t%1){var u=Ql();return Hl(n+u*(t-n+Qr("1e-"+((u+"").length-1))),t)}return tu(n,t)}function fa(n){return Qh(Ec(n).toLowerCase())}function ca(n){return n=Ec(n),n&&n.replace(Gt,ve).replace(Dr,"")}function aa(n,t,r){n=Ec(n),t=vu(t);var e=n.length;r=r===X?e:Mr(kc(r),0,e);var u=r;return r-=t.length,r>=0&&n.slice(r,u)==t}function la(n){return n=Ec(n),n&&At.test(n)?n.replace(xt,ge):n}function sa(n){return n=Ec(n),n&&Wt.test(n)?n.replace(St,"\\$&"):n}function ha(n,t,r){n=Ec(n),t=kc(t); +var e=t?V(n):0;if(!t||e>=t)return n;var u=(t-e)/2;return ri(Nl(u),r)+n+ri(Fl(u),r)}function pa(n,t,r){n=Ec(n),t=kc(t);var e=t?V(n):0;return t&&e>>0)?(n=Ec(n),n&&("string"==typeof t||null!=t&&!Ah(t))&&(t=vu(t),!t&&T(n))?Ou(G(n),0,r):n.split(t,r)):[]}function ba(n,t,r){return n=Ec(n),r=null==r?0:Mr(kc(r),0,n.length),t=vu(t),n.slice(r,r+t.length)==t}function wa(n,t,r){var e=Z.templateSettings;r&&Ui(n,t,r)&&(t=X),n=Ec(n),t=Sh({},t,e,li);var u,i,o=Sh({},t.imports,e.imports,li),f=Pc(o),c=E(o,f),a=0,l=t.interpolate||Ht,s="__p += '",h=sl((t.escape||Ht).source+"|"+l.source+"|"+(l===It?Ft:Ht).source+"|"+(t.evaluate||Ht).source+"|$","g"),p="//# sourceURL="+(bl.call(t,"sourceURL")?(t.sourceURL+"").replace(/\s/g," "):"lodash.templateSources["+ ++Zr+"]")+"\n"; +n.replace(h,function(t,r,e,o,f,c){return e||(e=o),s+=n.slice(a,c).replace(Jt,U),r&&(u=!0,s+="' +\n__e("+r+") +\n'"),f&&(i=!0,s+="';\n"+f+";\n__p += '"),e&&(s+="' +\n((__t = ("+e+")) == null ? '' : __t) +\n'"),a=c+t.length,t}),s+="';\n";var _=bl.call(t,"variable")&&t.variable;if(_){if(Dt.test(_))throw new fl(un)}else s="with (obj) {\n"+s+"\n}\n";s=(i?s.replace(dt,""):s).replace(bt,"$1").replace(wt,"$1;"),s="function("+(_||"obj")+") {\n"+(_?"":"obj || (obj = {});\n")+"var __t, __p = ''"+(u?", __e = _.escape":"")+(i?", __j = Array.prototype.join;\nfunction print() { __p += __j.call(arguments, '') }\n":";\n")+s+"return __p\n}"; +var v=Xh(function(){return cl(f,p+"return "+s).apply(X,c)});if(v.source=s,rc(v))throw v;return v}function ma(n){return Ec(n).toLowerCase()}function xa(n){return Ec(n).toUpperCase()}function ja(n,t,r){if(n=Ec(n),n&&(r||t===X))return R(n);if(!n||!(t=vu(t)))return n;var e=G(n),u=G(t);return Ou(e,W(e,u),L(e,u)+1).join("")}function Aa(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.slice(0,H(n)+1);if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,0,L(e,G(t))+1).join("")}function ka(n,t,r){if(n=Ec(n),n&&(r||t===X))return n.replace(Lt,""); +if(!n||!(t=vu(t)))return n;var e=G(n);return Ou(e,W(e,G(t))).join("")}function Oa(n,t){var r=An,e=kn;if(fc(t)){var u="separator"in t?t.separator:u;r="length"in t?kc(t.length):r,e="omission"in t?vu(t.omission):e}n=Ec(n);var i=n.length;if(T(n)){var o=G(n);i=o.length}if(r>=i)return n;var f=r-V(e);if(f<1)return e;var c=o?Ou(o,0,f).join(""):n.slice(0,f);if(u===X)return c+e;if(o&&(f+=c.length-f),Ah(u)){if(n.slice(f).search(u)){var a,l=c;for(u.global||(u=sl(u.source,Ec(Nt.exec(u))+"g")),u.lastIndex=0;a=u.exec(l);)var s=a.index; +c=c.slice(0,s===X?f:s)}}else if(n.indexOf(vu(u),f)!=f){var h=c.lastIndexOf(u);h>-1&&(c=c.slice(0,h))}return c+e}function Ia(n){return n=Ec(n),n&&jt.test(n)?n.replace(mt,ye):n}function Ra(n,t,r){return n=Ec(n),t=r?X:t,t===X?$(n)?Q(n):_(n):n.match(t)||[]}function za(t){var r=null==t?0:t.length,e=mi();return t=r?c(t,function(n){if("function"!=typeof n[1])throw new pl(en);return[e(n[0]),n[1]]}):[],uu(function(e){for(var u=-1;++uWn)return[];var r=Un,e=Hl(n,Un);t=mi(t),n-=Un;for(var u=O(e,t);++r1?n[t-1]:X;return r="function"==typeof r?(n.pop(), +r):X,Ho(n,r)}),Qs=gi(function(n){var t=n.length,r=t?n[0]:0,e=this.__wrapped__,u=function(t){return Tr(t,n)};return!(t>1||this.__actions__.length)&&e instanceof Ct&&Ci(r)?(e=e.slice(r,+r+(t?1:0)),e.__actions__.push({func:nf,args:[u],thisArg:X}),new Y(e,this.__chain__).thru(function(n){return t&&!n.length&&n.push(X),n})):this.thru(u)}),Xs=Fu(function(n,t,r){bl.call(n,r)?++n[r]:Br(n,r,1)}),nh=Ju(ho),th=Ju(po),rh=Fu(function(n,t,r){bl.call(n,r)?n[r].push(t):Br(n,r,[t])}),eh=uu(function(t,r,e){var u=-1,i="function"==typeof r,o=Hf(t)?il(t.length):[]; +return ys(t,function(t){o[++u]=i?n(r,t,e):Ie(t,r,e)}),o}),uh=Fu(function(n,t,r){Br(n,r,t)}),ih=Fu(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),oh=uu(function(n,t){if(null==n)return[];var r=t.length;return r>1&&Ui(n,t[0],t[1])?t=[]:r>2&&Ui(t[0],t[1],t[2])&&(t=[t[0]]),He(n,ee(t,1),[])}),fh=Dl||function(){return re.Date.now()},ch=uu(function(n,t,r){var e=_n;if(r.length){var u=N(r,wi(ch));e|=bn}return ai(n,e,t,r,u)}),ah=uu(function(n,t,r){var e=_n|vn;if(r.length){var u=N(r,wi(ah));e|=bn; +}return ai(t,e,n,r,u)}),lh=uu(function(n,t){return Gr(n,1,t)}),sh=uu(function(n,t,r){return Gr(n,Ic(t)||0,r)});Cf.Cache=sr;var hh=js(function(t,r){r=1==r.length&&bh(r[0])?c(r[0],z(mi())):c(ee(r,1),z(mi()));var e=r.length;return uu(function(u){for(var i=-1,o=Hl(u.length,e);++i=t}),dh=Re(function(){return arguments}())?Re:function(n){return cc(n)&&bl.call(n,"callee")&&!Wl.call(n,"callee")},bh=il.isArray,wh=ce?z(ce):ze,mh=ql||qa,xh=ae?z(ae):Ee,jh=le?z(le):Le,Ah=se?z(se):Be,kh=he?z(he):Te,Oh=pe?z(pe):$e,Ih=ii(Ne),Rh=ii(function(n,t){return n<=t}),zh=Nu(function(n,t){if(Mi(t)||Hf(t))return $u(t,Pc(t),n),X;for(var r in t)bl.call(t,r)&&Sr(n,r,t[r])}),Eh=Nu(function(n,t){$u(t,qc(t),n)}),Sh=Nu(function(n,t,r,e){$u(t,qc(t),n,e)}),Wh=Nu(function(n,t,r,e){$u(t,Pc(t),n,e); +}),Lh=gi(Tr),Ch=uu(function(n,t){n=ll(n);var r=-1,e=t.length,u=e>2?t[2]:X;for(u&&Ui(t[0],t[1],u)&&(e=1);++r1),t}),$u(n,di(n),r),e&&(r=Fr(r,an|ln|sn,hi));for(var u=t.length;u--;)yu(r,t[u]);return r}),Nh=gi(function(n,t){return null==n?{}:Je(n,t)}),Ph=ci(Pc),qh=ci(qc),Zh=Vu(function(n,t,r){return t=t.toLowerCase(),n+(r?fa(t):t)}),Kh=Vu(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Vh=Vu(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Gh=Ku("toLowerCase"),Hh=Vu(function(n,t,r){ +return n+(r?"_":"")+t.toLowerCase()}),Jh=Vu(function(n,t,r){return n+(r?" ":"")+Qh(t)}),Yh=Vu(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),Qh=Ku("toUpperCase"),Xh=uu(function(t,r){try{return n(t,X,r)}catch(n){return rc(n)?n:new fl(n)}}),np=gi(function(n,t){return r(t,function(t){t=no(t),Br(n,t,ch(n[t],n))}),n}),tp=Yu(),rp=Yu(!0),ep=uu(function(n,t){return function(r){return Ie(r,n,t)}}),up=uu(function(n,t){return function(r){return Ie(n,r,t)}}),ip=ti(c),op=ti(u),fp=ti(h),cp=ui(),ap=ui(!0),lp=ni(function(n,t){ +return n+t},0),sp=fi("ceil"),hp=ni(function(n,t){return n/t},1),pp=fi("floor"),_p=ni(function(n,t){return n*t},1),vp=fi("round"),gp=ni(function(n,t){return n-t},0);return Z.after=If,Z.ary=Rf,Z.assign=zh,Z.assignIn=Eh,Z.assignInWith=Sh,Z.assignWith=Wh,Z.at=Lh,Z.before=zf,Z.bind=ch,Z.bindAll=np,Z.bindKey=ah,Z.castArray=Nf,Z.chain=Qo,Z.chunk=uo,Z.compact=io,Z.concat=oo,Z.cond=za,Z.conforms=Ea,Z.constant=Sa,Z.countBy=Xs,Z.create=Sc,Z.curry=Ef,Z.curryRight=Sf,Z.debounce=Wf,Z.defaults=Ch,Z.defaultsDeep=Uh, +Z.defer=lh,Z.delay=sh,Z.difference=Us,Z.differenceBy=Bs,Z.differenceWith=Ts,Z.drop=fo,Z.dropRight=co,Z.dropRightWhile=ao,Z.dropWhile=lo,Z.fill=so,Z.filter=lf,Z.flatMap=sf,Z.flatMapDeep=hf,Z.flatMapDepth=pf,Z.flatten=_o,Z.flattenDeep=vo,Z.flattenDepth=go,Z.flip=Lf,Z.flow=tp,Z.flowRight=rp,Z.fromPairs=yo,Z.functions=$c,Z.functionsIn=Dc,Z.groupBy=rh,Z.initial=mo,Z.intersection=$s,Z.intersectionBy=Ds,Z.intersectionWith=Ms,Z.invert=Bh,Z.invertBy=Th,Z.invokeMap=eh,Z.iteratee=Ca,Z.keyBy=uh,Z.keys=Pc,Z.keysIn=qc, +Z.map=yf,Z.mapKeys=Zc,Z.mapValues=Kc,Z.matches=Ua,Z.matchesProperty=Ba,Z.memoize=Cf,Z.merge=Dh,Z.mergeWith=Mh,Z.method=ep,Z.methodOf=up,Z.mixin=Ta,Z.negate=Uf,Z.nthArg=Ma,Z.omit=Fh,Z.omitBy=Vc,Z.once=Bf,Z.orderBy=df,Z.over=ip,Z.overArgs=hh,Z.overEvery=op,Z.overSome=fp,Z.partial=ph,Z.partialRight=_h,Z.partition=ih,Z.pick=Nh,Z.pickBy=Gc,Z.property=Fa,Z.propertyOf=Na,Z.pull=Fs,Z.pullAll=Oo,Z.pullAllBy=Io,Z.pullAllWith=Ro,Z.pullAt=Ns,Z.range=cp,Z.rangeRight=ap,Z.rearg=vh,Z.reject=mf,Z.remove=zo,Z.rest=Tf, +Z.reverse=Eo,Z.sampleSize=jf,Z.set=Jc,Z.setWith=Yc,Z.shuffle=Af,Z.slice=So,Z.sortBy=oh,Z.sortedUniq=$o,Z.sortedUniqBy=Do,Z.split=da,Z.spread=$f,Z.tail=Mo,Z.take=Fo,Z.takeRight=No,Z.takeRightWhile=Po,Z.takeWhile=qo,Z.tap=Xo,Z.throttle=Df,Z.thru=nf,Z.toArray=jc,Z.toPairs=Ph,Z.toPairsIn=qh,Z.toPath=Ha,Z.toPlainObject=Rc,Z.transform=Qc,Z.unary=Mf,Z.union=Ps,Z.unionBy=qs,Z.unionWith=Zs,Z.uniq=Zo,Z.uniqBy=Ko,Z.uniqWith=Vo,Z.unset=Xc,Z.unzip=Go,Z.unzipWith=Ho,Z.update=na,Z.updateWith=ta,Z.values=ra,Z.valuesIn=ea, +Z.without=Ks,Z.words=Ra,Z.wrap=Ff,Z.xor=Vs,Z.xorBy=Gs,Z.xorWith=Hs,Z.zip=Js,Z.zipObject=Jo,Z.zipObjectDeep=Yo,Z.zipWith=Ys,Z.entries=Ph,Z.entriesIn=qh,Z.extend=Eh,Z.extendWith=Sh,Ta(Z,Z),Z.add=lp,Z.attempt=Xh,Z.camelCase=Zh,Z.capitalize=fa,Z.ceil=sp,Z.clamp=ua,Z.clone=Pf,Z.cloneDeep=Zf,Z.cloneDeepWith=Kf,Z.cloneWith=qf,Z.conformsTo=Vf,Z.deburr=ca,Z.defaultTo=Wa,Z.divide=hp,Z.endsWith=aa,Z.eq=Gf,Z.escape=la,Z.escapeRegExp=sa,Z.every=af,Z.find=nh,Z.findIndex=ho,Z.findKey=Wc,Z.findLast=th,Z.findLastIndex=po, +Z.findLastKey=Lc,Z.floor=pp,Z.forEach=_f,Z.forEachRight=vf,Z.forIn=Cc,Z.forInRight=Uc,Z.forOwn=Bc,Z.forOwnRight=Tc,Z.get=Mc,Z.gt=gh,Z.gte=yh,Z.has=Fc,Z.hasIn=Nc,Z.head=bo,Z.identity=La,Z.includes=gf,Z.indexOf=wo,Z.inRange=ia,Z.invoke=$h,Z.isArguments=dh,Z.isArray=bh,Z.isArrayBuffer=wh,Z.isArrayLike=Hf,Z.isArrayLikeObject=Jf,Z.isBoolean=Yf,Z.isBuffer=mh,Z.isDate=xh,Z.isElement=Qf,Z.isEmpty=Xf,Z.isEqual=nc,Z.isEqualWith=tc,Z.isError=rc,Z.isFinite=ec,Z.isFunction=uc,Z.isInteger=ic,Z.isLength=oc,Z.isMap=jh, +Z.isMatch=ac,Z.isMatchWith=lc,Z.isNaN=sc,Z.isNative=hc,Z.isNil=_c,Z.isNull=pc,Z.isNumber=vc,Z.isObject=fc,Z.isObjectLike=cc,Z.isPlainObject=gc,Z.isRegExp=Ah,Z.isSafeInteger=yc,Z.isSet=kh,Z.isString=dc,Z.isSymbol=bc,Z.isTypedArray=Oh,Z.isUndefined=wc,Z.isWeakMap=mc,Z.isWeakSet=xc,Z.join=xo,Z.kebabCase=Kh,Z.last=jo,Z.lastIndexOf=Ao,Z.lowerCase=Vh,Z.lowerFirst=Gh,Z.lt=Ih,Z.lte=Rh,Z.max=Ya,Z.maxBy=Qa,Z.mean=Xa,Z.meanBy=nl,Z.min=tl,Z.minBy=rl,Z.stubArray=Pa,Z.stubFalse=qa,Z.stubObject=Za,Z.stubString=Ka, +Z.stubTrue=Va,Z.multiply=_p,Z.nth=ko,Z.noConflict=$a,Z.noop=Da,Z.now=fh,Z.pad=ha,Z.padEnd=pa,Z.padStart=_a,Z.parseInt=va,Z.random=oa,Z.reduce=bf,Z.reduceRight=wf,Z.repeat=ga,Z.replace=ya,Z.result=Hc,Z.round=vp,Z.runInContext=p,Z.sample=xf,Z.size=kf,Z.snakeCase=Hh,Z.some=Of,Z.sortedIndex=Wo,Z.sortedIndexBy=Lo,Z.sortedIndexOf=Co,Z.sortedLastIndex=Uo,Z.sortedLastIndexBy=Bo,Z.sortedLastIndexOf=To,Z.startCase=Jh,Z.startsWith=ba,Z.subtract=gp,Z.sum=el,Z.sumBy=ul,Z.template=wa,Z.times=Ga,Z.toFinite=Ac,Z.toInteger=kc, +Z.toLength=Oc,Z.toLower=ma,Z.toNumber=Ic,Z.toSafeInteger=zc,Z.toString=Ec,Z.toUpper=xa,Z.trim=ja,Z.trimEnd=Aa,Z.trimStart=ka,Z.truncate=Oa,Z.unescape=Ia,Z.uniqueId=Ja,Z.upperCase=Yh,Z.upperFirst=Qh,Z.each=_f,Z.eachRight=vf,Z.first=bo,Ta(Z,function(){var n={};return ue(Z,function(t,r){bl.call(Z.prototype,r)||(n[r]=t)}),n}(),{chain:!1}),Z.VERSION=nn,r(["bind","bindKey","curry","curryRight","partial","partialRight"],function(n){Z[n].placeholder=Z}),r(["drop","take"],function(n,t){Ct.prototype[n]=function(r){ +r=r===X?1:Gl(kc(r),0);var e=this.__filtered__&&!t?new Ct(this):this.clone();return e.__filtered__?e.__takeCount__=Hl(r,e.__takeCount__):e.__views__.push({size:Hl(r,Un),type:n+(e.__dir__<0?"Right":"")}),e},Ct.prototype[n+"Right"]=function(t){return this.reverse()[n](t).reverse()}}),r(["filter","map","takeWhile"],function(n,t){var r=t+1,e=r==Rn||r==En;Ct.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:mi(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),r(["head","last"],function(n,t){ +var r="take"+(t?"Right":"");Ct.prototype[n]=function(){return this[r](1).value()[0]}}),r(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Ct.prototype[n]=function(){return this.__filtered__?new Ct(this):this[r](1)}}),Ct.prototype.compact=function(){return this.filter(La)},Ct.prototype.find=function(n){return this.filter(n).head()},Ct.prototype.findLast=function(n){return this.reverse().find(n)},Ct.prototype.invokeMap=uu(function(n,t){return"function"==typeof n?new Ct(this):this.map(function(r){ +return Ie(r,n,t)})}),Ct.prototype.reject=function(n){return this.filter(Uf(mi(n)))},Ct.prototype.slice=function(n,t){n=kc(n);var r=this;return r.__filtered__&&(n>0||t<0)?new Ct(r):(n<0?r=r.takeRight(-n):n&&(r=r.drop(n)),t!==X&&(t=kc(t),r=t<0?r.dropRight(-t):r.take(t-n)),r)},Ct.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Ct.prototype.toArray=function(){return this.take(Un)},ue(Ct.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=Z[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t); +u&&(Z.prototype[t]=function(){var t=this.__wrapped__,o=e?[1]:arguments,f=t instanceof Ct,c=o[0],l=f||bh(t),s=function(n){var t=u.apply(Z,a([n],o));return e&&h?t[0]:t};l&&r&&"function"==typeof c&&1!=c.length&&(f=l=!1);var h=this.__chain__,p=!!this.__actions__.length,_=i&&!h,v=f&&!p;if(!i&&l){t=v?t:new Ct(this);var g=n.apply(t,o);return g.__actions__.push({func:nf,args:[s],thisArg:X}),new Y(g,h)}return _&&v?n.apply(this,o):(g=this.thru(s),_?e?g.value()[0]:g.value():g)})}),r(["pop","push","shift","sort","splice","unshift"],function(n){ +var t=_l[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);Z.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(bh(u)?u:[],n)}return this[r](function(r){return t.apply(bh(r)?r:[],n)})}}),ue(Ct.prototype,function(n,t){var r=Z[t];if(r){var e=r.name+"";bl.call(fs,e)||(fs[e]=[]),fs[e].push({name:t,func:r})}}),fs[Qu(X,vn).name]=[{name:"wrapper",func:X}],Ct.prototype.clone=$t,Ct.prototype.reverse=Yt,Ct.prototype.value=Qt,Z.prototype.at=Qs, +Z.prototype.chain=tf,Z.prototype.commit=rf,Z.prototype.next=ef,Z.prototype.plant=of,Z.prototype.reverse=ff,Z.prototype.toJSON=Z.prototype.valueOf=Z.prototype.value=cf,Z.prototype.first=Z.prototype.head,Ul&&(Z.prototype[Ul]=uf),Z},be=de();"function"==typeof define&&"object"==typeof define.amd&&define.amd?(re._=be,define(function(){return be})):ue?((ue.exports=be)._=be,ee._=be):re._=be}).call(this); \ No newline at end of file diff --git a/js/vendor/modernizr-2.6.2-respond-1.1.0.min.js b/public/js/modernizr-2.6.2-respond-1.1.0.min.js similarity index 100% rename from js/vendor/modernizr-2.6.2-respond-1.1.0.min.js rename to public/js/modernizr-2.6.2-respond-1.1.0.min.js diff --git a/js/real.js b/public/js/real.js similarity index 100% rename from js/real.js rename to public/js/real.js diff --git a/js/vendor/semver.js b/public/js/semver.js similarity index 100% rename from js/vendor/semver.js rename to public/js/semver.js diff --git a/js/vendor/three.js b/public/js/three.js similarity index 100% rename from js/vendor/three.js rename to public/js/three.js diff --git a/js/vendor/three.min.js b/public/js/three.min.js similarity index 100% rename from js/vendor/three.min.js rename to public/js/three.min.js diff --git a/js/vendor/webm-writer/ArrayBufferDataStream.js b/public/js/webm-writer/ArrayBufferDataStream.js similarity index 100% rename from js/vendor/webm-writer/ArrayBufferDataStream.js rename to public/js/webm-writer/ArrayBufferDataStream.js diff --git a/js/vendor/webm-writer/BlobBuffer.js b/public/js/webm-writer/BlobBuffer.js similarity index 100% rename from js/vendor/webm-writer/BlobBuffer.js rename to public/js/webm-writer/BlobBuffer.js diff --git a/js/vendor/webm-writer/Readme.md b/public/js/webm-writer/Readme.md similarity index 100% rename from js/vendor/webm-writer/Readme.md rename to public/js/webm-writer/Readme.md diff --git a/js/vendor/webm-writer/WebMWriter.js b/public/js/webm-writer/WebMWriter.js similarity index 100% rename from js/vendor/webm-writer/WebMWriter.js rename to public/js/webm-writer/WebMWriter.js diff --git a/js/webworkers/csv-export-worker.js b/public/js/webworkers/csv-export-worker.js similarity index 69% rename from js/webworkers/csv-export-worker.js rename to public/js/webworkers/csv-export-worker.js index ad734130..3790e961 100644 --- a/js/webworkers/csv-export-worker.js +++ b/public/js/webworkers/csv-export-worker.js @@ -1,4 +1,4 @@ -importScripts("/node_modules/lodash/lodash.min.js"); +importScripts("/js/lodash.min.js"); onmessage = function(event) { @@ -25,6 +25,21 @@ onmessage = function(event) { .join(opts.columnDelimiter); } + /** + * Converts `null` entries in columns and other empty non-numeric values to NaN value string. + * + * @param {array} columns + * @returns {string} + */ + function joinColumnValues(columns) { + return _(columns) + .map(value => + (_.isNumber(value) || _.value) + ? value + : "NaN") + .join(opts.columnDelimiter); + } + let opts = event.data.opts, stringDelim = opts.quoteStrings ? opts.stringDelimiter @@ -32,7 +47,7 @@ onmessage = function(event) { mainFields = _([joinColumns(event.data.fieldNames)]) .concat(_(event.data.frames) .flatten() - .map(row => joinColumns(row)) + .map(row => joinColumnValues(row)) .value()) .join("\n"), headers = _(event.data.sysConfig) @@ -42,4 +57,4 @@ onmessage = function(event) { postMessage(result); -}; \ No newline at end of file +}; diff --git a/public/js/webworkers/gpx-export-worker.js b/public/js/webworkers/gpx-export-worker.js new file mode 100644 index 00000000..ee6afb10 --- /dev/null +++ b/public/js/webworkers/gpx-export-worker.js @@ -0,0 +1,52 @@ +importScripts("/js/lodash.min.js"); + +onmessage = function (event) { + const header = ` + + + + Betaflight Blackbox Explorer + + + `; + + const footer = ``; + + const timeIndex = event.data.fieldNames.indexOf("time"); + const latIndex = event.data.fieldNames.indexOf("GPS_coord[0]"); + const lngIndex = event.data.fieldNames.indexOf("GPS_coord[1]"); + const altitudeIndex = event.data.fieldNames.indexOf("GPS_altitude"); + + let trkpts = ""; + for (const chunk of event.data.frames) { + for (const frame of chunk) { + if (!frame[latIndex] || !frame[lngIndex]) { + continue; + } + const timeMillis = Math.floor(frame[timeIndex] / 1000); + const lat = frame[latIndex] / 10000000; + const lng = frame[lngIndex] / 10000000; + const altitude = frame[altitudeIndex] / 10; + + let date = new Date(event.data.sysConfig["Log start datetime"]); + date.setTime(date.getTime() + timeMillis); + + let trkpt = ``; + trkpt += `${altitude}`; + trkpt += ``; + trkpt += `\n`; + + trkpts += trkpt; + } + } + + let trk = ` + + ${trkpts} + + `; + + postMessage(header + "\n" + trk + "\n" + footer); +}; diff --git a/screenshots/pwa-install-dialog.webp b/screenshots/pwa-install-dialog.webp new file mode 100644 index 00000000..471f8502 Binary files /dev/null and b/screenshots/pwa-install-dialog.webp differ diff --git a/screenshots/url-bar.webp b/screenshots/url-bar.webp new file mode 100644 index 00000000..db7febb9 Binary files /dev/null and b/screenshots/url-bar.webp differ diff --git a/js/cache.js b/src/cache.js similarity index 96% rename from js/cache.js rename to src/cache.js index 7d2f3a37..79b559d6 100644 --- a/js/cache.js +++ b/src/cache.js @@ -1,7 +1,5 @@ -"use strict"; - /** - * A FIFO cache to hold key-pair mappings. Its capacity will be at least the intialCapacity + * A FIFO cache to hold key-pair mappings. Its capacity will be at least the initialCapacity * supplied on creation, which you can increase by increasing the "capacity" property. * * One extra element beyond the set capacity will be stored which can be fetched by calling "recycle()". @@ -10,7 +8,7 @@ * * Element age is determined by the time it was added or last get()'d from the cache. */ -function FIFOCache(initialCapacity) { +export function FIFOCache(initialCapacity) { //Private: var queue = [], diff --git a/js/configuration.js b/src/configuration.js similarity index 97% rename from js/configuration.js rename to src/configuration.js index b59bfb47..b57049d4 100644 --- a/js/configuration.js +++ b/src/configuration.js @@ -1,5 +1,3 @@ -'use strict'; - /** * Configuration * @@ -7,8 +5,7 @@ * */ - -function Configuration(file, configurationDefaults, showConfigFile) { +export function Configuration(file, configurationDefaults, showConfigFile) { // Private Variables var that = this; // generic pointer back to this function @@ -120,7 +117,7 @@ function Configuration(file, configurationDefaults, showConfigFile) { } -function ConfigurationDefaults(prefs) { +export function ConfigurationDefaults(prefs) { // Special configuration file that handles default values only diff --git a/js/craft_2d.js b/src/craft_2d.js similarity index 98% rename from js/craft_2d.js rename to src/craft_2d.js index ede08a56..e160121c 100644 --- a/js/craft_2d.js +++ b/src/craft_2d.js @@ -1,6 +1,4 @@ -"use strict"; - -function Craft2D(flightLog, canvas, propColors) { +export function Craft2D(flightLog, canvas, propColors) { var ARM_THICKNESS_MULTIPLIER = 0.18, ARM_EXTEND_BEYOND_MOTOR_MULTIPLIER = 1.1, @@ -22,7 +20,7 @@ function Craft2D(flightLog, canvas, propColors) { } var numMotors; - if(customMix===null) { + if(!customMix) { numMotors = propColors.length; } else { numMotors = customMix.motorOrder.length; @@ -38,7 +36,7 @@ function Craft2D(flightLog, canvas, propColors) { yawOffset; // Motor numbering in counter-clockwise order starting from the 3 o'clock position - if(customMix===null) { + if(!customMix) { switch (numMotors) { case 3: motorOrder = [0, 1, 2]; // Put motor 1 at the right diff --git a/js/craft_3d.js b/src/craft_3d.js similarity index 99% rename from js/craft_3d.js rename to src/craft_3d.js index 23cdc543..94eea37c 100644 --- a/js/craft_3d.js +++ b/src/craft_3d.js @@ -1,6 +1,4 @@ -"use strict"; - -function Craft3D(flightLog, canvas, propColors) { +export function Craft3D(flightLog, canvas, propColors) { var // Sets the distance between the center point and the center of the motor mount ARM_LENGTH = 1, diff --git a/css/header_dialog.css b/src/css/header_dialog.css similarity index 99% rename from css/header_dialog.css rename to src/css/header_dialog.css index 7b70e9b9..cdf3d1fc 100644 --- a/css/header_dialog.css +++ b/src/css/header_dialog.css @@ -1,4 +1,4 @@ -/* Colums START> */ +/* Columns START> */ .cf_column { min-height: 18px; margin-bottom: 0; diff --git a/css/keys_dialog.css b/src/css/keys_dialog.css similarity index 100% rename from css/keys_dialog.css rename to src/css/keys_dialog.css diff --git a/css/main.css b/src/css/main.css similarity index 92% rename from css/main.css rename to src/css/main.css index 78f59608..74c62483 100644 --- a/css/main.css +++ b/src/css/main.css @@ -1,3 +1,7 @@ +@import "bootstrap/dist/css/bootstrap.min.css"; +@import "bootstrap/dist/css/bootstrap-theme.min.css"; +@import "leaflet/dist/leaflet.css"; + body { padding-bottom:2em; } @@ -491,6 +495,7 @@ html.has-log .log-graph-config { #craftCanvas, #analyserCanvas, +#mapContainer, #stickCanvas { position:absolute; top:0; @@ -501,18 +506,58 @@ html.has-log .log-graph-config { html.has-craft #craftCanvas, html.has-analyser #analyserCanvas, +html.has-map #mapContainer, html.has-sticks #stickCanvas { display:block; } +#mapContainer.no-gps-data:before { + position: absolute; + display: block; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 9999; + content: ""; + background-color: gray; + opacity: .5; +} + +#mapContainer.no-gps-data:after { + position: absolute; + display: block; + top: calc(30%); + right: 0; + bottom: 0; + left: 0; + z-index: 10001; + content: "No GPS Data"; + text-align: center; + font-size: 3vw; +} + html.has-analyser-fullscreen.has-analyser .analyser input:not(.onlyFullScreenException) { display:block; } -#analyser { +#analyser ,#log-seek-bar { z-index: 10; } +/* This filters change the color of a black png image. For new colors check: https://codepen.io/sosuke/pen/Pjoqqp */ +.isBF #mapContainer .icon { + filter: invert(36%) sepia(28%) saturate(3957%) hue-rotate(28deg) brightness(93%) contrast(103%); +} + +.isCF #mapContainer .icon { + filter: invert(28%) sepia(100%) saturate(2050%) hue-rotate(134deg) brightness(100%) contrast(104%); +} + +.isINAV #mapContainer .icon { + filter: invert(14%) sepia(100%) saturate(4698%) hue-rotate(244deg) brightness(64%) contrast(130%); +} + .analyser:hover .non-shift #analyserResize { opacity: 1; height: auto; @@ -552,6 +597,7 @@ html.has-analyser-fullscreen.has-analyser .analyser input:not(.onlyFullScreenExc .analyser #spectrumType select { border-radius: 3px; padding: 0px 5px; + color: black; } .analyser:hover .non-shift #overdrawSpectrumType { @@ -561,7 +607,6 @@ html.has-analyser-fullscreen.has-analyser .analyser input:not(.onlyFullScreenExc } .analyser #overdrawSpectrumType { - color: #bbb; height: 0; overflow: hidden; opacity: 0; @@ -575,6 +620,7 @@ html.has-analyser-fullscreen.has-analyser .analyser input:not(.onlyFullScreenExc .analyser #overdrawSpectrumType select { border-radius: 3px; padding: 0px 5px; + color: black; } .analyser #analyserResize:hover { @@ -606,10 +652,42 @@ html.has-analyser-fullscreen.has-analyser .analyser input:not(.onlyFullScreenExc position: absolute; } -.analyser { +.analyser, .map-container, .log-seek-bar { position: absolute; } +#log-seek-bar { + width: 100%; +} + +.log-seek-bar:hover .non-shift #seekbarTypeSelect { + opacity: 1; + height: auto; + transition: opacity 500ms ease-in; +} + +#seekbarToolbar { + position: absolute; + top: 8px; + left: 20px; +} +.log-seek-bar #seekbarTypeSelect { + height: 0; + overflow: hidden; + opacity: 0; + left: 5px; + float: left; + z-index: 9; + position: absolute; + font-size: 9px; +} + +.log-seek-bar #seekbarTypeSelect select { + border-radius: 3px; + padding: 0px 5px; + color: black; +} + .log-graph video { position:absolute; top:0; @@ -719,6 +797,10 @@ html.has-config .configuration-list { padding-right: 0.7em; } +.graph-legend-field-name:hover { + text-decoration: underline; +} + .graph-legend-field-value { float: right; } @@ -741,6 +823,11 @@ html.has-config .configuration-list { margin-left: 0.7em; } +.graph-legend-field-visibility { + float: right; + margin-left: 0.7em; +} + html.has-video .graph-row, html.has-log .graph-row { display:-webkit-flex; @@ -774,6 +861,7 @@ html .view-analyser-fullscreen { html.has-analyser-sticks.isBF .view-analyser-sticks, html.has-analyser.isBF .view-analyser, +html.has-map.isBF .view-map, html.has-table.isBF .view-table, html.has-sticks.isBF .view-sticks, html.has-craft.isBF .view-craft, @@ -783,6 +871,7 @@ html:not(.video-hidden).isBF .view-video { html.has-analyser-sticks.isCF .view-analyser-sticks, html.has-analyser.isCF .view-analyser, +html.has-map.isCF .view-map, html.has-table.isCF .view-table, html.has-sticks.isCF .view-sticks, html.has-craft.isCF .view-craft, @@ -792,6 +881,7 @@ html:not(.video-hidden).isCF .view-video { html.has-analyser-sticks.isINAV .view-analyser-sticks, html.has-analyser.isINAV .view-analyser, +html.has-map.isINAV .view-map, html.has-table.isINAV .view-table, html.has-sticks.isINAV .view-sticks, html.has-craft.isINAV .view-craft, @@ -978,7 +1068,7 @@ html.has-log .video-top-controls { .playback-rate-control, .graph-zoom-control { - width: 130px; + width: 200px; margin-right: 13px; margin-top: 6px; } @@ -1132,19 +1222,19 @@ html.isINAV .navbar-logo { } html.isCF .navbar-logo img { - content: url(".././images/cf_logo_white.svg"); + content: url("/images/cf_logo_white.svg"); width: 160px; } html.isBF .navbar-logo img { - content: url(".././images/light-wide-2.svg"); + content: url("/images/light-wide-2.svg"); width: 282px; height: 50px; margin-left: 0 !important; } html.isINAV .navbar-logo img { - content: url(".././images/inav_logo_white.png"); + content: url("/images/inav_logo_white.png"); width: auto; left: -16px; top: 3px; @@ -1205,6 +1295,12 @@ html:not(.has-log) #status-bar { display: none; } +html:not(.has-gps) .view-map, +html:not(.has-gps) .map-container, +html:not(.has-gps) .btn-gpx-export { + display: none !important; +} + #status-bar .bookmark-1, #status-bar .bookmark-2, #status-bar .bookmark-3, @@ -1620,3 +1716,7 @@ dialog h3 { padding-right: 9px; line-height: 28px; } + +.actual-lograte { + color: red; +} diff --git a/css/user_settings_dialog.css b/src/css/user_settings_dialog.css similarity index 97% rename from css/user_settings_dialog.css rename to src/css/user_settings_dialog.css index 7f54e51c..f2918766 100644 --- a/css/user_settings_dialog.css +++ b/src/css/user_settings_dialog.css @@ -21,7 +21,7 @@ .user-settings-dialog .watermark-logo-image { - background-image: url('../images/logo-background.png'); + background-image: url('/images/logo-background.png'); height: 100px; width: 100px; background-repeat:no-repeat; diff --git a/js/csv-exporter.js b/src/csv-exporter.js similarity index 94% rename from js/csv-exporter.js rename to src/csv-exporter.js index cb76a196..7dae6bcd 100644 --- a/js/csv-exporter.js +++ b/src/csv-exporter.js @@ -1,5 +1,3 @@ -"use strict"; - /** * @typedef {object} ExportOptions * @property {string} columnDelimiter @@ -12,7 +10,7 @@ * @param {FlightLog} flightLog * @param {ExportOptions} [opts={}] */ -let CsvExporter = function(flightLog, opts={}) { +export function CsvExporter(flightLog, opts={}) { var opts = _.merge({ columnDelimiter: ",", @@ -44,4 +42,4 @@ let CsvExporter = function(flightLog, opts={}) { return { dump: dump, }; -}; \ No newline at end of file +}; diff --git a/src/datastream.js b/src/datastream.js new file mode 100644 index 00000000..f043a6df --- /dev/null +++ b/src/datastream.js @@ -0,0 +1,171 @@ +import { + signExtend16Bit, + signExtend8Bit, +} from "./tools"; + +var EOF = -1; + +/* + * Take an array of unsigned byte data and present it as a stream with various methods + * for reading data in different formats. + */ +export const ArrayDataStream = function(data, start, end) { + this.data = data; + this.eof = false; + this.start = start === undefined ? 0 : start; + this.end = end === undefined ? data.length : end; + this.pos = this.start; +}; + +/** + * Read a single byte from the string and turn it into a JavaScript string (assuming ASCII). + * + * @returns String containing one character, or EOF if the end of file was reached (eof flag + * is set). + */ +ArrayDataStream.prototype.readChar = function() { + if (this.pos < this.end) + return String.fromCharCode(this.data[this.pos++]); + + this.eof = true; + return EOF; +}; + +/** + * Read one unsigned byte from the stream + * + * @returns Unsigned byte, or EOF if the end of file was reached (eof flag is set). + */ +ArrayDataStream.prototype.readByte = function() { + if (this.pos < this.end) + return this.data[this.pos++]; + + this.eof = true; + return EOF; +}; + +//Synonym: +ArrayDataStream.prototype.readU8 = ArrayDataStream.prototype.readByte; + +ArrayDataStream.prototype.readS8 = function() { + return signExtend8Bit(this.readByte()); +}; + +ArrayDataStream.prototype.unreadChar = function(c) { + this.pos--; +}; + +ArrayDataStream.prototype.peekChar = function() { + if (this.pos < this.end) + return String.fromCharCode(this.data[this.pos]); + + this.eof = true; + return EOF; +}; + +/** + * Read a (maximally 32-bit) unsigned integer from the stream which was encoded in Variable Byte format. + * + * @returns the unsigned integer, or 0 if a valid integer could not be read (EOF was reached or integer format + * was invalid). + */ +ArrayDataStream.prototype.readUnsignedVB = function() { + var + i, b, + shift = 0, result = 0; + + // 5 bytes is enough to encode 32-bit unsigned quantities + for (i = 0; i < 5; i++) { + b = this.readByte(); + + if (b == EOF) + return 0; + + result = result | ((b & ~0x80) << shift); + + // Final byte? + if (b < 128) { + /* + * Force the 32-bit integer to be reinterpreted as unsigned by doing an unsigned right shift, so that + * the top bit being set doesn't cause it to interpreted as a negative number. + */ + return result >>> 0; + } + + shift += 7; + } + + // This VB-encoded int is too long! + return 0; +}; + +ArrayDataStream.prototype.readSignedVB = function() { + var unsigned = this.readUnsignedVB(); + + // Apply ZigZag decoding to recover the signed value + return (unsigned >>> 1) ^ -(unsigned & 1); +}; + +ArrayDataStream.prototype.readString = function(length) { + var + chars = new Array(length), + i; + + for (i = 0; i < length; i++) { + chars[i] = this.readChar(); + } + + return chars.join(""); +}; + +ArrayDataStream.prototype.readS16 = function() { + var + b1 = this.readByte(), + b2 = this.readByte(); + + return signExtend16Bit(b1 | (b2 << 8)); +}; + +ArrayDataStream.prototype.readU16 = function() { + var + b1 = this.readByte(), + b2 = this.readByte(); + + return b1 | (b2 << 8); +}; + +ArrayDataStream.prototype.readU32 = function() { + var + b1 = this.readByte(), + b2 = this.readByte(), + b3 = this.readByte(), + b4 = this.readByte(); + return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); +}; + +/** + * Search for the string 'needle' beginning from the current stream position up + * to the end position. Return the offset of the first occurrence found. + * + * @param needle + * String to search for + * @returns Position of the start of needle in the stream, or -1 if it wasn't + * found + */ +ArrayDataStream.prototype.nextOffsetOf = function(needle) { + var i, j; + + for (i = this.pos; i <= this.end - needle.length; i++) { + if (this.data[i] == needle[0]) { + for (j = 1; j < needle.length && this.data[i + j] == needle[j]; j++) + ; + + if (j == needle.length) + return i; + } + } + + return -1; +}; + +ArrayDataStream.prototype.EOF = EOF; diff --git a/js/decoders.js b/src/decoders.js similarity index 97% rename from js/decoders.js rename to src/decoders.js index 71863cd4..f7767076 100644 --- a/js/decoders.js +++ b/src/decoders.js @@ -1,4 +1,14 @@ -"use strict"; +import { ArrayDataStream } from './datastream.js'; +import { + signExtend24Bit, + signExtend16Bit, + signExtend8Bit, + signExtend7Bit, + signExtend6Bit, + signExtend5Bit, + signExtend4Bit, + signExtend2Bit, +} from './tools.js'; /** * Extend ArrayDataStream with decoders for advanced formats. diff --git a/js/expo.js b/src/expo.js similarity index 97% rename from js/expo.js rename to src/expo.js index c1a21a6c..ccac92fb 100644 --- a/js/expo.js +++ b/src/expo.js @@ -1,11 +1,9 @@ -"use strict"; - /** * Creates a lookup-table based expo curve, which takes values that range between -inputrange and +inputRange, and * scales them to -outputRange to +outputRange with the given power curve (curve <1.0 exaggerates values near the origin, * curve = 1.0 is a straight line mapping). */ -function ExpoCurve(offset, power, inputRange, outputRange, steps) { +export function ExpoCurve(offset, power, inputRange, outputRange, steps) { var curve, inputScale, rawInputScale; diff --git a/js/flightlog.js b/src/flightlog.js similarity index 91% rename from js/flightlog.js rename to src/flightlog.js index 9e8fb994..4c27b792 100644 --- a/js/flightlog.js +++ b/src/flightlog.js @@ -1,4 +1,23 @@ -"use strict"; +import { FlightLogIndex } from "./flightlog_index"; +import { FlightLogParser } from "./flightlog_parser"; +import { + MAX_MOTOR_NUMBER, + DSHOT_MIN_VALUE, + DSHOT_RANGE, + FlightLogEvent, + AXIS, + FAST_PROTOCOL, + SUPER_EXPO_YAW, +} from "./flightlog_fielddefs"; +import { IMU } from "./imu"; +import { FIFOCache } from "./cache"; +import { + binarySearchOrPrevious, + binarySearchOrNext, + constrain, + validate, + firmwareGreaterOrEqual, +} from "./tools"; /** * Uses a FlightLogParser to provide on-demand parsing (and caching) of the flight data log. @@ -9,9 +28,9 @@ * Additional computed fields are derived from the original data set and added as new fields in the resulting data. * Window based smoothing of fields is offered. */ -function FlightLog(logData) { +export function FlightLog(logData) { var - ADDITIONAL_COMPUTED_FIELD_COUNT = 23, /** attitude + PID_SUM + PID_ERROR + RCCOMMAND_SCALED + MOTOR_LEGACY **/ + ADDITIONAL_COMPUTED_FIELD_COUNT = 15, /** attitude + PID_SUM + PID_ERROR + RCCOMMAND_SCALED **/ that = this, logIndex = false, @@ -141,6 +160,8 @@ function FlightLog(logData) { return { times: directory.times, avgThrottle: directory.avgThrottle, + maxMotorDiff: directory.maxMotorDiff, + maxRC: directory.maxRC, hasEvent: directory.hasEvent }; }; @@ -214,8 +235,16 @@ function FlightLog(logData) { // Add names of slow fields which we'll merge into the main stream if (parser.frameDefs.S) { - for (let i = 0; i < parser.frameDefs.S.name.length; i++) { - fieldNames.push(parser.frameDefs.S.name[i]); + for (const name of parser.frameDefs.S.name) { + fieldNames.push(name); + } + } + // Add names of gps fields which we'll merge into the main stream + if (parser.frameDefs.G) { + for (const name of parser.frameDefs.G.name) { + if (name !== 'time') { // remove duplicate time field + fieldNames.push(name); + } } } @@ -229,18 +258,9 @@ function FlightLog(logData) { if (!that.isFieldDisabled().SETPOINT) { fieldNames.push("rcCommands[0]", "rcCommands[1]", "rcCommands[2]", "rcCommands[3]"); // Custom calculated scaled rccommand } - if (!(that.isFieldDisabled().GYRO || that.isFieldDisabled().PID)) { + if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().PID) { fieldNames.push("axisError[0]", "axisError[1]", "axisError[2]"); // Custom calculated error field } - if (!that.isFieldDisabled().MOTORS) { - for (let i = 0; i < MAX_MOTOR_NUMBER; i++) { - if (fieldNames.find(element => element === `motor[${i}]`)) { - fieldNames.push(`motorLegacy[${i}]`); - } else { - break; - } - } - } fieldNameToIndex = {}; for (let i = 0; i < fieldNames.length; i++) { @@ -375,16 +395,19 @@ function FlightLog(logData) { var mainFrameIndex = 0, slowFrameLength = parser.frameDefs.S ? parser.frameDefs.S.count : 0, - lastSlow = parser.frameDefs.S ? iframeDirectory.initialSlow[chunkIndex].slice(0) : []; + lastSlow = parser.frameDefs.S ? iframeDirectory.initialSlow[chunkIndex].slice(0) : [], + lastGPSLength = parser.frameDefs.G ? parser.frameDefs.G.count-1 : 0, // -1 since we exclude the time field + lastGPS = parser.frameDefs.G ? iframeDirectory.initialGPS[chunkIndex].slice(0) : []; parser.onFrameReady = function(frameValid, frame, frameType, frameOffset, frameSize) { var - destFrame; + destFrame, + destFrame_currentIndex; - // The G frames need to be processed always. They are "invalid" if not H (Home) has been detected - // before, but if not processed the viewer shows cuts and gaps. This happens if the quad takes off before + // The G frames need to be processed always. They are "invalid" if not H (Home) has been detected + // before, but if not processed the viewer shows cuts and gaps. This happens if the quad takes off before // fixing enough satellites. - if (frameValid || (frameType == 'G')) { + if (frameValid || (frameType == 'G' && frame)) { switch (frameType) { case 'P': case 'I': @@ -392,7 +415,7 @@ function FlightLog(logData) { //The parser re-uses the "frame" array so we must copy that data somewhere else var - numOutputFields = frame.length + slowFrameLength + ADDITIONAL_COMPUTED_FIELD_COUNT; + numOutputFields = frame.length + slowFrameLength + lastGPSLength + ADDITIONAL_COMPUTED_FIELD_COUNT; //Do we have a recycled chunk to copy on top of? if (chunk.frames[mainFrameIndex]) { @@ -409,10 +432,18 @@ function FlightLog(logData) { destFrame[i] = frame[i]; } + destFrame_currentIndex = frame.length; // Keeps track of where to place direct data in the destFrame. // Then merge in the last seen slow-frame data - for (var i = 0; i < slowFrameLength; i++) { - destFrame[i + frame.length] = lastSlow[i] === undefined ? null : lastSlow[i]; + for (let slowFrameIndex = 0; slowFrameIndex < slowFrameLength; slowFrameIndex++) { + destFrame[slowFrameIndex + destFrame_currentIndex] = lastSlow[slowFrameIndex] === undefined ? null : lastSlow[slowFrameIndex]; } + destFrame_currentIndex += slowFrameLength; + + // Also merge last seen gps-frame data + for (let gpsFrameIndex = 0; gpsFrameIndex < lastGPSLength; gpsFrameIndex++) { + destFrame[gpsFrameIndex + destFrame_currentIndex] = lastGPS[gpsFrameIndex] === undefined ? null : lastGPS[gpsFrameIndex]; + } + // destFrame_currentIndex += lastGPSLength; Add this line if you wish to add more fields. for (var i = 0; i < eventNeedsTimestamp.length; i++) { eventNeedsTimestamp[i].time = frame[FlightLogParser.prototype.FLIGHT_LOG_FIELD_INDEX_TIME]; @@ -446,10 +477,18 @@ function FlightLog(logData) { } break; case 'H': + // TODO + // contains coordinates only + // should be handled separately case 'G': - // TODO pending to do something with GPS frames // The frameValid can be false, when no GPS home (the G frames contains GPS position as diff of GPS Home position). // But other data from the G frame can be valid (time, num sats) + + //H Field G name:time,GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed,GPS_ground_course + frame.shift(); // remove time + for (let i = 0; i < frame.length; i++) { + lastGPS[i] = frame[i]; + } break; } } else { @@ -537,9 +576,6 @@ function FlightLog(logData) { [fieldNameToIndex["axisP[1]"], fieldNameToIndex["axisI[1]"], fieldNameToIndex["axisD[1]"], fieldNameToIndex["axisF[1]"]], [fieldNameToIndex["axisP[2]"], fieldNameToIndex["axisI[2]"], fieldNameToIndex["axisD[2]"], fieldNameToIndex["axisF[2]"]]]; - let motor = [fieldNameToIndex["motor[0]"], fieldNameToIndex["motor[1]"], fieldNameToIndex["motor[2]"], fieldNameToIndex["motor[3]"], - fieldNameToIndex["motor[4]"], fieldNameToIndex["motor[5]"], fieldNameToIndex["motor[6]"], fieldNameToIndex["motor[7]"]]; - let sourceChunkIndex; let destChunkIndex; let attitude; @@ -575,10 +611,6 @@ function FlightLog(logData) { axisPID = false; } - if (!motor[0]) { - motor = false; - } - sourceChunkIndex = 0; destChunkIndex = 0; @@ -604,7 +636,7 @@ function FlightLog(logData) { destFrame = destChunk.frames[i], fieldIndex = destFrame.length - ADDITIONAL_COMPUTED_FIELD_COUNT; - if (gyroADC) { //don't calculate attitude if no gyro data + if (!that.isFieldDisabled().GYRO) { //don't calculate attitude if no gyro data attitude = chunkIMU.updateEstimatedAttitude( [srcFrame[gyroADC[0]], srcFrame[gyroADC[1]], srcFrame[gyroADC[2]]], [srcFrame[accSmooth[0]], srcFrame[accSmooth[1]], srcFrame[accSmooth[2]]], @@ -619,7 +651,7 @@ function FlightLog(logData) { } // Add the Feedforward PID sum (P+I+D+F) - if (axisPID) { + if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().PID) { for (var axis = 0; axis < 3; axis++) { let pidSum = (axisPID[axis][0] !== undefined ? srcFrame[axisPID[axis][0]] : 0) + @@ -634,7 +666,7 @@ function FlightLog(logData) { } // Assign value - destFrame[fieldIndex++] = pidSum; + destFrame[fieldIndex++] = pidSum; } } @@ -644,42 +676,37 @@ function FlightLog(logData) { // Calculate the Scaled rcCommand (setpoint) (in deg/s, % for throttle) var fieldIndexRcCommands = fieldIndex; - // Since version 4.0 is not more a virtual field. Copy the real field to the virtual one to maintain the name, workspaces, etc. - if (sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(sysConfig.firmwareVersion, '4.0.0')) { - // Roll, pitch and yaw - for (var axis = 0; axis <= AXIS.YAW; axis++) { - destFrame[fieldIndex++] = srcFrame[setpoint[axis]]; - } - // Throttle - destFrame[fieldIndex++] = srcFrame[setpoint[AXIS.YAW + 1]]/10; + if (!that.isFieldDisabled().SETPOINT) { - // Versions earlier to 4.0 we must calculate the expected setpoint - } else { - // Roll, pitch and yaw - for (var axis = 0; axis <= AXIS.YAW; axis++) { + // Since version 4.0 is not more a virtual field. Copy the real field to the virtual one to maintain the name, workspaces, etc. + if (sysConfig.firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(sysConfig.firmwareVersion, '4.0.0')) { + // Roll, pitch and yaw + for (let axis = 0; axis <= AXIS.YAW; axis++) { + destFrame[fieldIndex++] = srcFrame[setpoint[axis]]; + } + // Throttle + destFrame[fieldIndex++] = srcFrame[setpoint[AXIS.YAW + 1]]/10; + + // Versions earlier to 4.0 we must calculate the expected setpoint + } else { + // Roll, pitch and yaw + for (let axis = 0; axis <= AXIS.YAW; axis++) { + destFrame[fieldIndex++] = rcCommand[axis] !== undefined ? that.rcCommandRawToDegreesPerSecond(srcFrame[rcCommand[axis]], axis, currentFlightMode) : 0; + } + // Throttle destFrame[fieldIndex++] = - (rcCommand[axis] !== undefined ? that.rcCommandRawToDegreesPerSecond(srcFrame[rcCommand[axis]], axis, currentFlightMode) : 0); - } - // Throttle - destFrame[fieldIndex++] = - (rcCommand[AXIS.YAW + 1] !== undefined ? that.rcCommandRawToThrottle(srcFrame[rcCommand[AXIS.YAW + 1]]) : 0); + (rcCommand[AXIS.YAW + 1] !== undefined ? that.rcCommandRawToThrottle(srcFrame[rcCommand[AXIS.YAW + 1]]) : 0); + } } // Calculate the PID Error - if (axisPID && gyroADC) { + if (!that.isFieldDisabled().GYRO && !that.isFieldDisabled().PID) { for (var axis = 0; axis < 3; axis++) { let gyroADCdegrees = (gyroADC[axis] !== undefined ? that.gyroRawToDegreesPerSecond(srcFrame[gyroADC[axis]]) : 0); destFrame[fieldIndex++] = destFrame[fieldIndexRcCommands + axis] - gyroADCdegrees; } } - // Duplicate the motor field to show the motor legacy values - if (motor) { - for (let motorNumber = 0; motorNumber < numMotors; motorNumber++) { - destFrame[fieldIndex++] = srcFrame[motor[motorNumber]]; - } - } - // Remove empty fields at the end destFrame.splice(fieldIndex); @@ -1032,6 +1059,10 @@ function FlightLog(logData) { return true; }; + + this.hasGpsData = function() { + return this.getStats()?.frame?.G ? true : false;; + }; } FlightLog.prototype.accRawToGs = function(value) { @@ -1075,7 +1106,7 @@ FlightLog.prototype.rcCommandRawToDegreesPerSecond = function(value, axis, curre } var rcRate = sysConfig["rc_rates"][axis] / 100.0; - if (rcRate > 2.0) { + if (rcRate > 2.0) { rcRate += RC_RATE_INCREMENTAL * (rcRate - 2.0); } @@ -1170,13 +1201,6 @@ FlightLog.prototype.rcCommandRawToThrottle = function(value) { return Math.min(Math.max(((value - this.getSysConfig().minthrottle) / (this.getSysConfig().maxthrottle - this.getSysConfig().minthrottle)) * 100.0, 0.0),100.0); }; -FlightLog.prototype.rcMotorRawToPctEffective = function(value) { - // Motor displayed as percentage - const minValue = (this.isDigitalProtocol() && this.getSysConfig().dynamic_idle_min_rpm > 0) ? this.getSysConfig().digitalIdleOffset : this.getSysConfig().motorOutput[0]; - return Math.min((value - minValue) / (this.getSysConfig().motorOutput[1] - minValue) * 100.0, 100.0); - -}; - FlightLog.prototype.rcMotorRawToPctPhysical = function(value) { // Motor displayed as percentage @@ -1333,5 +1357,7 @@ FlightLog.prototype.isFieldDisabled = function() { DEBUG : (disabledFields & (1 << 9))!==0, MOTORS : (disabledFields & (1 << 10))!==0, GPS : (disabledFields & (1 << 11))!==0, + RPM : (disabledFields & (1 << 12))!==0, + GYROUNFILT : (disabledFields & (1 << 13))!==0, }; }; diff --git a/src/flightlog_fielddefs.js b/src/flightlog_fielddefs.js new file mode 100644 index 00000000..c60b4400 --- /dev/null +++ b/src/flightlog_fielddefs.js @@ -0,0 +1,553 @@ +function makeReadOnly(x) { + // Make read-only if browser supports it: + if (Object.freeze) { + return Object.freeze(x); + } + + // Otherwise a no-op + return x; +} + +// Some constants used at different places +export const MAX_MOTOR_NUMBER = 8; +export const DSHOT_MIN_VALUE = 48; +const DSHOT_MAX_VALUE = 2047; +export const DSHOT_RANGE = DSHOT_MAX_VALUE - DSHOT_MIN_VALUE; + +// Fields definitions for lists +export const FlightLogEvent = makeReadOnly({ + SYNC_BEEP: 0, + + AUTOTUNE_CYCLE_START: 10, + AUTOTUNE_CYCLE_RESULT: 11, + AUTOTUNE_TARGETS: 12, + INFLIGHT_ADJUSTMENT: 13, + LOGGING_RESUME: 14, + DISARM: 15, + + GTUNE_CYCLE_RESULT: 20, + FLIGHT_MODE: 30, + TWITCH_TEST: 40, // Feature for latency testing + + CUSTOM : 250, // Virtual Event Code - Never part of Log File. + CUSTOM_BLANK : 251, // Virtual Event Code - Never part of Log File. - No line shown + LOG_END: 255, +}); + +// Add a general axis index. +export const AXIS = makeReadOnly({ + ROLL: 0, + PITCH: 1, + YAW: 2, +}); + +export let FLIGHT_LOG_FLIGHT_MODE_NAME = []; + +export const FLIGHT_LOG_FLIGHT_MODE_NAME_PRE_3_3 = makeReadOnly([ + 'ARM', + 'ANGLE', + 'HORIZON', + 'BARO', + 'ANTIGRAVITY', + 'MAG', + 'HEADFREE', + 'HEADADJ', + 'CAMSTAB', + 'CAMTRIG', + 'GPSHOME', + 'GPSHOLD', + 'PASSTHRU', + 'BEEPER', + 'LEDMAX', + 'LEDLOW', + 'LLIGHTS', + 'CALIB', + 'GOV', + 'OSD', + 'TELEMETRY', + 'GTUNE', + 'SONAR', + 'SERVO1', + 'SERVO2', + 'SERVO3', + 'BLACKBOX', + 'FAILSAFE', + 'AIRMODE', + '3DDISABLE', + 'FPVANGLEMIX', + 'BLACKBOXERASE', + 'CAMERA1', + 'CAMERA2', + 'CAMERA3', + 'FLIPOVERAFTERCRASH', + 'PREARM', +]); + +export const FLIGHT_LOG_FLIGHT_MODE_NAME_POST_3_3 = makeReadOnly([ + 'ARM', + 'ANGLE', + 'HORIZON', + 'MAG', + 'BARO', + 'GPSHOME', + 'GPSHOLD', + 'HEADFREE', + 'PASSTHRU', + 'RANGEFINDER', + 'FAILSAFE', + 'GPSRESCUE', + 'ANTIGRAVITY', + 'HEADADJ', + 'CAMSTAB', + 'CAMTRIG', + 'BEEPER', + 'LEDMAX', + 'LEDLOW', + 'LLIGHTS', + 'CALIB', + 'GOV', + 'OSD', + 'TELEMETRY', + 'GTUNE', + 'SERVO1', + 'SERVO2', + 'SERVO3', + 'BLACKBOX', + 'AIRMODE', + '3D', + 'FPVANGLEMIX', + 'BLACKBOXERASE', + 'CAMERA1', + 'CAMERA2', + 'CAMERA3', + 'FLIPOVERAFTERCRASH', + 'PREARM', + 'BEEPGPSCOUNT', + 'VTXPITMODE', + 'USER1', + 'USER2', + 'USER3', + 'USER4', + 'PIDAUDIO', + 'ACROTRAINER', + 'VTXCONTROLDISABLE', + 'LAUNCHCONTROL', +]); + +export const FLIGHT_LOG_FEATURES = makeReadOnly([ + 'RX_PPM', + 'VBAT', + 'INFLIGHT_ACC_CAL', + 'RX_SERIAL', + 'MOTOR_STOP', + 'SERVO_TILT', + 'SOFTSERIAL', + 'GPS', + 'FAILSAFE', + 'SONAR', + 'TELEMETRY', + 'CURRENT_METER', + '3D', + 'RX_PARALLEL_PWM', + 'RX_MSP', + 'RSSI_ADC', + 'LED_STRIP', + 'DISPLAY', + 'ONESHOT125', + 'BLACKBOX', + 'CHANNEL_FORWARDING', + 'TRANSPONDER', + 'AIRMODE', + 'SUPEREXPO_RATES', +]); + +export const OFF_ON = makeReadOnly([ + "OFF", + "ON", +]); + +export const FAST_PROTOCOL = makeReadOnly([ + "PWM", + "ONESHOT125", + "ONESHOT42", + "MULTISHOT", + "BRUSHED", + "DSHOT150", + "DSHOT300", + "DSHOT600", + "DSHOT1200", //deprecated + "PROSHOT1000", +]); + +export const MOTOR_SYNC = makeReadOnly([ + "SYNCED", + "UNSYNCED", +]); + +export const SERIALRX_PROVIDER = makeReadOnly([ + "SPEK1024", + "SPEK2048", + "SBUS", + "SUMD", + "SUMH", + "XB-B", + "XB-B-RJ01", + "IBUS", + "JETIEXBUS", + "CRSF", + "SRXL", + "CUSTOM", + "FPORT", + "SRXL2", + "GHST", +]); + +export const ANTI_GRAVITY_MODE = makeReadOnly([ + "SMOOTH", + "STEP", +]); + +export const RC_SMOOTHING_TYPE = makeReadOnly([ + "INTERPOLATION", + "FILTER", +]); + +export const RC_SMOOTHING_MODE = makeReadOnly([ + "OFF", + "ON", +]); + +export const RC_SMOOTHING_DEBUG_AXIS = makeReadOnly([ + "ROLL", + "PITCH", + "YAW", + "THROTTLE", +]); + +export const FILTER_TYPE = makeReadOnly([ + "PT1", + "BIQUAD", + "PT2", + "PT3", +]); + +export let DEBUG_MODE = []; + +export const DEBUG_MODE_COMPLETE = makeReadOnly([ + "NONE", + "CYCLETIME", + "BATTERY", + "GYRO", // deprecated (GYRO_FILTERED) + "ACCELEROMETER", + "MIXER", // deprecated + "AIRMODE", // deprecated + "PIDLOOP", + "NOTCH", // deprecated (GYRO_SCALED) + "RC_INTERPOLATION", + "VELOCITY", // deprecated + "DTERM_FILTER", // deprecated + "ANGLERATE", + "ESC_SENSOR", + "SCHEDULER", + "STACK", + "ESC_SENSOR_RPM", + "ESC_SENSOR_TMP", + "ALTITUDE", + "FFT", + "FFT_TIME", + "FFT_FREQ", + "RX_FRSKY_SPI", + "GYRO_RAW", // deprecated + "DUAL_GYRO", // deprecated + "DUAL_GYRO_RAW", + "DUAL_GYRO_COMBINED", // deprecated + "DUAL_GYRO_DIFF", + "MAX7456_SIGNAL", + "MAX7456_SPICLOCK", + "SBUS", + "FPORT", + "RANGEFINDER", + "RANGEFINDER_QUALITY", + "LIDAR_TF", + "ADC_INTERNAL", + "RUNAWAY_TAKEOFF", + "SDIO", + "CURRENT_SENSOR", + "USB", + "SMARTAUDIO", + "RTH", + "ITERM_RELAX", + "ACRO_TRAINER", + "RC_SMOOTHING", + "RX_SIGNAL_LOSS", + "RC_SMOOTHING_RATE", + "ANTI_GRAVITY", + "DYN_LPF", + "RX_SPECTRUM_SPI", + "DSHOT_RPM_TELEMETRY", + "RPM_FILTER", + "D_MIN", + "AC_CORRECTION", + "AC_ERROR", + "DUAL_GYRO_SCALED", + "DSHOT_RPM_ERRORS", + "CRSF_LINK_STATISTICS_UPLINK", + "CRSF_LINK_STATISTICS_PWR", + "CRSF_LINK_STATISTICS_DOWN", + "BARO", + "GPS_RESCUE_THROTTLE_PID", + "DYN_IDLE", + "FF_LIMIT", // deprecated (FEEDFORWARD_LIMIT) + "FF_INTERPOLATED", // deprecated (FEEDFORWARD) + "BLACKBOX_OUTPUT", + "GYRO_SAMPLE", + "RX_TIMING", + "D_LPF", + "VTX_TRAMP", + "GHST", + "GHST_MSP", + "SCHEDULER_DETERMINISM", + "TIMING_ACCURACY", + "RX_EXPRESSLRS_SPI", + "RX_EXPRESSLRS_PHASELOCK", + "RX_STATE_TIME", + "GPS_RESCUE_VELOCITY", + "GPS_RESCUE_HEADING", + "GPS_RESCUE_TRACKING", + "GPS_CONNECTION", + "ATTITUDE", + "VTX_MSP", + "GPS_DOP", + "FAILSAFE", + "GYRO_CALIBRATION", + "ANGLE_MODE", + "ANGLE_TARGET", + "CURRENT_ANGLE", + "DSHOT_TELEMETRY_COUNTS", + "RPM_LIMIT", + "RC_STATS", + "MAG_CALIB", + "MAG_TASK_RATE", + "EZLANDING", +]); + +export const SUPER_EXPO_YAW = makeReadOnly([ + "OFF", + "ON", + "ALWAYS", +]); + +export const GYRO_LPF = makeReadOnly([ + "OFF", + "188HZ", + "98HZ", + "42HZ", + "20HZ", + "10HZ", + "5HZ", + "EXPERIMENTAL", +]); + +export const GYRO_HARDWARE_LPF = makeReadOnly([ + "NORMAL", + "EXPERIMENTAL", + "1KHZ_SAMPLING", +]); + +export const GYRO_32KHZ_HARDWARE_LPF = makeReadOnly([ + "NORMAL", + "EXPERIMENTAL", +]); + +export const ACC_HARDWARE = makeReadOnly([ + "AUTO", + "NONE", + "ADXL345", + "MPU6050", + "MMA8452", + "BMA280", + "LSM303DLHC", + "MPU6000", + "MPU6500", + "MPU9250", + "ICM20601", + "ICM20602", + "ICM20608G", + "ICM20649", + "ICM20689", + "ICM42605", + "ICM42688P", + "BMI160", + "BMI270", + "LSM6DSO", + "LSM6DSV16X", + "VIRTUAL", +]); + +export const BARO_HARDWARE = makeReadOnly([ + "AUTO", + "NONE", + "BMP085", + "MS5611", + "BMP280", + "LPS", + "QMP6988", + "BMP388", + "DPS310", + "2SMPB_02B", +]); + +export const MAG_HARDWARE = makeReadOnly([ + "AUTO", + "NONE", + "HMC5883", + "AK8975", + "AK8963", + "QMC5883", + "LIS3MDL", + "MAG_MPU925X_AK8963", +]); + +export const FLIGHT_LOG_FLIGHT_STATE_NAME = makeReadOnly([ + "GPS_FIX_HOME", + "GPS_FIX", + "CALIBRATE_MAG", + "SMALL_ANGLE", + "FIXED_WING", +]); + +export const FLIGHT_LOG_FAILSAFE_PHASE_NAME = makeReadOnly([ + "IDLE", + "RX_LOSS_DETECTED", + "LANDING", + "LANDED", +]); + +export const FFT_CALC_STEPS = makeReadOnly([ + "ARM_CFFT_F32", + "BITREVERSAL", + "STAGE_RFFT_F32", + "ARM_CMPLX_MAG_F32", + "CALC_FREQUENCIES", + "UPDATE_FILTERS", + "HANNING", +]); + +export const ITERM_RELAX = makeReadOnly([ + "OFF", + "RP", + "RPY", + "RP_INC", + "RPY_INC", +]); + +export const ITERM_RELAX_TYPE = makeReadOnly([ + "GYRO", + "SETPOINT", +]); + +export const FLIGHT_LOG_DISARM_REASON = makeReadOnly([ + "ARMING_DISABLED", + "FAILSAFE", + "THROTTLE_TIMEOUT", + "STICKS", + "SWITCH", + "CRASH_PROTECTION", + "RUNAWAY_TAKEOFF", + "GPS_RESCUE", + "SERIAL_IO", +]); + +export const RATES_TYPE = makeReadOnly([ + "BETAFLIGHT", + "RACEFLIGHT", + "KISS", + "ACTUAL", + "QUICK", +]); + +export const GYRO_TO_USE = makeReadOnly([ + "FIRST", + "SECOND", + "BOTH", +]); + +export const FF_AVERAGING = makeReadOnly([ + "OFF", + "2_POINT", + "3_POINT", + "4_POINT", +]); + +export const SIMPLIFIED_PIDS_MODE = makeReadOnly([ + "OFF", + "ON - RP", + "ON - RPY", +]); + +export const THROTTLE_LIMIT_TYPE = makeReadOnly([ + "OFF", + "SCALE", + "CLIP", +]); + +export function adjustFieldDefsList(firmwareType, firmwareVersion) { + if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.gte(firmwareVersion, '3.3.0')) { + + // Debug names + DEBUG_MODE = DEBUG_MODE_COMPLETE.slice(0); + DEBUG_MODE.splice(DEBUG_MODE.indexOf('MIXER'), 1); + DEBUG_MODE.splice(DEBUG_MODE.indexOf('AIRMODE'), 1); + DEBUG_MODE.splice(DEBUG_MODE.indexOf('VELOCITY'), 1); + DEBUG_MODE.splice(DEBUG_MODE.indexOf('DTERM_FILTER'), 1); + + if (semver.gte(firmwareVersion, '3.4.0')) { + DEBUG_MODE.splice(DEBUG_MODE.indexOf('GYRO'), 1, 'GYRO_FILTERED'); + DEBUG_MODE.splice(DEBUG_MODE.indexOf('NOTCH'), 1, 'GYRO_SCALED'); + } + if (semver.gte(firmwareVersion, '4.0.0')) { + DEBUG_MODE.splice(DEBUG_MODE.indexOf('GYRO_RAW'), 0, 'RX_SFHSS_SPI'); + } + if (semver.gte(firmwareVersion, '4.1.0')) { + DEBUG_MODE.splice(DEBUG_MODE.indexOf('DUAL_GYRO'), 1); + DEBUG_MODE.splice(DEBUG_MODE.indexOf('DUAL_GYRO_COMBINED'), 1); + } + if (semver.gte(firmwareVersion, '4.3.0')) { + DEBUG_MODE.splice(DEBUG_MODE.indexOf('FF_INTERPOLATED'), 1, 'FEEDFORWARD'); + DEBUG_MODE.splice(DEBUG_MODE.indexOf('FF_LIMIT'), 1, 'FEEDFORWARD_LIMIT'); + } + + DEBUG_MODE = makeReadOnly(DEBUG_MODE); + + // Flight mode names + FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_POST_3_3.slice(0); + if (semver.lt(firmwareVersion, '3.4.0')) { + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSRESCUE'), 1); + } + if (semver.gte(firmwareVersion, '3.5.0')) { + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('RANGEFINDER'), 1); + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('CAMTRIG'), 1); + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('LEDMAX'), 1); + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('LLIGHTS'), 1); + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GOV'), 1); + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GTUNE'), 1); + } + if (semver.gte(firmwareVersion, '4.0.0')) { + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('BARO'), 1); + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSHOME'), 1); + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('GPSHOLD'), 1); + } + FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME); + + } else { + DEBUG_MODE = DEBUG_MODE_COMPLETE; + + FLIGHT_LOG_FLIGHT_MODE_NAME = FLIGHT_LOG_FLIGHT_MODE_NAME_PRE_3_3.slice(0); + + if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.lte(firmwareVersion, '3.1.6')) { + FLIGHT_LOG_FLIGHT_MODE_NAME.splice(FLIGHT_LOG_FLIGHT_MODE_NAME.indexOf('ANTIGRAVITY'), 1); + } + + FLIGHT_LOG_FLIGHT_MODE_NAME = makeReadOnly(FLIGHT_LOG_FLIGHT_MODE_NAME); + } +} diff --git a/src/flightlog_fields_presenter.js b/src/flightlog_fields_presenter.js new file mode 100644 index 00000000..eb3ea01c --- /dev/null +++ b/src/flightlog_fields_presenter.js @@ -0,0 +1,1930 @@ +import { + FLIGHT_LOG_FLIGHT_MODE_NAME, + FLIGHT_LOG_FEATURES, + DEBUG_MODE, + FLIGHT_LOG_FLIGHT_STATE_NAME, + FLIGHT_LOG_FAILSAFE_PHASE_NAME, + FFT_CALC_STEPS, +} from "./flightlog_fielddefs"; +import { formatTime } from "./tools"; + +export function FlightLogFieldPresenter() { + // this is intentional +} + +const FRIENDLY_FIELD_NAMES = { + + 'axisP[all]': 'PID P', + 'axisP[0]': 'PID P [roll]', + 'axisP[1]': 'PID P [pitch]', + 'axisP[2]': 'PID P [yaw]', + + 'axisI[all]': 'PID I', + 'axisI[0]': 'PID I [roll]', + 'axisI[1]': 'PID I [pitch]', + 'axisI[2]': 'PID I [yaw]', + + 'axisD[all]': 'PID D', + 'axisD[0]': 'PID D [roll]', + 'axisD[1]': 'PID D [pitch]', + 'axisD[2]': 'PID D [yaw]', + + 'axisF[all]': 'PID Feedforward', + 'axisF[0]': 'PID Feedforward [roll]', + 'axisF[1]': 'PID Feedforward [pitch]', + 'axisF[2]': 'PID Feedforward [yaw]', + + //Virtual field + 'axisSum[all]': 'PID Sum', + 'axisSum[0]' : 'PID Sum [roll]', + 'axisSum[1]' : 'PID Sum [pitch]', + 'axisSum[2]' : 'PID Sum [yaw]', + + //Virtual field + 'axisError[all]': 'PID Error', + 'axisError[0]' : 'PID Error [roll]', + 'axisError[1]' : 'PID Error [pitch]', + 'axisError[2]' : 'PID Error [yaw]', + + //Virtual field + 'rcCommands[all]': 'Setpoints', + 'rcCommands[0]' : 'Setpoint [roll]', + 'rcCommands[1]' : 'Setpoint [pitch]', + 'rcCommands[2]' : 'Setpoint [yaw]', + 'rcCommands[3]' : 'Setpoint [throttle]', + + 'rcCommand[all]': 'RC Commands', + 'rcCommand[0]': 'RC Command [roll]', + 'rcCommand[1]': 'RC Command [pitch]', + 'rcCommand[2]': 'RC Command [yaw]', + 'rcCommand[3]': 'RC Command [throttle]', + + 'gyroADC[all]': 'Gyros', + 'gyroADC[0]': 'Gyro [roll]', + 'gyroADC[1]': 'Gyro [pitch]', + 'gyroADC[2]': 'Gyro [yaw]', + + 'gyroUnfilt[all]': 'Unfiltered Gyros', + 'gyroUnfilt[0]': 'Unfiltered Gyro [roll]', + 'gyroUnfilt[1]': 'Unfiltered Gyro [pitch]', + 'gyroUnfilt[2]': 'Unfiltered Gyro [yaw]', + + //End-users prefer 1-based indexing + 'motor[all]': 'Motors', + 'motor[0]': 'Motor [1]', + 'motor[1]': 'Motor [2]', + 'motor[2]': 'Motor [3]', + 'motor[3]': 'Motor [4]', + 'motor[4]': 'Motor [5]', + 'motor[5]': 'Motor [6]', + 'motor[6]': 'Motor [7]', + 'motor[7]': 'Motor [8]', + + 'eRPM[all]': 'RPM', + 'eRPM[0]': 'RPM [1]', + 'eRPM[1]': 'RPM [2]', + 'eRPM[2]': 'RPM [3]', + 'eRPM[3]': 'RPM [4]', + 'eRPM[4]': 'RPM [5]', + 'eRPM[5]': 'RPM [6]', + 'eRPM[6]': 'RPM [7]', + 'eRPM[7]': 'RPM [8]', + + 'servo[all]': 'Servos', + 'servo[5]': 'Servo Tail', + + 'vbatLatest': 'Battery volt.', + 'amperageLatest': 'Amperage', + 'baroAlt': 'Barometer', + + 'heading[all]': 'Heading', + 'heading[0]': 'Heading [roll]', + 'heading[1]': 'Heading [pitch]', + 'heading[2]': 'Heading [yaw]', + + 'accSmooth[all]': 'Accel.', + 'accSmooth[0]': 'Accel. [X]', + 'accSmooth[1]': 'Accel. [Y]', + 'accSmooth[2]': 'Accel. [Z]', + + 'magADC[all]': 'Compass', + 'magADC[0]': 'Compass [X]', + 'magADC[1]': 'Compass [Y]', + 'magADC[2]': 'Compass [Z]', + + 'flightModeFlags': 'Flight Mode Flags', + 'stateFlags': 'State Flags', + 'failsafePhase': 'Failsafe Phase', + 'rxSignalReceived': 'RX Signal Received', + 'rxFlightChannelsValid': 'RX Flight Ch. Valid', + 'rssi': 'RSSI', + + 'GPS_numSat': "GPS Sat Count", + 'GPS_coord[0]': "GPS Latitude", + 'GPS_coord[1]': "GPS Longitude", + 'GPS_altitude': "GPS Altitude ASL", + 'GPS_speed': "GPS Speed", + 'GPS_ground_course': "GPS Heading", +}; + +const DEBUG_FRIENDLY_FIELD_NAMES_INITIAL = { + 'NONE' : { + 'debug[all]':'Debug [all]', + 'debug[0]':'Debug [0]', + 'debug[1]':'Debug [1]', + 'debug[2]':'Debug [2]', + 'debug[3]':'Debug [3]', + 'debug[4]':'Debug [4]', + 'debug[5]':'Debug [5]', + 'debug[6]':'Debug [6]', + 'debug[7]':'Debug [7]', + }, + 'CYCLETIME' : { + 'debug[all]':'Debug Cycle Time', + 'debug[0]':'Cycle Time', + 'debug[1]':'CPU Load', + 'debug[2]':'Motor Update', + 'debug[3]':'Motor Deviation', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'BATTERY' : { + 'debug[all]':'Debug Battery', + 'debug[0]':'Battery Volt ADC', + 'debug[1]':'Battery Volt', + 'debug[2]':'Not Used', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GYRO' : { + 'debug[all]':'Debug Gyro', + 'debug[0]':'Gyro Raw [X]', + 'debug[1]':'Gyro Raw [Y]', + 'debug[2]':'Gyro Raw [Z]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GYRO_FILTERED' : { + 'debug[all]':'Debug Gyro Filtered', + 'debug[0]':'Gyro Filtered [X]', + 'debug[1]':'Gyro Filtered [Y]', + 'debug[2]':'Gyro Filtered [Z]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ACCELEROMETER' : { + 'debug[all]':'Debug Accel.', + 'debug[0]':'Accel. Raw [X]', + 'debug[1]':'Accel. Raw [Y]', + 'debug[2]':'Accel. Raw [Z]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'MIXER' : { + 'debug[all]':'Debug Mixer', + 'debug[0]':'Roll-Pitch-Yaw Mix [0]', + 'debug[1]':'Roll-Pitch-Yaw Mix [1]', + 'debug[2]':'Roll-Pitch-Yaw Mix [2]', + 'debug[3]':'Roll-Pitch-Yaw Mix [3]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'PIDLOOP' : { + 'debug[all]':'Debug PID', + 'debug[0]':'Wait Time', + 'debug[1]':'Sub Update Time', + 'debug[2]':'PID Update Time', + 'debug[3]':'Motor Update Time', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'NOTCH' : { + 'debug[all]':'Debug Notch', + 'debug[0]':'Gyro Pre-Notch [roll]', + 'debug[1]':'Gyro Pre-Notch [pitch]', + 'debug[2]':'Gyro Pre-Notch [yaw]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GYRO_SCALED' : { + 'debug[all]':'Debug Gyro Scaled', + 'debug[0]':'Gyro Scaled [roll]', + 'debug[1]':'Gyro Scaled [pitch]', + 'debug[2]':'Gyro Scaled [yaw]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RC_INTERPOLATION' : { + 'debug[all]':'Debug RC Interpolation', + 'debug[0]':'Raw RC Command [roll]', + 'debug[1]':'Current RX Refresh Rate', + 'debug[2]':'Interpolation Step Count', + 'debug[3]':'RC Setpoint [roll]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DTERM_FILTER' : { + 'debug[all]':'Debug Filter', + 'debug[0]':'DTerm Filter [roll]', + 'debug[1]':'DTerm Filter [pitch]', + 'debug[2]':'Not Used', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ANGLERATE' : { + 'debug[all]':'Debug Angle Rate', + 'debug[0]':'Angle Rate[roll]', + 'debug[1]':'Angle Rate[pitch]', + 'debug[2]':'Angle Rate[yaw]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ESC_SENSOR' : { + 'debug[all]':'ESC Sensor', + 'debug[0]':'Motor Index', + 'debug[1]':'Timeouts', + 'debug[2]':'CNC errors', + 'debug[3]':'Data age', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'SCHEDULER' : { + 'debug[all]':'Scheduler', + 'debug[0]':'Not Used', + 'debug[1]':'Not Used', + 'debug[2]':'Schedule Time', + 'debug[3]':'Function Exec Time', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'STACK' : { + 'debug[all]':'Stack', + 'debug[0]':'Stack High Mem', + 'debug[1]':'Stack Low Mem', + 'debug[2]':'Stack Current', + 'debug[3]':'Stack p', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ESC_SENSOR_RPM' : { + 'debug[all]':'ESC Sensor RPM', + 'debug[0]':'Motor 1', + 'debug[1]':'Motor 2', + 'debug[2]':'Motor 3', + 'debug[3]':'Motor 4', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ESC_SENSOR_TMP' : { + 'debug[all]':'ESC Sensor Temp', + 'debug[0]':'Motor 1', + 'debug[1]':'Motor 2', + 'debug[2]':'Motor 3', + 'debug[3]':'Motor 4', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ALTITUDE' : { + 'debug[all]':'Altitude', + 'debug[0]':'GPS Trust * 100', + 'debug[1]':'Baro Altitude', + 'debug[2]':'GPS Altitude', + 'debug[3]':'Vario', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'FFT' : { + 'debug[all]':'Debug FFT', + 'debug[0]':'Gyro Scaled [dbg-axis]', + 'debug[1]':'Gyro Pre-Dyn [dbg-axis]', + 'debug[2]':'Gyro Downsampled [roll]', + 'debug[3]':'FFT Center Index [roll]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'FFT_TIME' : { + 'debug[all]':'Debug FFT TIME', + 'debug[0]':'Active calc step', + 'debug[1]':'Step duration', + 'debug[2]':'Additional steps', + 'debug[3]':'Not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'FFT_FREQ' : { + 'debug[all]':'Debug FFT FREQ', + 'debug[0]':'Center Freq [roll]', + 'debug[1]':'Center Freq [pitch]', + 'debug[2]':'Gyro Pre-Dyn [dbg-axis]', + 'debug[3]':'Gyro Scaled [dbg-axis]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RX_FRSKY_SPI' : { + 'debug[all]':'FrSky SPI Rx', + 'debug[0]':'Looptime', + 'debug[1]':'Packet', + 'debug[2]':'Missing Packets', + 'debug[3]':'State', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RX_SFHSS_SPI' : { + 'debug[all]':'SFHSS SPI Rx', + 'debug[0]':'State', + 'debug[1]':'Missing Frame', + 'debug[2]':'Offset Max', + 'debug[3]':'Offset Min', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GYRO_RAW' : { + 'debug[all]':'Debug Gyro Raw', + 'debug[0]':'Gyro Raw [X]', + 'debug[1]':'Gyro Raw [Y]', + 'debug[2]':'Gyro Raw [Z]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DUAL_GYRO' : { + 'debug[all]':'Debug Dual Gyro', + 'debug[0]':'Gyro 1 Filtered [roll]', + 'debug[1]':'Gyro 1 Filtered [pitch]', + 'debug[2]':'Gyro 2 Filtered [roll]', + 'debug[3]':'Gyro 2 Filtered [pitch]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DUAL_GYRO_RAW': { + 'debug[all]':'Debug Dual Gyro Raw', + 'debug[0]':'Gyro 1 Raw [roll]', + 'debug[1]':'Gyro 1 Raw [pitch]', + 'debug[2]':'Gyro 2 Raw [roll]', + 'debug[3]':'Gyro 2 Raw [pitch]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DUAL_GYRO_COMBINED': { + 'debug[all]':'Debug Dual Combined', + 'debug[0]':'Not Used', + 'debug[1]':'Gyro Filtered [roll]', + 'debug[2]':'Gyro Filtered [pitch]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DUAL_GYRO_DIFF': { + 'debug[all]':'Debug Dual Gyro Diff', + 'debug[0]':'Gyro Diff [roll]', + 'debug[1]':'Gyro Diff [pitch]', + 'debug[2]':'Gyro Diff [yaw]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'MAX7456_SIGNAL' : { + 'debug[all]':'Max7456 Signal', + 'debug[0]':'Mode Reg', + 'debug[1]':'Sense', + 'debug[2]':'ReInit', + 'debug[3]':'Rows', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'MAX7456_SPICLOCK' : { + 'debug[all]':'Max7456 SPI Clock', + 'debug[0]':'Overclock', + 'debug[1]':'DevType', + 'debug[2]':'Divisor', + 'debug[3]':'not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'SBUS' : { + 'debug[all]':'SBus Rx', + 'debug[0]':'Frame Flags', + 'debug[1]':'State Flags', + 'debug[2]':'Frame Time', + 'debug[3]':'not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'FPORT' : { + 'debug[all]':'FPort Rx', + 'debug[0]':'Frame Interval', + 'debug[1]':'Frame Errors', + 'debug[2]':'Last Error', + 'debug[3]':'Telemetry Interval', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RANGEFINDER' : { + 'debug[all]':'Rangefinder', + 'debug[0]':'not used', + 'debug[1]':'Raw Altitude', + 'debug[2]':'Calc Altituded', + 'debug[3]':'SNR', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RANGEFINDER_QUALITY' : { + 'debug[all]':'Rangefinder Quality', + 'debug[0]':'Raw Altitude', + 'debug[1]':'SNR Threshold Reached', + 'debug[2]':'Dyn Distance Threshold', + 'debug[3]':'Is Surface Altitude Valid', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'LIDAR_TF' : { + 'debug[all]':'Lidar TF', + 'debug[0]':'Distance', + 'debug[1]':'Strength', + 'debug[2]':'TF Frame (4)', + 'debug[3]':'TF Frame (5)', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ADC_INTERNAL' : { + 'debug[all]':'ADC Internal', + 'debug[0]':'Core Temp', + 'debug[1]':'VRef Internal Sample', + 'debug[2]':'Temp Sensor Sample', + 'debug[3]':'Vref mV', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RUNAWAY_TAKEOFF' : { + 'debug[all]':'Runaway Takeoff', + 'debug[0]':'Enabled', + 'debug[1]':'Activating Delay', + 'debug[2]':'Deactivating Delay', + 'debug[3]':'Deactivating Time', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'CURRENT_SENSOR' : { + 'debug[all]':'Current Sensor', + 'debug[0]':'milliVolts', + 'debug[1]':'centiAmps', + 'debug[2]':'Amps Latest', + 'debug[3]':'mAh Drawn', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'USB' : { + 'debug[all]':'USB', + 'debug[0]':'Cable In', + 'debug[1]':'VCP Connected', + 'debug[2]':'not used', + 'debug[3]':'not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'SMART AUDIO' : { + 'debug[all]':'Smart Audio VTx', + 'debug[0]':'Device + Version', + 'debug[1]':'Channel', + 'debug[2]':'Frequency', + 'debug[3]':'Power', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RTH' : { + 'debug[all]':'RTH', + 'debug[0]':'Rescue Throttle', + 'debug[1]':'Rescue Angle', + 'debug[2]':'Altitude Adjustment', + 'debug[3]':'Rescue State', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ITERM_RELAX' : { + 'debug[all]':'I-term Relax', + 'debug[0]':'Setpoint HPF [roll]', + 'debug[1]':'I Relax Factor [roll]', + 'debug[2]':'Relaxed I Error [roll]', + 'debug[3]':'Axis Error [roll]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ACRO_TRAINER' : { + 'debug[all]':'Acro Trainer (a_t_axis)', + 'debug[0]':'Current Angle * 10 [deg]', + 'debug[1]':'Axis State', + 'debug[2]':'Correction amount', + 'debug[3]':'Projected Angle * 10 [deg]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RC_SMOOTHING' : { + 'debug[all]':'Debug RC Smoothing', + 'debug[0]':'Raw RC Command', + 'debug[1]':'Raw RC Derivative', + 'debug[2]':'Smoothed RC Derivative', + 'debug[3]':'RX Refresh Rate', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RX_SIGNAL_LOSS' : { + 'debug[all]':'Rx Signal Loss', + 'debug[0]':'Signal Received', + 'debug[1]':'Failsafe', + 'debug[2]':'Not used', + 'debug[3]':'Throttle', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RC_SMOOTHING_RATE' : { + 'debug[all]':'Debug RC Smoothing Rate', + 'debug[0]':'Current RX Refresh Rate', + 'debug[1]':'Training Step Count', + 'debug[2]':'Average RX Refresh Rate', + 'debug[3]':'Sampling State', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'ANTI_GRAVITY' : { + 'debug[all]':'I-term Relax', + 'debug[0]':'Base I gain * 1000', + 'debug[1]':'Final I gain * 1000', + 'debug[2]':'P gain [roll] * 1000', + 'debug[3]':'P gain [pitch] * 1000', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DYN_LPF' : { + 'debug[all]':'Debug Dyn LPF', + 'debug[0]':'Gyro Scaled [dbg-axis]', + 'debug[1]':'Notch Center [roll]', + 'debug[2]':'Lowpass Cutoff', + 'debug[3]':'Gyro Pre-Dyn [dbg-axis]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DSHOT_RPM_TELEMETRY' : { + 'debug[all]':'DShot Telemetry RPM', + 'debug[0]':'Motor 1 - DShot', + 'debug[1]':'Motor 2 - DShot', + 'debug[2]':'Motor 3 - DShot', + 'debug[3]':'Motor 4 - DShot', + 'debug[4]':'Motor 5 - DShot', + 'debug[5]':'Motor 6 - DShot', + 'debug[6]':'Motor 7 - DShot', + 'debug[7]':'Motor 8 - DShot', + }, + 'RPM_FILTER' : { + 'debug[all]':'RPM Filter', + 'debug[0]':'Motor 1 - rpmFilter', + 'debug[1]':'Motor 2 - rpmFilter', + 'debug[2]':'Motor 3 - rpmFilter', + 'debug[3]':'Motor 4 - rpmFilter', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'D_MIN' : { + 'debug[all]':'D_MIN', + 'debug[0]':'Gyro Factor [roll]', + 'debug[1]':'Setpoint Factor [roll]', + 'debug[2]':'Actual D [roll]', + 'debug[3]':'Actual D [pitch]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'AC_CORRECTION' : { + 'debug[all]':'AC Correction', + 'debug[0]':'AC Correction [roll]', + 'debug[1]':'AC Correction [pitch]', + 'debug[2]':'AC Correction [yaw]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'AC_ERROR' : { + 'debug[all]':'AC Error', + 'debug[0]':'AC Error [roll]', + 'debug[1]':'AC Error [pitch]', + 'debug[2]':'AC Error [yaw]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DUAL_GYRO_SCALED' : { + 'debug[all]':'Dual Gyro Scaled', + 'debug[0]':'Gyro 1 [roll]', + 'debug[1]':'Gyro 1 [pitch]', + 'debug[2]':'Gyro 2 [roll]', + 'debug[3]':'Gyro 2 [pitch]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DSHOT_RPM_ERRORS' : { + 'debug[all]':'DSHOT RPM Error', + 'debug[0]':'DSHOT RPM Error [1]', + 'debug[1]':'DSHOT RPM Error [2]', + 'debug[2]':'DSHOT RPM Error [3]', + 'debug[3]':'DSHOT RPM Error [4]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'CRSF_LINK_STATISTICS_UPLINK' : { + 'debug[all]':'CRSF Stats Uplink', + 'debug[0]':'Uplink RSSI 1', + 'debug[1]':'Uplink RSSI 2', + 'debug[2]':'Uplink Link Quality', + 'debug[3]':'RF Mode', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'CRSF_LINK_STATISTICS_PWR' : { + 'debug[all]':'CRSF Stats Power', + 'debug[0]':'Antenna', + 'debug[1]':'SNR', + 'debug[2]':'TX Power', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'CRSF_LINK_STATISTICS_DOWN' : { + 'debug[all]':'CRSF Stats Downlink', + 'debug[0]':'Downlink RSSI', + 'debug[1]':'Downlink Link Quality', + 'debug[2]':'Downlink SNR', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'BARO' : { + 'debug[all]':'Debug Barometer', + 'debug[0]':'Baro State', + 'debug[1]':'Baro Temperature', + 'debug[2]':'Baro Pressure', + 'debug[3]':'Baro Pressure Sum', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GPS_RESCUE_THROTTLE_PID' : { + 'debug[all]':'GPS Rescue Throttle PID', + 'debug[0]':'Throttle P', + 'debug[1]':'Throttle I', + 'debug[2]':'Throttle D', + 'debug[3]':'Z Velocity', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'DYN_IDLE' : { + 'debug[all]':'Dyn Idle', + 'debug[0]':'Motor Range Min Inc', + 'debug[1]':'Target RPS Change Rate', + 'debug[2]':'Error', + 'debug[3]':'Min RPM', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'FF_LIMIT' : { + 'debug[all]':'FF Limit', + 'debug[0]':'FF input [roll]', + 'debug[1]':'FF input [pitch]', + 'debug[2]':'FF limited [roll]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'FF_INTERPOLATED' : { + 'debug[all]':'FF Interpolated [roll]', + 'debug[0]':'Setpoint Delta Impl [roll]', + 'debug[1]':'Boost amount [roll]', + 'debug[2]':'Boost amount, clipped [roll]', + 'debug[3]':'Clip amount [roll]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'BLACKBOX_OUTPUT' : { + 'debug[all]':'Blackbox Output', + 'debug[0]':'Blackbox Rate', + 'debug[1]':'Blackbox Max Rate', + 'debug[2]':'Dropouts', + 'debug[3]':'Tx Bytes Free', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GYRO_SAMPLE' : { + 'debug[all]':'Gyro Sample', + 'debug[0]':'Before downsampling', + 'debug[1]':'After downsampling', + 'debug[2]':'After RPM', + 'debug[3]':'After all but Dyn Notch', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RX_TIMING' : { + 'debug[all]':'Receiver Timing (us)', + 'debug[0]':'Frame Delta', + 'debug[1]':'Frame Age', + 'debug[2]':'not used', + 'debug[3]':'not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'D_LPF' : { + 'debug[all]':'D-Term [D_LPF]', + 'debug[0]':'Unfiltered D [roll]', + 'debug[1]':'Unfiltered D [pitch]', + 'debug[2]':'Filtered, with DMax [roll]', + 'debug[3]':'Filtered, with DMax [pitch]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'VTX_TRAMP' : { + 'debug[all]':'Tramp VTx', + 'debug[0]':'Status', + 'debug[1]':'Reply Code', + 'debug[2]':'Pit Mode', + 'debug[3]':'Retry Count', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GHST' : { + 'debug[all]':'Ghost Rx', + 'debug[0]':'CRC Error Count', + 'debug[1]':'Unknown Frame Count', + 'debug[2]':'RSSI', + 'debug[3]':'Link Quality', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GHST_MSP' : { + 'debug[all]':'Ghost MSP', + 'debug[0]':'MSP Frame Count', + 'debug[1]':'MSP Frame Counter', + 'debug[2]':'Not used', + 'debug[3]':'Not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'SCHEDULER_DETERMINISM' : { + 'debug[all]':'Scheduler Determinism', + 'debug[0]':'Cycle Start time', + 'debug[1]':'ID of Late Task', + 'debug[2]':'Task Delay Time', + 'debug[3]':'Gyro Clock Skew', + 'debug[4]':'Minimum Gyro period in 100th of a us', + 'debug[5]':'Maximum Gyro period in 100th of a us', + 'debug[6]':'Span of Gyro period in 100th of a us', + 'debug[7]':'Gyro cycle deviation in 100th of a us', + }, + 'TIMING_ACCURACY' : { + 'debug[all]':'Timing Accuracy', + 'debug[0]':'CPU Busy', + 'debug[1]':'Late Tasks per second', + 'debug[2]':'Total delay in last second', + 'debug[3]':'Total Tasks per second', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RX_EXPRESSLRS_SPI' : { + 'debug[all]':'ExpressLRS SPI Rx', + 'debug[0]':'Lost Connection Count', + 'debug[1]':'RSSI', + 'debug[2]':'SNR', + 'debug[3]':'Uplink LQ', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RX_EXPRESSLRS_PHASELOCK' : { + 'debug[all]':'ExpressLRS SPI Phaselock', + 'debug[0]':'Phase offset', + 'debug[1]':'Filtered phase offset', + 'debug[2]':'Frequency Offset', + 'debug[3]':'Phase Shift', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'RX_STATE_TIME' : { + 'debug[all]':'Rx State Time', + 'debug[0]':'Time 0', + 'debug[1]':'Time 1', + 'debug[2]':'Time 2', + 'debug[3]':'Time 3', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GPS_RESCUE_VELOCITY' : { + 'debug[all]':'GPS Rescue Velocity', + 'debug[0]':'Velocity P', + 'debug[1]':'Velocity D', + 'debug[2]':'Velocity to Home', + 'debug[3]':'Target Velocity', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GPS_RESCUE_HEADING' : { + 'debug[all]':'GPS Rescue Heading', + 'debug[0]':'Ground Speed', + 'debug[1]':'GPS Heading', + 'debug[2]':'IMU Attitude', + 'debug[3]':'Angle to home', + 'debug[4]':'magYaw', + 'debug[5]':'Roll MixAtt', + 'debug[6]':'Roll Added', + 'debug[7]':'Rescue Yaw Rate', + }, + 'GPS_RESCUE_TRACKING' : { + 'debug[all]':'GPS Rescue Tracking', + 'debug[0]':'Velocity to home', + 'debug[1]':'Target velocity', + 'debug[2]':'Altitude', + 'debug[3]':'Target altitude', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GPS_CONNECTION' : { + 'debug[all]':'GPS Connection', + 'debug[0]':'Nav Model', + 'debug[1]':'GPS Nav interval', + 'debug[2]':'Task timer', + 'debug[3]':'Baud Rate / FC interval', + 'debug[4]':'State*100 +SubState', + 'debug[5]':'ExecuteTime', + 'debug[6]':'Ack State', + 'debug[7]':'Rx buffer size', + }, + 'ATTITUDE' : { + 'debug[all]':'Attitude', + 'debug[0]':'IMU Gain', + 'debug[1]':'EZ_EF', + 'debug[2]':'GroundSpeedError', + 'debug[3]':'VelocityFactor', + }, + 'VTX_MSP' : { + 'debug[all]': 'VTX MSP', + 'debug[0]': 'packetCounter', + 'debug[1]': 'isCrsfPortConfig', + 'debug[2]': 'isLowPowerDisarmed', + 'debug[3]': 'mspTelemetryDescriptor', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'GPS_DOP' : { + 'debug[all]': 'GPS Dilution of Precision', + 'debug[0]': 'Number of Satellites', + 'debug[1]': 'pDOP (positional - 3D)', + 'debug[2]': 'hDOP (horizontal - 2D)', + 'debug[3]': 'vDOP (vertical - 1D)', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }, + 'FAILSAFE' : { + 'debug[all]': 'Failsafe', + 'debug[0]': 'Failsafe Phase switch', + 'debug[1]': 'Failsafe State', + 'debug[2]': 'Receiving data from Rx', + 'debug[3]': 'Failsafe Phase', + }, + 'GYRO_CALIBRATION' : { + 'debug[all]': 'Gyro Calibration', + 'debug[0]': 'Gyro Calibration X', + 'debug[1]': 'Gyro Calibration Y', + 'debug[2]': 'Gyro Calibration Z', + 'debug[3]': 'Calibration Cycles remaining', + }, + 'ANGLE_MODE' : { + 'debug[all]': 'Angle Mode', + 'debug[0]': 'Target Angle', + 'debug[1]': 'Error P correction', + 'debug[2]': 'Feedforward correction', + 'debug[3]': 'Angle Achieved', + }, + 'ANGLE_TARGET' : { + 'debug[all]': 'Angle Target', + 'debug[0]': 'Angle Target', + 'debug[1]': 'Sin Angle', + 'debug[2]': 'Current PID Setpoint', + 'debug[3]': 'Angle Current', + }, + 'CURRENT_ANGLE' : { + 'debug[all]': 'Current Angle', + 'debug[0]': 'Current Angle X', + 'debug[1]': 'Current Angle Y', + 'debug[2]': 'Current Angle Z', + }, + 'DSHOT_TELEMETRY_COUNTS' : { + 'debug[all]': 'DShot Telemetry Counts', + 'debug[0]': 'DShot Telemetry Debug[0] + 1', + 'debug[1]': 'DShot Telemetry Debug[1] + 1', + 'debug[2]': 'DShot Telemetry Debug[2] + 1', + 'debug[3]': 'Preamble Skip', + }, + 'RPM_LIMIT' : { + 'debug[all]': 'RPM Limit', + 'debug[0]': 'Average RPM', + 'debug[1]': 'Average RPM (unsmoothed)', + 'debug[2]': 'RPM Limit throttle scale', + 'debug[3]': 'Throttle', + 'debug[4]': 'Error', + 'debug[5]': 'Proportional', + 'debug[6]': 'Integral', + 'debug[7]': 'Derivative', + }, + 'RC_STATS' : { + 'debug[all]': 'RC Stats', + 'debug[0]': 'Average Throttle', + }, + 'MAG_CALIB' : { + 'debug[all]': 'Mag Calibration', + 'debug[0]': 'Mag X', + 'debug[1]': 'Mag Y', + 'debug[2]': 'Mag Z', + 'debug[3]': 'Field Strength', + 'debug[4]': 'Estimated Mag Bias X', + 'debug[5]': 'Estimated Mag Bias Y', + 'debug[6]': 'Estimated Mag Bias Z', + 'debug[7]': 'Lambda', + }, + 'MAG_TASK_RATE' : { + 'debug[all]': 'Mag Task Rate', + 'debug[0]': 'Task Rate (Hz)', + 'debug[1]': 'Actual Data Rate (Hz)', + 'debug[2]': 'Data Interval (Us)', + 'debug[3]': 'Execute Time (Us)', + 'debug[4]': 'Bus Busy', + 'debug[5]': 'Read State', + 'debug[6]': 'Task Time (Us)', + }, + 'EZLANDING' : { + 'debug[all]': 'EZ Landing', + 'debug[0]': 'EZ Land Factor', + 'debug[1]': 'Adjusted Throttle', + 'debug[2]': 'Upper Limit', + 'debug[3]': 'EZ Land Limit', + 'debug[4]': 'Stick Limit', + 'debug[5]': 'Speed Limit', + }, +}; + +let DEBUG_FRIENDLY_FIELD_NAMES = null; + +FlightLogFieldPresenter.adjustDebugDefsList = function(firmwareType, firmwareVersion) { + + DEBUG_FRIENDLY_FIELD_NAMES = {...DEBUG_FRIENDLY_FIELD_NAMES_INITIAL}; + + if (firmwareType === FIRMWARE_TYPE_BETAFLIGHT) { + + if (semver.gte(firmwareVersion, '4.1.0')) { + DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = { + 'debug[all]':'Feedforward [roll]', + 'debug[0]':'Setpoint Delta [roll]', + 'debug[1]':'Boost [roll]', + 'debug[2]':'Boost, clipped [roll]', + 'debug[3]':'Duplicate Counter [roll]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.FF_LIMIT = { + 'debug[all]':'Feedforward Limit [roll]', + 'debug[0]':'FF limit input [roll]', + 'debug[1]':'FF limit input [pitch]', + 'debug[2]':'FF limited [roll]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + } + + if (semver.gte(firmwareVersion, '4.2.0')) { + DEBUG_FRIENDLY_FIELD_NAMES.FF_INTERPOLATED = { + 'debug[all]':'Feedforward [roll]', + 'debug[0]':'Setpoint Delta [roll]', + 'debug[1]':'Acceleration [roll]', + 'debug[2]':'Acceleration, clipped [roll]', + 'debug[3]':'Duplicate Counter [roll]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + } + + if (semver.gte(firmwareVersion, '4.3.0')) { + DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD = { + 'debug[all]':'Feedforward [roll]', + 'debug[0]':'Setpoint, un-smoothed [roll]', + 'debug[1]':'Delta, smoothed [roll]', + 'debug[2]':'Boost, smoothed [roll]', + 'debug[3]':'rcCommand Delta [roll]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.FEEDFORWARD_LIMIT = { + 'debug[all]':'Feedforward Limit [roll]', + 'debug[0]':'Feedforward input [roll]', + 'debug[1]':'Feedforward input [pitch]', + 'debug[2]':'Feedforward limited [roll]', + 'debug[3]':'Not Used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.DYN_IDLE = { + 'debug[all]':'Dyn Idle', + 'debug[0]':'Dyn Idle P [roll]', + 'debug[1]':'Dyn Idle I [roll]', + 'debug[2]':'Dyn Idle D [roll]', + 'debug[3]':'Min RPM', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.FFT = { + 'debug[all]':'Debug FFT', + 'debug[0]':'Gyro Pre Dyn Notch [dbg-axis]', + 'debug[1]':'Gyro Post Dyn Notch [dbg-axis]', + 'debug[2]':'Gyro Downsampled [dbg-axis]', + 'debug[3]':'Not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.FFT_TIME = { + 'debug[all]':'Debug FFT TIME', + 'debug[0]':'Active calc step', + 'debug[1]':'Step duration', + 'debug[2]':'Not used', + 'debug[3]':'Not used', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.FFT_FREQ = { + 'debug[all]':'Debug FFT FREQ', + 'debug[0]':'Notch 1 Center Freq [dbg-axis]', + 'debug[1]':'Notch 2 Center Freq [dbg-axis]', + 'debug[2]':'Notch 3 Center Freq [dbg-axis]', + 'debug[3]':'Gyro Pre Dyn Notch [dbg-axis]', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.GPS_RESCUE_THROTTLE_PID = { + 'debug[all]':'GPS Rescue Altitude', + 'debug[0]':'Throttle P', + 'debug[1]':'Throttle D', + 'debug[2]':'Altitude', + 'debug[3]':'Target Altitude', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + } + + if (semver.gte(firmwareVersion, '4.4.0')) { + DEBUG_FRIENDLY_FIELD_NAMES.BARO = { + 'debug[all]':'Debug Barometer', + 'debug[0]':'Baro State', + 'debug[1]':'Baro Pressure', + 'debug[2]':'Baro Temperature', + 'debug[3]':'Baro Altitude', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.RTH = { + 'debug[all]':'RTH Rescue codes', + 'debug[0]':'Pitch angle, deg', + 'debug[1]':'Rescue Phase', + 'debug[2]':'Failure code', + 'debug[3]':'Failure timers', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + DEBUG_FRIENDLY_FIELD_NAMES.GPS_RESCUE_THROTTLE_PID = { + 'debug[all]':'GPS Rescue throttle PIDs', + 'debug[0]':'Throttle P', + 'debug[1]':'Throttle D', + 'debug[2]':'Altitude', + 'debug[3]':'Target altitude', + 'debug[4]':'Not Used', + 'debug[5]':'Not Used', + 'debug[6]':'Not Used', + 'debug[7]':'Not Used', + }; + } + } +}; + +FlightLogFieldPresenter.presentFlags = function(flags, flagNames) { + let + printedFlag = false, + i, + result = ""; + + i = 0; + + while (flags > 0) { + if ((flags & 1) != 0) { + if (printedFlag) { + result += "|"; + } else { + printedFlag = true; + } + + result += flagNames[i]; + } + + flags >>= 1; + i++; + } + + if (printedFlag) { + return result; + } else { + return "0"; //No flags set + } +}; + +// Only list events that have changed, flag with eirer go ON or OFF. +FlightLogFieldPresenter.presentChangeEvent = function presentChangeEvent(flags, lastFlags, flagNames) { + let eventState = ''; + let found = false; + + for (let i = 0; i < flagNames.length; i++) { + if ((1 << i) & (flags ^ lastFlags)) { // State Changed + eventState += '|' + flagNames[i] + ' ' + (((1 << i) & flags) ? 'ON' : 'OFF'); + found = true; + } + } + if (!found) { eventState += ' | ACRO'; } // Catch the state when all flags are off, which is ACRO of course + return eventState; +}; + +FlightLogFieldPresenter.presentEnum = function presentEnum(value, enumNames) { + if (enumNames[value] === undefined) { + return value; + } + + return enumNames[value]; +}; + +/** + * Function to translate altitudes from the default meters + * to the user selected measurement unit. + * @param altitude String: Altitude in meters. + * @param altitudeUnits Integer: 1 for meters, 2 for feet. + * + * @returns String: readable meters in selected unit. + */ + +FlightLogFieldPresenter.decodeCorrectAltitude = function(altitude, altitudeUnits) { + switch (altitudeUnits) { + case 1: // Keep it in meters. + return (altitude).toFixed(2) + " m"; + case 2: // Translate it into feet. + return (altitude * 3.28).toFixed(2) + " ft"; + } +}; + +/** + * Attempt to decode the given raw logged value into something more human readable, or return an empty string if + * no better representation is available. + * + * @param fieldName Name of the field + * @param value Value of the field + */ +FlightLogFieldPresenter.decodeFieldToFriendly = function(flightLog, fieldName, value, currentFlightMode) { + if (value === undefined) { + return ""; + } + + const highResolutionScale = (flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0) ? 10 : 1; + const highResolutionAddPrecision = (flightLog && flightLog.getSysConfig().blackbox_high_resolution > 0) ? 1 : 0; + + switch (fieldName) { + case 'time': + return formatTime(value / 1000, true); + + case 'gyroADC[0]': + case 'gyroADC[1]': + case 'gyroADC[2]': + case 'gyroUnfilt[0]': + case 'gyroUnfilt[1]': + case 'gyroUnfilt[2]': + return flightLog.gyroRawToDegreesPerSecond(value / highResolutionScale).toFixed(highResolutionAddPrecision) + " °/s"; + + case 'gyroADCs[0]': + case 'gyroADCs[1]': + case 'gyroADCs[2]': + return value.toFixed(0) + " °/s"; + + case 'axisError[0]': + case 'axisError[1]': + case 'axisError[2]': + return (value / highResolutionScale).toFixed(highResolutionAddPrecision) + " °/s"; + + case 'rcCommand[0]': + case 'rcCommand[1]': + case 'rcCommand[2]': + return (value / highResolutionScale + 1500).toFixed(highResolutionAddPrecision) + " us"; + case 'rcCommand[3]': + return (value / highResolutionScale).toFixed(highResolutionAddPrecision) + " us"; + + case 'motor[0]': + case 'motor[1]': + case 'motor[2]': + case 'motor[3]': + case 'motor[4]': + case 'motor[5]': + case 'motor[6]': + case 'motor[7]': + return `${flightLog.rcMotorRawToPctPhysical(value).toFixed(2)} %`; + + case 'eRPM[0]': + case 'eRPM[1]': + case 'eRPM[2]': + case 'eRPM[3]': + case 'eRPM[4]': + case 'eRPM[5]': + case 'eRPM[6]': + case 'eRPM[7]': + let motor_poles = flightLog.getSysConfig()['motor_poles']; + return (value * 200 / motor_poles).toFixed(0) + " rpm / " + (value * 3.333 / motor_poles).toFixed(1) + ' hz'; + + case 'rcCommands[0]': + case 'rcCommands[1]': + case 'rcCommands[2]': + return (value / highResolutionScale).toFixed(highResolutionAddPrecision) + " °/s"; + case 'rcCommands[3]': + return value.toFixed(1) + "%"; + + case 'axisSum[0]': + case 'axisSum[1]': + case 'axisSum[2]': + case 'axisP[0]': + case 'axisP[1]': + case 'axisP[2]': + case 'axisI[0]': + case 'axisI[1]': + case 'axisI[2]': + case 'axisD[0]': + case 'axisD[1]': + case 'axisD[2]': + case 'axisF[0]': + case 'axisF[1]': + case 'axisF[2]': + return flightLog.getPIDPercentage(value).toFixed(1) + " %"; + + case 'accSmooth[0]': + case 'accSmooth[1]': + case 'accSmooth[2]': + return flightLog.accRawToGs(value).toFixed(2 + highResolutionAddPrecision) + " g"; + + case 'vbatLatest': + if (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '4.0.0')) { + return (value / 100).toFixed(2) + "V" + ", " + (value / 100 / flightLog.getNumCellsEstimate()).toFixed(2) + " V/cell"; + } else if ((flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '3.1.0')) || + (flightLog.getSysConfig().firmwareType === FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '2.0.0'))) { + return (value / 10).toFixed(2) + "V" + ", " + (value / 10 / flightLog.getNumCellsEstimate()).toFixed(2) + " V/cell"; + } else { + return (flightLog.vbatADCToMillivolts(value) / 1000).toFixed(2) + "V" + ", " + (flightLog.vbatADCToMillivolts(value) / 1000 / flightLog.getNumCellsEstimate()).toFixed(2) + " V/cell"; + } + + case 'amperageLatest': + if ((flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '3.1.7')) || + (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_CLEANFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '2.0.0'))) { + return (value / 100).toFixed(2) + "A" + ", " + (value / 100 / flightLog.getNumMotors()).toFixed(2) + " A/motor"; + } else if (flightLog.getSysConfig().firmwareType == FIRMWARE_TYPE_BETAFLIGHT && semver.gte(flightLog.getSysConfig().firmwareVersion, '3.1.0')) { + return (value / 100).toFixed(2) + "A" + ", " + (value / 100 / flightLog.getNumMotors()).toFixed(2) + " A/motor"; + } else { + return (flightLog.amperageADCToMillivolts(value) / 1000).toFixed(2) + "A" + ", " + (flightLog.amperageADCToMillivolts(value) / 1000 / flightLog.getNumMotors()).toFixed(2) + " A/motor"; + } + + case 'heading[0]': + case 'heading[1]': + case 'heading[2]': + return (value / Math.PI * 180).toFixed(1) + "°"; + + case 'baroAlt': + return FlightLogFieldPresenter.decodeCorrectAltitude((value/100), userSettings.altitudeUnits); + + case 'flightModeFlags': + return FlightLogFieldPresenter.presentFlags(value, FLIGHT_LOG_FLIGHT_MODE_NAME); + + case 'stateFlags': + return FlightLogFieldPresenter.presentFlags(value, FLIGHT_LOG_FLIGHT_STATE_NAME); + + case 'failsafePhase': + return FlightLogFieldPresenter.presentEnum(value, FLIGHT_LOG_FAILSAFE_PHASE_NAME); + + case 'features': + return FlightLogFieldPresenter.presentEnum(value, FLIGHT_LOG_FEATURES); + + case 'rssi': + return (value / 1024 * 100).toFixed(2) + " %"; + + //H Field G name:time,GPS_numSat,GPS_coord[0],GPS_coord[1],GPS_altitude,GPS_speed,GPS_ground_course + case 'GPS_numSat': + return `${value}`; + case 'GPS_coord[0]': + case 'GPS_coord[1]': + return `${(value/10000000).toFixed(5)}`; + case 'GPS_altitude': + return FlightLogFieldPresenter.decodeCorrectAltitude((value/10), userSettings.altitudeUnits); + case 'GPS_speed': + switch (userSettings.speedUnits) { + case 1: + return `${(value/100).toFixed(2)} m/s`; + case 2: + return `${((value/100) * 3.6).toFixed(2)} kph`; + case 3: + return `${((value/100) * 2.2369).toFixed(2)} mph`; + } + case 'GPS_ground_course': + return `${(value/10).toFixed(1)} °`; + + case 'debug[0]': + case 'debug[1]': + case 'debug[2]': + case 'debug[3]': + case 'debug[4]': + case 'debug[5]': + case 'debug[6]': + case 'debug[7]': + return FlightLogFieldPresenter.decodeDebugFieldToFriendly(flightLog, fieldName, value, currentFlightMode); + + default: + return ""; + } +}; + +FlightLogFieldPresenter.decodeDebugFieldToFriendly = function(flightLog, fieldName, value) { + if (flightLog) { + const debugModeName = DEBUG_MODE[flightLog.getSysConfig().debug_mode]; // convert to recognisable name + switch (debugModeName) { + case 'NONE': + case 'AIRMODE': + case 'BARO': + switch (fieldName) { + case 'debug[1]': + return `${value.toFixed(0)} hPa`; + case 'debug[2]': + return `${(value / 100).toFixed(2)} °C`; + case 'debug[3]': + return `${(value / 100).toFixed(2)} m`; + default: + return `${value.toFixed(0)}`; + } + case 'VELOCITY': + case 'DFILTER': + return ""; + case 'CYCLETIME': + switch (fieldName) { + case 'debug[1]': + return value.toFixed(0) + " %"; + default: + return value.toFixed(0) + "\u03BCS"; + } + case 'BATTERY': + switch (fieldName) { + case 'debug[0]': + return value.toFixed(0); + default: + return (value / 10).toFixed(1) + " V"; + } + case 'ACCELEROMETER': + return flightLog.accRawToGs(value).toFixed(2) + " g"; + case 'MIXER': + return Math.round(flightLog.rcCommandRawToThrottle(value)) + " %"; + case 'PIDLOOP': + return value.toFixed(0) + " \u03BCS"; + case 'RC_INTERPOLATION': + switch (fieldName) { + case 'debug[1]': // current RX refresh rate + return value.toFixed(0) + ' ms'; + case 'debug[3]': // setpoint [roll] + return value.toFixed(0) + " °/s"; + default: + return value.toFixed(0); + } + case 'GYRO': + case 'GYRO_FILTERED': + case 'GYRO_SCALED': + case 'DUAL_GYRO': + case 'DUAL_GYRO_COMBINED': + case 'DUAL_GYRO_DIFF': + case 'DUAL_GYRO_RAW': + case 'NOTCH': + return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s"; + case 'ANGLERATE': + return value.toFixed(0) + " °/s"; + case 'ESC_SENSOR': + switch (fieldName) { + case 'debug[3]': + return value.toFixed(0) + " \u03BCS"; + default: + return value.toFixed(0); + } + case 'SCHEDULER': + return value.toFixed(0) + " \u03BCS"; + case 'STACK': + return value.toFixed(0); + case 'ESC_SENSOR_RPM': + return value.toFixed(0) + " rpm"; + case 'ESC_SENSOR_TMP': + return value.toFixed(0) + " °C"; + case 'ALTITUDE': + switch (fieldName) { + case 'debug[0]': // GPS Trust * 100 + return value.toFixed(0); + case 'debug[1]': // GPS Altitude cm + case 'debug[2]': // OSD Altitude cm + case 'debug[3]': // Control Altitude + return (value / 100).toFixed(2) + ' m'; + default: + return value.toFixed(0); + } + case 'FFT': + switch (fieldName) { + case 'debug[0]': // gyro pre dyn notch [for gyro debug axis] + case 'debug[1]': // gyro post dyn notch [for gyro debug axis] + case 'debug[2]': // gyro pre dyn notch, downsampled for FFT [for gyro debug axis] + return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s"; + // debug 3 = not used + default: + return value.toFixed(0); + } + case 'FFT_TIME': + switch (fieldName) { + case 'debug[0]': + return FlightLogFieldPresenter.presentEnum(value, FFT_CALC_STEPS); + case 'debug[1]': + return value.toFixed(0) + " \u03BCs"; + // debug 2 = not used + // debug 3 = not used + default: + return value.toFixed(0); + } + case 'FFT_FREQ': + switch (fieldName) { + case 'debug[3]': // gyro pre dyn notch [for gyro debug axis] + return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s"; + default: + return value.toFixed(0) + " Hz"; + } + case 'RTH': + switch (fieldName) { +// temporarily, perhaps +// case 'debug[0]': // pitch angle +/-4000 means +/- 40 deg +// return (value / 100).toFixed(1) + " °"; + default: + return value.toFixed(0); + } + case 'ITERM_RELAX': + switch (fieldName) { + case 'debug[0]': // roll setpoint high-pass filtered + return value.toFixed(0) + " °/s"; + case 'debug[1]': // roll I-term relax factor + return value.toFixed(0) + ' %'; + case 'debug[3]': // roll absolute control axis error + return (value / 10).toFixed(1) + " °"; + default: + return value.toFixed(0); + } + case 'RC_SMOOTHING': + switch (fieldName) { + case 'debug[0]': + return (value + 1500).toFixed(0) + " us"; + case 'debug[3]': // rx frame rate [us] + return (value / 1000).toFixed(1) + ' ms'; + default: + return value.toFixed(0); + } + case 'RC_SMOOTHING_RATE': + switch (fieldName) { + case 'debug[0]': // current frame rate [us] + case 'debug[2]': // average frame rate [us] + return (value / 1000).toFixed(2) + ' ms'; + default: + return value.toFixed(0); + } + case 'DSHOT_RPM_TELEMETRY': + return (value * 200 / flightLog.getSysConfig()['motor_poles']).toFixed(0) + " rpm / " + (value * 3.333 / flightLog.getSysConfig()['motor_poles']).toFixed(0) + ' hz'; + case 'RPM_FILTER': + return (value * 60).toFixed(0) + "rpm / " + value.toFixed(0) + " Hz"; + case 'D_MIN': + switch (fieldName) { + case 'debug[0]': // roll gyro factor + case 'debug[1]': // roll setpoint Factor + return value.toFixed(0) + ' %'; + case 'debug[2]': // roll actual D + case 'debug[3]': // pitch actual D + return (value / 10).toFixed(1); + default: + return value.toFixed(0); + } + case 'DYN_LPF': + switch (fieldName) { + case 'debug[0]': // gyro scaled [for selected axis] + case 'debug[3]': // pre-dyn notch gyro [for selected axis] + return Math.round(flightLog.gyroRawToDegreesPerSecond(value)) + " °/s"; + default: + return value.toFixed(0) + " Hz"; + } + case 'DYN_IDLE': + switch (fieldName) { + case 'debug[3]': // minRPS + return (value * 6) + ' rpm / ' + (value / 10).toFixed(0) +' hz'; + default: + return value.toFixed(0); + } + case 'AC_CORRECTION': + return (value / 10).toFixed(1) + " °/s"; + case 'AC_ERROR': + return (value / 10).toFixed(1) + " °"; + case 'RX_TIMING': + switch (fieldName) { + case 'debug[0]': // Frame delta us/10 + case 'debug[1]': // Frame age us/10 + return (value / 100).toFixed(2) + ' ms'; + default: + return value.toFixed(0); + } + case 'GHST': + switch (fieldName) { + // debug 0 is CRC error count 0 to int16_t + // debug 1 is unknown frame count 0 to int16_t + // debug 2 is RSSI 0 to -128 -> 0 to 128 + case 'debug[3]': // LQ 0-100 + return value.toFixed(0) + ' %'; + default: + return value.toFixed(0); + } + case 'GHST_MSP': + switch (fieldName) { + // debug 0 is msp frame count + // debug 1 is msp frame count + // debug 2 and 3 not used + default: + return value.toFixed(0); + } + case 'SCHEDULER_DETERMINISM': + switch (fieldName) { + case 'debug[0]': // cycle time in us*10 + case 'debug[2]': // task delay time in us*10 + case 'debug[3]': // task delay time in us*10 + return (value / 10).toFixed(1) + ' us'; + // debug 1 is task ID of late task + default: + return value.toFixed(0); + } + case 'TIMING_ACCURACY': + switch (fieldName) { + case 'debug[0]': // CPU Busy % + return value.toFixed(1) + ' %'; + case 'debug[2]': // task delay time in us*10 + return (value / 10).toFixed(1) + ' us'; + default: + return value.toFixed(0); + } + case 'RX_EXPRESSLRS_SPI': + switch (fieldName) { + case 'debug[3]': // uplink LQ % + return value.toFixed(1) + ' %'; + // debug 0 = Lost connection count + // debug 1 = RSSI + // debug 2 = SNR + default: + return value.toFixed(0); + } + case 'RX_EXPRESSLRS_PHASELOCK': + switch (fieldName) { + case 'debug[2]': // Frequency offset in ticks + return value.toFixed(0) + ' ticks'; + // debug 0 = Phase offset us + // debug 1 = Filtered phase offset us + // debug 3 = Pphase shift in us + default: + return value.toFixed(0) + ' us'; + } + case 'GPS_RESCUE_THROTTLE_PID': + switch (fieldName) { + case 'debug[0]': // Throttle P added uS + case 'debug[1]': // Throttle D added * uS + return value.toFixed(0) + ' uS'; + case 'debug[2]': // current altitude in m + case 'debug[3]': // TARGET altitude in m + return (value / 100).toFixed(1) + ' m'; + default: + return value.toFixed(0); + } + case 'GPS_RESCUE_VELOCITY': + switch (fieldName) { + case 'debug[0]': // Pitch P degrees * 100 + case 'debug[1]': // Pitch D degrees * 100 + return (value / 100).toFixed(1) + " °"; + case 'debug[2]': // velocity to home cm/s + case 'debug[3]': // velocity target cm/s + return (value / 100).toFixed(1) + ' m/s'; + default: + return value.toFixed(0); + } + case 'GPS_RESCUE_HEADING': + switch (fieldName) { + case 'debug[0]': // Ground speed cm/s + return (value / 100).toFixed(2) + ' m/s'; + case 'debug[1]': // GPS Ground course degrees * 10 + case 'debug[2]': // Attitude in degrees * 10 + case 'debug[3]': // Angle to home in degrees * 10 + case 'debug[4]': // magYaw in degrees * 10 + return (value / 10).toFixed(1) + " °"; + case 'debug[6]': // Roll Added deg * 100 + return (value / 100).toFixed(1) + " °"; + case 'debug[5]': // Roll Mix Att + case 'debug[7]': // Rescue Yaw Rate + default: + return value.toFixed(0); + } + case 'GPS_RESCUE_TRACKING': + switch (fieldName) { + case 'debug[0]': // velocity to home cm/s + case 'debug[1]': // velocity target cm/s + return (value / 100).toFixed(1) + ' m/s'; + case 'debug[2]': // altitude cm + case 'debug[3]': // altitude target cm + return (value / 100).toFixed(1) + ' m'; + default: + return value.toFixed(0); + } + case 'GPS__CONNECTION': + switch (fieldName) { + case 'debug[0]': // Flight model + case 'debug[1]': // GPS Nav packet interval + case 'debug[2]': // FC Nav data time + return value.toFixed(0); + case 'debug[3]': // Baud Rate / Nav interval + return (value * 100).toFixed(0); + case 'debug[4]': // main state * 100 + subState + case 'debug[5]': // executeTimeUs + case 'debug[6]': // ack state + case 'debug[7]': // serial Rx buffer + default: + return value.toFixed(0); + } + case 'ATTITUDE': + switch (fieldName) { + case 'debug[0]': // accADC X + case 'debug[1]': // accADC Y + case 'debug[2]': // setpoint Roll + case 'debug[3]': // setpoint Pitch + default: + return value.toFixed(0); + } + case 'VTX_MSP': + switch (fieldName) { + case 'debug[0]': // packetCounter + case 'debug[1]': // isCrsfPortConfig + case 'debug[2]': // isLowPowerDisarmed + case 'debug[3]': // mspTelemetryDescriptor + default: + return value.toFixed(0); + } + case 'GPS_DOP': + switch (fieldName) { + case 'debug[0]': // Number of Satellites + return value.toFixed(0); + case 'debug[1]': // pDOP (positional - 3D) + case 'debug[2]': // hDOP (horizontal - 2D) + case 'debug[3]': // vDOP (vertical - 1D) + default: + return (value / 100).toFixed(2); + } + case 'FAILSAFE': + return value.toFixed(0); + case 'GYRO_CALIBRATION': + return value.toFixed(0); + case 'ANGLE_MODE': + switch (fieldName) { + case 'debug[0]': // target angle + case 'debug[1]': // angle error + case 'debug[2]': // angle feedforward + case 'debug[3]': // angle achieved + return (value / 10).toFixed(1) + " °"; + default: + return value.toFixed(0); + } + case 'ANGLE_TARGET': + return value.toFixed(0); + case 'CURRENT_ANGLE': + return value.toFixed(0); + case 'DSHOT_TELEMETRY_COUNTS': + return value.toFixed(0); + case 'EZLANDING': + return `${(value / 100.0).toFixed(2)} %`; + } + return value.toFixed(0); + } + return ""; +}; + +FlightLogFieldPresenter.fieldNameToFriendly = function(fieldName, debugMode) { + if (debugMode) { + if (fieldName.includes('debug')) { + let debugModeName = DEBUG_MODE[debugMode]; + let debugFields; + + if (debugModeName) { + debugFields = DEBUG_FRIENDLY_FIELD_NAMES[debugModeName]; + } + + if (!debugFields) { + if (fieldName === 'debug[all]') { + return 'Debug (' + (debugModeName || debugMode) + ')'; + } + debugFields = DEBUG_FRIENDLY_FIELD_NAMES[DEBUG_MODE[0]]; + } + + return debugFields[fieldName]; + } + } + if (FRIENDLY_FIELD_NAMES[fieldName]) { + return FRIENDLY_FIELD_NAMES[fieldName]; + } + + return fieldName; +}; diff --git a/js/flightlog_index.js b/src/flightlog_index.js similarity index 80% rename from js/flightlog_index.js rename to src/flightlog_index.js index a6c836da..4c5db51c 100644 --- a/js/flightlog_index.js +++ b/src/flightlog_index.js @@ -1,6 +1,10 @@ -"use strict"; +import { FlightLogParser } from "./flightlog_parser"; +import { FlightLogEvent } from "./flightlog_fielddefs"; +import { IMU } from "./imu"; +import { ArrayDataStream } from "./datastream"; +import './decoders'; -function FlightLogIndex(logData) { +export function FlightLogIndex(logData) { //Private: var that = this, @@ -43,9 +47,12 @@ function FlightLogIndex(logData) { times: [], offsets: [], avgThrottle: [], + maxRC: [], + maxMotorDiff: [], initialIMU: [], initialSlow: [], initialGPSHome: [], + initialGPS: [], hasEvent: [], minTime: false, maxTime: false @@ -56,8 +63,12 @@ function FlightLogIndex(logData) { iframeCount = 0, motorFields = [], + maxRCFields = [], matches, throttleTotal, + rcTotal, + maxMotor, + minMotor, eventInThisChunk = null, parsedHeader, sawEndMarker = false; @@ -77,20 +88,29 @@ function FlightLogIndex(logData) { var sysConfig = parser.sysConfig, mainFrameDef = parser.frameDefs.I, - + gyroADC = [mainFrameDef.nameToIndex["gyroADC[0]"], mainFrameDef.nameToIndex["gyroADC[1]"], mainFrameDef.nameToIndex["gyroADC[2]"]], accSmooth = [mainFrameDef.nameToIndex["accSmooth[0]"], mainFrameDef.nameToIndex["accSmooth[1]"], mainFrameDef.nameToIndex["accSmooth[2]"]], magADC = [mainFrameDef.nameToIndex["magADC[0]"], mainFrameDef.nameToIndex["magADC[1]"], mainFrameDef.nameToIndex["magADC[2]"]], lastSlow = [], - lastGPSHome = []; - + lastGPSHome = [], + lastGPS = []; + // Identify motor fields so they can be used to show the activity summary bar - for (var j = 0; j < 8; j++) { + for (let j = 0; j < 8; j++) { if (mainFrameDef.nameToIndex["motor[" + j + "]"] !== undefined) { motorFields.push(mainFrameDef.nameToIndex["motor[" + j + "]"]); } } + + for (let j = 0; j < 3; j++) { + if (mainFrameDef.nameToIndex["rcCommand[" + j + "]"] !== undefined) { + maxRCFields.push(mainFrameDef.nameToIndex["rcCommand[" + j + "]"]); + } else { + console.log("RCField not found"); + } + } // Do we have mag fields? If not mark that data as absent if (magADC[0] === undefined) { @@ -125,12 +145,25 @@ function FlightLogIndex(logData) { if (motorFields.length) { throttleTotal = 0; - for (var j = 0; j < motorFields.length; j++) { - throttleTotal += frame[motorFields[j]]; + maxMotor = 0; + minMotor = 2000; + for (let mofo of motorFields) { + maxMotor = Math.max(frame[mofo], maxMotor); + minMotor = Math.min(frame[mofo], minMotor); + throttleTotal += frame[mofo]; } + intraIndex.maxMotorDiff.push(maxMotor - minMotor); intraIndex.avgThrottle.push(Math.round(throttleTotal / motorFields.length)); } + if (maxRCFields.length) { + rcTotal = 0; + for (let rcfo of maxRCFields) { + rcTotal += Math.max(rcTotal,Math.abs(frame[rcfo])); + } + + intraIndex.maxRC.push(rcTotal); + } /* To enable seeking to an arbitrary point in the log without re-reading anything * that came before, we have to record the initial state of various items which aren't @@ -139,6 +172,7 @@ function FlightLogIndex(logData) { intraIndex.initialIMU.push(new IMU(imu)); intraIndex.initialSlow.push(lastSlow); intraIndex.initialGPSHome.push(lastGPSHome); + intraIndex.initialGPS.push(lastGPS); } iframeCount++; @@ -153,6 +187,10 @@ function FlightLogIndex(logData) { magADC ? [frame[magADC[0]], frame[magADC[1]], frame[magADC[2]]] : false ); break; + case 'G': + lastGPS = frame.slice(0); + lastGPS.shift(); // Remove the time field + break; case 'H': lastGPSHome = frame.slice(0); break; @@ -227,7 +265,9 @@ function FlightLogIndex(logData) { offsets: new Array(sourceIndex.offsets.length), minTime: sourceIndex.minTime, maxTime: sourceIndex.maxTime, - avgThrottle: new Array(sourceIndex.avgThrottle.length) + avgThrottle: new Array(sourceIndex.avgThrottle.length), + maxRC: new Array(sourceIndex.maxRC.length), + maxMotorDiff: new Array(sourceIndex.maxMotorDiff.length), }; if (sourceIndex.times.length > 0) { @@ -250,11 +290,14 @@ function FlightLogIndex(logData) { } if (sourceIndex.avgThrottle.length > 0) { - for (j = 0; j < sourceIndex.avgThrottle.length; j++) { + // Assuming that avgThrottle, maxRC and maxMotorDiff Arrays are the same length + // since they are build in the same loop. Just to get rid of a codesmell on Sonarcloud + for (let j = 0; j < sourceIndex.avgThrottle.length; j++) { resultIndex.avgThrottle[j] = sourceIndex.avgThrottle[j] - 1000; + resultIndex.maxRC[j] = sourceIndex.maxRC[j] * 20 - 1000; + resultIndex.maxMotorDiff[j] = sourceIndex.maxMotorDiff[j] * 20 - 1000; } - } - + } resultIndexes[i] = resultIndex; } diff --git a/js/flightlog_parser.js b/src/flightlog_parser.js similarity index 96% rename from js/flightlog_parser.js rename to src/flightlog_parser.js index f2827e8e..9bc48998 100644 --- a/js/flightlog_parser.js +++ b/src/flightlog_parser.js @@ -1,14 +1,24 @@ -"use strict"; - -var FlightLogIndex, - - FIRMWARE_TYPE_UNKNOWN = 0, - FIRMWARE_TYPE_BASEFLIGHT = 1, - FIRMWARE_TYPE_CLEANFLIGHT = 2, - FIRMWARE_TYPE_BETAFLIGHT = 3, - FIRMWARE_TYPE_INAV = 4; - -var FlightLogParser = function(logData) { +import { FlightLogFieldPresenter } from "./flightlog_fields_presenter"; +import { adjustFieldDefsList, FlightLogEvent } from "./flightlog_fielddefs"; +import { ArrayDataStream } from "./datastream"; +import './decoders'; +import { + hexToFloat, + uint32ToFloat, + asciiArrayToString, + asciiStringToByteArray, + signExtend14Bit, + stringHasComma, + parseCommaSeparatedString, +} from "./tools"; + +globalThis.FIRMWARE_TYPE_UNKNOWN = 0; +globalThis.FIRMWARE_TYPE_BASEFLIGHT = 1; +globalThis.FIRMWARE_TYPE_CLEANFLIGHT = 2; +globalThis.FIRMWARE_TYPE_BETAFLIGHT = 3; +globalThis.FIRMWARE_TYPE_INAV = 4; + +export function FlightLogParser(logData) { //Private constants: var FLIGHT_LOG_MAX_FIELDS = 128, @@ -187,7 +197,7 @@ var FlightLogParser = function(logData) { vbatmaxcellvoltage:43, vbatwarningcellvoltage: 35, gyroScale: 0.0001, // Not even close to the default, but it's hardware specific so we can't do much better - acc_1G: 4096, // Ditto ^ + acc_1G: 2048, // Ditto ^ minthrottle: 1150, maxthrottle: 1850, currentMeterOffset: 0, @@ -200,12 +210,16 @@ var FlightLogParser = function(logData) { // be an older name which is translated into a current name in the table below defaultSysConfigExtension = { - abs_control_gain:null, // Aboslute control gain + abs_control_gain:null, // Absolute control gain anti_gravity_gain:null, // Anti gravity gain + anti_gravity_p_gain:null, // Anti gravity P gain anti_gravity_mode:null, // Anti gravity mode anti_gravity_threshold:null, // Anti gravity threshold for step mode + anti_gravity_cutoff_hz:null, // Anti gravity Cutoff + blackbox_high_resolution:null, // Blackbox high resolution mode thrMid:null, // Throttle Mid Position thrExpo:null, // Throttle Expo + tpa_mode:null, // TPA Mode tpa_breakpoint:null, // TPA Breakpoint airmode_activate_throttle:null, // airmode activation level serialrx_provider:null, // name of the serial rx provider @@ -236,6 +250,7 @@ var FlightLogParser = function(logData) { dshot_bidir:null, // DShot bidir protocol enabled dterm_lpf_hz:null, // DTerm Lowpass Filter Hz dterm_lpf_dyn_hz:[null, null], // DTerm Lowpass Dynamic Filter Min and Max Hz + dterm_lpf_dyn_expo:null, // DTerm Lowpass Dynamic Filter Expo dterm_lpf2_hz:null, // DTerm Lowpass Filter Hz 2 dterm_differentiator:null, // DTerm Differentiator H_sensitivity:null, // Horizon Sensitivity @@ -246,6 +261,7 @@ var FlightLogParser = function(logData) { gyro_32khz_hardware_lpf:null, // Gyro 32khz hardware lpf setting. (post BF3.4) gyro_lowpass_hz:null, // Gyro Soft Lowpass Filter Hz gyro_lowpass_dyn_hz:[null, null], // Gyro Soft Lowpass Dynamic Filter Min and Max Hz + gyro_lowpass_dyn_expo:null, // Gyro Soft Lowpass Dynamic Filter Expo gyro_lowpass2_hz:null, // Gyro Soft Lowpass Filter Hz 2 gyro_notch_hz:null, // Gyro Notch Frequency gyro_notch_cutoff:null, // Gyro Notch Cutoff @@ -272,7 +288,7 @@ var FlightLogParser = function(logData) { rc_smoothing_active_cutoffs:[null,null],// RC Smoothing active cutoffs rc_smoothing_cutoffs:[null, null], // RC Smoothing input and derivative cutoff rc_smoothing_filter_type:[null,null], // RC Smoothing input and derivative type - rc_smoothing_rx_average:null, // RC Smoothing rx average readed in ms + rc_smoothing_rx_average:null, // RC Smoothing rx average read in ms rc_smoothing_debug_axis:null, // Axis recorded in the debug mode of rc_smoothing dterm_filter_type:null, // D term filtering type (PT1, BIQUAD, PT2, PT3) dterm_filter2_type:null, // D term 2 filtering type (PT1, BIQUAD, PT2, PT3) @@ -346,6 +362,11 @@ var FlightLogParser = function(logData) { throttle_limit_percent: null, throttle_boost: null, // throttle boost throttle_boost_cutoff: null, + thrust_linear: null, + tpa_low_rate: null, + tpa_low_breakpoint: null, + tpa_low_always: null, + mixer_type: null, unknownHeaders : [] // Unknown Extra Headers }, @@ -367,6 +388,7 @@ var FlightLogParser = function(logData) { dterm_lpf1_type : "dterm_filter_type", dterm_lpf1_static_hz : "dterm_lpf_hz", dterm_lpf1_dyn_hz : "dterm_lpf_dyn_hz", + dterm_lpf1_dyn_expo : "dterm_lpf_dyn_expo", dterm_lpf2_type : "dterm_filter2_type", dterm_lpf2_static_hz : "dterm_lpf2_hz", dterm_setpoint_weight : "dtermSetpointWeight", @@ -389,6 +411,7 @@ var FlightLogParser = function(logData) { gyro_lpf1_type : "gyro_soft_type", gyro_lpf1_static_hz : "gyro_lowpass_hz", gyro_lpf1_dyn_hz : "gyro_lowpass_dyn_hz", + gyro_lpf1_dyn_expo : "gyro_lowpass_dyn_expo", gyro_lpf2_type : "gyro_soft2_type", gyro_lpf2_static_hz : "gyro_lowpass2_hz", "gyro.scale" : "gyro_scale", @@ -419,12 +442,17 @@ var FlightLogParser = function(logData) { setpoint_relaxation_ratio : "setpointRelaxRatio", thr_expo : "thrExpo", thr_mid : "thrMid", - tpa_rate : "dynThrPID", + dynThrPID : "tpa_rate", use_unsynced_pwm : "unsynced_fast_pwm", vbat_scale : "vbatscale", vbat_pid_gain : "vbat_pid_compensation", yaw_accel_limit : "yawRateAccelLimit", yaw_lowpass_hz : "yaw_lpf_hz", + thrust_linear : "thrust_linear", + tpa_low_rate : "tpa_low_rate", + tpa_low_breakpoint : "tpa_low_breakpoint", + tpa_low_always : "tpa_low_always", + mixer_type : "mixer_type", }, frameTypes, @@ -609,7 +637,8 @@ var FlightLogParser = function(logData) { case "rcRate": case "thrMid": case "thrExpo": - case "dynThrPID": + case "tpa_rate": + case "tpa_mode": case "tpa_breakpoint": case "airmode_activate_throttle": case "serialrx_provider": @@ -683,6 +712,8 @@ var FlightLogParser = function(logData) { case "debug_mode": case "anti_gravity_mode": case "anti_gravity_gain": + case "anti_gravity_p_gain": + case "anti_gravity_cutoff_hz": case "abs_control_gain": case "use_integrated_yaw": case "d_min_gain": @@ -737,6 +768,8 @@ var FlightLogParser = function(logData) { case "throttle_boost_cutoff": case "motor_poles": + + case "blackbox_high_resolution": that.sysConfig[fieldName] = parseInt(fieldValue, 10); break; case "rc_expo": @@ -830,6 +863,13 @@ var FlightLogParser = function(logData) { case "rc_smoothing_active_cutoffs": case "rc_smoothing_active_cutoffs_ff_sp_thr": case "gyro_lowpass_dyn_hz": + case "gyro_lowpass_dyn_expo": + case "dterm_lpf_dyn_expo": + case "thrust_linear": + case "tpa_low_rate": + case "tpa_low_breakpoint": + case "tpa_low_always": + case "mixer_type": case "dterm_lpf_dyn_hz": that.sysConfig[fieldName] = parseCommaSeparatedString(fieldValue); break; @@ -984,9 +1024,11 @@ var FlightLogParser = function(logData) { console.log("Saw unsupported field header \"" + fieldName + "\""); } } else { - console.log("Ignoring unsupported header \"" + fieldName + "\""); - if(that.sysConfig.unknownHeaders==null) that.sysConfig.unknownHeaders = new Array(); - that.sysConfig.unknownHeaders.push({ name: fieldName, value: fieldValue });// Save the unknown headers + console.log(`Ignoring unsupported header ${fieldName} ${fieldValue}`); + if (that.sysConfig.unknownHeaders === null) { + that.sysConfig.unknownHeaders = new Array(); + } + that.sysConfig.unknownHeaders.push({ name: fieldName, value: fieldValue }); // Save the unknown headers } break; } diff --git a/js/flightlog_video_renderer.js b/src/flightlog_video_renderer.js similarity index 94% rename from js/flightlog_video_renderer.js rename to src/flightlog_video_renderer.js index db41f6a9..7c5a9e17 100644 --- a/js/flightlog_video_renderer.js +++ b/src/flightlog_video_renderer.js @@ -1,4 +1,4 @@ -"use strict"; +import { FlightLogGrapher } from "./grapher"; /** * Render a video of the given log using the given videoOptions (user video settings) and logParameters. @@ -22,7 +22,7 @@ * onComplete - On render completion, called with (success, frameCount) * onProgress - Called periodically with (frameIndex, frameCount) to report progress */ -function FlightLogVideoRenderer(flightLog, logParameters, videoOptions, events) { +export function FlightLogVideoRenderer(flightLog, logParameters, videoOptions, events) { var WORK_CHUNK_SIZE_FOCUSED = 8, WORK_CHUNK_SIZE_UNFOCUSED = 32, @@ -99,7 +99,7 @@ function FlightLogVideoRenderer(flightLog, logParameters, videoOptions, events) function openFileForWrite(suggestedName, onComplete) { return new Promise(function(resolve, reject) { chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: suggestedName, - accepts: [{extensions: ['webm']}]}, function(fileEntry) { + accepts: [{extensions: ['webm'], description: "WebM video"}]}, function(fileEntry) { var error = chrome.runtime.lastError; @@ -182,9 +182,9 @@ function FlightLogVideoRenderer(flightLog, logParameters, videoOptions, events) renderFrame = function() { graph.render(frameTime); - if(logParameters.hasSticks) canvasContext.drawImage(stickCanvas, stickCanvasLeft, stickCanvasTop); - if(logParameters.hasCraft) canvasContext.drawImage(craftCanvas, craftCanvasLeft, craftCanvasTop); - if(logParameters.hasAnalyser) canvasContext.drawImage(analyserCanvas, analyserCanvasLeft, analyserCanvasTop); + if (logParameters.hasSticks && parseInt(userSettings.sticks.size) > 0) canvasContext.drawImage(stickCanvas, stickCanvasLeft, stickCanvasTop); + if (logParameters.hasCraft && parseInt(userSettings.craft.size) > 0) canvasContext.drawImage(craftCanvas, craftCanvasLeft, craftCanvasTop); + if (logParameters.hasAnalyser && parseInt(userSettings.analyser.size) > 0) canvasContext.drawImage(analyserCanvas, analyserCanvasLeft, analyserCanvasTop); videoWriter.addFrame(canvas); diff --git a/src/gpx-exporter.js b/src/gpx-exporter.js new file mode 100644 index 00000000..eaafe8cf --- /dev/null +++ b/src/gpx-exporter.js @@ -0,0 +1,30 @@ +/** + * @constructor + * @param {FlightLog} flightLog + */ +export function GpxExporter(flightLog) { + + /** + * @param {function} success is a callback triggered when export is done + */ + function dump(success) { + let frames = _(flightLog.getChunksInTimeRange(flightLog.getMinTime(), flightLog.getMaxTime())) + .map(chunk => chunk.frames).value(), + worker = new Worker("/js/webworkers/gpx-export-worker.js"); + + worker.onmessage = event => { + success(event.data); + worker.terminate(); + }; + worker.postMessage({ + sysConfig: flightLog.getSysConfig(), + fieldNames: flightLog.getMainFieldNames(), + frames: frames, + }); + } + + // exposed functions + return { + dump: dump, + }; +}; diff --git a/src/graph_config.js b/src/graph_config.js new file mode 100644 index 00000000..cdf92759 --- /dev/null +++ b/src/graph_config.js @@ -0,0 +1,1271 @@ +import { FlightLogFieldPresenter } from "./flightlog_fields_presenter"; +import { DSHOT_MIN_VALUE, DSHOT_RANGE, RATES_TYPE, DEBUG_MODE } from "./flightlog_fielddefs"; +import { escapeRegExp } from "./tools"; + +export function GraphConfig(graphConfig) { + var + graphs = graphConfig ? graphConfig : [], + listeners = [], + that = this; + + function notifyListeners() { + for (var i = 0; i < listeners.length; i++) { + listeners[i](that); + } + } + + this.selectedFieldName = null; + this.selectedGraphIndex = 0; + this.selectedFieldIndex = 0; + + this.highlightGraphIndex = null; + this.highlightFieldIndex = null; + + const hiddenGraphFields = new Set(); + + this.getGraphs = function() { + return graphs; + }; + + /** + * newGraphs is an array of objects like {label: "graph label", height:, fields:[{name: curve:{offset:, power:, inputRange:, outputRange:, steps:}, color:, }, ...]} + */ + this.setGraphs = function(newGraphs) { + graphs = newGraphs; + + hiddenGraphFields.clear(); + + notifyListeners(); + }; + + /** + * Convert the given graph configs to make them appropriate for the given flight log. + */ + this.adaptGraphs = function(flightLog, graphs) { + var + logFieldNames = flightLog.getMainFieldNames(), + + // Make copies of graphs into here so we can modify them without wrecking caller's copy + newGraphs = []; + + for (var i = 0; i < graphs.length; i++) { + var + graph = graphs[i], + newGraph = $.extend( + // Default values for missing properties: + { + height: 1, + }, + // The old graph + graph, + // New fields to replace the old ones: + { + fields:[], + }, + ), + colorIndex = 0; + + for (var j = 0; j < graph.fields.length; j++) { + var + field = graph.fields[j], + matches; + + var adaptField = function(field, colorIndexOffset, forceNewCurve) { + const defaultCurve = GraphConfig.getDefaultCurveForField(flightLog, field.name); + + if (field.curve === undefined || forceNewCurve) { + field.curve = defaultCurve; + } else { + /* The curve may have been originally created for a craft with different endpoints, so use the + * recommended offset and input range instead of the provided one. + */ + field.curve.offset = defaultCurve.offset; + field.curve.inputRange = defaultCurve.inputRange; + } + + if(colorIndexOffset!=null && field.color != undefined) { // auto offset the actual color (to expand [all] selections) + var index; + for(index=0; index < GraphConfig.PALETTE.length; index++) + { + if(GraphConfig.PALETTE[index].color == field.color) break; + } + field.color = GraphConfig.PALETTE[(index + colorIndexOffset) % GraphConfig.PALETTE.length].color + } + + if (field.color === undefined) { + field.color = GraphConfig.PALETTE[colorIndex % GraphConfig.PALETTE.length].color; + colorIndex++; + } + + if (field.smoothing === undefined) { + field.smoothing = GraphConfig.getDefaultSmoothingForField(flightLog, field.name); + } + + return field; + }; + + if ((matches = field.name.match(/^(.+)\[all\]$/))) { + var + nameRoot = matches[1], + nameRegex = new RegExp("^" + escapeRegExp(nameRoot) + "\[[0-9]+\]$"), + colorIndexOffset = 0; + + for (var k = 0; k < logFieldNames.length; k++) { + if (logFieldNames[k].match(nameRegex)) { + // add special condition for rcCommands and debug as each of the fields requires a different scaling. + let forceNewCurve = (nameRoot=='rcCommand') || (nameRoot=='rcCommands') || (nameRoot=='debug'); + newGraph.fields.push(adaptField($.extend({}, field, {curve: $.extend({}, field.curve), name: logFieldNames[k], friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(logFieldNames[k], flightLog.getSysConfig().debug_mode)}), colorIndexOffset, forceNewCurve)); + colorIndexOffset++; + } + } + } else { + // Don't add fields if they don't exist in this log + if (flightLog.getMainFieldIndexByName(field.name) !== undefined) { + newGraph.fields.push(adaptField($.extend({}, field, {curve: $.extend({}, field.curve), friendlyName: FlightLogFieldPresenter.fieldNameToFriendly(field.name, flightLog.getSysConfig().debug_mode)}))); + } + } + } + + newGraphs.push(newGraph); + } + + this.setGraphs(newGraphs); + }; + + this.addListener = function(listener) { + listeners.push(listener); + }; + + this.toggleGraphField = (graphIndex, fieldIndex) => { + const item = graphIndex + ":" + fieldIndex; + if (hiddenGraphFields.has(item)) { + hiddenGraphFields.delete(item); + } else { + hiddenGraphFields.add(item); + } + }; + + this.isGraphFieldHidden = (graphIndex, fieldIndex) => { + return hiddenGraphFields.has(graphIndex + ":" + fieldIndex); + }; +} + +GraphConfig.PALETTE = [ + {color: "#fb8072", name: "Red" }, + {color: "#8dd3c7", name: "Cyan" }, + {color: "#ffffb3", name: "Yellow" }, + {color: "#bebada", name: "Purple" }, + {color: "#80b1d3", name: "Blue" }, + {color: "#fdb462", name: "Orange" }, + {color: "#b3de69", name: "Green" }, + {color: "#fccde5", name: "Pink" }, + {color: "#d9d9d9", name: "Grey" }, + {color: "#bc80bd", name: "Dark Purple" }, + {color: "#ccebc5", name: "Light Green" }, + {color: "#ffed6f", name: "Dark Yellow" } +]; + + +GraphConfig.load = function(config) { + // Upgrade legacy configs to suit the newer standard by translating field names + if (config) { + for (var i = 0; i < config.length; i++) { + var graph = config[i]; + + for (var j = 0; j < graph.fields.length; j++) { + var + field = graph.fields[j], + matches; + + if ((matches = field.name.match(/^gyroData(.+)$/))) { + field.name = "gyroADC" + matches[1]; + } + } + } + } else { + config = false; + } + + return config; +}; + +GraphConfig.getDefaultSmoothingForField = function(flightLog, fieldName) { + try{ + if (fieldName.match(/^motor(Raw)?\[/)) { + return 5000; + } else if (fieldName.match(/^servo\[/)) { + return 5000; + } else if (fieldName.match(/^gyroADC.*\[/)) { + return 3000; + } else if (fieldName.match(/^gyroUnfilt.*\[/)) { + return 3000; + } else if (fieldName.match(/^accSmooth\[/)) { + return 3000; + } else if (fieldName.match(/^axis.+\[/)) { + return 3000; + } else { + return 0; + } + } catch (e) { return 0;} +}; + +GraphConfig.getDefaultCurveForField = function(flightLog, fieldName) { + var + sysConfig = flightLog.getSysConfig(); + + var maxDegreesSecond = function(scale) { + switch(sysConfig["rates_type"]){ + case RATES_TYPE.indexOf('ACTUAL'): + case RATES_TYPE.indexOf('QUICK'): + return Math.max(sysConfig["rates"][0] * 10.0 * scale, + sysConfig["rates"][1] * 10.0 * scale, + sysConfig["rates"][2] * 10.0 * scale); + default: + return Math.max(flightLog.rcCommandRawToDegreesPerSecond(500,0) * scale, + flightLog.rcCommandRawToDegreesPerSecond(500,1) * scale, + flightLog.rcCommandRawToDegreesPerSecond(500,2) * scale); + } + } + + var getMinMaxForFields = function(/* fieldName1, fieldName2, ... */) { + // helper to make a curve scale based on the combined min/max of one or more fields + var + stats = flightLog.getStats(), + min = Number.MAX_VALUE, + max = Number.MIN_VALUE; + + for(var i in arguments) { + var + fieldIndex = flightLog.getMainFieldIndexByName(arguments[i]), + fieldStat = fieldIndex !== undefined ? stats.field[fieldIndex] : false; + + if (fieldStat) { + min = Math.min(min, fieldStat.min); + max = Math.max(max, fieldStat.max); + } + } + + if (min != Number.MAX_VALUE && max != Number.MIN_VALUE) { + return {min:min, max:max}; + } + + return {min:-500, max:500}; + } + + var getCurveForMinMaxFields = function(/* fieldName1, fieldName2, ... */) { + var mm = getMinMaxForFields.apply(null, arguments); + + return { + offset: -(mm.max + mm.min) / 2, + power: 1.0, + inputRange: Math.max((mm.max - mm.min) / 2, 1.0), + outputRange: 1.0 + }; + } + + var getCurveForMinMaxFieldsZeroOffset = function(/* fieldName1, fieldName2, ... */) { + var mm = getMinMaxForFields.apply(null, arguments); + + return { + offset: 0, + power: 1.0, + inputRange: Math.max(Math.max(Math.abs(mm.max), Math.abs(mm.min)), 1.0), + outputRange: 1.0 + }; + } + + const gyroScaleMargin = 1.20; // Give a 20% margin for gyro graphs + const highResolutionScale = sysConfig.blackbox_high_resolution > 0 ? 10 : 1; + + try { + if (fieldName.match(/^motor\[/)) { + return { + offset: flightLog.isDigitalProtocol() ? + -(DSHOT_MIN_VALUE + DSHOT_RANGE / 2) : -(sysConfig.minthrottle + (sysConfig.maxthrottle - sysConfig.minthrottle) / 2), + power: 1.0, + inputRange: flightLog.isDigitalProtocol() ? + DSHOT_RANGE / 2 : (sysConfig.maxthrottle - sysConfig.minthrottle) / 2, + outputRange: 1.0, + }; + } else if (fieldName.match(/^eRPM\[/)) { + return getCurveForMinMaxFields('eRPM[0]', 'eRPM[1]', 'eRPM[2]', 'eRPM[3]', 'eRPM[4]', 'eRPM[5]', 'eRPM[6]', 'eRPM[7]'); + } else if (fieldName.match(/^servo\[/)) { + return { + offset: -1500, + power: 1.0, + inputRange: 500, + outputRange: 1.0 + }; + } else if (fieldName.match(/^accSmooth\[/)) { + return { + offset: 0, + power: 0.5, + inputRange: sysConfig.acc_1G * 16.0, /* Reasonable typical maximum for acc */ + outputRange: 1.0 + }; + } else if (fieldName == "rcCommands[3]") { // Throttle scaled + return { + offset: -50, + power: 1.0, /* Make this 1.0 to scale linearly */ + inputRange: 50, + outputRange: 1.0 + }; + } else if (fieldName.match(/^axisError\[/) || // Gyro, Gyro Scaled, RC Command Scaled and axisError + fieldName.match(/^rcCommands\[/) || // These use the same scaling as they are in the + fieldName.match(/^gyroADC\[/) || // same range. + fieldName.match(/^gyroUnfilt\[/)) { + return { + offset: 0, + power: 0.25, /* Make this 1.0 to scale linearly */ + inputRange: maxDegreesSecond(gyroScaleMargin * highResolutionScale), // Maximum grad/s + 20% + outputRange: 1.0 + }; + } else if (fieldName.match(/^axis.+\[/)) { + return { + offset: 0, + power: 0.3, + inputRange: 1000, // Was 400 ? + outputRange: 1.0 + }; + } else if (fieldName == "rcCommand[3]") { // Throttle + return { + offset: -1500 * highResolutionScale, + power: 1.0, + inputRange: 500 * highResolutionScale, + outputRange: 1.0 + }; + } else if (fieldName.match(/^rcCommand\[/)) { + return { + offset: 0, + power: 0.25, + inputRange: 500 * highResolutionScale * gyroScaleMargin, // +20% to let compare in the same scale with the rccommands + outputRange: 1.0 + }; + } else if (fieldName == "heading[2]") { + return { + offset: -Math.PI, + power: 1.0, + inputRange: Math.PI, + outputRange: 1.0 + }; + } else if (fieldName.match(/^heading\[/)) { + return { + offset: 0, + power: 1.0, + inputRange: Math.PI, + outputRange: 1.0 + }; + } else if (fieldName.match(/^sonar.*/)) { + return { + offset: -200, + power: 1.0, + inputRange: 200, + outputRange: 1.0 + }; + } else if (fieldName.match(/^rssi.*/)) { + return { + offset: -512, + power: 1.0, + inputRange: 512, + outputRange: 1.0 + }; + } else if (fieldName == 'GPS_ground_course') { + return { + offset: -1800, + power: 1.0, + inputRange: 1800, + outputRange: 1.0 + }; + } else if (fieldName == 'GPS_numSat') { + return { + offset: -20, + power: 1.0, + inputRange: 20, + outputRange: 1.0 + }; + } else if (fieldName == 'GPS_speed') { + return { + offset: 0, + power: 1.0, + inputRange: 1000, + outputRange: 1.0 + }; + } else if (fieldName.match(/^debug.*/) && sysConfig.debug_mode!=null) { + + var debugModeName = DEBUG_MODE[sysConfig.debug_mode]; + switch (debugModeName) { + case 'CYCLETIME': + switch (fieldName) { + case 'debug[1]': //CPU Load + return { + offset: -50, + power: 1, + inputRange: 50, + outputRange: 1.0 + }; + default: + return { + offset: -1000, // zero offset + power: 1.0, + inputRange: 1000, // 0-2000uS + outputRange: 1.0 + }; + } + case 'PIDLOOP': + return { + offset: -250, // zero offset + power: 1.0, + inputRange: 250, // 0-500uS + outputRange: 1.0 + }; + case 'GYRO': + case 'GYRO_FILTERED': + case 'GYRO_SCALED': + case 'DUAL_GYRO': + case 'DUAL_GYRO_COMBINED': + case 'DUAL_GYRO_DIFF': + case 'DUAL_GYRO_RAW': + case 'DUAL_GYRO_SCALED': + case 'NOTCH': + case 'AC_CORRECTION': + case 'AC_ERROR': + return { + offset: 0, + power: 0.25, + inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% + outputRange: 1.0 + }; + case 'ACCELEROMETER': + return { + offset: 0, + power: 0.5, + inputRange: sysConfig.acc_1G * 16.0, /* Reasonable typical maximum for acc */ + outputRange: 1.0 + }; + case 'MIXER': + return { + offset: -(sysConfig.motorOutput[1] + sysConfig.motorOutput[0]) / 2, + power: 1.0, + inputRange: (sysConfig.motorOutput[1] - sysConfig.motorOutput[0]) / 2, + outputRange: 1.0 + }; + case 'BATTERY': + switch (fieldName) { + case 'debug[0]': //Raw Value (0-4095) + return { + offset: -2048, + power: 1, + inputRange: 2048, + outputRange: 1.0 + }; + default: + return { + offset: -130, + power: 1.0, + inputRange: 130, // 0-26.0v + outputRange: 1.0 + }; + } + case 'RC_INTERPOLATION': + switch (fieldName) { + case 'debug[0]': // Roll RC Command + case 'debug[3]': // refresh period + return getCurveForMinMaxFieldsZeroOffset(fieldName); + default: + return getCurveForMinMaxFields(fieldName); + } + case 'RC_SMOOTHING': + switch (fieldName) { + case 'debug[0]': // raw RC command + return { + offset: 0, + power: 0.25, + inputRange: 500 * gyroScaleMargin, // +20% to let compare in the same scale with the rccommands + outputRange: 1.0 + }; + case 'debug[1]': // raw RC command derivative + case 'debug[2]': // smoothed RC command derivative + return getCurveForMinMaxFieldsZeroOffset('debug[1]', 'debug[2]'); + default: + return getCurveForMinMaxFields(fieldName); + } + case 'RC_SMOOTHING_RATE': + switch (fieldName) { + case 'debug[0]': // current frame rate [us] + case 'debug[2]': // average frame rate [us] + return getCurveForMinMaxFields('debug[0]', 'debug[2]'); + default: + return getCurveForMinMaxFields(fieldName); + } + case 'ANGLERATE': + return { + offset: 0, + power: 0.25, /* Make this 1.0 to scale linearly */ + inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% + outputRange: 1.0 + }; + case 'ALTITUDE': + switch (fieldName) { + case 'debug[0]': // GPS Trust + return { + offset: 0, + power: 1.0, + inputRange: 200, + outputRange: 1.0, + }; + case 'debug[1]': // Baro Alt + case 'debug[2]': // GPS Alt + return { + offset: 0, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; + case 'debug[3]': // Vario + return { + offset: 0, + power: 1.0, + inputRange: 500, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'FFT': + switch (fieldName) { + case 'debug[0]': // pre-dyn notch gyro [for gyro debug axis] + case 'debug[1]': // post-dyn notch gyro [for gyro debug axis] + case 'debug[2]': // pre-dyn notch gyro downsampled for FFT [for gyro debug axis] + return { + offset: 0, + power: 1.0, + inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% + outputRange: 1.0 + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'FFT_FREQ': + switch (fieldName) { + case 'debug[0]': // notch 1 center freq [for gyro debug axis] + case 'debug[1]': // notch 2 center freq [for gyro debug axis] + case 'debug[2]': // notch 3 center freq [for gyro debug axis] + return getCurveForMinMaxFields('debug[0]', 'debug[1]', 'debug[2]'); + case 'debug[3]': // pre-dyn notch gyro [for gyro debug axis] + return { + offset: 0, + power: 1.0, + inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% + outputRange: 1.0 + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'DYN_LPF': + switch (fieldName) { + case 'debug[1]': // Notch center + case 'debug[2]': // Lowpass Cutoff + return getCurveForMinMaxFields('debug[1]', 'debug[2]'); + case 'debug[0]': // gyro scaled [for selected axis] + case 'debug[3]': // pre-dyn notch gyro [for selected axis] + return { + offset: 0, + power: 0.25, + inputRange: maxDegreesSecond(gyroScaleMargin), // Maximum grad/s + 20% + outputRange: 1.0 + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'FFT_TIME': + return { + offset: 0, + power: 1.0, + inputRange: 100, + outputRange: 1.0 + }; + case 'ESC_SENSOR_RPM': + case 'DSHOT_RPM_TELEMETRY': + case 'RPM_FILTER': + return getCurveForMinMaxFields('debug[0]', 'debug[1]', 'debug[2]', 'debug[3]'); + case 'D_MIN': + switch (fieldName) { + case 'debug[0]': // roll gyro factor + case 'debug[1]': // roll setpoint Factor + return getCurveForMinMaxFields('debug[0]', 'debug[1]'); + case 'debug[2]': // roll actual D + case 'debug[3]': // pitch actual D + return getCurveForMinMaxFields('debug[2]', 'debug[3]'); + default: + return getCurveForMinMaxFields(fieldName); + } + case 'ITERM_RELAX': + switch (fieldName) { + case 'debug[2]': // roll I relaxed error + case 'debug[3]': // roll absolute control axis error + return getCurveForMinMaxFieldsZeroOffset(fieldName); + default: + return getCurveForMinMaxFields(fieldName); + } + case 'FF_INTERPOLATED': + switch (fieldName) { + case 'debug[0]': // setpoint Delta + case 'debug[1]': // AccelerationModified + case 'debug[2]': // Acceleration + return { + offset: 0, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + case 'debug[3]': // Clip or Count + return { + offset: -10, + power: 1.0, + inputRange: 10, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'FEEDFORWARD': // replaces FF_INTERPOLATED in 4.3 + switch (fieldName) { + case 'debug[0]': // in 4.3 is interpolated setpoint + return { + offset: 0, + power: 1.0, + inputRange: maxDegreesSecond(gyroScaleMargin), + outputRange: 1.0, + }; + case 'debug[1]': // feedforward delta element + case 'debug[2]': // feedforward boost element + return { + offset: 0, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + case 'debug[3]': // rcCommand delta + return { + offset: 0, + power: 1.0, + inputRange: 10000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'FF_LIMIT': + case 'FEEDFORWARD_LIMIT': + return { + offset: 0, + power: 1.0, + inputRange: 300, + outputRange: 1.0, + }; + case 'BARO': + switch (fieldName) { + case 'debug[0]': // Baro state 0-10 + return { + offset: 0, + power: 1.0, + inputRange: 20, + outputRange: 1.0, + }; + case 'debug[1]': // Baro Temp + case 'debug[2]': // Baro Raw + case 'debug[3]': // Baro smoothed + return { + offset: 0, + power: 1.0, + inputRange: 2000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GPS_RESCUE_THROTTLE_PID': + switch (fieldName) { + case 'debug[0]': // Throttle P uS added + case 'debug[1]': // Throttle D uS added + return { + offset: 0, + power: 1.0, + inputRange: 200, + outputRange: 1.0, + }; + case 'debug[2]': // Altitude + case 'debug[3]': // Target Altitude + return { + offset: 0, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'DYN_IDLE': + switch (fieldName) { + case 'debug[0]': // in 4.3 is dyn idle P + case 'debug[1]': // in 4.3 is dyn idle I + case 'debug[2]': // in 4.3 is dyn idle D + return { + offset: 0, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + case 'debug[3]': // in 4.3 and 4.2 is minRPS + return { + offset: -1000, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GYRO_SAMPLE': + switch (fieldName) { + case 'debug[0]': // Before downsampling + case 'debug[1]': // After downsampling + case 'debug[2]': // After RPM + case 'debug[3]': // After all but Dyn Notch + return { + offset: 0, + power: 0.25, /* Make this 1.0 to scale linearly */ + inputRange: maxDegreesSecond(gyroScaleMargin * highResolutionScale), // Maximum grad/s + 20% + outputRange: 1.0 + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'RX_TIMING': + switch (fieldName) { + case 'debug[0]': // CRC 0 to max int16_t + return { // start at bottom, scale up to 20ms + offset: -1000, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + // debug 1 is Count of Unknown Frames + // debug 2 and 3 not used + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GHST': + switch (fieldName) { + case 'debug[0]': // CRC 0 to max int16_t + case 'debug[1]': // Count of Unknown Frames + return getCurveForMinMaxFieldsZeroOffset(fieldName); + case 'debug[2]': // RSSI + return { + offset: 128, + power: 1.0, + inputRange: 128, + outputRange: 1.0, + }; + case 'debug[3]': // LQ percent + return { + offset: -50, + power: 1.0, + inputRange: 50, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'SCHEDULER_DETERMINISM': + switch (fieldName) { + case 'debug[0]': // Gyro task cycle us * 10 so 1250 = 125us + return { + offset: -5000, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; + case 'debug[1]': // ID of late task + case 'debug[2]': // task delay time 100us in middle + return { + offset: -1000, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + case 'debug[3]': // gyro skew 100 = 10us + return { + offset: 0, + power: 1.0, + inputRange: 500, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'TIMING_ACCURACY': + switch (fieldName) { + case 'debug[0]': // % CPU Busy + case 'debug[1]': // late tasks per second + return { + offset: -50, + power: 1.0, + inputRange: 50, + outputRange: 1.0, + }; + case 'debug[2]': // total delay in last second + return { + offset: -500, + power: 1.0, + inputRange: 500, + outputRange: 1.0, + }; + case 'debug[3]': // total tasks per second + return { + offset: -5000, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'RX_EXPRESSLRS_SPI': + switch (fieldName) { + case 'debug[2]': // Uplink LQ + return { + offset: -50, + power: 1.0, + inputRange: 50, + outputRange: 1.0, + }; + // debug 0 = Lost connection count + // debug 1 = RSSI + // debug 3 = SNR + default: + return getCurveForMinMaxFields(fieldName); + } + case 'RX_EXPRESSLRS_PHASELOCK': + switch (fieldName) { + case 'debug[2]': // Frequency offset in ticks + return getCurveForMinMaxFieldsZeroOffset(fieldName); + // debug 0 = Phase offset us + // debug 1 = Filtered phase offset us + // debug 3 = Phase shift in us + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GPS_RESCUE_VELOCITY': + switch (fieldName) { + case 'debug[0]': // Pitch P deg * 100 + case 'debug[1]': // Pitch D deg * 100 + return { + offset: 0, + power: 1.0, + inputRange: 2000, + outputRange: 1.0, + }; + case 'debug[2]': // Velocity in cm/s + case 'debug[3]': // Velocity to home in cm/s + return { + offset: 0, + power: 1.0, + inputRange: 500, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GPS_RESCUE_HEADING': + switch (fieldName) { + case 'debug[0]': // Groundspeed cm/s + return { + offset: 0, + power: 1.0, + inputRange: 10000, + outputRange: 1.0, + }; + case 'debug[1]': // GPS GroundCourse + case 'debug[2]': // Yaw attitude * 10 + case 'debug[3]': // Angle to home * 10 + case 'debug[4]': // magYaw * 10 + return { + offset: -1800, + power: 1.0, + inputRange: 1800, + outputRange: 1.0, + }; + case 'debug[5]': // magYaw * 10 + return { + offset: -10, + power: 1.0, + inputRange: 10, + outputRange: 1.0, + }; + case 'debug[6]': // roll angle *100 + return { + offset: -900, + power: 1.0, + inputRange: 900, + outputRange: 1.0, + }; + case 'debug[7]': // yaw rate deg/s + return { + offset: -100, + power: 1.0, + inputRange: 100, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'RTH': + switch (fieldName) { + case 'debug[0]': // Pitch angle, deg * 100 + return { + offset: 0, + power: 1.0, + inputRange: 4000, + outputRange: 1.0, + }; + case 'debug[1]': // Rescue Phase + case 'debug[2]': // Failure code + return { + offset: -10, + power: 1.0, + inputRange: 10, + outputRange: 1.0, + }; + case 'debug[3]': // Failure counters coded + return { + offset: -2000, + power: 1.0, + inputRange: 2000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GPS_RESCUE_TRACKING': + switch (fieldName) { + case 'debug[0]': // velocity to home cm/s + case 'debug[1]': // target velocity cm/s + return { + offset: 0, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + case 'debug[2]': // altitude m + case 'debug[3]': // Target altitude m + return { + offset: 0, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GPS_CONNECTION': + switch (fieldName) { + case 'debug[0]': // GPS flight model + case 'debug[1]': // Nav Data interval + return { + offset: 0, + power: 1.0, + inputRange: 200, + outputRange: 1.0, + }; + case 'debug[2]': // task interval + return { + offset: 0, + power: 1.0, + inputRange: 200, + outputRange: 1.0, + }; + case 'debug[3]': // Baud rate / resolved packet interval + case 'debug[4]': // State*100 + SubState + return getCurveForMinMaxFields(fieldName); + case 'debug[5]': // ExecuteTimeUs + return { + offset: 0, + power: 1.0, + inputRange: 100, + outputRange: 1.0, + }; + case 'debug[6]': // ackState + return { + offset: 0, + power: 1.0, + inputRange: 10, + outputRange: 1.0, + }; + case 'debug[7]': // Incoming buffer + return { + offset: 0, + power: 1.0, + inputRange: 100, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'GPS_DOP': + switch (fieldName) { + case 'debug[0]': // Number of Satellites (now this is in normal GPS data, maybe gpsTrust?) + case 'debug[1]': // pDOP + case 'debug[2]': // hDOP + case 'debug[3]': // vDOP + return { + offset: 0, + power: 1.0, + inputRange: 200, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'FAILSAFE': + switch (fieldName) { + case 'debug[0]': + case 'debug[1]': + case 'debug[2]': + case 'debug[3]': + return { + offset: 0, + power: 1.0, + inputRange: 200, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'ANGLE_MODE': + switch (fieldName) { + case 'debug[0]': // angle target + case 'debug[3]': // angle achieved + return { + offset: 0, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + case 'debug[1]': // angle error correction + case 'debug[2]': // angle feedforward + return { + offset: 0, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'DSHOT_TELEMETRY_COUNTS': + switch (fieldName) { + case 'debug[0]': + case 'debug[1]': + case 'debug[2]': + case 'debug[3]': + return { + offset: 0, + power: 1.0, + inputRange: 200, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'MAG_CALIB': + switch (fieldName) { + case 'debug[0]': // X + case 'debug[1]': // Y + case 'debug[2]': // Z + case 'debug[3]': // Field + return { + offset: 0, + power: 1.0, + inputRange: 2000, + outputRange: 1.0, + }; + case 'debug[4]': // X Cal + case 'debug[5]': // Y Cal + case 'debug[6]': // Z Cal + return { + offset: 0, + power: 1.0, + inputRange: 500, + outputRange: 1.0, + }; + case 'debug[7]': // Lambda + return { + offset: -2000, + power: 1.0, + inputRange: 2000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'MAG_TASK_RATE': + switch (fieldName) { + case 'debug[0]': // Task Rate + case 'debug[1]': // Data Rate + return { + offset: 0, + power: 1.0, + inputRange: 1000, + outputRange: 1.0, + }; + case 'debug[2]': // Data Interval + return { + offset: 0, + power: 1.0, + inputRange: 10000, + outputRange: 1.0, + }; + case 'debug[3]': // Execute Time + return { + offset: 0, + power: 1.0, + inputRange: 20, + outputRange: 1.0, + }; + case 'debug[4]': // Bus Busy Check + case 'debug[5]': // Read State Check + return { + offset: 0, + power: 1.0, + inputRange: 2, + outputRange: 1.0, + }; + case 'debug[6]': // Time since previous task uS + return { + offset: 0, + power: 1.0, + inputRange: 10000, + outputRange: 1.0, + }; + default: + return getCurveForMinMaxFields(fieldName); + } + case 'EZLANDING': + return { + offset: -5000, + power: 1.0, + inputRange: 5000, + outputRange: 1.0, + }; + } + } + // if not found above then + // Scale and center the field based on the whole-log observed ranges for that field + return getCurveForMinMaxFields(fieldName); + } catch(e) { + return { + offset: 0, + power: 1.0, + inputRange: 500, + outputRange: 1.0 + }; + } +}; + +/** + * Get an array of suggested graph configurations will be usable for the fields available in the given flightlog. + * + * Supply an array of strings `graphNames` to only fetch the graph with the given names. + */ +GraphConfig.getExampleGraphConfigs = function(flightLog, graphNames) { + var + result = [], + i, j; + + const EXAMPLE_GRAPHS = []; + + if (!flightLog.isFieldDisabled().MOTORS) { + EXAMPLE_GRAPHS.push({label: "Motors",fields: ["motor[all]", "servo[5]"]}); + EXAMPLE_GRAPHS.push({label: "Motors (Legacy)",fields: ["motorLegacy[all]", "servo[5]"]}); + } + if (!flightLog.isFieldDisabled().RPM) { + EXAMPLE_GRAPHS.push({label: "RPM",fields: ["eRPM[all]"]}); + } + if (!flightLog.isFieldDisabled().GYRO) { + EXAMPLE_GRAPHS.push({label: "Gyros",fields: ["gyroADC[all]"]}); + } + if (!flightLog.isFieldDisabled().GYROUNFILT) { + EXAMPLE_GRAPHS.push({label: "Unfiltered Gyros",fields: ["gyroUnfilt[all]"]}); + } + if (!flightLog.isFieldDisabled().SETPOINT) { + EXAMPLE_GRAPHS.push({label: "Setpoint",fields: ["rcCommands[all]"]}); + } + if (!flightLog.isFieldDisabled().RC_COMMANDS) { + EXAMPLE_GRAPHS.push({label: "RC Command",fields: ["rcCommand[all]"]}); + } + if (!flightLog.isFieldDisabled().PID) { + EXAMPLE_GRAPHS.push({label: "PIDs",fields: ["axisSum[all]"]}); + } + if (!(flightLog.isFieldDisabled().GYRO || flightLog.isFieldDisabled().PID)) { + EXAMPLE_GRAPHS.push({label: "PID Error",fields: ["axisError[all]"]}, + {label: "Gyro + PID roll",fields: ["axisP[0]", "axisI[0]", "axisD[0]", "axisF[0]", "gyroADC[0]"]}, + {label: "Gyro + PID pitch",fields: ["axisP[1]", "axisI[1]", "axisD[1]", "axisF[1]", "gyroADC[1]"]}, + {label: "Gyro + PID yaw",fields: ["axisP[2]", "axisI[2]", "axisD[2]", "axisF[2]", "gyroADC[2]"]}); + } + if (!flightLog.isFieldDisabled().ACC) { + EXAMPLE_GRAPHS.push({label: "Accelerometers",fields: ["accSmooth[all]"]}); + } + if (!flightLog.isFieldDisabled().HEADING) { + EXAMPLE_GRAPHS.push({label: "Heading",fields: ["heading[all]"]}); + } + if (!flightLog.isFieldDisabled().MAGNETOMETER) { + EXAMPLE_GRAPHS.push({label: "Compass",fields: ["magADC[all]"]}); + } + if (!flightLog.isFieldDisabled().DEBUG) { + EXAMPLE_GRAPHS.push({label: "Debug",fields: ["debug[all]"]}); + } + + if (!flightLog.isFieldDisabled().GPS) { + EXAMPLE_GRAPHS.push({label: "GPS",fields: ["GPS_numSat", "GPS_altitude", "GPS_speed", "GPS_ground_course", "GPS_coord[all]"]}); + } + + for (i = 0; i < EXAMPLE_GRAPHS.length; i++) { + var + srcGraph = EXAMPLE_GRAPHS[i], + destGraph = { + label: srcGraph.label, + fields: [], + height: srcGraph.height || 1 + }, + found; + + if (graphNames !== undefined) { + found = false; + for (j = 0; j < graphNames.length; j++) { + if (srcGraph.label == graphNames[j]) { + found = true; + break; + } + } + + if (!found) { + continue; + } + } + + for (j = 0; j < srcGraph.fields.length; j++) { + var + srcFieldName = srcGraph.fields[j], + destField = { + name: srcFieldName + }; + + destGraph.fields.push(destField); + } + + result.push(destGraph); + } + + return result; +}; diff --git a/js/graph_config_dialog.js b/src/graph_config_dialog.js similarity index 89% rename from js/graph_config_dialog.js rename to src/graph_config_dialog.js index 7a3c89bd..839690e9 100644 --- a/js/graph_config_dialog.js +++ b/src/graph_config_dialog.js @@ -1,47 +1,48 @@ -"use strict"; +import { GraphConfig } from "./graph_config"; +import { FlightLogFieldPresenter } from "./flightlog_fields_presenter"; -function GraphConfigurationDialog(dialog, onSave) { +export function GraphConfigurationDialog(dialog, onSave) { var // Some fields it doesn't make sense to graph BLACKLISTED_FIELDS = {time:true, loopIteration:true, 'setpoint[0]':true, 'setpoint[1]':true, 'setpoint[2]':true, 'setpoint[3]':true}, offeredFieldNames = [], exampleGraphs = [], activeFlightLog; - + function chooseColor(currentSelection) { - var selectColor = $(''); - for(var i=0; i') - .text(GraphConfig.PALETTE[i].name) - .attr('value', GraphConfig.PALETTE[i].color) - .css('color', GraphConfig.PALETTE[i].color); - if(currentSelection == GraphConfig.PALETTE[i].color) { - option.attr('selected', 'selected'); - selectColor.css('background', GraphConfig.PALETTE[i].color) - .css('color', GraphConfig.PALETTE[i].color); - } - selectColor.append(option); - } - - return selectColor; + var selectColor = $(''); + for(var i=0; i') + .text(GraphConfig.PALETTE[i].name) + .attr('value', GraphConfig.PALETTE[i].color) + .css('color', GraphConfig.PALETTE[i].color); + if(currentSelection == GraphConfig.PALETTE[i].color) { + option.attr('selected', 'selected'); + selectColor.css('background', GraphConfig.PALETTE[i].color) + .css('color', GraphConfig.PALETTE[i].color); + } + selectColor.append(option); + } + + return selectColor; } - + function chooseHeight(currentSelection) { var MAX_HEIGHT = 5; - var selectHeight = $(''); - for(var i=1; i<=MAX_HEIGHT; i++) { - var option = $('') - .text(i) - .attr('value', i); - if(currentSelection == i || (currentSelection==null && i==1)) { - option.attr('selected', 'selected'); - } - selectHeight.append(option); - } - - return selectHeight; + var selectHeight = $(''); + for(var i=1; i<=MAX_HEIGHT; i++) { + var option = $('') + .text(i) + .attr('value', i); + if(currentSelection == i || (currentSelection==null && i==1)) { + option.attr('selected', 'selected'); + } + selectHeight.append(option); + } + + return selectHeight; } // Show/Hide remove all button @@ -67,18 +68,18 @@ function GraphConfigurationDialog(dialog, onSave) { } function renderFieldOption(fieldName, selectedName) { - var + var option = $("") .text(FlightLogFieldPresenter.fieldNameToFriendly(fieldName, activeFlightLog.getSysConfig().debug_mode)) .attr("value", fieldName); - + if (fieldName == selectedName) { option.attr("selected", "selected"); } - + return option; } - + // Set the current smoothing options for a field function renderSmoothingOptions(elem, flightLog, field) { if(elem) { @@ -100,7 +101,7 @@ function GraphConfigurationDialog(dialog, onSave) { * initial selection. */ function renderField(flightLog, field, color) { - var + var elem = $( '' + '' @@ -116,11 +117,11 @@ function GraphConfigurationDialog(dialog, onSave) { select = $('select.form-control', elem), selectedFieldName = field ?field.name : false, i; - + for (i = 0; i < offeredFieldNames.length; i++) { select.append(renderFieldOption(offeredFieldNames[i], selectedFieldName)); } - + // Set the smoothing values renderSmoothingOptions(elem, flightLog, field); @@ -132,9 +133,9 @@ function GraphConfigurationDialog(dialog, onSave) { //Populate the Color Picker $('select.color-picker', elem).replaceWith(chooseColor(color)); - - // Ade event when selection changed to retreive the current smoothing settings. + + // Add event when selection changed to retrieve the current smoothing settings. $('select.form-control', elem).change( function() { var selectedField = { name: $('select.form-control option:selected', elem).val() @@ -147,13 +148,13 @@ function GraphConfigurationDialog(dialog, onSave) { $(this).css('background', $('select.color-picker option:selected', elem).val()) .css('color', $('select.color-picker option:selected', elem).val()); }); - + return elem; } - + function renderGraph(flightLog, index, graph) { - var + var graphElem = $( '
  • ' + '
    ' @@ -201,9 +202,9 @@ function GraphConfigurationDialog(dialog, onSave) { + '
  • ' ), fieldList = $(".config-graph-field-list", graphElem); - + $("input", graphElem).val(graph.label); - + var fieldCount = graph.fields.length; // "Add field" button @@ -221,23 +222,23 @@ function GraphConfigurationDialog(dialog, onSave) { }); //Populate the Height seletor - $('select.graph-height', graphElem).replaceWith(chooseHeight(graph.height?(graph.height):1)); + $('select.graph-height', graphElem).replaceWith(chooseHeight(graph.height?(graph.height):1)); // Add Field List for (var i = 0; i < graph.fields.length; i++) { - var + var field = graph.fields[i], fieldElem = renderField(flightLog, field, field.color?(field.color):(GraphConfig.PALETTE[i].color)); - + fieldList.append(fieldElem); } - + fieldList.on('click', 'button', function(e) { var parentGraph = $(this).parents('.config-graph'); - + $(this).parents('.config-graph-field').remove(); - + // Remove the graph upon removal of the last field if ($(".config-graph-field", parentGraph).length === 0) { parentGraph.remove(); @@ -248,73 +249,73 @@ function GraphConfigurationDialog(dialog, onSave) { }); updateRemoveAllButton(); - + return graphElem; } - + function renderGraphs(flightLog, graphs) { var graphList = $(".config-graphs-list", dialog); - + graphList.empty(); - + for (var i = 0; i < graphs.length; i++) { graphList.append(renderGraph(flightLog, i, graphs[i])); } } - + function populateExampleGraphs(flightLog, menu) { var i; - + menu.empty(); - + exampleGraphs = GraphConfig.getExampleGraphConfigs(flightLog); - + exampleGraphs.unshift({ label: "Custom graph", fields: [{name:""}], dividerAfter: true }); - + for (i = 0; i < exampleGraphs.length; i++) { - var + var graph = exampleGraphs[i], li = $('
  • '); - + $('a', li) .text(graph.label) .data('graphIndex', i); - + menu.append(li); - + if (graph.dividerAfter) { menu.append('
  • '); } } } - + function convertUIToGraphConfig() { - var + var graphs = [], graph, field; - + $(".config-graph", dialog).each(function() { graph = { fields: [], height: 1 }; - + graph.label = $("input[type='text']", this).val(); graph.height = parseInt($('select.graph-height option:selected', this).val()); - + $(".config-graph-field", this).each(function() { field = { name: $("select", this).val(), smoothing: parseInt($("input[name=smoothing]", this).val())*100, // Value 0-100% = 0-10000uS (higher values are more smooth, 30% is typical) curve: { - power: parseInt($("input[name=power]", this).val())/100.0, // Value 0-100% = 0-1.0 (lower values exagerate center values - expo) + power: parseInt($("input[name=power]", this).val())/100.0, // Value 0-100% = 0-1.0 (lower values exaggerate center values - expo) outputRange: parseInt($("input[name=scale]", this).val())/100.0 // Value 0-100% = 0-1.0 (higher values > 100% zoom in graph vertically) }, default: { // These are used to restore configuration if using mousewheel adjustments @@ -326,7 +327,7 @@ function GraphConfigurationDialog(dialog, onSave) { lineWidth: parseInt($("input[name=linewidth]", this).val()), grid: $('input[name=grid]', this).is(':checked'), }; - + if (field.name.length > 0) { graph.fields.push(field); } @@ -334,7 +335,7 @@ function GraphConfigurationDialog(dialog, onSave) { graphs.push(graph); }); - + return graphs; } @@ -345,64 +346,64 @@ function GraphConfigurationDialog(dialog, onSave) { lastRoot = null, fieldNames = flightLog.getMainFieldNames(), fieldsSeen = {}; - + offeredFieldNames = []; - + for (i = 0; i < fieldNames.length; i++) { // For fields with multiple bracketed x[0], x[1] versions, add an "[all]" option - var + var fieldName = fieldNames[i], matches = fieldName.match(/^(.+)\[[0-9]+\]$/); - + if (BLACKLISTED_FIELDS[fieldName]) continue; - + if (matches) { if (matches[1] != lastRoot) { lastRoot = matches[1]; - + offeredFieldNames.push(lastRoot + "[all]"); fieldsSeen[lastRoot + "[all]"] = true; } } else { lastRoot = null; } - + offeredFieldNames.push(fieldName); fieldsSeen[fieldName] = true; } - - /* + + /* * If the graph config has any fields in it that we don't have available in our flight log, add them to * the GUI anyway. (This way we can build a config when using a tricopter (which includes a tail servo) and * keep that tail servo in the config when we're viewing a quadcopter). */ for (i = 0; i < config.length; i++) { - var + var graph = config[i]; - + for (j = 0; j < graph.fields.length; j++) { - var + var field = graph.fields[j]; - + if (!fieldsSeen[field.name]) { offeredFieldNames.push(field.name); } } } } - + this.show = function(flightLog, config) { dialog.modal('show'); - + activeFlightLog = flightLog; - + buildOfferedFieldNamesList(flightLog, config); populateExampleGraphs(flightLog, exampleGraphsMenu); renderGraphs(flightLog, config); }; - + $(".graph-configuration-dialog-save").click(function() { onSave(convertUIToGraphConfig()); }); @@ -424,16 +425,16 @@ function GraphConfigurationDialog(dialog, onSave) { exampleGraphsButton.dropdown(); exampleGraphsMenu.on("click", "a", function(e) { - var + var graph = exampleGraphs[$(this).data("graphIndex")], graphElem = renderGraph(activeFlightLog, $(".config-graph", dialog).length, graph); - + $(configGraphsList, dialog).append(graphElem); updateRemoveAllButton(); - + // Dismiss the dropdown button exampleGraphsButton.dropdown("toggle"); - + e.preventDefault(); }); diff --git a/js/graph_legend.js b/src/graph_legend.js similarity index 82% rename from js/graph_legend.js rename to src/graph_legend.js index e2f49ac7..7f09b0bf 100644 --- a/js/graph_legend.js +++ b/src/graph_legend.js @@ -1,6 +1,6 @@ -"use strict"; +import { FlightLogFieldPresenter } from "./flightlog_fields_presenter"; -function GraphLegend(targetElem, config, onVisibilityChange, onNewSelectionChange, onHighlightChange, onZoomGraph, onExpandGraph, onNewGraphConfig) { +export function GraphLegend(targetElem, config, onVisibilityChange, onNewSelectionChange, onHighlightChange, onZoomGraph, onExpandGraph, onNewGraphConfig) { var that = this; @@ -8,7 +8,7 @@ function GraphLegend(targetElem, config, onVisibilityChange, onNewSelectionChang var graphs = config.getGraphs(), i, j; - + targetElem.empty(); for (i = 0; i < graphs.length; i++) { @@ -27,10 +27,11 @@ function GraphLegend(targetElem, config, onVisibilityChange, onNewSelectionChang li = $('
  • '), nameElem = $(''), valueElem = $(''), - settingsElem = $('
    '), - analyseElem = $(''); + settingsElem = $('
    '), + visibilityIcon = config.isGraphFieldHidden(i, j) ? "glyphicon-eye-close" : "glyphicon-eye-open", + visibilityElem = $(''); li.append(nameElem); - li.append(analyseElem); + li.append(visibilityElem); li.append(valueElem); li.append(settingsElem); @@ -63,7 +64,7 @@ function GraphLegend(targetElem, config, onVisibilityChange, onNewSelectionChang }); // Add a trigger on legend; select the analyser graph/field to plot - $('.graph-legend-field').on('click', function(e) { + $('.graph-legend-field-name, .graph-legend-field-settings, .graph-legend-field-value').on('click', function (e) { if(e.which!=1) return; // only accept left mouse clicks @@ -134,7 +135,31 @@ function GraphLegend(targetElem, config, onVisibilityChange, onNewSelectionChang }); // on first show, hide the analyser button - if(!config.selectedFieldName) $('.hide-analyser-window').hide(); + if (!config.selectedFieldName) $('.hide-analyser-window').hide(); + + // Add a trigger on legend; select the analyser graph/field to plot + $('.graph-legend-field-visibility').on('click', function (e) { + if (e.which != 1) { + return; // only accept left mouse clicks + } + + const $this = $(this), + graphIndex = $this.attr('graph'), + fieldIndex = $this.attr('field'); + + config.toggleGraphField(graphIndex, fieldIndex); + onHighlightChange(); + + if (config.isGraphFieldHidden(graphIndex, fieldIndex)) { + $this.removeClass("glyphicon-eye-open"); + $this.addClass("glyphicon-eye-close"); + } else { + $this.addClass("glyphicon-eye-open"); + $this.removeClass("glyphicon-eye-close"); + } + + e.preventDefault(); + }); } this.updateValues = function (flightLog, frame) { diff --git a/src/graph_map.js b/src/graph_map.js new file mode 100644 index 00000000..20907b74 --- /dev/null +++ b/src/graph_map.js @@ -0,0 +1,415 @@ +export function MapGrapher() { + let myMap, + currentLogStartDateTime, + currentTime, + craftPosition, + groundCourse, + homePosition, + craftMarker, + homeMarker, + trailLayers = new Map(), + previousLogIndex, + latIndexAtFrame, + lngIndexAtFrame, + altitudeIndexAtFrame, + groundCourseIndexAtFrame, + flightLog; + + const coordinateDivider = 10000000; + const altitudeDivider = 10; + const grounCourseDivider = 10; + + const mapOptions = { + center: [0, 0], + zoom: 1, + }; + + const craftIcon = L.icon({ + iconUrl: "../images/markers/craft.png", + iconSize: [30, 30], + iconAnchor: [15, 15], + className: "icon", + }); + + const homeIcon = L.icon({ + iconUrl: "../images/markers/home.png", + iconSize: [40, 40], + iconAnchor: [20, 35], + className: "icon", + }); + + const polylineOptions = { + color: "#2db0e3", + opacity: 0.8, + smoothFactor: 1, + }; + + // flight trail colors + const colorTrailGradient = [ + { color: "#00ffe0bf" }, + { color: "#00ff8cbf" }, + { color: "#00ff02bf" }, + { color: "#75ff00bf" }, + { color: "#e5ff00bf" }, + { color: "#ffb100bf" }, + { color: "#ff4c00bf" }, + { color: "#ff1414" }, + ]; + + // debug circles can be used to aligh icons at the correct coordinates + const debugCircle = false; + const debugCircleOptions = { + color: "red", + fillColor: "red", + fillOpacity: 0.8, + radius: 1, + }; + + this.initialize = function () { + if (myMap) { + return; + } + + myMap = L.map("mapContainer", mapOptions); + + L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { + maxZoom: 19, + minZoom: 1, + attribution: + '© OpenStreetMap', + }).addTo(myMap); + }; + + this.reset = function () { + if (!myMap) { + return; + } + this.clearMap(previousLogIndex); + previousLogIndex = null; + currentTime = null; + craftPosition = null; + groundCourse = null; + homePosition = null; + craftMarker = null; + homeMarker = null; + trailLayers = new Map(); + previousLogIndex = null; + latIndexAtFrame = null; + lngIndexAtFrame = null; + altitudeIndexAtFrame = null; + groundCourseIndexAtFrame = null; + myMap.setView(mapOptions.center, mapOptions.zoom); + }; + + this.setFlightLog = function (newFlightLog) { + flightLog = newFlightLog; + + const newLogStartDateTime = flightLog.getSysConfig()["Log start datetime"]; + if (currentLogStartDateTime != newLogStartDateTime) { + this.reset(); + currentLogStartDateTime = newLogStartDateTime; + } + + const logIndex = flightLog.getLogIndex(); + + // if this log is already proccesed its skipped + if (trailLayers.has(logIndex)) { + return; + } + + this.setFlightLogIndexs(); + let { latlngs, maxAlt, minAlt } = this.getPolylinesData(); + + const hasGpsData = latlngs.length > 0; + + if (hasGpsData) { + const polyline = L.polyline(latlngs, polylineOptions); + + const polylineC = this.createAltitudeColoredPolyline( + latlngs, + maxAlt, + minAlt + ); + + trailLayers.set(logIndex, { polyline, polylineC }); + + homePosition = this.getHomeCoordinatesFromFlightLog(flightLog); + } else { + console.debug("FlightLog has no gps data."); + } + + $("#mapContainer").toggleClass("no-gps-data", !hasGpsData); + }; + + this.setFlightLogIndexs = function () { + latIndexAtFrame = flightLog.getMainFieldIndexByName("GPS_coord[0]"); + lngIndexAtFrame = flightLog.getMainFieldIndexByName("GPS_coord[1]"); + altitudeIndexAtFrame = flightLog.getMainFieldIndexByName("GPS_altitude"); + groundCourseIndexAtFrame = + flightLog.getMainFieldIndexByName("GPS_ground_course"); + }; + + this.getPolylinesData = function () { + let latlngs = []; + let maxAlt = Number.MIN_VALUE; + let minAlt = Number.MAX_VALUE; + + const chunks = flightLog.getChunksInTimeRange( + flightLog.getMinTime(), + flightLog.getMaxTime() + ); + + for (const chunk of chunks) { + for (const fi in chunk.frames) { + const frame = chunk.frames[fi]; + const coordinates = this.getCoordinatesFromFrame( + frame, + latIndexAtFrame, + lngIndexAtFrame, + altitudeIndexAtFrame + ); + + // if there are no coordinates the frame is skipped + if (!coordinates) { + continue; + } + + // Altitude max and min values can be obtained from the stats but fixing the index at 4 doesn't seem safe + // const maxAlt = flightLog.getStats().frame.G.field[4].max / altitudeDivider; + // const minAlt = flightLog.getStats().frame.G.field[4].min / altitudeDivider; + maxAlt = coordinates.alt > maxAlt ? coordinates.alt : maxAlt; + minAlt = coordinates.alt < minAlt ? coordinates.alt : minAlt; + + // 1/4 of the dots is enough to draw the line + if (fi % 4 == 0) { + latlngs.push(coordinates); + } + } + } + return { latlngs, maxAlt, minAlt }; + }; + + this.createAltitudeColoredPolyline = function (latlngs, maxAlt, minAlt) { + const divider = colorTrailGradient.length - 1; + + const delta = maxAlt - minAlt; + + const thresholdIncrement = delta / divider; + + let altThresholds = []; + let threshold = minAlt; + for (let i = 0; i < divider; i++) { + //amount of colors - min and max that are set + threshold += thresholdIncrement; + altThresholds.push(threshold); + } + + return L.multiOptionsPolyline(latlngs, { + multiOptions: { + optionIdxFn: function (latLng) { + for (let i = 0; i < altThresholds.length; i++) { + if (latLng.alt <= altThresholds[i]) { + return i; + } + } + return altThresholds.length; + }, + options: colorTrailGradient, + }, + weight: 3, + lineCap: "butt", + opacity: 1, + smoothFactor: 1, + }); + }; + + this.updateCurrentPosition = function () { + try { + const frame = flightLog.getCurrentFrameAtTime(currentTime); + craftPosition = this.getCoordinatesFromFrame( + frame.current, + latIndexAtFrame, + lngIndexAtFrame, + altitudeIndexAtFrame + ); + groundCourse = this.getGroundCourseFromFrame( + frame.current, + groundCourseIndexAtFrame + ); + } catch (e) {} + }; + + this.setUserSettings = function (newUserSettings) { + globalThis.userSettings = newUserSettings; + }; + + this.redrawAll = function () { + if (trailLayers.size <= 0 || !myMap) { + return; + } + + this.redrawFlightTrail(); + this.redrawHomeMarker(); + this.redrawCraftMarker(); + }; + + this.redrawFlightTrail = function () { + // If flightLog has changed redraw flight trail + const currentLogIndex = flightLog.getLogIndex(); + if (previousLogIndex != currentLogIndex) { + this.clearMap(previousLogIndex); + if (trailLayers.has(currentLogIndex)) { + const polyline = userSettings.mapTrailAltitudeColored + ? trailLayers.get(currentLogIndex).polylineC + : trailLayers.get(currentLogIndex).polyline; + polyline.addTo(myMap); + myMap.fitBounds(polyline.getBounds()); + } + + previousLogIndex = currentLogIndex; + } + }; + + this.redrawHomeMarker = function () { + if (homePosition) { + if (homeMarker) { + homeMarker.icon.setLatLng(homePosition).addTo(myMap); + + // debug circle + if (debugCircle) { + homeMarker.circle.setLatLng(homePosition).addTo(myMap); + } + } else { + homeMarker = {}; + + homeMarker.icon = L.marker(homePosition, { + icon: homeIcon, + }).addTo(myMap); + + // debug circle + if (debugCircle) { + homeMarker.circle = L.circle(homePosition, debugCircleOptions).addTo( + myMap + ); + } + } + } + }; + + this.redrawCraftMarker = function () { + if (craftPosition) { + if (craftMarker) { + craftMarker.icon.setLatLng(craftPosition); + craftMarker.icon.setRotationAngle(groundCourse).addTo(myMap); + // debug circle + if (debugCircle) { + homeMarker.circle.setLatLng(craftPosition).addTo(myMap); + } + } else { + craftMarker = {}; + craftMarker.icon = L.rotatedMarker(craftPosition, { + icon: craftIcon, + rotationAngle: groundCourse, + rotationOrigin: "center center", + }).addTo(myMap); + + // debug circle + if (debugCircle) { + craftMarker.circle = L.circle( + craftPosition, + debugCircleOptions + ).addTo(myMap); + } + } + } + }; + + this.clearMap = function (trailIndex) { + this.clearMapFlightTrails(trailIndex); + this.clearMapMarkers(); + }; + + this.clearMapFlightTrails = function (trailIndex) { + if (trailLayers.has(trailIndex)) { + const p = trailLayers.get(trailIndex).polyline; + const pc = trailLayers.get(trailIndex).polylineC; + if (p) { + myMap.removeLayer(p); + } + if (pc) { + myMap.removeLayer(pc); + } + } + }; + + this.clearMapMarkers = function () { + if (homeMarker) { + if (myMap.hasLayer(homeMarker.icon)) { + myMap.removeLayer(homeMarker.icon); + } + if (debugCircle && myMap.hasLayer(homeMarker.circle)) { + myMap.removeLayer(homeMarker.circle); + } + } + if (craftMarker) { + if (myMap.hasLayer(craftMarker.icon)) { + myMap.removeLayer(craftMarker.icon); + } + if (debugCircle && myMap.hasLayer(craftMarker.circle)) { + myMap.removeLayer(craftMarker.circle); + } + } + }; + + this.resize = function (width, height) { + if (!userSettings) { + return; + } + const containerstyle = { + height: (height * parseInt(userSettings.map.size)) / 100.0, + width: (width * parseInt(userSettings.map.size)) / 100.0, + left: (width * parseInt(userSettings.map.left)) / 100.0, + top: (height * parseInt(userSettings.map.top)) / 100.0, + }; + $("#mapContainer").css(containerstyle); + }; + + this.getCoordinatesFromFrame = function ( + frame, + latIndex, + lngIndex, + altitudeIndex + ) { + const lat = frame[latIndex]; + const lng = frame[lngIndex]; + const alt = frame[altitudeIndex]; + + return this.isNumber(lat) && this.isNumber(lng) + ? L.latLng( + lat / coordinateDivider, + lng / coordinateDivider, + alt / altitudeDivider + ) + : null; + }; + + this.isNumber = function (n) { + return typeof n === "number" && !isNaN(n); + }; + + this.getGroundCourseFromFrame = function (frame, groundCourseIndex) { + const gc = frame[groundCourseIndex]; + return typeof gc == "number" ? gc / grounCourseDivider : 0; + }; + + this.getHomeCoordinatesFromFlightLog = function (flightLog) { + const home = flightLog.getStats().frame.H.field; + return [home[0].min / coordinateDivider, home[1].min / coordinateDivider]; + }; + + this.setCurrentTime = function (newTime) { + currentTime = newTime; + this.updateCurrentPosition(); + this.redrawAll(); + }; +} diff --git a/js/graph_spectrum.js b/src/graph_spectrum.js similarity index 90% rename from js/graph_spectrum.js rename to src/graph_spectrum.js index 50363103..ffa8a1db 100644 --- a/js/graph_spectrum.js +++ b/src/graph_spectrum.js @@ -1,6 +1,9 @@ -"use strict"; +import { debounce } from "throttle-debounce"; +import { GraphSpectrumCalc } from "./graph_spectrum_calc"; +import { GraphSpectrumPlot, SPECTRUM_TYPE, SPECTRUM_OVERDRAW_TYPE } from "./graph_spectrum_plot"; +import { PrefStorage } from "./pref_storage"; -function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { +export function FlightLogAnalyser(flightLog, canvas, analyserCanvas) { const ANALYSER_LARGE_LEFT_MARGIN = 10, @@ -8,7 +11,7 @@ const ANALYSER_LARGE_HEIGHT_MARGIN = 20, ANALYSER_LARGE_WIDTH_MARGIN = 20; -var +var that = this, analyserZoomX = 1.0, /* 100% */ @@ -31,9 +34,9 @@ var var isFullscreen = false; var sysConfig = flightLog.getSysConfig(); - GraphSpectrumCalc.initialize(flightLog, sysConfig); + const logRateInfo = GraphSpectrumCalc.initialize(flightLog, sysConfig); GraphSpectrumPlot.initialize(analyserCanvas, sysConfig); - + GraphSpectrumPlot.setLogRateWarningInfo(logRateInfo); var analyserZoomXElem = $("#analyserZoomX"); var analyserZoomYElem = $("#analyserZoomY"); @@ -75,7 +78,7 @@ var } }; - this.resize = function() { + this.resize = function() { var newSize = getSize(); @@ -112,6 +115,10 @@ var fftData = GraphSpectrumCalc.dataLoadFrequencyVsThrottle(); break; + case SPECTRUM_TYPE.FREQ_VS_RPM: + fftData = GraphSpectrumCalc.dataLoadFrequencyVsRpm(); + break; + case SPECTRUM_TYPE.PIDERROR_VS_SETPOINT: fftData = GraphSpectrumCalc.dataLoadPidErrorVsSetpoint(); break; @@ -121,13 +128,12 @@ var fftData = GraphSpectrumCalc.dataLoadFrequency(); break; } - }; /* This function is called from the canvas drawing routines within grapher.js - It is only used to record the current curve positions, collect the data and draw the + It is only used to record the current curve positions, collect the data and draw the analyser on screen*/ - this.plotSpectrum = function (fieldIndex, curve, fieldName) { + this.plotSpectrum = function (fieldIndex, curve, fieldName) { // Store the data pointers dataBuffer = { fieldIndex: fieldIndex, @@ -138,7 +144,7 @@ var // Detect change of selected field.... reload and redraw required. if ((fftData == null) || (fieldIndex != fftData.fieldIndex) || dataReload) { dataReload = false; - dataLoad(); + dataLoad(); GraphSpectrumPlot.setData(fftData, userSettings.spectrumType); } @@ -168,7 +174,7 @@ var /* add zoom controls */ const DEFAULT_ZOOM = 100; - analyserZoomXElem.on('input', $.debounce(100, function() { + analyserZoomXElem.on('input', debounce(100, function() { analyserZoomX = (analyserZoomXElem.val() / 100); GraphSpectrumPlot.setZoom(analyserZoomX, analyserZoomY); that.refresh(); @@ -176,7 +182,7 @@ var $(this).val(DEFAULT_ZOOM).trigger("input"); }).val(DEFAULT_ZOOM);; - analyserZoomYElem.on('input', $.debounce(100, function() { + analyserZoomYElem.on('input', debounce(100, function() { analyserZoomY = 1 / (analyserZoomYElem.val() / 100); GraphSpectrumPlot.setZoom(analyserZoomX, analyserZoomY); that.refresh(); @@ -227,7 +233,7 @@ var // track frequency under mouse var lastMouseX = 0, lastMouseY = 0; - + function trackFrequency(e, analyser) { if(e.shiftKey) { @@ -253,6 +259,7 @@ var function saveOneUserSetting(name, value) { prefs.get('userSettings', function(data) { + data = data || {}; data[name] = value; prefs.set('userSettings', data); }); diff --git a/js/graph_spectrum_calc.js b/src/graph_spectrum_calc.js similarity index 61% rename from js/graph_spectrum_calc.js rename to src/graph_spectrum_calc.js index a784f35c..01b16d31 100644 --- a/js/graph_spectrum_calc.js +++ b/src/graph_spectrum_calc.js @@ -1,13 +1,16 @@ -"use strict"; +import { FlightLogFieldPresenter } from "./flightlog_fields_presenter"; const - FIELD_THROTTLE_NAME = 'rcCommands[3]', + FIELD_THROTTLE_NAME = ['rcCommands[3]'], + FIELD_RPM_NAMES = ['eRPM[0]', 'eRPM[1]', 'eRPM[2]', 'eRPM[3]', 'eRPM[4]', 'eRPM[5]', 'eRPM[6]', 'eRPM[7]'], FREQ_VS_THR_CHUNK_TIME_MS = 300, FREQ_VS_THR_WINDOW_DIVISOR = 6, MAX_ANALYSER_LENGTH = 300 * 1000 * 1000, // 5min - THROTTLE_VALUES = 100; + NUM_VS_BINS = 100, + WARNING_RATE_DIFFERENCE = 0.05, + MAX_RPM_VALUE = 10000; -var GraphSpectrumCalc = GraphSpectrumCalc || { +export const GraphSpectrumCalc = { _analyserTimeRange : { in: 0, out: MAX_ANALYSER_LENGTH @@ -24,7 +27,7 @@ var GraphSpectrumCalc = GraphSpectrumCalc || { GraphSpectrumCalc.initialize = function(flightLog, sysConfig) { - this._flightLog = flightLog; + this._flightLog = flightLog; this._sysConfig = sysConfig; var gyroRate = (1000000 / this._sysConfig['looptime']).toFixed(0); @@ -32,6 +35,33 @@ GraphSpectrumCalc.initialize = function(flightLog, sysConfig) { if (this._sysConfig.pid_process_denom != null) { this._blackBoxRate = this._blackBoxRate / this._sysConfig.pid_process_denom; } + this._BetaflightRate = this._blackBoxRate; + + let minTime = this._flightLog.getMinTime(), + maxTime = this._flightLog.getMaxTime(); + let timeRange = maxTime - minTime; + if (timeRange > MAX_ANALYSER_LENGTH) { + maxTime = minTime + MAX_ANALYSER_LENGTH; + timeRange = MAX_ANALYSER_LENGTH; + } + const allChunks = this._flightLog.getChunksInTimeRange(minTime, maxTime); + const length = allChunks.reduce((acc, chunk) => acc + chunk.frames.length, 0); + this._actualeRate = 1e6 * length / timeRange; + + if (Math.abs(this._BetaflightRate - this._actualeRate) / this._actualeRate > WARNING_RATE_DIFFERENCE) + this._blackBoxRate = Math.round(this._actualeRate); + + if (this._BetaflightRate !== this._blackBoxRate) { + $('.actual-lograte').text(this._actualeRate.toFixed(0) + "/" + this._BetaflightRate.toFixed(0)+"Hz"); + return { + actualRate: this._actualeRate, + betaflightRate: this._BetaflightRate, + }; + } else { + $('.actual-lograte').text(""); + } + + return undefined; }; GraphSpectrumCalc.setInTime = function(time) { @@ -50,6 +80,7 @@ GraphSpectrumCalc.setOutTime = function(time) { GraphSpectrumCalc.setDataBuffer = function(dataBuffer) { this._dataBuffer = dataBuffer; + return undefined; }; GraphSpectrumCalc.dataLoadFrequency = function() { @@ -69,25 +100,28 @@ GraphSpectrumCalc.dataLoadFrequency = function() { return fftData; }; -GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() { - var flightSamples = this._getFlightSamplesFreqVsThrottle(); +GraphSpectrumCalc._dataLoadFrequencyVsX = function(vsFieldNames, minValue = Infinity, maxValue = -Infinity) { - // We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle - // for each chunk. We use a moving window to get more chunks available. + let flightSamples = this._getFlightSamplesFreqVsX(vsFieldNames, minValue, maxValue); + + // We divide it into FREQ_VS_THR_CHUNK_TIME_MS FFT chunks, we calculate the average throttle + // for each chunk. We use a moving window to get more chunks available. var fftChunkLength = this._blackBoxRate * FREQ_VS_THR_CHUNK_TIME_MS / 1000; var fftChunkWindow = Math.round(fftChunkLength / FREQ_VS_THR_WINDOW_DIVISOR); - var maxNoiseThrottle = 0; // Stores the max noise produced - var matrixFftOutput = new Array(THROTTLE_VALUES); // One for each throttle value, without decimal part - var numberSamplesThrottle = new Uint32Array(THROTTLE_VALUES); // Number of samples in each throttle value, used to average them later. + let maxNoise = 0; // Stores the maximum amplitude of the fft over all chunks + // Matrix where each row represents a bin of vs values, and the columns are amplitudes at frequencies + let matrixFftOutput = new Array(NUM_VS_BINS).fill(null).map(() => new Float64Array(fftChunkLength * 2)); + + let numberSamples = new Uint32Array(NUM_VS_BINS); // Number of samples in each vs value, used to average them later. var fft = new FFT.complex(fftChunkLength, false); for (var fftChunkIndex = 0; fftChunkIndex + fftChunkLength < flightSamples.samples.length; fftChunkIndex += fftChunkWindow) { - - var fftInput = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength); - var fftOutput = new Float64Array(fftChunkLength * 2); - + + let fftInput = flightSamples.samples.slice(fftChunkIndex, fftChunkIndex + fftChunkLength); + let fftOutput = new Float64Array(fftChunkLength * 2); + // Hanning window applied to input data if(userSettings.analyserHanning) { this._hanningWindow(fftInput, fftChunkLength); @@ -98,44 +132,43 @@ GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() { fftOutput = fftOutput.slice(0, fftChunkLength); // Use only abs values - for (var i = 0; i < fftChunkLength; i++) { + for (let i = 0; i < fftChunkLength; i++) { fftOutput[i] = Math.abs(fftOutput[i]); - if (fftOutput[i] > maxNoiseThrottle) { - maxNoiseThrottle = fftOutput[i]; - } + maxNoise = Math.max(fftOutput[i], maxNoise); } - // Calculate average throttle - var avgThrottle = 0; - for (var indexThrottle = fftChunkIndex; indexThrottle < fftChunkIndex + fftChunkLength; indexThrottle++) { - avgThrottle += flightSamples.throttle[indexThrottle]; - } - // Average throttle, removing the decimal part - avgThrottle = Math.round(avgThrottle / 10 / fftChunkLength); - - numberSamplesThrottle[avgThrottle]++; - if (!matrixFftOutput[avgThrottle]) { - matrixFftOutput[avgThrottle] = fftOutput; - } else { - matrixFftOutput[avgThrottle] = matrixFftOutput[avgThrottle].map(function (num, idx) { - return num + fftOutput[idx]; - }); + // calculate a bin index and put the fft value in that bin for each field (e.g. eRPM[0], eRPM[1]..) sepparately + for (const vsValueArray of flightSamples.vsValues) { + // Calculate average of the VS values in the chunk + let sumVsValues = 0; + for (let indexVs = fftChunkIndex; indexVs < fftChunkIndex + fftChunkLength; indexVs++) { + sumVsValues += vsValueArray[indexVs]; + } + // Translate the average vs value to a bin index + const avgVsValue = sumVsValues / fftChunkLength; + let vsBinIndex = Math.floor(NUM_VS_BINS * (avgVsValue - flightSamples.minValue) / (flightSamples.maxValue - flightSamples.minValue)); + // ensure that avgVsValue == flightSamples.maxValue does not result in an out of bounds access + if (vsBinIndex === NUM_VS_BINS) { vsBinIndex = NUM_VS_BINS - 1; } + numberSamples[vsBinIndex]++; + + // add the output from the fft to the row given by the vs value bin index + for (let i = 0; i < fftOutput.length; i++) { + matrixFftOutput[vsBinIndex][i] += fftOutput[i]; + } } } - // Divide by the number of samples - for (var i = 0; i < THROTTLE_VALUES; i++) { - if (numberSamplesThrottle[i] > 1) { + // Divide the values from the fft in each row (vs value bin) by the number of samples in the bin + for (let i = 0; i < NUM_VS_BINS; i++) { + if (numberSamples[i] > 1) { for (var j = 0; j < matrixFftOutput[i].length; j++) { - matrixFftOutput[i][j] /= numberSamplesThrottle[i]; + matrixFftOutput[i][j] /= numberSamples[i]; } - } else if (numberSamplesThrottle[i] == 0) { - matrixFftOutput[i] = new Float64Array(fftChunkLength * 2); } } - // The output data needs to be smoothed, the sampling is not perfect - // but after some tests we let the data as is, an we prefer to apply a + // The output data needs to be smoothed, the sampling is not perfect + // but after some tests we let the data as is, an we prefer to apply a // blur algorithm to the heat map image var fftData = { @@ -143,14 +176,27 @@ GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() { fieldName : this._dataBuffer.fieldName, fftLength : fftChunkLength, fftOutput : matrixFftOutput, - maxNoise : maxNoiseThrottle, + maxNoise : maxNoise, blackBoxRate : this._blackBoxRate, + vsRange : { min: flightSamples.minValue, max: flightSamples.maxValue}, }; return fftData; }; +GraphSpectrumCalc.dataLoadFrequencyVsThrottle = function() { + return this._dataLoadFrequencyVsX(FIELD_THROTTLE_NAME, 0, 100); +}; + +GraphSpectrumCalc.dataLoadFrequencyVsRpm = function() { + let fftData = this._dataLoadFrequencyVsX(FIELD_RPM_NAMES, 0); + const motorPoles = this._flightLog.getSysConfig()['motor_poles']; + fftData.vsRange.max *= 3.333 / motorPoles; + fftData.vsRange.min *= 3.333 / motorPoles; + return fftData; +}; + GraphSpectrumCalc.dataLoadPidErrorVsSetpoint = function() { // Detect the axis @@ -215,13 +261,13 @@ GraphSpectrumCalc._getFlightChunks = function() { logStart = this._analyserTimeRange.in; } else { logStart = this._flightLog.getMinTime(); - } + } - var logEnd = 0; + var logEnd = 0; if(this._analyserTimeRange.out) { logEnd = this._analyserTimeRange.out; } else { - logEnd = this._flightLog.getMaxTime(); + logEnd = this._flightLog.getMaxTime(); } // Limit size @@ -254,30 +300,69 @@ GraphSpectrumCalc._getFlightSamplesFreq = function() { }; }; -GraphSpectrumCalc._getFlightSamplesFreqVsThrottle = function() { +GraphSpectrumCalc._getVsIndexes = function(vsFieldNames) { + let fieldIndexes = []; + for (const fieldName of vsFieldNames) { + if (Object.hasOwn(this._flightLog.getMainFieldIndexes(), fieldName)) { + fieldIndexes.push(this._flightLog.getMainFieldIndexByName(fieldName)); + } + } + return fieldIndexes; +}; + +GraphSpectrumCalc._getFlightSamplesFreqVsX = function(vsFieldNames, minValue = Infinity, maxValue = -Infinity) { var allChunks = this._getFlightChunks(); + let vsIndexes = this._getVsIndexes(vsFieldNames); var samples = new Float64Array(MAX_ANALYSER_LENGTH / (1000 * 1000) * this._blackBoxRate); - var throttle = new Uint16Array(MAX_ANALYSER_LENGTH / (1000 * 1000) * this._blackBoxRate); + let vsValues = new Array(vsIndexes.length).fill(null).map(() => new Float64Array(MAX_ANALYSER_LENGTH / (1000 * 1000) * this._blackBoxRate)); - const FIELD_THROTTLE_INDEX = this._flightLog.getMainFieldIndexByName(FIELD_THROTTLE_NAME); - - // Loop through all the samples in the chunks and assign them to a sample array ready to pass to the FFT. var samplesCount = 0; + let lastRPM = 0; for (var chunkIndex = 0; chunkIndex < allChunks.length; chunkIndex++) { var chunk = allChunks[chunkIndex]; for (var frameIndex = 0; frameIndex < chunk.frames.length; frameIndex++) { samples[samplesCount] = (this._dataBuffer.curve.lookupRaw(chunk.frames[frameIndex][this._dataBuffer.fieldIndex])); - throttle[samplesCount] = chunk.frames[frameIndex][FIELD_THROTTLE_INDEX]*10; + + for (let i = 0; i < vsIndexes.length; i++) { + let vsFieldIx = vsIndexes[i]; + let value = chunk.frames[frameIndex][vsFieldIx]; + if (vsFieldNames == FIELD_RPM_NAMES) { + if (value > MAX_RPM_VALUE || value < 0) + value = lastRPM; + lastRPM = value; + } + maxValue = Math.max(maxValue, value); + minValue = Math.min(minValue, value); + vsValues[i][samplesCount] = value; + } samplesCount++; } } + if (minValue > maxValue) { + if (minValue == Infinity) { // this should never happen + minValue = 0; + maxValue = 100; + console.log("Invalid minimum value"); + } else { + console.log("Maximum value %f smaller than minimum value %d", maxValue, minValue); + minValue = 0; + maxValue = 100; + } + } + + let slicedVsValues = []; + for (const vsValueArray of vsValues) { + slicedVsValues.push(vsValueArray.slice(0, samplesCount)); + } return { - samples : samples, - throttle : throttle, - count : samplesCount + samples : samples.slice(0, samplesCount), + vsValues : slicedVsValues, + count : samplesCount, + minValue : minValue, + maxValue : maxValue, }; }; @@ -356,7 +441,7 @@ GraphSpectrumCalc._normalizeFft = function(fftOutput, fftLength) { var noiseLowEndIdx = 100 / maxFrequency * fftLength; var maxNoiseIdx = 0; var maxNoise = 0; - + for (var i = 0; i < fftLength; i++) { fftOutput[i] = Math.abs(fftOutput[i]); if (i > noiseLowEndIdx && fftOutput[i] > maxNoise) { diff --git a/js/graph_spectrum_plot.js b/src/graph_spectrum_plot.js similarity index 89% rename from js/graph_spectrum_plot.js rename to src/graph_spectrum_plot.js index 90d708eb..e4fcdd76 100644 --- a/js/graph_spectrum_plot.js +++ b/src/graph_spectrum_plot.js @@ -1,4 +1,5 @@ -"use strict"; +import { FILTER_TYPE } from "./flightlog_fielddefs"; +import { constrain } from "./tools"; const BLUR_FILTER_PIXEL = 1, DEFAULT_FONT_FACE = "Verdana, Arial, sans-serif", @@ -11,13 +12,14 @@ const BLUR_FILTER_PIXEL = 1, PID_ERROR_VERTICAL_CHUNK= 5, ZOOM_X_MAX = 5; -const SPECTRUM_TYPE = { +export const SPECTRUM_TYPE = { FREQUENCY : 0, FREQ_VS_THROTTLE : 1, PIDERROR_VS_SETPOINT : 2, + FREQ_VS_RPM : 3, }; -const SPECTRUM_OVERDRAW_TYPE = { +export const SPECTRUM_OVERDRAW_TYPE = { ALL_FILTERS : 0, GYRO_FILTERS : 1, DTERM_FILTERS : 2, @@ -26,7 +28,7 @@ const SPECTRUM_OVERDRAW_TYPE = { AUTO : 5, }; -window.GraphSpectrumPlot = window.GraphSpectrumPlot || { +export const GraphSpectrumPlot = window.GraphSpectrumPlot || { _isFullScreen : false, _cachedCanvas : null, _cachedDataCanvas : null, @@ -52,6 +54,9 @@ GraphSpectrumPlot.initialize = function(canvas, sysConfig) { this._sysConfig = sysConfig; this._invalidateCache(); this._invalidateDataCache(); + this._logRateWarning = undefined; + this._zoomX = 1; + this._zoomY = 1; }; GraphSpectrumPlot.setZoom = function(zoomX, zoomY) { @@ -129,7 +134,11 @@ GraphSpectrumPlot._drawGraph = function(canvasCtx) { break; case SPECTRUM_TYPE.FREQ_VS_THROTTLE: - this._drawFrequencyVsThrottleGraph(canvasCtx); + this._drawFrequencyVsXGraph(canvasCtx); + break; + + case SPECTRUM_TYPE.FREQ_VS_RPM: + this._drawFrequencyVsXGraph(canvasCtx); break; case SPECTRUM_TYPE.PIDERROR_VS_SETPOINT: @@ -179,7 +188,7 @@ GraphSpectrumPlot._drawFrequencyGraph = function(canvasCtx) { }; -GraphSpectrumPlot._drawFrequencyVsThrottleGraph = function(canvasCtx) { +GraphSpectrumPlot._drawFrequencyVsXGraph = function(canvasCtx) { const PLOTTED_BLACKBOX_RATE = this._fftData.blackBoxRate / this._zoomX; @@ -204,8 +213,12 @@ GraphSpectrumPlot._drawFrequencyVsThrottleGraph = function(canvasCtx) { this._drawAxisLabel(canvasCtx, this._fftData.fieldName, WIDTH - 4, HEIGHT - 6, 'right'); this._drawHorizontalGridLines(canvasCtx, PLOTTED_BLACKBOX_RATE / 2, LEFT, TOP, WIDTH, HEIGHT, MARGIN_BOTTOM, 'Hz'); - this._drawVerticalGridLines(canvasCtx, LEFT, TOP, WIDTH, HEIGHT, 100, '%'); + if (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE) { + this._drawVerticalGridLines(canvasCtx, LEFT, TOP, WIDTH, HEIGHT, this._fftData.vsRange.min, this._fftData.vsRange.max, '%'); + } else if (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_RPM) { + this._drawVerticalGridLines(canvasCtx, LEFT, TOP, WIDTH, HEIGHT, this._fftData.vsRange.min, this._fftData.vsRange.max, 'Hz'); + } }; GraphSpectrumPlot._drawHeatMap = function() { @@ -269,7 +282,7 @@ GraphSpectrumPlot._drawPidErrorVsSetpointGraph = function(canvasCtx) { this._drawAxisLabel(canvasCtx, this._fftData.axisName, WIDTH - 4, HEIGHT - 6, 'right'); this._drawHorizontalGridLines(canvasCtx, dataLimits.currentDrawMaxSetpoint, LEFT, TOP, WIDTH, HEIGHT, MARGIN_BOTTOM, 'deg/s'); - this._drawVerticalGridLines(canvasCtx, LEFT, TOP, WIDTH, HEIGHT, dataLimits.currentDrawMaxPidError, 'deg/s'); + this._drawVerticalGridLines(canvasCtx, LEFT, TOP, WIDTH, HEIGHT, 0, dataLimits.currentDrawMaxPidError, 'deg/s'); }; GraphSpectrumPlot._drawPidErrorVsSetpointGraphProcessData = function() { @@ -453,6 +466,8 @@ GraphSpectrumPlot._drawFiltersAndMarkers = function(canvasCtx) { offset++; } + this._drawRateWarning(canvasCtx); + }; GraphSpectrumPlot._drawNotCachedElements = function() { @@ -533,7 +548,7 @@ GraphSpectrumPlot._drawHorizontalGridLines = function(canvasCtx, maxValue, LEFT, } }; -GraphSpectrumPlot._drawVerticalGridLines = function(canvasCtx, LEFT, TOP, WIDTH, HEIGHT, maxValue, label) { +GraphSpectrumPlot._drawVerticalGridLines = function(canvasCtx, LEFT, TOP, WIDTH, HEIGHT, minValue, maxValue, label) { const TICKS = 5; @@ -547,7 +562,7 @@ GraphSpectrumPlot._drawVerticalGridLines = function(canvasCtx, LEFT, TOP, WIDTH, canvasCtx.lineTo(WIDTH, verticalPosition); canvasCtx.stroke(); - const verticalAxisValue = maxValue - i * maxValue / TICKS; + const verticalAxisValue = ((maxValue - minValue) * ((TICKS - i) / TICKS) + minValue).toFixed(0); let textBaseline; switch (i) { case 0: @@ -559,7 +574,7 @@ GraphSpectrumPlot._drawVerticalGridLines = function(canvasCtx, LEFT, TOP, WIDTH, default: textBaseline = 'middle'; } - this._drawAxisLabel(canvasCtx, `${verticalAxisValue}${label}`, 0, verticalPosition, "right", textBaseline); + this._drawAxisLabel(canvasCtx, `${verticalAxisValue}${label}`, 0, verticalPosition, 'center', textBaseline); } }; @@ -590,9 +605,9 @@ GraphSpectrumPlot._drawVerticalMarkerLine = function(canvasCtx, value, axisMaxim return x; }; -GraphSpectrumPlot._drawHorizontalMarkerLine = function(canvasCtx, value, maxAxisValue, label, WIDTH, HEIGHT, OFFSET, stroke, lineWidth){ +GraphSpectrumPlot._drawHorizontalMarkerLine = function(canvasCtx, value, minAxisValue, maxAxisValue, label, WIDTH, HEIGHT, OFFSET, stroke, lineWidth){ - const y = HEIGHT * (maxAxisValue - value) / maxAxisValue; // percentage of range where throttle lies + const y = HEIGHT * (maxAxisValue - value) / (maxAxisValue - minAxisValue); // percentage of range where throttle lies let realLineWidth = (lineWidth || DEFAULT_MARK_LINE_WIDTH); if (realLineWidth > 5) { // is the linewidth specified as a frequency band @@ -651,7 +666,7 @@ GraphSpectrumPlot._drawLowpassDynFilter = function(canvasCtx, frequency1, freque const x1 = this._drawVerticalMarkerLine(canvasCtx, frequency1, sampleRate / 2, dynFilterLabel, WIDTH, HEIGHT, OFFSET, stroke, lineWidth); // frequency2 line - const offsetByType = (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE)? 0 : OFFSET; + const offsetByType = (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE || this._spectrumType === SPECTRUM_TYPE.FREQ_VS_RPM)? 0 : OFFSET; const x2 = this._drawVerticalMarkerLine(canvasCtx, frequency2, sampleRate / 2, null, WIDTH, HEIGHT, offsetByType, stroke, lineWidth); // Join line between frequency1 and frequency2 lines @@ -659,7 +674,7 @@ GraphSpectrumPlot._drawLowpassDynFilter = function(canvasCtx, frequency1, freque canvasCtx.lineWidth = lineWidth || DEFAULT_MARK_LINE_WIDTH; canvasCtx.strokeStyle = stroke || "rgba(128,128,255,0.50)"; - if (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE) { + if (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE || this._spectrumType === SPECTRUM_TYPE.FREQ_VS_RPM) { /* * It draws a curve: * frequency = (throttle - (throttle * throttle * throttle) / 3.0f) * 1.5f; @@ -712,7 +727,7 @@ GraphSpectrumPlot._drawNotchFilter = function(canvasCtx, center, cutoff, sampleR canvasCtx.lineWidth = lineWidth || DEFAULT_MARK_LINE_WIDTH; canvasCtx.strokeStyle = stroke || "rgba(128,128,255,0.50)"; - if (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE) { + if (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE || this._spectrumType === SPECTRUM_TYPE.FREQ_VS_RPM) { canvasCtx.moveTo(cutoffX, 0); canvasCtx.lineTo(centerX*2 - cutoffX, HEIGHT); @@ -742,7 +757,7 @@ GraphSpectrumPlot._drawNotchFilter = function(canvasCtx, center, cutoff, sampleR GraphSpectrumPlot._drawMousePosition = function(canvasCtx, mouseX, mouseY, WIDTH, HEIGHT, OFFSET, stroke, lineWidth) { // X axis - if (this._spectrumType === SPECTRUM_TYPE.FREQUENCY || this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE) { + if (this._spectrumType === SPECTRUM_TYPE.FREQUENCY || this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE || this._spectrumType === SPECTRUM_TYPE.FREQ_VS_RPM) { // Calculate frequency at mouse const sampleRate = this._fftData.blackBoxRate / this._zoomX; const marginLeft = this._getActualMarginLeft(); @@ -753,11 +768,25 @@ GraphSpectrumPlot._drawMousePosition = function(canvasCtx, mouseX, mouseY, WIDTH } // Y axis - if (this._spectrumType === SPECTRUM_TYPE.FREQ_VS_THROTTLE) { - const mouseThrottle = (1 - (mouseY / HEIGHT)) * 100; - if (mouseThrottle >= 0 && mouseThrottle <= 100) { - const throttleLabel = `${mouseThrottle.toFixed(0)}%`; - this._drawHorizontalMarkerLine(canvasCtx, mouseThrottle, 100, throttleLabel, WIDTH, HEIGHT, OFFSET, stroke, lineWidth); + let unitLabel; + switch (this._spectrumType) { + case SPECTRUM_TYPE.FREQ_VS_THROTTLE: + unitLabel = '%'; + break; + case SPECTRUM_TYPE.FREQ_VS_RPM: + unitLabel = 'Hz'; + break; + default: + unitLabel = null; + break; + } + if (unitLabel !== null) { + const val_min = this._fftData.vsRange.min; + const val_max = this._fftData.vsRange.max; + const mouseValue = (1 - (mouseY / HEIGHT)) * (val_max - val_min) + val_min; + if (mouseValue >= val_min && mouseValue <= val_max) { + const valueLabel = `${mouseValue.toFixed(0)}${unitLabel}`; + this._drawHorizontalMarkerLine(canvasCtx, mouseValue, val_min, val_max, valueLabel, WIDTH, HEIGHT, OFFSET, stroke, lineWidth); } } } else if (this._spectrumType === SPECTRUM_TYPE.PIDERROR_VS_SETPOINT) { @@ -776,7 +805,7 @@ GraphSpectrumPlot._drawMousePosition = function(canvasCtx, mouseX, mouseY, WIDTH const mousePidError = (1 - (mouseY / HEIGHT)) * dataLimits.currentDrawMaxPidError; if (mousePidError >= 0 && mousePidError <= dataLimits.currentDrawMaxPidError) { const pidErrorLabel = `${mousePidError.toFixed(1)}deg/sec`; - this._drawHorizontalMarkerLine(canvasCtx, mousePidError, dataLimits.currentDrawMaxPidError, pidErrorLabel, WIDTH, HEIGHT, OFFSET, stroke, lineWidth); + this._drawHorizontalMarkerLine(canvasCtx, mousePidError, 0, dataLimits.currentDrawMaxPidError, pidErrorLabel, WIDTH, HEIGHT, OFFSET, stroke, lineWidth); } } }; @@ -787,6 +816,7 @@ GraphSpectrumPlot._getActualMarginLeft = function() { switch (this._spectrumType) { case SPECTRUM_TYPE.FREQ_VS_THROTTLE: + case SPECTRUM_TYPE.FREQ_VS_RPM: actualMarginLeft = (this._isFullScreen)? MARGIN_LEFT_FULLSCREEN : MARGIN_LEFT; break; case SPECTRUM_TYPE.PIDERROR_VS_SETPOINT: @@ -831,3 +861,29 @@ GraphSpectrumPlot._invalidateCache = function() { GraphSpectrumPlot._invalidateDataCache = function() { this._cachedDataCanvas = null; }; + +GraphSpectrumPlot.setLogRateWarningInfo = function(logRateInfo) { + this._logRateWarning = logRateInfo; +}; + +GraphSpectrumPlot._drawRateWarning = function(canvasCtx) { + if (this._logRateWarning != undefined) { + canvasCtx.save(); + + canvasCtx.font = `${((this._isFullScreen)? this._drawingParams.fontSizeFrameLabelFullscreen : this._drawingParams.fontSizeFrameLabel)}pt ${DEFAULT_FONT_FACE}`; + canvasCtx.fillStyle = 'orange'; + canvasCtx.textAlign = 'center'; + canvasCtx.shadowColor = 'black'; + canvasCtx.strokeStyle = 'black'; + + const actualRate = this._logRateWarning.actualRate.toFixed(0), + betaflightRate = this._logRateWarning.betaflightRate.toFixed(0); + const WarningText = "THE ACTUAL AND CONFIG LOG DATA RATE DIFFERENCE: " + actualRate + " : " + betaflightRate; + const X = canvasCtx.canvas.width/2, + Y = canvasCtx.canvas.height/12; + canvasCtx.strokeText(WarningText, X, Y); + canvasCtx.fillText(WarningText, X, Y); + + canvasCtx.restore(); + } +}; diff --git a/js/grapher.js b/src/grapher.js similarity index 97% rename from js/grapher.js rename to src/grapher.js index c76e1023..7397c280 100644 --- a/js/grapher.js +++ b/src/grapher.js @@ -1,6 +1,20 @@ -"use strict"; - -function FlightLogGrapher(flightLog, graphConfig, canvas, stickCanvas, craftCanvas, analyserCanvas, options) { +import { FlightLogSticks } from "./sticks"; +import { FlightLogParser } from "./flightlog_parser"; +import { FlightLogFieldPresenter } from "./flightlog_fields_presenter"; +import { + FlightLogEvent, + FLIGHT_LOG_FLIGHT_MODE_NAME, + FLIGHT_LOG_DISARM_REASON, +} from "./flightlog_fielddefs"; +import { Craft2D } from "./craft_2d"; +import { Craft3D } from "./craft_3d"; +import { FlightLogAnalyser } from "./graph_spectrum"; +import { LapTimer } from "./laptimer"; +import { GraphConfig } from "./graph_config"; +import { ExpoCurve } from "./expo"; +import { leftPad, formatTime } from "./tools"; + +export function FlightLogGrapher(flightLog, graphConfig, canvas, stickCanvas, craftCanvas, analyserCanvas, options) { var PID_P = 0, PID_I = 1, @@ -232,7 +246,7 @@ function FlightLogGrapher(flightLog, graphConfig, canvas, stickCanvas, craftCanv idents.vbatField = fieldIndex; idents.numCells = flightLog.getNumCellsEstimate(); break; - case "BaroAlt": + case "baroAlt": idents.baroField = fieldIndex; break; case "roll": @@ -729,7 +743,7 @@ function FlightLogGrapher(flightLog, graphConfig, canvas, stickCanvas, craftCanv craft3D.resize(craftSize, craftSize); } - // Positon the craft canvas according to options + // Position the craft canvas according to options $(craftCanvas).css({ left:Math.max(((canvas.width * parseInt(options.craft.left) / 100.0) - (craftSize / 2)), 0) + "px", top: Math.max(((canvas.height * parseInt(options.craft.top) / 100.0) - (craftSize / 2)), 0) + "px", @@ -800,6 +814,9 @@ function FlightLogGrapher(flightLog, graphConfig, canvas, stickCanvas, craftCanv drawAxisBackground(canvas.height * graph.height); for (j = 0; j < graph.fields.length; j++) { + if (graphConfig.isGraphFieldHidden(i, j)) { + continue; + } var field = graph.fields[j]; plotField(chunks, startFrameIndex, field.index, field.curve, canvas.height * graph.height / 2, field.color ? field.color : GraphConfig.PALETTE[j % GraphConfig.PALETTE.length], @@ -1025,7 +1042,7 @@ function FlightLogGrapher(flightLog, graphConfig, canvas, stickCanvas, craftCanv } this.refreshLogo = function() { - if(options.watermark.logo) { + if(options?.watermark?.logo) { watermarkLogo = new Image(); watermarkLogo.src = options.watermark.logo; } diff --git a/js/header_dialog.js b/src/header_dialog.js similarity index 95% rename from js/header_dialog.js rename to src/header_dialog.js index cb36fc9b..4f0413a7 100644 --- a/js/header_dialog.js +++ b/src/header_dialog.js @@ -1,6 +1,31 @@ -'use strict'; - -function HeaderDialog(dialog, onSave) { +import { + OFF_ON, + FAST_PROTOCOL, + MOTOR_SYNC, + SERIALRX_PROVIDER, + ANTI_GRAVITY_MODE, + RC_SMOOTHING_TYPE, + RC_SMOOTHING_MODE, + RC_SMOOTHING_DEBUG_AXIS, + FILTER_TYPE, + SUPER_EXPO_YAW, + GYRO_LPF, + GYRO_HARDWARE_LPF, + GYRO_32KHZ_HARDWARE_LPF, + ACC_HARDWARE, + BARO_HARDWARE, + MAG_HARDWARE, + ITERM_RELAX, + ITERM_RELAX_TYPE, + RATES_TYPE, + GYRO_TO_USE, + FF_AVERAGING, + SIMPLIFIED_PIDS_MODE, + THROTTLE_LIMIT_TYPE, + DEBUG_MODE, +} from "./flightlog_fielddefs"; + +export function HeaderDialog(dialog, onSave) { // Private Variables @@ -63,12 +88,14 @@ function HeaderDialog(dialog, onSave) { {name:'motorOutputHigh' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.1.0', max:'999.9.9'}, {name:'digitalIdleOffset' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.1.0', max:'999.9.9'}, {name:'antiGravityGain' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.1.0', max:'999.9.9'}, - {name:'antiGravityThreshold' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.1.0', max:'999.9.9'}, + {name:'antiGravityThreshold' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.1.0', max:'4.3.999'}, + {name:'anti_gravity_cutoff_hz' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'4.4.0', max:'999.9.9'}, + {name:'anti_gravity_p_gain' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'4.4.0', max:'999.9.9'}, {name:'itermWindupPointPercent' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.1.0', max:'999.9.9'}, {name:'pidSumLimit' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.3.0', max:'999.9.9'}, {name:'pidSumLimitYaw' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.3.0', max:'999.9.9'}, {name:'rc_smoothing_type' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.4.0', max:'4.2.999'}, - {name:'antiGravityMode' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.5.0', max:'999.9.9'}, + {name:'antiGravityMode' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.5.0', max:'4.3.999'}, {name:'rc_smoothing_rx_average' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.5.0', max:'999.9.9'}, {name:'rc_smoothing_debug_axis' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'3.5.0', max:'999.9.9'}, {name:'abs_control_gain' , type:FIRMWARE_TYPE_BETAFLIGHT, min:'4.0.0', max:'999.9.9'}, @@ -317,7 +344,7 @@ function HeaderDialog(dialog, onSave) { {bit: 13, group: 'rxMode', mode: 'group', name: 'RX_PARALLEL_PWM', description: 'PWM receiver selected'}, {bit: 14, group: 'rxMode', mode: 'group', name: 'RX_MSP', description: 'Controller over MSP'}, {bit: 15, group: 'other', name: 'RSSI_ADC', description: 'ADC RSSI Monitoring'}, - {bit: 16, group: 'other', name: 'LED_STRIP', description: 'Addressible RGB LED strip support'}, + {bit: 16, group: 'other', name: 'LED_STRIP', description: 'Addressable RGB LED strip support'}, {bit: 17, group: 'other', name: 'DISPLAY', description: 'OLED Screen Display'}, {bit: 20, group: 'other', name: 'CHANNEL_FORWARDING', description: 'Forward aux channels to servo outputs'}, {bit: 21, group: 'other', name: 'TRANSPONDER', description: 'Race Transponder'}, @@ -454,14 +481,16 @@ function HeaderDialog(dialog, onSave) { {name: 'Magnetometer', description: ''}, {name: 'Altitude', description: 'Barometer and rangefinder'}, {name: 'RSSI', description: ''}, - {name: 'Gyroscope', description: 'Raw gyro data'}, + {name: 'Filtered Gyroscope', description: 'Filtered gyro data'}, {name: 'Accelerometer', description: 'Raw accelerometer data'}, {name: 'Debug', description: 'Debug values'}, {name: 'Motors', description: 'Motors and tricopter servo values'}, {name: 'GPS', description: 'All GPS-related values'}, + {name: 'RPM', description: 'Angular velocity for all motors'}, + {name: 'Unfiltered Gyroscope', description: 'Unfiltered gyro data'}, ]; - const fieldsList_e = $('.fields_list'); + const fieldsList_e = $('.fields_list').empty(); for (let i = 0; i < fields.length; i++) { const row_e = $(`