├── .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 | 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 | 3 | 4 | 5 | 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 --------------------------------------------------------------------------------