├── .gitattributes ├── .github ├── FUNDING.yml ├── issue_template.md ├── no-response.yml ├── pull_request_template.md ├── release.yml └── workflows │ ├── build-release.yml │ ├── ci.yml │ ├── nightly.yml │ ├── pr.yml │ └── stale.yaml ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── bin └── build.sh ├── docs └── assets │ └── images │ ├── background_script_setup.png │ ├── bf_lua_logo_dark_mode.png │ ├── bf_lua_logo_light_mode.png │ ├── download_vtx_tables.gif │ ├── how_to_use.gif │ ├── how_to_use_cms.gif │ └── install.gif └── src ├── SCRIPTS ├── BF │ ├── BOARD_INFO │ │ └── readme.txt │ ├── CMS │ │ ├── common.lua │ │ └── crsf.lua │ ├── COMPILE │ │ ├── compile.lua │ │ └── scripts_compiled.lua │ ├── CONFIRM │ │ ├── acc_cal.lua │ │ ├── pwm.lua │ │ └── vtx_tables.lua │ ├── MSP │ │ ├── common.lua │ │ ├── crsf.lua │ │ ├── ghst.lua │ │ ├── messages.lua │ │ └── sp.lua │ ├── PAGES │ │ ├── INIT │ │ │ ├── pwm.lua │ │ │ └── vtx.lua │ │ ├── acc_trim.lua │ │ ├── failsafe.lua │ │ ├── filters1.lua │ │ ├── filters2.lua │ │ ├── gpspids.lua │ │ ├── pid_advanced.lua │ │ ├── pids1.lua │ │ ├── pids2.lua │ │ ├── pos_osd.lua │ │ ├── profiles.lua │ │ ├── pwm.lua │ │ ├── rates.lua │ │ ├── rescue.lua │ │ ├── rx.lua │ │ ├── simplified_tuning.lua │ │ └── vtx.lua │ ├── RATETABLES │ │ ├── ACTUAL.lua │ │ ├── BF.lua │ │ ├── KISS.lua │ │ ├── QUICK.lua │ │ └── RF.lua │ ├── TEMPLATES │ │ ├── 128x64.lua │ │ ├── 128x96.lua │ │ ├── 212x64.lua │ │ ├── 320x480.lua │ │ ├── 480x272.lua │ │ └── 480x320.lua │ ├── VTX_TABLES │ │ └── vtx_defaults.lua │ ├── acc_cal.lua │ ├── api_version.lua │ ├── background.lua │ ├── board_info.lua │ ├── cms.lua │ ├── features.lua │ ├── features_info.lua │ ├── mcu_id.lua │ ├── pages.lua │ ├── protocols.lua │ ├── radios.lua │ ├── rssi.lua │ ├── rtc.lua │ ├── ui.lua │ ├── ui_init.lua │ └── vtx_tables.lua ├── FUNCTIONS │ ├── bfbkgd.lua │ └── bfpids.lua └── TOOLS │ ├── bf.lua │ └── bfCms.lua └── SOUNDS └── en ├── d.wav ├── dsetpt.wav ├── i.wav └── p.wav /.gitattributes: -------------------------------------------------------------------------------- 1 | LICENSE text eol=lf 2 | *.md text eol=lf 3 | *.c text eol=lf 4 | *.cc text eol=lf 5 | *.h text eol=lf 6 | *.cc text eol=lf 7 | *.S text eol=lf 8 | *.s text eol=lf 9 | *.hex -crlf -diff 10 | *.elf -crlf -diff 11 | *.ld text eol=lf 12 | Makefile text eol=lf 13 | *.mk text eol=lf 14 | *.nomk text eol=lf 15 | *.pl text eol=lf 16 | *.js text eol=lf 17 | *.json text eol=lf 18 | *.html text eol=lf 19 | *.css text eol=lf 20 | *.svg text eol=lf 21 | *.png -crlf -diff 22 | *.yml text eol=lf 23 | *.xml text eol=lf 24 | *.mcm text eol=lf 25 | *.nsi text eol=lf 26 | *.nsh text eol=lf 27 | *.lua text eol=lf 28 | *.txt text eol=lf 29 | *.sh text eol=lf 30 | *.config text eol=lf 31 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: https://paypal.me/betaflight 2 | patreon: betaflight 3 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | # If your issue looks like a hardware fault or a configuration problem please don't raise an issue here. 2 | 3 | ## Please consider using other user support options such as asking the manufacturer of the hardware you are using, RCGroups: https://rcgroups.com/forums/showthread.php?t=2464844, or Slack (registration at https://slack.betaflight.com/) or other user support forums & groups (e.g. Facebook). 4 | 5 | ## If you believe there is an issue with the firmware itself please follow these steps: 6 | 1. Describe your problem; 7 | 2. Include ways to reproduce the problem; 8 | 3. Provide as much information as possible about your hardware and software, including: 9 | - what hardware / software you are using; 10 | - the version of all the software used; 11 | - how things are connected / wired up. 12 | 4. Remove this Text :). 13 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 1 5 | # Label requiring a response 6 | responseRequiredLabel: Missing Information 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because the information we asked 10 | to be provided when opening it was not supplied by the original author. 11 | With only the information that is currently in the issue, we don't have 12 | enough information to take action. 13 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Important considerations when opening a pull request: 2 | 3 | 1. Make sure you do not make the changes you want to open a pull request for on the `master` branch of your fork, or open the pull request from the `master` branch of your fork. Some of our integrations will fail if you do this, resulting in your pull request not being accepted. If this is your first pull request, it is probably a good idea to first read up on how opening pull requests work (https://opensource.com/article/19/7/create-pull-request-github is a good introduction); 4 | 5 | 2. Pull requests will only be accepted if they are opened against the `master` branch of our repository. Pull requests opened against other branches without prior consent from the maintainers will be closed; 6 | 7 | 3. Please follow the coding style guidelines: https://github.com/betaflight/betaflight/blob/master/docs/development/CodingStyle.md 8 | 9 | 4. Keep your pull requests as small and concise as possible. One pull request should only ever add / update one feature. If the change that you are proposing has a wider scope, consider splitting it over multiple pull requests. In particular, pull requests that combine changes to features and one or more new targets are not acceptable. 10 | 11 | 5. Ideally, a pull request should contain only one commit, with a descriptive message. If your changes use more than one commit, rebase / squash them into one commit before submitting a pull request. If you need to amend your pull request, make sure that the additional commit has a descriptive message, or - even better - use `git commit --amend` to amend your original commit. 12 | 13 | 6. All pull requests are reviewed. Be ready to receive constructive criticism, and to learn and improve your coding style. Also, be ready to clarify anything that isn't already sufficiently explained in the code and text of the pull request, and to defend your ideas. 14 | 15 | 7. We use continuous integration (CI) with [Travis](https://travis-ci.com/betaflight) to build all targets and run the test suite for every pull request. Pull requests that fail any of the builds or fail tests will most likely not be reviewed before they are fixed to build successfully and pass the tests. In order to get a quick idea if there are things that need fixing **before** opening a pull request or pushing an update into an existing pull request, run `make pre-push` to run a representative subset of the CI build. _Note: This is not an exhaustive test (which will take hours to run on any desktop type system), so even if this passes the CI build might still fail._ 16 | 17 | 8. If your pull request is a fix for one or more issues that are open in GitHub, add a comment to your pull request, and add the issue numbers of the issues that are fixed in the form `Fixes #`. This will cause the issues to be closed when the pull request is merged; 18 | 19 | 9. Remove this Text :). 20 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - "cleanup" 5 | - "RN: IGNORE" 6 | categories: 7 | - title: Features 8 | labels: 9 | - "RN: FEATURE" 10 | - "RN: MAJOR FEATURE" 11 | - "RN: MINOR FEATURE" 12 | - title: Improvements 13 | labels: 14 | - "RN: IMPROVEMENT" 15 | - "RN: UI" 16 | - "RN: REFACTOR" 17 | - "RN: FONT" 18 | - title: Fixes 19 | labels: 20 | - "RN: BUGFIX" 21 | - title: Translation 22 | labels: 23 | - "RN: TRANSLATION" 24 | - title: Known Issues 25 | labels: 26 | - BUG 27 | -------------------------------------------------------------------------------- /.github/workflows/build-release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | ci: 9 | name: CI 10 | uses: ./.github/workflows/ci.yml 11 | with: 12 | release_build: true 13 | 14 | release: 15 | name: Release 16 | needs: ci 17 | runs-on: ubuntu-22.04 18 | steps: 19 | - name: Code Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Fetch build artifacts 23 | uses: actions/download-artifact@v4.1.7 24 | 25 | - name: List assets 26 | run: ls -al Assets 27 | 28 | - name: Attach assets to release 29 | run: | 30 | set -x 31 | ASSETS=() 32 | for asset in Assets/*.zip; do 33 | ASSETS+=("$asset") 34 | echo "$asset" 35 | done 36 | TAG_NAME="${GITHUB_REF##*/}" 37 | gh release upload "${TAG_NAME}" "${ASSETS[@]}" 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Builds Betaflight Lua Scripts. 2 | # 3 | # After building, artifacts are kept for 7 days. 4 | 5 | name: CI 6 | 7 | on: 8 | workflow_call: 9 | inputs: 10 | release_build: 11 | description: 'Specifies if it is a debug build or a release build' 12 | default: false 13 | required: false 14 | type: boolean 15 | 16 | jobs: 17 | build: 18 | name: Build 19 | runs-on: ubuntu-22.04 20 | steps: 21 | - name: Code Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Install Lua 25 | run: sudo apt-get -y install lua5.2 26 | 27 | - name: Install Zip 28 | run: sudo apt-get -y install zip 29 | 30 | - name: Execute Build 31 | run: make release 32 | 33 | - name: Publish build artifacts 34 | uses: actions/upload-artifact@v3 35 | with: 36 | name: Assets 37 | path: ./release/*.zip 38 | retention-days: 7 39 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | # You'll need to setup the follwing environment variables: 2 | # env.repo_nightly - The repository to release nightly builds to e.g. betaflight-tx-lua-scripts-nightlies 3 | # env.release_notes - The release notes to be published as part of the github release 4 | # env.debug_release_notes - The release notes to be published as part of the github debug release 5 | # secrets.REPO_TOKEN - A GitHub token with permissions to push and publish releases to the nightly repo 6 | 7 | env: 8 | repo_nightly: betaflight/betaflight-tx-lua-scripts-nightlies 9 | debug_release_notes: > 10 | This is an automated development build. 11 | It may be unstable and result in corrupted configurations or data loss. 12 | **Use only for testing.** 13 | release_notes: This is a release build. 14 | 15 | name: Nightly 16 | 17 | on: 18 | push: 19 | branches: 20 | - master 21 | 22 | jobs: 23 | ci: 24 | name: CI 25 | uses: ./.github/workflows/ci.yml 26 | with: 27 | release_build: false 28 | 29 | release: 30 | name: Nightly release 31 | needs: ci 32 | runs-on: ubuntu-22.04 33 | steps: 34 | - name: Code Checkout 35 | uses: actions/checkout@v4 36 | 37 | - name: Fetch build artifacts 38 | uses: actions/download-artifact@v4.1.7 39 | 40 | - name: Select release notes 41 | id: notes 42 | run: | 43 | set -- Assets/*.zip 44 | echo "notes=$(test -e "$1" && echo '${{ env.debug_release_notes }}' || echo '${{ env.release_notes }}')" >> $GITHUB_OUTPUT 45 | - name: Get current date 46 | id: date 47 | run: echo "today=$(date '+%Y%m%d')" >> $GITHUB_OUTPUT 48 | 49 | - name: Release 50 | uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v0.1.14 51 | with: 52 | token: ${{ secrets.REPO_TOKEN }} 53 | repository: ${{ env.repo_nightly }} 54 | tag_name: v${{ steps.date.outputs.today }}.${{ github.run_number }} 55 | files: Assets/*.zip 56 | draft: false 57 | prerelease: false 58 | fail_on_unmatched_files: true 59 | body: | 60 | ${{ steps.notes.outputs.notes }} 61 | ### Repository: 62 | ${{ github.repository }} ([link](${{ github.event.repository.html_url }})) 63 | ### Branch: 64 | ${{ github.ref_name }} ([link](${{ github.event.repository.html_url }}/tree/${{ github.ref_name }})) 65 | ### Latest changeset: 66 | ${{ github.event.head_commit.id }} ([link](${{ github.event.head_commit.url }})) 67 | ### Changes: 68 | ${{ github.event.head_commit.message }} 69 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | - '*-maintenance' 8 | 9 | jobs: 10 | ci: 11 | name: CI 12 | uses: ./.github/workflows/ci.yml 13 | -------------------------------------------------------------------------------- /.github/workflows/stale.yaml: -------------------------------------------------------------------------------- 1 | name: 'Close stale issues' 2 | 3 | on: 4 | schedule: 5 | - cron: "30 4 * * *" 6 | 7 | jobs: 8 | stale: 9 | name: 'Check and close stale issues' 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v3 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | operations-per-run: 30 16 | days-before-stale: 30 17 | days-before-close: 7 18 | stale-issue-message: > 19 | This issue has been automatically marked as stale because it 20 | has not had recent activity. It will be closed if no further activity occurs 21 | within a week. 22 | close-issue-message: 'Issue closed automatically as inactive.' 23 | exempt-issue-labels: 'BUG,Feature Request,Pinned' 24 | stale-issue-label: 'Inactive' 25 | stale-pr-message: > 26 | This pull request has been automatically marked as stale because it 27 | has not had recent activity. It will be closed if no further activity occurs 28 | within a week. 29 | close-pr-message: 'Pull request closed automatically as inactive.' 30 | exempt-pr-labels: 'Pinned' 31 | stale-pr-label: 'Inactive' 32 | exempt-all-milestones: true 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # Luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | # Eclipse 43 | .project 44 | 45 | # Visual Studio Code 46 | .vscode 47 | 48 | # Directories 49 | tmp 50 | obj 51 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | 4 | sudo: false 5 | 6 | dist: trusty 7 | 8 | addons: 9 | apt: 10 | packages: 11 | - lua5.2 12 | 13 | git: 14 | depth: 5 15 | 16 | language: c 17 | 18 | install: 19 | 20 | script: ./bin/build.sh 21 | 22 | # Notifications are encrypted to betaflight/betaflight to avoid spam from forks 23 | # Command: `travis encrypt "" --com -r betaflight/blackbox-log-viewer 24 | notifications: 25 | slack: 26 | rooms: 27 | - secure: O3Wc0/JyHXJHbTKNqNqf05zZuYNa57TcmXTuml/CSpnEHzoCuphq9N769HSg9szZPvyw27VdK2VZ7bXl28i/thXmPjkGkKnCk0mV3Jd2rnn6UNMF2E33Qymjup02cActNWSxmPQEDPmIZIC0qTyZ9HBs/9Fun/BXQpIKl0SBDe/N1Cz3YIclF3RkGIk8GwDE9IChQi6f6vf+nXaUXOgVMqDG0ZfuIFdW/sEuDjqBPx+GXQrnHgJLpVb/tFrTtQz69A6zsUwcxODaxVYavtLgTgWV7dTufnSo1cM/2hIjXaV0BO2mwbgeAM4thaKnUkfywy4yBTd7m3W80pdsV5/amyok7VsjynkLKKtyQAnNv47/kAeEbzlOmh666LdI9hnFI/HPg2n0zfI7rsX3reU13tkQHbJJSxaxSPA/61fQ1qYxNsr2OJ+MVkyuSUlCczyVzvAsT4eNAA16EDpCat6HeiIXgkrRpiPMWIBh62EUxbI/hirhlv31GAAkl5AuEGx23gSW6bDQrT+W8qJQVA3ClYDZ+HL34zS+JQ4MzVRQRqxF57Jnuxz4evhaVVa7rgKBylOn5IzpIQanmxEM0H0CEpzt7+zFb3OFAQDsVdPR0xXBmA/ATW78xYLgwkjiwmrITIwTTa6scPiSjGWij1L2mnvVepHGdqhpfKNddlDiEkY= 28 | webhooks: 29 | urls: 30 | - secure: pjDcl+AVC2Nppo49jmGGv61cgbDBQRUq/B/eAE23kord/W9WeIPEw0BV5fj4Mt5KTDDkfqyLaKgo6UO0XFcWLbqp0HRzWVsBNSc6PLzWlO3ZpuwrhtH8TrhH18iJLoGJcb6kHvCLqLf6PO6LKihVG9TG6MKl41grZD5auw/0iKzweFSxZts01Z83xzZkmOYgTQzaLMeGozV2P3x1C1cjFD8woInKG8YQhxDyYacXcdFKboQXAjD8lRNrE0nrHmLXQx6q7Dwf15lg0xkFsvKB0NivBGwBQY+zUBankweghVAazOahtO9m1w20iEZS9jhekmodAHWrMEH9TSkWMt+T1S0EqPp9/i2ZiPh5WSLfWQ32GQkOGGA05GxTQMK0xxwPe0yEtORS2tEoluERq/xQaIx5HYubG7QrF1bo8K+KoNaHrLOuNKcdTbiDc9GUQW24zbD85S1UFhO3+H5It/aHBIWSQuNgENw6tp5i9O79ZIJbLjA23J2GY43Red0rTrSAsM3JMO6ktQhAP2IJ3NOvoOpoI/rH1c+PGzxFJnhc42Xo/Npyfdr3L7zNPiKhsGkY8QUe3sd5T6iPOXeUWhx1PdVqExjzJiTyzyGe5PGtZAB1vIjpRY5Jl7wbGYzxpBO3qhBukapWGy+UqUyEj9g5x2lT6W1AcwA0cbwFhAMmulA= 31 | on_success: always # options: [always|never|change] default: always 32 | on_failure: always # options: [always|never|change] default: always 33 | on_start: always # options: [always|never|change] default: always 34 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at conduct-violations@betaflight.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Issues and Support. 2 | 3 | Please remember the issue tracker on github is _not_ for user support. Please also do not email developers directly for support. Instead please use IRC or the forums first, then if the problem is confirmed create an issue that details how to repeat the problem so it can be investigated. 4 | 5 | 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. 6 | 7 | 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. 8 | 9 | Please search for existing issues *before* creating new ones. 10 | 11 | # Developers 12 | 13 | Please refer to the development section in the [this folder](https://github.com/betaflight/betaflight/tree/master/docs/development). 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DIR := ${CURDIR} 2 | 3 | all: files 4 | 5 | files: 6 | @bin/build.sh 7 | 8 | clean: 9 | @rm -rf obj/* 10 | 11 | release: clean files 12 | @RELEASE_DIR=release; \ 13 | FILE_NAME="betaflight-tx-lua-scripts_$$(git describe --abbrev=0 --tags).zip"; \ 14 | mkdir -p $${RELEASE_DIR}; \ 15 | rm -f $${RELEASE_DIR}/$${FILE_NAME}; \ 16 | zip -q -r $${RELEASE_DIR}/$${FILE_NAME} obj/ 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![BF lua logo light mode](docs/assets/images/bf_lua_logo_light_mode.png#gh-light-mode-only) 2 | ![BF lua logo dark mode](docs/assets/images/bf_lua_logo_dark_mode.png#gh-dark-mode-only) 3 | 4 | [![Latest version](https://img.shields.io/github/v/release/betaflight/betaflight-tx-lua-scripts)](https://github.com/betaflight/betaflight-tx-lua-scripts/releases) [![Build](https://img.shields.io/github/actions/workflow/status/betaflight/betaflight-tx-lua-scripts/nightly.yml?branch=master)](https://github.com/betaflight/betaflight-tx-lua-scripts/actions/workflows/nightly.yml) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 5 | 6 | ## Requirements 7 | 8 | - Betaflight - As a best practice, it is recommended to use the most recent stable release of Betaflight to obtain the best possible results; 9 | - Telemetry - Telemetry has to be enabled in Betaflight Receiver tab and supported by the TX / RX for the scripts to be able to communicate with the flight controller; 10 | - OpenTX - 2.3.12 or newer; 11 | - EdgeTX - 2.4.0 or newer; 12 | - ExpressLRS - 2.0.1 or newer; 13 | - TBS Crossfire/Tracer TX / RX - v2.11 or newer; 14 | - FrSky TX / RX with support for SmartPort - While most receivers with SmartPort support work fine, it is recommended to update the receiver to the most recent firmware version to correct any known bugs in telemetry. 15 | - ImmersionRC Ghost RX module - Latest firmware and module set to RF Mode "Race250" - see (Ghost receiver manual pg 36-38) (Other RF Modes have issues or do not have telemetry to allow Betaflight LUA to work). 16 | - Baud rate - Set to 400K in EdgeTX/OpenTX for Crossfire, Tracer, ExpressLRS. 17 | 18 | ## Installing 19 | 20 | **!! IMPORTANT: DON'T COPY THE CONTENTS OF THIS REPOSITORY ONTO YOUR SDCARD !!** 21 | 22 | Download a zip file containing the latest version from the [releases page](https://github.com/betaflight/betaflight-tx-lua-scripts/releases). 23 | 24 | Unzip the files from the link above and drag the contents of the `obj` folder to your radios SD card. If you do this correctly, the `SCRIPTS` directory will merge with your existing directories, placing the scripts in their appropriate paths. You will know if you did this correctly if the `bf.lua` file shows up in your `/SCRIPTS/TOOLS` directory. 25 | 26 | ![Install](docs/assets/images/install.gif) 27 | 28 | ## How to use 29 | 30 | Navigate to the TOOLS menu in OpenTX, select "Betaflight setup" or "Betaflight CMS" and press the [ENTER] button. The first time the script is launched after a clean install or upgrade it will go through it's compile procedure and exit back to the TOOLS menu when it's done. 31 | 32 | ### Betaflight setup 33 | 34 | The "Betaflight setup" script lets you configure Betaflight through the MSP protocol. 35 | 36 | ![Betaflight setup](docs/assets/images/how_to_use.gif) 37 | 38 | #### Controls 39 | 40 | - [+] / [-] / [ROTARY ENCODER] - Used to navigate. 41 | - [PAGE] - Press to move to the next page. Long press to move to the previous page. 42 | - [ENTER] - Press to access the selected element. Long press to open the function menu. 43 | - [EXIT] - Press to go back or exit the script. 44 | 45 | #### Saving your changes 46 | 47 | Any changes to parameters in the script will not take effect until a save is manually initiated. Change the parameters you want to change, open the function menu by long pressing [ENTER] and select "save page" to send the modified parameters back to the flight controller. 48 | 49 | #### Setting up VTX tables 50 | 51 | If you are using a VTX that supports the SmartAudio or Tramp protocols then bands and channels etc. are managed using VTX tables since Betaflight version 4.1.0. The script will be downloading and storing the current VTX table for every model the first time the model is connected and the script is run. If you change the VTX table, you have to re-load the updated VTX table in the script, by choosing the 'vtx tables' option in the function menu. 52 | 53 | Depending on the size of the vtx tables and the telemetry protocol used, downloading the vtx tables can take a while. 54 | 55 | ![Download VTX tables](docs/assets/images/download_vtx_tables.gif) 56 | 57 | Please note that if the "VTX" feature wasn't included in your Betaflight firmware build, the downloading of the VTX tables will be skipped and the VTX menu item won't be available. The initial setup of the VTX/VTX tables must be done using the [Configurator](https://github.com/betaflight/betaflight-configurator). 58 | 59 | ### Betaflight CMS 60 | 61 | **!! IMPORTANT: TBS Crossfire/Tracer only !!** 62 | 63 | "Betaflight CMS" lets you access the same CMS menu that is available in the OSD. 64 | 65 | ![Betaflight CMS](docs/assets/images/how_to_use_cms.gif) 66 | 67 | #### Controls 68 | 69 | - [PITCH] - Navigate the current menu. 70 | - [ROLL] - Enter menu or change selected parameter. 71 | - [YAW] - Left to go back and right to enter the "SAVE / EXIT" menu. 72 | - [+] / [ENTER] - Manual refresh. Press if the script doesn't update. 73 | - [EXIT] - Close CMS menu and exit script. **!! IMPORTANT: Single press only. Long press will exit the script without closing CMS and as a result you will not be able to arm !!** 74 | 75 | ### Background script 76 | 77 | The optional background script offers RTC synchronization and RSSI through MSP. RSSI will only be sent if no other RSSI source is detected. It can be setup as a special or global function in OpenTX. The image below shows how to run the background script as a special function. 78 | 79 | ![Background script setup](docs/assets/images/background_script_setup.png) 80 | 81 | ## Unstable testing versions 82 | 83 | Unstable testing versions of the latest builds of the Lua Script can be downloaded from [here](https://github.com/betaflight/betaflight-tx-lua-scripts-nightlies/releases). 84 | 85 | Be aware that these versions are intended for testing / feedback only, and may be buggy or broken. Caution is advised when using these versions. 86 | 87 | ## Building from source 88 | 89 | - Be sure to have `make` and `luac` in version 5.2 installed in the path 90 | - Run `make` from the root folder 91 | - The installation files will be created in the `obj` folder. Copy the files to your transmitter as instructed in the '[Installing](#installing)' section as if you unzipped from a downloaded file. 92 | -------------------------------------------------------------------------------- /bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ -d obj ]; then 4 | rm -fR obj/* 5 | else 6 | mkdir obj 7 | fi 8 | 9 | cp -fR src/* obj 10 | 11 | MANIFEST=(`find obj/ -name *.lua -type f`); 12 | LAST_FAILURE=0 13 | 14 | if [ ${#MANIFEST[@]} -eq 0 ]; then 15 | echo -e "\e[1m\e[39m[\e[31mTEST FAILED\e[39m]\e[21m No scripts could be found!." 16 | exit 1 17 | fi 18 | 19 | SCRIPTS_LUA=obj/SCRIPTS/BF/COMPILE/scripts.lua 20 | 21 | echo 'local scripts = {' >> $SCRIPTS_LUA 22 | for f in ${MANIFEST[@]}; 23 | do 24 | echo ' ''"'${f/\obj/}'",' >> $SCRIPTS_LUA 25 | done 26 | echo '}' >> $SCRIPTS_LUA 27 | echo 'return scripts[...]' >> $SCRIPTS_LUA 28 | 29 | MANIFEST+=($SCRIPTS_LUA); 30 | 31 | for f in ${MANIFEST[@]}; 32 | do 33 | SRC_NAME=$f 34 | echo -e "Testing file \e[1m${SRC_NAME}\e[21m..." 35 | luac -p ${SRC_NAME} 36 | _fail=$? 37 | if [[ $_fail -ne 0 ]]; then 38 | LAST_FAILURE=$_fail 39 | echo -e "\e[1m\e[39m[\e[31mBUILD FAILED\e[39m]\e[21m Error in file ${SRC_NAME}\e[1m" 40 | fi 41 | done 42 | 43 | if [[ $LAST_FAILURE -eq 0 ]]; then 44 | echo -e "\e[1m\e[39m[\e[32mTEST SUCCESSFUL\e[39m]\e[21m" 45 | fi 46 | exit $LAST_FAILURE 47 | -------------------------------------------------------------------------------- /docs/assets/images/background_script_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/docs/assets/images/background_script_setup.png -------------------------------------------------------------------------------- /docs/assets/images/bf_lua_logo_dark_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/docs/assets/images/bf_lua_logo_dark_mode.png -------------------------------------------------------------------------------- /docs/assets/images/bf_lua_logo_light_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/docs/assets/images/bf_lua_logo_light_mode.png -------------------------------------------------------------------------------- /docs/assets/images/download_vtx_tables.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/docs/assets/images/download_vtx_tables.gif -------------------------------------------------------------------------------- /docs/assets/images/how_to_use.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/docs/assets/images/how_to_use.gif -------------------------------------------------------------------------------- /docs/assets/images/how_to_use_cms.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/docs/assets/images/how_to_use_cms.gif -------------------------------------------------------------------------------- /docs/assets/images/install.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/docs/assets/images/install.gif -------------------------------------------------------------------------------- /src/SCRIPTS/BF/BOARD_INFO/readme.txt: -------------------------------------------------------------------------------- 1 | Board info downloaded from the flight controller will be stored in this folder. 2 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/CMS/common.lua: -------------------------------------------------------------------------------- 1 | local CONST = { 2 | bitmask = { 3 | firstChunk = 0x80, 4 | lastChunk = 0x40, 5 | batchId = 0x3F, 6 | rleCharValueMask = 0x7F, 7 | rleCharRepeatedMask = 0x80 8 | }, 9 | offset = { 10 | meta = 1, 11 | sequence = 2, 12 | data = 3 13 | } 14 | } 15 | 16 | local function cRleDecode(buf) 17 | local dest = {} 18 | local rpt = false 19 | local c = nil 20 | for i = 1, #buf do 21 | if (rpt == false) then 22 | c = bit32.band(buf[i], CONST.bitmask.rleCharValueMask) 23 | if (bit32.band(buf[i], CONST.bitmask.rleCharRepeatedMask) > 0) then 24 | rpt = true 25 | else 26 | dest[#dest + 1] = c 27 | end 28 | else 29 | for j = 1, buf[i] do 30 | dest[#dest + 1] = c 31 | end 32 | rpt = false 33 | end 34 | end 35 | return dest 36 | end 37 | 38 | screen = { 39 | config = nil, 40 | buffer = {}, 41 | data = {}, 42 | batchId = 0, 43 | sequence = 0, 44 | redraws = 2, 45 | reset = function() 46 | screen.data = {} 47 | screen.batchId = 0 48 | screen.sequence = 0 49 | end, 50 | draw = function() 51 | lcd.clear() 52 | lcd.drawText(screen.config.refresh.left, screen.config.refresh.top, screen.config.refresh.text, screen.config.textSize) 53 | for char = 1, #screen.buffer do 54 | if (screen.buffer[char] ~= 32) then -- skip spaces to avoid CPU spikes 55 | local c = string.char(screen.buffer[char]) 56 | local row = math.ceil(char / screen.config.cols) 57 | local col = char - ((row - 1) * screen.config.cols) 58 | local xPos = ((col - 1) * screen.config.pixelsPerChar) + screen.config.xIndent + 1 59 | local yPos = ((row - 1) * screen.config.pixelsPerRow) + screen.config.yOffset + 1 60 | lcd.drawText(xPos, yPos, c, screen.config.textSize) 61 | end 62 | end 63 | end, 64 | } 65 | 66 | cms = { 67 | menuOpen = false, 68 | synced = false, 69 | init = function(cmsConfig) 70 | screen.config = assert(cmsConfig, "Resolution not supported") 71 | screen.reset() 72 | protocol.cms.close() 73 | cms.menuOpen = false 74 | cms.synced = false 75 | end, 76 | open = function() 77 | protocol.cms.open(screen.config.rows, screen.config.cols) 78 | end, 79 | refresh = function() 80 | protocol.cms.refresh() 81 | end, 82 | close = function() 83 | protocol.cms.close() 84 | cms.menuOpen = false 85 | end, 86 | update = function() 87 | local command, data = protocol.cms.poll() 88 | if (command == "update") then 89 | local firstChunk = bit32.btest(data[CONST.offset.meta], CONST.bitmask.firstChunk) 90 | local lastChunk = bit32.btest(data[CONST.offset.meta], CONST.bitmask.lastChunk) 91 | local batchId = bit32.band(data[CONST.offset.meta], CONST.bitmask.batchId) 92 | local sequence = data[CONST.offset.sequence] 93 | if firstChunk then 94 | screen.reset() 95 | screen.batchId = batchId 96 | screen.sequence = 0 97 | end 98 | if (screen.batchId == batchId) and (screen.sequence == sequence) then 99 | screen.sequence = sequence + 1 100 | for i = CONST.offset.data, #data do 101 | screen.data[#screen.data + 1] = data[i] 102 | end 103 | if lastChunk then 104 | screen.buffer = cRleDecode(screen.data) 105 | screen.redraws = 2 106 | screen.reset() 107 | cms.synced = true 108 | end 109 | else 110 | protocol.cms.refresh() 111 | end 112 | cms.menuOpen = true 113 | elseif (command == "clear") then 114 | screen.reset() 115 | end 116 | if screen.redraws > 0 then 117 | screen.draw() 118 | screen.redraws = screen.redraws - 1 119 | end 120 | end 121 | } 122 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/CMS/crsf.lua: -------------------------------------------------------------------------------- 1 | local CONST = { 2 | frame = { 3 | destination = 1, 4 | source = 2, 5 | command = 3, 6 | content = 4 7 | }, 8 | address = { 9 | transmitter = 0xEA, 10 | betaflight = 0xC8 11 | }, 12 | frameType = { 13 | displayPort = 0x7D 14 | }, 15 | command = { 16 | update = 0x01, 17 | clear = 0x02, 18 | open = 0x03, 19 | close = 0x04, 20 | refresh = 0x05 21 | } 22 | } 23 | 24 | local function crsfDisplayPortCmd(cmd, data) 25 | local payloadOut = { CONST.address.betaflight, CONST.address.transmitter, cmd } 26 | if data ~= nil then 27 | for i = 1, #(data) do 28 | payloadOut[3 + i] = data[i] 29 | end 30 | end 31 | return crossfireTelemetryPush(CONST.frameType.displayPort, payloadOut) 32 | end 33 | 34 | protocol.cms.poll = function() 35 | local command = nil 36 | local dataOut = {} 37 | local frameType, data = crossfireTelemetryPop() 38 | if (data ~= nil) and (#data > 2) then 39 | if (frameType == CONST.frameType.displayPort) and (data[CONST.frame.destination] == CONST.address.transmitter) and (data[CONST.frame.source] == CONST.address.betaflight) then 40 | for k,v in pairs(CONST.command) do 41 | if (v == data[CONST.frame.command]) then 42 | command = k 43 | end 44 | end 45 | for i = CONST.frame.content, #data do 46 | dataOut[#dataOut + 1] = data[i] 47 | end 48 | end 49 | end 50 | return command, dataOut 51 | end 52 | 53 | protocol.cms.open = function(rows, cols) 54 | return crsfDisplayPortCmd(CONST.command.open, { rows, cols }) 55 | end 56 | 57 | protocol.cms.close = function() 58 | return crsfDisplayPortCmd(CONST.command.close, nil) 59 | end 60 | 61 | protocol.cms.refresh = function() 62 | return crsfDisplayPortCmd(CONST.command.refresh, nil) 63 | end 64 | 65 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/COMPILE/compile.lua: -------------------------------------------------------------------------------- 1 | local i = 1 2 | 3 | local function compile() 4 | local script = assert(loadScript("COMPILE/scripts.lua"))(i) 5 | collectgarbage() 6 | i = i + 1 7 | if script then 8 | lcd.clear() 9 | lcd.drawText(2, 2, "Compiling...", SMLSIZE) 10 | lcd.drawText(2, 22, script, SMLSIZE) 11 | assert(loadScript(script, 'c')) 12 | collectgarbage() 13 | return 0 14 | end 15 | local file = io.open("COMPILE/scripts_compiled.lua", 'w') 16 | io.write(file, "return true") 17 | io.close(file) 18 | assert(loadScript("COMPILE/scripts_compiled.lua", 'c')) 19 | return 1 20 | end 21 | 22 | return compile 23 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/COMPILE/scripts_compiled.lua: -------------------------------------------------------------------------------- 1 | return false 2 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/CONFIRM/acc_cal.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | labels[#labels + 1] = { t = "Make sure the craft is level", x = x, y = inc.y(lineSpacing) } 15 | labels[#labels + 1] = { t = "and stable, then press", x = x, y = inc.y(lineSpacing) } 16 | labels[#labels + 1] = { t = "[ENTER] to calibrate, or", x = x, y = inc.y(lineSpacing) } 17 | labels[#labels + 1] = { t = "[EXIT] to cancel.", x = x, y = inc.y(lineSpacing) } 18 | fields[#fields + 1] = { x = x, y = inc.y(lineSpacing), value = "", ro = true } 19 | 20 | return { 21 | title = "Accelerometer", 22 | labels = labels, 23 | fields = fields, 24 | init = assert(loadScript("acc_cal.lua"))(), 25 | } 26 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/CONFIRM/pwm.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local lineSpacing = template.lineSpacing 4 | local yMinLim = radio.yMinLimit 5 | local x = margin 6 | local y = yMinLim - lineSpacing 7 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 8 | local labels = {} 9 | local fields = {} 10 | 11 | labels[#labels + 1] = { t = "Download Board Info? Press", x = x, y = inc.y(lineSpacing) } 12 | labels[#labels + 1] = { t = "[ENTER] to download, or", x = x, y = inc.y(lineSpacing) } 13 | labels[#labels + 1] = { t = "[EXIT] to cancel.", x = x, y = inc.y(lineSpacing) } 14 | fields[#fields + 1] = { x = x, y = inc.y(lineSpacing), value = "", ro = true } 15 | 16 | return { 17 | title = "Board Info", 18 | labels = labels, 19 | fields = fields, 20 | init = assert(loadScript("board_info.lua"))(), 21 | } 22 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/CONFIRM/vtx_tables.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local lineSpacing = template.lineSpacing 4 | local yMinLim = radio.yMinLimit 5 | local x = margin 6 | local y = yMinLim - lineSpacing 7 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 8 | local labels = {} 9 | local fields = {} 10 | 11 | labels[#labels + 1] = { t = "Download VTX tables? Press", x = x, y = inc.y(lineSpacing) } 12 | labels[#labels + 1] = { t = "[ENTER] to download, or", x = x, y = inc.y(lineSpacing) } 13 | labels[#labels + 1] = { t = "[EXIT] to cancel.", x = x, y = inc.y(lineSpacing) } 14 | fields[#fields + 1] = { x = x, y = inc.y(lineSpacing), value = "", ro = true } 15 | 16 | return { 17 | title = "VTX Tables", 18 | labels = labels, 19 | fields = fields, 20 | init = assert(loadScript("vtx_tables.lua"))(), 21 | } 22 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/MSP/common.lua: -------------------------------------------------------------------------------- 1 | -- Protocol version 2 | local MSP_VERSION = bit32.lshift(1,5) 3 | local MSP_STARTFLAG = bit32.lshift(1,4) 4 | 5 | -- Sequence number for next MSP packet 6 | local mspSeq = 0 7 | local mspRemoteSeq = 0 8 | local mspRxBuf = {} 9 | local mspRxError = false 10 | local mspRxSize = 0 11 | local mspRxCRC = 0 12 | local mspRxReq = 0 13 | local mspStarted = false 14 | local mspLastReq = 0 15 | local mspTxBuf = {} 16 | local mspTxIdx = 1 17 | local mspTxCRC = 0 18 | 19 | function mspProcessTxQ() 20 | if (#(mspTxBuf) == 0) then 21 | return false 22 | end 23 | if not protocol.push() then 24 | return true 25 | end 26 | local payload = {} 27 | payload[1] = mspSeq + MSP_VERSION 28 | mspSeq = bit32.band(mspSeq + 1, 0x0F) 29 | if mspTxIdx == 1 then 30 | -- start flag 31 | payload[1] = payload[1] + MSP_STARTFLAG 32 | end 33 | local i = 2 34 | while (i <= protocol.maxTxBufferSize) and mspTxIdx <= #mspTxBuf do 35 | payload[i] = mspTxBuf[mspTxIdx] 36 | mspTxIdx = mspTxIdx + 1 37 | mspTxCRC = bit32.bxor(mspTxCRC,payload[i]) 38 | i = i + 1 39 | end 40 | if i <= protocol.maxTxBufferSize then 41 | payload[i] = mspTxCRC 42 | protocol.mspSend(payload) 43 | mspTxBuf = {} 44 | mspTxIdx = 1 45 | mspTxCRC = 0 46 | return false 47 | end 48 | protocol.mspSend(payload) 49 | return true 50 | end 51 | 52 | function mspSendRequest(cmd, payload) 53 | -- busy 54 | if #(mspTxBuf) ~= 0 or not cmd then 55 | return nil 56 | end 57 | mspTxBuf[1] = #(payload) 58 | mspTxBuf[2] = bit32.band(cmd,0xFF) -- MSP command 59 | for i=1,#(payload) do 60 | mspTxBuf[i+2] = bit32.band(payload[i],0xFF) 61 | end 62 | mspLastReq = cmd 63 | return mspProcessTxQ() 64 | end 65 | 66 | function mspReceivedReply(payload) 67 | local idx = 1 68 | local status = payload[idx] 69 | local version = bit32.rshift(bit32.band(status, 0x60), 5) 70 | local start = bit32.btest(status, 0x10) 71 | local seq = bit32.band(status, 0x0F) 72 | idx = idx + 1 73 | if start then 74 | mspRxBuf = {} 75 | mspRxError = bit32.btest(status, 0x80) 76 | mspRxSize = payload[idx] 77 | mspRxReq = mspLastReq 78 | idx = idx + 1 79 | if version == 1 then 80 | mspRxReq = payload[idx] 81 | idx = idx + 1 82 | end 83 | mspRxCRC = bit32.bxor(mspRxSize, mspRxReq) 84 | if mspRxReq == mspLastReq then 85 | mspStarted = true 86 | end 87 | elseif not mspStarted then 88 | return nil 89 | elseif bit32.band(mspRemoteSeq + 1, 0x0F) ~= seq then 90 | mspStarted = false 91 | return nil 92 | end 93 | while (idx <= protocol.maxRxBufferSize) and (#mspRxBuf < mspRxSize) do 94 | mspRxBuf[#mspRxBuf + 1] = payload[idx] 95 | mspRxCRC = bit32.bxor(mspRxCRC, payload[idx]) 96 | idx = idx + 1 97 | end 98 | if idx > protocol.maxRxBufferSize then 99 | mspRemoteSeq = seq 100 | return false 101 | end 102 | mspStarted = false 103 | -- check CRC 104 | if mspRxCRC ~= payload[idx] and version == 0 then 105 | return nil 106 | end 107 | return true 108 | end 109 | 110 | function mspPollReply() 111 | while true do 112 | local mspData = protocol.mspPoll() 113 | if mspData == nil then 114 | return nil 115 | elseif mspReceivedReply(mspData) then 116 | mspLastReq = 0 117 | return mspRxReq, mspRxBuf, mspRxError 118 | end 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/MSP/crsf.lua: -------------------------------------------------------------------------------- 1 | -- CRSF Devices 2 | local CRSF_ADDRESS_BETAFLIGHT = 0xC8 3 | local CRSF_ADDRESS_RADIO_TRANSMITTER = 0xEA 4 | -- CRSF Frame Types 5 | local CRSF_FRAMETYPE_MSP_REQ = 0x7A -- response request using msp sequence as command 6 | local CRSF_FRAMETYPE_MSP_RESP = 0x7B -- reply with 60 byte chunked binary 7 | local CRSF_FRAMETYPE_MSP_WRITE = 0x7C -- write with 60 byte chunked binary 8 | 9 | local crsfMspCmd = 0 10 | 11 | protocol.mspSend = function(payload) 12 | local payloadOut = { CRSF_ADDRESS_BETAFLIGHT, CRSF_ADDRESS_RADIO_TRANSMITTER } 13 | for i=1, #(payload) do 14 | payloadOut[i+2] = payload[i] 15 | end 16 | return protocol.push(crsfMspCmd, payloadOut) 17 | end 18 | 19 | protocol.mspRead = function(cmd) 20 | crsfMspCmd = CRSF_FRAMETYPE_MSP_REQ 21 | return mspSendRequest(cmd, {}) 22 | end 23 | 24 | protocol.mspWrite = function(cmd, payload) 25 | crsfMspCmd = CRSF_FRAMETYPE_MSP_WRITE 26 | return mspSendRequest(cmd, payload) 27 | end 28 | 29 | protocol.mspPoll = function() 30 | while true do 31 | local cmd, data = crossfireTelemetryPop() 32 | if cmd == CRSF_FRAMETYPE_MSP_RESP and data[1] == CRSF_ADDRESS_RADIO_TRANSMITTER and data[2] == CRSF_ADDRESS_BETAFLIGHT then 33 | local mspData = {} 34 | for i = 3, #data do 35 | mspData[i - 2] = data[i] 36 | end 37 | return mspData 38 | elseif cmd == nil then 39 | return nil 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/MSP/ghst.lua: -------------------------------------------------------------------------------- 1 | -- GHST Frame Types 2 | local GHST_FRAMETYPE_MSP_REQ = 0x21 3 | local GHST_FRAMETYPE_MSP_WRITE = 0x22 4 | local GHST_FRAMETYPE_MSP_RESP = 0x28 5 | 6 | local ghstMspType = 0 7 | 8 | protocol.mspSend = function(payload) 9 | return protocol.push(ghstMspType, payload) 10 | end 11 | 12 | protocol.mspRead = function(cmd) 13 | ghstMspType = GHST_FRAMETYPE_MSP_REQ 14 | return mspSendRequest(cmd, {}) 15 | end 16 | 17 | protocol.mspWrite = function(cmd, payload) 18 | ghstMspType = GHST_FRAMETYPE_MSP_WRITE 19 | return mspSendRequest(cmd, payload) 20 | end 21 | 22 | protocol.mspPoll = function() 23 | while true do 24 | local type, data = ghostTelemetryPop() 25 | if type == GHST_FRAMETYPE_MSP_RESP then 26 | return data 27 | elseif type == nil then 28 | return nil 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/MSP/messages.lua: -------------------------------------------------------------------------------- 1 | MSP_PID_FORMAT = { 2 | read = 112, -- MSP_PID 3 | write = 202, -- MSP_SET_PID 4 | minBytes = 8, 5 | fields = { 6 | -- P 7 | { vals = { 1 } }, 8 | { vals = { 4 } }, 9 | { vals = { 7 } }, 10 | -- I 11 | { vals = { 2 } }, 12 | { vals = { 5 } }, 13 | { vals = { 8 } }, 14 | -- D 15 | { vals = { 3 } }, 16 | { vals = { 6 } }, 17 | }, 18 | } 19 | 20 | MSP_PID_ADVANCED_FORMAT = { 21 | read = 94, -- MSP_PID_ADVANCED 22 | write = 95, -- MSP_SET_PID_ADVANCED 23 | minBytes = 23, 24 | fields = { 25 | -- weight 26 | { vals = { 10 }, scale = 100 }, 27 | -- transition 28 | { vals = { 9 }, scale = 100 }, 29 | }, 30 | } 31 | 32 | local INTRO_DELAY = 1600 33 | local READOUT_DELAY = 500 34 | 35 | function extractMspValues(cmd, rx_buf, msgFormat, msgValues) 36 | if cmd == nil or rx_buf == nil then 37 | return 38 | end 39 | if cmd ~= msgFormat.read then 40 | return 41 | end 42 | if #(rx_buf) > 0 then 43 | msgValues.raw = {} 44 | for i=1,#(rx_buf) do 45 | msgValues.raw[i] = rx_buf[i] 46 | end 47 | 48 | msgValues.values = {} 49 | for i=1,#(msgFormat.fields) do 50 | if (#(msgValues.raw) or 0) >= msgFormat.minBytes then 51 | local f = msgFormat.fields[i] 52 | if f.vals then 53 | local value = 0; 54 | for idx=1, #(f.vals) do 55 | local raw_val = msgValues.raw[f.vals[idx]] 56 | raw_val = bit32.lshift(raw_val, (idx-1)*8) 57 | value = bit32.bor(value, raw_val) 58 | end 59 | msgValues.values[i] = value/(f.scale or 1) 60 | end 61 | end 62 | end 63 | end 64 | end 65 | 66 | function readoutMsp(msgFormat, msg) 67 | local t = getTime() 68 | if msg.lastTrigger == nil or msg.lastTrigger + INTRO_DELAY <= t then 69 | playFile(msg.intro) 70 | msg.lastTrigger = t 71 | elseif msg.reqTS == nil or msg.reqTS + READOUT_DELAY <= t then 72 | protocol.mspRead(msgFormat.read) 73 | msg.reqTS = t 74 | else 75 | local cmd, rx_buf = mspPollReply() 76 | extractMspValues(cmd, rx_buf, msgFormat, msg) 77 | if msg.raw then 78 | for i=1,#(msg.readoutValues) do 79 | playNumber(msg.values[msg.readoutValues[i]], 0) 80 | end 81 | msg.raw = nil 82 | end 83 | end 84 | mspProcessTxQ() 85 | end 86 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/MSP/sp.lua: -------------------------------------------------------------------------------- 1 | local LOCAL_SENSOR_ID = 0x0D 2 | local SMARTPORT_REMOTE_SENSOR_ID = 0x1B 3 | local FPORT_REMOTE_SENSOR_ID = 0x00 4 | local REQUEST_FRAME_ID = 0x30 5 | local REPLY_FRAME_ID = 0x32 6 | 7 | local lastSensorId, lastFrameId, lastDataId, lastValue 8 | 9 | protocol.mspSend = function(payload) 10 | local dataId = payload[1] + bit32.lshift(payload[2], 8) 11 | local value = 0 12 | for i = 3, #payload do 13 | value = value + bit32.lshift(payload[i], (i - 3) * 8) 14 | end 15 | return protocol.push(LOCAL_SENSOR_ID, REQUEST_FRAME_ID, dataId, value) 16 | end 17 | 18 | protocol.mspRead = function(cmd) 19 | return mspSendRequest(cmd, {}) 20 | end 21 | 22 | protocol.mspWrite = function(cmd, payload) 23 | return mspSendRequest(cmd, payload) 24 | end 25 | 26 | -- Discards duplicate data from lua input buffer 27 | local function smartPortTelemetryPop() 28 | while true do 29 | local sensorId, frameId, dataId, value = sportTelemetryPop() 30 | if not sensorId then 31 | return nil 32 | elseif (lastSensorId == sensorId) and (lastFrameId == frameId) and (lastDataId == dataId) and (lastValue == value) then 33 | -- Keep checking 34 | else 35 | lastSensorId = sensorId 36 | lastFrameId = frameId 37 | lastDataId = dataId 38 | lastValue = value 39 | return sensorId, frameId, dataId, value 40 | end 41 | end 42 | end 43 | 44 | protocol.mspPoll = function() 45 | while true do 46 | local sensorId, frameId, dataId, value = smartPortTelemetryPop() 47 | if (sensorId == SMARTPORT_REMOTE_SENSOR_ID or sensorId == FPORT_REMOTE_SENSOR_ID) and frameId == REPLY_FRAME_ID then 48 | local payload = {} 49 | payload[1] = bit32.band(dataId, 0xFF) 50 | dataId = bit32.rshift(dataId, 8) 51 | payload[2] = bit32.band(dataId, 0xFF) 52 | payload[3] = bit32.band(value, 0xFF) 53 | value = bit32.rshift(value, 8) 54 | payload[4] = bit32.band(value, 0xFF) 55 | value = bit32.rshift(value, 8) 56 | payload[5] = bit32.band(value, 0xFF) 57 | value = bit32.rshift(value, 8) 58 | payload[6] = bit32.band(value, 0xFF) 59 | return payload 60 | elseif sensorId == nil then 61 | return nil 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/INIT/pwm.lua: -------------------------------------------------------------------------------- 1 | local function precondition() 2 | if apiVersion < 1.44 then 3 | -- BOARD_INFO is unavailable below 1.44 4 | return nil 5 | end 6 | local hasBoardInfo = loadScript("BOARD_INFO/"..mcuId..".lua") 7 | collectgarbage() 8 | if hasBoardInfo then 9 | return nil 10 | else 11 | return "CONFIRM/pwm.lua" 12 | end 13 | end 14 | 15 | return precondition() 16 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/INIT/vtx.lua: -------------------------------------------------------------------------------- 1 | local function precondition() 2 | local hasVtxTable = loadScript("VTX_TABLES/"..mcuId..".lua") 3 | collectgarbage() 4 | if hasVtxTable then 5 | return nil 6 | else 7 | return "CONFIRM/vtx_tables.lua" 8 | end 9 | end 10 | 11 | return precondition() 12 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/acc_trim.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | labels[#labels + 1] = { t = "Trim Accelerometer", x = x, y = inc.y(lineSpacing) } 15 | fields[#fields + 1] = { t = "Pitch", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = -300, max = 300, vals = { 1, 2 } } 16 | fields[#fields + 1] = { t = "Roll", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = -300, max = 300, vals = { 3, 4 } } 17 | 18 | return { 19 | read = 240, -- MSP_ACC_TRIM 20 | write = 239, -- MSP_SET_ACC_TRIM 21 | title = "Acc", 22 | reboot = false, 23 | eepromWrite = true, 24 | minBytes = 4, 25 | labels = labels, 26 | fields = fields, 27 | } 28 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/failsafe.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | local procedure = { [0] = "Land", "Drop" } 15 | 16 | if apiVersion >= 1.39 then 17 | procedure[#procedure + 1] = "Rescue" 18 | end 19 | 20 | if apiVersion >= 1.39 then 21 | labels[#labels + 1] = { t = "Failsafe Switch", x = x, y = inc.y(lineSpacing) } 22 | fields[#fields + 1] = { t = "Action", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 2, vals = { 5 }, table = { [0] = "Stage 1", "Kill", "Stage 2" } } 23 | else 24 | fields[#fields + 1] = { t = "Kill switch", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 5 }, table = { [0] = "OFF", "ON" } } 25 | end 26 | 27 | labels[#labels + 1] = { t = "Stage 2 Settings" , x = x, y = inc.y(lineSpacing) } 28 | fields[#fields + 1] = { t = "Procedure", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #procedure, vals = { 8 }, table = procedure } 29 | fields[#fields + 1] = { t = "Guard Time", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 1 }, scale = 10 } 30 | fields[#fields + 1] = { t = "Thrl Low Delay", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 300, vals = { 6, 7 }, scale = 10 } 31 | 32 | labels[#labels + 1] = { t = "Stage 2 Land Settings", x = x, y = inc.y(lineSpacing) } 33 | fields[#fields + 1] = { t = "Thrl Land Value", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 750, max = 2250, vals = { 3, 4 } } 34 | fields[#fields + 1] = { t = "Motor Off Delay", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 2 }, scale = 10 } 35 | 36 | return { 37 | read = 75, -- MSP_FAILSAFE_CONFIG 38 | write = 76, -- MSP_SET_FAILSAFE_CONFIG 39 | title = "Failsafe", 40 | reboot = true, 41 | eepromWrite = true, 42 | minBytes = 8, 43 | labels = labels, 44 | fields = fields, 45 | } 46 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/filters1.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | local gyroFilterType = { [0] = "PT1", "BIQUAD" } 15 | 16 | if apiVersion >= 1.44 then 17 | gyroFilterType[#gyroFilterType + 1] = "PT2" 18 | gyroFilterType[#gyroFilterType + 1] = "PT3" 19 | end 20 | 21 | local dtermFilterType = gyroFilterType 22 | 23 | if apiVersion >= 1.36 and apiVersion <= 1.38 then 24 | dtermFilterType = { [0] = "PT1", "BIQUAD", "FIR" } 25 | end 26 | 27 | local dtermFilterType2 = gyroFilterType 28 | 29 | if apiVersion >= 1.41 then 30 | labels[#labels + 1] = { t = "Gyro Lowpass 1 Dynamic", x = x, y = inc.y(lineSpacing) } 31 | fields[#fields + 1] = { t = "Min Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1000, vals = { 30, 31 } } 32 | fields[#fields + 1] = { t = "Max Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1000, vals = { 32, 33 } } 33 | fields[#fields + 1] = { t = "Filter Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #gyroFilterType, vals = { 25 }, table = gyroFilterType } 34 | end 35 | 36 | if apiVersion >= 1.16 then 37 | labels[#labels + 1] = { t = "Gyro Lowpass 1", x = x, y = inc.y(lineSpacing) } 38 | if apiVersion >= 1.39 then 39 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 21, 22 } } 40 | fields[#fields + 1] = { t = "Filter Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #gyroFilterType, vals = { 25 }, table = gyroFilterType } 41 | else 42 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 255, vals = { 1 } } 43 | end 44 | end 45 | 46 | if apiVersion >= 1.39 then 47 | labels[#labels + 1] = { t = "Gyro Lowpass 2", x = x, y = inc.y(lineSpacing) } 48 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 23, 24 } } 49 | fields[#fields + 1] = { t = "Filter Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #gyroFilterType, vals = { 26 }, table = gyroFilterType } 50 | end 51 | 52 | if apiVersion >= 1.20 then 53 | labels[#labels + 1] = { t = "Gyro Notch 1", x = x, y = inc.y(lineSpacing) } 54 | fields[#fields + 1] = { t = "Center", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 6, 7 } } 55 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 8, 9 } } 56 | end 57 | 58 | if apiVersion >= 1.21 then 59 | labels[#labels + 1] = { t = "Gyro Notch 2", x = x, y = inc.y(lineSpacing) } 60 | fields[#fields + 1] = { t = "Center", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 14, 15 } } 61 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 16, 17 } } 62 | end 63 | 64 | if apiVersion >= 1.41 then 65 | labels[#labels + 1] = { t = "D Term Lowpass 1 Dynamic", x = x, y = inc.y(lineSpacing) } 66 | fields[#fields + 1] = { t = "Min Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1000, vals = { 34, 35 } } 67 | fields[#fields + 1] = { t = "Max Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1000, vals = { 36, 37 } } 68 | fields[#fields + 1] = { t = "Filter Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #dtermFilterType, vals = { 18 }, table = dtermFilterType } 69 | end 70 | 71 | if apiVersion >= 1.16 then 72 | labels[#labels + 1] = { t = "D Term Lowpass 1", x = x, y = inc.y(lineSpacing) } 73 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 2, 3 } } 74 | if apiVersion >= 1.36 then 75 | fields[#fields + 1] = { t = "Filter Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #dtermFilterType, vals = { 18 }, table = dtermFilterType } 76 | end 77 | end 78 | 79 | if apiVersion >= 1.39 then 80 | labels[#labels + 1] = { t = "D Term Lowpass 2", x = x, y = inc.y(lineSpacing) } 81 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 27, 28 } } 82 | if apiVersion >= 1.41 then 83 | fields[#fields + 1] = { t = "Filter Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #dtermFilterType2, vals = { 29 }, table = dtermFilterType2 } 84 | end 85 | end 86 | 87 | if apiVersion >= 1.20 then 88 | labels[#labels + 1] = { t = "D Term Notch", x = x, y = inc.y(lineSpacing) } 89 | fields[#fields + 1] = { t = "Center", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 10, 11 } } 90 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 16000, vals = { 12, 13 } } 91 | end 92 | 93 | if apiVersion >= 1.16 then 94 | labels[#labels + 1] = { t = "Yaw Lowpass", x = x, y = inc.y(lineSpacing) } 95 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 500, vals = { 4, 5 } } 96 | end 97 | 98 | return { 99 | read = 92, -- MSP_FILTER_CONFIG 100 | write = 93, -- MSP_SET_FILTER_CONFIG 101 | eepromWrite = true, 102 | reboot = false, 103 | title = "Filters (1/2)", 104 | minBytes = 5, 105 | labels = labels, 106 | fields = fields, 107 | } 108 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/filters2.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | if apiVersion >= 1.42 then 15 | labels[#labels + 1] = { t = "Gyro RPM Filter", x = x, y = inc.y(lineSpacing) } 16 | fields[#fields + 1] = { t = "Harmonics", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 3, vals = { 44 } } 17 | fields[#fields + 1] = { t = "Min Frequency", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 50, max = 200, vals = { 45 } } 18 | labels[#labels + 1] = { t = "Dynamic Notch Filter", x = x, y = inc.y(lineSpacing) } 19 | if apiVersion < 1.43 then 20 | fields[#fields + 1] = { t = "Range", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 3, vals = { 38 }, table = { [0]="HIGH", "MEDIUM", "LOW", "AUTO" } } 21 | end 22 | if apiVersion >= 1.44 then 23 | fields[#fields + 1] = { t = "Count", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 5, vals = { 49 } } 24 | else 25 | fields[#fields + 1] = { t = "Width %", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 20, vals = { 39 } } 26 | end 27 | fields[#fields + 1] = { t = "Q", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 1000, vals = { 40, 41 } } 28 | if apiVersion >= 1.43 then 29 | fields[#fields + 1] = { t = "Min Frequency", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 60, max = 250, vals = { 42, 43 } } 30 | fields[#fields + 1] = { t = "Max Frequency", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 200, max = 1000, vals = { 46, 47 } } 31 | else 32 | fields[#fields + 1] = { t = "Min Frequency", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 60, max = 1000, vals = { 42, 43 } } 33 | end 34 | end 35 | 36 | return { 37 | read = 92, -- MSP_FILTER_CONFIG 38 | write = 93, -- MSP_SET_FILTER_CONFIG 39 | eepromWrite = true, 40 | reboot = false, 41 | title = "Filters (2/2)", 42 | minBytes = 5, 43 | labels = labels, 44 | fields = fields, 45 | postLoad = function(self) 46 | self.rpmHarmonics = self.values[44] 47 | end, 48 | preSave = function(self) 49 | self.reboot = self.values[44] == 0 and self.rpmHarmonics ~= 0 and apiVersion <= 1.43 50 | return self.values 51 | end, 52 | } 53 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/gpspids.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | if apiVersion >= 1.41 then 15 | x = margin 16 | y = yMinLim - tableSpacing.header 17 | labels[#labels + 1] = { t = "", x = x, y = inc.y(tableSpacing.header) } 18 | labels[#labels + 1] = { t = "Throttle", x = x, y = inc.y(tableSpacing.row) } 19 | labels[#labels + 1] = { t = "Velocity", x = x, y = inc.y(tableSpacing.row) } 20 | labels[#labels + 1] = { t = "Yaw", x = x, y = inc.y(tableSpacing.row) } 21 | 22 | x = x + tableSpacing.col*1.3 23 | y = yMinLim - tableSpacing.header 24 | 25 | labels[#labels + 1] = { t = "P", x = x, y = inc.y(tableSpacing.header) } 26 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 200, vals = { 1, 2 } } 27 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 200, vals = { 7, 8 } } 28 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 500, vals = { 13, 14 } } 29 | 30 | x = x + tableSpacing.col 31 | y = yMinLim - tableSpacing.header 32 | 33 | labels[#labels + 1] = { t = "I", x = x, y = inc.y(tableSpacing.header) } 34 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 200, vals = { 3, 4 } } 35 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 200, vals = { 9, 10 } } 36 | 37 | x = x + tableSpacing.col 38 | y = yMinLim - tableSpacing.header 39 | 40 | labels[#labels + 1] = { t = "D", x = x, y = inc.y(tableSpacing.header) } 41 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 200, vals = { 5, 6 } } 42 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 200, vals = { 11, 12 } } 43 | end 44 | 45 | return { 46 | read = 136, -- MSP_GPS_RESCUE_PIDS 47 | write = 226, -- MSP_SET_GPS_RESCUE_PIDS 48 | title = "GPS Rescue / PIDs", 49 | reboot = false, 50 | eepromWrite = true, 51 | minBytes = 14, 52 | labels = labels, 53 | fields = fields, 54 | } 55 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/pid_advanced.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | if apiVersion >= 1.31 then 15 | labels[#labels + 1] = { t = "Angle/Horizon", x = x, y = inc.y(lineSpacing) } 16 | fields[#fields + 1] = { t = "Angle Limit", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 10, max = 90, vals = { 18 } } 17 | if apiVersion < 1.36 then 18 | fields[#fields + 1] = { t = "Stick Sensitivity", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 10, max = 200, vals = { 19 } } 19 | end 20 | end 21 | 22 | if apiVersion >= 1.40 then 23 | labels[#labels + 1] = { t = "Acro Trainer", x = x, y = inc.y(lineSpacing) } 24 | fields[#fields + 1] = { t = "Angle Limit", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 20, max = 80, vals = { 32 } } 25 | fields[#fields + 1] = { t = "Throttle Boost", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 31 } } 26 | fields[#fields + 1] = { t = "Absolute Control", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 20, vals = { 30 } } 27 | fields[#fields + 1] = { t = "I Term Rotation", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 26 }, table = { [0] = "OFF", "ON" } } 28 | end 29 | 30 | if apiVersion >= 1.43 then 31 | fields[#fields + 1] = { t = "Motor Output Limit",x = x, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 100, vals = { 48 } } 32 | if apiVersion >= 1.45 then 33 | fields[#fields + 1] = { t = "Dynamic Idle", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 50 } } 34 | else 35 | fields[#fields + 1] = { t = "Dynamic Idle", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 50 } } 36 | end 37 | end 38 | 39 | if apiVersion >= 1.16 and apiVersion <= 1.43 then 40 | fields[#fields + 1] = { t = "VBAT Compensation", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 8 }, table = { [0] = "OFF", "ON" } } 41 | end 42 | 43 | if apiVersion >= 1.44 then 44 | fields[#fields + 1] = { t = "Vbat Sag Comp", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 150, vals = { 56 } } 45 | fields[#fields + 1] = { t = "Thrust Linear", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 150, vals = { 57 } } 46 | end 47 | 48 | if apiVersion >= 1.45 then 49 | labels[#labels + 1] = { t = "TPA", x = x, y = inc.y(lineSpacing) } 50 | fields[#fields + 1] = { t = "Mode", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 58 }, table = { [0] = "PD", "D" } } 51 | fields[#fields + 1] = { t = "Rate", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 59 } , scale = 100 } 52 | fields[#fields + 1] = { t = "Breakpoint", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 750, max = 2250, vals = { 60, 61 } } 53 | end 54 | 55 | if apiVersion >= 1.40 and apiVersion <= 1.41 then 56 | fields[#fields + 1] = { t = "Smart Feedforward", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 27 }, table = { [0] = "OFF", "ON" } } 57 | end 58 | 59 | if apiVersion >= 1.41 then 60 | fields[#fields + 1] = { t = "Integrated Yaw", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 45 }, table = { [0] = "OFF", "ON" } } 61 | end 62 | 63 | if apiVersion >= 1.40 then 64 | labels[#labels + 1] = { t = "I Term Relax", x = x, y = inc.y(lineSpacing) } 65 | fields[#fields + 1] = { t = "Axes", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 4, vals = { 28 }, table = { [0] = "NONE", "RP", "RPY", "RP (inc)", "RPY (inc)" } } 66 | fields[#fields + 1] = { t = "Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 29 }, table = { [0] = "Gyro", "Setpoint" } } 67 | if apiVersion >= 1.43 then 68 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 50, vals = { 47 } } 69 | elseif apiVersion >= 1.42 then 70 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 100, vals = { 47 } } 71 | end 72 | end 73 | 74 | if apiVersion >= 1.36 then 75 | labels[#labels + 1] = { t = "Anti Gravity", x = x, y = inc.y(lineSpacing) } 76 | if apiVersion >= 1.40 and apiVersion <= 1.44 then 77 | fields[#fields + 1] = { t = "Mode", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 39 }, table = { [0] = "Smooth", "Step" } } 78 | end 79 | if apiVersion >= 1.45 then 80 | fields[#fields + 1] = { t = "Gain", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 250, vals = { 22, 23 }, scale = 10 } 81 | elseif apiVersion >= 1.44 then 82 | fields[#fields + 1] = { t = "Gain", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 30000, vals = { 22, 23 }, scale = 1000, mult = 100 } 83 | else 84 | fields[#fields + 1] = { t = "Gain", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 30000, vals = { 22, 23 }, scale = 1000, mult = 100 } 85 | end 86 | if apiVersion <= 1.44 then 87 | fields[#fields + 1] = { t = "Threshold", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 20, max = 1000, vals = { 20, 21 } } 88 | end 89 | end 90 | 91 | return { 92 | read = 94, -- MSP_PID_ADVANCED 93 | write = 95, -- MSP_SET_PID_ADVANCED 94 | title = "PID Advanced", 95 | reboot = false, 96 | eepromWrite = true, 97 | minBytes = 8, 98 | labels = labels, 99 | fields = fields, 100 | postLoad = function(self) 101 | self.dynamicIdle = self.values[50] 102 | end, 103 | preSave = function(self) 104 | self.reboot = self.values[50] ~= self.dynamicIdle and apiVersion <= 1.43 105 | return self.values 106 | end, 107 | } 108 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/pids1.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | local pidMax = 200 15 | local dLabel = "D" 16 | 17 | if apiVersion >= 1.44 then 18 | pidMax = 250 19 | dLabel = "D Max" 20 | end 21 | 22 | if apiVersion >= 1.16 then 23 | x = margin 24 | y = yMinLim - tableSpacing.header 25 | 26 | labels[#labels + 1] = { t = "", x = x, y = inc.y(tableSpacing.header) } 27 | labels[#labels + 1] = { t = "ROLL", x = x, y = inc.y(tableSpacing.row) } 28 | labels[#labels + 1] = { t = "PITCH", x = x, y = inc.y(tableSpacing.row) } 29 | labels[#labels + 1] = { t = "YAW", x = x, y = inc.y(tableSpacing.row) } 30 | 31 | x = x + tableSpacing.col 32 | y = yMinLim - tableSpacing.header 33 | 34 | labels[#labels + 1] = { t = "P", x = x, y = inc.y(tableSpacing.header) } 35 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 1 } } 36 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 4 } } 37 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 7 } } 38 | 39 | x = x + tableSpacing.col 40 | y = yMinLim - tableSpacing.header 41 | 42 | labels[#labels + 1] = { t = "I", x = x, y = inc.y(tableSpacing.header) } 43 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 2 } } 44 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 5 } } 45 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 8 } } 46 | 47 | x = x + tableSpacing.col 48 | y = yMinLim - tableSpacing.header 49 | 50 | labels[#labels + 1] = { t = dLabel, x = x, y = inc.y(tableSpacing.header) } 51 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 3 } } 52 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 6 } } 53 | if apiVersion >= 1.41 then 54 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = pidMax, vals = { 9 } } 55 | end 56 | end 57 | 58 | return { 59 | read = 112, -- MSP_PID 60 | write = 202, -- MSP_SET_PID 61 | title = "PIDs (1/2)", 62 | reboot = false, 63 | eepromWrite = true, 64 | minBytes = 9, 65 | labels = labels, 66 | fields = fields, 67 | } 68 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/pids2.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | local dMinMax = 100 15 | 16 | if apiVersion >= 1.44 then 17 | dMinMax = 250 18 | end 19 | 20 | if apiVersion >= 1.40 then 21 | x = margin 22 | y = yMinLim - tableSpacing.header 23 | 24 | labels[#labels + 1] = { t = "", x = x, y = inc.y(tableSpacing.header) } 25 | labels[#labels + 1] = { t = "ROLL", x = x, y = inc.y(tableSpacing.row) } 26 | labels[#labels + 1] = { t = "PITCH", x = x, y = inc.y(tableSpacing.row) } 27 | labels[#labels + 1] = { t = "YAW", x = x, y = inc.y(tableSpacing.row) } 28 | 29 | x = x + tableSpacing.col 30 | y = yMinLim - tableSpacing.header 31 | 32 | labels[#labels + 1] = { t = "FF", x = x, y = inc.y(tableSpacing.header) } 33 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 2000, vals = { 33, 34 } } 34 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 2000, vals = { 35, 36 } } 35 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 2000, vals = { 37, 38 } } 36 | 37 | if apiVersion >= 1.41 then 38 | x = x + tableSpacing.col 39 | y = yMinLim - tableSpacing.header 40 | 41 | labels[#labels + 1] = { t = "D Min", x = x, y = inc.y(tableSpacing.header) } 42 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = dMinMax, vals = { 40 } } 43 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = dMinMax, vals = { 41 } } 44 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = dMinMax, vals = { 42 } } 45 | end 46 | 47 | x = margin 48 | y = inc.y(lineSpacing*0.4) 49 | end 50 | 51 | if apiVersion >= 1.40 then 52 | labels[#labels + 1] = { t = "Feedforward", x = x, y = inc.y(lineSpacing) } 53 | if apiVersion >= 1.44 then 54 | fields[#fields + 1] = { t = "Jitter Reduction", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 20, vals = { 55 } } 55 | fields[#fields + 1] = { t = "Smoothness", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 75, vals = { 52 } } 56 | fields[#fields + 1] = { t = "Averaging", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 3, vals = { 51 }, table = { [0] = "OFF", "2_POINT", "3_POINT", "4_POINT" } } 57 | fields[#fields + 1] = { t = "Boost", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 50, vals = { 53 } } 58 | fields[#fields + 1] = { t = "Max Rate Limit", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 150, vals = { 54 } } 59 | end 60 | fields[#fields + 1] = { t = "Transition", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 9 }, scale = 100 } 61 | end 62 | 63 | if apiVersion >= 1.41 then 64 | labels[#labels + 1] = { t = "D Min", x = x, y = inc.y(lineSpacing) } 65 | fields[#fields + 1] = { t = "Gain", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 43 } } 66 | fields[#fields + 1] = { t = "Advance", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 44 } } 67 | end 68 | 69 | if apiVersion >= 1.21 and apiVersion <= 1.39 then 70 | labels[#labels + 1] = { t = "Dterm Setpoint", x = x, y = inc.y(lineSpacing) } 71 | if apiVersion >= 1.21 and apiVersion <= 1.38 then 72 | fields[#fields + 1] = { t = "Weight", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 254, vals = { 10 }, scale = 100 } 73 | else 74 | fields[#fields + 1] = { t = "Weight", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 254, vals = { 25 }, scale = 100 } 75 | end 76 | fields[#fields + 1] = { t = "Transition", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 9 }, scale = 100 } 77 | end 78 | 79 | return { 80 | read = 94, -- MSP_PID_ADVANCED 81 | write = 95, -- MSP_SET_PID_ADVANCED 82 | title = "PIDs (2/2)", 83 | reboot = false, 84 | eepromWrite = true, 85 | minBytes = 17, 86 | labels = labels, 87 | fields = fields, 88 | } 89 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/pos_osd.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local lineSpacing = template.lineSpacing 4 | local tableSpacing = template.tableSpacing 5 | local yMinLim = radio.yMinLimit 6 | local x = margin 7 | local y = yMinLim - lineSpacing 8 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 9 | local labels = {} 10 | local fields = {} 11 | 12 | local items = { 13 | {["address"] = 0, ["firstVal"] = 11, ["secondVal"] = 12, ["nameCli"] = "rssi_pos"}, 14 | {["address"] = 1, ["firstVal"] = 13, ["secondVal"] = 14, ["nameCli"] = "vbat_pos"}, 15 | {["address"] = 2, ["firstVal"] = 15, ["secondVal"] = 16, ["nameCli"] = "crosshairs_pos"}, 16 | {["address"] = 3, ["firstVal"] = 17, ["secondVal"] = 18, ["nameCli"] = "ah_pos"}, 17 | {["address"] = 4, ["firstVal"] = 19, ["secondVal"] = 20, ["nameCli"] = "ah_sbar_pos"}, 18 | {["address"] = 5, ["firstVal"] = 21, ["secondVal"] = 22, ["nameCli"] = "tim_1_pos"}, 19 | {["address"] = 6, ["firstVal"] = 23, ["secondVal"] = 24, ["nameCli"] = "tim_2_pos"}, 20 | {["address"] = 7, ["firstVal"] = 25, ["secondVal"] = 26, ["nameCli"] = "flymode_pos"}, 21 | {["address"] = 8, ["firstVal"] = 27, ["secondVal"] = 28, ["nameCli"] = "craft_name_pos"}, 22 | {["address"] = 9, ["firstVal"] = 29, ["secondVal"] = 30, ["nameCli"] = "throttle_pos"}, 23 | {["address"] = 10, ["firstVal"] = 31, ["secondVal"] = 32, ["nameCli"] = "vtx_channel_pos"}, 24 | {["address"] = 11, ["firstVal"] = 33, ["secondVal"] = 34, ["nameCli"] = "current_pos"}, 25 | {["address"] = 12, ["firstVal"] = 35, ["secondVal"] = 36, ["nameCli"] = "mah_drawn_pos"}, 26 | {["address"] = 13, ["firstVal"] = 37, ["secondVal"] = 38, ["nameCli"] = "gps_speed_pos"}, 27 | {["address"] = 14, ["firstVal"] = 39, ["secondVal"] = 40, ["nameCli"] = "gps_sats_pos"}, 28 | {["address"] = 15, ["firstVal"] = 41, ["secondVal"] = 42, ["nameCli"] = "altitude_pos"}, 29 | {["address"] = 16, ["firstVal"] = 43, ["secondVal"] = 44, ["nameCli"] = "pid_roll_pos"}, 30 | {["address"] = 17, ["firstVal"] = 45, ["secondVal"] = 46, ["nameCli"] = "pid_pitch_pos"}, 31 | {["address"] = 18, ["firstVal"] = 47, ["secondVal"] = 48, ["nameCli"] = "pid_yaw_pos"}, 32 | {["address"] = 19, ["firstVal"] = 49, ["secondVal"] = 50, ["nameCli"] = "power_pos"}, 33 | {["address"] = 20, ["firstVal"] = 51, ["secondVal"] = 52, ["nameCli"] = "pidrate_profile_pos"}, 34 | {["address"] = 21, ["firstVal"] = 53, ["secondVal"] = 54, ["nameCli"] = "warnings_pos"}, 35 | {["address"] = 22, ["firstVal"] = 55, ["secondVal"] = 56, ["nameCli"] = "avg_cell_voltage_pos"}, 36 | {["address"] = 23, ["firstVal"] = 57, ["secondVal"] = 58, ["nameCli"] = "gps_lon_pos"}, 37 | {["address"] = 24, ["firstVal"] = 59, ["secondVal"] = 60, ["nameCli"] = "gps_lat_pos"}, 38 | {["address"] = 25, ["firstVal"] = 61, ["secondVal"] = 62, ["nameCli"] = "debug_pos"}, 39 | {["address"] = 26, ["firstVal"] = 63, ["secondVal"] = 64, ["nameCli"] = "pit_ang_pos"}, 40 | {["address"] = 27, ["firstVal"] = 65, ["secondVal"] = 66, ["nameCli"] = "rol_ang_pos"}, 41 | {["address"] = 28, ["firstVal"] = 67, ["secondVal"] = 68, ["nameCli"] = "battery_usage_pos"}, 42 | {["address"] = 29, ["firstVal"] = 69, ["secondVal"] = 70, ["nameCli"] = "disarmed_pos"}, 43 | {["address"] = 30, ["firstVal"] = 71, ["secondVal"] = 72, ["nameCli"] = "home_dir_pos"}, 44 | {["address"] = 31, ["firstVal"] = 73, ["secondVal"] = 74, ["nameCli"] = "home_dist_pos"}, 45 | {["address"] = 32, ["firstVal"] = 75, ["secondVal"] = 76, ["nameCli"] = "nheading_pos"}, 46 | {["address"] = 33, ["firstVal"] = 77, ["secondVal"] = 78, ["nameCli"] = "nvario_pos"}, 47 | {["address"] = 34, ["firstVal"] = 79, ["secondVal"] = 80, ["nameCli"] = "compass_bar_pos"}, 48 | {["address"] = 35, ["firstVal"] = 81, ["secondVal"] = 82, ["nameCli"] = "esc_tmp_pos"}, 49 | {["address"] = 36, ["firstVal"] = 83, ["secondVal"] = 84, ["nameCli"] = "esc_rpm_pos"}, 50 | {["address"] = 37, ["firstVal"] = 85, ["secondVal"] = 86, ["nameCli"] = "remaining_time_estimate_pos"}, 51 | {["address"] = 38, ["firstVal"] = 87, ["secondVal"] = 88, ["nameCli"] = "rtc_date_time_pos"}, 52 | {["address"] = 39, ["firstVal"] = 89, ["secondVal"] = 90, ["nameCli"] = "adjustment_range_pos"}, 53 | {["address"] = 40, ["firstVal"] = 91, ["secondVal"] = 92, ["nameCli"] = "core_temp_pos"}, 54 | {["address"] = 41, ["firstVal"] = 93, ["secondVal"] = 94, ["nameCli"] = "anti_gravity_pos"}, 55 | {["address"] = 42, ["firstVal"] = 95, ["secondVal"] = 96, ["nameCli"] = "g_force_pos"}, 56 | {["address"] = 43, ["firstVal"] = 97, ["secondVal"] = 98, ["nameCli"] = "motor_diag_pos"}, 57 | {["address"] = 44, ["firstVal"] = 99, ["secondVal"] = 100, ["nameCli"] = "log_status_pos"}, 58 | {["address"] = 45, ["firstVal"] = 101, ["secondVal"] = 102, ["nameCli"] = "flip_arrow_pos"}, 59 | {["address"] = 46, ["firstVal"] = 103, ["secondVal"] = 104, ["nameCli"] = "link_quality_pos"}, 60 | {["address"] = 47, ["firstVal"] = 105, ["secondVal"] = 106, ["nameCli"] = "flight_dist_pos"}, 61 | {["address"] = 48, ["firstVal"] = 107, ["secondVal"] = 108, ["nameCli"] = "stick_overlay_left_pos"}, 62 | {["address"] = 49, ["firstVal"] = 109, ["secondVal"] = 110, ["nameCli"] = "stick_overlay_right_pos"}, 63 | {["address"] = 50, ["firstVal"] = 111, ["secondVal"] = 112, ["nameCli"] = "display_name_pos"}, 64 | {["address"] = 51, ["firstVal"] = 113, ["secondVal"] = 114, ["nameCli"] = "esc_rpm_freq_pos"}, 65 | {["address"] = 52, ["firstVal"] = 115, ["secondVal"] = 116, ["nameCli"] = "rate_profile_name_pos"}, 66 | {["address"] = 53, ["firstVal"] = 117, ["secondVal"] = 118, ["nameCli"] = "pid_profile_name_pos"}, 67 | {["address"] = 54, ["firstVal"] = 119, ["secondVal"] = 120, ["nameCli"] = "profile_name_pos"}, 68 | {["address"] = 55, ["firstVal"] = 121, ["secondVal"] = 122, ["nameCli"] = "rssi_dbm_pos"}, 69 | {["address"] = 56, ["firstVal"] = 123, ["secondVal"] = 124, ["nameCli"] = "rcchannels_pos"}, 70 | {["address"] = 57, ["firstVal"] = 125, ["secondVal"] = 126, ["nameCli"] = "camera_frame_pos"}, 71 | {["address"] = 58, ["firstVal"] = 127, ["secondVal"] = 128, ["nameCli"] = "efficiency_pos"}, 72 | {["address"] = 59, ["firstVal"] = 129, ["secondVal"] = 130, ["nameCli"] = "total_flights_pos"}, 73 | {["address"] = 60, ["firstVal"] = 131, ["secondVal"] = 132, ["nameCli"] = "up_down_reference_pos"}, 74 | {["address"] = 61, ["firstVal"] = 133, ["secondVal"] = 134, ["nameCli"] = "link_tx_power_pos"}, 75 | {["address"] = 62, ["firstVal"] = 135, ["secondVal"] = 136, ["nameCli"] = "wh_drawn_pos"}, 76 | {["address"] = 63, ["firstVal"] = 137, ["secondVal"] = 138, ["nameCli"] = "aux_pos"}, 77 | {["address"] = 64, ["firstVal"] = 139, ["secondVal"] = 140, ["nameCli"] = "ready_mode_pos"}, 78 | {["address"] = 65, ["firstVal"] = 141, ["secondVal"] = 142, ["nameCli"] = "rsnr_pos"}, 79 | {["address"] = 66, ["firstVal"] = 143, ["secondVal"] = 144, ["nameCli"] = "sys_goggle_voltage_pos"}, 80 | {["address"] = 67, ["firstVal"] = 145, ["secondVal"] = 146, ["nameCli"] = "sys_vtx_voltage_pos"}, 81 | {["address"] = 68, ["firstVal"] = 147, ["secondVal"] = 148, ["nameCli"] = "sys_bitrate_pos"}, 82 | {["address"] = 69, ["firstVal"] = 149, ["secondVal"] = 150, ["nameCli"] = "sys_delay_pos"}, 83 | {["address"] = 70, ["firstVal"] = 151, ["secondVal"] = 152, ["nameCli"] = "sys_distance_pos"}, 84 | {["address"] = 71, ["firstVal"] = 153, ["secondVal"] = 154, ["nameCli"] = "sys_lq_pos"}, 85 | {["address"] = 72, ["firstVal"] = 155, ["secondVal"] = 156, ["nameCli"] = "sys_goggle_dvr_pos"}, 86 | {["address"] = 73, ["firstVal"] = 157, ["secondVal"] = 158, ["nameCli"] = "sys_vtx_dvr_pos"}, 87 | {["address"] = 74, ["firstVal"] = 159, ["secondVal"] = 160, ["nameCli"] = "sys_warnings_pos"}, 88 | {["address"] = 75, ["firstVal"] = 161, ["secondVal"] = 162, ["nameCli"] = "sys_vtx_temp_pos"}, 89 | {["address"] = 76, ["firstVal"] = 163, ["secondVal"] = 164, ["nameCli"] = "sys_fan_speed_pos"}, 90 | } 91 | 92 | address = address or 0 93 | local firstVal = items[address + 1]["firstVal"] 94 | local secondVal = items[address + 1]["secondVal"] 95 | local nameCli = items[address + 1]["nameCli"] 96 | 97 | x = margin 98 | y = yMinLim - tableSpacing.header 99 | 100 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.header), min = 0, max = (#items - 1), vals = { 1 }, upd = function(self) self.updateItems(self) end } 101 | labels[#labels + 1] = { t = nameCli, x = x + tableSpacing.col * 1, y = y } 102 | 103 | labels[#labels + 1] = { t = "POS", x = x, y = inc.y(tableSpacing.header) } 104 | labels[#labels + 1] = { t = "OP1", x = x + tableSpacing.col, y = y } 105 | labels[#labels + 1] = { t = "OP2", x = x + tableSpacing.col * 2, y = y } 106 | labels[#labels + 1] = { t = "OP3", x = x + tableSpacing.col * 3, y = y } 107 | 108 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 2047, vals = { 2, 3 } } 109 | fields[#fields + 1] = { x = x + tableSpacing.col, y = y, min = 0, max = 1, vals = { 1, 2 }, table = { [0] = "OFF", "ON" } } 110 | fields[#fields + 1] = { x = x + tableSpacing.col * 2, y = y, min = 0, max = 1, vals = { 1, 2 }, table = { [0] = "OFF", "ON" } } 111 | fields[#fields + 1] = { x = x + tableSpacing.col * 3, y = y, min = 0, max = 1, vals = { 1, 2 }, table = { [0] = "OFF", "ON" } } 112 | 113 | return { 114 | read = 84, -- MSP_OSD_CONFIG 115 | write = 85, -- MSP_SET_OSD_CONFIG 116 | title = "OSD Elements", 117 | reboot = false, 118 | eepromWrite = true, 119 | minBytes = 3, 120 | labels = labels, 121 | fields = fields, 122 | postLoad = function(self) 123 | self.fields[1].value = address 124 | self.fields[2].value, self.fields[3].value, self.fields[4].value, self.fields[5].value = self.splitVal(self, self.values[firstVal], self.values[secondVal]) 125 | end, 126 | preSave = function(self) 127 | self.values = {} 128 | self.values[1] = self.fields[1].value 129 | local combineValue = self.checkProfile(self, self.fields[2].value, self.fields[3].value, self.fields[4].value, self.fields[5].value) 130 | self.values[2] = bit32.band(combineValue, 0xFF) 131 | self.values[3] = bit32.rshift(combineValue, 8) 132 | return self.values 133 | end, 134 | checkProfile = function(_, value, profileFirst, profileSecond, profileThird) 135 | local profiles = profileFirst + bit32.lshift(profileSecond, 1) + bit32.lshift(profileThird, 2) 136 | local output = bit32.replace(value, profiles, 11, 3) 137 | return output 138 | end, 139 | splitVal = function(_, inputFirstVal, inputSecondVal) 140 | local inputVal = inputFirstVal + bit32.lshift(inputSecondVal, 8) 141 | local profiles = bit32.extract(inputVal, 11, 3) 142 | local fieldPos = bit32.extract(inputVal, 0, 11) 143 | local fieldFirstProfile = bit32.band(bit32.rshift(profiles, 0), 1) 144 | local FieldSecondProfile = bit32.band(bit32.rshift(profiles, 1), 1) 145 | local FieldThirdProfile = bit32.band(bit32.rshift(profiles, 2), 1) 146 | return fieldPos, fieldFirstProfile, FieldSecondProfile, FieldThirdProfile 147 | end, 148 | updateItems = function(self) 149 | if self.fields[1].value ~= items[address + 1]["address"] then 150 | address = self.fields[1].value 151 | firstVal = items[address + 1]["firstVal"] 152 | secondVal = items[address + 1]["secondVal"] 153 | address = items[address + 1]["address"] 154 | nameCli = items[address + 1]["nameCli"] 155 | self.labels[1].t = nameCli 156 | self.fields[2].value, self.fields[3].value, self.fields[4].value, self.fields[5].value = self.splitVal(self, self.values[firstVal], self.values[secondVal]) 157 | end 158 | end 159 | } 160 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/profiles.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | local RATEPROFILE_MASK = bit32.lshift(1, 7) 15 | local profileNumbers = { [0] = "1", "2", "3", "4", "5", "6", "7", "8", "9", "10" } 16 | 17 | fields[#fields + 1] = { t = "PID Profile", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 11 }, table = profileNumbers } 18 | fields[#fields + 1] = { t = "Rate Profile", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 5, vals = { 15 }, table = profileNumbers } 19 | 20 | return { 21 | read = 150, -- MSP_STATUS_EX 22 | write = 210, -- MSP_SELECT_SETTING 23 | title = "Profiles", 24 | reboot = false, 25 | eepromWrite = true, 26 | minBytes = 11, 27 | labels = labels, 28 | fields = fields, 29 | pidProfile = 0, 30 | postLoad = function(self) 31 | local pidProfileCount = self.values[14] 32 | self.fields[1].max = pidProfileCount - 1 33 | self.pidProfile = self.fields[1].value 34 | end, 35 | preSave = function(self) 36 | local value = 0 37 | if self.fields[1].value ~= self.pidProfile then 38 | value = self.fields[1].value 39 | else 40 | value = bit32.bor(self.fields[2].value, RATEPROFILE_MASK) 41 | end 42 | self.values = {} 43 | self.values[1] = value 44 | return self.values 45 | end, 46 | } 47 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/pwm.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | local gyroSampleRateKhz 15 | 16 | if apiVersion >= 1.44 then 17 | gyroSampleRateKhz = assert(loadScript("BOARD_INFO/"..mcuId..".lua"))().gyroSampleRateHz / 1000 18 | end 19 | 20 | local escProtocols = { [0] = "PWM", "OS125", "OS42", "MSHOT" } 21 | 22 | if apiVersion >= 1.20 then 23 | escProtocols[#escProtocols + 1] = "BRSH" 24 | end 25 | if apiVersion >= 1.31 then 26 | escProtocols[#escProtocols + 1] = "DS150" 27 | escProtocols[#escProtocols + 1] = "DS300" 28 | escProtocols[#escProtocols + 1] = "DS600" 29 | if apiVersion < 1.42 then 30 | escProtocols[#escProtocols + 1] = "DS1200" 31 | end 32 | if apiVersion >= 1.36 then 33 | escProtocols[#escProtocols + 1] = "PS1000" 34 | end 35 | end 36 | 37 | if apiVersion >= 1.43 then 38 | escProtocols[#escProtocols + 1] = "DISABLED" 39 | end 40 | 41 | labels[#labels + 1] = { t = "System Config", x = x, y = inc.y(lineSpacing) } 42 | if apiVersion >= 1.31 and apiVersion <= 1.40 then 43 | fields[#fields + 1] = { t = "32kHz Sampling", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 9 }, table = { [0] = "OFF", "ON" }, upd = function(self) self.updateRateTables(self) end } 44 | end 45 | if apiVersion >= 1.44 then 46 | fields[#fields + 1] = { t = "Gyro Update", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 32, vals = { 1 }, table = {}, upd = function(self) self.updatePidRateTable(self) end, mult = -1, ro = true } 47 | elseif apiVersion <= 1.42 then 48 | fields[#fields + 1] = { t = "Gyro Update", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 32, vals = { 1 }, table = {}, upd = function(self) self.updatePidRateTable(self) end, mult = -1 } 49 | end 50 | if apiVersion <= 1.42 or apiVersion >= 1.44 then 51 | fields[#fields + 1] = { t = "PID Loop", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 16, vals = { 2 }, table = {}, mult = -1 } 52 | end 53 | 54 | labels[#labels + 1] = { t = "ESC/Motor", x = x, y = inc.y(lineSpacing) } 55 | fields[#fields + 1] = { t = "Protocol", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = #escProtocols, vals = { 4 }, table = escProtocols } 56 | if apiVersion >= 1.31 then 57 | fields[#fields + 1] = { t = "Idle Throttle %", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 2000, vals = { 7, 8 }, scale = 100 } 58 | end 59 | fields[#fields + 1] = { t = "Unsynced PWM", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 3 }, table = { [0] = "OFF", "ON" } } 60 | fields[#fields + 1] = { t = "Frequency", x = x + indent*2, y = inc.y(lineSpacing), sp = x + sp, min = 200, max = 32000, vals = { 5, 6 }, } 61 | 62 | return { 63 | read = 90, -- MSP_ADVANCED_CONFIG 64 | write = 91, -- MSP_SET_ADVANCED_CONFIG 65 | reboot = true, 66 | eepromWrite = true, 67 | title = "PWM", 68 | minBytes = 6, 69 | labels = labels, 70 | fields = fields, 71 | gyroRates = {}, 72 | getGyroDenomFieldIndex = function(self) 73 | for i=1,#self.fields do 74 | if self.fields[i].vals[1] == 1 then 75 | return i 76 | end 77 | end 78 | end, 79 | getPidDenomFieldIndex = function(self) 80 | for i=1,#self.fields do 81 | if self.fields[i].vals[1] == 2 then 82 | return i 83 | end 84 | end 85 | end, 86 | calculateGyroRates = function(self, baseRate) 87 | local idx = self.getGyroDenomFieldIndex(self) 88 | baseRate = gyroSampleRateKhz or baseRate 89 | for i=1, 32 do 90 | self.gyroRates[i] = baseRate/i 91 | self.fields[idx].table[i] = string.format("%.2f",baseRate/i) 92 | end 93 | end, 94 | calculatePidRates = function(self, baseRate) 95 | local idx = self.getPidDenomFieldIndex(self) 96 | for i=1, 16 do 97 | self.fields[idx].table[i] = string.format("%.2f",baseRate/i) 98 | end 99 | end, 100 | updateRateTables = function(self) 101 | if (self.values[9] or 0) == 0 then 102 | self.calculateGyroRates(self, 8) 103 | self.calculatePidRates(self, 8) 104 | elseif self.values[9] == 1 then 105 | self.calculateGyroRates(self, 32) 106 | self.calculatePidRates(self, 32) 107 | end 108 | end, 109 | updatePidRateTable = function(self) 110 | self.updateRateTables(self) 111 | local newRateIdx = self.values[1] 112 | local newRate = self.gyroRates[newRateIdx] 113 | self.calculatePidRates(self, newRate) 114 | end 115 | } 116 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/rates.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | if apiVersion >= 1.16 then 15 | y = yMinLim - tableSpacing.header 16 | 17 | labels[#labels + 1] = { t = "", x = x, y = inc.y(tableSpacing.header) } 18 | labels[#labels + 1] = { t = "", x = x, y = inc.y(tableSpacing.header) } 19 | labels[#labels + 1] = { t = "ROLL", x = x, y = inc.y(tableSpacing.row) } 20 | labels[#labels + 1] = { t = "PITCH", x = x, y = inc.y(tableSpacing.row) } 21 | labels[#labels + 1] = { t = "YAW", x = x, y = inc.y(tableSpacing.row) } 22 | 23 | x = x + tableSpacing.col 24 | y = yMinLim - tableSpacing.header 25 | 26 | labels[#labels + 1] = { t = "RC", x = x, y = inc.y(tableSpacing.header) } 27 | labels[#labels + 1] = { t = "Rate", x = x, y = inc.y(tableSpacing.header) } 28 | if apiVersion >= 1.37 then 29 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 255, vals = { 1 }, scale = 100 } 30 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 255, vals = { 13 }, scale = 100 } 31 | else 32 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row*1.5), min = 0, max = 255, vals = { 1 }, scale = 100 } 33 | inc.y(tableSpacing.row*0.5) 34 | end 35 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 255, vals = { 12 }, scale = 100 } 36 | 37 | x = x + tableSpacing.col 38 | y = yMinLim - tableSpacing.header 39 | 40 | labels[#labels + 1] = { t = "Super", x = x, y = inc.y(tableSpacing.header) } 41 | labels[#labels + 1] = { t = "Rate", x = x, y = inc.y(tableSpacing.header) } 42 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 100, vals = { 3 }, scale = 100 } 43 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 100, vals = { 4 }, scale = 100 } 44 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 255, vals = { 5 }, scale = 100 } 45 | 46 | x = x + tableSpacing.col 47 | y = yMinLim - tableSpacing.header 48 | 49 | labels[#labels + 1] = { t = "RC", x = x, y = inc.y(tableSpacing.header) } 50 | labels[#labels + 1] = { t = "Expo", x = x, y = inc.y(tableSpacing.header) } 51 | if apiVersion >= 1.37 then 52 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 100, vals = { 2 }, scale = 100 } 53 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 100, vals = { 14 }, scale = 100 } 54 | else 55 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row*1.5), min = 0, max = 100, vals = { 2 }, scale = 100 } 56 | inc.y(tableSpacing.row*0.5) 57 | end 58 | fields[#fields + 1] = { x = x, y = inc.y(tableSpacing.row), min = 0, max = 100, vals = { 11 }, scale = 100 } 59 | 60 | x = margin 61 | inc.y(lineSpacing*0.4) 62 | end 63 | 64 | if apiVersion >= 1.43 then 65 | fields[#fields + 1] = { t = "Rates Type", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 4, vals = { 23 }, table = { [0] = "BF", "RF", "KISS", "ACTUAL", "QUICK"}, postEdit = function(self) self.updateRatesType(self, true) end } 66 | end 67 | 68 | if apiVersion >= 1.16 then 69 | labels[#labels + 1] = { t = "Throttle", x = x, y = inc.y(lineSpacing) } 70 | fields[#fields + 1] = { t = "Mid", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 7 }, scale = 100 } 71 | fields[#fields + 1] = { t = "Expo", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 8 }, scale = 100 } 72 | if apiVersion >= 1.41 then 73 | fields[#fields + 1] = { t = "Limit Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 2, vals = { 15 }, table = { [0] = "OFF", "SCALE", "CLIP" } } 74 | fields[#fields + 1] = { t = "Limit %", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 25, max = 100, vals = { 16 } } 75 | end 76 | end 77 | 78 | if apiVersion >= 1.16 and apiVersion <= 1.44 then 79 | labels[#labels + 1] = { t = "TPA", x = x, y = inc.y(lineSpacing) } 80 | fields[#fields + 1] = { t = "Rate", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 100, vals = { 6 } , scale = 100 } 81 | fields[#fields + 1] = { t = "Breakpoint", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 2000, vals = { 9, 10 } } 82 | end 83 | 84 | return { 85 | read = 111, -- MSP_RC_TUNING 86 | write = 204, -- MSP_SET_RC_TUNING 87 | title = "Rates", 88 | reboot = false, 89 | eepromWrite = true, 90 | minBytes = 12, 91 | labels = labels, 92 | fields = fields, 93 | ratesType, 94 | getRatesType = function(self) 95 | for i = 1, #self.fields do 96 | if self.fields[i].vals and self.fields[i].vals[1] == 23 then 97 | return self.fields[i].table[self.fields[i].value] 98 | end 99 | end 100 | end, 101 | updateRatesType = function(self, applyDefaults) 102 | local ratesTable = assert(loadScript("RATETABLES/"..self.getRatesType(self)..".lua"))() 103 | for i = 1, #ratesTable.labels do 104 | self.labels[i].t = ratesTable.labels[i] 105 | end 106 | for i = 1, #ratesTable.fields do 107 | for k, v in pairs(ratesTable.fields[i]) do 108 | self.fields[i][k] = v 109 | end 110 | end 111 | if applyDefaults and self.ratesType ~= self.getRatesType(self) then 112 | for i = 1, #ratesTable.defaults do 113 | local f = self.fields[i] 114 | f.value = ratesTable.defaults[i] 115 | for idx=1, #f.vals do 116 | self.values[f.vals[idx]] = bit32.rshift(math.floor(f.value*(f.scale or 1) + 0.5), (idx-1)*8) 117 | end 118 | end 119 | else 120 | for i = 1, 9 do 121 | local f = self.fields[i] 122 | f.value = 0 123 | for idx=1, #f.vals do 124 | local raw_val = self.values[f.vals[idx]] or 0 125 | raw_val = bit32.lshift(raw_val, (idx-1)*8) 126 | f.value = bit32.bor(f.value, raw_val) 127 | end 128 | f.value = f.value/(f.scale or 1) 129 | end 130 | end 131 | self.ratesType = self.getRatesType(self) 132 | end, 133 | postLoad = function(self) 134 | if apiVersion >= 1.43 then 135 | self.updateRatesType(self) 136 | end 137 | end, 138 | } 139 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/rescue.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | if apiVersion >= 1.41 then 15 | fields[#fields + 1] = { t = "Min Sats.", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 50, vals = { 16 } } 16 | fields[#fields + 1] = { t = "Angle", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 1, 2 } } 17 | fields[#fields + 1] = { t = "Initial Altitude", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 20, max = 100, vals = { 3, 4 } } 18 | fields[#fields + 1] = { t = "Descent Distance", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 30, max = 500, vals = { 5, 6 } } 19 | 20 | if apiVersion >= 1.45 then 21 | fields[#fields + 1] = { t = "Ground Speed", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 3000, vals = { 7, 8 }, scale = 100 } 22 | else 23 | fields[#fields + 1] = { t = "Ground Speed", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 30, max = 3000, vals = { 7, 8 } } 24 | end 25 | 26 | if apiVersion >= 1.43 then 27 | fields[#fields + 1] = { t = "Ascend Rate", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 100, max = 2500, vals = { 17, 18 }, scale = 100 } 28 | fields[#fields + 1] = { t = "Descend Rate", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 100, max = 500, vals = { 19, 20 }, scale = 100 } 29 | fields[#fields + 1] = { t = "Arm w/o fix", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 21 }, table = { [0]="OFF","ON"} } 30 | fields[#fields + 1] = { t = "Altitude Mode",x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 2, vals = { 22 }, table = { [0]="Maximum", "Fixed", "Current"} } 31 | end 32 | 33 | fields[#fields + 1] = { t = "Sanity Check", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 2, vals = { 15 }, table = { [0]="OFF","ON","FS_ONLY"} } 34 | labels[#labels + 1] = { t = "Throttle", x = x, y = inc.y(lineSpacing) } 35 | fields[#fields + 1] = { t = "Min", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 2000, vals = { 9, 10 } } 36 | fields[#fields + 1] = { t = "Hover", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 2000, vals = { 13, 14 } } 37 | fields[#fields + 1] = { t = "Max", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 2000, vals = { 11, 12 } } 38 | 39 | if apiVersion >= 1.44 then 40 | fields[#fields + 1] = { t = "Min Dth", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 50, max = 1000, vals = { 23, 24 } } 41 | end 42 | end 43 | 44 | return { 45 | read = 135, -- MSP_GPS_RESCUE 46 | write = 225, -- MSP_SET_GPS_RESCUE 47 | title = "GPS Rescue", 48 | reboot = false, 49 | eepromWrite = true, 50 | minBytes = 16, 51 | labels = labels, 52 | fields = fields, 53 | } 54 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/rx.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | if apiVersion >= 1.16 then 15 | labels[#labels + 1] = { t = "Stick", x = x, y = inc.y(lineSpacing) } 16 | fields[#fields + 1] = { t = "Low", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 2000, vals = { 6, 7 } } 17 | fields[#fields + 1] = { t = "Center", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 2000, vals = { 4, 5 } } 18 | fields[#fields + 1] = { t = "High", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1000, max = 2000, vals = { 2, 3 } } 19 | end 20 | 21 | if apiVersion >= 1.44 then 22 | labels[#labels + 1] = { t = "RC Smoothing", x = x, y = inc.y(lineSpacing) } 23 | fields[#fields + 1] = { t = "Mode", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 25 }, table = { [0] = "ON", "OFF" } } 24 | labels[#labels + 1] = { t = "Cutoffs", x = x + indent, y = inc.y(lineSpacing) } 25 | fields[#fields + 1] = { t = "Setpoint", x = x + indent*2, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 255, vals = { 26 }, table = { [0] = "Auto" } } 26 | fields[#fields + 1] = { t = "Feedforward", x = x + indent*2, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 255, vals = { 27 }, table = { [0] = "Auto" } } 27 | labels[#labels + 1] = { t = "Auto Smoothness", x = x + indent, y = inc.y(lineSpacing) } 28 | fields[#fields + 1] = { t = "Setpoint", x = x + indent*2, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 250, vals = { 31 } } 29 | else 30 | if apiVersion >= 1.40 then 31 | labels[#labels + 1] = { t = "RC Smoothing", x = x, y = inc.y(lineSpacing) } 32 | fields[#fields + 1] = { t = "Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 25 }, table = { [0] = "Interpolation", "Filter" } } 33 | fields[#fields + 1] = { t = "Channels", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 4, vals = { 24 }, table = { [0] = "RP", "RPY", "RPYT", "T", "RT" } } 34 | labels[#labels + 1] = { t = "Input Filter", x = x, y = inc.y(lineSpacing) } 35 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 255, vals = { 26 }, table = { [0] = "Auto" } } 36 | fields[#fields + 1] = { t = "Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 28 }, table = { [0] = "PT1", "BIQUAD"} } 37 | labels[#labels + 1] = { t = "Derivative Filter", x = x, y = inc.y(lineSpacing) } 38 | fields[#fields + 1] = { t = "Cutoff", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 255, vals = { 27 }, table = { [0] = "Auto" } } 39 | fields[#fields + 1] = { t = "Type", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 3, vals = { 29 }, table = { [0] = "Off", "PT1", "BIQUAD", "Auto"} } 40 | end 41 | 42 | if apiVersion >= 1.20 then 43 | fields[#fields + 1] = { t = "Interpolation", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 3, vals = { 13 }, table={ [0]="Off", "Preset", "Auto", "Manual"} } 44 | fields[#fields + 1] = { t = "Interval", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 1, max = 50, vals = { 14 } } 45 | end 46 | 47 | if apiVersion >= 1.42 then 48 | fields[#fields + 1] = { t = "Auto Smoothness", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 50, vals = { 31 } } 49 | end 50 | end 51 | 52 | if apiVersion >= 1.31 then 53 | fields[#fields + 1] = { t = "Cam Angle", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 90, vals = { 23 } } 54 | end 55 | 56 | return { 57 | read = 44, -- MSP_RX_CONFIG 58 | write = 45, -- MSP_SET_RX_CONFIG 59 | title = "RX", 60 | reboot = false, 61 | eepromWrite = true, 62 | minBytes = 12, 63 | labels = labels, 64 | fields = fields, 65 | } 66 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/simplified_tuning.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local indent = template.indent 4 | local lineSpacing = template.lineSpacing 5 | local tableSpacing = template.tableSpacing 6 | local sp = template.listSpacing.field 7 | local yMinLim = radio.yMinLimit 8 | local x = margin 9 | local y = yMinLim - lineSpacing 10 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 11 | local labels = {} 12 | local fields = {} 13 | 14 | labels[#labels + 1] = { t = "Simplified PID", x = x, y = inc.y(lineSpacing) } 15 | fields[#fields + 1] = { t = "PID Tuning", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 2, vals = { 1 }, table = { [0] = "OFF", "RP", "RPY" } } 16 | fields[#fields + 1] = { t = "D Gains", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 5 }, scale = 100, mult = 5 } 17 | fields[#fields + 1] = { t = "P&I Gains", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 6 }, scale = 100, mult = 5 } 18 | fields[#fields + 1] = { t = "FF Gains", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 8 }, scale = 100, mult = 5 } 19 | fields[#fields + 1] = { t = "D Max", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 7 }, scale = 100, mult = 5 } 20 | fields[#fields + 1] = { t = "I Gains", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 4 }, scale = 100, mult = 5 } 21 | fields[#fields + 1] = { t = "Pitch:Roll D", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 3 }, scale = 100, mult = 5 } 22 | fields[#fields + 1] = { t = "Pitch:Roll P,I&FF", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 9 }, scale = 100, mult = 5 } 23 | fields[#fields + 1] = { t = "Master Multiplier", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 200, vals = { 2 }, scale = 100, mult = 5 } 24 | 25 | labels[#labels + 1] = { t = "Simplified Filter", x = x, y = inc.y(lineSpacing) } 26 | fields[#fields + 1] = { t = "Gyro Tuning", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 36 }, table = { [0] = "OFF", "ON" } } 27 | fields[#fields + 1] = { t = "Gyro Multiplier", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 10, max = 200, vals = { 37 }, scale = 100, mult = 5 } 28 | fields[#fields + 1] = { t = "D Tuning", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 0, max = 1, vals = { 18 }, table = { [0] = "OFF", "ON" } } 29 | fields[#fields + 1] = { t = "D Multiplier", x = x + indent, y = inc.y(lineSpacing), sp = x + sp, min = 10, max = 200, vals = { 19 }, scale = 100, mult = 5 } 30 | 31 | return { 32 | read = 140, -- MSP_SIMPLIFIED_TUNING 33 | write = 141, -- MSP_SET_SIMPLIFIED_TUNING 34 | title = "Simplified Tuning", 35 | reboot = false, 36 | eepromWrite = true, 37 | minBytes = 53, 38 | labels = labels, 39 | fields = fields, 40 | } 41 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/PAGES/vtx.lua: -------------------------------------------------------------------------------- 1 | local template = assert(loadScript(radio.template))() 2 | local margin = template.margin 3 | local lineSpacing = template.lineSpacing 4 | local sp = template.listSpacing.field 5 | local yMinLim = radio.yMinLimit 6 | local x = margin 7 | local y = yMinLim - lineSpacing 8 | local inc = { x = function(val) x = x + val return x end, y = function(val) y = y + val return y end } 9 | local labels = {} 10 | local fields = {} 11 | 12 | local vtx_tables 13 | if apiVersion >= 1.42 then 14 | vtx_tables = assert(loadScript("VTX_TABLES/"..mcuId..".lua"), "No VTX table!")() 15 | else 16 | vtx_tables = assert(loadScript("VTX_TABLES/vtx_defaults.lua"))() 17 | end 18 | local deviceTable = { [1]="6705", [3]="SA", [4]="Tramp", [255]="None" } 19 | local pitModeTable = { [0]="OFF", "ON" } 20 | 21 | if apiVersion >= 1.36 then 22 | fields[#fields + 1] = { t = "Band", x = x, y = inc.y(lineSpacing), sp = x + sp, min=0, max=#(vtx_tables.bandTable), vals = { 2 }, table = vtx_tables.bandTable, upd = function(self) self.handleBandChanUpdate(self) end } 23 | fields[#fields + 1] = { t = "Channel", x = x, y = inc.y(lineSpacing), sp = x + sp, min=1, max=vtx_tables.frequenciesPerBand, vals = { 3 }, upd = function(self) self.handleBandChanUpdate(self) end } 24 | fields[#fields + 1] = { t = "Power", x = x, y = inc.y(lineSpacing), sp = x + sp, min=1, vals = { 4 }, upd = function(self) self.updatePowerTable(self) end } 25 | fields[#fields + 1] = { t = "Pit Mode", x = x, y = inc.y(lineSpacing), sp = x + sp, min=0, max=#(pitModeTable), vals = { 5 }, table = pitModeTable } 26 | fields[#fields + 1] = { t = "Protocol", x = x, y = inc.y(lineSpacing), sp = x + sp, vals = { 1 }, write = false, ro = true, table = deviceTable } 27 | end 28 | 29 | if apiVersion >= 1.37 then 30 | fields[#fields + 1] = { t = "Frequency", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 5000, max = 5999, vals = { 6 }, upd = function(self) self.handleFreqValUpdate(self) end } 31 | elseif apiVersion >= 1.36 then 32 | fields[#fields + 1] = { t = "Frequency", x = x, y = inc.y(lineSpacing), sp = x + sp, min = 5000, max = 5999, ro = true } 33 | end 34 | 35 | -- Vals Fields 36 | -- 1 Device Type Band 37 | -- 2 Band Channel 38 | -- 3 Channel Power 39 | -- 4 Power Pit 40 | -- 5 Pit Device Type 41 | -- 6 Freq Frequency 42 | 43 | return { 44 | read = 88, -- MSP_VTX_CONFIG 45 | write = 89, -- MSP_VTX_SET_CONFIG 46 | eepromWrite = true, 47 | reboot = false, 48 | title = "VTX", 49 | minBytes = 5, 50 | prevBandVal = 0, 51 | prevChanVal = 0, 52 | prevFreqVal = 0, 53 | lastFreqUpdTS = 0, 54 | freqModCounter = 0, 55 | labels = labels, 56 | fields = fields, 57 | freqLookup = vtx_tables.frequencyTable, 58 | postLoad = function (self) 59 | if (self.values[2] or 0) < 0 or (self.values[3] or 0) == 0 or (self.values[4] or 0) == 0 then 60 | self.values = {} 61 | else 62 | self.prevBandVal = 0 -- reset value trackers 63 | self.prevChanVal = 0 64 | self.prevFreqVal = 0 65 | local rFreq 66 | if (self.values[7] or 0) > 0 then 67 | rFreq = math.floor(self.values[6] + (self.values[7] * 256)) 68 | else 69 | rFreq = 0 70 | end 71 | if (self.values[2] or 0) > 0 then -- band != 0 72 | if rFreq > 0 then 73 | self.prevFreqVal = rFreq 74 | self.prevBandVal = self.values[2] 75 | self.prevChanVal = self.values[3] 76 | self.fields[1].min = 0 -- make sure 'U' band allowed 77 | self.eepromWrite = true 78 | self.fields[6].value = rFreq 79 | self.values[6] = rFreq 80 | else -- if user freq not supported then 81 | self.fields[1].min = 1 -- don't allow 'U' band 82 | self.eepromWrite = false -- don't write EEPROM on older Betaflight versions 83 | end 84 | else -- band == 0 85 | if rFreq > 0 then 86 | self.prevFreqVal = rFreq 87 | self.fields[1].min = 0 -- make sure 'U' band allowed 88 | self.eepromWrite = true 89 | self.fields[6].value = rFreq 90 | self.values[6] = rFreq 91 | -- set chan via freq / 100 92 | self.prevChanVal = clipValue(math.floor((rFreq - 5100) / 100), 93 | self.fields[2].min, self.fields[2].max) 94 | self.fields[2].value = self.prevChanVal 95 | self.values[3] = self.prevChanVal 96 | else 97 | self.values = {} 98 | end 99 | end 100 | end 101 | end, 102 | preSave = function(self) 103 | local valsTemp = {} 104 | if self.values then 105 | local channel 106 | if self.values[2] > 0 then -- band != 0 107 | channel = (self.values[2]-1)*8 + self.values[3]-1 108 | elseif self.fields[6].value then -- band == 0 109 | channel = self.fields[6].value 110 | else 111 | channel = 24 112 | end 113 | valsTemp[1] = bit32.band(channel,0xFF) 114 | valsTemp[2] = bit32.rshift(channel,8) 115 | valsTemp[3] = self.values[4] 116 | valsTemp[4] = self.values[5] 117 | end 118 | return valsTemp 119 | end, 120 | -- find closest value in freq table that is above/below given freq 121 | findNextInFreqTable = function(self, newFreq) 122 | local startBand 123 | local endBand 124 | local incFlag -- freq increasing or decreasing 125 | if newFreq > self.prevFreqVal then 126 | incFlag = 1 127 | startBand = 1 128 | endBand = self.fields[1].max 129 | else 130 | incFlag = -1 131 | startBand = self.fields[1].max 132 | endBand = 1 133 | end 134 | local curBand = self.values[2] 135 | local curChan = self.values[3] 136 | local selBand = 0 137 | local selChan = 0 138 | local selFreq = 0 139 | local diffVal = 9999 140 | local fVal 141 | local minChan = self.fields[2].min 142 | local maxChan = self.fields[2].max 143 | -- need to scan bands in same "direction" as 'incFlag' 144 | -- so same-freq selections will be handled properly (F8 & R7) 145 | for band=startBand,endBand,incFlag do 146 | for chan=minChan,maxChan do 147 | if band ~= curBand or chan ~= curChan then -- don't reselect same band/chan 148 | fVal = self.freqLookup[band][chan] 149 | if incFlag > 0 then 150 | if fVal >= self.prevFreqVal and fVal - self.prevFreqVal < diffVal then 151 | -- if same freq then only select if "next" band: 152 | if fVal ~= self.prevFreqVal or band > curBand then 153 | selBand = band 154 | selChan = chan 155 | selFreq = fVal 156 | diffVal = fVal - self.prevFreqVal 157 | end 158 | end 159 | else 160 | if fVal <= self.prevFreqVal and self.prevFreqVal - fVal < diffVal then 161 | -- if same freq then only select if "previous" band: 162 | if fVal ~= self.prevFreqVal or band < curBand then 163 | selBand = band 164 | selChan = chan 165 | selFreq = fVal 166 | diffVal = self.prevFreqVal - fVal 167 | end 168 | end 169 | end 170 | end 171 | end 172 | end 173 | return selFreq, selBand, selChan 174 | end, 175 | -- returns the next user-frequency value in MHz; implements an 176 | -- "exponential" modification rate so dialing in values is faster 177 | getNextUserFreqValue = function(self, newFreq) 178 | local now = getTime() -- track rate of change for possible mod speedup 179 | if now < self.lastFreqUpdTS + 15 then 180 | self.freqModCounter = self.freqModCounter + (15-(self.lastFreqUpdTS-now)) -- increase counter for mod speedup 181 | else 182 | self.freqModCounter = 0 -- no mod speedup 183 | end 184 | local uFreq 185 | if self.freqModCounter > 65 then -- rate is fast enough; do mod speedup 186 | if newFreq > self.prevFreqVal then 187 | uFreq = clipValue(newFreq + math.floor(self.freqModCounter / 65), 188 | self.fields[6].min, self.fields[6].max) 189 | else 190 | uFreq = clipValue(newFreq - math.floor(self.freqModCounter / 65), 191 | self.fields[6].min, self.fields[6].max) 192 | end 193 | else 194 | uFreq = newFreq 195 | end 196 | self.lastFreqUpdTS = now 197 | return uFreq 198 | end, 199 | updatePowerTable = function(self) 200 | if self.values and not self.fields[3].table then 201 | if vtx_tables.powerTable then 202 | self.fields[3].table = vtx_tables.powerTable 203 | self.fields[3].max = #(vtx_tables.powerTable) 204 | else 205 | if self.values[1] == 1 then -- RTC6705 206 | self.fields[3].table = { 25, 200 } 207 | self.fields[3].max = 2 208 | self.fields[4].t = nil -- don't display Pit field 209 | self.fields[4].table = { [0]="", "" } 210 | elseif self.values[1] == 3 then -- SmartAudio 211 | self.fields[3].table = { 25, 200, 500, 800 } 212 | self.fields[3].max = 4 213 | elseif self.values[1] == 4 then -- Tramp 214 | self.fields[3].table = { 25, 100, 200, 400, 600 } 215 | self.fields[3].max = 5 216 | elseif self.values[1] == 255 then -- None/Unknown 217 | self.fields[3].t = nil -- don't display Power field 218 | self.fields[3].max = 1 219 | self.fields[3].table = { [1]="" } 220 | self.fields[4].t = nil -- don't display Pit field 221 | self.fields[4].table = { [0]="", "" } 222 | end 223 | end 224 | end 225 | end, 226 | handleBandChanUpdate = function(self) 227 | if (#(self.values) or 0) >= self.minBytes then 228 | if (self.values[3] or 0) > 0 then 229 | if self.values[2] ~= self.prevBandVal or self.values[3] ~= self.prevChanVal then 230 | if self.values[2] > 0 then -- band != 0 231 | self.prevFreqVal = self.freqLookup[self.values[2]][self.values[3]] 232 | else -- band == 0; set freq via channel*100 233 | self.prevFreqVal = math.floor(5100 + (self.values[3] * 100)) 234 | end 235 | self.fields[6].value = self.prevFreqVal 236 | self.values[6] = self.prevFreqVal 237 | self.prevBandVal = self.values[2] 238 | self.prevChanVal = self.values[3] 239 | end 240 | end 241 | end 242 | end, 243 | handleFreqValUpdate = function(self) 244 | if (#(self.values) or 0) >= self.minBytes and (self.fields[6].value or 0) > 0 then 245 | local newFreq = self.fields[6].value 246 | if newFreq ~= self.prevFreqVal then 247 | if self.values[2] == 0 then 248 | -- band == 0 249 | local uFreq = self.getNextUserFreqValue(self, newFreq) 250 | self.prevFreqVal = uFreq 251 | if uFreq ~= newFreq then 252 | self.fields[6].value = uFreq 253 | self.values[6] = uFreq 254 | end 255 | -- set channel value via freq/100 256 | self.prevChanVal = clipValue(math.floor((self.prevFreqVal - 5100) / 100), 257 | self.fields[2].min, self.fields[2].max) 258 | self.fields[2].value = self.prevChanVal 259 | self.values[3] = self.prevChanVal 260 | else 261 | -- band != 0; find closest freq in table that is above/below dialed freq 262 | local selFreq, selBand, selChan = self.findNextInFreqTable(self, newFreq) 263 | if selFreq > 0 then 264 | self.prevFreqVal = selFreq 265 | self.prevBandVal = selBand 266 | self.prevChanVal = selChan 267 | self.fields[6].value = selFreq -- using new freq from table 268 | self.values[6] = selFreq 269 | self.fields[1].value = self.prevBandVal -- set band value for freq 270 | self.values[2] = self.prevBandVal 271 | self.fields[2].value = self.prevChanVal -- set channel value for freq 272 | self.values[3] = self.prevChanVal 273 | else 274 | self.fields[6].value = self.prevFreqVal -- if no match then revert freq 275 | self.values[6] = self.prevFreqVal 276 | end 277 | end 278 | end 279 | end 280 | end 281 | } 282 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/RATETABLES/ACTUAL.lua: -------------------------------------------------------------------------------- 1 | return { 2 | labels = { "", "", "ROLL", "PITCH", "YAW", "Cntr", "Sens", "Max", "Rate", "", "Expo" }, 3 | fields = { 4 | { min = 1, max = 200, scale = 0.1 }, 5 | { min = 1, max = 200, scale = 0.1 }, 6 | { min = 1, max = 200, scale = 0.1 }, 7 | { min = 0, max = 200, scale = 0.1 }, 8 | { min = 0, max = 200, scale = 0.1 }, 9 | { min = 0, max = 200, scale = 0.1 }, 10 | { min = 0, max = 100, scale = 100 }, 11 | { min = 0, max = 100, scale = 100 }, 12 | { min = 0, max = 100, scale = 100 } 13 | }, 14 | defaults = { 200, 200, 200, 670, 670, 670, 0.54, 0.54, 0.54 } 15 | } 16 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/RATETABLES/BF.lua: -------------------------------------------------------------------------------- 1 | return { 2 | labels = { "", "", "ROLL", "PITCH", "YAW", "RC", "Rate", "Super", "Rate", "RC", "Expo" }, 3 | fields = { 4 | { min = 0, max = 255, scale = 100 }, 5 | { min = 0, max = 255, scale = 100 }, 6 | { min = 0, max = 255, scale = 100 }, 7 | { min = 0, max = 100, scale = 100 }, 8 | { min = 0, max = 100, scale = 100 }, 9 | { min = 0, max = 255, scale = 100 }, 10 | { min = 0, max = 100, scale = 100 }, 11 | { min = 0, max = 100, scale = 100 }, 12 | { min = 0, max = 100, scale = 100 } 13 | }, 14 | defaults = { 1.0, 1.0, 1.0, 0.7, 0.7, 0.7, 0.0, 0.0, 0.0 } 15 | } 16 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/RATETABLES/KISS.lua: -------------------------------------------------------------------------------- 1 | return { 2 | labels = { "", "", "ROLL", "PITCH", "YAW", "RC", "Rate", "", "Rate", "RC", "Curve" }, 3 | fields = { 4 | { min = 1, max = 255, scale = 100 }, 5 | { min = 1, max = 255, scale = 100 }, 6 | { min = 1, max = 255, scale = 100 }, 7 | { min = 0, max = 99, scale = 100 }, 8 | { min = 0, max = 99, scale = 100 }, 9 | { min = 0, max = 99, scale = 100 }, 10 | { min = 0, max = 100, scale = 100 }, 11 | { min = 0, max = 100, scale = 100 }, 12 | { min = 0, max = 100, scale = 100 } 13 | }, 14 | defaults = { 1.0, 1.0, 1.0, 0.7, 0.7, 0.7, 0.0, 0.0, 0.0 } 15 | } 16 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/RATETABLES/QUICK.lua: -------------------------------------------------------------------------------- 1 | return { 2 | labels = { "", "", "ROLL", "PITCH", "YAW", "RC", "Rate", "Max", "Rate", "", "Expo" }, 3 | fields = { 4 | { min = 1, max = 255, scale = 100 }, 5 | { min = 1, max = 255, scale = 100 }, 6 | { min = 1, max = 255, scale = 100 }, 7 | { min = 0, max = 200, scale = 0.1 }, 8 | { min = 0, max = 200, scale = 0.1 }, 9 | { min = 0, max = 200, scale = 0.1 }, 10 | { min = 0, max = 100, scale = 100 }, 11 | { min = 0, max = 100, scale = 100 }, 12 | { min = 0, max = 100, scale = 100 } 13 | }, 14 | defaults = { 1.0, 1.0, 1.0, 670, 670, 670, 0.0, 0.0, 0.0 } 15 | } 16 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/RATETABLES/RF.lua: -------------------------------------------------------------------------------- 1 | return { 2 | labels = { "", "", "ROLL", "PITCH", "YAW", "", "Rate", "", "Acro+", "", "Expo" }, 3 | fields = { 4 | { min = 1, max = 200, scale = 0.1 }, 5 | { min = 1, max = 200, scale = 0.1 }, 6 | { min = 1, max = 200, scale = 0.1 }, 7 | { min = 0, max = 100, scale = 1 }, 8 | { min = 0, max = 100, scale = 1 }, 9 | { min = 0, max = 100, scale = 1 }, 10 | { min = 0, max = 100, scale = 1 }, 11 | { min = 0, max = 100, scale = 1 }, 12 | { min = 0, max = 100, scale = 1 } 13 | }, 14 | defaults = { 370, 370, 370, 80, 80, 80, 50, 50, 50 } 15 | } 16 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/TEMPLATES/128x64.lua: -------------------------------------------------------------------------------- 1 | return { 2 | margin = 2, 3 | indent = 6, 4 | lineSpacing = 8, 5 | listSpacing = { line = 8, field = 88 }, 6 | tableSpacing = { row = 10, col = 30, header = 8 }, 7 | } 8 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/TEMPLATES/128x96.lua: -------------------------------------------------------------------------------- 1 | return { 2 | margin = 2, 3 | indent = 6, 4 | lineSpacing = 8, 5 | listSpacing = { line = 8, field = 88 }, 6 | tableSpacing = { row = 10, col = 30, header = 8 }, 7 | } 8 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/TEMPLATES/212x64.lua: -------------------------------------------------------------------------------- 1 | return { 2 | margin = 2, 3 | indent = 6, 4 | lineSpacing = 8, 5 | listSpacing = { line = 8, field = 88 }, 6 | tableSpacing = { row = 10, col = 30, header = 8 }, 7 | } 8 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/TEMPLATES/320x480.lua: -------------------------------------------------------------------------------- 1 | return { 2 | margin = 5, 3 | indent = 15, 4 | lineSpacing = 20, 5 | listSpacing = { line = 20, field = 170 }, 6 | tableSpacing = { row = 25, col = 60, header = 20 }, 7 | } 8 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/TEMPLATES/480x272.lua: -------------------------------------------------------------------------------- 1 | return { 2 | margin = 5, 3 | indent = 15, 4 | lineSpacing = 20, 5 | listSpacing = { line = 20, field = 170 }, 6 | tableSpacing = { row = 25, col = 60, header = 20 }, 7 | } 8 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/TEMPLATES/480x320.lua: -------------------------------------------------------------------------------- 1 | return { 2 | margin = 5, 3 | indent = 15, 4 | lineSpacing = 22, 5 | listSpacing = { line = 20, field = 170 }, 6 | tableSpacing = { row = 25, col = 60, header = 20 }, 7 | } 8 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/VTX_TABLES/vtx_defaults.lua: -------------------------------------------------------------------------------- 1 | return { 2 | frequencyTable = { 3 | { 5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725 }, -- Boscam A 4 | { 5733, 5752, 5771, 5790, 5809, 5828, 5847, 5866 }, -- Boscam B 5 | { 5705, 5685, 5665, 5645, 5885, 5905, 5925, 5945 }, -- Boscam E 6 | { 5740, 5760, 5780, 5800, 5820, 5840, 5860, 5880 }, -- FatShark 7 | { 5658, 5695, 5732, 5769, 5806, 5843, 5880, 5917 }, -- RaceBand 8 | }, 9 | frequenciesPerBand = 8, 10 | bandTable = { [0]="U", "A", "B", "E", "F", "R" }, 11 | } 12 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/acc_cal.lua: -------------------------------------------------------------------------------- 1 | local MSP_ACC_CALIBRATION = 205 2 | local accCalibrated = false 3 | local lastRunTS = 0 4 | local INTERVAL = 500 5 | 6 | local function processMspReply(cmd,rx_buf,err) 7 | if cmd == MSP_ACC_CALIBRATION and not err then 8 | accCalibrated = true 9 | end 10 | end 11 | 12 | local function accCal() 13 | if lastRunTS == 0 or lastRunTS + INTERVAL < getTime() then 14 | lastRunTS = getTime() 15 | if not accCalibrated then 16 | protocol.mspRead(MSP_ACC_CALIBRATION) 17 | end 18 | end 19 | mspProcessTxQ() 20 | processMspReply(mspPollReply()) 21 | return accCalibrated 22 | end 23 | 24 | return { f = accCal, t = "Calibrating Accelerometer" } 25 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/api_version.lua: -------------------------------------------------------------------------------- 1 | local MSP_API_VERSION = 1 2 | 3 | local apiVersionReceived = false 4 | local lastRunTS = 0 5 | local INTERVAL = 50 6 | 7 | local function processMspReply(cmd,rx_buf,err) 8 | if cmd == MSP_API_VERSION and #rx_buf >= 3 and not err then 9 | apiVersion = rx_buf[2] + rx_buf[3] / 100 10 | apiVersionReceived = true 11 | end 12 | end 13 | 14 | local function getApiVersion() 15 | if lastRunTS == 0 or lastRunTS + INTERVAL < getTime() then 16 | protocol.mspRead(MSP_API_VERSION) 17 | lastRunTS = getTime() 18 | end 19 | 20 | mspProcessTxQ() 21 | processMspReply(mspPollReply()) 22 | 23 | return apiVersionReceived 24 | end 25 | 26 | return { f = getApiVersion, t = "Waiting for API version" } 27 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/background.lua: -------------------------------------------------------------------------------- 1 | local apiVersionReceived = false 2 | local timeIsSet = false 3 | local getApiVersion, setRtc, rssiTask 4 | local rssiEnabled = true 5 | 6 | local function run_bg() 7 | if getRSSI() > 0 then 8 | -- Send data when the telemetry connection is available 9 | -- assuming when sensor value higher than 0 there is an telemetry connection 10 | if not apiVersionReceived then 11 | getApiVersion = getApiVersion or assert(loadScript("api_version.lua"))() 12 | apiVersionReceived = getApiVersion.f() 13 | if apiVersionReceived then 14 | getApiVersion = nil 15 | collectgarbage() 16 | end 17 | elseif not timeIsSet then 18 | setRtc = setRtc or assert(loadScript("rtc.lua"))() 19 | timeIsSet = setRtc.f() 20 | if timeIsSet then 21 | setRtc = nil 22 | collectgarbage() 23 | end 24 | elseif rssiEnabled and apiVersion >= 1.37 then 25 | rssiTask = rssiTask or assert(loadScript("rssi.lua"))() 26 | rssiEnabled = rssiTask() 27 | if not rssiEnabled then 28 | rssiTask = nil 29 | collectgarbage() 30 | end 31 | end 32 | else 33 | apiVersionReceived = false 34 | timeIsSet = false 35 | rssiEnabled = true 36 | if getApiVersion or setRtc or rssiTask then 37 | getApiVersion = nil 38 | setRtc = nil 39 | rssiTask = nil 40 | collectgarbage() 41 | end 42 | end 43 | end 44 | 45 | return run_bg 46 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/board_info.lua: -------------------------------------------------------------------------------- 1 | local MSP_BOARD_INFO = 4 2 | 3 | local boardInfoReceived = false 4 | 5 | local boardIdentifier = "" 6 | local hardwareRevision = 0 7 | local boardType = 0 8 | local targetCapabilities = 0 9 | local targetName = "" 10 | local boardName = "" 11 | local manufacturerId = "" 12 | local signature = {} 13 | local mcuTypeId = 255 14 | local configurationState = 0 15 | local gyroSampleRateHz = 0 16 | local configurationProblems = 0 17 | local spiRegisteredDeviceCount = 0 18 | local i2cRegisteredDeviceCount = 0 19 | 20 | local lastRunTS = 0 21 | local INTERVAL = 100 22 | 23 | local function processMspReply(cmd, payload, err) 24 | if cmd == MSP_BOARD_INFO and not err then 25 | local length 26 | local i = 1 27 | length = 4 28 | for c = 1, 4 do 29 | boardIdentifier = boardIdentifier..string.char(payload[i]) 30 | i = i + 1 31 | end 32 | for idx = 1, 2 do 33 | local raw_val = bit32.lshift(payload[i], (idx-1)*8) 34 | hardwareRevision = bit32.bor(hardwareRevision, raw_val) 35 | i = i + 1 36 | end 37 | if apiVersion >= 1.35 then 38 | boardType = payload[i] 39 | end 40 | i = i + 1 41 | if apiVersion >= 1.37 then 42 | targetCapabilities = payload[i] 43 | i = i + 1 44 | length = payload[i] 45 | i = i + 1 46 | for c = 1, length do 47 | targetName = targetName..string.char(payload[i]) 48 | i = i + 1 49 | end 50 | end 51 | if apiVersion >= 1.39 then 52 | length = payload[i] 53 | i = i + 1 54 | for c = 1, length do 55 | boardName = boardName..string.char(payload[i]) 56 | i = i + 1 57 | end 58 | length = payload[i] 59 | i = i + 1 60 | for c = 1, length do 61 | manufacturerId = manufacturerId..string.char(payload[i]) 62 | i = i + 1 63 | end 64 | length = 32 65 | for c = 1, 32 do 66 | signature[#signature + 1] = payload[i] 67 | i = i + 1 68 | end 69 | end 70 | i = i + 1 71 | if apiVersion >= 1.41 then 72 | mcuTypeId = payload[i] 73 | end 74 | i = i + 1 75 | if apiVersion >= 1.42 then 76 | configurationState = payload[i] 77 | end 78 | if apiVersion >= 1.43 then 79 | for idx = 1, 2 do 80 | local raw_val = bit32.lshift(payload[i], (idx-1)*8) 81 | gyroSampleRateHz = bit32.bor(gyroSampleRateHz, raw_val) 82 | i = i + 1 83 | end 84 | for idx = 1, 4 do 85 | local raw_val = bit32.lshift(payload[i], (idx-1)*8) 86 | configurationProblems = bit32.bor(configurationProblems, raw_val) 87 | i = i + 1 88 | end 89 | end 90 | if apiVersion >= 1.44 then 91 | spiRegisteredDeviceCount = payload[i] 92 | i = i + 1 93 | i2cRegisteredDeviceCount = payload[i] 94 | end 95 | boardInfoReceived = true 96 | end 97 | end 98 | 99 | local function getBoardInfo() 100 | if lastRunTS + INTERVAL < getTime() then 101 | lastRunTS = getTime() 102 | if not boardInfoReceived then 103 | protocol.mspRead(MSP_BOARD_INFO) 104 | end 105 | end 106 | mspProcessTxQ() 107 | processMspReply(mspPollReply()) 108 | if boardInfoReceived then 109 | local f = io.open("BOARD_INFO/"..mcuId..".lua", 'w') 110 | io.write(f, "return {", "\n") 111 | io.write(f, " boardIdentifier = "..'"'..boardIdentifier..'"'..",", "\n") 112 | io.write(f, " hardwareRevision = "..tostring(hardwareRevision)..",", "\n") 113 | io.write(f, " boardType = "..tostring(boardType)..",", "\n") 114 | io.write(f, " targetCapabilities = "..tostring(targetCapabilities)..",", "\n") 115 | io.write(f, " targetName = "..'"'..targetName..'"'..",", "\n") 116 | io.write(f, " boardName = "..'"'..boardName..'"'..",", "\n") 117 | io.write(f, " manufacturerId = "..'"'..manufacturerId..'"'..",", "\n") 118 | local signatureString = " signature = { " 119 | for i = 1, #signature do 120 | signatureString = signatureString..tostring(signature[i])..", " 121 | end 122 | signatureString = signatureString.."}," 123 | io.write(f, signatureString, "\n") 124 | io.write(f, " mcuTypeId = "..tostring(mcuTypeId)..",", "\n") 125 | io.write(f, " configurationState = "..tostring(configurationState)..",", "\n") 126 | io.write(f, " gyroSampleRateHz = "..tostring(gyroSampleRateHz)..",", "\n") 127 | io.write(f, " configurationProblems = "..tostring(configurationProblems)..",", "\n") 128 | io.write(f, " spiRegisteredDeviceCount = "..tostring(spiRegisteredDeviceCount)..",", "\n") 129 | io.write(f, " i2cRegisteredDeviceCount = "..tostring(i2cRegisteredDeviceCount)..",", "\n") 130 | io.write(f, "}", "\n") 131 | io.close(f) 132 | assert(loadScript("BOARD_INFO/"..mcuId..".lua", 'c')) 133 | end 134 | return boardInfoReceived 135 | end 136 | 137 | return { f = getBoardInfo, t = "Downloading board info" } 138 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/cms.lua: -------------------------------------------------------------------------------- 1 | local lastMenuEventTime = 0 2 | local INTERVAL = 80 3 | 4 | local function init() 5 | cms.init(radio) 6 | end 7 | 8 | local function stickMovement() 9 | local threshold = 30 10 | return math.abs(getValue('ele')) > threshold or math.abs(getValue('ail')) > threshold or math.abs(getValue('rud')) > threshold 11 | end 12 | 13 | local function run(event) 14 | cms.update() 15 | if cms.menuOpen == false then 16 | cms.open() 17 | end 18 | if event == radio.refresh.event then 19 | cms.synced = false 20 | lastMenuEventTime = 0 21 | elseif stickMovement() then 22 | cms.synced = false 23 | lastMenuEventTime = getTime() 24 | elseif event == EVT_VIRTUAL_EXIT then 25 | cms.close() 26 | return 1 27 | end 28 | if lastMenuEventTime + INTERVAL < getTime() and not cms.synced then 29 | lastMenuEventTime = getTime() 30 | cms.refresh() 31 | end 32 | return 0 33 | end 34 | 35 | return { init=init, run=run } 36 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/features.lua: -------------------------------------------------------------------------------- 1 | local features = { 2 | vtx = true, 3 | gps = true, 4 | osdSD = true, 5 | blackbox = true, 6 | } 7 | 8 | return features 9 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/features_info.lua: -------------------------------------------------------------------------------- 1 | local MSP_GPS_CONFIG = 135 2 | local MSP_VTX_CONFIG = 88 3 | local MSP_OSD_CONFIG = 84 4 | 5 | local MSP_BUILD_INFO = 5 6 | 7 | local BUILD_OPTION_GPS = 16412 8 | local BUILD_OPTION_VTX = 16421 9 | local BUILD_OPTION_OSD_SD = 16416 10 | 11 | local isGpsRead = false 12 | local isVtxRead = false 13 | local isOsdSDRead = false 14 | 15 | local lastRunTS = 0 16 | local INTERVAL = 100 17 | local isInFlight = false 18 | 19 | local returnTable = { 20 | f = nil, 21 | t = "", 22 | } 23 | 24 | local function processBuildInfoReply(payload) 25 | local headLength = 26 -- DATE(11) + TIME(8) + REVISION(7) 26 | local optionsLength = #payload - headLength 27 | if (optionsLength <= 0) or ((optionsLength % 2) ~= 0) then 28 | return -- invalid payload 29 | end 30 | 31 | features.gps = false 32 | features.vtx = false 33 | features.osdSD = false 34 | for i = headLength + 1, #payload, 2 do 35 | local byte1 = bit32.lshift(payload[i], 0) 36 | local byte2 = bit32.lshift(payload[i + 1], 8) 37 | local word = bit32.bor(byte1, byte2) 38 | if word == BUILD_OPTION_GPS then 39 | features.gps = true 40 | elseif word == BUILD_OPTION_VTX then 41 | features.vtx = true 42 | elseif word == BUILD_OPTION_OSD_SD then 43 | features.osdSD = true 44 | end 45 | end 46 | end 47 | 48 | local function processMspReply(cmd, payload, err) 49 | isInFlight = false 50 | local isOkay = not err 51 | if cmd == MSP_BUILD_INFO then 52 | isGpsRead = true 53 | isVtxRead = true 54 | isOsdSDRead = true 55 | if isOkay then 56 | processBuildInfoReply(payload) 57 | end 58 | elseif cmd == MSP_GPS_CONFIG then 59 | isGpsRead = true 60 | local providerSet = payload[1] ~= 0 61 | features.gps = isOkay and providerSet 62 | elseif cmd == MSP_VTX_CONFIG then 63 | isVtxRead = true 64 | local vtxTableAvailable = payload[12] ~= 0 65 | features.vtx = isOkay and vtxTableAvailable 66 | elseif cmd == MSP_OSD_CONFIG then 67 | isOsdSDRead = true 68 | local osdSDAvailable = payload[1] ~= 0 69 | features.osdSD = isOkay and osdSDAvailable 70 | end 71 | end 72 | 73 | local function updateFeatures() 74 | if lastRunTS + INTERVAL < getTime() then 75 | lastRunTS = getTime() 76 | local cmd 77 | if apiVersion >= 1.46 then 78 | cmd = MSP_BUILD_INFO 79 | returnTable.t = "Checking options..." 80 | elseif not isGpsRead then 81 | cmd = MSP_GPS_CONFIG 82 | returnTable.t = "Checking GPS..." 83 | elseif not isVtxRead then 84 | cmd = MSP_VTX_CONFIG 85 | returnTable.t = "Checking VTX..." 86 | elseif not isOsdSDRead then 87 | cmd = MSP_OSD_CONFIG 88 | returnTable.t = "Checking OSD (SD)..." 89 | end 90 | if cmd and not isInFlight then 91 | protocol.mspRead(cmd) 92 | isInFlight = true 93 | end 94 | end 95 | mspProcessTxQ() 96 | processMspReply(mspPollReply()) 97 | return isGpsRead and isVtxRead and isOsdSDRead 98 | end 99 | 100 | returnTable.f = updateFeatures 101 | 102 | return returnTable 103 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/mcu_id.lua: -------------------------------------------------------------------------------- 1 | local MSP_UID = 160 2 | 3 | local MCUIdReceived = false 4 | 5 | local lastRunTS = 0 6 | local INTERVAL = 100 7 | 8 | local function processMspReply(cmd, payload, err) 9 | if cmd == MSP_UID and not err then 10 | local i = 1 11 | local id = "" 12 | for j = 1, 3 do 13 | local s = "" 14 | for k = 1, 4 do 15 | s = string.format("%02x", payload[i])..s 16 | i = i + 1 17 | end 18 | id = id..s 19 | end 20 | mcuId = id 21 | MCUIdReceived = true 22 | end 23 | end 24 | 25 | local function getMCUId() 26 | if lastRunTS + INTERVAL < getTime() then 27 | lastRunTS = getTime() 28 | if not MCUIdReceived then 29 | protocol.mspRead(MSP_UID) 30 | end 31 | end 32 | mspProcessTxQ() 33 | processMspReply(mspPollReply()) 34 | return MCUIdReceived 35 | end 36 | 37 | return { f = getMCUId, t = "Waiting for device ID" } 38 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/pages.lua: -------------------------------------------------------------------------------- 1 | local PageFiles = {} 2 | 3 | if apiVersion >= 1.36 and features.vtx then 4 | PageFiles[#PageFiles + 1] = { title = "VTX Settings", script = "vtx.lua", init = "PAGES/INIT/vtx.lua" } 5 | end 6 | 7 | if apiVersion >= 1.16 then 8 | PageFiles[#PageFiles + 1] = { title = "Profiles", script = "profiles.lua" } 9 | end 10 | 11 | if apiVersion >= 1.16 then 12 | PageFiles[#PageFiles + 1] = { title = "PIDs 1", script = "pids1.lua" } 13 | end 14 | 15 | if apiVersion >= 1.21 then 16 | PageFiles[#PageFiles + 1] = { title = "PIDs 2", script = "pids2.lua" } 17 | end 18 | 19 | if apiVersion >= 1.16 then 20 | PageFiles[#PageFiles + 1] = { title = "Rates", script = "rates.lua" } 21 | end 22 | 23 | if apiVersion >= 1.16 then 24 | PageFiles[#PageFiles + 1] = { title = "Advanced PIDs", script = "pid_advanced.lua" } 25 | end 26 | 27 | if apiVersion >= 1.44 then 28 | PageFiles[#PageFiles + 1] = { title = "Simplified Tuning", script = "simplified_tuning.lua" } 29 | end 30 | 31 | if apiVersion >= 1.16 then 32 | PageFiles[#PageFiles + 1] = { title = "Filters 1", script = "filters1.lua" } 33 | end 34 | 35 | if apiVersion >= 1.42 then 36 | PageFiles[#PageFiles + 1] = { title = "Filters 2", script = "filters2.lua" } 37 | end 38 | 39 | if apiVersion >= 1.16 then 40 | PageFiles[#PageFiles + 1] = { title = "System / Motor", script = "pwm.lua", init = "PAGES/INIT/pwm.lua" } 41 | end 42 | 43 | if apiVersion >= 1.16 then 44 | PageFiles[#PageFiles + 1] = { title = "Receiver", script = "rx.lua" } 45 | end 46 | 47 | if apiVersion >= 1.16 then 48 | PageFiles[#PageFiles + 1] = { title = "Failsafe", script = "failsafe.lua" } 49 | end 50 | 51 | if apiVersion >= 1.41 and features.gps then 52 | PageFiles[#PageFiles + 1] = { title = "GPS Rescue", script = "rescue.lua" } 53 | end 54 | 55 | if apiVersion >= 1.41 and features.gps then 56 | PageFiles[#PageFiles + 1] = { title = "GPS PIDs", script = "gpspids.lua" } 57 | end 58 | 59 | if apiVersion >= 1.16 then 60 | PageFiles[#PageFiles + 1] = { title = "Trim Accelerometer", script = "acc_trim.lua" } 61 | end 62 | 63 | if apiVersion >= 1.45 and features.osdSD then 64 | PageFiles[#PageFiles + 1] = { title = "OSD Elements", script = "pos_osd.lua" } 65 | end 66 | 67 | return PageFiles 68 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/protocols.lua: -------------------------------------------------------------------------------- 1 | local supportedProtocols = 2 | { 3 | smartPort = 4 | { 5 | mspTransport = "MSP/sp.lua", 6 | push = sportTelemetryPush, 7 | maxTxBufferSize = 6, 8 | maxRxBufferSize = 6, 9 | saveMaxRetries = 2, 10 | saveTimeout = 500, 11 | cms = {}, 12 | }, 13 | crsf = 14 | { 15 | mspTransport = "MSP/crsf.lua", 16 | cmsTransport = "CMS/crsf.lua", 17 | push = crossfireTelemetryPush, 18 | maxTxBufferSize = 8, 19 | maxRxBufferSize = 58, 20 | saveMaxRetries = 2, 21 | saveTimeout = 150, 22 | cms = {}, 23 | }, 24 | ghst = 25 | { 26 | mspTransport = "MSP/ghst.lua", 27 | push = ghostTelemetryPush, 28 | maxTxBufferSize = 10, -- Tx -> Rx (Push) 29 | maxRxBufferSize = 6, -- Rx -> Tx (Pop) 30 | saveMaxRetries = 2, 31 | saveTimeout = 250, 32 | cms = {}, 33 | } 34 | } 35 | 36 | local function getProtocol() 37 | if supportedProtocols.smartPort.push() ~= nil then 38 | return supportedProtocols.smartPort 39 | elseif supportedProtocols.crsf.push() ~= nil then 40 | return supportedProtocols.crsf 41 | elseif supportedProtocols.ghst.push() ~= nil then 42 | return supportedProtocols.ghst 43 | end 44 | end 45 | 46 | local protocol = assert(getProtocol(), "Telemetry protocol not supported!") 47 | 48 | return protocol 49 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/radios.lua: -------------------------------------------------------------------------------- 1 | local supportedRadios = 2 | { 3 | ["128x64"] = 4 | { 5 | msp = { 6 | template = "TEMPLATES/128x64.lua", 7 | MenuBox = { x=15, y=12, w=100, x_offset=36, h_line=8, h_offset=3 }, 8 | SaveBox = { x=15, y=12, w=100, x_offset=4, h=30, h_offset=5 }, 9 | NoTelem = { 30, 55, "No Telemetry", BLINK }, 10 | textSize = SMLSIZE, 11 | yMinLimit = 12, 12 | yMaxLimit = 52, 13 | }, 14 | cms = { 15 | rows = 8, 16 | cols = 26, 17 | pixelsPerRow = 8, 18 | pixelsPerChar = 5, 19 | xIndent = 0, 20 | yOffset = 0, 21 | textSize = SMLSIZE, 22 | refresh = { 23 | event = EVT_VIRTUAL_ENTER, 24 | text = "Refresh: [ENT]", 25 | top = 1, 26 | left = 64, 27 | }, 28 | }, 29 | }, 30 | ["128x96"] = 31 | { 32 | msp = { 33 | template = "TEMPLATES/128x96.lua", 34 | MenuBox = { x=15, y=12, w=100, x_offset=36, h_line=8, h_offset=3 }, 35 | SaveBox = { x=15, y=12, w=100, x_offset=4, h=30, h_offset=5 }, 36 | NoTelem = { 30, 87, "No Telemetry", BLINK }, 37 | textSize = SMLSIZE, 38 | yMinLimit = 12, 39 | yMaxLimit = 84, 40 | }, 41 | cms = { 42 | rows = 12, 43 | cols = 26, 44 | pixelsPerRow = 8, 45 | pixelsPerChar = 5, 46 | xIndent = 0, 47 | yOffset = 0, 48 | textSize = SMLSIZE, 49 | refresh = { 50 | event = EVT_VIRTUAL_ENTER, 51 | text = "Refresh: [ENT]", 52 | top = 1, 53 | left = 64, 54 | }, 55 | }, 56 | }, 57 | ["212x64"] = 58 | { 59 | msp = { 60 | template = "TEMPLATES/212x64.lua", 61 | MenuBox = { x=40, y=12, w=120, x_offset=36, h_line=8, h_offset=3 }, 62 | SaveBox = { x=40, y=12, w=120, x_offset=4, h=30, h_offset=5 }, 63 | NoTelem = { 70, 55, "No Telemetry", BLINK }, 64 | textSize = SMLSIZE, 65 | yMinLimit = 12, 66 | yMaxLimit = 52, 67 | }, 68 | cms = { 69 | rows = 8, 70 | cols = 32, 71 | pixelsPerRow = 8, 72 | pixelsPerChar = 6, 73 | xIndent = 0, 74 | yOffset = 0, 75 | textSize = SMLSIZE, 76 | refresh = { 77 | event = EVT_VIRTUAL_INC, 78 | text = "Refresh: [+]", 79 | top = 1, 80 | left = 156, 81 | } 82 | }, 83 | }, 84 | ["480x272"] = 85 | { 86 | msp = { 87 | template = "TEMPLATES/480x272.lua", 88 | highRes = true, 89 | MenuBox = { x=120, y=100, w=200, x_offset=68, h_line=20, h_offset=6 }, 90 | SaveBox = { x=120, y=100, w=180, x_offset=12, h=60, h_offset=12 }, 91 | NoTelem = { 192, LCD_H - 28, "No Telemetry", (TEXT_COLOR or 0) + INVERS + BLINK }, 92 | textSize = 0, 93 | yMinLimit = 35, 94 | yMaxLimit = 235, 95 | }, 96 | cms = { 97 | rows = 9, 98 | cols = 32, 99 | pixelsPerRow = 24, 100 | pixelsPerChar = 14, 101 | xIndent = 14, 102 | yOffset = 32, 103 | textSize = MIDSIZE, 104 | refresh = { 105 | event = EVT_VIRTUAL_ENTER, 106 | text = "Refresh: [ENT]", 107 | top = 1, 108 | left = 300, 109 | } 110 | }, 111 | }, 112 | ["480x320"] = 113 | { 114 | msp = { 115 | template = "TEMPLATES/480x320.lua", 116 | highRes = true, 117 | MenuBox = { x=120, y=100, w=200, x_offset=68, h_line=20, h_offset=6 }, 118 | SaveBox = { x=120, y=100, w=180, x_offset=12, h=60, h_offset=12 }, 119 | NoTelem = { 192, LCD_H - 28, "No Telemetry", (TEXT_COLOR or 0) + INVERS + BLINK }, 120 | textSize = 0, 121 | yMinLimit = 35, 122 | yMaxLimit = 280, 123 | }, 124 | cms = { 125 | rows = 9, 126 | cols = 32, 127 | pixelsPerRow = 24, 128 | pixelsPerChar = 14, 129 | xIndent = 14, 130 | yOffset = 32, 131 | textSize = MIDSIZE, 132 | refresh = { 133 | event = EVT_VIRTUAL_ENTER, 134 | text = "Refresh: [ENT]", 135 | top = 1, 136 | left = 300, 137 | } 138 | }, 139 | }, 140 | ["320x480"] = 141 | { 142 | msp = { 143 | template = "TEMPLATES/320x480.lua", 144 | highRes = true, 145 | MenuBox = { x= (LCD_W -200)/2, y=LCD_H/2, w=200, x_offset=68, h_line=20, h_offset=6 }, 146 | SaveBox = { x= (LCD_W -200)/2, y=LCD_H/2, w=180, x_offset=12, h=60, h_offset=12 }, 147 | NoTelem = { LCD_W/2 - 50, LCD_H - 28, "No Telemetry", (TEXT_COLOR or 0) + INVERS + BLINK }, 148 | textSize = 0, 149 | yMinLimit = 35, 150 | yMaxLimit = 435, 151 | }, 152 | cms = nil, 153 | }, 154 | } 155 | 156 | local resolution = LCD_W.."x"..LCD_H 157 | local radio = assert(supportedRadios[resolution], resolution.." not supported") 158 | 159 | return radio 160 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/rssi.lua: -------------------------------------------------------------------------------- 1 | local MSP_SET_TX_INFO = 186 2 | local MSP_TX_INFO = 187 3 | 4 | local RSSI_SOURCE_NONE = 0 5 | local RSSI_SOURCE_MSP = 4 6 | 7 | local rssiSourceReceived = false 8 | local rssiSource = RSSI_SOURCE_NONE 9 | local lastRunTS = 0 10 | local INTERVAL = 50 11 | 12 | local function processMspReply(cmd,rx_buf,err) 13 | if cmd == MSP_TX_INFO and #rx_buf >= 1 and not err then 14 | rssiSource = rx_buf[1] 15 | rssiSourceReceived = true 16 | end 17 | end 18 | 19 | local function rssiTask() 20 | if lastRunTS == 0 or lastRunTS + INTERVAL < getTime() then 21 | if not rssiSourceReceived then 22 | protocol.mspRead(MSP_TX_INFO) 23 | elseif rssiSource == RSSI_SOURCE_NONE or rssiSource == RSSI_SOURCE_MSP then 24 | local rssi, alarm_low, alarm_crit = getRSSI() 25 | -- Scale the [0, 99] RSSI values to [0, 255] 26 | rssi = rssi * 255 / 99 27 | if rssi > 255 then 28 | rssi = 255 29 | end 30 | 31 | local values = {} 32 | values[1] = rssi 33 | 34 | protocol.mspWrite(MSP_SET_TX_INFO, values) 35 | end 36 | lastRunTS = getTime() 37 | end 38 | 39 | mspProcessTxQ() 40 | 41 | processMspReply(mspPollReply()) 42 | 43 | return rssiSource == RSSI_SOURCE_NONE or rssiSource == RSSI_SOURCE_MSP 44 | end 45 | 46 | return rssiTask 47 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/rtc.lua: -------------------------------------------------------------------------------- 1 | local MSP_SET_RTC = 246 2 | 3 | local timeIsSet = false 4 | local lastRunTS = 0 5 | local INTERVAL = 50 6 | 7 | local function processMspReply(cmd,rx_buf,err) 8 | if cmd == MSP_SET_RTC and not err then 9 | timeIsSet = true 10 | end 11 | end 12 | 13 | local function setRtc() 14 | if lastRunTS == 0 or lastRunTS + INTERVAL < getTime() then 15 | -- only send datetime one time after telemetry connection became available 16 | -- or when connection is restored after e.g. lipo refresh 17 | local values = {} 18 | if apiVersion >= 1.41 then 19 | -- format: seconds after the epoch (32) / milliseconds (16) 20 | local now = getRtcTime() 21 | 22 | for i = 1, 4 do 23 | values[i] = bit32.band(now, 0xFF) 24 | now = bit32.rshift(now, 8) 25 | end 26 | 27 | values[5] = 0 -- we don't have milliseconds 28 | values[6] = 0 29 | else 30 | -- format: year (16) / month (8) / day (8) / hour (8) / min (8) / sec (8) 31 | local now = getDateTime() 32 | local year = now.year 33 | 34 | values[1] = bit32.band(year, 0xFF) 35 | year = bit32.rshift(year, 8) 36 | values[2] = bit32.band(year, 0xFF) 37 | values[3] = now.mon 38 | values[4] = now.day 39 | values[5] = now.hour 40 | values[6] = now.min 41 | values[7] = now.sec 42 | end 43 | 44 | protocol.mspWrite(MSP_SET_RTC, values) 45 | lastRunTS = getTime() 46 | end 47 | 48 | mspProcessTxQ() 49 | processMspReply(mspPollReply()) 50 | 51 | return timeIsSet 52 | end 53 | 54 | return { f = setRtc, t = "" } 55 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/ui.lua: -------------------------------------------------------------------------------- 1 | local uiStatus = 2 | { 3 | init = 1, 4 | mainMenu = 2, 5 | pages = 3, 6 | confirm = 4, 7 | } 8 | 9 | local pageStatus = 10 | { 11 | display = 1, 12 | editing = 2, 13 | saving = 3, 14 | } 15 | 16 | local uiMsp = 17 | { 18 | reboot = 68, 19 | eepromWrite = 250, 20 | } 21 | 22 | local uiState = uiStatus.init 23 | local prevUiState 24 | local pageState = pageStatus.display 25 | local requestTimeout = 80 26 | local currentPage = 1 27 | local currentField = 1 28 | local saveTS = 0 29 | local saveTimeout = protocol.saveTimeout 30 | local saveRetries = 0 31 | local saveMaxRetries = protocol.saveMaxRetries 32 | local popupMenuActive = 1 33 | local killEnterBreak = 0 34 | local pageScrollY = 0 35 | local mainMenuScrollY = 0 36 | local PageFiles, Page, init, popupMenu 37 | 38 | local backgroundFill = TEXT_BGCOLOR or ERASE 39 | local foregroundColor = LINE_COLOR or SOLID 40 | 41 | local globalTextOptions = TEXT_COLOR or 0 42 | 43 | local function saveSettings() 44 | if Page.values then 45 | local payload = Page.values 46 | if Page.preSave then 47 | payload = Page.preSave(Page) 48 | end 49 | protocol.mspWrite(Page.write, payload) 50 | saveTS = getTime() 51 | if pageState == pageStatus.saving then 52 | saveRetries = saveRetries + 1 53 | else 54 | pageState = pageStatus.saving 55 | saveRetries = 0 56 | end 57 | end 58 | end 59 | 60 | local function invalidatePages() 61 | Page = nil 62 | pageState = pageStatus.display 63 | saveTS = 0 64 | collectgarbage() 65 | end 66 | 67 | local function rebootFc() 68 | protocol.mspRead(uiMsp.reboot) 69 | invalidatePages() 70 | end 71 | 72 | local function eepromWrite() 73 | protocol.mspRead(uiMsp.eepromWrite) 74 | end 75 | 76 | local function confirm(page) 77 | prevUiState = uiState 78 | uiState = uiStatus.confirm 79 | invalidatePages() 80 | currentField = 1 81 | Page = assert(loadScript(page))() 82 | collectgarbage() 83 | end 84 | 85 | local function createPopupMenu() 86 | popupMenuActive = 1 87 | popupMenu = {} 88 | if uiState == uiStatus.pages then 89 | popupMenu[#popupMenu + 1] = { t = "save page", f = saveSettings } 90 | popupMenu[#popupMenu + 1] = { t = "reload", f = invalidatePages } 91 | end 92 | popupMenu[#popupMenu + 1] = { t = "reboot", f = rebootFc } 93 | popupMenu[#popupMenu + 1] = { t = "acc cal", f = function() confirm("CONFIRM/acc_cal.lua") end } 94 | if apiVersion >= 1.42 then 95 | popupMenu[#popupMenu + 1] = { t = "vtx tables", f = function() confirm("CONFIRM/vtx_tables.lua") end } 96 | end 97 | if apiVersion >= 1.44 then 98 | popupMenu[#popupMenu + 1] = { t = "board info", f = function() confirm("CONFIRM/pwm.lua") end } 99 | end 100 | end 101 | 102 | local function processMspReply(cmd,rx_buf,err) 103 | if not Page or not rx_buf then 104 | elseif cmd == Page.write then 105 | if Page.eepromWrite then 106 | eepromWrite() 107 | else 108 | invalidatePages() 109 | end 110 | elseif cmd == uiMsp.eepromWrite then 111 | if Page.reboot then 112 | rebootFc() 113 | end 114 | invalidatePages() 115 | elseif cmd == Page.read and err then 116 | Page.fields = { { x = 6, y = radio.yMinLimit, value = "", ro = true } } 117 | Page.labels = { { x = 6, y = radio.yMinLimit, t = "N/A" } } 118 | elseif cmd == Page.read and #rx_buf > 0 then 119 | Page.values = rx_buf 120 | for i=1,#Page.fields do 121 | if #Page.values >= Page.minBytes then 122 | local f = Page.fields[i] 123 | if f.vals then 124 | f.value = 0 125 | for idx=1, #f.vals do 126 | local raw_val = Page.values[f.vals[idx]] or 0 127 | raw_val = bit32.lshift(raw_val, (idx-1)*8) 128 | f.value = bit32.bor(f.value, raw_val) 129 | end 130 | local bits = #f.vals * 8 131 | if f.min and f.min < 0 and bit32.btest(f.value, bit32.lshift(1, bits - 1)) then 132 | f.value = f.value - (2 ^ bits) 133 | end 134 | f.value = f.value/(f.scale or 1) 135 | end 136 | end 137 | end 138 | if Page.postLoad then 139 | Page.postLoad(Page) 140 | end 141 | end 142 | end 143 | 144 | local function incMax(val, inc, base) 145 | return ((val + inc + base - 1) % base) + 1 146 | end 147 | 148 | function clipValue(val,min,max) 149 | if val < min then 150 | val = min 151 | elseif val > max then 152 | val = max 153 | end 154 | return val 155 | end 156 | 157 | local function incPage(inc) 158 | currentPage = incMax(currentPage, inc, #PageFiles) 159 | currentField = 1 160 | invalidatePages() 161 | end 162 | 163 | local function incField(inc) 164 | currentField = clipValue(currentField + inc, 1, #Page.fields) 165 | end 166 | 167 | local function incMainMenu(inc) 168 | currentPage = clipValue(currentPage + inc, 1, #PageFiles) 169 | end 170 | 171 | local function incPopupMenu(inc) 172 | popupMenuActive = clipValue(popupMenuActive + inc, 1, #popupMenu) 173 | end 174 | 175 | local function requestPage() 176 | if Page.read and ((not Page.reqTS) or (Page.reqTS + requestTimeout <= getTime())) then 177 | Page.reqTS = getTime() 178 | protocol.mspRead(Page.read) 179 | end 180 | end 181 | 182 | local function drawScreenTitle(screenTitle) 183 | if radio.highRes then 184 | lcd.drawFilledRectangle(0, 0, LCD_W, 30, TITLE_BGCOLOR) 185 | lcd.drawText(5,5,screenTitle, MENU_TITLE_COLOR) 186 | else 187 | lcd.drawFilledRectangle(0, 0, LCD_W, 10, FORCE) 188 | lcd.drawText(1,1,screenTitle,INVERS) 189 | end 190 | end 191 | 192 | local function drawScreen() 193 | local yMinLim = radio.yMinLimit 194 | local yMaxLim = radio.yMaxLimit 195 | local currentFieldY = Page.fields[currentField].y 196 | local textOptions = radio.textSize + globalTextOptions 197 | if currentFieldY <= Page.fields[1].y then 198 | pageScrollY = 0 199 | elseif currentFieldY - pageScrollY <= yMinLim then 200 | pageScrollY = currentFieldY - yMinLim 201 | elseif currentFieldY - pageScrollY >= yMaxLim then 202 | pageScrollY = currentFieldY - yMaxLim 203 | end 204 | for i=1,#Page.labels do 205 | local f = Page.labels[i] 206 | local y = f.y - pageScrollY 207 | if y >= 0 and y <= LCD_H then 208 | lcd.drawText(f.x, y, f.t, textOptions) 209 | end 210 | end 211 | local val = "---" 212 | for i=1,#Page.fields do 213 | local f = Page.fields[i] 214 | local valueOptions = textOptions 215 | if i == currentField then 216 | valueOptions = valueOptions + INVERS 217 | if pageState == pageStatus.editing then 218 | valueOptions = valueOptions + BLINK 219 | end 220 | end 221 | if f.value then 222 | if f.upd and Page.values then 223 | f.upd(Page) 224 | end 225 | val = f.value 226 | if f.table and f.table[f.value] then 227 | val = f.table[f.value] 228 | end 229 | end 230 | local y = f.y - pageScrollY 231 | if y >= 0 and y <= LCD_H then 232 | if f.t then 233 | lcd.drawText(f.x, y, f.t, textOptions) 234 | end 235 | lcd.drawText(f.sp or f.x, y, val, valueOptions) 236 | end 237 | end 238 | drawScreenTitle("Betaflight / "..Page.title) 239 | end 240 | 241 | local function incValue(inc) 242 | local f = Page.fields[currentField] 243 | local scale = f.scale or 1 244 | local mult = f.mult or 1 245 | f.value = clipValue(f.value + inc*mult/scale, (f.min or 0)/scale, (f.max or 255)/scale) 246 | f.value = math.floor(f.value*scale/mult + 0.5)*mult/scale 247 | for idx=1, #f.vals do 248 | Page.values[f.vals[idx]] = bit32.rshift(math.floor(f.value*scale + 0.5), (idx-1)*8) 249 | end 250 | if f.upd and Page.values then 251 | f.upd(Page) 252 | end 253 | end 254 | 255 | local function drawPopupMenu() 256 | local x = radio.MenuBox.x 257 | local y = radio.MenuBox.y 258 | local w = radio.MenuBox.w 259 | local h_line = radio.MenuBox.h_line 260 | local h_offset = radio.MenuBox.h_offset 261 | local h = #popupMenu * h_line + h_offset*2 262 | 263 | lcd.drawFilledRectangle(x,y,w,h,backgroundFill) 264 | lcd.drawRectangle(x,y,w-1,h-1,foregroundColor) 265 | lcd.drawText(x+h_line/2,y+h_offset,"Menu:",globalTextOptions) 266 | 267 | for i,e in ipairs(popupMenu) do 268 | local textOptions = globalTextOptions 269 | if popupMenuActive == i then 270 | textOptions = textOptions + INVERS 271 | end 272 | lcd.drawText(x+radio.MenuBox.x_offset,y+(i-1)*h_line+h_offset,e.t,textOptions) 273 | end 274 | end 275 | 276 | local function run_ui(event) 277 | if popupMenu then 278 | drawPopupMenu() 279 | if event == EVT_VIRTUAL_EXIT then 280 | popupMenu = nil 281 | elseif event == EVT_VIRTUAL_PREV then 282 | incPopupMenu(-1) 283 | elseif event == EVT_VIRTUAL_NEXT then 284 | incPopupMenu(1) 285 | elseif event == EVT_VIRTUAL_ENTER then 286 | if killEnterBreak == 1 then 287 | killEnterBreak = 0 288 | else 289 | popupMenu[popupMenuActive].f() 290 | popupMenu = nil 291 | end 292 | end 293 | elseif uiState == uiStatus.init then 294 | lcd.clear() 295 | drawScreenTitle("Betaflight Config") 296 | init = init or assert(loadScript("ui_init.lua"))() 297 | lcd.drawText(6, radio.yMinLimit, init.t) 298 | if not init.f() then 299 | return 0 300 | end 301 | init = nil 302 | PageFiles = assert(loadScript("pages.lua"))() 303 | invalidatePages() 304 | uiState = prevUiState or uiStatus.mainMenu 305 | prevUiState = nil 306 | elseif uiState == uiStatus.mainMenu then 307 | if event == EVT_VIRTUAL_EXIT then 308 | return 2 309 | elseif event == EVT_VIRTUAL_NEXT then 310 | incMainMenu(1) 311 | elseif event == EVT_VIRTUAL_PREV then 312 | incMainMenu(-1) 313 | elseif event == EVT_VIRTUAL_ENTER then 314 | uiState = uiStatus.pages 315 | elseif event == EVT_VIRTUAL_ENTER_LONG then 316 | killEnterBreak = 1 317 | createPopupMenu() 318 | end 319 | lcd.clear() 320 | local yMinLim = radio.yMinLimit 321 | local yMaxLim = radio.yMaxLimit 322 | local lineSpacing = 10 323 | if radio.highRes then 324 | lineSpacing = 25 325 | end 326 | local currentFieldY = (currentPage-1)*lineSpacing + yMinLim 327 | if currentFieldY <= yMinLim then 328 | mainMenuScrollY = 0 329 | elseif currentFieldY - mainMenuScrollY <= yMinLim then 330 | mainMenuScrollY = currentFieldY - yMinLim 331 | elseif currentFieldY - mainMenuScrollY >= yMaxLim then 332 | mainMenuScrollY = currentFieldY - yMaxLim 333 | end 334 | for i=1, #PageFiles do 335 | local attr = currentPage == i and INVERS or 0 336 | local y = (i-1)*lineSpacing + yMinLim - mainMenuScrollY 337 | if y >= 0 and y <= LCD_H then 338 | lcd.drawText(6, y, PageFiles[i].title, attr) 339 | end 340 | end 341 | drawScreenTitle("Betaflight Config") 342 | elseif uiState == uiStatus.pages then 343 | if pageState == pageStatus.saving then 344 | if saveTS + saveTimeout < getTime() then 345 | if saveRetries < saveMaxRetries then 346 | saveSettings() 347 | else 348 | pageState = pageStatus.display 349 | invalidatePages() 350 | end 351 | end 352 | elseif pageState == pageStatus.display then 353 | if event == EVT_VIRTUAL_PREV_PAGE then 354 | incPage(-1) 355 | killEvents(event) -- X10/T16 issue: pageUp is a long press 356 | elseif event == EVT_VIRTUAL_NEXT_PAGE then 357 | incPage(1) 358 | elseif event == EVT_VIRTUAL_PREV or event == EVT_VIRTUAL_PREV_REPT then 359 | incField(-1) 360 | elseif event == EVT_VIRTUAL_NEXT or event == EVT_VIRTUAL_NEXT_REPT then 361 | incField(1) 362 | elseif event == EVT_VIRTUAL_ENTER then 363 | if Page then 364 | local f = Page.fields[currentField] 365 | if Page.values and f.vals and Page.values[f.vals[#f.vals]] and not f.ro then 366 | pageState = pageStatus.editing 367 | end 368 | end 369 | elseif event == EVT_VIRTUAL_ENTER_LONG then 370 | killEnterBreak = 1 371 | createPopupMenu() 372 | elseif event == EVT_VIRTUAL_EXIT then 373 | invalidatePages() 374 | currentField = 1 375 | uiState = uiStatus.mainMenu 376 | return 0 377 | end 378 | elseif pageState == pageStatus.editing then 379 | if event == EVT_VIRTUAL_EXIT or event == EVT_VIRTUAL_ENTER then 380 | if Page.fields[currentField].postEdit then 381 | Page.fields[currentField].postEdit(Page) 382 | end 383 | pageState = pageStatus.display 384 | elseif event == EVT_VIRTUAL_INC or event == EVT_VIRTUAL_INC_REPT then 385 | incValue(1) 386 | elseif event == EVT_VIRTUAL_DEC or event == EVT_VIRTUAL_DEC_REPT then 387 | incValue(-1) 388 | end 389 | end 390 | if not Page then 391 | local function selectPage(page) 392 | Page = assert(loadScript("PAGES/"..page))() 393 | collectgarbage() 394 | end 395 | 396 | local selectedPage = PageFiles[currentPage] 397 | if selectedPage.init then 398 | local initScript = assert(loadScript(selectedPage.init), "Missing init script")() 399 | collectgarbage() 400 | if initScript then 401 | confirm(initScript) 402 | else 403 | selectPage(selectedPage.script) 404 | end 405 | else 406 | selectPage(selectedPage.script) 407 | end 408 | end 409 | if not Page.values and pageState == pageStatus.display then 410 | requestPage() 411 | end 412 | lcd.clear() 413 | drawScreen() 414 | if pageState == pageStatus.saving then 415 | local saveMsg = "Saving..." 416 | if saveRetries > 0 then 417 | saveMsg = "Retrying" 418 | end 419 | lcd.drawFilledRectangle(radio.SaveBox.x,radio.SaveBox.y,radio.SaveBox.w,radio.SaveBox.h,backgroundFill) 420 | lcd.drawRectangle(radio.SaveBox.x,radio.SaveBox.y,radio.SaveBox.w,radio.SaveBox.h,SOLID) 421 | lcd.drawText(radio.SaveBox.x+radio.SaveBox.x_offset,radio.SaveBox.y+radio.SaveBox.h_offset,saveMsg,DBLSIZE + globalTextOptions) 422 | end 423 | elseif uiState == uiStatus.confirm then 424 | lcd.clear() 425 | drawScreen() 426 | if event == EVT_VIRTUAL_ENTER then 427 | uiState = uiStatus.init 428 | init = Page.init 429 | invalidatePages() 430 | elseif event == EVT_VIRTUAL_EXIT then 431 | invalidatePages() 432 | uiState = prevUiState 433 | prevUiState = nil 434 | end 435 | end 436 | if getRSSI() == 0 then 437 | lcd.drawText(radio.NoTelem[1],radio.NoTelem[2],radio.NoTelem[3],radio.NoTelem[4]) 438 | end 439 | mspProcessTxQ() 440 | processMspReply(mspPollReply()) 441 | return 0 442 | end 443 | 444 | return run_ui 445 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/ui_init.lua: -------------------------------------------------------------------------------- 1 | local apiVersionReceived = false 2 | local mcuIdReceived = false 3 | local featuresReceived = false 4 | local getApiVersion, getMCUId, getFeaturesInfo 5 | local returnTable = { f = nil, t = "" } 6 | 7 | local function init() 8 | if getRSSI() == 0 then 9 | returnTable.t = "Waiting for connection" 10 | elseif not apiVersionReceived then 11 | getApiVersion = getApiVersion or assert(loadScript("api_version.lua"))() 12 | returnTable.t = getApiVersion.t 13 | apiVersionReceived = getApiVersion.f() 14 | if apiVersionReceived then 15 | getApiVersion = nil 16 | collectgarbage() 17 | end 18 | elseif not mcuIdReceived and apiVersion >= 1.42 then 19 | getMCUId = getMCUId or assert(loadScript("mcu_id.lua"))() 20 | returnTable.t = getMCUId.t 21 | mcuIdReceived = getMCUId.f() 22 | if mcuIdReceived then 23 | getMCUId = nil 24 | collectgarbage() 25 | end 26 | elseif not featuresReceived and apiVersion >= 1.41 then 27 | getFeaturesInfo = getFeaturesInfo or assert(loadScript("features_info.lua"))() 28 | returnTable.t = getFeaturesInfo.t 29 | featuresReceived = getFeaturesInfo.f() 30 | if featuresReceived then 31 | getFeaturesInfo = nil 32 | collectgarbage() 33 | end 34 | else 35 | return true 36 | end 37 | return apiVersionReceived and mcuId and featuresReceived 38 | end 39 | 40 | returnTable.f = init 41 | 42 | return returnTable 43 | -------------------------------------------------------------------------------- /src/SCRIPTS/BF/vtx_tables.lua: -------------------------------------------------------------------------------- 1 | local MSP_VTX_CONFIG = 88 2 | local MSP_VTXTABLE_BAND = 137 3 | local MSP_VTXTABLE_POWERLEVEL = 138 4 | 5 | local vtxTableAvailable = false 6 | local vtxConfigReceived = false 7 | local vtxFrequencyTableReceived = false 8 | local vtxPowerTableReceived = false 9 | local vtxTablesReceived = false 10 | local requestedBand = 1 11 | local requestedPowerLevel = 1 12 | local vtxTableConfig = {} 13 | local frequencyTable = {} 14 | local bandTable = {} 15 | local powerTable = {} 16 | 17 | local lastRunTS = 0 18 | local INTERVAL = 100 19 | 20 | local function processMspReply(cmd, payload, err) 21 | if cmd == MSP_VTX_CONFIG then 22 | if err then 23 | -- Vtx not available. Create empty vtx table to skip future download attempts 24 | frequencyTable[1] = {} 25 | vtxTableConfig.channels = 0 26 | bandTable = { [0] = "U", "1" } 27 | powerTable = { "LV0" } 28 | vtxConfigReceived = true 29 | vtxTableAvailable = true 30 | vtxFrequencyTableReceived = true 31 | vtxPowerTableReceived = true 32 | features.vtx = false 33 | return 34 | end 35 | vtxConfigReceived = true 36 | vtxTableAvailable = payload[12] ~= 0 37 | features.vtx = vtxTableAvailable 38 | vtxTableConfig.bands = payload[13] 39 | vtxTableConfig.channels = payload[14] 40 | vtxTableConfig.powerLevels = payload[15] 41 | end 42 | if cmd == MSP_VTXTABLE_BAND and payload[1] == requestedBand then 43 | local i = 1 44 | local receivedBand = payload[i] 45 | i = i + 1 46 | local bandNameLength = payload[i] 47 | i = i + bandNameLength + 1 48 | bandTable[receivedBand] = string.char(payload[i]) 49 | i = i + 2 50 | local channels = payload[i] 51 | i = i + 1 52 | frequencyTable[receivedBand] = {} 53 | for channel = 1, channels do 54 | local frequency = 0 55 | for idx=1, 2 do 56 | local raw_val = payload[i] 57 | raw_val = bit32.lshift(raw_val, (idx-1)*8) 58 | frequency = bit32.bor(frequency, raw_val) 59 | i = i + 1 60 | end 61 | frequencyTable[receivedBand][channel] = frequency 62 | end 63 | requestedBand = requestedBand + 1 64 | vtxFrequencyTableReceived = requestedBand > vtxTableConfig.bands 65 | end 66 | if cmd == MSP_VTXTABLE_POWERLEVEL and payload[1] == requestedPowerLevel then 67 | local i = 1 68 | local powerLevel = payload[i] 69 | i = i + 3 70 | local powerLabelLength = payload[i] 71 | i = i + 1 72 | powerTable[powerLevel] = '' 73 | for c = 1, powerLabelLength do 74 | powerTable[powerLevel] = powerTable[powerLevel]..string.char(payload[i]) 75 | i = i + 1 76 | end 77 | requestedPowerLevel = requestedPowerLevel + 1 78 | vtxPowerTableReceived = requestedPowerLevel > vtxTableConfig.powerLevels 79 | end 80 | end 81 | 82 | local function getVtxTables() 83 | if lastRunTS + INTERVAL < getTime() then 84 | lastRunTS = getTime() 85 | if not vtxConfigReceived then 86 | protocol.mspRead(MSP_VTX_CONFIG) 87 | elseif vtxConfigReceived and not vtxTableAvailable then 88 | return true 89 | elseif not vtxFrequencyTableReceived then 90 | protocol.mspWrite(MSP_VTXTABLE_BAND, { requestedBand }) 91 | elseif not vtxPowerTableReceived then 92 | protocol.mspWrite(MSP_VTXTABLE_POWERLEVEL, { requestedPowerLevel }) 93 | else 94 | vtxTablesReceived = true 95 | end 96 | end 97 | if vtxTablesReceived then 98 | local f = io.open("VTX_TABLES/"..mcuId..".lua", 'w') 99 | io.write(f, "return {", "\n") 100 | io.write(f, " frequencyTable = {", "\n") 101 | for i = 1, #frequencyTable do 102 | local frequencyString = " { " 103 | for k = 1, #frequencyTable[i] do 104 | frequencyString = frequencyString..tostring(frequencyTable[i][k])..", " 105 | end 106 | frequencyString = frequencyString.."}," 107 | io.write(f, frequencyString, "\n") 108 | end 109 | io.write(f, " },", "\n") 110 | io.write(f, " frequenciesPerBand = "..tostring(vtxTableConfig.channels)..",", "\n") 111 | local bandString = " bandTable = { [0]=\"U\", " 112 | for i = 1, #bandTable do 113 | bandString = bandString.."\""..bandTable[i].."\", " 114 | end 115 | bandString = bandString.."}," 116 | io.write(f, bandString, "\n") 117 | local powerString = " powerTable = { " 118 | for i = 1, #powerTable do 119 | powerString = powerString.."\""..powerTable[i].."\", " 120 | end 121 | powerString = powerString.."}," 122 | io.write(f, powerString, "\n") 123 | io.write(f, "}", "\n") 124 | io.close(f) 125 | assert(loadScript("VTX_TABLES/"..mcuId..".lua", 'c')) 126 | end 127 | mspProcessTxQ() 128 | processMspReply(mspPollReply()) 129 | return vtxTablesReceived 130 | end 131 | 132 | return { f = getVtxTables, t = "Downloading VTX tables" } 133 | -------------------------------------------------------------------------------- /src/SCRIPTS/FUNCTIONS/bfbkgd.lua: -------------------------------------------------------------------------------- 1 | chdir("/SCRIPTS/BF") 2 | apiVersion = 0 3 | protocol = assert(loadScript("protocols.lua"))() 4 | assert(loadScript(protocol.mspTransport))() 5 | assert(loadScript("MSP/common.lua"))() 6 | local background = assert(loadScript("background.lua"))() 7 | 8 | return { run=background } 9 | -------------------------------------------------------------------------------- /src/SCRIPTS/FUNCTIONS/bfpids.lua: -------------------------------------------------------------------------------- 1 | chdir("/SCRIPTS/BF") 2 | 3 | assert(loadScript("MSP/messages.lua"))() 4 | 5 | local msg_p = { 6 | intro = "p.wav", 7 | readoutValues = {1, 2, 3}, 8 | } 9 | 10 | local msg_i = { 11 | intro = "i.wav", 12 | readoutValues = {4, 5, 6}, 13 | } 14 | 15 | local msg_d = { 16 | intro = "d.wav", 17 | readoutValues = {7, 8}, 18 | } 19 | 20 | local msg_dsetpt = { 21 | intro = "dsetpt.wav", 22 | readoutValues = {1, 2}, 23 | } 24 | 25 | local pidSelectorField = nil 26 | 27 | local function init_pids() 28 | pidSelectorField = getFieldInfo("trim-thr") 29 | end 30 | 31 | local function run_pids() 32 | local pidSelector = getValue(pidSelectorField.id) 33 | if pidSelector > 33 and pidSelector < 99 then 34 | readoutMsp(MSP_PID_FORMAT, msg_p) 35 | elseif pidSelector > 99 and pidSelector < 165 then 36 | readoutMsp(MSP_PID_FORMAT, msg_i) 37 | elseif pidSelector > 165 and pidSelector < 231 then 38 | readoutMsp(MSP_PID_FORMAT, msg_d) 39 | elseif pidSelector > -99 and pidSelector < -33 then 40 | readoutMsp(MSP_PID_ADVANCED_FORMAT, msg_dsetpt) 41 | end 42 | end 43 | 44 | return { init = init_pids, run = run_pids } 45 | -------------------------------------------------------------------------------- /src/SCRIPTS/TOOLS/bf.lua: -------------------------------------------------------------------------------- 1 | local toolName = "TNS|Betaflight setup|TNE" 2 | chdir("/SCRIPTS/BF") 3 | 4 | apiVersion = 0 5 | mcuId = nil 6 | 7 | local run = nil 8 | local scriptsCompiled = assert(loadScript("COMPILE/scripts_compiled.lua"))() 9 | 10 | if scriptsCompiled then 11 | protocol = assert(loadScript("protocols.lua"))() 12 | radio = assert(loadScript("radios.lua"))().msp 13 | assert(loadScript(protocol.mspTransport))() 14 | assert(loadScript("MSP/common.lua"))() 15 | features = assert(loadScript("features.lua"))() 16 | run = assert(loadScript("ui.lua"))() 17 | else 18 | run = assert(loadScript("COMPILE/compile.lua"))() 19 | end 20 | 21 | return { run=run } 22 | -------------------------------------------------------------------------------- /src/SCRIPTS/TOOLS/bfCms.lua: -------------------------------------------------------------------------------- 1 | local toolName = "TNS|Betaflight CMS|TNE" 2 | chdir("/SCRIPTS/BF") 3 | 4 | apiVersion = 0 5 | 6 | local cms = { run = nil, init = nil } 7 | local scriptsCompiled = assert(loadScript("COMPILE/scripts_compiled.lua"))() 8 | 9 | if scriptsCompiled then 10 | protocol = assert(loadScript("protocols.lua"))() 11 | local cmsTransport = assert(protocol.cmsTransport, "Telemetry protocol not supported!") 12 | radio = assert(loadScript("radios.lua"))().cms 13 | assert(loadScript(cmsTransport))() 14 | assert(loadScript("CMS/common.lua"))() 15 | cms = assert(loadScript("cms.lua"))() 16 | else 17 | cms = { run = assert(loadScript("COMPILE/compile.lua"))() } 18 | end 19 | 20 | return cms 21 | -------------------------------------------------------------------------------- /src/SOUNDS/en/d.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/src/SOUNDS/en/d.wav -------------------------------------------------------------------------------- /src/SOUNDS/en/dsetpt.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/src/SOUNDS/en/dsetpt.wav -------------------------------------------------------------------------------- /src/SOUNDS/en/i.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/src/SOUNDS/en/i.wav -------------------------------------------------------------------------------- /src/SOUNDS/en/p.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/betaflight/betaflight-tx-lua-scripts/1a9002439c9105c9063f1431f354043e655f9cff/src/SOUNDS/en/p.wav --------------------------------------------------------------------------------