├── .github
└── workflows
│ ├── artifacts.yml
│ └── build.yml
├── .gitignore
├── .idea
├── .gitignore
├── Backpack.iml
├── misc.xml
├── modules.xml
└── vcs.xml
├── LICENSE
├── README.md
├── hardware
└── targets.json
├── html
├── airplane.obj
├── elrs.css
├── logo.svg
├── mui.css
├── mui.js
├── p5.js
├── scan.js
├── texture.gif
├── timer_index.html
├── txbp_index.html
└── vrx_index.html
├── img
├── AMS1117-pinout.png
├── Nano_RX_Wiring.png
├── disconnecting-wires-post-flash.jpg
├── ep1-install-rapidfire
│ ├── EP1-heat-shrink.JPG
│ ├── EP1-wired-boot0.JPG
│ ├── EP1-wired.JPG
│ ├── EP1.JPG
│ ├── HDO2-button.JPG
│ ├── rapidfire-installed.jpg
│ ├── rapidfire-soldered.JPG
│ └── rapidfire-splitting.JPG
├── esp-on-rf.jpg
├── esp-wiring-diagram.jpg
├── flow-diagram-backpack.jpg
├── front-cover.jpg
├── heat-shrink.jpg
├── installed.jpg
├── kapton-tape.jpg
├── node-flasher-advanced-tab.png
├── node-flasher-config-tab.png
├── parts-needed.jpg
├── patch-wires-on-esp.jpg
├── reg-on-esp.jpg
├── rf-wiring0diagram.jpg
├── tx-backpack-buttons.jpg
├── up-top-of-rf.jpg
└── wires-on-reg.jpg
├── include
├── README
├── common.h
├── devwifi_proxies.h
├── helpers.h
└── options.h
├── lib
├── BUTTON
│ ├── button.h
│ ├── devButton.cpp
│ └── devButton.h
├── CRC
│ ├── crc.cpp
│ └── crc.h
├── Channels
│ ├── channels.cpp
│ └── channels.h
├── CrsfProtocol
│ └── crsf_protocol.h
├── DEVICE
│ ├── device.cpp
│ └── device.h
├── EEPROM
│ ├── elrs_eeprom.cpp
│ └── elrs_eeprom.h
├── Fusion
│ ├── CMakeLists.txt
│ ├── Fusion.h
│ ├── FusionAhrs.c
│ ├── FusionAhrs.h
│ ├── FusionAxes.h
│ ├── FusionCalibration.h
│ ├── FusionCompass.c
│ ├── FusionCompass.h
│ ├── FusionConvention.h
│ ├── FusionMath.h
│ ├── FusionOffset.c
│ └── FusionOffset.h
├── HeadTracker
│ ├── ICMSeries.cpp
│ ├── ICMSeries.h
│ ├── IMUBase.cpp
│ ├── IMUBase.h
│ ├── MPU6050.cpp
│ ├── MPU6050.h
│ ├── QMI8658C.cpp
│ ├── QMI8658C.h
│ ├── devHeadTracker.cpp
│ └── devHeadTracker.h
├── LED
│ ├── devLED.cpp
│ └── devLED.h
├── MAVLink
│ ├── MAVLink.cpp
│ └── MAVLink.h
├── MSP
│ ├── msp.cpp
│ ├── msp.h
│ └── msptypes.h
├── QMC5883L
│ ├── QMC5883LCompass.cpp
│ └── QMC5883LCompass.h
├── WIFI
│ ├── UpdateWrapper.h
│ ├── devWIFI.cpp
│ └── devWIFI.h
├── config
│ ├── config.cpp
│ └── config.h
└── logging
│ ├── logging.cpp
│ └── logging.h
├── platformio.ini
├── python
├── MANIFEST.in
├── UnifiedConfiguration.py
├── __init__.py
├── __main__.py
├── binary_configurator.py
├── bootloader.py
├── build_env_setup.py
├── build_flags.py
├── build_html.py
├── elrs_helpers.py
├── esp_compress.py
├── external
│ ├── bottle.py
│ ├── esptool
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── esptool.py
│ │ └── esptool
│ │ │ ├── __init__.py
│ │ │ ├── __main__.py
│ │ │ ├── bin_image.py
│ │ │ ├── cmds.py
│ │ │ ├── loader.py
│ │ │ ├── targets
│ │ │ ├── __init__.py
│ │ │ ├── esp32.py
│ │ │ ├── esp32c2.py
│ │ │ ├── esp32c3.py
│ │ │ ├── esp32c6beta.py
│ │ │ ├── esp32h2beta1.py
│ │ │ ├── esp32h2beta2.py
│ │ │ ├── esp32s2.py
│ │ │ ├── esp32s3.py
│ │ │ ├── esp32s3beta2.py
│ │ │ ├── esp8266.py
│ │ │ └── stub_flasher
│ │ │ │ ├── stub_flasher_32.json
│ │ │ │ ├── stub_flasher_32c2.json
│ │ │ │ ├── stub_flasher_32c3.json
│ │ │ │ ├── stub_flasher_32c6beta.json
│ │ │ │ ├── stub_flasher_32h2beta1.json
│ │ │ │ ├── stub_flasher_32h2beta2.json
│ │ │ │ ├── stub_flasher_32s2.json
│ │ │ │ ├── stub_flasher_32s3.json
│ │ │ │ ├── stub_flasher_32s3beta2.json
│ │ │ │ └── stub_flasher_8266.json
│ │ │ └── util.py
│ ├── six.py
│ ├── streamexpect.py
│ └── wheezy
│ │ ├── __init__.py
│ │ └── template
│ │ ├── __init__.py
│ │ ├── builder.py
│ │ ├── comp.py
│ │ ├── compiler.py
│ │ ├── console.py
│ │ ├── engine.py
│ │ ├── ext
│ │ ├── __init__.py
│ │ ├── code.py
│ │ ├── core.py
│ │ ├── determined.py
│ │ └── tests
│ │ │ ├── __init__.py
│ │ │ ├── test_code.py
│ │ │ ├── test_core.py
│ │ │ └── test_determined.py
│ │ ├── lexer.py
│ │ ├── loader.py
│ │ ├── parser.py
│ │ ├── preprocessor.py
│ │ ├── py.typed
│ │ ├── tests
│ │ ├── __init__.py
│ │ ├── test_builder.py
│ │ ├── test_console.py
│ │ ├── test_engine.py
│ │ ├── test_lexer.py
│ │ ├── test_loader.py
│ │ ├── test_parser.py
│ │ ├── test_preprocessor.py
│ │ └── test_utils.py
│ │ ├── typing.py
│ │ └── utils.py
├── minify
│ ├── __init__.py
│ ├── html_minifier.py
│ ├── rcssmin.py
│ ├── rjsmin.py
│ └── variables.py
├── osd_test.py
├── serials_find.py
├── serve_html.py
├── setup.py
├── upload_via_esp8266_backpack.py
└── utils
│ └── gpscsv_to_crsf.py
├── src
├── EspFlashStream.cpp
├── EspFlashStream.h
├── Timer_main.cpp
├── Tx_main.cpp
├── Vrx_main.cpp
├── devwifi_proxy_aat.cpp
├── hdzero.cpp
├── hdzero.h
├── mfd_crossbow.cpp
├── mfd_crossbow.h
├── module_aat.cpp
├── module_aat.h
├── module_base.cpp
├── module_base.h
├── module_crsf.cpp
├── module_crsf.h
├── options.cpp
├── orqa.cpp
├── orqa.h
├── rapidfire.cpp
├── rapidfire.h
├── rx5808.cpp
├── rx5808.h
├── skyzone_msp.cpp
├── skyzone_msp.h
├── steadyview.cpp
├── steadyview.h
├── tbs_fusion.cpp
└── tbs_fusion.h
├── targets
├── aat.ini
├── common.ini
├── debug.ini
├── fusion.ini
├── hdzero.ini
├── mfd_crossbow.ini
├── orqa.ini
├── rapidfire.ini
├── rx5808.ini
├── skyzone.ini
├── timer.ini
└── txbp_esp.ini
├── test
└── README
└── user_defines.txt
/.github/workflows/artifacts.yml:
--------------------------------------------------------------------------------
1 | name: Cleanup stale artifacts
2 | on:
3 | schedule:
4 | - cron: "30 1 * * *"
5 |
6 | jobs:
7 | cleanup-artifacts:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - name: Checkout code
11 | uses: actions/checkout@v4
12 | with:
13 | fetch-depth: 0
14 |
15 | - name: Remove stale artifacts from R2
16 | env:
17 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
18 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
19 | AWS_ENDPOINT_URL: ${{ secrets.AWS_ENDPOINT_URL }}
20 | AWS_REGION: "auto"
21 | run: |
22 | if [[ -z $AWS_ACCESS_KEY_ID || -z $AWS_SECRET_ACCESS_KEY || -z $AWS_ENDPOINT_URL ]]; then
23 | echo "Artifactory credentials are not defined. Most likely action is running from pull request. Not a bug."
24 | exit 0
25 | fi
26 |
27 | echo "Generating artifact index"
28 | aws s3 ls --endpoint-url $AWS_ENDPOINT_URL s3://expresslrs/Backpack/ | grep PRE | awk '{print $2}' | sed s/\\/// > /tmp/artifacts
29 |
30 | git branch --list --remotes --format '%(objectname)' > /tmp/hashes
31 | git tag --list --format '%(objectname)' >> /tmp/hashes
32 |
33 | for i in `grep -v -f /tmp/hashes /tmp/artifacts`; do
34 | aws s3 rm --recursive --endpoint-url $AWS_ENDPOINT_URL s3://expresslrs/Backpack/$i
35 | done
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .pio
2 | .pioenvs
3 | .piolibdeps
4 | .vscode
5 | __pycache__
6 | *.pyc
7 | include/WebContent.h
8 | super_defines.txt
9 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/Backpack.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## What is a "TX Backpack"?
2 | Some of the ExpressLRS TX modules include an additional ESP8285 chip, which lets us communicate wirelessly with other ESP8285 enabled devices using a protocol called espnow. We call this chip the "TX-Backpack". The aim of the TX-Backpack is to allow wireless communication between ExpressLRS, and other FPV related devices for command and control, or for querying config.
3 |
4 | ## Sounds interesting... What type of FPV devices can it talk to?
5 | A prime use case is your video receiver module (or VRX). Currently there aren't many VRX modules that have an ESP8285 built in to allow them to communicate with ExpressLRS, so in most cases you need to add your own. A small ESP based receiver can be "piggybacked" onto your VRX module, which allows ExpressLRS to control the band and channel that your goggles are set to. We call this device the "VRX-Backpack".
6 |
7 | ## Wow cool, so I'll be able to control the module via ELRS!? Which VRX modules does it work with?
8 | The list of supported modules can be found on the wiki:
9 | https://github.com/ExpressLRS/Backpack/wiki
10 |
11 | ## Great! I use one of the supported modules. How do I get a VRX-Backpack?
12 | There are a few different options for both DIY or compatible off the shelf backpacks... check the wiki for the current list:
13 | https://github.com/ExpressLRS/Backpack/wiki
--------------------------------------------------------------------------------
/html/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/html/texture.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/html/texture.gif
--------------------------------------------------------------------------------
/img/AMS1117-pinout.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/AMS1117-pinout.png
--------------------------------------------------------------------------------
/img/Nano_RX_Wiring.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/Nano_RX_Wiring.png
--------------------------------------------------------------------------------
/img/disconnecting-wires-post-flash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/disconnecting-wires-post-flash.jpg
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/EP1-heat-shrink.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/EP1-heat-shrink.JPG
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/EP1-wired-boot0.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/EP1-wired-boot0.JPG
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/EP1-wired.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/EP1-wired.JPG
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/EP1.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/EP1.JPG
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/HDO2-button.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/HDO2-button.JPG
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/rapidfire-installed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/rapidfire-installed.jpg
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/rapidfire-soldered.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/rapidfire-soldered.JPG
--------------------------------------------------------------------------------
/img/ep1-install-rapidfire/rapidfire-splitting.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/ep1-install-rapidfire/rapidfire-splitting.JPG
--------------------------------------------------------------------------------
/img/esp-on-rf.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/esp-on-rf.jpg
--------------------------------------------------------------------------------
/img/esp-wiring-diagram.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/esp-wiring-diagram.jpg
--------------------------------------------------------------------------------
/img/flow-diagram-backpack.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/flow-diagram-backpack.jpg
--------------------------------------------------------------------------------
/img/front-cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/front-cover.jpg
--------------------------------------------------------------------------------
/img/heat-shrink.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/heat-shrink.jpg
--------------------------------------------------------------------------------
/img/installed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/installed.jpg
--------------------------------------------------------------------------------
/img/kapton-tape.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/kapton-tape.jpg
--------------------------------------------------------------------------------
/img/node-flasher-advanced-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/node-flasher-advanced-tab.png
--------------------------------------------------------------------------------
/img/node-flasher-config-tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/node-flasher-config-tab.png
--------------------------------------------------------------------------------
/img/parts-needed.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/parts-needed.jpg
--------------------------------------------------------------------------------
/img/patch-wires-on-esp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/patch-wires-on-esp.jpg
--------------------------------------------------------------------------------
/img/reg-on-esp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/reg-on-esp.jpg
--------------------------------------------------------------------------------
/img/rf-wiring0diagram.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/rf-wiring0diagram.jpg
--------------------------------------------------------------------------------
/img/tx-backpack-buttons.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/tx-backpack-buttons.jpg
--------------------------------------------------------------------------------
/img/up-top-of-rf.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/up-top-of-rf.jpg
--------------------------------------------------------------------------------
/img/wires-on-reg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/img/wires-on-reg.jpg
--------------------------------------------------------------------------------
/include/README:
--------------------------------------------------------------------------------
1 |
2 | This directory is intended for project header files.
3 |
4 | A header file is a file containing C declarations and macro definitions
5 | to be shared between several project source files. You request the use of a
6 | header file in your project source file (C, C++, etc) located in `src` folder
7 | by including it, with the C preprocessing directive `#include'.
8 |
9 | ```src/main.c
10 |
11 | #include "header.h"
12 |
13 | int main (void)
14 | {
15 | ...
16 | }
17 | ```
18 |
19 | Including a header file produces the same results as copying the header file
20 | into each source file that needs it. Such copying would be time-consuming
21 | and error-prone. With a header file, the related declarations appear
22 | in only one place. If they need to be changed, they can be changed in one
23 | place, and programs that include the header file will automatically use the
24 | new version when next recompiled. The header file eliminates the labor of
25 | finding and changing all the copies as well as the risk that a failure to
26 | find one copy will result in inconsistencies within a program.
27 |
28 | In C, the usual convention is to give header files names that end with `.h'.
29 | It is most portable to use only letters, digits, dashes, and underscores in
30 | header file names, and at most one dot.
31 |
32 | Read more about using header files in official GCC documentation:
33 |
34 | * Include Syntax
35 | * Include Operation
36 | * Once-Only Headers
37 | * Computed Includes
38 |
39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
40 |
--------------------------------------------------------------------------------
/include/common.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | typedef enum
4 | {
5 | starting,
6 | binding,
7 | running,
8 | wifiUpdate,
9 | FAILURE_STATES
10 | } connectionState_e;
11 |
12 | extern connectionState_e connectionState;
13 | extern unsigned long bindingStart;
14 |
--------------------------------------------------------------------------------
/include/devwifi_proxies.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #if defined(AAT_BACKPACK)
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | void WebAatAppendConfig(ArduinoJson::JsonDocument &json);
10 | void WebAatInit(AsyncWebServer &server);
11 |
12 | #endif /* defined(AAT_BACKPACK) */
13 |
--------------------------------------------------------------------------------
/include/helpers.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
4 |
--------------------------------------------------------------------------------
/include/options.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | extern const unsigned char target_name[];
4 | extern const uint8_t target_name_size;
5 | extern const char PROGMEM compile_options[];
6 |
7 | typedef struct {
8 | uint8_t uid[6];
9 | bool hasUID;
10 | char home_wifi_ssid[33];
11 | char home_wifi_password[65];
12 | char product_name[65];
13 | } firmware_options_t;
14 |
15 | extern firmware_options_t firmwareOptions;
16 |
17 | extern bool options_init();
--------------------------------------------------------------------------------
/lib/BUTTON/button.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | template
6 | class Button
7 | {
8 | private:
9 | // Constants
10 | static constexpr uint32_t MS_DEBOUNCE = 25; // how long the switch must change state to be considered
11 | static constexpr uint32_t MS_LONG = 500; // duration held to be considered a long press (repeats)
12 | static constexpr uint32_t MS_MULTI_TIMEOUT = 500; // duration without a press before the short count is reset
13 |
14 | static constexpr unsigned STATE_IDLE = 0b111;
15 | static constexpr unsigned STATE_FALL = 0b100;
16 | static constexpr unsigned STATE_RISE = 0b011;
17 | static constexpr unsigned STATE_HELD = 0b000;
18 |
19 | // State
20 | uint32_t _lastCheck; // millis of last pin read
21 | uint32_t _lastFallingEdge; // millis of last debounced falling edge
22 | uint8_t _state; // pin history
23 | bool _isLongPress; // true if last press was a long
24 | uint8_t _longCount; // number of times long press has repeated
25 | uint8_t _pressCount; // number of short presses before timeout
26 | public:
27 | // Callbacks
28 | std::functionOnShortPress;
29 | std::functionOnLongPress;
30 | // Properties
31 | uint8_t getCount() const { return _pressCount; }
32 | uint8_t getLongCount() const { return _longCount; }
33 |
34 | Button() :
35 | _lastCheck(0), _lastFallingEdge(0), _state(STATE_IDLE),
36 | _isLongPress(false), _longCount(0), _pressCount(0)
37 | {
38 | pinMode(PIN, IDLELOW ? INPUT : INPUT_PULLUP);
39 | }
40 |
41 | // Call this in loop()
42 | int update()
43 | {
44 | const uint32_t now = millis();
45 |
46 | // Reset press count if it has been too long since last rising edge
47 | if (now - _lastFallingEdge > MS_MULTI_TIMEOUT)
48 | _pressCount = 0;
49 |
50 | _state = (_state << 1) & 0b110;
51 | _state |= digitalRead(PIN) ^ IDLELOW;
52 |
53 | // If rising edge (release)
54 | if (_state == STATE_RISE)
55 | {
56 | if (!_isLongPress)
57 | {
58 | DBGLN("Button short");
59 | ++_pressCount;
60 | if (OnShortPress)
61 | OnShortPress();
62 | }
63 | _isLongPress = false;
64 | }
65 | // Two low in a row
66 | else if (_state == STATE_FALL)
67 | {
68 | _lastFallingEdge = now;
69 | _longCount = 0;
70 | }
71 | // Three or more low in a row
72 | else if (_state == STATE_HELD)
73 | {
74 | if (now - _lastFallingEdge > MS_LONG)
75 | {
76 | DBGLN("Button long %d", _longCount);
77 | _isLongPress = true;
78 | if (OnLongPress)
79 | OnLongPress();
80 | // Reset time so long can fire again
81 | _lastFallingEdge = now;
82 | _longCount++;
83 | }
84 | }
85 | return MS_DEBOUNCE;
86 | }
87 | };
88 |
--------------------------------------------------------------------------------
/lib/BUTTON/devButton.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "common.h"
3 | #include "device.h"
4 | #include "config.h"
5 |
6 | #if defined(PIN_BUTTON)
7 | #include "logging.h"
8 | #include "button.h"
9 |
10 | static Button button;
11 |
12 | extern unsigned long rebootTime;
13 | void RebootIntoWifi(wifi_service_t service);
14 |
15 | static void shortPress()
16 | {
17 | if (connectionState == wifiUpdate)
18 | {
19 | rebootTime = millis();
20 | }
21 | else
22 | {
23 | RebootIntoWifi(WIFI_SERVICE_UPDATE);
24 | }
25 | }
26 |
27 | static void initialize()
28 | {
29 | button.OnShortPress = shortPress;
30 | }
31 |
32 | static int start()
33 | {
34 | return DURATION_IMMEDIATELY;
35 | }
36 |
37 | static int timeout()
38 | {
39 | return button.update();
40 | }
41 |
42 | device_t Button_device = {
43 | .initialize = initialize,
44 | .start = start,
45 | .event = NULL,
46 | .timeout = timeout
47 | };
48 |
49 | #endif
--------------------------------------------------------------------------------
/lib/BUTTON/devButton.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "device.h"
4 |
5 | extern device_t Button_device;
6 |
--------------------------------------------------------------------------------
/lib/CRC/crc.cpp:
--------------------------------------------------------------------------------
1 | #include "crc.h"
2 |
3 | GENERIC_CRC8::GENERIC_CRC8(uint8_t poly)
4 | {
5 | uint8_t crc;
6 |
7 | for (uint16_t i = 0; i < crclen; i++)
8 | {
9 | crc = i;
10 | for (uint8_t j = 0; j < 8; j++)
11 | {
12 | crc = (crc << 1) ^ ((crc & 0x80) ? poly : 0);
13 | }
14 | crc8tab[i] = crc & 0xFF;
15 | }
16 | }
17 |
18 | uint8_t ICACHE_RAM_ATTR GENERIC_CRC8::calc(const uint8_t data)
19 | {
20 | return crc8tab[data];
21 | }
22 |
23 | uint8_t ICACHE_RAM_ATTR GENERIC_CRC8::calc(const uint8_t *data, uint8_t len, uint8_t crc)
24 | {
25 | while (len--)
26 | {
27 | crc = crc8tab[crc ^ *data++];
28 | }
29 | return crc;
30 | }
31 |
32 | GENERIC_CRC14::GENERIC_CRC14(uint16_t poly)
33 | {
34 | uint16_t crc;
35 | for (uint16_t i = 0; i < crclen; i++)
36 | {
37 | crc = i << (14 - 8);
38 | for (uint8_t j = 0; j < 8; j++)
39 | {
40 | crc = (crc << 1) ^ ((crc & 0x2000) ? poly : 0);
41 | }
42 | crc14tab[i] = crc;
43 | }
44 | }
45 |
46 | uint16_t ICACHE_RAM_ATTR GENERIC_CRC14::calc(uint8_t *data, uint8_t len, uint16_t crc)
47 | {
48 | while (len--)
49 | {
50 | crc = (crc << 8) ^ crc14tab[((crc >> 6) ^ (uint16_t) *data++) & 0x00FF];
51 | }
52 | return crc & 0x3FFF;
53 | }
54 |
55 | uint16_t ICACHE_RAM_ATTR GENERIC_CRC14::calc(volatile uint8_t *data, uint8_t len, uint16_t crc)
56 | {
57 | while (len--)
58 | {
59 | crc = (crc << 8) ^ crc14tab[((crc >> 6) ^ (uint16_t) *data++) & 0x00FF];
60 | }
61 | return crc & 0x3FFF;
62 | }
63 |
--------------------------------------------------------------------------------
/lib/CRC/crc.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 |
5 | #define crclen 256
6 |
7 | class GENERIC_CRC8
8 | {
9 | private:
10 | uint8_t crc8tab[crclen];
11 | uint8_t crcpoly;
12 |
13 | public:
14 | GENERIC_CRC8(uint8_t poly);
15 | uint8_t calc(const uint8_t data);
16 | uint8_t calc(const uint8_t *data, uint8_t len, uint8_t crc = 0);
17 | };
18 |
19 | class GENERIC_CRC14
20 | {
21 | private:
22 | uint16_t crc14tab[crclen];
23 | uint16_t crcpoly;
24 |
25 | public:
26 | GENERIC_CRC14(uint16_t poly);
27 | uint16_t calc(uint8_t *data, uint8_t len, uint16_t crc);
28 | uint16_t calc(volatile uint8_t *data, uint8_t len, uint16_t crc);
29 | };
30 |
--------------------------------------------------------------------------------
/lib/Channels/channels.cpp:
--------------------------------------------------------------------------------
1 | #include "channels.h"
2 |
3 | uint16_t GetFrequency(uint8_t index)
4 | {
5 | if (index >= 0 && index < 48)
6 | return frequencyTable[index];
7 | else
8 | return 0;
9 | }
10 |
11 | uint8_t GetBand(uint8_t index)
12 | {
13 | return index / 8 + 1;
14 | }
15 |
16 | uint8_t GetChannel(uint8_t index)
17 | {
18 | return (index % 8) + 1;
19 | }
--------------------------------------------------------------------------------
/lib/Channels/channels.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | const uint16_t frequencyTable[48] = {
6 | 5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725, // A
7 | 5733, 5752, 5771, 5790, 5809, 5828, 5847, 5866, // B
8 | 5705, 5685, 5665, 5645, 5885, 5905, 5925, 5945, // E
9 | 5740, 5760, 5780, 5800, 5820, 5840, 5860, 5880, // F
10 | 5658, 5695, 5732, 5769, 5806, 5843, 5880, 5917, // R
11 | 5333, 5373, 5413, 5453, 5493, 5533, 5573, 5613 // L
12 | };
13 |
14 | uint16_t GetFrequency(uint8_t index);
15 | uint8_t GetBand(uint8_t index);
16 | uint8_t GetChannel(uint8_t index);
--------------------------------------------------------------------------------
/lib/CrsfProtocol/crsf_protocol.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #define CRSF_CRC_POLY 0xd5
4 | #define CRSF_SYNC_BYTE 0xc8
5 |
6 | #define CRSF_FRAMETYPE_GPS 0x02
7 | #define CRSF_FRAMETYPE_LINK_STATISTICS 0x14
8 | #define CRSF_FRAMETYPE_BATTERY_SENSOR 0x08
9 |
10 | #define CRSF_CHANNEL_VALUE_1000 191
11 | #define CRSF_CHANNEL_VALUE_MID 992
12 | #define CRSF_CHANNEL_VALUE_2000 1792
13 |
14 | #define PACKED __attribute__((packed))
15 |
16 | /**
17 | * Define the shape of a standard header
18 | */
19 | typedef struct crsf_header_s
20 | {
21 | uint8_t sync_byte; // CRSF_SYNC_BYTE
22 | uint8_t frame_size; // counts size after this byte, so it must be the payload size + 2 (type and crc)
23 | uint8_t type; // from crsf_frame_type_e
24 | } PACKED crsf_header_t;
25 |
26 | #define CRSF_MK_FRAME_T(payload) struct payload##_frame_s { crsf_header_t h; payload p; uint8_t crc; } PACKED
27 |
28 | typedef struct crsf_sensor_gps_s {
29 | int32_t lat; // degrees * 1e7
30 | int32_t lon; // degrees * 1e7
31 | uint16_t speed; // big-endian km/h * 10
32 | uint16_t heading; // big-endian degrees * 10
33 | uint16_t altitude; // big endian meters + 1000
34 | uint8_t satcnt; // number of satellites
35 | } crsf_sensor_gps_t;
36 | typedef CRSF_MK_FRAME_T(crsf_sensor_gps_t) crsf_packet_gps_t;
37 |
38 | #if !defined(__linux__)
39 | static inline uint16_t htobe16(uint16_t val)
40 | {
41 | #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
42 | return val;
43 | #else
44 | return __builtin_bswap16(val);
45 | #endif
46 | }
47 |
48 | static inline uint16_t be16toh(uint16_t val)
49 | {
50 | #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
51 | return val;
52 | #else
53 | return __builtin_bswap16(val);
54 | #endif
55 | }
56 |
57 | static inline uint32_t htobe32(uint32_t val)
58 | {
59 | #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
60 | return val;
61 | #else
62 | return __builtin_bswap32(val);
63 | #endif
64 | }
65 |
66 | static inline uint32_t be32toh(uint32_t val)
67 | {
68 | #if (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
69 | return val;
70 | #else
71 | return __builtin_bswap32(val);
72 | #endif
73 | }
74 | #endif
75 |
--------------------------------------------------------------------------------
/lib/DEVICE/device.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "device.h"
4 | #include "common.h"
5 | #include "helpers.h"
6 |
7 | static bool eventFired = false;
8 | static device_t **uiDevices;
9 | static uint8_t deviceCount;
10 | static unsigned long deviceTimeout[16] = {0};
11 |
12 | static connectionState_e lastConnectionState = starting;
13 |
14 | void devicesInit(device_t **devices, uint8_t count)
15 | {
16 | uiDevices = devices;
17 | deviceCount = count;
18 | for(size_t i=0 ; iinitialize) {
20 | (uiDevices[i]->initialize)();
21 | }
22 | }
23 | }
24 |
25 | void devicesStart()
26 | {
27 | unsigned long now = millis();
28 | for(size_t i=0 ; istart)
32 | {
33 | int delay = (uiDevices[i]->start)();
34 | deviceTimeout[i] = delay == DURATION_NEVER ? 0xFFFFFFFF : now + delay;
35 | }
36 | }
37 | }
38 |
39 | void devicesTriggerEvent()
40 | {
41 | eventFired = true;
42 | }
43 |
44 | void devicesUpdate(unsigned long now)
45 | {
46 | bool handleEvents = eventFired;
47 | eventFired = false;
48 | for(size_t i=0 ; ievent)
51 | {
52 | int delay = (uiDevices[i]->event)();
53 | if (delay != DURATION_IGNORE)
54 | {
55 | deviceTimeout[i] = delay == DURATION_NEVER ? 0xFFFFFFFF : now + delay;
56 | }
57 | }
58 | }
59 | lastConnectionState = connectionState;
60 | for(size_t i=0 ; i deviceTimeout[i] && uiDevices[i]->timeout)
63 | {
64 | int delay = (uiDevices[i]->timeout)();
65 | deviceTimeout[i] = delay == DURATION_NEVER ? 0xFFFFFFFF : now + delay;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/lib/DEVICE/device.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 |
6 | // duration constants which can be returned from start(), event() or timeout()
7 | #define DURATION_IGNORE -2 // when returned from event() does not update the current timeout
8 | #define DURATION_NEVER -1 // timeout() will not be called, only event()
9 | #define DURATION_IMMEDIATELY 0 // timeout() will be called each loop
10 |
11 | typedef struct {
12 | // Called at the beginning of setup() so the device can configure IO pins etc
13 | void (*initialize)();
14 | // start() is called at the end of setup() and returns the number of ms when to call timeout()
15 | int (*start)();
16 | // An event was fired, take action and return new duration for timeout call
17 | int (*event)();
18 | // The duration has passed so take appropriate action and return a new duration, this function should never return DURATION_IGNORE
19 | int (*timeout)();
20 | } device_t;
21 |
22 | void devicesInit(device_t **devices, uint8_t count);
23 | void devicesStart();
24 | void devicesUpdate(unsigned long now);
25 | void devicesTriggerEvent();
--------------------------------------------------------------------------------
/lib/EEPROM/elrs_eeprom.cpp:
--------------------------------------------------------------------------------
1 | #include "elrs_eeprom.h"
2 | #include "logging.h"
3 | #include
4 |
5 | void
6 | ELRS_EEPROM::Begin()
7 | {
8 | EEPROM.begin(RESERVED_EEPROM_SIZE);
9 | }
10 |
11 | uint8_t
12 | ELRS_EEPROM::ReadByte(const uint32_t address)
13 | {
14 | if (address >= RESERVED_EEPROM_SIZE)
15 | {
16 | // address is out of bounds
17 | ERRLN("EEPROM address is out of bounds");
18 | return 0;
19 | }
20 | return EEPROM.read(address);
21 | }
22 |
23 | void
24 | ELRS_EEPROM::WriteByte(const uint32_t address, const uint8_t value)
25 | {
26 | if (address >= RESERVED_EEPROM_SIZE)
27 | {
28 | // address is out of bounds
29 | ERRLN("EEPROM address is out of bounds");
30 | return;
31 | }
32 | EEPROM.write(address, value);
33 | }
34 |
35 | void
36 | ELRS_EEPROM::Commit()
37 | {
38 | if (!EEPROM.commit())
39 | {
40 | ERRLN("EEPROM commit failed");
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/lib/EEPROM/elrs_eeprom.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include
5 |
6 | #define RESERVED_EEPROM_SIZE 1024
7 |
8 | class ELRS_EEPROM
9 | {
10 | public:
11 | void Begin();
12 | uint8_t ReadByte(const uint32_t address);
13 | void WriteByte(const uint32_t address, const uint8_t value);
14 | void Commit();
15 |
16 | // The extEEPROM lib that we use for STM doesn't have the get and put templates
17 | // These templates need to be reimplemented here
18 | template void Get(uint32_t addr, T &value)
19 | {
20 | uint8_t* p = (uint8_t*)(void*)&value;
21 | size_t i = sizeof(value);
22 | while(i--) *p++ = ReadByte(addr++);
23 | };
24 |
25 | template const void Put(uint32_t addr, const T &value)
26 | {
27 | const uint8_t* p = (const uint8_t*)(const void*)&value;
28 | size_t i = sizeof(value);
29 | while(i--) WriteByte(addr++, *p++);
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/lib/Fusion/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | file(GLOB_RECURSE files "*.c")
2 |
3 | add_library(Fusion ${files})
4 |
5 | if(UNIX AND NOT APPLE)
6 | target_link_libraries(Fusion m) # link math library for Linux
7 | endif()
8 |
--------------------------------------------------------------------------------
/lib/Fusion/Fusion.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file Fusion.h
3 | * @author Seb Madgwick
4 | * @brief Main header file for the Fusion library. This is the only file that
5 | * needs to be included when using the library.
6 | */
7 |
8 | #ifndef FUSION_H
9 | #define FUSION_H
10 |
11 | //------------------------------------------------------------------------------
12 | // Includes
13 |
14 | #ifdef __cplusplus
15 | extern "C" {
16 | #endif
17 |
18 | #include "FusionAhrs.h"
19 | #include "FusionAxes.h"
20 | #include "FusionCalibration.h"
21 | #include "FusionCompass.h"
22 | #include "FusionConvention.h"
23 | #include "FusionMath.h"
24 | #include "FusionOffset.h"
25 |
26 | #ifdef __cplusplus
27 | }
28 | #endif
29 |
30 | #endif
31 | //------------------------------------------------------------------------------
32 | // End of file
33 |
--------------------------------------------------------------------------------
/lib/Fusion/FusionAhrs.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file FusionAhrs.h
3 | * @author Seb Madgwick
4 | * @brief AHRS algorithm to combine gyroscope, accelerometer, and magnetometer
5 | * measurements into a single measurement of orientation relative to the Earth.
6 | */
7 |
8 | #ifndef FUSION_AHRS_H
9 | #define FUSION_AHRS_H
10 |
11 | //------------------------------------------------------------------------------
12 | // Includes
13 |
14 | #include "FusionConvention.h"
15 | #include "FusionMath.h"
16 | #include
17 |
18 | //------------------------------------------------------------------------------
19 | // Definitions
20 |
21 | /**
22 | * @brief AHRS algorithm settings.
23 | */
24 | typedef struct {
25 | FusionConvention convention;
26 | float gain;
27 | float gyroscopeRange;
28 | float accelerationRejection;
29 | float magneticRejection;
30 | unsigned int recoveryTriggerPeriod;
31 | } FusionAhrsSettings;
32 |
33 | /**
34 | * @brief AHRS algorithm structure. Structure members are used internally and
35 | * must not be accessed by the application.
36 | */
37 | typedef struct {
38 | FusionAhrsSettings settings;
39 | FusionQuaternion quaternion;
40 | FusionVector accelerometer;
41 | bool initialising;
42 | float rampedGain;
43 | float rampedGainStep;
44 | bool angularRateRecovery;
45 | FusionVector halfAccelerometerFeedback;
46 | FusionVector halfMagnetometerFeedback;
47 | bool accelerometerIgnored;
48 | int accelerationRecoveryTrigger;
49 | int accelerationRecoveryTimeout;
50 | bool magnetometerIgnored;
51 | int magneticRecoveryTrigger;
52 | int magneticRecoveryTimeout;
53 | } FusionAhrs;
54 |
55 | /**
56 | * @brief AHRS algorithm internal states.
57 | */
58 | typedef struct {
59 | float accelerationError;
60 | bool accelerometerIgnored;
61 | float accelerationRecoveryTrigger;
62 | float magneticError;
63 | bool magnetometerIgnored;
64 | float magneticRecoveryTrigger;
65 | } FusionAhrsInternalStates;
66 |
67 | /**
68 | * @brief AHRS algorithm flags.
69 | */
70 | typedef struct {
71 | bool initialising;
72 | bool angularRateRecovery;
73 | bool accelerationRecovery;
74 | bool magneticRecovery;
75 | } FusionAhrsFlags;
76 |
77 | //------------------------------------------------------------------------------
78 | // Function declarations
79 |
80 | void FusionAhrsInitialise(FusionAhrs *const ahrs);
81 |
82 | void FusionAhrsReset(FusionAhrs *const ahrs);
83 |
84 | void FusionAhrsSetSettings(FusionAhrs *const ahrs, const FusionAhrsSettings *const settings);
85 |
86 | void FusionAhrsUpdate(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, const FusionVector magnetometer, const float deltaTime);
87 |
88 | void FusionAhrsUpdateNoMagnetometer(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, const float deltaTime);
89 |
90 | void FusionAhrsUpdateExternalHeading(FusionAhrs *const ahrs, const FusionVector gyroscope, const FusionVector accelerometer, const float heading, const float deltaTime);
91 |
92 | FusionQuaternion FusionAhrsGetQuaternion(const FusionAhrs *const ahrs);
93 |
94 | void FusionAhrsSetQuaternion(FusionAhrs *const ahrs, const FusionQuaternion quaternion);
95 |
96 | FusionVector FusionAhrsGetLinearAcceleration(const FusionAhrs *const ahrs);
97 |
98 | FusionVector FusionAhrsGetEarthAcceleration(const FusionAhrs *const ahrs);
99 |
100 | FusionAhrsInternalStates FusionAhrsGetInternalStates(const FusionAhrs *const ahrs);
101 |
102 | FusionAhrsFlags FusionAhrsGetFlags(const FusionAhrs *const ahrs);
103 |
104 | void FusionAhrsSetHeading(FusionAhrs *const ahrs, const float heading);
105 |
106 | #endif
107 |
108 | //------------------------------------------------------------------------------
109 | // End of file
110 |
--------------------------------------------------------------------------------
/lib/Fusion/FusionCalibration.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file FusionCalibration.h
3 | * @author Seb Madgwick
4 | * @brief Gyroscope, accelerometer, and magnetometer calibration models.
5 | */
6 |
7 | #ifndef FUSION_CALIBRATION_H
8 | #define FUSION_CALIBRATION_H
9 |
10 | //------------------------------------------------------------------------------
11 | // Includes
12 |
13 | #include "FusionMath.h"
14 |
15 | //------------------------------------------------------------------------------
16 | // Inline functions
17 |
18 | /**
19 | * @brief Gyroscope and accelerometer calibration model.
20 | * @param uncalibrated Uncalibrated measurement.
21 | * @param misalignment Misalignment matrix.
22 | * @param sensitivity Sensitivity.
23 | * @param offset Offset.
24 | * @return Calibrated measurement.
25 | */
26 | static inline FusionVector FusionCalibrationInertial(const FusionVector uncalibrated, const FusionMatrix misalignment, const FusionVector sensitivity, const FusionVector offset) {
27 | return FusionMatrixMultiplyVector(misalignment, FusionVectorHadamardProduct(FusionVectorSubtract(uncalibrated, offset), sensitivity));
28 | }
29 |
30 | /**
31 | * @brief Magnetometer calibration model.
32 | * @param uncalibrated Uncalibrated measurement.
33 | * @param softIronMatrix Soft-iron matrix.
34 | * @param hardIronOffset Hard-iron offset.
35 | * @return Calibrated measurement.
36 | */
37 | static inline FusionVector FusionCalibrationMagnetic(const FusionVector uncalibrated, const FusionMatrix softIronMatrix, const FusionVector hardIronOffset) {
38 | return FusionMatrixMultiplyVector(softIronMatrix, FusionVectorSubtract(uncalibrated, hardIronOffset));
39 | }
40 |
41 | #endif
42 |
43 | //------------------------------------------------------------------------------
44 | // End of file
45 |
--------------------------------------------------------------------------------
/lib/Fusion/FusionCompass.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file FusionCompass.c
3 | * @author Seb Madgwick
4 | * @brief Tilt-compensated compass to calculate the magnetic heading using
5 | * accelerometer and magnetometer measurements.
6 | */
7 |
8 | //------------------------------------------------------------------------------
9 | // Includes
10 |
11 | #include "FusionAxes.h"
12 | #include "FusionCompass.h"
13 | #include // atan2f
14 |
15 | //------------------------------------------------------------------------------
16 | // Functions
17 |
18 | /**
19 | * @brief Calculates the magnetic heading.
20 | * @param convention Earth axes convention.
21 | * @param accelerometer Accelerometer measurement in any calibrated units.
22 | * @param magnetometer Magnetometer measurement in any calibrated units.
23 | * @return Heading angle in degrees.
24 | */
25 | float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer, const FusionVector magnetometer) {
26 | switch (convention) {
27 | case FusionConventionNwu: {
28 | const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer));
29 | const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer));
30 | return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x));
31 | }
32 | case FusionConventionEnu: {
33 | const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(accelerometer, magnetometer));
34 | const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, accelerometer));
35 | const FusionVector east = FusionVectorMultiplyScalar(west, -1.0f);
36 | return FusionRadiansToDegrees(atan2f(north.axis.x, east.axis.x));
37 | }
38 | case FusionConventionNed: {
39 | const FusionVector up = FusionVectorMultiplyScalar(accelerometer, -1.0f);
40 | const FusionVector west = FusionVectorNormalise(FusionVectorCrossProduct(up, magnetometer));
41 | const FusionVector north = FusionVectorNormalise(FusionVectorCrossProduct(west, up));
42 | return FusionRadiansToDegrees(atan2f(west.axis.x, north.axis.x));
43 | }
44 | }
45 | return 0; // avoid compiler warning
46 | }
47 |
48 | //------------------------------------------------------------------------------
49 | // End of file
50 |
--------------------------------------------------------------------------------
/lib/Fusion/FusionCompass.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file FusionCompass.h
3 | * @author Seb Madgwick
4 | * @brief Tilt-compensated compass to calculate the magnetic heading using
5 | * accelerometer and magnetometer measurements.
6 | */
7 |
8 | #ifndef FUSION_COMPASS_H
9 | #define FUSION_COMPASS_H
10 |
11 | //------------------------------------------------------------------------------
12 | // Includes
13 |
14 | #include "FusionConvention.h"
15 | #include "FusionMath.h"
16 |
17 | //------------------------------------------------------------------------------
18 | // Function declarations
19 |
20 | float FusionCompassCalculateHeading(const FusionConvention convention, const FusionVector accelerometer, const FusionVector magnetometer);
21 |
22 | #endif
23 |
24 | //------------------------------------------------------------------------------
25 | // End of file
26 |
--------------------------------------------------------------------------------
/lib/Fusion/FusionConvention.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file FusionConvention.h
3 | * @author Seb Madgwick
4 | * @brief Earth axes convention.
5 | */
6 |
7 | #ifndef FUSION_CONVENTION_H
8 | #define FUSION_CONVENTION_H
9 |
10 | //------------------------------------------------------------------------------
11 | // Definitions
12 |
13 | /**
14 | * @brief Earth axes convention.
15 | */
16 | typedef enum {
17 | FusionConventionNwu, /* North-West-Up */
18 | FusionConventionEnu, /* East-North-Up */
19 | FusionConventionNed, /* North-East-Down */
20 | } FusionConvention;
21 |
22 | #endif
23 |
24 | //------------------------------------------------------------------------------
25 | // End of file
26 |
--------------------------------------------------------------------------------
/lib/Fusion/FusionOffset.c:
--------------------------------------------------------------------------------
1 | /**
2 | * @file FusionOffset.c
3 | * @author Seb Madgwick
4 | * @brief Gyroscope offset correction algorithm for run-time calibration of the
5 | * gyroscope offset.
6 | */
7 |
8 | //------------------------------------------------------------------------------
9 | // Includes
10 |
11 | #include "FusionOffset.h"
12 | #include // fabsf
13 |
14 | //------------------------------------------------------------------------------
15 | // Definitions
16 |
17 | /**
18 | * @brief Cutoff frequency in Hz.
19 | */
20 | #define CUTOFF_FREQUENCY (0.02f)
21 |
22 | /**
23 | * @brief Timeout in seconds.
24 | */
25 | #define TIMEOUT (5)
26 |
27 | /**
28 | * @brief Threshold in degrees per second.
29 | */
30 | #define THRESHOLD (3.0f)
31 |
32 | //------------------------------------------------------------------------------
33 | // Functions
34 |
35 | /**
36 | * @brief Initialises the gyroscope offset algorithm.
37 | * @param offset Gyroscope offset algorithm structure.
38 | * @param sampleRate Sample rate in Hz.
39 | */
40 | void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate) {
41 | offset->filterCoefficient = 2.0f * (float) M_PI * CUTOFF_FREQUENCY * (1.0f / (float) sampleRate);
42 | offset->timeout = TIMEOUT * sampleRate;
43 | offset->timer = 0;
44 | offset->gyroscopeOffset = FUSION_VECTOR_ZERO;
45 | }
46 |
47 | /**
48 | * @brief Updates the gyroscope offset algorithm and returns the corrected
49 | * gyroscope measurement.
50 | * @param offset Gyroscope offset algorithm structure.
51 | * @param gyroscope Gyroscope measurement in degrees per second.
52 | * @return Corrected gyroscope measurement in degrees per second.
53 | */
54 | FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope) {
55 |
56 | // Subtract offset from gyroscope measurement
57 | gyroscope = FusionVectorSubtract(gyroscope, offset->gyroscopeOffset);
58 |
59 | // Reset timer if gyroscope not stationary
60 | if ((fabsf(gyroscope.axis.x) > THRESHOLD) || (fabsf(gyroscope.axis.y) > THRESHOLD) || (fabsf(gyroscope.axis.z) > THRESHOLD)) {
61 | offset->timer = 0;
62 | return gyroscope;
63 | }
64 |
65 | // Increment timer while gyroscope stationary
66 | if (offset->timer < offset->timeout) {
67 | offset->timer++;
68 | return gyroscope;
69 | }
70 |
71 | // Adjust offset if timer has elapsed
72 | offset->gyroscopeOffset = FusionVectorAdd(offset->gyroscopeOffset, FusionVectorMultiplyScalar(gyroscope, offset->filterCoefficient));
73 | return gyroscope;
74 | }
75 |
76 | //------------------------------------------------------------------------------
77 | // End of file
78 |
--------------------------------------------------------------------------------
/lib/Fusion/FusionOffset.h:
--------------------------------------------------------------------------------
1 | /**
2 | * @file FusionOffset.h
3 | * @author Seb Madgwick
4 | * @brief Gyroscope offset correction algorithm for run-time calibration of the
5 | * gyroscope offset.
6 | */
7 |
8 | #ifndef FUSION_OFFSET_H
9 | #define FUSION_OFFSET_H
10 |
11 | //------------------------------------------------------------------------------
12 | // Includes
13 |
14 | #include "FusionMath.h"
15 |
16 | //------------------------------------------------------------------------------
17 | // Definitions
18 |
19 | /**
20 | * @brief Gyroscope offset algorithm structure. Structure members are used
21 | * internally and must not be accessed by the application.
22 | */
23 | typedef struct {
24 | float filterCoefficient;
25 | unsigned int timeout;
26 | unsigned int timer;
27 | FusionVector gyroscopeOffset;
28 | } FusionOffset;
29 |
30 | //------------------------------------------------------------------------------
31 | // Function declarations
32 |
33 | void FusionOffsetInitialise(FusionOffset *const offset, const unsigned int sampleRate);
34 |
35 | FusionVector FusionOffsetUpdate(FusionOffset *const offset, FusionVector gyroscope);
36 |
37 | #endif
38 |
39 | //------------------------------------------------------------------------------
40 | // End of file
41 |
--------------------------------------------------------------------------------
/lib/HeadTracker/ICMSeries.cpp:
--------------------------------------------------------------------------------
1 | #include "Arduino.h"
2 | #include "Wire.h"
3 | #include "ICMSeries.h"
4 |
5 | #define WHO_AM_I 0x75
6 | #define REVISION_ID 0x01
7 | #define ACCEL_X1 0x0B
8 | #define INT_STATUS_DRDY 0x39
9 |
10 | #define ARES (16.0 / 32768)
11 | #define GRES (2000.0 / 32768)
12 |
13 | bool ICMSeries::initialize() {
14 | // Make sure it's one of the supported ICM IMUs
15 | // ICM42607P, ICM42607C, ICM42670P, ICM42670S, ICM42670T
16 | int whoami = readRegister(WHO_AM_I);
17 | if (whoami != 0x60 && whoami != 0x61 && whoami != 0x67 && whoami != 0x69 && whoami != 0x64) {
18 | return false;
19 | }
20 |
21 | /* Reset device */
22 | writeRegister(0x7C, 0); // BLK_SEL_R
23 | writeRegister(0x79, 0); // BLK_SEL_W
24 | writeRegister(0x02, 0x10); // SIGNAL_PATH_RESET (soft reset device)
25 | delay(1);
26 | writeRegister(0x01, 0x04); // DEVICE_CONFIG
27 | writeRegister(0x36, 0x41); // INF_CONFIG1, i2c mode, PLL clock
28 |
29 | readRegister(0x3A); // Clear the reset done interrupt
30 |
31 | writeRegister(0x06, 0x03); // INT_CONFIG, interrupt push-pull, active high
32 | writeRegister(0x2B, 0x08); // INT_SOURCE0, drdy
33 |
34 | writeRegister(0x21, 9); // 16G, 100Hz
35 | writeRegister(0x20, 9); // 2000dps, 100Hz
36 | writeRegister(0x1F, 0x0F); // Low noise mode for Accel/Gyro
37 |
38 | gyroRange = 2000.0;
39 | gRes = GRES;
40 |
41 | return true;
42 | }
43 |
44 | bool ICMSeries::getDataFromRegisters(FusionVector &accel, FusionVector &gyro) {
45 | uint8_t values[12];
46 |
47 | // return if NOT ready
48 | if (!(readRegister(INT_STATUS_DRDY) & 1)) {
49 | return false;
50 | }
51 |
52 | // read the accel and gyro data
53 | readBuffer(ACCEL_X1, values, 12);
54 |
55 | // map into Gs and dps
56 | accel.axis.x = (int16_t)((values[0] << 8) | values[1]) * ARES;
57 | accel.axis.y = (int16_t)((values[2] << 8) | values[3]) * ARES;
58 | accel.axis.z = (int16_t)((values[4] << 8) | values[5]) * ARES;
59 | gyro.axis.x = (int16_t)((values[6] << 8) | values[7]) * GRES;
60 | gyro.axis.y = (int16_t)((values[8] << 8) | values[9]) * GRES;
61 | gyro.axis.z = (int16_t)((values[10] << 8) | values[11]) * GRES;
62 | return true;
63 | }
64 |
--------------------------------------------------------------------------------
/lib/HeadTracker/ICMSeries.h:
--------------------------------------------------------------------------------
1 | #ifndef BACKPACK_ICM_H
2 | #define BACKPACK_ICM_H
3 |
4 | #include "FusionMath.h"
5 | #include "IMUBase.h"
6 |
7 | class ICMSeries : public IMUBase {
8 | public:
9 | ICMSeries() : IMUBase(0x68) {}
10 | bool initialize();
11 |
12 | protected:
13 | bool getDataFromRegisters(FusionVector &accel, FusionVector &gyro);
14 |
15 | private:
16 | void writeMem1Register(uint8_t reg, uint8_t val);
17 | };
18 |
19 | #endif //BACKPACK_ICM_H
20 |
--------------------------------------------------------------------------------
/lib/HeadTracker/IMUBase.h:
--------------------------------------------------------------------------------
1 | #ifndef BACKPACK_IMUBASE_H
2 | #define BACKPACK_IMUBASE_H
3 |
4 | #include "FusionMath.h"
5 |
6 | class IMUBase {
7 | public:
8 | virtual ~IMUBase() = default;
9 |
10 | virtual bool initialize() = 0;
11 | void setInterruptHandler(int pin);
12 |
13 | bool readIMUData(FusionVector &accel, FusionVector &gyro);
14 |
15 | void beginCalibration();
16 | bool updateCalibration(FusionVector &g);
17 |
18 | void setCalibration(float (*calibration)[3]);
19 |
20 | const float *getCalibration();
21 |
22 | int getSampleRate() const { return sampleRate; }
23 | float getGyroRange() const { return gyroRange; }
24 |
25 | protected:
26 | int address;
27 | int sampleRate = 0;
28 | float gyroRange;
29 | float gRes = 0;
30 |
31 | explicit IMUBase(int _address) : address(_address) {}
32 |
33 | void writeRegister(uint8_t reg, uint8_t val);
34 | uint8_t readRegister(uint8_t reg);
35 |
36 | uint8_t readBuffer(uint8_t reg, uint8_t *buffer, int length);
37 |
38 | virtual bool getDataFromRegisters(FusionVector &accel, FusionVector &gyro) = 0;
39 | };
40 |
41 | #endif //BACKPACK_IMUBASE_H
42 |
--------------------------------------------------------------------------------
/lib/HeadTracker/MPU6050.cpp:
--------------------------------------------------------------------------------
1 | #include "Arduino.h"
2 | #include "MPU6050.h"
3 |
4 | #define WHO_AM_I 0x75
5 | #define ACCEL_X_H 0x3B
6 |
7 | #define ARES (16.0 / 32768)
8 | #define GRES (2000.0 / 32768)
9 |
10 | bool MPU6050::initialize() {
11 | // First check if this an MPU6050
12 | if (((readRegister(WHO_AM_I) >> 1) & 0x3F) != 0x34) {
13 | return false;
14 | }
15 |
16 | writeRegister(0x6B, 0x01); // PWR_MGMT_1, use X axis gyro as clock reference
17 | writeRegister(0x1B, 0x18); // GYRO_CONFIG, 2000dps
18 | writeRegister(0x1C, 0x18); // ACCEL_CONFIG, +-16g
19 | writeRegister(0x1A, 0x01); // CONFIG, 184/188Hz DLPF
20 | writeRegister(0x19, 0x09); // SMPRT_DIV, 1kHz / (1 + 9) = 100Hz
21 | writeRegister(0x37, 0x02); // INT_PIN_CFG, I2C_BYPASS_EN
22 | writeRegister(0x38, 0x01); // INT_ENABLE, DATA_RDY_EN
23 | writeRegister(0x23, 0x00); // FIFO_EN, disabled
24 |
25 | gyroRange = 2000.0;
26 | gRes = GRES;
27 |
28 | return true;
29 | }
30 |
31 | bool MPU6050::getDataFromRegisters(FusionVector &accel, FusionVector &gyro) {
32 | uint8_t values[14];
33 |
34 | // Read Accel, temp & gyro data (ignore the temp)
35 | readBuffer(ACCEL_X_H, values, 14);
36 | accel.axis.x = (int16_t)((values[0] << 8) | values[1]) * ARES;
37 | accel.axis.y = (int16_t)((values[2] << 8) | values[3]) * ARES;
38 | accel.axis.z = (int16_t)((values[4] << 8) | values[5]) * ARES;
39 | gyro.axis.x = (int16_t)((values[8] << 8) | values[9]) * GRES;
40 | gyro.axis.y = (int16_t)((values[10] << 8) | values[11]) * GRES;
41 | gyro.axis.z = (int16_t)((values[12] << 8) | values[13]) * GRES;
42 | return true;
43 | }
44 |
--------------------------------------------------------------------------------
/lib/HeadTracker/MPU6050.h:
--------------------------------------------------------------------------------
1 | #include "FusionMath.h"
2 | #include "IMUBase.h"
3 |
4 | class MPU6050 : public IMUBase {
5 | public:
6 | MPU6050() : IMUBase(0x68) {}
7 |
8 | bool initialize();
9 |
10 | protected:
11 | bool getDataFromRegisters(FusionVector &accel, FusionVector &gyro);
12 | };
--------------------------------------------------------------------------------
/lib/HeadTracker/QMI8658C.cpp:
--------------------------------------------------------------------------------
1 | #include "Arduino.h"
2 | #include "logging.h"
3 | #include "Wire.h"
4 | #include "QMI8658C.h"
5 |
6 | #define WHO_AM_I 0x00
7 | #define REVISION_ID 0x01
8 | #define ACCEL_X_L 0x35
9 |
10 | #define ARES (16.0 / 32768)
11 | #define GRES (1024.0 / 32768)
12 |
13 | int QMI8658C::writeCommand(uint8_t cmd)
14 | {
15 | int val;
16 | uint32_t startMillis;
17 |
18 | writeRegister(0x0A, cmd);
19 | startMillis = millis();
20 | do {
21 | val = readRegister(0x2D);
22 | if (millis() - startMillis > 1000) {
23 | DBGLN("Wait for ctrl9 command done time out : %x val: %x", cmd, val);
24 | return -1;
25 | }
26 | } while (!(val & 0x80));
27 |
28 | writeRegister(0x0A, 0x00);
29 |
30 | startMillis = millis();
31 | do {
32 | val = readRegister(0x2D);
33 | if (millis() - startMillis > 1000) {
34 | DBGLN("Clear ctrl9 command done flag time out : %x val: %x", cmd, val);
35 | return -1;
36 | }
37 | } while ((val & 0x80));
38 |
39 | return 0;
40 | }
41 |
42 | bool QMI8658C::initialize() {
43 | writeRegister(0x60, 0xB0); // Reset
44 | while(!(readRegister(0x4D) & 0x80));
45 |
46 | writeRegister(0x02, 0b01100000); // enable auto-increment, BIG endian
47 |
48 | if (readRegister(WHO_AM_I) != 0x05) {
49 | return false;
50 | }
51 |
52 | if (readRegister(REVISION_ID) != 0x7C) {
53 | DBGLN("REVISION_ID failed");
54 | return false;
55 | }
56 |
57 | writeRegister(0x03, 0b00110011); // 16G, 896.8 ODR
58 | writeRegister(0x06, 0b00000111); // both LPF enabled @ 13.37% ODR
59 | writeRegister(0x03, 0b10110011); // 16G, 896.8 ODR + self test
60 | writeRegister(0x04, 0b01100011); // 1024dps, 896.8 ODR
61 | writeRegister(0x06, 0b01110111); // both LPF enabled @ 13.37% ODR
62 | writeRegister(0x04, 0b11100011); // 1024dps, 896.8 ODR + self test
63 | writeRegister(0x08, 0b10000011); // SyncSample, G+A enabled
64 |
65 | // disable AHB clock gating
66 | writeRegister(0x0B, 0x01);
67 | writeCommand(0x12); // times out, but meh!
68 |
69 | writeRegister(0x02, 0b01110000); // enable auto-increment, BE, INT2 enable
70 |
71 | if ((readRegister(0x08) & 3) != 3) {
72 | DBGLN("Check mode failed");
73 | return false;
74 | }
75 |
76 | gyroRange = 1024.0;
77 | gRes = GRES;
78 |
79 | return true;
80 | }
81 |
82 | bool QMI8658C::getDataFromRegisters(FusionVector &accel, FusionVector &gyro) {
83 | uint8_t values[12];
84 |
85 | // Wait for data ready
86 | uint32_t t1 = millis();
87 | do {
88 | readBuffer(0x2D, values, 3);
89 | } while ((values[0] & 3) != 3 && (millis() - t1 < 1000));
90 | if ((values[0] & 3) != 3) {
91 | return false;
92 | }
93 |
94 | // read teh accel and gyro data
95 | readBuffer(ACCEL_X_L, values, 12);
96 |
97 | // map into Gs and dps
98 | accel.axis.x = (int16_t)((values[1] << 8) | values[0]) * ARES;
99 | accel.axis.y = (int16_t)((values[3] << 8) | values[2]) * ARES;
100 | accel.axis.z = (int16_t)((values[5] << 8) | values[4]) * ARES;
101 | gyro.axis.x = (int16_t)((values[7] << 8) | values[6]) * GRES;
102 | gyro.axis.y = (int16_t)((values[9] << 8) | values[8]) * GRES;
103 | gyro.axis.z = (int16_t)((values[11] << 8) | values[10]) * GRES;
104 | return true;
105 | }
106 |
--------------------------------------------------------------------------------
/lib/HeadTracker/QMI8658C.h:
--------------------------------------------------------------------------------
1 | #include "IMUBase.h"
2 |
3 | class QMI8658C : public IMUBase {
4 | public:
5 | QMI8658C() : IMUBase(0x6B) {}
6 |
7 | bool initialize();
8 |
9 | protected:
10 | bool getDataFromRegisters(FusionVector &accel, FusionVector &gyro);
11 |
12 | private:
13 | int writeCommand(uint8_t cmd);
14 | };
--------------------------------------------------------------------------------
/lib/HeadTracker/devHeadTracker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "device.h"
4 |
5 | extern device_t HeadTracker_device;
6 |
7 | typedef enum {
8 | STATE_ERROR,
9 | STATE_RUNNING,
10 | STATE_IMU_CALIBRATING
11 | } HeadTrackerState;
12 |
13 | void startIMUCalibration();
14 | void resetBoardOrientation();
15 | void saveBoardOrientation();
16 | void setBoardOrientation(int xAngle, int yAngle, int zAngle);
17 | HeadTrackerState getHeadTrackerState();
18 |
19 | void resetCenter();
20 | void getEuler(float *yaw, float *pitch, float *roll);
21 |
--------------------------------------------------------------------------------
/lib/LED/devLED.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "common.h"
3 | #include "device.h"
4 |
5 | #if defined(TARGET_RX)
6 | extern bool connectionHasModelMatch;
7 | #endif
8 |
9 | constexpr uint8_t LEDSEQ_WIFI_UPDATE[] = { 2, 3 }; // 20ms on, 30ms off
10 | constexpr uint8_t LEDSEQ_BINDING[] = { 10, 10, 10, 100 }; // 2x 100ms blink, 1s pause
11 |
12 | static bool blipLED;
13 |
14 | #ifdef LED_INVERTED
15 | static uint8_t _pin_inverted = true;
16 | #else
17 | static uint8_t _pin_inverted = false;
18 | #endif
19 |
20 | #define UNDEF_PIN (-1)
21 |
22 | #ifndef PIN_LED
23 | #define PIN_LED UNDEF_PIN
24 | #endif
25 |
26 | static uint8_t _pin = -1;
27 | static const uint8_t *_durations;
28 | static uint8_t _count;
29 | static uint8_t _counter = 0;
30 |
31 | static uint16_t updateLED()
32 | {
33 | if(_counter % 2 == 1)
34 | digitalWrite(_pin, LOW ^ _pin_inverted);
35 | else
36 | digitalWrite(_pin, HIGH ^ _pin_inverted);
37 | if (_counter >= _count)
38 | {
39 | _counter = 0;
40 | }
41 | return _durations[_counter++] * 10;
42 | }
43 |
44 | static uint16_t flashLED(uint8_t pin, const uint8_t durations[], uint8_t count)
45 | {
46 | _counter = 0;
47 | _pin = pin;
48 | _durations = durations;
49 | _count = count;
50 | return updateLED();
51 | }
52 |
53 | static void initialize()
54 | {
55 | pinMode(PIN_LED, OUTPUT);
56 | digitalWrite(PIN_LED, HIGH ^ _pin_inverted);
57 | }
58 |
59 | static int timeout()
60 | {
61 | if (connectionState == running && blipLED)
62 | {
63 | blipLED = false;
64 | digitalWrite(PIN_LED, HIGH ^ _pin_inverted);
65 | return DURATION_NEVER;
66 | }
67 | return updateLED();
68 | }
69 |
70 | static int event()
71 | {
72 | if (connectionState == running && blipLED)
73 | {
74 | digitalWrite(PIN_LED, LOW ^ _pin_inverted);
75 | return 50; // 50ms off
76 | }
77 | if (connectionState == binding)
78 | {
79 | return flashLED(PIN_LED, LEDSEQ_BINDING, sizeof(LEDSEQ_BINDING));
80 | }
81 | if (connectionState == wifiUpdate)
82 | {
83 | return flashLED(PIN_LED, LEDSEQ_WIFI_UPDATE, sizeof(LEDSEQ_WIFI_UPDATE));
84 | }
85 | return DURATION_NEVER;
86 | }
87 |
88 | void turnOffLED()
89 | {
90 | digitalWrite(PIN_LED, LOW ^ _pin_inverted);
91 | }
92 |
93 | void blinkLED()
94 | {
95 | blipLED = true;
96 | devicesTriggerEvent();
97 | }
98 |
99 | device_t LED_device = {
100 | .initialize = initialize,
101 | .start = event,
102 | .event = event,
103 | .timeout = timeout
104 | };
105 |
--------------------------------------------------------------------------------
/lib/LED/devLED.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "device.h"
4 |
5 | extern device_t LED_device;
6 |
7 | void turnOffLED();
8 | void blinkLED();
9 |
--------------------------------------------------------------------------------
/lib/MAVLink/MAVLink.cpp:
--------------------------------------------------------------------------------
1 | #if defined(MAVLINK_ENABLED)
2 | #include
3 | #include "MAVLink.h"
4 | #include
5 |
6 | void
7 | MAVLink::ProcessMAVLinkFromTX(uint8_t c)
8 | {
9 | mavlink_status_t status;
10 | mavlink_message_t msg;
11 |
12 | if (mavlink_frame_char(MAVLINK_COMM_0, c, &msg, &status) != MAVLINK_FRAMING_INCOMPLETE)
13 | {
14 | if (mavlink_to_gcs_buf_count >= MAVLINK_BUF_SIZE)
15 | {
16 | // Cant fit any more msgs in the queue,
17 | // drop the oldest msg and start overwriting
18 | mavlink_stats.overflows_downlink++;
19 | mavlink_to_gcs_buf_count = 0;
20 | }
21 |
22 | // Track gaps in the sequence number, add to a dropped counter
23 | uint8_t seq = msg.seq;
24 | if (expectedSeqSet && seq != expectedSeq)
25 | {
26 | // account for rollovers
27 | if (seq < expectedSeq)
28 | {
29 | mavlink_stats.drops_downlink += (UINT8_MAX - expectedSeq) + seq;
30 | }
31 | else
32 | {
33 | mavlink_stats.drops_downlink += seq - expectedSeq;
34 | }
35 | }
36 | expectedSeq = seq + 1;
37 | expectedSeqSet = true;
38 |
39 | // Queue the msgs, to forward to peers
40 | mavlink_to_gcs_buf[mavlink_to_gcs_buf_count] = msg;
41 | mavlink_to_gcs_buf_count++;
42 | mavlink_stats.packets_downlink++;
43 | }
44 | }
45 |
46 | void
47 | MAVLink::ProcessMAVLinkFromGCS(uint8_t *data, uint16_t len)
48 | {
49 | mavlink_status_t status;
50 | mavlink_message_t msg;
51 |
52 | for (uint16_t i = 0; i < len; i++)
53 | {
54 | if (mavlink_frame_char(MAVLINK_COMM_1, data[i], &msg, &status) != MAVLINK_FRAMING_INCOMPLETE)
55 | {
56 | // Send the message to the tx uart
57 | uint8_t buf[MAVLINK_MAX_PACKET_LEN];
58 | uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
59 | Serial.write(buf, len);
60 | mavlink_stats.packets_uplink++;
61 | }
62 | }
63 | }
64 | #endif
65 |
--------------------------------------------------------------------------------
/lib/MAVLink/MAVLink.h:
--------------------------------------------------------------------------------
1 | #if defined(MAVLINK_ENABLED)
2 | #include "common/mavlink.h"
3 |
4 | // Size of the buffer that we will use to store mavlink packets in units of mavlink_message_t
5 | constexpr size_t MAVLINK_BUF_SIZE = 16;
6 | // Threshold at which we will flush the buffer
7 | constexpr size_t MAVLINK_BUF_THRESHOLD = MAVLINK_BUF_SIZE / 2;
8 | // Timeout for flushing the buffer in ms
9 | constexpr size_t MAVLINK_BUF_TIMEOUT = 100;
10 |
11 | typedef struct {
12 | uint32_t packets_downlink; // packets from the aircraft
13 | uint32_t packets_uplink; // packets to the aircraft
14 | uint32_t drops_downlink; // dropped packets from the aircraft
15 | //uint32_t drops_uplink; // framing in the uplink direction cost too much time
16 | uint32_t overflows_downlink; // buffer overflows from the aircraft
17 | } mavlink_stats_t;
18 |
19 | class MAVLink
20 | {
21 | public:
22 | MAVLink() : mavlink_to_gcs_buf_count(0), expectedSeq(0), expectedSeqSet(false) {}
23 | void ProcessMAVLinkFromTX(uint8_t c);
24 | void ProcessMAVLinkFromGCS(uint8_t *data, uint16_t len);
25 |
26 | // Getters
27 | mavlink_stats_t* GetMavlinkStats() { return &mavlink_stats; }
28 | mavlink_message_t* GetQueuedMsgs() { return mavlink_to_gcs_buf; }
29 | uint8_t GetQueuedMsgCount() { return mavlink_to_gcs_buf_count; }
30 |
31 | // Setters
32 | void ResetQueuedMsgCount() { mavlink_to_gcs_buf_count = 0; }
33 |
34 | private:
35 | mavlink_stats_t mavlink_stats;
36 | mavlink_message_t mavlink_to_gcs_buf[MAVLINK_BUF_SIZE]; // Buffer for storing mavlink packets from the aircraft so that we can send them to the GCS efficiently
37 | uint8_t mavlink_to_gcs_buf_count; // Count of the number of messages in the buffer
38 | uint8_t expectedSeq;
39 | bool expectedSeqSet;
40 | };
41 | #endif
42 |
--------------------------------------------------------------------------------
/lib/MSP/msp.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | // TODO: MSP_PORT_INBUF_SIZE should be changed to
6 | // dynamically allocate array length based on the payload size
7 | // Hardcoding payload size to 64 bytes for now, to allow enough space
8 | // for custom OSD text.
9 | #define MSP_PORT_INBUF_SIZE 64
10 |
11 | #define CHECK_PACKET_PARSING() \
12 | if (packet->readError) {\
13 | return;\
14 | }
15 |
16 | typedef enum {
17 | MSP_IDLE,
18 | MSP_HEADER_START,
19 | MSP_HEADER_X,
20 |
21 | MSP_HEADER_V2_NATIVE,
22 | MSP_PAYLOAD_V2_NATIVE,
23 | MSP_CHECKSUM_V2_NATIVE,
24 |
25 | MSP_COMMAND_RECEIVED
26 | } mspState_e;
27 |
28 | typedef enum {
29 | MSP_PACKET_UNKNOWN,
30 | MSP_PACKET_COMMAND,
31 | MSP_PACKET_RESPONSE
32 | } mspPacketType_e;
33 |
34 | typedef struct __attribute__((packed)) {
35 | uint8_t flags;
36 | uint16_t function;
37 | uint16_t payloadSize;
38 | } mspHeaderV2_t;
39 |
40 | typedef struct {
41 | mspPacketType_e type;
42 | uint8_t flags;
43 | uint16_t function;
44 | uint16_t payloadSize;
45 | uint8_t payload[MSP_PORT_INBUF_SIZE];
46 | uint16_t payloadReadIterator;
47 | bool readError;
48 |
49 | void reset()
50 | {
51 | type = MSP_PACKET_UNKNOWN;
52 | flags = 0;
53 | function = 0;
54 | payloadSize = 0;
55 | payloadReadIterator = 0;
56 | readError = false;
57 | }
58 |
59 | void addByte(uint8_t b)
60 | {
61 | payload[payloadSize++] = b;
62 | }
63 |
64 | void makeResponse()
65 | {
66 | type = MSP_PACKET_RESPONSE;
67 | }
68 |
69 | void makeCommand()
70 | {
71 | type = MSP_PACKET_COMMAND;
72 | }
73 |
74 | uint8_t readByte()
75 | {
76 | if (payloadReadIterator >= payloadSize) {
77 | // We are trying to read beyond the length of the payload
78 | readError = true;
79 | return 0;
80 | }
81 |
82 | return payload[payloadReadIterator++];
83 | }
84 | } mspPacket_t;
85 |
86 | /////////////////////////////////////////////////
87 |
88 | class MSP
89 | {
90 | public:
91 | MSP();
92 | bool processReceivedByte(uint8_t c);
93 | mspPacket_t* getReceivedPacket();
94 | void markPacketReceived();
95 | bool sendPacket(mspPacket_t* packet, Stream* port);
96 | uint8_t convertToByteArray(mspPacket_t* packet, uint8_t* byteArray);
97 | uint8_t getTotalPacketSize(mspPacket_t* packet);
98 | bool awaitPacket(mspPacket_t* packet, Stream* port, uint32_t timeoutMillis);
99 |
100 | private:
101 | mspState_e m_inputState;
102 | uint16_t m_offset;
103 | uint8_t m_inputBuffer[MSP_PORT_INBUF_SIZE];
104 | mspPacket_t m_packet;
105 | uint8_t m_crc;
106 | };
107 |
--------------------------------------------------------------------------------
/lib/MSP/msptypes.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #define MSP_ELRS_FUNC 0x4578 // ['E','x']
4 |
5 | #define MSP_SET_RX_CONFIG 45
6 | #define MSP_VTX_CONFIG 88 //out message Get vtx settings - betaflight
7 | #define MSP_SET_VTX_CONFIG 89 //in message Set vtx settings - betaflight
8 | #define MSP_EEPROM_WRITE 250 //in message no param
9 |
10 | // ELRS specific opcodes
11 | #define MSP_ELRS_RF_MODE 0x06
12 | #define MSP_ELRS_TX_PWR 0x07
13 | #define MSP_ELRS_TLM_RATE 0x08
14 | #define MSP_ELRS_BIND 0x09
15 | #define MSP_ELRS_MODEL_ID 0x0A
16 | #define MSP_ELRS_REQU_VTX_PKT 0x0B
17 | #define MSP_ELRS_SET_TX_BACKPACK_WIFI_MODE 0x0C
18 | #define MSP_ELRS_SET_VRX_BACKPACK_WIFI_MODE 0x0D
19 | #define MSP_ELRS_SET_RX_WIFI_MODE 0x0E
20 | #define MSP_ELRS_SET_RX_LOAN_MODE 0x0F
21 | #define MSP_ELRS_GET_BACKPACK_VERSION 0x10
22 | #define MSP_ELRS_BACKPACK_CRSF_TLM 0x11
23 | #define MSP_ELRS_SET_SEND_UID 0x00B5
24 | #define MSP_ELRS_SET_OSD 0x00B6
25 |
26 | // Config opcodes
27 | #define MSP_ELRS_BACKPACK_CONFIG 0x30
28 | #define MSP_ELRS_BACKPACK_CONFIG_TLM_MODE 0x31
29 |
30 | // CRSF encapsulated msp defines
31 | #define ENCAPSULATED_MSP_PAYLOAD_SIZE 4
32 | #define ENCAPSULATED_MSP_FRAME_LEN 8
33 |
34 | // ELRS backpack protocol opcodes
35 | // See: https://docs.google.com/document/d/1u3c7OTiO4sFL2snI-hIo-uRSLfgBK4h16UrbA08Pd6U/edit#heading=h.1xw7en7jmvsj
36 |
37 | // outgoing, packets originating from the backpack or forwarded from the TX backpack to the VRx
38 | #define MSP_ELRS_BACKPACK_GET_CHANNEL_INDEX 0x0300
39 | #define MSP_ELRS_BACKPACK_SET_CHANNEL_INDEX 0x0301
40 | #define MSP_ELRS_BACKPACK_GET_FREQUENCY 0x0302
41 | #define MSP_ELRS_BACKPACK_SET_FREQUENCY 0x0303
42 | #define MSP_ELRS_BACKPACK_GET_RECORDING_STATE 0x0304
43 | #define MSP_ELRS_BACKPACK_SET_RECORDING_STATE 0x0305
44 | #define MSP_ELRS_BACKPACK_GET_VRX_MODE 0x0306
45 | #define MSP_ELRS_BACKPACK_SET_VRX_MODE 0x0307
46 | #define MSP_ELRS_BACKPACK_GET_RSSI 0x0308
47 | #define MSP_ELRS_BACKPACK_GET_BATTERY_VOLTAGE 0x0309
48 | #define MSP_ELRS_BACKPACK_GET_FIRMWARE 0x030A
49 | #define MSP_ELRS_BACKPACK_SET_BUZZER 0x030B
50 | #define MSP_ELRS_BACKPACK_SET_OSD_ELEMENT 0x030C
51 | #define MSP_ELRS_BACKPACK_SET_HEAD_TRACKING 0x030D // enable/disable head-tracking forwarding packets to the TX
52 | #define MSP_ELRS_BACKPACK_SET_RTC 0x030E
53 |
54 | // incoming, packets originating from the VRx
55 | #define MSP_ELRS_BACKPACK_SET_MODE 0x0380 // enable wifi/binding mode
56 | #define MSP_ELRS_BACKPACK_GET_VERSION 0x0381 // get the bacpack firmware version
57 | #define MSP_ELRS_BACKPACK_GET_STATUS 0x0382 // get the status of the backpack
58 | #define MSP_ELRS_BACKPACK_SET_PTR 0x0383 // forwarded back to TX backpack
59 |
--------------------------------------------------------------------------------
/lib/QMC5883L/QMC5883LCompass.h:
--------------------------------------------------------------------------------
1 | #ifndef QMC5883L_Compass
2 | #define QMC5883L_Compass
3 |
4 | #include "Arduino.h"
5 | #include "Wire.h"
6 |
7 | class QMC5883LCompass {
8 |
9 | public:
10 | QMC5883LCompass();
11 | void init();
12 | void setADDR(byte b);
13 | void setMode(byte mode, byte odr, byte rng, byte osr);
14 | void setMagneticDeclination(int degrees, uint8_t minutes);
15 | void setSmoothing(byte steps, bool adv);
16 | void setCalibration(int x_min, int x_max, int y_min, int y_max, int z_min,
17 | int z_max);
18 | void setCalibrationOffsets(float x_offset, float y_offset, float z_offset);
19 | void setCalibrationScales(float x_scale, float y_scale, float z_scale);
20 | float getCalibrationOffset(uint8_t index);
21 | float getCalibrationScale(uint8_t index);
22 | void clearCalibration();
23 | void setReset();
24 | void read();
25 | int getX();
26 | int getY();
27 | int getZ();
28 | int getAzimuth();
29 | byte getBearing(int azimuth);
30 | void getDirection(char *myArray, int azimuth);
31 | int readChipId();
32 |
33 | private:
34 | void _writeReg(byte reg, byte val);
35 | int _get(int index);
36 | float _magneticDeclinationDegrees = 0;
37 | bool _smoothUse = false;
38 | byte _smoothSteps = 5;
39 | bool _smoothAdvanced = false;
40 | byte _ADDR = 0x0D;
41 | int _vRaw[3] = {0, 0, 0};
42 | int _vHistory[10][3];
43 | int _vScan = 0;
44 | long _vTotals[3] = {0, 0, 0};
45 | int _vSmooth[3] = {0, 0, 0};
46 | void _smoothing();
47 | float _offset[3] = {0., 0., 0.};
48 | float _scale[3] = {1., 1., 1.};
49 | int _vCalibrated[3];
50 | void _applyCalibration();
51 | const char _bearings[16][3] = {
52 | {' ', ' ', 'N'}, {'N', 'N', 'E'}, {' ', 'N', 'E'}, {'E', 'N', 'E'},
53 | {' ', ' ', 'E'}, {'E', 'S', 'E'}, {' ', 'S', 'E'}, {'S', 'S', 'E'},
54 | {' ', ' ', 'S'}, {'S', 'S', 'W'}, {' ', 'S', 'W'}, {'W', 'S', 'W'},
55 | {' ', ' ', 'W'}, {'W', 'N', 'W'}, {' ', 'N', 'W'}, {'N', 'N', 'W'},
56 | };
57 | };
58 |
59 | #endif
--------------------------------------------------------------------------------
/lib/WIFI/UpdateWrapper.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | class UpdateWrapper {
4 | public:
5 |
6 | #if PLATFORM_ESP8266
7 | bool begin(size_t size) {
8 | #else
9 | bool begin() {
10 | #endif
11 | _running = true;
12 | #if PLATFORM_ESP8266
13 | return Update.begin(size, U_FLASH);
14 | #else
15 | return Update.begin();
16 | #endif
17 | }
18 |
19 | size_t write(uint8_t *data, size_t len) {
20 | return Update.write(data, len);
21 | }
22 |
23 | bool end(bool evenIfRemaining = false) {
24 | _running = false;
25 | return Update.end(evenIfRemaining);
26 | }
27 |
28 | void printError(Print &out) {
29 | return Update.printError(out);
30 | }
31 |
32 | bool hasError() {
33 | return Update.hasError();
34 | }
35 |
36 | void runAsync(bool async) {
37 | #ifdef PLATFORM_ESP8266
38 | Update.runAsync(async);
39 | #endif
40 | }
41 |
42 | bool isRunning() {
43 | return _running;
44 | }
45 |
46 | #ifdef PLATFORM_ESP32
47 | void abort() {
48 | Update.abort();
49 | }
50 | #endif
51 |
52 | private:
53 | bool _running = false;
54 | };
55 |
--------------------------------------------------------------------------------
/lib/WIFI/devWIFI.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "device.h"
4 |
5 | #if defined(PLATFORM_ESP32) || defined(PLATFORM_ESP8266)
6 | extern device_t WIFI_device;
7 | #define HAS_WIFI
8 |
9 | extern const char *VERSION;
10 | #endif
--------------------------------------------------------------------------------
/lib/logging/logging.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "logging.h"
4 |
5 | #ifdef LOG_USE_PROGMEM
6 | #define GETCHAR pgm_read_byte(fmt)
7 | #else
8 | #define GETCHAR *fmt
9 | #endif
10 |
11 | void debugPrintf(const char* fmt, ...)
12 | {
13 | char c;
14 | va_list vlist;
15 | va_start(vlist,fmt);
16 |
17 | c = GETCHAR;
18 | while(c) {
19 | if (c == '%') {
20 | fmt++;
21 | c = GETCHAR;
22 | switch (c) {
23 | case 's':
24 | LOGGING_UART.print(va_arg(vlist,const char *));
25 | break;
26 | case 'd':
27 | LOGGING_UART.print(va_arg(vlist,int32_t), DEC);
28 | break;
29 | case 'u':
30 | LOGGING_UART.print(va_arg(vlist,uint32_t), DEC);
31 | break;
32 | case 'x':
33 | LOGGING_UART.print(va_arg(vlist,uint32_t), HEX);
34 | break;
35 | default:
36 | break;
37 | }
38 | } else {
39 | LOGGING_UART.write(c);
40 | }
41 | fmt++;
42 | c = GETCHAR;
43 | }
44 | va_end(vlist);
45 | }
--------------------------------------------------------------------------------
/lib/logging/logging.h:
--------------------------------------------------------------------------------
1 | #ifndef DEBUG_H
2 | #define DEBUG_H
3 |
4 | /**
5 | * Debug logging macros. Define DEBUG_LOG or DEBUG_LOG_VERBOSE to enable logging,
6 | * verbose adds overwhelming detail. All macros start with DBG or DBGV (for verbose)
7 | * DBGCR / DBGVCR - Print newline (Serial.println())
8 | * DBG / DBGV - Print messag with optional format specifier (Serial.printf(x, ...))
9 | * DBGLN / DBGVLN - Same as DBG except also includes newline
10 | * DBGW / DBGVW - Write a single byte to logging (Serial.write(x))
11 | *
12 | * Set LOGGING_UART define to Serial instance to use if not Serial
13 | **/
14 |
15 | #ifndef LOGGING_UART
16 | #define LOGGING_UART Serial
17 | #endif
18 |
19 | // #define LOG_USE_PROGMEM
20 |
21 | extern void debugPrintf(const char* fmt, ...);
22 |
23 | #define INFOLN(msg, ...) { \
24 | debugPrintf(msg, ##__VA_ARGS__); \
25 | LOGGING_UART.println(); \
26 | }
27 | #define ERRLN(msg) LOGGING_UART.println("ERROR: " msg)
28 |
29 | #if defined(DEBUG_LOG) || defined(DEBUG_LOG_VERBOSE)
30 | #define DBGCR LOGGING_UART.println()
31 | #define DBGW(c) LOGGING_UART.write(c)
32 | #ifndef LOG_USE_PROGMEM
33 | #define DBG(msg, ...) debugPrintf(msg, ##__VA_ARGS__)
34 | #define DBGLN(msg, ...) { \
35 | debugPrintf(msg, ##__VA_ARGS__); \
36 | LOGGING_UART.println(); \
37 | }
38 | #else
39 | #define DBG(msg, ...) debugPrintf(PSTR(msg), ##__VA_ARGS__)
40 | #define DBGLN(msg, ...) { \
41 | debugPrintf(PSTR(msg), ##__VA_ARGS__); \
42 | LOGGING_UART.println(); \
43 | }
44 | #endif
45 |
46 | // Verbose logging is for spammy stuff
47 | #if defined(DEBUG_LOG_VERBOSE)
48 | #define DBGVCR DBGCR
49 | #define DBGVW(c) DBGW(c)
50 | #define DBGV(...) DBG(__VA_ARGS__)
51 | #define DBGVLN(...) DBGLN(__VA_ARGS__)
52 | #else
53 | #define DBGVCR
54 | #define DBGVW(c)
55 | #define DBGV(...)
56 | #define DBGVLN(...)
57 | #endif
58 | #else
59 | #define DBGCR
60 | #define DBGW(c)
61 | #define DBG(...)
62 | #define DBGLN(...)
63 | #define DBGVCR
64 | #define DBGV(...)
65 | #define DBGVLN(...)
66 | #endif
67 |
68 | #endif
--------------------------------------------------------------------------------
/platformio.ini:
--------------------------------------------------------------------------------
1 | ; ExpressLRS Backpack PlatformIO Project Configuration File
2 |
3 | [platformio]
4 | extra_configs =
5 | # keep common first
6 | targets/common.ini
7 | targets/debug.ini
8 | targets/txbp_esp.ini
9 | targets/timer.ini
10 | # defined one by one to maintain the order
11 | targets/aat.ini
12 | targets/fusion.ini
13 | targets/hdzero.ini
14 | targets/orqa.ini
15 | targets/rapidfire.ini
16 | targets/rx5808.ini
17 | targets/skyzone.ini
18 | targets/mfd_crossbow.ini
19 |
--------------------------------------------------------------------------------
/python/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include *.py
2 | recursive-include external *.py *.json
3 |
--------------------------------------------------------------------------------
/python/UnifiedConfiguration.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 |
3 | import json
4 | import struct
5 | import sys
6 |
7 | def findFirmwareEnd(f):
8 | f.seek(0, 0)
9 | (magic, segments, _, _, _) = struct.unpack(' typing.Mapping[str, typing.Any]:
9 | builder_rules: typing.Dict[str, typing.List[BuilderRule]] = {}
10 | for extension in extensions:
11 | if hasattr(extension, "builder_rules"):
12 | rules = extension.builder_rules
13 | for token, builder in rules:
14 | builder_rules.setdefault(token, []).append(builder)
15 | return {"builder_rules": builder_rules}
16 |
17 |
18 | class BlockBuilder(Builder):
19 |
20 | __slots__ = ("rules", "indent", "lineno", "buf")
21 |
22 | def __init__(
23 | self,
24 | rules: typing.Dict[str, typing.List[BuilderRule]],
25 | indent: str = "",
26 | lineno: int = 0,
27 | ) -> None:
28 | self.rules = rules
29 | self.indent = indent
30 | self.lineno = lineno
31 | self.buf: typing.List[str] = []
32 |
33 | def start_block(self) -> None:
34 | self.indent += " "
35 |
36 | def end_block(self) -> None:
37 | if len(self.indent) < 4:
38 | raise SyntaxError("Unexpected end of block.")
39 | self.indent = self.indent[:-4]
40 |
41 | def add(self, lineno: int, code: str) -> None:
42 | if lineno < self.lineno:
43 | raise SyntaxError(
44 | "Inconsistence at %s : %s" % (self.lineno, lineno)
45 | )
46 | if lineno == self.lineno:
47 | line = self.buf[-1]
48 | if code:
49 | if line:
50 | if line[-1:] == ":":
51 | self.buf[-1] = line + code
52 | else:
53 | self.buf[-1] = line + "; " + code
54 | else:
55 | self.buf[-1] = self.indent + code
56 | else:
57 | pad = lineno - self.lineno - 1
58 | if pad > 0:
59 | self.buf.extend([""] * pad)
60 | if code:
61 | self.buf.append(self.indent + code)
62 | else:
63 | self.buf.append("")
64 | self.lineno = lineno + code.count("\n")
65 |
66 | def build_block(self, nodes: typing.Iterable[Token]) -> None:
67 | for lineno, token, value in nodes:
68 | self.build_token(lineno, token, value)
69 |
70 | def build_token(
71 | self,
72 | lineno: int,
73 | token: str,
74 | value: typing.Union[str, typing.Iterable[Token]],
75 | ) -> None:
76 | if token in self.rules:
77 | for rule in self.rules[token]:
78 | if rule(self, lineno, token, value):
79 | break
80 | else:
81 | raise SyntaxError(
82 | 'No rule to build "%s" token at line %d.' % (token, lineno)
83 | )
84 |
85 | def to_string(self) -> str:
86 | return "\n".join(self.buf)
87 |
88 |
89 | class SourceBuilder(object):
90 |
91 | __slots__ = ("rules", "lineno")
92 |
93 | def __init__(
94 | self,
95 | builder_rules: typing.Dict[str, typing.List[BuilderRule]],
96 | builder_offset: int = 2,
97 | **ignore: typing.Any
98 | ) -> None:
99 | self.rules = builder_rules
100 | self.lineno = 0 - builder_offset
101 |
102 | def build_source(self, nodes: typing.Iterable[Token]) -> str:
103 | builder = BlockBuilder(self.rules)
104 | builder.build_block(nodes)
105 | return builder.to_string()
106 |
107 | def build_render(self, nodes: typing.Iterable[Token]) -> str:
108 | builder = BlockBuilder(self.rules, lineno=self.lineno)
109 | builder.add(
110 | self.lineno + 1, "def render(ctx, local_defs, super_defs):"
111 | )
112 | builder.start_block()
113 | builder.build_token(self.lineno + 2, "render", nodes)
114 | return builder.to_string()
115 |
116 | def build_module(self, nodes: typing.Iterable[Token]) -> str:
117 | builder = BlockBuilder(self.rules, lineno=self.lineno)
118 | builder.add(self.lineno + 1, "local_defs = {}; super_defs = {}")
119 | builder.build_token(self.lineno + 2, "module", nodes)
120 | return builder.to_string()
121 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/comp.py:
--------------------------------------------------------------------------------
1 | import ast
2 | import typing
3 | from _thread import allocate_lock # noqa
4 |
5 |
6 | def adjust_source_lineno(source: str, name: str, lineno: int) -> typing.Any:
7 | node = compile(source, name, "exec", ast.PyCF_ONLY_AST)
8 | ast.increment_lineno(node, lineno)
9 | return node
10 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/compiler.py:
--------------------------------------------------------------------------------
1 | import typing
2 | from types import ModuleType
3 |
4 | from external.wheezy.template.comp import adjust_source_lineno
5 |
6 |
7 | class Compiler(object):
8 | def __init__(
9 | self, global_vars: typing.Dict[str, typing.Any], source_lineno: int
10 | ) -> None:
11 | self.global_vars = global_vars
12 | self.source_lineno = source_lineno
13 |
14 | def compile_module(self, source: str, name: str) -> ModuleType:
15 | node = adjust_source_lineno(source, name, self.source_lineno)
16 | compiled = compile(node, name, "exec")
17 | module = ModuleType(name)
18 | module.__name__ = name
19 | module.__dict__.update(self.global_vars)
20 | exec(compiled, module.__dict__)
21 | return module
22 |
23 | def compile_source(
24 | self, source: str, name: str
25 | ) -> typing.Dict[str, typing.Any]:
26 | node = adjust_source_lineno(source, name, self.source_lineno)
27 | compiled = compile(node, name, "exec")
28 | local_vars: typing.Dict[str, typing.Any] = {}
29 | exec(compiled, self.global_vars, local_vars)
30 | return local_vars
31 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/console.py:
--------------------------------------------------------------------------------
1 | import getopt
2 | import json
3 | import os
4 | import sys
5 | import typing
6 |
7 | from external.wheezy.template.engine import Engine
8 | from external.wheezy.template.ext.code import CodeExtension
9 | from external.wheezy.template.ext.core import CoreExtension
10 | from external.wheezy.template.loader import FileLoader
11 |
12 | try:
13 | from wheezy.html.utils import escape_html as escape # type: ignore[import]
14 | except ImportError: # pragma: nocover
15 | from html import escape
16 |
17 |
18 | class Options:
19 | def __init__(
20 | self,
21 | token_start: str = "@",
22 | line_join: str = "\\",
23 | searchpath: typing.Optional[typing.List[str]] = None,
24 | extensions: typing.Optional[typing.List[str]] = None,
25 | ) -> None:
26 | self.token_start = token_start
27 | self.line_join = line_join
28 | self.searchpath = searchpath or ["."]
29 | self.extensions = extensions or []
30 | self.template = ""
31 | self.context: typing.List[str] = []
32 |
33 |
34 | def main(argv: typing.Optional[typing.List[str]] = None) -> int:
35 | args = parse_args(argv or sys.argv[1:])
36 | if not args:
37 | return 2
38 | ts = args.token_start
39 | extensions = [CoreExtension(ts, args.line_join), CodeExtension(ts)]
40 | extensions.extend(args.extensions)
41 | engine = Engine(FileLoader(args.searchpath), extensions)
42 | engine.global_vars.update({"h": escape})
43 | t = engine.get_template(args.template)
44 | sys.stdout.write(t.render(load_context(args.context)))
45 | return 0
46 |
47 |
48 | def load_context(sources: typing.List[str]) -> typing.Mapping[str, typing.Any]:
49 | c: typing.Dict[str, typing.Any] = {}
50 | for s in sources:
51 | if os.path.isfile(s):
52 | d = json.load(open(s))
53 | else:
54 | d = json.loads(s)
55 | c.update(d)
56 | return c
57 |
58 |
59 | def parse_args( # noqa: C901
60 | args: typing.List[str],
61 | ) -> typing.Optional[Options]:
62 | try:
63 | opts, value = getopt.getopt(args, "s:t:j:wh")
64 | except getopt.GetoptError:
65 | e = sys.exc_info()[1]
66 | usage()
67 | print("error: %s" % e)
68 | return None
69 | d = Options(token_start="@", searchpath=["."], extensions=[])
70 | for o, a in opts:
71 | if o == "-h":
72 | return None
73 | elif o == "-t":
74 | d.token_start = a
75 | elif o == "-j":
76 | d.line_join = a
77 | elif o == "-s":
78 | d.searchpath = a.split(";")
79 | elif o == "-w": # pragma: nocover
80 | from wheezy.html.ext.template import ( # type: ignore[import]
81 | WhitespaceExtension,
82 | )
83 |
84 | d.extensions.append(WhitespaceExtension())
85 | if not value:
86 | usage()
87 | return None
88 | d.template = value[0]
89 | d.context = value[1:]
90 | return d
91 |
92 |
93 | def usage() -> None:
94 | from datetime import datetime
95 | from os.path import basename
96 |
97 | from wheezy.template import __version__
98 |
99 | print(
100 | """\
101 | wheezy.template %s
102 | Copyright (C) 2012-%d by Andriy Kornatskyy
103 |
104 | renders a template with the given context.
105 |
106 | usage: %s template [ context ... ]
107 |
108 | positional arguments:
109 |
110 | template a filename
111 | context a filename or JSON string
112 |
113 | optional arguments:
114 |
115 | -s path search path for templates ( . )
116 | -t token token start ( @ )
117 | -j token line join ( \\ )
118 | -w whitespace clean up
119 | -h show this help message
120 | """
121 | % (__version__, datetime.now().year, basename(sys.argv[0]))
122 | )
123 |
124 |
125 | if __name__ == "__main__": # pragma: nocover
126 | sys.exit(main())
127 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/ext/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/python/external/wheezy/template/ext/__init__.py
--------------------------------------------------------------------------------
/python/external/wheezy/template/ext/code.py:
--------------------------------------------------------------------------------
1 | import re
2 | import typing
3 |
4 | from external.wheezy.template.typing import Builder, LexerRule, ParserRule, Token
5 | from external.wheezy.template.utils import find_balanced
6 |
7 | # region: lexer extensions
8 |
9 |
10 | def code_token(m: typing.Match[str]) -> Token:
11 | source = m.string
12 | start = m.end()
13 | end = find_balanced(source, start)
14 | if source[end::1] == "\n":
15 | end += 1
16 | return end, "code", source[start:end]
17 |
18 |
19 | # region: parser
20 |
21 |
22 | def parse_code(value: str) -> typing.List[str]:
23 | lines = value.rstrip("\n")[1:-1].split("\n")
24 | lines[0] = lines[0].lstrip()
25 | if len(lines) == 1:
26 | return lines
27 | line = lines[1]
28 | n = len(line) - len(line.lstrip())
29 | return [s[:n].lstrip() + s[n:] for s in lines]
30 |
31 |
32 | # region: block_builders
33 |
34 |
35 | def build_code(
36 | builder: Builder, lineno: int, token: str, lines: typing.List[str]
37 | ) -> bool:
38 | for line in lines:
39 | builder.add(lineno, line)
40 | lineno += 1
41 | return True
42 |
43 |
44 | # region: core extension
45 |
46 |
47 | class CodeExtension:
48 | """Includes support for embedded python code."""
49 |
50 | def __init__(self, token_start: str = "@") -> None:
51 |
52 | self.lexer_rules: typing.Mapping[int, LexerRule] = {
53 | 300: (re.compile(r"\s*%s(?=\()" % token_start), code_token),
54 | }
55 |
56 | parser_rules: typing.Mapping[str, ParserRule] = {"code": parse_code}
57 |
58 | builder_rules: typing.List[typing.Tuple[str, typing.Any]] = [
59 | ("code", build_code)
60 | ]
61 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/ext/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/python/external/wheezy/template/ext/tests/__init__.py
--------------------------------------------------------------------------------
/python/external/wheezy/template/ext/tests/test_determined.py:
--------------------------------------------------------------------------------
1 | """ Unit tests for ``wheezy.templates.ext.determined``.
2 | """
3 |
4 | import unittest
5 |
6 | from wheezy.template.ext.determined import DeterminedExtension
7 |
8 |
9 | class DeterminedTestCase(unittest.TestCase):
10 | """Test the ``DeterminedExtension``."""
11 |
12 | def setUp(self) -> None:
13 | self.preprocess = DeterminedExtension(
14 | known_calls=["path_for", "_"]
15 | ).preprocessors[0]
16 |
17 | def test_determined(self) -> None:
18 | """Substitute determinded expressions for known calls to
19 | preprocessor calls.
20 | """
21 | assert """\
22 | #ctx['_']('Name:')
23 | #ctx['path_for']('default')
24 | #ctx['path_for']('static', path='/static/css/site.css')
25 | """ == self.preprocess(
26 | """\
27 | @_('Name:')
28 | @path_for('default')
29 | @path_for('static', path='/static/css/site.css')
30 | """
31 | )
32 |
33 | def test_undetermined(self) -> None:
34 | """Calls that are not determined left unchanged."""
35 | assert """\
36 | @path_for('item', id=id)
37 | @model.username.label(_('Username: '))
38 | """ == self.preprocess(
39 | """\
40 | @path_for('item', id=id)
41 | @model.username.label(_('Username: '))
42 | """
43 | )
44 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/lexer.py:
--------------------------------------------------------------------------------
1 | import typing
2 |
3 | from external.wheezy.template.typing import (
4 | LexerRule,
5 | PostProcessorRule,
6 | PreProcessorRule,
7 | Token,
8 | )
9 |
10 |
11 | def lexer_scan(
12 | extensions: typing.List[typing.Any],
13 | ) -> typing.Mapping[str, typing.Any]:
14 | """Scans extensions for ``lexer_rules`` and ``preprocessors``
15 | attributes.
16 | """
17 | lexer_rules: typing.Dict[int, LexerRule] = {}
18 | preprocessors: typing.List[PreProcessorRule] = []
19 | postprocessors: typing.List[PostProcessorRule] = []
20 | for extension in extensions:
21 | if hasattr(extension, "lexer_rules"):
22 | lexer_rules.update(extension.lexer_rules)
23 | if hasattr(extension, "preprocessors"):
24 | preprocessors.extend(extension.preprocessors)
25 | if hasattr(extension, "postprocessors"):
26 | postprocessors.extend(extension.postprocessors)
27 | return {
28 | "lexer_rules": [lexer_rules[k] for k in sorted(lexer_rules.keys())],
29 | "preprocessors": preprocessors,
30 | "postprocessors": postprocessors,
31 | }
32 |
33 |
34 | class Lexer(object):
35 | """Tokenizes input source per rules supplied."""
36 |
37 | def __init__(
38 | self,
39 | lexer_rules: typing.List[LexerRule],
40 | preprocessors: typing.Optional[typing.List[PreProcessorRule]] = None,
41 | postprocessors: typing.Optional[typing.List[PostProcessorRule]] = None,
42 | **ignore: typing.Any
43 | ) -> None:
44 | """Initializes with ``rules``. Rules must be a list of
45 | two elements tuple: ``(regex, tokenizer)`` where
46 | tokenizer if a callable of the following contract::
47 |
48 | def tokenizer(match):
49 | return end_index, token, value
50 | """
51 | self.rules = lexer_rules
52 | self.preprocessors = preprocessors or []
53 | self.postprocessors = postprocessors or []
54 |
55 | def tokenize(self, source: str) -> typing.List[Token]:
56 | """Translates ``source`` accoring to lexer rules into
57 | an iteratable of tokens.
58 | """
59 | for preprocessor in self.preprocessors:
60 | source = preprocessor(source)
61 | tokens: typing.List[Token] = []
62 | append = tokens.append
63 | pos = 0
64 | lineno = 1
65 | end = len(source)
66 | while pos < end:
67 | for regex, tokenizer in self.rules:
68 | m = regex.match(source, pos, end)
69 | if m is not None:
70 | npos, token, value = tokenizer(m)
71 | assert npos > pos
72 | append((lineno, token, value))
73 | lineno += source[pos:npos].count("\n")
74 | pos = npos
75 | break
76 | else:
77 | raise AssertionError("Lexer pattern mismatch.")
78 | for postprocessor in self.postprocessors:
79 | postprocessor(tokens)
80 | return tokens
81 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/parser.py:
--------------------------------------------------------------------------------
1 | import typing
2 |
3 | from external.wheezy.template.typing import ParserConfig, ParserRule, Token
4 |
5 |
6 | def parser_scan(
7 | extensions: typing.List[typing.Any],
8 | ) -> typing.Mapping[str, typing.Any]:
9 | parser_rules = {}
10 | parser_configs = []
11 | for extension in extensions:
12 | if hasattr(extension, "parser_rules"):
13 | parser_rules.update(extension.parser_rules)
14 | if hasattr(extension, "parser_configs"):
15 | parser_configs.extend(extension.parser_configs)
16 | return {
17 | "parser_rules": parser_rules,
18 | "parser_configs": parser_configs,
19 | }
20 |
21 |
22 | class Parser(ParserConfig):
23 | """
24 | ``continue_tokens`` are used to insert ``end`` node right
25 | before them to simulate a block end. Such nodes have token
26 | value ``None``.
27 |
28 | ``out_tokens`` are combined together into a single node.
29 | """
30 |
31 | def __init__(
32 | self,
33 | parser_rules: typing.Dict[str, ParserRule],
34 | parser_configs: typing.Optional[
35 | typing.List[typing.Callable[[ParserConfig], None]]
36 | ] = None,
37 | **ignore: typing.Any
38 | ) -> None:
39 | self.end_tokens: typing.List[str] = []
40 | self.continue_tokens: typing.List[str] = []
41 | self.compound_tokens: typing.List[str] = []
42 | self.out_tokens: typing.List[str] = []
43 | self.rules = parser_rules
44 | if parser_configs:
45 | for config in parser_configs:
46 | config(self)
47 |
48 | def end_continue(
49 | self, tokens: typing.List[Token]
50 | ) -> typing.Iterator[Token]:
51 | """If token is in ``continue_tokens`` prepend it
52 | with end token so it simulate a closed block.
53 | """
54 | for t in tokens:
55 | if t[1] in self.continue_tokens:
56 | yield (t[0], "end", "")
57 | yield t
58 |
59 | def parse_iter(
60 | self, tokens: typing.Iterator[Token]
61 | ) -> typing.Iterator[typing.Any]:
62 | operands = []
63 | for lineno, token, value in tokens:
64 | if token in self.rules:
65 | value = self.rules[token](value) # type: ignore[assignment]
66 | if token in self.out_tokens:
67 | operands.append((lineno, token, value))
68 | else:
69 | if operands:
70 | yield operands[0][0], "out", operands
71 | operands = []
72 | if token in self.compound_tokens:
73 | yield lineno, token, (value, list(self.parse_iter(tokens)))
74 | else:
75 | yield lineno, token, value
76 | if token in self.end_tokens:
77 | break
78 | if operands:
79 | yield operands[0][0], "out", operands
80 |
81 | def parse(self, tokens: typing.List[Token]) -> typing.List[typing.Any]:
82 | return list(self.parse_iter(self.end_continue(tokens)))
83 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/py.typed:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/python/external/wheezy/template/py.typed
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/python/external/wheezy/template/tests/__init__.py
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/test_builder.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from wheezy.template.builder import BlockBuilder
4 |
5 |
6 | class BlockBuilderTestCase(unittest.TestCase):
7 | """Test the ``BlockBuilder`` blocks."""
8 |
9 | def setUp(self) -> None:
10 | self.builder = BlockBuilder({})
11 |
12 | def test_start_end_block(self) -> None:
13 | """Test start_block and end_block."""
14 | self.builder.start_block()
15 | assert self.builder.indent == " "
16 | self.builder.end_block()
17 | assert self.builder.indent == ""
18 |
19 | def test_unexpected_end_block(self) -> None:
20 | """Test raises error."""
21 | self.assertRaises(SyntaxError, lambda: self.builder.end_block())
22 |
23 | def test_inconsistence(self) -> None:
24 | """Test add a line with wrong lineno."""
25 | self.assertRaises(SyntaxError, lambda: self.builder.add(-1, ""))
26 |
27 | def test_unknown_token(self) -> None:
28 | """Test raises error if token is unknown."""
29 | self.assertRaises(
30 | SyntaxError, lambda: self.builder.build_token(1, "x", "")
31 | )
32 |
33 |
34 | class BlockBuilderAddSameLineTestCase(unittest.TestCase):
35 | """Test the ``BlockBuilder.add`` to the same line."""
36 |
37 | def setUp(self) -> None:
38 | self.builder = BlockBuilder({}, indent=" ", lineno=1)
39 |
40 | def test_code_empty(self) -> None:
41 | self.builder.buf = [""]
42 | self.builder.add(1, "")
43 | assert "" == self.builder.to_string()
44 |
45 | def test_line_empty(self) -> None:
46 | self.builder.buf = [""]
47 | self.builder.add(1, "pass")
48 | assert " pass" == self.builder.to_string()
49 |
50 | def test_line_ends_colon(self) -> None:
51 | self.builder.buf = ["def title():"]
52 | self.builder.add(1, 'return ""')
53 | assert 'def title():return ""' == self.builder.to_string()
54 |
55 | def test_continue_same_line(self) -> None:
56 | self.builder.buf = ["pass"]
57 | self.builder.add(1, "pass")
58 | assert "pass; pass" == self.builder.to_string()
59 |
60 |
61 | class BlockBuilderAddNextLineTestCase(unittest.TestCase):
62 | """Test the ``BlockBuilder.add`` to add a new line."""
63 |
64 | def setUp(self) -> None:
65 | self.builder = BlockBuilder({}, indent=" ")
66 |
67 | def test_code_empty(self) -> None:
68 | self.builder.add(1, "pass")
69 | self.builder.add(2, "")
70 | assert " pass\n" == self.builder.to_string()
71 |
72 | def test_pad(self) -> None:
73 | self.builder.add(1, "pass")
74 | self.builder.add(3, "pass")
75 | assert " pass\n\n pass" == self.builder.to_string()
76 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/test_console.py:
--------------------------------------------------------------------------------
1 | """ Unit tests for ``wheezy.templates.console``.
2 | """
3 |
4 | import unittest
5 |
6 | from wheezy.template.console import main
7 |
8 |
9 | class ConsoleTestCase(unittest.TestCase):
10 | """Test the console ``main`` function."""
11 |
12 | def test_usage(self) -> None:
13 | assert 2 == main(["-h"])
14 | assert 2 == main(["-t @"])
15 | assert 2 == main(["-j \\"])
16 | assert 2 == main(["-x"])
17 |
18 | def test_context_file(self) -> None:
19 | assert 0 == main(
20 | ["demos/helloworld/hello.txt", "demos/helloworld/hello.json"]
21 | )
22 |
23 | def test_context_string(self) -> None:
24 | assert 0 == main(["demos/helloworld/hello.txt", '{"name": "World"}'])
25 |
26 | def test_master(self) -> None:
27 | assert 0 == main(["-s", "demos/master", "index.html"])
28 |
29 | def test_line_join(self) -> None:
30 | assert 0 == main(["-j", "\\", "-s", "demos/master", "index.html"])
31 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/test_engine.py:
--------------------------------------------------------------------------------
1 | import typing
2 | import unittest
3 |
4 | from wheezy.template.engine import Engine
5 | from wheezy.template.ext.core import CoreExtension
6 | from wheezy.template.loader import DictLoader
7 |
8 |
9 | class EngineTestCase(unittest.TestCase):
10 | """Test the ``Engine``."""
11 |
12 | def setUp(self) -> None:
13 | self.templates: typing.Dict[str, str] = {"a": ""}
14 | self.engine = Engine(
15 | loader=DictLoader(templates=self.templates),
16 | extensions=[CoreExtension()],
17 | )
18 |
19 | def test_template_not_found(self) -> None:
20 | """Raises IOError."""
21 | self.assertRaises(IOError, lambda: self.engine.get_template("x"))
22 |
23 | def test_import_not_found(self) -> None:
24 | """Raises IOError."""
25 | self.assertRaises(IOError, lambda: self.engine.import_name("x"))
26 |
27 | def test_remove_unknown_name(self) -> None:
28 | """Invalidate name that is not known to engine."""
29 | self.engine.remove("x")
30 |
31 | def test_remove_name(self) -> None:
32 | """Invalidate name that is known to engine."""
33 | # self.templates["a"] = ""
34 | self.engine.compile_import("a")
35 | self.engine.compile_template("a")
36 | self.engine.remove("a")
37 |
38 |
39 | class EngineSyntaxErrorTestCase(unittest.TestCase):
40 | """Test the ``Engine``."""
41 |
42 | def setUp(self) -> None:
43 | self.templates: typing.Dict[str, str] = {}
44 | self.engine = Engine(
45 | loader=DictLoader(templates=self.templates),
46 | extensions=[CoreExtension()],
47 | )
48 |
49 | def test_compile_template_error(self) -> None:
50 | """Raises SyntaxError."""
51 | self.templates[
52 | "x"
53 | ] = """
54 | @if :
55 | @end
56 | """
57 | self.assertRaises(
58 | SyntaxError, lambda: self.engine.compile_template("x")
59 | )
60 |
61 | def test_compile_import_error(self) -> None:
62 | """Raises SyntaxError."""
63 | self.templates[
64 | "m"
65 | ] = """
66 | @def x():
67 | @# ignore
68 | @if :
69 | @end
70 | @end
71 | """
72 | self.assertRaises(SyntaxError, lambda: self.engine.compile_import("m"))
73 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/test_lexer.py:
--------------------------------------------------------------------------------
1 | import re
2 | import typing
3 | import unittest
4 |
5 | from wheezy.template.lexer import Lexer, lexer_scan
6 | from wheezy.template.typing import Token
7 |
8 |
9 | class LexerTestCase(unittest.TestCase):
10 | """Test the ``Lexer``."""
11 |
12 | def test_tokenize(self) -> None:
13 | """Test with simple rules"""
14 |
15 | def word_token(m: typing.Match[str]) -> Token:
16 | return m.end(), "w", m.group()
17 |
18 | def blank_token(m: typing.Match[str]) -> Token:
19 | return m.end(), "b", m.group()
20 |
21 | def to_upper(s: str) -> str:
22 | return s.upper()
23 |
24 | def cleanup(tokens: typing.List[Token]) -> None:
25 | for i in range(len(tokens)):
26 | t = tokens[i]
27 | if t[i] == "b":
28 | tokens[i] = (t[0], "b", " ")
29 |
30 | class Extension(object):
31 | lexer_rules = {
32 | 100: (re.compile(r"\w+"), word_token),
33 | 200: (re.compile(r"\s+"), blank_token),
34 | }
35 | preprocessors = [to_upper]
36 | postprocessors = [cleanup]
37 |
38 | lexer = Lexer(**lexer_scan([Extension]))
39 | assert [
40 | (1, "w", "HELLO"),
41 | (1, "b", " "),
42 | (2, "w", "WORLD"),
43 | ] == lexer.tokenize("hello\n world")
44 |
45 | def test_trivial(self) -> None:
46 | """Empty rules and source"""
47 | lexer = Lexer([])
48 | assert [] == lexer.tokenize("")
49 |
50 | def test_raises_error(self) -> None:
51 | """If there is no match it raises AssertionError."""
52 | lexer = Lexer([])
53 | self.assertRaises(AssertionError, lambda: lexer.tokenize("test"))
54 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/test_parser.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from wheezy.template.parser import Parser
4 |
5 |
6 | class ParserTestCase(unittest.TestCase):
7 | """Test the ``Parser``."""
8 |
9 | def setUp(self) -> None:
10 | self.parser = Parser({})
11 | self.tokens = [
12 | (1, "a", "11"),
13 | (2, "b", "12"),
14 | (3, "c", "13"),
15 | (4, "b", "14"),
16 | (5, "c", "15"),
17 | ]
18 |
19 | def test_end_continue(self) -> None:
20 | """Ensure end nodes are inserted before continue tokens."""
21 | self.parser.continue_tokens = ["b"]
22 | nodes = list(self.parser.end_continue(self.tokens))
23 | assert 7 == len(nodes)
24 | assert (2, "end", "") == nodes[1]
25 | assert (2, "b", "12") == nodes[2]
26 | assert (4, "end", "") == nodes[4]
27 | assert (4, "b", "14") == nodes[5]
28 |
29 | def test_parse_unchanged(self) -> None:
30 | """If there is no parser tokens defined the result is unchanged
31 | input.
32 | """
33 | nodes = list(self.parser.parse(self.tokens))
34 | assert self.tokens == nodes
35 |
36 | def test_parse_with_rules(self) -> None:
37 | """Ensure the rules applied."""
38 | self.parser.rules["a"] = lambda value: value + "100"
39 | self.parser.rules["b"] = lambda value: value + "10"
40 | nodes = list(self.parser.parse(self.tokens))
41 | assert (1, "a", "11100") == nodes[0]
42 | assert (2, "b", "1210") == nodes[1]
43 | assert (4, "b", "1410") == nodes[3]
44 |
45 | def test_out_tokens(self) -> None:
46 | """Tokens from ``out_tokens`` are combined together into a single
47 | node.
48 | """
49 | self.parser.out_tokens = ["a", "b"]
50 | nodes = list(self.parser.parse(self.tokens))
51 | assert 4 == len(nodes)
52 | assert (1, "out", [(1, "a", "11"), (2, "b", "12")]) == nodes[0]
53 | assert (4, "out", [(4, "b", "14")]) == nodes[2]
54 |
55 | self.parser.out_tokens = ["b", "c"]
56 | nodes = list(self.parser.parse(self.tokens))
57 | assert 2 == len(nodes)
58 | assert (
59 | 2,
60 | "out",
61 | [(2, "b", "12"), (3, "c", "13"), (4, "b", "14"), (5, "c", "15")],
62 | ) == nodes[1]
63 |
64 | def test_compound(self) -> None:
65 | """"""
66 | self.parser.compound_tokens = ["b"]
67 | self.parser.end_tokens = ["c"]
68 | nodes = list(self.parser.parse(self.tokens))
69 | assert 3 == len(nodes)
70 | assert (2, "b", ("12", [(3, "c", "13")])) == nodes[1]
71 | assert (4, "b", ("14", [(5, "c", "15")])) == nodes[2]
72 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/test_preprocessor.py:
--------------------------------------------------------------------------------
1 | import typing
2 | import unittest
3 |
4 | from wheezy.template.engine import Engine
5 | from wheezy.template.ext.core import CoreExtension
6 | from wheezy.template.loader import DictLoader
7 | from wheezy.template.preprocessor import Preprocessor
8 | from wheezy.template.typing import Loader
9 |
10 |
11 | class PreprocessorTestCase(unittest.TestCase):
12 | """Test the ``Preprocessor``."""
13 |
14 | def setUp(self) -> None:
15 | def runtime_engine_factory(loader: Loader) -> Engine:
16 | engine = Engine(
17 | loader=loader,
18 | extensions=[
19 | CoreExtension(),
20 | ],
21 | )
22 | return engine
23 |
24 | self.templates: typing.Dict[str, str] = {}
25 | engine = Engine(
26 | loader=DictLoader(templates=self.templates),
27 | extensions=[
28 | CoreExtension("#", line_join=""),
29 | ],
30 | )
31 | self.engine = Preprocessor(
32 | runtime_engine_factory, engine, key_factory=lambda ctx: ""
33 | )
34 |
35 | def render(self, name: str, ctx: typing.Mapping[str, typing.Any]) -> str:
36 | template = self.engine.get_template(name)
37 | return template.render(ctx)
38 |
39 | def test_render(self) -> None:
40 | self.templates[
41 | "test.html"
42 | ] = """\
43 | #require(_)
44 | @require(username)
45 | #_('Welcome,') @username!"""
46 |
47 | assert "Welcome, John!" == self.render(
48 | "test.html", ctx={"_": lambda x: x, "username": "John"}
49 | )
50 |
51 | def test_extends(self) -> None:
52 | self.templates.update(
53 | {
54 | "master.html": """\
55 | #require(_)
56 | @def say_hi(name):
57 | #_('Hello,') @name!
58 | @end
59 | @say_hi('John')""",
60 | "tmpl.html": """\
61 | #require(_)
62 | @extends('master.html')
63 | @def say_hi(name):
64 | #_('Hi,') @name!
65 | @end
66 | """,
67 | }
68 | )
69 |
70 | assert " Hi, John!\n" == self.render(
71 | "tmpl.html",
72 | ctx={
73 | "_": lambda x: x,
74 | },
75 | )
76 |
77 | def test_remove(self) -> None:
78 | self.templates["test.html"] = "Hello"
79 | assert "Hello" == self.render("test.html", {})
80 | self.engine.remove("x")
81 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from wheezy.template.utils import find_all_balanced, find_balanced
4 |
5 |
6 | class FindAllBalancedTestCase(unittest.TestCase):
7 | """Test the ``find_all_balanced``."""
8 |
9 | def test_start_out(self) -> None:
10 | """The start index is out of range."""
11 | assert 10 == find_all_balanced("test", 10)
12 |
13 | def test_start_separator(self) -> None:
14 | """If text doesn't start with ``([`` return."""
15 | assert 0 == find_all_balanced("test([", 0)
16 | assert 3 == find_all_balanced("test([", 3)
17 |
18 | def test_not_balanced(self) -> None:
19 | """Separators are not balanced."""
20 | assert 4 == find_all_balanced("test(a, b", 4)
21 | assert 4 == find_all_balanced("test[a, b()", 4)
22 |
23 | def test_balanced(self) -> None:
24 | """Separators are balanced."""
25 | assert 10 == find_all_balanced("test(a, b)", 4)
26 | assert 13 == find_all_balanced("test(a, b)[0]", 4)
27 | assert 12 == find_all_balanced("test(a, b())", 4)
28 | assert 17 == find_all_balanced("test(a, b())[0]()", 4)
29 |
30 |
31 | class FindBalancedTestCase(unittest.TestCase):
32 | """Test the ``find_balanced``."""
33 |
34 | def test_start_out(self) -> None:
35 | """The start index is out of range."""
36 | assert 10 == find_balanced("test", 10)
37 |
38 | def test_start_separator(self) -> None:
39 | """If text doesn't start with ``start_sep`` return."""
40 | assert 0 == find_balanced("test(", 0)
41 | assert 3 == find_balanced("test(", 3)
42 |
43 | def test_not_balanced(self) -> None:
44 | """Separators are not balanced."""
45 | assert 4 == find_balanced("test(a, b", 4)
46 | assert 4 == find_balanced("test(a, b()", 4)
47 |
48 | def test_balanced(self) -> None:
49 | """Separators are balanced."""
50 | assert 10 == find_balanced("test(a, b)", 4)
51 | assert 12 == find_balanced("test(a, b())", 4)
52 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/typing.py:
--------------------------------------------------------------------------------
1 | import typing
2 | from abc import abstractmethod
3 |
4 | Token = typing.Tuple[int, str, str]
5 |
6 |
7 | class Builder:
8 | lineno: int
9 |
10 | @abstractmethod
11 | def start_block(self) -> None:
12 | ... # pragma: nocover
13 |
14 | @abstractmethod
15 | def end_block(self) -> None:
16 | ... # pragma: nocover
17 |
18 | @abstractmethod
19 | def add(self, lineno: int, code: str) -> None:
20 | ... # pragma: nocover
21 |
22 | @abstractmethod
23 | def build_block(self, nodes: typing.Iterable[Token]) -> None:
24 | ... # pragma: nocover
25 |
26 | @abstractmethod
27 | def build_token(
28 | self,
29 | lineno: int,
30 | token: str,
31 | value: typing.Union[str, typing.Iterable[Token]],
32 | ) -> None:
33 | ... # pragma: nocover
34 |
35 |
36 | Tokenizer = typing.Callable[[typing.Match], Token]
37 | LexerRule = typing.Tuple[typing.Pattern, Tokenizer]
38 | PreProcessorRule = typing.Callable[[str], str]
39 | PostProcessorRule = typing.Callable[[typing.List[Token]], str]
40 | BuilderRule = typing.Callable[
41 | [
42 | Builder,
43 | int,
44 | str,
45 | typing.Union[str, typing.List[str], typing.Iterable[Token]],
46 | ],
47 | bool,
48 | ]
49 | ParserRule = typing.Callable[[str], typing.Union[str, typing.List[str]]]
50 |
51 |
52 | class ParserConfig:
53 | end_tokens: typing.List[str]
54 | continue_tokens: typing.List[str]
55 | compound_tokens: typing.List[str]
56 | out_tokens: typing.List[str]
57 |
58 |
59 | RenderTemplate = typing.Callable[
60 | [
61 | typing.Mapping[str, typing.Any],
62 | typing.Mapping[str, typing.Any],
63 | typing.Mapping[str, typing.Any],
64 | ],
65 | str,
66 | ]
67 |
68 |
69 | class SupportsRender:
70 | @abstractmethod
71 | def render(self, ctx: typing.Mapping[str, typing.Any]) -> str:
72 | ... # pragma: nocover
73 |
74 |
75 | TemplateClass = typing.Callable[[str, RenderTemplate], SupportsRender]
76 |
77 |
78 | class Loader:
79 | @abstractmethod
80 | def list_names(self) -> typing.Tuple[str, ...]:
81 | ... # pragma: nocover
82 |
83 | @abstractmethod
84 | def load(self, name: str) -> typing.Optional[str]:
85 | ... # pragma: nocover
86 |
--------------------------------------------------------------------------------
/python/external/wheezy/template/utils.py:
--------------------------------------------------------------------------------
1 | def find_all_balanced(text: str, start: int = 0) -> int:
2 | """Finds balanced ``([`` with ``])`` assuming
3 | that ``start`` is pointing to ``(`` or ``[`` in ``text``.
4 | """
5 | if start >= len(text) or text[start] not in "([":
6 | return start
7 | while 1:
8 | pos = find_balanced(text, start)
9 | pos = find_balanced(text, pos, "[", "]")
10 | if pos != start:
11 | start = pos
12 | else:
13 | return pos
14 |
15 |
16 | def find_balanced(
17 | text: str, start: int = 0, start_sep: str = "(", end_sep: str = ")"
18 | ) -> int:
19 | """Finds balanced ``start_sep`` with ``end_sep`` assuming
20 | that ``start`` is pointing to ``start_sep`` in ``text``.
21 | """
22 | if start >= len(text) or start_sep != text[start]:
23 | return start
24 | balanced = 1
25 | pos = start + 1
26 | while pos < len(text):
27 | token = text[pos]
28 | pos += 1
29 | if token == end_sep:
30 | if balanced == 1:
31 | return pos
32 | balanced -= 1
33 | elif token == start_sep:
34 | balanced += 1
35 | return start
36 |
37 |
38 | def print_source(source: str, lineno: int = 1) -> None: # pragma: nocover
39 | lines = []
40 | for line in source.split("\n"):
41 | lines.append("%02d " % lineno + line)
42 | lineno += line.count("\n") + 1
43 | print("\n".join(lines))
44 |
--------------------------------------------------------------------------------
/python/minify/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ExpressLRS/Backpack/f421d8ed44eda4208e7a0a665717e868c8f4a035/python/minify/__init__.py
--------------------------------------------------------------------------------
/python/osd_test.py:
--------------------------------------------------------------------------------
1 | #
2 | # To use this program to send OSD messages to HDZero googles for example.
3 | # 1. Ensure the VRX backpack (HDZero goggles backpack) is flashed with the latest version of it's backpack.
4 | # 2. Flash an ESP32 device using "DEBUG_ESP32_TX_Backpack_via_UART" or similar TX backpack.
5 | # 3. Run this python program and send commands.
6 | # Depending on the font used for the OSD only UPPERCASE letters may be displayed as letters.
7 | # This is because the other character positions are used to display other symbols on the OSD.
8 |
9 | import serial
10 | import argparse
11 | import serials_find
12 | import sys
13 | import threading
14 |
15 | def crc8_dvb_s2(crc, a):
16 | crc = crc ^ a
17 | for ii in range(8):
18 | if crc & 0x80:
19 | crc = (crc << 1) ^ 0xD5
20 | else:
21 | crc = crc << 1
22 | return crc & 0xFF
23 |
24 | def send_msp(s, body):
25 | crc = 0
26 | for x in body:
27 | crc = crc8_dvb_s2(crc, x)
28 | msp = [ord('$'),ord('X'),ord('<')]
29 | msp = msp + body
30 | msp.append(crc)
31 | s.write(msp)
32 | print('Sending ' + str(msp))
33 |
34 | def send_clear(s):
35 | msp = [0,0xb6,0x00,1,0,0x02]
36 | send_msp(s, msp)
37 |
38 | def send_display(s):
39 | msp = [0,0xb6,0x00,1,0,0x04]
40 | send_msp(s, msp)
41 |
42 | def send_msg(s, row, col, str):
43 | l = 4+len(str)
44 | msp = [0,0xb6,0x00,l%256,int(l/256),0x03,row,col,0]
45 | for x in [*str]:
46 | msp.append(ord(x))
47 | send_msp(s, msp)
48 |
49 | def thread_function(s: serial.Serial):
50 | while True:
51 | b = s.readall()
52 | if len(b): print(b)
53 |
54 | def short_help():
55 | print("Command should be one of:")
56 | print("C = clear the OSD canvas")
57 | print("D = display the OSD canvas")
58 | print("H = Print the full help message")
59 | print(" = send message to OSD canvas")
60 |
61 | def help():
62 | print()
63 | print("Depending on the OSD font only UPPERCASE letters ay display as actual letters,")
64 | print("this is because the other character positions are used to display other symbols on the OSD.")
65 | short_help()
66 | print()
67 | print("Example:")
68 | print("C")
69 | print("10 10 ELRS ROCKS")
70 | print("D")
71 |
72 | if __name__ == '__main__':
73 | parser = argparse.ArgumentParser(
74 | description="Send OSD messages to a VRX backpack via a TX backpack connected to a Serial port")
75 | parser.add_argument("-b", "--baud", type=int, default=460800,
76 | help="Baud rate for passthrough communication")
77 | parser.add_argument("-p", "--port", type=str,
78 | help="Override serial port autodetection and use PORT")
79 | args = parser.parse_args()
80 |
81 | if (args.port == None):
82 | args.port = serials_find.get_serial_port()
83 |
84 | s = serial.Serial(port=args.port, baudrate=args.baud, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=0, rtscts=0)
85 | threading.Thread(target=thread_function, args=(s,)).start()
86 |
87 | help()
88 | for line in sys.stdin:
89 | if line.upper().startswith('C'):
90 | send_clear(s)
91 | elif line.upper().startswith('D'):
92 | send_display(s)
93 | elif line.upper().startswith('H'):
94 | help()
95 | else:
96 | import re
97 | parts=re.search('(\d+) (\d+) (.+)', line)
98 | if parts and len(parts.groups()) == 3:
99 | row = int(parts.group(1))
100 | col = int(parts.group(2))
101 | message = parts.group(3)
102 | send_msg(s, row, col, message)
103 | else:
104 | short_help()
105 |
--------------------------------------------------------------------------------
/python/serials_find.py:
--------------------------------------------------------------------------------
1 | import serial
2 | import sys, glob
3 |
4 |
5 | def serial_ports():
6 | """ Lists serial port names
7 |
8 | :raises Exception:
9 | On unsupported or unknown platforms
10 | :returns:
11 | A list of the serial ports available on the system
12 | """
13 | result = []
14 | ports = []
15 |
16 | try:
17 | from serial.tools.list_ports import comports
18 | if comports:
19 | print(" ** Searching flight controllers **")
20 | __ports = list(comports())
21 | for port in __ports:
22 | if (port.manufacturer and port.manufacturer in ['FTDI', 'Betaflight', ]) or \
23 | (port.product and "STM32" in port.product) or (port.vid and port.vid == 0x0483):
24 | print(" > FC found from '%s'" % port.device)
25 | ports.append(port.device)
26 | except ImportError:
27 | pass
28 |
29 | if not ports:
30 | print(" ** No FC found, find all ports **")
31 |
32 | platform = sys.platform.lower()
33 | if platform.startswith('win'):
34 | ports = ['COM%s' % (i + 1) for i in range(256)]
35 | elif platform.startswith('linux') or platform.startswith('cygwin'):
36 | # this excludes your current terminal "/dev/tty"
37 | #ports = glob.glob('/dev/tty[A-Za-z]*')
38 | # List all ttyACM* and ttyUSB* ports only
39 | ports = glob.glob('/dev/ttyACM*')
40 | ports.extend(glob.glob('/dev/ttyUSB*'))
41 | elif platform.startswith('darwin'):
42 | ports = glob.glob('/dev/tty.usbmodem*')
43 | ports.extend(glob.glob('/dev/tty.SLAB*'))
44 | ports.extend(glob.glob('/dev/tty.usbserial*'))
45 | else:
46 | raise Exception('Unsupported platform')
47 |
48 | for port in ports:
49 | try:
50 | s = serial.Serial(port)
51 | s.close()
52 | result.append(port)
53 | except (OSError, serial.SerialException) as error:
54 | if "permission denied" in str(error).lower():
55 | raise Exception("You don't have persmission to use serial port!")
56 | pass
57 | result.reverse()
58 | return result
59 |
60 | def get_serial_port(debug=True):
61 | result = serial_ports()
62 | if debug:
63 | print()
64 | print("Detected the following serial ports on this system:")
65 | for port in result:
66 | print(" %s" % port)
67 | print()
68 |
69 | if len(result) == 0:
70 | raise Exception('No valid serial port detected or port already open')
71 |
72 | return result[0]
73 |
74 | if __name__ == '__main__':
75 | results = get_serial_port(True)
76 | print("Found: %s" % (results, ))
77 |
--------------------------------------------------------------------------------
/python/setup.py:
--------------------------------------------------------------------------------
1 | import setuptools
2 | setuptools.setup(
3 | name="flasher",
4 | version="3.3.0",
5 | author="ExpressLRS Team",
6 | author_email="",
7 | description="ExpressLRS Backpack Binary Installer",
8 | long_description='ExpressLRS binary configurator and flasher tool all-in-one',
9 | long_description_content_type="text/markdown",
10 | url="https://github.com/ExpressLRS/Backpack",
11 | packages=['.'] + setuptools.find_packages(),
12 | include_package_data=True,
13 | entry_points={
14 | "console_scripts": ["flash=binary_configurator:main"],
15 | },
16 | install_requires=['pyserial'],
17 | classifiers=[
18 | "Programming Language :: Python :: 3",
19 | "Operating System :: OS Independent",
20 | ],
21 | python_requires='>=3.6',
22 | )
23 |
--------------------------------------------------------------------------------
/python/upload_via_esp8266_backpack.py:
--------------------------------------------------------------------------------
1 | import subprocess, os
2 |
3 | def do_upload(elrs_bin_target, upload_addr, env):
4 | bootloader_target = None
5 | app_start = 0 # eka bootloader offset
6 |
7 | cmd = ["curl", "--max-time", "60",
8 | "--retry", "2", "--retry-delay", "1",
9 | "-F", "data=@%s" % (elrs_bin_target,)]
10 |
11 | if bootloader_target is not None:
12 | cmd_bootloader = ["curl", "--max-time", "60",
13 | "--retry", "2", "--retry-delay", "1",
14 | "-F", "data=@%s" % (bootloader_target,), "-F", "flash_address=0x0000"]
15 |
16 | upload_port = env.get('UPLOAD_PORT', None)
17 | if upload_port is not None:
18 | upload_addr = [upload_port]
19 |
20 | for addr in upload_addr:
21 | addr = "http://%s/%s" % (addr, ['update', 'upload'])
22 | print(" ** UPLOADING TO: %s" % addr)
23 | try:
24 | if bootloader_target is not None:
25 | print("** Flashing Bootloader...")
26 | print(cmd_bootloader,cmd)
27 | subprocess.check_call(cmd_bootloader + [addr])
28 | print("** Bootloader Flashed!")
29 | print()
30 | subprocess.check_call(cmd + [addr])
31 | print()
32 | print("** UPLOAD SUCCESS. Flashing in progress.")
33 | print("** Please wait for LED to resume blinking before disconnecting power")
34 | return
35 | except subprocess.CalledProcessError:
36 | print("FAILED!")
37 |
38 | raise Exception("WIFI upload FAILED!")
39 |
40 | def on_upload(source, target, env):
41 | upload_addr = ['elrs_txbp', 'elrs_txbp.local']
42 |
43 | firmware_path = str(source[0])
44 | bin_path = os.path.dirname(firmware_path)
45 | elrs_bin_target = os.path.join(bin_path, 'firmware.elrs')
46 | if not os.path.exists(elrs_bin_target):
47 | elrs_bin_target = os.path.join(bin_path, 'firmware.bin.gz')
48 | if not os.path.exists(elrs_bin_target):
49 | elrs_bin_target = os.path.join(bin_path, 'firmware.bin')
50 | if not os.path.exists(elrs_bin_target):
51 | raise Exception("No valid binary found!")
52 | do_upload(elrs_bin_target, upload_addr, env)
53 |
--------------------------------------------------------------------------------
/src/EspFlashStream.cpp:
--------------------------------------------------------------------------------
1 | #include "EspFlashStream.h"
2 |
3 | EspFlashStream::EspFlashStream()
4 | {
5 | setBaseAddress(0);
6 | }
7 |
8 | void EspFlashStream::fillBuffer()
9 | {
10 | // Could also use spi_flash_read() here, but the return values differ between ESP (SPI_FLASH_RESULT_OK) and ESP32 (ESP_OK)
11 | if (ESP.flashRead(_flashBase + _flashOffset, (uint32_t *)_buffer, sizeof(_buffer)))
12 | {
13 | _bufferPos = 0;
14 | }
15 | else
16 | {
17 | // _bufferPos > sizeof() indicates error
18 | _bufferPos = sizeof(_buffer) + 1;
19 | }
20 | }
21 |
22 | void EspFlashStream::setBaseAddress(size_t base)
23 | {
24 | _flashBase = base;
25 | _flashOffset = 0 - sizeof(_buffer); // underflow intentionally so offset+pos = 0
26 | _bufferPos = sizeof(_buffer);
27 | }
28 |
29 | void EspFlashStream::setPosition(size_t offset)
30 | {
31 | // align(4), rounding down
32 | _flashOffset = (offset >> 2) << 2;
33 | // capture the new bufferPos before fill advances it
34 | uint8_t newBufferPos = offset - _flashOffset;
35 |
36 | fillBuffer();
37 | _bufferPos = newBufferPos;
38 | }
39 |
40 | int EspFlashStream::read()
41 | {
42 | int retVal = peek();
43 | ++_bufferPos;
44 | return retVal;
45 | }
46 |
47 | int EspFlashStream::peek()
48 | {
49 | if (_bufferPos == sizeof(_buffer))
50 | {
51 | _flashOffset += sizeof(_buffer);
52 | fillBuffer();
53 | }
54 | return _buffer[_bufferPos];
55 | }
56 |
--------------------------------------------------------------------------------
/src/EspFlashStream.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | class EspFlashStream : public Stream
6 | {
7 | public:
8 | EspFlashStream();
9 | // Set the starting address to use with seek()s
10 | void setBaseAddress(size_t base);
11 | size_t getPosition() const { return _flashOffset + _bufferPos; }
12 | void setPosition(size_t offset);
13 |
14 | // Stream class overrides
15 | virtual size_t write(uint8_t) { return 0; }
16 | virtual int available() { return (_bufferPos <= sizeof(_buffer)) ? 1 : 0; }
17 | virtual int read();
18 | virtual int peek();
19 |
20 | private:
21 | size_t _flashBase;
22 | size_t _flashOffset;
23 | uint8_t _buffer[4];
24 | uint8_t _bufferPos;
25 |
26 | void fillBuffer();
27 | };
--------------------------------------------------------------------------------
/src/devwifi_proxy_aat.cpp:
--------------------------------------------------------------------------------
1 | #if defined(AAT_BACKPACK)
2 |
3 | #include "devwifi_proxies.h"
4 | #include "module_aat.h"
5 |
6 | void WebAatAppendConfig(ArduinoJson::JsonDocument &json)
7 | {
8 | auto aat = json["config"].createNestedObject("aat");
9 | aat["satmin"] = config.GetAatSatelliteHomeMin();
10 | aat["azim_sff"] = config.GetAatAzimuthServoFastFlip();
11 | aat["servosmoo"] = config.GetAatServoSmooth();
12 | aat["servomode"] = config.GetAatServoMode();
13 | aat["project"] = config.GetAatProject();
14 | aat["azim_center"] = config.GetAatCenterDir();
15 | aat["azim_min"] = config.GetAatServoLow(0);
16 | aat["azim_max"] = config.GetAatServoHigh(0);
17 | aat["elev_min"] = config.GetAatServoLow(1);
18 | aat["elev_max"] = config.GetAatServoHigh(1);
19 |
20 | // VBat
21 | auto vbat = json["config"].createNestedObject("vbat");
22 | vbat["offset"] = config.GetVbatOffset();
23 | vbat["scale"] = config.GetVbatScale();
24 | vbat["vbat"] = vrxModule.getVbat();
25 | }
26 |
27 | void WebAatConfig(AsyncWebServerRequest *request)
28 | {
29 | // Servos
30 | if (request->hasArg("servosmoo"))
31 | config.SetAatServoSmooth(request->arg("servosmoo").toInt());
32 | if (request->hasArg("azim_sff"))
33 | config.SetAatAzimuthServoFastFlip(request->arg("azim_sff").toInt());
34 | else
35 | config.SetAatAzimuthServoFastFlip(0);
36 | if (request->hasArg("servomode"))
37 | config.SetAatServoMode(request->arg("servomode").toInt());
38 | if (request->hasArg("azim_center"))
39 | config.SetAatCenterDir(request->arg("azim_center").toInt());
40 | if (request->hasArg("azim_min"))
41 | config.SetAatServoLow(0, request->arg("azim_min").toInt());
42 | if (request->hasArg("azim_max"))
43 | config.SetAatServoHigh(0, request->arg("azim_max").toInt());
44 | if (request->hasArg("elev_min"))
45 | config.SetAatServoLow(1, request->arg("elev_min").toInt());
46 | if (request->hasArg("elev_max"))
47 | config.SetAatServoHigh(1, request->arg("elev_max").toInt());
48 | // VBat
49 | if (request->hasArg("vbat_offset"))
50 | config.SetVbatOffset(request->arg("vbat_offset").toInt());
51 | if (request->hasArg("vbat_scale"))
52 | config.SetVbatScale(request->arg("vbat_scale").toInt());
53 | // Servo Target Override (must be set after azim_center because bearing relies on center)
54 | if (request->hasArg("bear"))
55 | vrxModule.overrideTargetBearing(request->arg("bear").toInt());
56 | if (request->hasArg("elev"))
57 | vrxModule.overrideTargetElev(request->arg("elev").toInt());
58 | // Satellite Config
59 | if (request->hasArg("satmin"))
60 | config.SetAatSatelliteHomeMin(request->arg("satmin").toInt());
61 |
62 | const char *response;
63 | if (request->arg("commit").toInt() == 1)
64 | {
65 | response = "Saved";
66 | config.Commit();
67 | }
68 | else
69 | response = "Modified";
70 |
71 | request->send(200, "text/plain", response);
72 | }
73 |
74 | void WebAatInit(AsyncWebServer &server)
75 | {
76 | server.on("/aatconfig", WebAatConfig);
77 | }
78 |
79 | #endif /* defined(AAT_BACKPACK) */
80 |
81 |
--------------------------------------------------------------------------------
/src/hdzero.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "hdzero.h"
3 | #include "msptypes.h"
4 | #include "logging.h"
5 | #include "time.h"
6 |
7 | void
8 | HDZero::Init()
9 | {
10 | ModuleBase::Init();
11 | }
12 |
13 | void
14 | HDZero::SendIndexCmd(uint8_t index)
15 | {
16 | uint8_t retries = 3;
17 | while (GetChannelIndex() != index && retries > 0)
18 | {
19 | SetChannelIndex(index);
20 | retries--;
21 | }
22 | }
23 |
24 | uint8_t
25 | HDZero::GetChannelIndex()
26 | {
27 | MSP msp;
28 | mspPacket_t packet;
29 | packet.reset();
30 | packet.makeCommand();
31 | packet.function = MSP_ELRS_BACKPACK_GET_CHANNEL_INDEX;
32 |
33 | // Send request, then wait for a response back from the VRX
34 | bool receivedResponse = msp.awaitPacket(&packet, m_port, VRX_RESPONSE_TIMEOUT);
35 |
36 | if (receivedResponse)
37 | {
38 | mspPacket_t *packetResponse = msp.getReceivedPacket();
39 | msp.markPacketReceived();
40 | return packetResponse->readByte();
41 | }
42 |
43 | DBGLN("HDZero module: Exceeded timeout while waiting for channel index response");
44 | return CHANNEL_INDEX_UNKNOWN;
45 | }
46 |
47 | void
48 | HDZero::SetChannelIndex(uint8_t index)
49 | {
50 | MSP msp;
51 | mspPacket_t packet;
52 | packet.reset();
53 | packet.makeCommand();
54 | packet.function = MSP_ELRS_BACKPACK_SET_CHANNEL_INDEX;
55 | packet.addByte(index); // payload
56 |
57 | msp.sendPacket(&packet, m_port);
58 | }
59 |
60 | uint8_t
61 | HDZero::GetRecordingState()
62 | {
63 | MSP msp;
64 | mspPacket_t packet;
65 | packet.reset();
66 | packet.makeCommand();
67 | packet.function = MSP_ELRS_BACKPACK_GET_RECORDING_STATE;
68 |
69 | // Send request, then wait for a response back from the VRX
70 | bool receivedResponse = msp.awaitPacket(&packet, m_port, VRX_RESPONSE_TIMEOUT);
71 |
72 | if (receivedResponse)
73 | {
74 | mspPacket_t *packetResponse = msp.getReceivedPacket();
75 | msp.markPacketReceived();
76 | return packetResponse->readByte() ? VRX_DVR_RECORDING_ACTIVE : VRX_DVR_RECORDING_INACTIVE;
77 | }
78 |
79 | DBGLN("HDZero module: Exceeded timeout while waiting for recording state response");
80 | return VRX_DVR_RECORDING_UNKNOWN;
81 | }
82 |
83 | void
84 | HDZero::SetRecordingState(uint8_t recordingState, uint16_t delay)
85 | {
86 | DBGLN("SetRecordingState = %d delay = %d", recordingState, delay);
87 |
88 | MSP msp;
89 | mspPacket_t packet;
90 | packet.reset();
91 | packet.makeCommand();
92 | packet.function = MSP_ELRS_BACKPACK_SET_RECORDING_STATE;
93 | packet.addByte(recordingState);
94 | packet.addByte(delay & 0xFF); // delay byte 1
95 | packet.addByte(delay >> 8); // delay byte 2
96 |
97 | msp.sendPacket(&packet, m_port);
98 | }
99 |
100 | void
101 | HDZero::SendHeadTrackingEnableCmd(bool enable)
102 | {
103 | MSP msp;
104 | mspPacket_t packet;
105 | packet.reset();
106 | packet.makeCommand();
107 | packet.function = MSP_ELRS_BACKPACK_SET_HEAD_TRACKING;
108 | packet.addByte(enable);
109 |
110 | msp.sendPacket(&packet, m_port);
111 | }
112 |
113 | void
114 | HDZero::SetOSD(mspPacket_t *packet)
115 | {
116 | MSP msp;
117 | msp.sendPacket(packet, m_port);
118 | }
119 |
120 | void
121 | HDZero::SetRTC()
122 | {
123 | MSP msp;
124 | mspPacket_t packet;
125 | tm timeData;
126 | if(!getLocalTime(&timeData)) {
127 | DBGLN("Could not obtain time data.");
128 | return;
129 | }
130 | packet.reset();
131 | packet.makeCommand();
132 | packet.function = MSP_ELRS_BACKPACK_SET_RTC;
133 | packet.addByte(timeData.tm_year);
134 | packet.addByte(timeData.tm_mon);
135 | packet.addByte(timeData.tm_mday);
136 | packet.addByte(timeData.tm_hour);
137 | packet.addByte(timeData.tm_min);
138 | packet.addByte(timeData.tm_sec);
139 |
140 | msp.sendPacket(&packet, m_port);
141 | }
142 |
--------------------------------------------------------------------------------
/src/hdzero.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include
5 |
6 | #define VRX_BOOT_DELAY 7000
7 |
8 | #define VRX_RESPONSE_TIMEOUT 500
9 | #define VRX_UART_BAUD 115200 // hdzero uses 115k baud between the ESP8285 and the STM32
10 |
11 | #define CHANNEL_INDEX_UNKNOWN 255
12 | #define VRX_DVR_RECORDING_ACTIVE 1
13 | #define VRX_DVR_RECORDING_INACTIVE 0
14 | #define VRX_DVR_RECORDING_UNKNOWN 255
15 |
16 | class HDZero : public MSPModuleBase
17 | {
18 | public:
19 | explicit HDZero(Stream *port) : MSPModuleBase(port) {};
20 | void Init();
21 | void SendIndexCmd(uint8_t index);
22 | uint8_t GetChannelIndex();
23 | void SetChannelIndex(uint8_t index);
24 | uint8_t GetRecordingState();
25 | void SetRecordingState(uint8_t recordingState, uint16_t delay);
26 | void SendHeadTrackingEnableCmd(bool enable);
27 | void SetOSD(mspPacket_t *packet);
28 | void SetRTC();
29 | };
30 |
--------------------------------------------------------------------------------
/src/mfd_crossbow.cpp:
--------------------------------------------------------------------------------
1 | #include "mfd_crossbow.h"
2 | #include "common/mavlink.h"
3 |
4 | MFDCrossbow::MFDCrossbow(HardwareSerial *port) :
5 | m_port(port),
6 | gpsLastSent(0),
7 | gpsLastUpdated(0),
8 | heading(0),
9 | lat(0.0),
10 | lon(0.0),
11 | alt(0.0),
12 | groundspeed(0.0),
13 | gps_sats(0),
14 | gps_alt(0),
15 | gps_hdop(100),
16 | fixType(3)
17 | {
18 | }
19 |
20 | void
21 | MFDCrossbow::SendHeartbeat()
22 | {
23 | // Initialize the required buffers
24 | mavlink_message_t msg;
25 | uint8_t buf[MAVLINK_MAX_PACKET_LEN];
26 |
27 | // Pack the message
28 | mavlink_msg_heartbeat_pack(MAVLINK_SYSTEM_ID, MAVLINK_COMPONENT_ID, &msg, MAVLINK_SYSTEM_TYPE, MAVLINK_AUTOPILOT_TYPE, MAVLINK_SYSTEM_MODE, MAVLINK_CUSTOM_MODE, MAVLINK_SYSTEM_STATE);
29 |
30 | // Copy the message to the send buffer
31 | uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
32 |
33 | // Send the message
34 | m_port->write(buf, len);
35 | }
36 |
37 | void
38 | MFDCrossbow::SendGpsRawInt()
39 | {
40 | // Initialize the required buffers
41 | mavlink_message_t msg;
42 | uint8_t buf[MAVLINK_MAX_PACKET_LEN];
43 |
44 | const uint16_t eph = UINT16_MAX;
45 | const uint16_t epv = UINT16_MAX;
46 | const uint16_t cog = UINT16_MAX;
47 | const uint32_t alt_ellipsoid = 0;
48 | const uint32_t h_acc = 0;
49 | const uint32_t v_acc = 0;
50 | const uint32_t vel_acc = 0;
51 | const uint32_t hdg_acc = 0;
52 |
53 | // Pack the message
54 | mavlink_msg_gps_raw_int_pack(MAVLINK_SYSTEM_ID, MAVLINK_COMPONENT_ID, &msg, MAVLINK_UPTIME, fixType, lat, lon, alt, eph, epv, groundspeed, cog, gps_sats, alt_ellipsoid, h_acc, v_acc, vel_acc, hdg_acc, heading);
55 |
56 | // Copy the message to the send buffer
57 | uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
58 |
59 | // Send the message
60 | m_port->write(buf, len);
61 | }
62 |
63 | void
64 | MFDCrossbow::SendGlobalPositionInt()
65 | {
66 | const int16_t velx = 0;
67 | const int16_t vely = 0;
68 | const int16_t velz = 0;
69 |
70 | // Initialize the required buffers
71 | mavlink_message_t msg;
72 | uint8_t buf[MAVLINK_MAX_PACKET_LEN];
73 |
74 | // Pack the message
75 | mavlink_msg_global_position_int_pack(MAVLINK_SYSTEM_ID, MAVLINK_COMPONENT_ID, &msg, MAVLINK_UPTIME, lat, lon, gps_alt, alt, velx, vely, velz, heading);
76 |
77 | // Copy the message to the send buffer
78 | uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
79 |
80 | // Send the message
81 | m_port->write(buf, len);
82 | }
83 |
84 | void
85 | MFDCrossbow::SendGpsTelemetry(crsf_packet_gps_t *packet)
86 | {
87 | int32_t rawLat = be32toh(packet->p.lat); // Convert to host byte order
88 | int32_t rawLon = be32toh(packet->p.lon); // Convert to host byte order
89 | lat = static_cast(rawLat);
90 | lon = static_cast(rawLon);
91 |
92 | // Convert from CRSF scales to mavlink scales
93 | groundspeed = be16toh(packet->p.speed) / 36.0 * 100.0;
94 | heading = be16toh(packet->p.heading);
95 | alt = (be16toh(packet->p.altitude) - 1000) * 1000.0;
96 | gps_alt = alt;
97 | gps_sats = packet->p.satcnt;
98 |
99 | // Send heartbeat and GPS mavlink messages to the tracker
100 | SendHeartbeat();
101 | SendGpsRawInt();
102 | SendGlobalPositionInt();
103 |
104 | // Log the last time we received new GPS coords
105 | gpsLastUpdated = millis();
106 | }
107 |
108 | void
109 | MFDCrossbow::Loop(uint32_t now)
110 | {
111 | ModuleBase::Loop(now);
112 |
113 | // If the GPS data is <= 10 seconds old, keep spamming it out at 10hz
114 | bool gpsIsValid = (now < gpsLastUpdated + 10000) && gps_sats > 0;
115 |
116 | if (now > gpsLastSent + 100 && gpsIsValid)
117 | {
118 | SendHeartbeat();
119 | SendGpsRawInt();
120 | SendGlobalPositionInt();
121 |
122 | gpsLastSent = now;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/mfd_crossbow.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include "msptypes.h"
5 |
6 | #define VRX_UART_BAUD 115200
7 |
8 | #define MAVLINK_SYSTEM_ID 1
9 | #define MAVLINK_COMPONENT_ID 1
10 | #define MAVLINK_SYSTEM_TYPE 1
11 | #define MAVLINK_AUTOPILOT_TYPE 3
12 | #define MAVLINK_SYSTEM_MODE 64
13 | #define MAVLINK_CUSTOM_MODE 0
14 | #define MAVLINK_SYSTEM_STATE 4
15 | #define MAVLINK_UPTIME 0
16 |
17 | class MFDCrossbow : public ModuleBase
18 | {
19 | public:
20 | MFDCrossbow(HardwareSerial *port);
21 | void SendGpsTelemetry(crsf_packet_gps_t *packet);
22 | void Loop(uint32_t now);
23 |
24 | private:
25 | void SendHeartbeat();
26 | void SendGpsRawInt();
27 | void SendGlobalPositionInt();
28 |
29 | HardwareSerial *m_port;
30 | uint32_t gpsLastSent;
31 | uint32_t gpsLastUpdated;
32 |
33 | int16_t heading; // Geographical heading angle in degrees
34 | float lat; // GPS latitude in degrees (example: 47.123456)
35 | float lon; // GPS longitude in degrees
36 | float alt; // Relative flight altitude in m
37 | float groundspeed; // Groundspeed in m/s
38 | int16_t gps_sats; // Number of visible GPS satellites
39 | int32_t gps_alt; // GPS altitude (Altitude above MSL)
40 | float gps_hdop; // GPS HDOP
41 | uint8_t fixType; // GPS fix type. 0-1: no fix, 2: 2D fix, 3: 3D fix
42 | };
43 |
--------------------------------------------------------------------------------
/src/module_aat.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #if defined(AAT_BACKPACK)
4 | #include "config.h"
5 | #include "module_crsf.h"
6 | #include "crsf_protocol.h"
7 | #include "devWIFI.h"
8 |
9 | #if defined(PIN_SERVO_AZIM) || defined(PIN_SERVO_ELEV)
10 | #include
11 | #endif
12 |
13 | #if defined(PIN_OLED_SDA)
14 | #include
15 | #include
16 | #include
17 |
18 | #define SCREEN_WIDTH 128
19 | #define SCREEN_HEIGHT 64
20 | #define SCREEN_ADDRESS 0x3C
21 | #endif
22 |
23 | class VbatSampler
24 | {
25 | public:
26 | VbatSampler();
27 |
28 | void update(uint32_t now);
29 | // Reported in V * 10
30 | uint32_t value() const { return _value; }
31 | private:
32 | const uint32_t VBAT_UPDATE_INTERVAL = 100U;
33 | const uint32_t VBAT_UPDATE_COUNT = 5U;
34 |
35 | uint32_t _lastUpdate;
36 | uint32_t _value;
37 | uint32_t _sum; // in progress sum/count
38 | uint32_t _cnt;
39 | };
40 |
41 | class AatModule : public CrsfModuleBase
42 | {
43 | public:
44 | AatModule() = delete;
45 | AatModule(Stream &port);
46 |
47 | void Init();
48 | void Loop(uint32_t now);
49 |
50 | void SendGpsTelemetry(crsf_packet_gps_t *packet);
51 | bool isHomeSet() const { return _home.lat != 0 || _home.lon != 0 || _isOverrideMode; }
52 | bool isGpsActive() const { return _gpsLast.lastUpdateMs != 0; };
53 |
54 | void overrideTargetBearing(int32_t bearing);
55 | void overrideTargetElev(int32_t elev);
56 | uint32_t getVbat();
57 | protected:
58 | void overrideTargetCommon(int32_t azimuth, int32_t elevation);
59 | virtual void onCrsfPacketIn(const crsf_header_t *pkt);
60 | private:
61 | enum ServoIndex { IDX_AZIM, IDX_ELEV, IDX_COUNT };
62 | enum ServoMode { TwoToOne, Clip180, Flip180 };
63 |
64 | void displayInit();
65 | void updateGpsInterval(uint32_t interval);
66 | uint8_t calcGpsIntervalPct(uint32_t now);
67 | int32_t calcProjectedAzim(uint32_t now);
68 | void servoApplyMode(int32_t azim, int32_t elev, int32_t newServoPos[]);
69 | void processGps(uint32_t now);
70 | void servoUpdate(uint32_t now);
71 | const int32_t azimToBearing(int32_t azim) const;
72 |
73 | #if defined(PIN_OLED_SDA)
74 | void displayState();
75 | void displayGpsIdle(uint32_t now);
76 | void displayActive(uint32_t now, int32_t projectedAzim);
77 | void displayGpsIntervalBar(uint32_t now);
78 | void displayAzimuthExtent(int32_t y);
79 | void displayAzimuth(int32_t projectedAzim);
80 | void displayAltitude(int32_t azimPos, int32_t elevPos);
81 | void displayTargetCircle(int32_t projectedAzim);
82 | void displayTargetDistance();
83 | void displayVBat();
84 | #endif
85 |
86 | struct {
87 | int32_t lat;
88 | int32_t lon;
89 | uint32_t speed; // km/h * 10
90 | uint32_t heading; // degrees * 10
91 | int32_t altitude; // meters
92 | uint32_t lastUpdateMs; // timestamp of last update
93 | uint8_t satcnt; // number of satellites
94 | bool updated;
95 | } _gpsLast;
96 | struct {
97 | int32_t lat;
98 | int32_t lon;
99 | int32_t alt;
100 | } _home;
101 | uint32_t _gpsAvgUpdateIntervalMs;
102 | // Servo Position
103 | uint32_t _lastServoUpdateMs;
104 | uint32_t _targetDistance; // meters
105 | uint16_t _targetAzim; // degrees
106 | uint8_t _targetElev; // degrees
107 | bool _isOverrideMode;
108 | int32_t _azimMsPerDegree; // milliseconds per degree
109 | int32_t _servoPos[IDX_COUNT]; // smoothed azim servo output us
110 | uint32_t _lastAzimFlipMs;
111 | VbatSampler _vbat;
112 |
113 | #if defined(PIN_SERVO_AZIM)
114 | Servo _servo_Azim;
115 | #endif
116 | #if defined(PIN_SERVO_ELEV)
117 | Servo _servo_Elev;
118 | #endif
119 | #if defined(PIN_OLED_SDA)
120 | Adafruit_SSD1306 _display;
121 | uint32_t _lastDisplayActiveMs;
122 | #endif
123 | };
124 |
125 | extern AatModule vrxModule;
126 |
127 | #endif /* AAT_BACKPACK */
--------------------------------------------------------------------------------
/src/module_base.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include "crsf_protocol.h"
5 | #include "msp.h"
6 |
7 | class ModuleBase
8 | {
9 | public:
10 | void Init();
11 | void SendIndexCmd(uint8_t index);
12 | void SetRecordingState(uint8_t recordingState, uint16_t delay);
13 | void SetOSD(mspPacket_t *packet);
14 | void SendHeadTrackingEnableCmd(bool enable);
15 | void SetRTC();
16 | void SendLinkTelemetry(uint8_t *rawCrsfPacket);
17 | void SendBatteryTelemetry(uint8_t *rawCrsfPacket);
18 | void SendGpsTelemetry(crsf_packet_gps_t *packet) {}
19 | void Loop(uint32_t now);
20 | };
21 |
22 | class MSPModuleBase : public ModuleBase
23 | {
24 | public:
25 | MSPModuleBase(Stream *port) : m_port(port) {};
26 | void Loop(uint32_t);
27 |
28 | void sendResponse(uint16_t function, const uint8_t *response, uint32_t responseSize);
29 |
30 | Stream *m_port;
31 | MSP msp;
32 | };
--------------------------------------------------------------------------------
/src/module_crsf.cpp:
--------------------------------------------------------------------------------
1 | #include "module_crsf.h"
2 |
3 | void CrsfModuleBase::Loop(uint32_t now)
4 | {
5 | while (_port.available())
6 | {
7 | uint8_t b = _port.read();
8 | _rxBuf[_rxBufPos++] = b;
9 |
10 | handleByteReceived();
11 |
12 | if (_rxBufPos == (sizeof(_rxBuf)/sizeof(_rxBuf[0])))
13 | {
14 | // Packet buffer filled and no valid packet found, dump the whole thing
15 | _rxBufPos = 0;
16 | }
17 | }
18 | ModuleBase::Loop(now);
19 | }
20 |
21 | void CrsfModuleBase::handleByteReceived()
22 | {
23 | bool reprocess;
24 | do
25 | {
26 | reprocess = false;
27 | if (_rxBufPos > 1)
28 | {
29 | uint8_t len = _rxBuf[1];
30 | // Sanity check the declared length isn't outside Type + X{1,CRSF_MAX_PAYLOAD_LEN} + CRC
31 | // assumes there never will be a CRSF message that just has a type and no data (X)
32 | if (len < 3 || len > (CRSF_MAX_PAYLOAD_LEN + 2))
33 | {
34 | shiftRxBuffer(1);
35 | reprocess = true;
36 | }
37 |
38 | else if (_rxBufPos >= (len + 2))
39 | {
40 | uint8_t inCrc = _rxBuf[2 + len - 1];
41 | uint8_t crc = _crc.calc(&_rxBuf[2], len - 1);
42 | if (crc == inCrc)
43 | {
44 | onCrsfPacketIn((const crsf_header_t *)_rxBuf);
45 |
46 | shiftRxBuffer(len + 2);
47 | reprocess = true;
48 | }
49 | else
50 | {
51 | shiftRxBuffer(1);
52 | reprocess = true;
53 | }
54 | } // if complete packet
55 | } // if pos > 1
56 | } while (reprocess);
57 | }
58 |
59 | // Shift the bytes in the RxBuf down by cnt bytes
60 | void CrsfModuleBase::shiftRxBuffer(uint8_t cnt)
61 | {
62 | // If removing the whole thing, just set pos to 0
63 | if (cnt >= _rxBufPos)
64 | {
65 | _rxBufPos = 0;
66 | return;
67 | }
68 |
69 | // Otherwise do the slow shift down
70 | uint8_t *src = &_rxBuf[cnt];
71 | uint8_t *dst = &_rxBuf[0];
72 | _rxBufPos -= cnt;
73 | uint8_t left = _rxBufPos;
74 | while (left--)
75 | *dst++ = *src++;
76 | }
77 |
--------------------------------------------------------------------------------
/src/module_crsf.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include "crsf_protocol.h"
5 | #include "crc.h"
6 |
7 | class CrsfModuleBase : public ModuleBase
8 | {
9 | public:
10 | CrsfModuleBase() = delete;
11 | CrsfModuleBase(Stream &port) :
12 | _port(port), _crc(CRSF_CRC_POLY),
13 | _rxBufPos(0)
14 | {};
15 | void Loop(uint32_t now);
16 |
17 | virtual void onCrsfPacketIn(const crsf_header_t *pkt) {};
18 |
19 | protected:
20 | Stream &_port;
21 |
22 | static constexpr uint8_t CRSF_MAX_PACKET_SIZE = 64U;
23 | static constexpr uint8_t CRSF_MAX_PAYLOAD_LEN = (CRSF_MAX_PACKET_SIZE - 4U);
24 |
25 | GENERIC_CRC8 _crc;
26 | uint8_t _rxBufPos;
27 | uint8_t _rxBuf[CRSF_MAX_PACKET_SIZE];
28 |
29 | void processPacketIn(uint8_t len);
30 | void shiftRxBuffer(uint8_t cnt);
31 | void handleByteReceived();
32 | };
--------------------------------------------------------------------------------
/src/orqa.cpp:
--------------------------------------------------------------------------------
1 | #include "orqa.h"
2 |
3 | GENERIC_CRC8 ghst_crc(GHST_CRC_POLY);
4 |
5 | void Orqa::SendIndexCmd(uint8_t index)
6 | {
7 | uint8_t band = GetBand(index);
8 | uint8_t channel = GetChannel(index);
9 | uint8_t currentGHSTChannel = GHSTChannel(band, channel);
10 | uint16_t currentFrequency = GetFrequency(index);
11 | SendGHSTUpdate(currentFrequency, currentGHSTChannel);
12 | }
13 |
14 |
15 | void Orqa::SendGHSTUpdate(uint16_t freq, uint8_t ghstChannel)
16 | {
17 | uint8_t packet[] = {
18 | 0x82, 0x0C, 0x20, // Header
19 | 0x00, (uint8_t)(freq & 0xFF), (uint8_t)(freq >> 8), // Frequency
20 | 0x01, 0x00, ghstChannel, // Band & Channel
21 | 0x00, 0x00, 0x00, 0x00, // Power Level?
22 | 0x00 }; // CRC
23 | uint8_t crc = ghst_crc.calc(&packet[2], 11);
24 | packet[13] = crc;
25 |
26 | for(uint8_t i = 0; i < 14; i++)
27 | {
28 | Serial.write(packet[i]);
29 | }
30 | }
31 |
32 | uint8_t Orqa::GHSTChannel(uint8_t band, uint8_t channel)
33 | {
34 | // ELRS Bands: A, B, E, I/F, R, L
35 | // Orqa/Rapidfire Bands: I/F, R, E, B, A, L, X
36 | uint8_t ghstChannel = 0x00;
37 |
38 | switch(band)
39 | {
40 | case 0x01:
41 | ghstChannel |= 0x50;
42 | break;
43 | case 0x02:
44 | ghstChannel |= 0x40;
45 | break;
46 | case 0x03:
47 | ghstChannel |= 0x30;
48 | break;
49 | case 0x04:
50 | ghstChannel |= 0x10;
51 | break;
52 | case 0x05:
53 | ghstChannel |= 0x20;
54 | break;
55 | case 0x06:
56 | ghstChannel |= 0x60;
57 | break;
58 | }
59 | ghstChannel |= channel;
60 |
61 | return ghstChannel;
62 | }
--------------------------------------------------------------------------------
/src/orqa.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include
5 | #include
6 |
7 | #define VRX_UART_BAUD 100000
8 | #define VRX_BOOT_DELAY 7000
9 | #define GHST_CRC_POLY 0xD5
10 |
11 | class Orqa : public ModuleBase
12 | {
13 | public:
14 | void SendIndexCmd(uint8_t index);
15 | private:
16 | uint8_t GHSTChannel(uint8_t band, uint8_t channel);
17 | void SendGHSTUpdate(uint16_t freq, uint8_t ghstChannel);
18 | };
--------------------------------------------------------------------------------
/src/rapidfire.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include
5 |
6 | #define VRX_BOOT_DELAY 2000
7 |
8 | #define BIT_BANG_FREQ 1000
9 | #define SPAM_COUNT 3
10 |
11 | #define RF_API_DIR_GRTHAN 0x3E // '>'
12 | #define RF_API_DIR_EQUAL 0x3D // '='
13 | #define RF_API_BEEP_CMD 0x53 // 'S'
14 | #define RF_API_CHANNEL_CMD 0x43 // 'C'
15 | #define RF_API_BAND_CMD 0x42 // 'B'
16 |
17 | class Rapidfire : public ModuleBase
18 | {
19 | public:
20 | void Init();
21 | void SendBuzzerCmd();
22 | void SendIndexCmd(uint8_t index);
23 | void SendChannelCmd(uint8_t channel);
24 | void SendBandCmd(uint8_t band);
25 |
26 | private:
27 | void SendSPI(uint8_t* buf, uint8_t bufLen);
28 | void EnableSPIMode();
29 | uint8_t crc8(uint8_t* buf, uint8_t bufLen);
30 | bool SPIModeEnabled = false;
31 | };
32 |
--------------------------------------------------------------------------------
/src/rx5808.cpp:
--------------------------------------------------------------------------------
1 | #include "rx5808.h"
2 | #include
3 | #include "logging.h"
4 |
5 | void
6 | RX5808::Init()
7 | {
8 | ModuleBase::Init();
9 |
10 | pinMode(PIN_MOSI, INPUT);
11 | pinMode(PIN_CLK, INPUT);
12 | pinMode(PIN_CS, INPUT);
13 |
14 | #if defined(PIN_CS_2)
15 | pinMode(PIN_CS_2, INPUT);
16 | #endif
17 |
18 | DBGLN("RX5808 init complete");
19 | }
20 |
21 | void
22 | RX5808::EnableSPIMode()
23 | {
24 | pinMode(PIN_MOSI, OUTPUT);
25 | pinMode(PIN_CLK, OUTPUT);
26 | pinMode(PIN_CS, OUTPUT);
27 |
28 | digitalWrite(PIN_MOSI, LOW);
29 | digitalWrite(PIN_CLK, LOW);
30 | digitalWrite(PIN_CS, HIGH);
31 |
32 | #if defined(PIN_CS_2)
33 | pinMode(PIN_CS_2, OUTPUT);
34 | digitalWrite(PIN_CS_2, HIGH);
35 | #endif
36 |
37 | SPIModeEnabled = true;
38 |
39 | DBGLN("SPI config complete");
40 | }
41 |
42 | void
43 | RX5808::SendIndexCmd(uint8_t index)
44 | {
45 | DBG("Setting index ");
46 | DBGLN("%x", index);
47 |
48 | uint16_t f = frequencyTable[index];
49 |
50 | uint32_t data = ((((f - 479) / 2) / 32) << 7) | (((f - 479) / 2) % 32);
51 |
52 | uint32_t newRegisterData = SYNTHESIZER_REG_B | (RX5808_WRITE_CTRL_BIT << 4) | (data << 5);
53 |
54 | uint32_t currentRegisterData = SYNTHESIZER_REG_B | (RX5808_WRITE_CTRL_BIT << 4) | rtc6705readRegister(SYNTHESIZER_REG_B);
55 |
56 | if (newRegisterData != currentRegisterData)
57 | {
58 | rtc6705WriteRegister(newRegisterData);
59 | }
60 | }
61 |
62 | void
63 | RX5808::rtc6705WriteRegister(uint32_t buf)
64 | {
65 | if (!SPIModeEnabled)
66 | {
67 | EnableSPIMode();
68 | }
69 |
70 | uint32_t periodMicroSec = 1000000 / BIT_BANG_FREQ;
71 |
72 | digitalWrite(PIN_CS, LOW);
73 | #if defined(PIN_CS_2)
74 | digitalWrite(PIN_CS_2, LOW);
75 | #endif
76 | delayMicroseconds(periodMicroSec);
77 |
78 | for (uint8_t i = 0; i < RX5808_PACKET_LENGTH; ++i)
79 | {
80 | digitalWrite(PIN_CLK, LOW);
81 | delayMicroseconds(periodMicroSec / 4);
82 | digitalWrite(PIN_MOSI, buf & 0x01);
83 | delayMicroseconds(periodMicroSec / 4);
84 | digitalWrite(PIN_CLK, HIGH);
85 | delayMicroseconds(periodMicroSec / 2);
86 |
87 | buf >>= 1;
88 | }
89 |
90 | digitalWrite(PIN_CLK, LOW);
91 | delayMicroseconds(periodMicroSec);
92 | digitalWrite(PIN_MOSI, LOW);
93 | digitalWrite(PIN_CLK, LOW);
94 | digitalWrite(PIN_CS, HIGH);
95 | #if defined(PIN_CS_2)
96 | digitalWrite(PIN_CS_2, HIGH);
97 | #endif
98 | }
99 |
100 | uint32_t
101 | RX5808::rtc6705readRegister(uint8_t readRegister)
102 | {
103 | if (!SPIModeEnabled)
104 | {
105 | EnableSPIMode();
106 | }
107 |
108 | uint32_t buf = readRegister | (RX5808_READ_CTRL_BIT << 4);
109 | uint32_t registerData = 0;
110 |
111 | uint32_t periodMicroSec = 1000000 / BIT_BANG_FREQ;
112 |
113 | digitalWrite(PIN_CS, LOW);
114 | delayMicroseconds(periodMicroSec);
115 |
116 | // Write register address and read bit
117 | for (uint8_t i = 0; i < RX5808_ADDRESS_R_W_LENGTH; ++i)
118 | {
119 | digitalWrite(PIN_CLK, LOW);
120 | delayMicroseconds(periodMicroSec / 4);
121 | digitalWrite(PIN_MOSI, buf & 0x01);
122 | delayMicroseconds(periodMicroSec / 4);
123 | digitalWrite(PIN_CLK, HIGH);
124 | delayMicroseconds(periodMicroSec / 2);
125 |
126 | buf >>= 1;
127 | }
128 |
129 | // Change pin from output to input
130 | pinMode(PIN_MOSI, INPUT);
131 |
132 | // Read data 20 bits
133 | for (uint8_t i = 0; i < RX5808_DATA_LENGTH; i++)
134 | {
135 | digitalWrite(PIN_CLK, LOW);
136 | delayMicroseconds(periodMicroSec / 4);
137 |
138 | if (digitalRead(PIN_MOSI))
139 | {
140 | registerData = registerData | (1 << (5 + i));
141 | }
142 |
143 | delayMicroseconds(periodMicroSec / 4);
144 | digitalWrite(PIN_CLK, HIGH);
145 | delayMicroseconds(periodMicroSec / 2);
146 | }
147 |
148 | // Change pin back to output
149 | pinMode(PIN_MOSI, OUTPUT);
150 |
151 | digitalWrite(PIN_MOSI, LOW);
152 | digitalWrite(PIN_CLK, LOW);
153 | digitalWrite(PIN_CS, HIGH);
154 |
155 | return registerData;
156 | }
157 |
--------------------------------------------------------------------------------
/src/rx5808.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include
5 |
6 | #define BIT_BANG_FREQ 10000
7 |
8 | #define SYNTHESIZER_REG_A 0x00
9 | #define SYNTHESIZER_REG_B 0x01
10 | #define SYNTHESIZER_REG_C 0x02
11 | #define SYNTHESIZER_REG_D 0x03
12 | #define VCO_SWITCH_CAP_CONTROL_REGISTER 0x04
13 | #define DFC_CONTROL_REGISTER 0x05
14 | #define SIXM_AUDIO_DEMODULATOR_CONTROL_REGISTER 0x06
15 | #define SIXM5_AUDIO_DEMODULATOR_CONTROL_REGISTER 0x07
16 | #define RECEIVER_CONTROL_REGISTER_1 0x08
17 | #define RECEIVER_CONTROL_REGISTER_2 0x09
18 | #define POWER_DOWN_CONTROL_REGISTER 0x0A
19 | #define STATE_REGISTER 0x0F
20 |
21 | #define RX5808_READ_CTRL_BIT 0x00
22 | #define RX5808_WRITE_CTRL_BIT 0x01
23 | #define RX5808_ADDRESS_R_W_LENGTH 5
24 | #define RX5808_DATA_LENGTH 20
25 | #define RX5808_PACKET_LENGTH 25
26 |
27 | const uint16_t frequencyTable[48] = {
28 | 5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725, // A
29 | 5733, 5752, 5771, 5790, 5809, 5828, 5847, 5866, // B
30 | 5705, 5685, 5665, 5645, 5885, 5905, 5925, 5945, // E
31 | 5740, 5760, 5780, 5800, 5820, 5840, 5860, 5880, // F
32 | 5658, 5695, 5732, 5769, 5806, 5843, 5880, 5917, // R
33 | 5333, 5373, 5413, 5453, 5493, 5533, 5573, 5613 // L
34 | };
35 |
36 | class RX5808 : public ModuleBase
37 | {
38 | public:
39 | void Init();
40 | void SendIndexCmd(uint8_t index);
41 |
42 | private:
43 | void rtc6705WriteRegister(uint32_t buf);
44 | uint32_t rtc6705readRegister(uint8_t readRegister);
45 | void EnableSPIMode();
46 | bool SPIModeEnabled = false;
47 | };
48 |
--------------------------------------------------------------------------------
/src/skyzone_msp.cpp:
--------------------------------------------------------------------------------
1 | #include "skyzone_msp.h"
2 | #include "logging.h"
3 | #include
4 |
5 | void
6 | SkyzoneMSP::Init()
7 | {
8 | ModuleBase::Init();
9 | m_delay = 0;
10 | }
11 |
12 | void
13 | SkyzoneMSP::SendIndexCmd(uint8_t index)
14 | {
15 | uint8_t retries = 3;
16 | while (GetChannelIndex() != index && retries > 0)
17 | {
18 | SetChannelIndex(index);
19 | retries--;
20 | }
21 | }
22 |
23 | uint8_t
24 | SkyzoneMSP::GetChannelIndex()
25 | {
26 | MSP msp;
27 | mspPacket_t packet;
28 | packet.reset();
29 | packet.makeCommand();
30 | packet.function = MSP_ELRS_BACKPACK_GET_CHANNEL_INDEX;
31 |
32 | // Send request, then wait for a response back from the VRX
33 | bool receivedResponse = msp.awaitPacket(&packet, m_port, VRX_RESPONSE_TIMEOUT);
34 |
35 | if (receivedResponse)
36 | {
37 | mspPacket_t* packetResponse = msp.getReceivedPacket();
38 | msp.markPacketReceived();
39 | return packetResponse->readByte();
40 | }
41 |
42 | DBGLN("Skyzone module: Exceeded timeout while waiting for channel index response");
43 | return CHANNEL_INDEX_UNKNOWN;
44 | }
45 |
46 | void
47 | SkyzoneMSP::SetChannelIndex(uint8_t index)
48 | {
49 | MSP msp;
50 | mspPacket_t packet;
51 | packet.reset();
52 | packet.makeCommand();
53 | packet.function = MSP_ELRS_BACKPACK_SET_CHANNEL_INDEX;
54 | packet.addByte(index); // payload
55 |
56 | msp.sendPacket(&packet, m_port);
57 | }
58 |
59 | uint8_t
60 | SkyzoneMSP::GetRecordingState()
61 | {
62 | MSP msp;
63 | mspPacket_t packet;
64 | packet.reset();
65 | packet.makeCommand();
66 | packet.function = MSP_ELRS_BACKPACK_GET_RECORDING_STATE;
67 |
68 | // Send request, then wait for a response back from the VRX
69 | bool receivedResponse = msp.awaitPacket(&packet, m_port, VRX_RESPONSE_TIMEOUT);
70 |
71 | if (receivedResponse)
72 | {
73 | mspPacket_t *packetResponse = msp.getReceivedPacket();
74 | msp.markPacketReceived();
75 | return packetResponse->readByte() ? VRX_DVR_RECORDING_ACTIVE : VRX_DVR_RECORDING_INACTIVE;
76 | }
77 |
78 | DBGLN("Skyzone module: Exceeded timeout while waiting for recording state response");
79 | return VRX_DVR_RECORDING_UNKNOWN;
80 | }
81 |
82 | void
83 | SkyzoneMSP::SetRecordingState(uint8_t recordingState, uint16_t delay)
84 | {
85 | DBGLN("SetRecordingState = %d delay = %d", recordingState, delay);
86 |
87 | m_recordingState = recordingState;
88 | m_delay = delay * 1000; // delay is in seconds, convert to milliseconds
89 | m_delayStartMillis = millis();
90 |
91 | if (m_delay == 0)
92 | {
93 | SendRecordingState();
94 | }
95 | }
96 |
97 | void
98 | SkyzoneMSP::SendRecordingState()
99 | {
100 | DBGLN("SendRecordingState = %d delay = %d", m_recordingState, m_delay);
101 |
102 | MSP msp;
103 | mspPacket_t packet;
104 | packet.reset();
105 | packet.makeCommand();
106 | packet.function = MSP_ELRS_BACKPACK_SET_RECORDING_STATE;
107 | packet.addByte(m_recordingState);
108 | packet.addByte(m_delay & 0xFF); // delay byte 1
109 | packet.addByte(m_delay >> 8); // delay byte 2
110 |
111 | msp.sendPacket(&packet, m_port);
112 | }
113 |
114 | void
115 | SkyzoneMSP::SetOSD(mspPacket_t *packet)
116 | {
117 | MSP msp;
118 | msp.sendPacket(packet, m_port);
119 | }
120 |
121 | void
122 | SkyzoneMSP::SendHeadTrackingEnableCmd(bool enable)
123 | {
124 | MSP msp;
125 | mspPacket_t packet;
126 | packet.reset();
127 | packet.makeCommand();
128 | packet.function = MSP_ELRS_BACKPACK_SET_HEAD_TRACKING;
129 | packet.addByte(enable);
130 |
131 | msp.sendPacket(&packet, m_port);
132 | }
133 |
134 | void
135 | SkyzoneMSP::Loop(uint32_t now)
136 | {
137 | MSPModuleBase::Loop(now);
138 |
139 | // Handle delay timer for SendRecordingState()
140 | if (m_delay != 0)
141 | {
142 | if (now - m_delayStartMillis >= m_delay)
143 | {
144 | SendRecordingState();
145 | m_delay = 0;
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/skyzone_msp.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "msp.h"
4 | #include "msptypes.h"
5 | #include "module_base.h"
6 | #include
7 |
8 | #define VRX_BOOT_DELAY 2000
9 |
10 | #define VRX_RESPONSE_TIMEOUT 500
11 | #define VRX_UART_BAUD 115200 // skyzone uses 115k baud between the ESP32-PICO and their MCU
12 |
13 | #define CHANNEL_INDEX_UNKNOWN 255
14 | #define VRX_DVR_RECORDING_ACTIVE 1
15 | #define VRX_DVR_RECORDING_INACTIVE 0
16 | #define VRX_DVR_RECORDING_UNKNOWN 255
17 |
18 | class SkyzoneMSP : public MSPModuleBase
19 | {
20 | public:
21 | SkyzoneMSP(Stream *port) : MSPModuleBase(port) {};
22 | void Init();
23 | void SendIndexCmd(uint8_t index);
24 | uint8_t GetChannelIndex();
25 | void SetChannelIndex(uint8_t index);
26 | uint8_t GetRecordingState();
27 | void SetRecordingState(uint8_t recordingState, uint16_t delay);
28 | void SetOSD(mspPacket_t *packet);
29 | void SendHeadTrackingEnableCmd(bool enable);
30 | void Loop(uint32_t now);
31 |
32 | private:
33 | void SendRecordingState();
34 |
35 | uint8_t m_recordingState;
36 | uint16_t m_delay;
37 | uint32_t m_delayStartMillis;
38 | };
39 |
--------------------------------------------------------------------------------
/src/steadyview.cpp:
--------------------------------------------------------------------------------
1 | #include "steadyview.h"
2 | #include
3 | #include "logging.h"
4 |
5 | void
6 | SteadyView::Init()
7 | {
8 | ModuleBase::Init();
9 |
10 | pinMode(PIN_MOSI, OUTPUT);
11 | pinMode(PIN_CLK, OUTPUT);
12 | pinMode(PIN_CS, OUTPUT);
13 |
14 | digitalWrite(PIN_CLK, LOW);
15 | digitalWrite(PIN_MOSI, HIGH);
16 | digitalWrite(PIN_CS, HIGH);
17 |
18 | DBGLN("SPI config complete");
19 | delay(100);
20 | SetMode(ModeMix);
21 | }
22 |
23 | void
24 | SteadyView::SendIndexCmd(uint8_t index)
25 | {
26 | DBG("Setting index ");
27 | DBGLN("%x", index);
28 |
29 | uint16_t f = frequencyTable[index];
30 | uint32_t data = ((((f - 479) / 2) / 32) << 7) | (((f - 479) / 2) % 32);
31 | uint32_t newRegisterData = SYNTHESIZER_REG_B | (RX5808_WRITE_CTRL_BIT << 4) | (data << 5);
32 |
33 | uint32_t currentRegisterData = SYNTHESIZER_REG_B | (RX5808_WRITE_CTRL_BIT << 4) | rtc6705readRegister(SYNTHESIZER_REG_B);
34 |
35 | if (newRegisterData != currentRegisterData)
36 | {
37 | rtc6705WriteRegister(SYNTHESIZER_REG_A | (RX5808_WRITE_CTRL_BIT << 4) | (0x8 << 5));
38 | rtc6705WriteRegister(newRegisterData);
39 | }
40 | currentIndex = index;
41 | }
42 |
43 | void
44 | SteadyView::SetMode(videoMode_t mode)
45 | {
46 | if (mode == ModeMix)
47 | {
48 | digitalWrite(PIN_CLK, HIGH);
49 | delay(100);
50 | digitalWrite(PIN_CLK, LOW);
51 | delay(500);
52 | }
53 | uint16_t f = frequencyTable[currentIndex];
54 | uint32_t data = ((((f - 479) / 2) / 32) << 7) | (((f - 479) / 2) % 32);
55 | uint32_t registerData = SYNTHESIZER_REG_B | (RX5808_WRITE_CTRL_BIT << 4) | (data << 5);
56 |
57 | rtc6705WriteRegister(SYNTHESIZER_REG_A | (RX5808_WRITE_CTRL_BIT << 4) | (0x8 << 5));
58 | delayMicroseconds(500);
59 | rtc6705WriteRegister(SYNTHESIZER_REG_A | (RX5808_WRITE_CTRL_BIT << 4) | (0x8 << 5));
60 | rtc6705WriteRegister(registerData);
61 | }
62 |
63 | void
64 | SteadyView::rtc6705WriteRegister(uint32_t buf)
65 | {
66 | uint32_t periodMicroSec = 1000000 / BIT_BANG_FREQ;
67 |
68 | digitalWrite(PIN_CS, LOW);
69 | delayMicroseconds(periodMicroSec);
70 |
71 | for (uint8_t i = 0; i < RX5808_PACKET_LENGTH; ++i)
72 | {
73 | digitalWrite(PIN_MOSI, buf & 0x01);
74 | delayMicroseconds(periodMicroSec / 4);
75 | digitalWrite(PIN_CLK, HIGH);
76 | delayMicroseconds(periodMicroSec / 4);
77 | digitalWrite(PIN_CLK, LOW);
78 | delayMicroseconds(periodMicroSec / 4);
79 |
80 | buf >>= 1;
81 | }
82 |
83 | delayMicroseconds(periodMicroSec);
84 | digitalWrite(PIN_MOSI, HIGH);
85 | digitalWrite(PIN_CS, HIGH);
86 | }
87 |
88 | uint32_t
89 | SteadyView::rtc6705readRegister(uint8_t readRegister)
90 | {
91 | uint32_t buf = readRegister | (RX5808_READ_CTRL_BIT << 4);
92 | uint32_t registerData = 0;
93 |
94 | uint32_t periodMicroSec = 1000000 / BIT_BANG_FREQ;
95 |
96 | digitalWrite(PIN_CS, LOW);
97 | delayMicroseconds(periodMicroSec);
98 |
99 | // Write register address and read bit
100 | for (uint8_t i = 0; i < RX5808_ADDRESS_R_W_LENGTH; ++i)
101 | {
102 | digitalWrite(PIN_MOSI, buf & 0x01);
103 | delayMicroseconds(periodMicroSec / 4);
104 | digitalWrite(PIN_CLK, HIGH);
105 | delayMicroseconds(periodMicroSec / 4);
106 | digitalWrite(PIN_CLK, LOW);
107 | delayMicroseconds(periodMicroSec / 4);
108 |
109 | buf >>= 1;
110 | }
111 |
112 | // Change pin from output to input
113 | pinMode(PIN_MOSI, INPUT);
114 |
115 | // Read data 20 bits
116 | for (uint8_t i = 0; i < RX5808_DATA_LENGTH; i++)
117 | {
118 | digitalWrite(PIN_CLK, HIGH);
119 | delayMicroseconds(periodMicroSec / 4);
120 |
121 | if (digitalRead(PIN_MOSI))
122 | {
123 | registerData = registerData | (1 << (5 + i));
124 | }
125 |
126 | delayMicroseconds(periodMicroSec / 4);
127 | digitalWrite(PIN_CLK, LOW);
128 | delayMicroseconds(periodMicroSec / 2);
129 | }
130 |
131 | // Change pin back to output
132 | pinMode(PIN_MOSI, OUTPUT);
133 |
134 | digitalWrite(PIN_MOSI, LOW);
135 | digitalWrite(PIN_CS, HIGH);
136 |
137 | return registerData;
138 | }
139 |
--------------------------------------------------------------------------------
/src/steadyview.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include
5 |
6 | #define BIT_BANG_FREQ 10000
7 |
8 | #define SYNTHESIZER_REG_A 0x00
9 | #define SYNTHESIZER_REG_B 0x01
10 | #define SYNTHESIZER_REG_C 0x02
11 | #define SYNTHESIZER_REG_D 0x03
12 | #define VCO_SWITCH_CAP_CONTROL_REGISTER 0x04
13 | #define DFC_CONTROL_REGISTER 0x05
14 | #define SIXM_AUDIO_DEMODULATOR_CONTROL_REGISTER 0x06
15 | #define SIXM5_AUDIO_DEMODULATOR_CONTROL_REGISTER 0x07
16 | #define RECEIVER_CONTROL_REGISTER_1 0x08
17 | #define RECEIVER_CONTROL_REGISTER_2 0x09
18 | #define POWER_DOWN_CONTROL_REGISTER 0x0A
19 | #define STATE_REGISTER 0x0F
20 |
21 | #define RX5808_READ_CTRL_BIT 0x00
22 | #define RX5808_WRITE_CTRL_BIT 0x01
23 | #define RX5808_ADDRESS_R_W_LENGTH 5
24 | #define RX5808_DATA_LENGTH 20
25 | #define RX5808_PACKET_LENGTH 25
26 |
27 | const uint16_t frequencyTable[48] = {
28 | 5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725, // A
29 | 5733, 5752, 5771, 5790, 5809, 5828, 5847, 5866, // B
30 | 5705, 5685, 5665, 5645, 5885, 5905, 5925, 5945, // E
31 | 5740, 5760, 5780, 5800, 5820, 5840, 5860, 5880, // F
32 | 5658, 5695, 5732, 5769, 5806, 5843, 5880, 5917, // R
33 | 5333, 5373, 5413, 5453, 5493, 5533, 5573, 5613 // L
34 | };
35 |
36 | typedef enum {
37 | ModeMix = 0,
38 | ModeDiversity
39 | } videoMode_t;
40 |
41 | class SteadyView : public ModuleBase
42 | {
43 | public:
44 | void Init();
45 | void SendIndexCmd(uint8_t index);
46 | void SetMode(videoMode_t mode);
47 |
48 | private:
49 | void rtc6705WriteRegister(uint32_t buf);
50 | uint32_t rtc6705readRegister(uint8_t readRegister);
51 | uint8_t currentIndex;
52 | };
53 |
--------------------------------------------------------------------------------
/src/tbs_fusion.cpp:
--------------------------------------------------------------------------------
1 | #include "tbs_fusion.h"
2 | #include "logging.h"
3 | #include "crc.h"
4 | #include "crsf_protocol.h"
5 | #include
6 |
7 | GENERIC_CRC8 crsf_crc(CRSF_CRC_POLY);
8 |
9 | void
10 | Fusion::Init()
11 | {
12 | ModuleBase::Init();
13 | DBGLN("Fusion backpack init complete");
14 | }
15 |
16 | void
17 | Fusion::SendIndexCmd(uint8_t index)
18 | {
19 | uint16_t f = frequencyTable[index];
20 | uint8_t buf[12];
21 | uint8_t pos = 0;
22 |
23 | // 0xC8, 0xA, 0x40, 0x0, 0x0, 0x10, 0xEC, 0xE, 0x16, 0x94, 0x1, 0xBC, 0xC8, 0x04, 0x28, 0x00, 0x0E, 0x7C
24 |
25 | buf[pos++] = 0xC8; // framing byte
26 | buf[pos++] = 0x0A; // len
27 | buf[pos++] = 0x40; // some form of device / origin address ?
28 | buf[pos++] = 0x00; // some form of device / origin address ?
29 | buf[pos++] = 0x00; // some form of device / origin address ?
30 | buf[pos++] = 0x10;
31 | buf[pos++] = 0xEC;
32 | buf[pos++] = 0x0E;
33 | buf[pos++] = f >> 8; // freq byte 1
34 | buf[pos++] = f & 0xFF; // freq byte 2
35 | buf[pos++] = 0x01;
36 |
37 | uint8_t crc = crsf_crc.calc(&buf[2], pos - 2); // first 2 bytes not included in CRC
38 | buf[pos++] = crc;
39 |
40 | for (uint8_t i = 0; i < pos; ++i)
41 | {
42 | Serial.write(buf[i]);
43 | }
44 |
45 | // Leaving this in as its useful to debug
46 | // for (uint8_t i = 0; i < pos; ++i)
47 | // {
48 | // Serial.print("0x"); Serial.print(buf[i], HEX); Serial.print(", ");
49 | // }
50 | // Serial.println("");
51 | }
52 |
53 | void
54 | Fusion::SendLinkTelemetry(uint8_t *rawCrsfPacket)
55 | {
56 | // uplink_RSSI_1
57 | uint8_t rssi = rawCrsfPacket[3] * -1;
58 | // uplink_Link_quality
59 | uint8_t lq = rawCrsfPacket[5] / 3;
60 | // SNR
61 | uint8_t snr = rawCrsfPacket[6]; // actually int8_t
62 | // Construct our outbound buffer to the Fusion STM32
63 | uint8_t buf[] = {
64 | 0xC8, // Flight controller
65 | 0x0F, // Overall length of extended packet
66 | 0x40, // Extended frametype
67 | 0x00,
68 | 0x00,
69 | 0x14, // CRSF_FRAMETYPE_LINK_STATISTICS
70 | 0x1D, // ?
71 | rssi,
72 | lq,
73 | snr,
74 | 0xFD, // ?
75 | 0x02,
76 | 0x02,
77 | 0x1B,
78 | 0x64,
79 | 0x47,
80 | 0x00 // CRC
81 | };
82 | // Calculate & write CRC
83 | buf[sizeof(buf) - 1] = crsf_crc.calc(&buf[2], sizeof(buf) - 3);
84 | for (uint8_t i = 0; i < sizeof(buf); i++)
85 | {
86 | Serial.write(buf[i]);
87 | }
88 | }
89 |
90 | void
91 | Fusion::SendBatteryTelemetry(uint8_t *rawCrsfPacket)
92 | {
93 | uint16_t voltage = ((uint16_t)rawCrsfPacket[3] << 8 | rawCrsfPacket[4]);
94 | uint16_t amperage = ((uint16_t)rawCrsfPacket[5] << 8 | rawCrsfPacket[6]);
95 | uint32_t mah = ((uint32_t)rawCrsfPacket[7] << 16 | (uint32_t)rawCrsfPacket[8] << 8 | rawCrsfPacket[9]);
96 | uint8_t percentage = rawCrsfPacket[10];
97 | // Construct our outbound buffer to the Fusion STM32
98 | uint8_t buf[] = {
99 | 0xC8, // Flight controller
100 | 0x0D, // Overall length of extended packet
101 | 0x40, // Extended frametype
102 | 0x00,
103 | 0x00,
104 | 0x08, // CRSF_FRAMETYPE_BATTERY_SENSOR
105 | (uint8_t)(voltage >> 8),
106 | (uint8_t)(voltage & 0xFF),
107 | (uint8_t)(amperage >> 8),
108 | (uint8_t)(amperage & 0xFF),
109 | (uint8_t)(mah >> 16),
110 | (uint8_t)(mah >> 8),
111 | (uint8_t)(mah & 0xFF),
112 | percentage,
113 | 0x00 // CRC
114 | };
115 | // Calculate & write CRC
116 | buf[sizeof(buf) - 1] = crsf_crc.calc(&buf[2], sizeof(buf) - 3);
117 | for (uint8_t i = 0; i < sizeof(buf); i++)
118 | {
119 | Serial.write(buf[i]);
120 | }
121 | }
--------------------------------------------------------------------------------
/src/tbs_fusion.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "module_base.h"
4 | #include
5 |
6 | #define VRX_BOOT_DELAY 1000
7 |
8 | #define VRX_UART_BAUD 500000 // fusion uses 500k baud between the ESP8266 and the STM32
9 |
10 | const uint16_t frequencyTable[48] = {
11 | 5865, 5845, 5825, 5805, 5785, 5765, 5745, 5725, // A
12 | 5733, 5752, 5771, 5790, 5809, 5828, 5847, 5866, // B
13 | 5705, 5685, 5665, 5645, 5885, 5905, 5925, 5945, // E
14 | 5740, 5760, 5780, 5800, 5820, 5840, 5860, 5880, // F
15 | 5658, 5695, 5732, 5769, 5806, 5843, 5880, 5917, // R
16 | 5333, 5373, 5413, 5453, 5493, 5533, 5573, 5613 // L
17 | };
18 |
19 | class Fusion : public ModuleBase
20 | {
21 | public:
22 | void Init();
23 | void SendIndexCmd(uint8_t index);
24 | void SendLinkTelemetry(uint8_t *rawCrsfPacket);
25 | void SendBatteryTelemetry(uint8_t *rawCrsfPacket);
26 | };
27 |
--------------------------------------------------------------------------------
/targets/aat.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # AAT VRX backpack -- Automatic antenna tracking
3 | # ********************************
4 |
5 | [env:AAT_ESP_Backpack_via_UART]
6 | extends = env_common_esp12e
7 | build_flags =
8 | ${common_env_data.build_flags}
9 | ${env_common_esp12e.build_flags}
10 | -D TARGET_VRX_BACKPACK
11 | -D AAT_BACKPACK
12 | -D SSD1306_NO_SPLASH
13 | -D PIN_BUTTON=0
14 | -D PIN_LED=16
15 | -D LED_INVERTED
16 | -D PIN_SERVO_AZIM=9
17 | -D PIN_SERVO_ELEV=10
18 | -D PIN_OLED_SCL=1
19 | -D PIN_OLED_SDA=3
20 | ; -D DEBUG_LOG
21 | lib_deps =
22 | ${env.lib_deps}
23 | adafruit/Adafruit SSD1306 @ 2.5.9
24 | build_src_filter = ${common_env_data.build_src_filter} - - - - - -
25 |
26 | [env:AAT_ESP_Backpack_via_WIFI]
27 | extends = env:AAT_ESP_Backpack_via_UART
28 |
--------------------------------------------------------------------------------
/targets/fusion.ini:
--------------------------------------------------------------------------------
1 |
2 | # ********************************
3 | # VRX backpack targets
4 | # ********************************
5 |
6 | [env:Fusion_Builtin_Backpack_via_UART]
7 | extends = env_common_esp12e, fusion_vrx_backpack_common
8 | monitor_speed = 500000
9 | build_flags =
10 | ${env_common_esp12e.build_flags}
11 | ${fusion_vrx_backpack_common.build_flags}
12 | -D PIN_LED=2
13 |
14 | [env:Fusion_Builtin_Backpack_via_WIFI]
15 | extends = env:Fusion_Builtin_Backpack_via_UART
16 |
--------------------------------------------------------------------------------
/targets/hdzero.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # VRX backpack targets
3 | # ********************************
4 |
5 | [env:HDZero_RX51_ESP_RX_Backpack_via_UART]
6 | extends = env_common_esp8285, hdzero_vrx_backpack_common
7 | monitor_speed = 115200
8 | build_flags =
9 | ${env_common_esp8285.build_flags}
10 | ${hdzero_vrx_backpack_common.build_flags}
11 | -D PIN_BUTTON=0
12 | -D PIN_LED=16
13 |
14 | [env:HDZero_RX51_ESP_RX_Backpack_via_WIFI]
15 | extends = env:HDZero_RX51_ESP_RX_Backpack_via_UART
16 |
17 | [env:HDZero_RX51_ESP01F_Backpack_via_UART]
18 | extends = env:HDZero_RX51_ESP_RX_Backpack_via_UART
19 |
20 | [env:HDZero_RX51_ESP01F_Backpack_via_WIFI]
21 | extends = env:HDZero_RX51_ESP_RX_Backpack_via_UART
22 |
23 | [env:HDZero_RX51_HappyModel_EP82_VRX_Backpack_via_UART]
24 | extends = env:HDZero_RX51_ESP_RX_Backpack_via_UART
25 |
26 | [env:HDZero_RX51_HappyModel_EP82_VRX_Backpack_via_WIFI]
27 | extends = env:HDZero_RX51_ESP_RX_Backpack_via_UART
28 |
29 | [env:HDZero_RX51_ESP32_Backpack_via_UART]
30 | extends = env_common_esp32, hdzero_vrx_backpack_common
31 | monitor_speed = 115200
32 | build_flags =
33 | ${env_common_esp32.build_flags}
34 | ${hdzero_vrx_backpack_common.build_flags}
35 | -D PIN_LED=4
36 |
37 | [env:HDZero_RX51_ESP32_Backpack_via_WIFI]
38 | extends = env:HDZero_RX51_ESP32_Backpack_via_UART
39 |
40 |
41 |
42 | [env:HDZero_Goggle_ESP32_Backpack_via_UART]
43 | extends = env_common_esp32, hdzero_vrx_backpack_common
44 | monitor_speed = 115200
45 | build_flags =
46 | ${env_common_esp32.build_flags}
47 | ${hdzero_vrx_backpack_common.build_flags}
48 | -D PIN_LED=4
49 | -D NO_AUTOBIND=1
50 | -D SUPPORT_HEADTRACKING
51 |
52 | [env:HDZero_Goggle_ESP32_Backpack_via_WIFI]
53 | extends = env:HDZero_Goggle_ESP32_Backpack_via_UART
54 |
--------------------------------------------------------------------------------
/targets/mfd_crossbow.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # MFD Crossbow tracker backpack
3 | # ********************************
4 |
5 | [env:MFD_Crossbow_ESP8285_Backpack_via_UART]
6 | extends = env_common_esp8285, mfd_crossbow_backpack_common
7 | build_flags =
8 | ${env_common_esp8285.build_flags}
9 | ${mfd_crossbow_backpack_common.build_flags}
10 | -D PIN_BUTTON=0
11 | -D PIN_LED=16
12 |
13 | [env:MFD_Crossbow_ESP8285_Backpack_via_WIFI]
14 | extends = env:MFD_Crossbow_ESP8285_Backpack_via_UART
15 |
16 | [env:MFD_Crossbow_ESP32_Backpack_via_UART]
17 | extends = env_common_esp32, mfd_crossbow_backpack_common
18 | build_flags =
19 | ${env_common_esp32.build_flags}
20 | ${mfd_crossbow_backpack_common.build_flags}
21 | -D PIN_BUTTON=0
22 |
23 | [env:MFD_Crossbow_ESP32_Backpack_via_WIFI]
24 | extends = env:MFD_Crossbow_ESP32_Backpack_via_UART
25 |
26 | [env:MFD_Crossbow_ESP32C3_Backpack_via_UART]
27 | extends = env_common_esp32c3, mfd_crossbow_backpack_common
28 | build_flags =
29 | ${env_common_esp32c3.build_flags}
30 | ${mfd_crossbow_backpack_common.build_flags}
31 | -D PIN_BUTTON=9
32 |
33 | [env:MFD_Crossbow_ESP32C3_Backpack_via_WIFI]
34 | extends = env:MFD_Crossbow_ESP32C3_Backpack_via_UART
35 |
36 | [env:MFD_Crossbow_ESP32S3_Backpack_via_UART]
37 | extends = env_common_esp32s3, mfd_crossbow_backpack_common
38 | build_flags =
39 | ${env_common_esp32s3.build_flags}
40 | ${mfd_crossbow_backpack_common.build_flags}
41 | -D PIN_BUTTON=0
42 |
43 | [env:MFD_Crossbow_ESP32S3_Backpack_via_WIFI]
44 | extends = env:MFD_Crossbow_ESP32S3_Backpack_via_UART
45 |
--------------------------------------------------------------------------------
/targets/orqa.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # VRX backpack targets
3 | # ********************************
4 |
5 | [env:Orqa_ESP_RX_Backpack_via_UART]
6 | extends = env_common_esp8285, orqa_backpack_common
7 | build_flags =
8 | ${env_common_esp8285.build_flags}
9 | ${orqa_backpack_common.build_flags}
10 | -D PIN_LED=16
11 |
12 | [env:Orqa_ESP_RX_Backpack_via_WIFI]
13 | extends = env:Orqa_ESP_RX_Backpack_via_UART
14 |
15 | [env:Orqa_ESP01F_Backpack_via_UART]
16 | extends = env_common_esp8285, orqa_backpack_common
17 | build_flags =
18 | ${env_common_esp8285.build_flags}
19 | ${orqa_backpack_common.build_flags}
20 | -D PIN_BUTTON=0
21 | -D PIN_LED=16
22 |
23 | [env:Orqa_ESP01F_Backpack_via_WIFI]
24 | extends = env:Orqa_ESP01F_Backpack_via_UART
25 |
26 | [env:Orqa_HappyModel_EP82_VRX_Backpack_via_UART]
27 | extends = env:Orqa_ESP01F_Backpack_via_UART
28 |
29 | [env:Orqa_HappyModel_EP82_VRX_Backpack_via_WIFI]
30 | extends = env:Orqa_HappyModel_EP82_VRX_Backpack_via_UART
31 |
32 | [env:Orqa_HT_Backpack_via_UART]
33 | extends = env:Orqa_ESP01F_Backpack_via_UART
34 | build_flags =
35 | ${env:Orqa_ESP01F_Backpack_via_UART.build_flags}
36 | -D HAS_HEADTRACKING
37 | -D PIN_SDA=2
38 | -D PIN_SCL=4
39 | -D PIN_INT=15
40 |
41 | [env:Orqa_HT_Backpack_via_WIFI]
42 | extends = env:Orqa_HT_Backpack_via_UART
43 |
--------------------------------------------------------------------------------
/targets/rapidfire.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # VRX backpack targets
3 | # ********************************
4 |
5 | [env:Rapidfire_ESP_RX_Backpack_via_UART]
6 | extends = env_common_esp8285, rapidfire_vrx_backpack_common
7 | build_flags =
8 | ${env_common_esp8285.build_flags}
9 | ${rapidfire_vrx_backpack_common.build_flags}
10 | -D PIN_LED=16
11 | ; TX pin (GPIO1) has a pullup on some receivers
12 | ; This MUST be connected to CS or it wont work
13 | ; The RX and BOOT pins can be used for either DATA or CLK without issue
14 | -D PIN_MOSI=3 ;RX pad
15 | -D PIN_CLK=0 ;Boot pad
16 | -D PIN_CS=1 ;TX pad
17 |
18 | [env:Rapidfire_ESP_RX_Backpack_via_WIFI]
19 | extends = env:Rapidfire_ESP_RX_Backpack_via_UART
20 |
21 | [env:Rapidfire_ESP01F_Backpack_via_UART]
22 | extends = env_common_esp8285, rapidfire_vrx_backpack_common
23 | build_flags =
24 | ${env_common_esp8285.build_flags}
25 | ${rapidfire_vrx_backpack_common.build_flags}
26 | -D PIN_BUTTON=0
27 | -D PIN_LED=16
28 | -D PIN_MOSI=13
29 | -D PIN_CLK=14
30 | -D PIN_CS=15
31 |
32 | [env:Rapidfire_ESP01F_Backpack_via_WIFI]
33 | extends = env:Rapidfire_ESP01F_Backpack_via_UART
34 |
35 | [env:Rapidfire_HappyModel_EP82_VRX_Backpack_via_UART]
36 | extends = env:Rapidfire_ESP01F_Backpack_via_UART
37 |
38 | [env:Rapidfire_HappyModel_EP82_VRX_Backpack_via_WIFI]
39 | extends = env:Rapidfire_HappyModel_EP82_VRX_Backpack_via_UART
40 |
41 | [env:Rapidfire_ESP12F_Backpack_via_UART]
42 | extends = env_common_esp12e, rapidfire_vrx_backpack_common
43 | build_flags =
44 | ${env_common_esp12e.build_flags}
45 | ${rapidfire_vrx_backpack_common.build_flags}
46 | -D LED_INVERTED
47 | -D PIN_BUTTON=0
48 | -D PIN_LED=2
49 | -D PIN_MOSI=13
50 | -D PIN_CLK=14
51 | -D PIN_CS=12
52 |
53 | [env:Rapidfire_ESP12F_Backpack_via_WIFI]
54 | extends = env:Rapidfire_ESP12F_Backpack_via_UART
55 |
56 | [env:Rapidfire_HT_Backpack_via_UART]
57 | extends = env:Rapidfire_ESP01F_Backpack_via_UART
58 | build_flags =
59 | ${env:Rapidfire_ESP01F_Backpack_via_UART.build_flags}
60 | -D HAS_HEADTRACKING
61 | -D PIN_SDA=2
62 | -D PIN_SCL=4
63 | -D PIN_INT=9
64 |
65 | [env:Rapidfire_HT_Backpack_via_WIFI]
66 | extends = env:Rapidfire_HT_Backpack_via_UART
67 |
--------------------------------------------------------------------------------
/targets/rx5808.ini:
--------------------------------------------------------------------------------
1 |
2 | # ********************************
3 | # VRX backpack targets
4 | # ********************************
5 |
6 | [env:RX5808_ESP_RX_Backpack_via_UART]
7 | extends = env_common_esp8285, rx5808_vrx_backpack_common
8 | build_flags =
9 | ${env_common_esp8285.build_flags}
10 | ${rx5808_vrx_backpack_common.build_flags}
11 | -D PIN_LED=16
12 | -D PIN_MOSI=3 ;RX pad
13 | -D PIN_CLK=0 ;Boot pad
14 | -D PIN_CS=1 ;TX pad
15 |
16 | [env:RX5808_ESP_RX_Backpack_via_WIFI]
17 | extends = env:RX5808_ESP_RX_Backpack_via_UART
18 |
19 | [env:RX5808_ESP01F_Backpack_via_UART]
20 | extends = env_common_esp8285, rx5808_vrx_backpack_common
21 | build_flags =
22 | ${env_common_esp8285.build_flags}
23 | ${rx5808_vrx_backpack_common.build_flags}
24 | -D PIN_BUTTON=0
25 | -D PIN_LED=16
26 | -D PIN_MOSI=13
27 | -D PIN_CLK=14
28 | -D PIN_CS=15
29 |
30 | [env:RX5808_ESP01F_Backpack_via_WIFI]
31 | extends = env:RX5808_ESP01F_Backpack_via_UART
32 |
33 | [env:RX5808_HappyModel_EP82_VRX_Backpack_via_UART]
34 | extends = env:RX5808_ESP01F_Backpack_via_UART
35 |
36 | [env:RX5808_HappyModel_EP82_VRX_Backpack_via_WIFI]
37 | extends = env:RX5808_HappyModel_EP82_VRX_Backpack_via_UART
38 |
39 | [env:RX5808_ESP12F_Backpack_via_UART]
40 | extends = env_common_esp12e, rx5808_vrx_backpack_common
41 | build_flags =
42 | ${env_common_esp12e.build_flags}
43 | ${rx5808_vrx_backpack_common.build_flags}
44 | -D LED_INVERTED
45 | -D PIN_BUTTON=0
46 | -D PIN_LED=2
47 | -D PIN_MOSI=13
48 | -D PIN_CLK=14
49 | -D PIN_CS=12
50 |
51 | [env:RX5808_ESP12F_Backpack_via_WIFI]
52 | extends = env:RX5808_ESP12F_Backpack_via_UART
53 |
54 |
55 | [env:RX5808_Diversity_ESP01F_Backpack_via_UART]
56 | extends = env:RX5808_ESP01F_Backpack_via_UART
57 | build_flags =
58 | ${env:RX5808_ESP01F_Backpack_via_UART.build_flags}
59 | -D PIN_CS_2=5
60 |
61 | [env:RX5808_Diversity_ESP01F_Backpack_via_WIFI]
62 | extends = env:RX5808_Diversity_ESP01F_Backpack_via_UART
63 |
64 | [env:RX5808_HT_Backpack_via_UART]
65 | extends = env:RX5808_ESP01F_Backpack_via_UART
66 | build_flags =
67 | ${env:RX5808_ESP01F_Backpack_via_UART.build_flags}
68 | -D HAS_HEADTRACKING
69 | -D PIN_SDA=2
70 | -D PIN_SCL=4
71 | -D PIN_INT=12
72 |
73 | [env:RX5808_HT_Backpack_via_WIFI]
74 | extends = env:RX5808_HT_Backpack_via_UART
75 |
76 | [env:RX5808_Diversity_HT_Backpack_via_UART]
77 | extends = env:RX5808_HT_Backpack_via_UART
78 | build_flags =
79 | ${env:RX5808_HT_Backpack_via_UART.build_flags}
80 | -D PIN_CS_2=12
81 |
82 | [env:RX5808_Diversity_HT_Backpack_via_WIFI]
83 | extends = env:RX5808_Diversity_HT_Backpack_via_UART
84 |
--------------------------------------------------------------------------------
/targets/skyzone.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # VRX backpack targets
3 | # ********************************
4 |
5 | [env:Skyzone_SteadyView_ESP_RX_Backpack_via_UART]
6 | extends = env_common_esp8285, steadyview_vrx_backpack_common
7 | build_flags =
8 | ${env_common_esp8285.build_flags}
9 | ${steadyview_vrx_backpack_common.build_flags}
10 | -D PIN_LED=16
11 | -D PIN_MOSI=3 ;RX pad
12 | -D PIN_CLK=0 ;Boot pad
13 | -D PIN_CS=1 ;TX pad
14 |
15 | [env:Skyzone_SteadyView_ESP_RX_Backpack_via_WIFI]
16 | extends = env:Skyzone_SteadyView_ESP_RX_Backpack_via_UART
17 |
18 | [env:Skyzone_SteadyView_ESP01F_Backpack_via_UART]
19 | extends = env_common_esp8285, steadyview_vrx_backpack_common
20 | build_flags =
21 | ${env_common_esp8285.build_flags}
22 | ${steadyview_vrx_backpack_common.build_flags}
23 | -D PIN_BUTTON=0
24 | -D PIN_LED=16
25 | -D PIN_MOSI=13
26 | -D PIN_CLK=14
27 | -D PIN_CS=15
28 |
29 | [env:Skyzone_SteadyView_ESP01F_Backpack_via_WIFI]
30 | extends = env:Skyzone_SteadyView_ESP01F_Backpack_via_UART
31 |
32 | [env:Skyzone_SteadyView_HappyModel_EP82_VRX_Backpack_via_UART]
33 | extends = env:Skyzone_SteadyView_ESP01F_Backpack_via_UART
34 |
35 | [env:Skyzone_SteadyView_HappyModel_EP82_VRX_Backpack_via_WIFI]
36 | extends = env:Skyzone_SteadyView_HappyModel_EP82_VRX_Backpack_via_UART
37 |
38 | [env:Skyzone_Onboard_ESP32_Backpack_via_UART]
39 | extends = env_common_esp32, skyzone_msp_vrx_backpack_common
40 | monitor_speed = 115200
41 | build_flags =
42 | ${env_common_esp32.build_flags}
43 | ${skyzone_msp_vrx_backpack_common.build_flags}
44 | -D PIN_LED=4
45 |
46 | [env:Skyzone_Onboard_ESP32_Backpack_via_WIFI]
47 | extends = env:Skyzone_Onboard_ESP32_Backpack_via_UART
48 |
--------------------------------------------------------------------------------
/targets/timer.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # Timer backpack targets
3 | # ********************************
4 |
5 | [env:TIMER_ESP32_Backpack_via_UART]
6 | extends = env_common_esp32, timer_backpack_common
7 | build_flags =
8 | ${env_common_esp32.build_flags}
9 | ${timer_backpack_common.build_flags}
10 | -D PIN_BUTTON=0
11 | -D PIN_LED=4
12 |
13 | [env:TIMER_ESP32_Backpack_via_WIFI]
14 | extends = env:TIMER_ESP32_Backpack_via_UART
15 |
16 | [env:TIMER_ESP82_Backpack_via_UART]
17 | extends = env_common_esp8285, timer_backpack_common
18 | build_flags =
19 | ${env_common_esp8285.build_flags}
20 | ${timer_backpack_common.build_flags}
21 | -D PIN_BUTTON=0
22 | -D PIN_LED=16
23 |
24 | [env:TIMER_ESP82_Backpack_via_WIFI]
25 | extends = env:TIMER_ESP82_Backpack_via_UART
26 |
27 | [env:TIMER_ESP12F_Backpack_via_UART]
28 | extends = env_common_esp12e, timer_backpack_common
29 | build_flags =
30 | ${env_common_esp12e.build_flags}
31 | ${timer_backpack_common.build_flags}
32 | -D PIN_BUTTON=0
33 | -D PIN_LED=2
34 |
35 | [env:TIMER_ESP12F_Backpack_via_WIFI]
36 | extends = env:TIMER_ESP12F_Backpack_via_UART
37 |
38 | [env:TIMER_ESP32C3_Backpack_via_UART]
39 | extends = env_common_esp32c3, timer_backpack_common
40 | build_flags =
41 | ${env_common_esp32c3.build_flags}
42 | ${timer_backpack_common.build_flags}
43 | -D PIN_BUTTON=9
44 | -D PIN_LED=8
45 |
46 | [env:TIMER_ESP32C3_Backpack_via_WIFI]
47 | extends = env:TIMER_ESP32C3_Backpack_via_UART
48 |
49 | [env:TIMER_ESP32S3_Backpack_via_UART]
50 | extends = env_common_esp32s3, timer_backpack_common
51 | build_flags =
52 | ${env_common_esp32s3.build_flags}
53 | ${timer_backpack_common.build_flags}
54 | -D PIN_BUTTON=0
55 | -D PIN_LED=21
56 |
57 | [env:TIMER_ESP32S3_Backpack_via_WIFI]
58 | extends = env:TIMER_ESP32S3_Backpack_via_UART
59 |
60 | [env:NuclearHazard_Backpack_via_UART]
61 | extends = env_common_esp32, timer_backpack_common
62 | build_flags =
63 | ${env_common_esp32.build_flags}
64 | ${timer_backpack_common.build_flags}
65 | -D PIN_BUTTON=0
66 | -D PIN_LED=15
67 |
68 | [env:NuclearHazard_Backpack_via_WIFI]
69 | extends = env:NuclearHazard_Backpack_via_UART
--------------------------------------------------------------------------------
/targets/txbp_esp.ini:
--------------------------------------------------------------------------------
1 | # ********************************
2 | # Transmitter backpack targets
3 | # ********************************
4 |
5 | [env:ESP_TX_Backpack_via_UART]
6 | extends = env_common_esp8285, tx_backpack_common
7 | lib_deps =
8 | ${tx_backpack_common.lib_deps}
9 | ${common_env_data.mavlink_lib_dep}
10 | build_flags =
11 | ${env_common_esp8285.build_flags}
12 | ${tx_backpack_common.build_flags}
13 | -D MAVLINK_ENABLED=1
14 | -D PIN_BUTTON=0
15 | -D PIN_LED=16
16 | upload_resetmethod = nodemcu
17 |
18 | [env:ESP_TX_Backpack_via_ETX]
19 | extends = env:ESP_TX_Backpack_via_UART
20 | upload_speed = 460800
21 | upload_command = python "$PROJECT_DIR/python/external/esptool/esptool.py" --passthrough -b $UPLOAD_SPEED ${UPLOAD_PORT and "-p "+UPLOAD_PORT} -c esp8266 --before etx --after soft_reset write_flash 0x0000 "$SOURCE"
22 |
23 | [env:ESP_TX_Backpack_via_WIFI]
24 | extends = env:ESP_TX_Backpack_via_UART
25 |
26 | [env:ESP_TX_Backpack_via_PASSTHRU]
27 | extends = env:ESP_TX_Backpack_via_UART
28 | upload_speed = 230400
29 | upload_command = python "$PROJECT_DIR/python/external/esptool/esptool.py" --passthrough -b $UPLOAD_SPEED ${UPLOAD_PORT and "-p "+UPLOAD_PORT} -c esp8266 --before passthru --after hard_reset write_flash 0x0000 "$SOURCE"
30 |
31 |
32 | [env:ESP32C3_TX_Backpack_via_UART]
33 | extends = env_common_esp32c3, tx_backpack_common
34 | lib_deps =
35 | ${tx_backpack_common.lib_deps}
36 | ${common_env_data.mavlink_lib_dep}
37 | build_flags =
38 | ${env_common_esp32c3.build_flags}
39 | ${tx_backpack_common.build_flags}
40 | -D MAVLINK_ENABLED=1
41 | -D PIN_BUTTON=9
42 | -D PIN_LED=8
43 | upload_resetmethod = nodemcu
44 |
45 | [env:ESP32C3_TX_Backpack_via_ETX]
46 | extends = env:ESP32C3_TX_Backpack_via_UART
47 | upload_speed = 460800
48 | upload_command = python "$PROJECT_DIR/python/external/esptool/esptool.py" --passthrough -b $UPLOAD_SPEED ${UPLOAD_PORT and "-p "+UPLOAD_PORT} -c esp32c3 --before etx --after hard_reset write_flash 0x0000 "$BUILD_DIR/bootloader.bin" 0x8000 "$BUILD_DIR/partitions.bin" 0xe000 "$BUILD_DIR/boot_app0.bin" 0x10000 "$SOURCE"
49 |
50 | [env:ESP32C3_TX_Backpack_via_WIFI]
51 | extends = env:ESP32C3_TX_Backpack_via_UART
52 |
53 | [env:ESP32C3_TX_Backpack_via_PASSTHRU]
54 | extends = env:ESP32C3_TX_Backpack_via_UART
55 | upload_speed = 230400
56 | upload_command = python "$PROJECT_DIR/python/external/esptool/esptool.py" --passthrough -b $UPLOAD_SPEED ${UPLOAD_PORT and "-p "+UPLOAD_PORT} -c esp32c3 --before passthru --after hard_reset write_flash 0x0000 "$BUILD_DIR/bootloader.bin" 0x8000 "$BUILD_DIR/partitions.bin" 0xe000 "$BUILD_DIR/boot_app0.bin" 0x10000 "$SOURCE"
57 |
--------------------------------------------------------------------------------
/test/README:
--------------------------------------------------------------------------------
1 |
2 | This directory is intended for PlatformIO Unit Testing and project tests.
3 |
4 | Unit Testing is a software testing method by which individual units of
5 | source code, sets of one or more MCU program modules together with associated
6 | control data, usage procedures, and operating procedures, are tested to
7 | determine whether they are fit for use. Unit testing finds problems early
8 | in the development cycle.
9 |
10 | More information about PlatformIO Unit Testing:
11 | - https://docs.platformio.org/page/plus/unit-testing.html
12 |
--------------------------------------------------------------------------------
/user_defines.txt:
--------------------------------------------------------------------------------
1 | ### The Legal Stuff ###
2 |
3 | # The use and operation of this type of device may require a license and some countries may forbid its use entirely.
4 | # It is entirely up to the end user to ensure compliance with local regulations. No claim of regulatory compliance is made. In most cases a HAM license is required.
5 | # This is experimental software/hardware and there is no guarantee of stability or reliability. USE AT YOUR OWN RISK
6 |
7 | # HOW TO USE THIS FILE: https://www.expresslrs.org/1.0/software/user-defines/
8 | # SIMPLY SEARCH WHICH DEFINE YOU NEED HELP WITH
9 |
10 |
11 | ### BINDING PHRASE: ###
12 | # https://github.com/ExpressLRS/Backpack/wiki#binding
13 | #-DMY_BINDING_PHRASE="default ExpressLRS binding phrase"
14 |
15 | #-DHOME_WIFI_SSID=""
16 | #-DHOME_WIFI_PASSWORD=""
17 |
18 | # Turn on debug messages, if disabled then all debugging options (starting with DEBUG_) are disabled
19 | #-DDEBUG_LOG
20 | # Use DEBUG_LOG_VERBOSE instead (or both) to see verbose debug logging (spammy stuff)
21 | #-DDEBUG_LOG_VERBOSE
--------------------------------------------------------------------------------