[
  {
    "path": ".clang-format",
    "content": "BasedOnStyle: Google\nIndentWidth: 4\nColumnLimit: 120"
  },
  {
    "path": ".gitattributes",
    "content": "case/** filter=lfs diff=lfs merge=lfs -text\ncase/** linguist-detectable=false\n"
  },
  {
    "path": ".github/copilot-instructions.md",
    "content": "# MSRC – Copilot Instructions\n\n## Project Overview\n\nMSRC (Multi Sensor for RC) is a telemetry system for RC models running on the **RP2040** microcontroller. It reads data from sensors (ESC, GPS, barometer, analog) and transmits it to an RC receiver using one of ~19 telemetry protocols. A companion **Qt5 desktop app** (`msrc_gui/`) configures the board over USB CDC. A Lua script (`lua/MSRC.lua`) enables wireless config from OpenTX/EdgeTX transmitters via SmartPort.\n\n## Architecture\n\n```\nmain.c  →  protocol_task (priority 3-4)  →  set_config()  →  sensor tasks (priority 2)\n  ├── usb_task (priority 1)     GUI ↔ board binary protocol\n  ├── led_task (priority 1)     WS2812 on GPIO 16\n  └── dbg_task (priority 1)     Ring-buffer debug output\n```\n\n**Data flow:** Sensor tasks write to shared `float *` pointers (atomic on Cortex-M0+). Protocol tasks read these floats, format them per-protocol, and transmit to the receiver via UART/PIO. No mutexes are used for sensor data—relies on aligned 32-bit atomic writes.\n\n**UART notification model:** ISRs push bytes into FreeRTOS queues, then after a packet-gap timeout call `vTaskNotifyGiveIndexedFromISR(context.uart0_notify_task_handle, 1, ...)`. Tasks block on `ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY)`.\n\n## Key Directories\n\n| Path | Purpose |\n|------|---------|\n| `board/project/protocol/` | 18 receiver protocol implementations (smartport, crsf, ibus, sbus, etc.) |\n| `board/project/sensor/` | ~30 sensor drivers (ESC variants, GPS, baro, analog, fuel, GPIO) |\n| `board/project/pio/` | PIO programs (.pio + .c/.h): soft UART, Castle Link, I2C, WS2812 |\n| `board/project/` | Core firmware: main.c, config, UART, USB, LED, common utilities |\n| `include/shared.h` | **Shared between firmware AND GUI** — all enums (`rx_protocol_t`, `esc_protocol_t`) and `config_t` struct |\n| `msrc_gui/` | Qt5 desktop configuration app (MSRC Link) |\n| `lua/MSRC.lua` | OpenTX/EdgeTX Lua script for wireless config via SmartPort |\n\n## Protocol Communication Models\n\nProtocols follow two models:\n\n### Request/Response (SmartPort, IBUS, XBUS, Hitec, SBUS, SRXL, Multiplex, JetiEx, Sanwa, HOTT, JR DMSS)\nThe protocol task blocks on `ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY)` until the receiver sends a request. The task then parses the request, looks up the relevant sensor `float *` values, formats them per protocol, and sends a response.\n\n### Periodic Push (CRSF, GHST, FBUS, FPort)\nThe protocol task sends telemetry frames at a fixed interval using `vTaskDelay()`, reading current sensor floats each cycle.\n\n**SmartPort is special** — besides telemetry, it supports a maintenance/config mode for the Lua configuration script. Commands `0x30`/`0x31`/`0x32` get/set individual `config_t` fields by their SmartPort `data_id`.\n\n## Adding a New Sensor\n\nFollow the established pattern (see `board/project/sensor/esc_hw3.c` as reference):\n\n1. **Header** — Define a parameter struct with config fields and `float *` output pointers:\n   ```c\n   typedef struct my_sensor_parameters_t {\n       float alpha;\n       float *value;  // allocated by protocol layer\n   } my_sensor_parameters_t;\n   ```\n2. **Task function** — `void my_sensor_task(void *parameters)`:\n   - Copy parameter struct locally: `my_sensor_parameters_t parameter = *(my_sensor_parameters_t *)parameters;`\n   - Signal readiness: `xTaskNotifyGive(context.receiver_task_handle);`\n   - Initialize output: `*parameter.value = 0;`\n   - Loop: read hardware, apply `get_average()`, write to `*parameter.value`\n3. **Wire in protocol** — In each protocol's `set_config()`, allocate `malloc(sizeof(float))` for the output pointer and create the sensor task with `xTaskCreate`. Wait for task init: `ulTaskNotifyTake(pdTRUE, portMAX_DELAY)`.\n4. **CMake** — Add the `.c` file to `board/project/sensor/CMakeLists.txt`.\n5. **Stack size** — Define `STACK_MY_SENSOR` in `constants.h` as `(measured_min + STACK_EXTRA)`.\n\n## Adding a New Protocol\n\nFollow the pattern in `board/project/protocol/ibus.c`:\n\n1. **`set_config()`** — Read `config_t`, create sensor tasks based on enabled sensors, build protocol-specific sensor list.\n2. **Main task** — Init UART via `uart0_begin()`, call `set_config()`, enter loop (request/response or periodic push).\n3. **Format function** — Convert `float *` sensor values to protocol wire format (handle byte order, scaling, units).\n4. **Register in `main.c`** — Add `case RX_MY_PROTO:` to the switch, calling `xTaskCreate(my_proto_task, ...)`.\n5. **Update enums** — Add to `rx_protocol_t` in `include/shared.h` (**both** `#ifdef __cplusplus` and C blocks must be updated in sync).\n6. **Update GUI** — Add the protocol to `msrc_gui/mainwindow.cpp` combo box (`ui->cbReceiver->addItem(...)`) and handle protocol-specific widget visibility in `on_cbReceiver_currentTextChanged()`.\n\n## Spektrum Smart ESC/BAT Sensor\n\nThe Smart ESC driver (`board/project/sensor/smart_esc.c`) is the most complex sensor. It **emulates a Spektrum SRXL2 receiver** to communicate with a Spektrum Smart ESC/Battery over UART1 at 115200 baud.\n\n### How it works\n1. **PWM capture** — Uses PIO (`capture_edge`, pio0) to read throttle (GPIO 12) and reverse (GPIO 13) PWM signals from the actual receiver.\n2. **SRXL2 handshake** — On startup, performs SRXL2 handshake with the ESC (ID `0x40`), presenting itself as receiver ID `0x21`.\n3. **Control packets** — Every 10ms (`SRXL2_INTERVAL_MS`), sends `SRXL2_PACKET_TYPE_CONTROL` packets forwarding captured throttle/reverse channels. Requests telemetry every 10th packet (`reply_id = esc_id`).\n4. **Telemetry parsing** — Receives telemetry in XBUS format, parsing two device types:\n   - **`XBUS_ESC_ID`**: RPM, voltage, current, FET temp, BEC voltage/current, BEC temp\n   - **`XBUS_SMART_BAT` (`0x42`)**: Sub-types `0x00` (realtime: temp, current, consumption), `0x10`/`0x20`/`0x30` (individual cell voltages, up to 18), `0x80` (battery ID: cell count, cycles)\n\n### Parameter struct (extensive)\n```c\ntypedef struct smart_esc_parameters_t {\n    bool calc_consumption;\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temperature_fet, *temperature_bec, *voltage_bec, *current_bec;\n    float *temperature_bat, *current_bat, *consumption;\n    float *cell[18];\n    uint8_t *cells;\n    uint16_t *cycles;\n} smart_esc_parameters_t;\n```\n\n### Protocol wiring\nIn `set_config()`, Smart ESC is created at **priority 4** (higher than normal sensors) because it needs low-latency SRXL2 timing. All 18 cell pointers are individually `malloc`'d. All protocols support Smart ESC — the wiring pattern in `set_config()` allocates ~25 float pointers:\n```c\nif (config->esc_protocol == ESC_SMART) {\n    smart_esc_parameters_t parameter;\n    parameter.rpm = malloc(sizeof(float));\n    // ... allocate all pointers ...\n    for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n    parameter.cells = malloc(sizeof(uint8_t));\n    parameter.cycles = malloc(sizeof(uint16_t));\n    xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n    context.uart1_notify_task_handle = task_handle;\n    ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n}\n```\n\nConfig field: `config->smart_esc_calc_consumption` — if `true`, consumption is calculated from motor current via `get_consumption()` instead of using the Smart Battery's reported value.\n\n## Config System\n\n- `config_t` is defined in `include/shared.h` — shared across firmware, GUI, and Lua script.\n- Stored in **RP2040 flash** at offset 768KB (`CONFIG_FLASH_TARGET_OFFSET`). Read via XIP pointer, written with interrupts disabled (`save_and_disable_interrupts()`).\n- Supports migration from old flash offset (512KB → 768KB) in `config_read()`.\n- Each field has a SmartPort `data_id` comment (0x5101–0x5152) used by the Lua config script.\n- **When adding fields:** append before `spare` fields and decrement a spare. Bump `CONFIG_VERSION` in `config.h`. Update `config_forze_write()` defaults in `config.c`.\n- Default values via compile-time `#define`s at top of `config.c` (e.g. `#define RX_PROTOCOL RX_SMARTPORT`).\n- Defaults can be force-written holding GPIO 15 low at boot, or by `CONFIG_FORZE_WRITE`.\n\n## USB Protocol (Board ↔ GUI)\n\nBinary protocol over CDC USB serial. All messages start with header byte `0x30`:\n\n| Bytes | Direction | Description |\n|-------|-----------|-------------|\n| `0x30 0x30` + `config_t` bytes | GUI→Board | Write config to flash (board blinks LED, writes, requires reset) |\n| `0x30 0x31` | GUI→Board | Request current config |\n| `0x30 0x32` + `config_t` bytes | Board→GUI | Config response |\n| `0x30 0x33` | GUI→Board | Enable debug output (text stream over USB) |\n| `0x30 0x34` | GUI→Board | Disable debug output |\n| `0x30 0x35` | GUI→Board | Force write default config to flash |\n\nThe USB task (`board/project/usb.c`) polls every 1 second with `getchar_timeout_us(1000)`. Config is sent/received as raw `config_t` struct bytes — **both sides must agree on struct layout and `CONFIG_VERSION`**.\n\n## GUI Application (MSRC Link)\n\nQt5/C++ desktop app in `msrc_gui/`. Key files:\n\n| File | Purpose |\n|------|---------|\n| `mainwindow.cpp/.h` | Main application logic: serial comm, config UI, circuit diagram |\n| `mainwindow.ui` | Qt Designer UI layout |\n| `circuitdialog.cpp/.h` | Zoomable circuit diagram overlay dialog |\n| `circuitdialog.ui` | Circuit dialog layout |\n| `main.cpp` | App entry point |\n| `msrc_gui.pro` | qmake project file |\n| `res/` | PNG images for circuit diagram overlays (one per sensor/receiver type) |\n\n### Architecture\n- Uses `QSerialPort` at 115200/8N1 for USB CDC communication.\n- Port list is auto-refreshed every 1 second via `QTimer`.\n- On connect: disables debug, waits 2 seconds, requests config. Config arrives as `sizeof(config_t) + 2` bytes, validated by header `[0x30, 0x32]` and `config.version`.\n- Two main flows: `setUiFromConfig()` (config struct → UI widgets) and `getConfigFromUi()` (UI widgets → config struct). These must be updated whenever `config_t` changes.\n- **Circuit diagram**: `generateCircuit()` composites PNG overlays on a base RP2040 Zero pinout image depending on which sensors/receiver are enabled. Each sensor/receiver type has a dedicated PNG in `res/`.\n- **Debug mode**: when enabled, all serial data is displayed as text in the debug panel (`ptDebug`).\n- Config can be saved/loaded as `.cfg` binary files (raw `config_t` dump).\n- Protocol-specific widgets are shown/hidden dynamically in `on_cbReceiver_currentTextChanged()` — e.g. fuel meter only for SmartPort/JetiEx/XBUS/HOTT/FPort/FBUS; GPIO only for SmartPort/FPort/FBUS.\n- ESC combo maps directly to `esc_protocol_t` enum values (index + 1, since ESC_NONE = 0 means unchecked).\n\n### When modifying the GUI\n1. Update both `setUiFromConfig()` and `getConfigFromUi()` — they are the bidirectional mapping between `config_t` and UI.\n2. For new receiver protocols: add to `cbReceiver` combo with the enum value as `userData`, update visibility logic in `on_cbReceiver_currentTextChanged()`.\n3. For new sensors: add UI widgets in `mainwindow.ui`, update circuit diagram with a new PNG overlay.\n\n## GPIO Pinout\n\n| GPIO | Function |\n|------|----------|\n| 0 | UART0 TX → receiver RX |\n| 1 | UART0 RX ← receiver TX |\n| 2 | I2C1 SDA (receiver, with pull-up) |\n| 3 | I2C1 SCL (receiver, with pull-up) |\n| 4 | PWM capture / Castle PWM / UART1 TX (unused) |\n| 5 | UART1 RX ← ESC TX / Castle ESC / Serial monitor |\n| 6 | PIO UART RX ← GPS TX |\n| 7 | Clock stretch NPN switch (XBUS) |\n| 8 | I2C0 SDA (vario/sensors) |\n| 9 | I2C0 SCL (vario/sensors) |\n| 10 | PWM out |\n| 11 | Fuel meter PWM capture |\n| 12 | Smart ESC throttle PWM capture |\n| 13 | Smart ESC reverse PWM capture |\n| 14 | PIO UART TX → GPS RX |\n| 15 | Restore default config (pull low at boot) |\n| 16 | WS2812 LED |\n| 17–22 | Digital GPIO switches |\n| 26 | ADC0 — Voltage |\n| 27 | ADC1 — Current |\n| 28 | ADC2 — NTC temperature |\n| 29 | ADC3 — Airspeed |\n\nAssignments are in `board/project/constants.h` — **do not reuse pins** without checking this table.\n\n## Lua Configuration Script\n\n`lua/MSRC.lua` runs on OpenTX/EdgeTX transmitters for **wireless in-field configuration** via SmartPort telemetry. It reads/writes individual `config_t` fields by their `data_id` (0x5101–0x5152) using SmartPort maintenance mode (`0x30`/`0x31` frame commands). Pages: sensor ID, refresh rates, averaging, ESC config, GPS, vario, fuel meter, GPIO, analog sensors, gyro.\n\nWhen adding a `config_t` field addressable via Lua: assign a `data_id` comment in `shared.h`, handle it in SmartPort maintenance mode (`smartport.c`), and add the corresponding entry in `MSRC.lua`.\n\n## Build\n\n```sh\n# Firmware (requires Pico SDK, ARM toolchain)\ncd board && mkdir build && cd build\ncmake .. && make -j$(nproc)\n# Output: MSRC-RP2040.uf2\n\n# GUI (requires Qt5, including QtSerialPort)\ncd msrc_gui && qmake && make\n```\n\nVersion is derived from `git describe --tags`. The version string is injected as `PROJECT_VERSION` define in both firmware and GUI.\n\n### Debugging with SWD probe\nIn `constants.h`, there are commented-out lines to remap UART from uart0 to uart1 for probe debugging. In `board/project/CMakeLists.txt`, toggle `pico_enable_stdio_usb` / `pico_enable_stdio_uart` (1/0 ↔ 0/1).\n\n### Flashing\nHold BOOTSEL on the RP2040, connect USB, drag `MSRC-RP2040.uf2` to the mass storage device. Or use `picotool`.\n\n## Code Conventions\n\n- **C99** for firmware, **C++/Qt5** for GUI. No C++ in firmware code.\n- Global `context_t context` in `main.c` holds all FreeRTOS task/queue handles—never create additional globals for IPC.\n- Sensor averaging uses `get_average(alpha, prev, new)` (exponential moving average) from `common.c`. Alpha is stored in config as `float`, converted from user-facing \"averaging elements\" via `ALPHA(n) = 2.0 / (n + 1)`.\n- Debug output via `debug(...)` macro—guarded by runtime `context.debug` flag, not compile-time. Can be toggled via USB commands.\n- Timeout callbacks use `add_alarm_in_ms()` to zero sensor values on data loss.\n- `#ifdef SIM_SENSORS` / `#ifdef SIM_RX` inject fake data for testing without hardware.\n- PIO `.pio` files are compiled to headers via `pico_generate_pio_header()` in CMake.\n- Byte order: use `swap_16()` / `swap_32()` macros from `common.h` for protocol wire formats.\n- All structs used for wire protocols use `__attribute__((packed))`.\n\n## FreeRTOS Priorities\n\n| Priority | Tasks |\n|----------|-------|\n| 4 | SmartPort, FPort, FBUS, Smart ESC (latency-sensitive) |\n| 3 | All other protocol tasks, timer service |\n| 2 | All sensor tasks |\n| 1 | USB, LED, debug |\n\nTime slicing is **disabled** (`configUSE_TIME_SLICING 0`). Equal-priority tasks do not round-robin — each must yield explicitly. FreeRTOS tick rate is 500Hz (2ms). Task notification index 1 is used for UART data arrival (`configTASK_NOTIFICATION_ARRAY_ENTRIES = 3`).\n"
  },
  {
    "path": ".github/workflows/build_release.yaml",
    "content": "name: Build Release\n\non:\n  release:\n    types: [published]\n\nenv:\n  FIRMWARE_FILENAME: MSRC-RP2040_${{ github.ref_name }}.uf2\n  LINK_FILENAME: msrc_link_${{ github.ref_name }}\n\npermissions:\n  contents: write\n  pages: write\n  id-token: write\n  \njobs:\n  firmware:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        with:\n          submodules: 'true'\n\n      - name: Git tags\n        run: git fetch --prune --unshallow\n\n      - name: Build firmware\n        uses: samyarsadat/Pico-Build-Action@v1\n        with:\n          source_dir: \"board\"\n          output_dir: \"../build\"\n          cmake_args: \"-DCMAKE_BUILD_TYPE=Release\"\n      \n      - name: Set version\n        run: sudo mv build/project/MSRC-RP2040.uf2 build/project/MSRC-RP2040_${{ github.ref_name }}.uf2\n\n      - name: Release\n        uses: softprops/action-gh-release@v2\n        with:\n          files: build/project/MSRC-RP2040_${{ github.ref_name }}.uf2\n            \n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: build/project/MSRC-RP2040_${{ github.ref_name }}.uf2\n          github-token: ${{ secrets.TOKEN }}\n        \n  linux:\n    runs-on: ubuntu-22.04\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        \n      - name: Install QT\n        uses: jurplel/install-qt-action@v4\n        with:\n          version: '6.8.*'\n          modules: qtserialport\n\n      \n      - name: Git tags\n        run: git fetch --prune --unshallow\n\n      - name: Build\n        run: |\n          mkdir build\n          cd build\n          qmake ../msrc_gui\n          make\n          \n      - name: Install LinuxDeploy\n        run: |\n          cd msrc_gui\n          wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage\n          sudo chmod +x linuxdeployqt-continuous-x86_64.AppImage\n      \n      - name: Install fuse\n        run: sudo apt install libfuse2\n\n      - name: Create AppImage\n        run: |\n          cp build/msrc_gui msrc_gui/appdir\n          cd msrc_gui/appdir\n          ../linuxdeployqt-continuous-x86_64.AppImage msrc.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so\n          mv *.AppImage msrc_link_${{ github.ref_name }}.AppImage\n\n      - name: Release\n        uses: softprops/action-gh-release@v2\n        with:\n          files: msrc_gui/appdir/msrc_link_${{ github.ref_name }}.AppImage\n              \n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: msrc_gui/appdir/msrc_link_${{ github.ref_name }}.AppImage\n          github-token: ${{ secrets.TOKEN }}\n            \n  macos:\n    runs-on: macos-14\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        \n      - name: Install QT\n        uses: jurplel/install-qt-action@v4\n        with:\n          version: '6.8.*'\n          modules: qtserialport\n        \n      - name: Git tags\n        run: git fetch --prune --unshallow\n\n      - name: Build\n        run: |\n          mkdir build\n          cd build\n          qmake ../msrc_gui\n          make\n          mv msrc_gui.app msrc_link_${{ github.ref_name }}.app\n          macdeployqt msrc_link_${{ github.ref_name }}.app -dmg\n        \n      - name: Release\n        uses: softprops/action-gh-release@v2\n        with:\n          files: build/msrc_link_${{ github.ref_name }}.dmg\n                \n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: build/msrc_link_${{ github.ref_name }}.dmg\n          github-token: ${{ secrets.TOKEN }}\n\n  windows:\n    runs-on: windows-2022\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        \n      - name: Install qt static\n        uses: orestonce/install-qt-static@v0.4.3\n        with:\n          version: Qt6.5.3-Windows-x86_64-MinGW13.2.0-ucrt-staticFull-20240527\n\n      - name: Git tags\n        run: git fetch --prune --unshallow\n        \n      - name: Build  \n        run: |\n          mkdir build\n          cd build\n          qmake ../msrc_gui\n          make\n          mingw32-make release\n          mv release/msrc_gui.exe release/msrc_link_${{ github.ref_name }}.exe\n\n      - name: Release\n        uses: softprops/action-gh-release@v2\n        with:\n          files: build/release/msrc_link_${{ github.ref_name }}.exe\n          \n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: build/release/msrc_link_${{ github.ref_name }}.exe\n          github-token: ${{ secrets.TOKEN }}\n\n"
  },
  {
    "path": ".github/workflows/ci_firmware.yaml",
    "content": "# gh search commits --hash 272effd --repo dgatf/msrc\n\nname: CI - MSRC firmware\n\non:\n  workflow_dispatch: {}\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    permissions:\n        contents: read\n        id-token: write \n        attestations: write\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        with:\n          submodules: 'true'\n\n      - name: Git tags\n        run: git fetch --prune --unshallow\n\n      - name: Build firmware\n        uses: samyarsadat/Pico-Build-Action@v1\n        with:\n          source_dir: \"board\"\n          output_dir: \"../build\"\n          cmake_args: \"-DCMAKE_BUILD_TYPE=Release\"\n        \n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: build/project/MSRC-RP2040.uf2\n          github-token: ${{ secrets.TOKEN }}\n\n      #- name: Upload artifact\n      #  uses: actions/upload-artifact@v4\n      #  with:\n      #    overwrite: 'true'\n      #    name: MSRC-RP2040.uf2\n      #    path: build/project/MSRC-RP2040.uf2]]\n\n      - name: Upload to Google Drive\n        uses: logickoder/g-drive-upload@main\n        with:\n          credentials: ${{ secrets.GOOGLE_ID }}\n          filename: 'build/project/MSRC-RP2040.uf2'\n          folderId: ${{ secrets.FOLDER_ID }}\n          overwrite: 'true'\n          name: MSRC-RP2040.uf2\n"
  },
  {
    "path": ".github/workflows/ci_msrc_link.yaml",
    "content": "name: CI - MSRC Link\n\non:\n  workflow_dispatch: {}\n\njobs:\n  linux:\n    runs-on: ubuntu-22.04\n    permissions:\n        contents: read\n        id-token: write \n        attestations: write\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        \n      - name: Install QT\n        uses: jurplel/install-qt-action@v4\n        with:\n          version: '6.8.*'\n          modules: qtserialport\n\n      - name: Git tags\n        run: git fetch --prune --unshallow\n\n      - name: Build\n        run: |\n          mkdir build\n          cd build\n          qmake ../msrc_gui\n          make\n          \n      - name: Install LinuxDeploy\n        run: |\n          cd msrc_gui\n          wget https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage\n          sudo chmod +x linuxdeployqt-continuous-x86_64.AppImage\n      \n      - name: Install fuse\n        run: sudo apt install libfuse2\n\n      - name: Create AppImage\n        run: |\n          cp build/msrc_gui msrc_gui/appdir\n          cd msrc_gui/appdir\n          ../linuxdeployqt-continuous-x86_64.AppImage msrc.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so\n          mv *.AppImage msrc_gui.AppImage\n\n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: msrc_gui/appdir/msrc_gui.AppImage\n          github-token: ${{ secrets.TOKEN }}\n\n      - name: Upload to Google Drive\n        uses: logickoder/g-drive-upload@main\n        with:\n          credentials: ${{ secrets.GOOGLE_ID }}\n          filename: 'msrc_gui/appdir/msrc_gui.AppImage'\n          folderId: ${{ secrets.FOLDER_ID }}\n          overwrite: 'true'\n          name: msrc_link.AppImage\n\n  macos:\n    runs-on: macos-14\n    permissions:\n        contents: read\n        id-token: write \n        attestations: write\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        \n      - name: Install QT\n        uses: jurplel/install-qt-action@v4\n        with:\n          version: '6.8.*'\n          modules: qtserialport\n        \n      - name: Git tags\n        run: git fetch --prune --unshallow\n      \n      - name: Build\n        run: |\n          mkdir build\n          cd build\n          qmake ../msrc_gui\n          make\n          macdeployqt msrc_gui.app -dmg\n\n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: build/msrc_gui.dmg\n          github-token: ${{ secrets.TOKEN }}\n        \n      - name: Upload to Google Drive\n        uses: logickoder/g-drive-upload@main\n        with:\n          credentials: ${{ secrets.GOOGLE_ID }}\n          filename: 'build/msrc_gui.dmg'\n          folderId: ${{ secrets.FOLDER_ID }}\n          overwrite: 'true'\n          name: msrc_link.dmg\n\n  windows:\n    runs-on: windows-2022\n    permissions:\n        contents: read\n        id-token: write \n        attestations: write\n\n    steps:\n      - name: Get actions code\n        uses: actions/checkout@v4\n        \n      - name: install qt static\n        uses: orestonce/install-qt-static@v0.4.3\n        with:\n          version: Qt6.5.3-Windows-x86_64-MinGW13.2.0-ucrt-staticFull-20240527\n\n      - name: Git tags\n        run: git fetch --prune --unshallow\n        \n      - name: Build  \n        run: |\n          mkdir build\n          cd build\n          qmake ../msrc_gui\n          make\n          mingw32-make release\n\n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: build/release/msrc_gui.exe\n          github-token: ${{ secrets.TOKEN }}\n\n      - name: Upload to Google Drive\n        uses: logickoder/g-drive-upload@main\n        with:\n          credentials: ${{ secrets.GOOGLE_ID }}\n          filename: 'build/release/msrc_gui.exe'\n          folderId: ${{ secrets.FOLDER_ID }}\n          overwrite: 'true'\n          name: msrc_link.exe"
  },
  {
    "path": ".github/workflows/dev_firmware.yaml",
    "content": "name: Dev Release (Firmware)\n\non:\n  workflow_dispatch: {}\n  push:\n    branches:\n      - master\n    paths:\n      - board/**\n\npermissions:\n  contents: write\n  id-token: write\n  attestations: write\n\nconcurrency:\n  group: dev-release-firmware\n  cancel-in-progress: true\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: true\n          fetch-depth: 0\n\n      - name: Build firmware\n        uses: samyarsadat/Pico-Build-Action@v1\n        with:\n          source_dir: \"board\"\n          output_dir: \"../build\"\n          cmake_args: \"-DCMAKE_BUILD_TYPE=Release\"\n\n      - name: Prepare artifact\n        run: |\n          mkdir -p dist\n          cp build/project/MSRC-RP2040.uf2 dist/MSRC_firmware.uf2\n\n      - name: Generate attestation\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: dist/MSRC_firmware.uf2\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Write release body\n        run: |\n          cat > release_body.md << EOF\n          Development (auto)\n\n          Firmware:\n          - commit: \"${{ github.event.head_commit.message }}\"\n          - commit-url: \"https://github.com/${{ github.repository }}/commit/${{ github.sha }}\"\n\n          EOF\n\n      - name: Publish firmware to Development release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: dev-latest\n          name: Development (auto)\n          prerelease: true\n          allowUpdates: true\n          commit: ${{ github.sha }}\n          artifacts: dist/MSRC_firmware.uf2\n          artifactContentType: application/octet-stream\n          replacesArtifacts: true\n          generateReleaseNotes: false\n          bodyFile: release_body.md"
  },
  {
    "path": ".github/workflows/dev_msrc_link.yaml",
    "content": "name: Dev Release - MSRC Link\n\non:\n  workflow_dispatch: {}\n  push:\n    branches:\n      - master\n    paths:\n      - msrc_gui/**\n\npermissions:\n  contents: write\n  id-token: write\n  attestations: write\n\nconcurrency:\n  group: dev-release-link\n  cancel-in-progress: true\n\njobs:\n  linux:\n    runs-on: ubuntu-22.04\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Install Qt\n        uses: jurplel/install-qt-action@v4\n        with:\n          version: '6.8.*'\n          modules: qtserialport\n\n      - name: Fetch tags\n        run: git fetch --force --tags\n\n      - name: Build\n        run: |\n          mkdir -p build\n          cd build\n          qmake ../msrc_gui\n          make\n\n      - name: Install linuxdeployqt\n        run: |\n          cd msrc_gui\n          wget -q https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage\n          chmod +x linuxdeployqt-continuous-x86_64.AppImage\n\n      - name: Install fuse\n        run: sudo apt-get update && sudo apt-get install -y libfuse2\n\n      - name: Create AppImage\n        run: |\n          cp build/msrc_gui msrc_gui/appdir\n          cd msrc_gui/appdir\n          ../linuxdeployqt-continuous-x86_64.AppImage msrc.desktop -appimage -extra-plugins=iconengines,platformthemes/libqgtk3.so\n          mv ./*.AppImage ./MSRC_Link_Linux.AppImage\n\n      - name: Collect artifact\n        run: |\n          mkdir -p dist\n          cp msrc_gui/appdir/MSRC_Link_Linux.AppImage dist/MSRC_Link_Linux.AppImage\n\n      - name: Upload artifact (Linux)\n        uses: actions/upload-artifact@v4\n        with:\n          name: msrc-link-linux\n          path: dist/MSRC_Link_Linux.AppImage\n          if-no-files-found: error\n\n  macos:\n    runs-on: macos-14\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Install Qt\n        uses: jurplel/install-qt-action@v4\n        with:\n          version: '6.8.*'\n          modules: qtserialport\n\n      - name: Fetch tags\n        run: git fetch --force --tags\n\n      - name: Build\n        run: |\n          mkdir -p build\n          cd build\n          qmake ../msrc_gui\n          make\n          macdeployqt msrc_gui.app -dmg\n\n      - name: Collect artifact\n        run: |\n          mkdir -p dist\n          cp build/msrc_gui.dmg dist/MSRC_Link_macOS.dmg\n\n      - name: Upload artifact (macOS)\n        uses: actions/upload-artifact@v4\n        with:\n          name: msrc-link-macos\n          path: dist/MSRC_Link_macOS.dmg\n          if-no-files-found: error\n\n  windows:\n    runs-on: windows-2022\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Install Qt (static)\n        uses: orestonce/install-qt-static@v0.4.3\n        with:\n          version: Qt6.5.3-Windows-x86_64-MinGW13.2.0-ucrt-staticFull-20240527\n\n      - name: Fetch tags\n        run: git fetch --force --tags\n\n      - name: Build\n        shell: bash\n        run: |\n          mkdir -p build\n          cd build\n          qmake ../msrc_gui\n          make\n          mingw32-make release\n\n      - name: Collect Windows executable\n        shell: bash\n        run: |\n          mkdir -p dist\n          cp build/release/msrc_gui.exe dist/MSRC_Link_Windows.exe\n\n      - name: Upload artifact (Windows)\n        uses: actions/upload-artifact@v4\n        with:\n          name: msrc-link-windows\n          path: dist/MSRC_Link_Windows.exe\n          if-no-files-found: error\n\n  publish:\n    runs-on: ubuntu-latest\n    needs: [linux, macos, windows]\n\n    steps:\n      - name: Download artifacts\n        uses: actions/download-artifact@v4\n        with:\n          path: dist\n\n      - name: Normalize dist layout\n        run: |\n          set -euo pipefail\n          find dist -type f -name \"MSRC_Link_Linux.AppImage\" -exec cp -f {} dist/ \\;\n          find dist -type f -name \"MSRC_Link_macOS.dmg\" -exec cp -f {} dist/ \\;\n          find dist -type f -name \"MSRC_Link_Windows.exe\" -exec cp -f {} dist/ \\;\n\n          test -f dist/MSRC_Link_Linux.AppImage\n          test -f dist/MSRC_Link_macOS.dmg\n          test -f dist/MSRC_Link_Windows.exe\n\n      - name: Generate attestation (Linux)\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: dist/MSRC_Link_Linux.AppImage\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Generate attestation (macOS)\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: dist/MSRC_Link_macOS.dmg\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Generate attestation (Windows)\n        uses: actions/attest-build-provenance@v3\n        with:\n          subject-path: dist/MSRC_Link_Windows.exe\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Write release body\n        run: |\n          cat > release_body.md << EOF\n          Development (auto)\n\n          MSRC Link:\n          - commit: \"${{ github.event.head_commit.message }}\"\n          - commit-url: \"https://github.com/${{ github.repository }}/commit/${{ github.sha }}\"\n\n          EOF\n\n      - name: Publish MSRC Link to Development release\n        uses: ncipollo/release-action@v1\n        with:\n          tag: dev-latest\n          name: Development (auto)\n          prerelease: true\n          allowUpdates: true\n          commit: ${{ github.sha }}\n          artifacts: dist/MSRC_Link_Linux.AppImage,dist/MSRC_Link_macOS.dmg,dist/MSRC_Link_Windows.exe\n          replacesArtifacts: true\n          generateReleaseNotes: false"
  },
  {
    "path": ".gitignore",
    "content": "misc\nbuild/\nbuild_*/\nmsrc_gui/msrc_gui.pro.user*\n\n### macOS ###\n.DS_Store\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"board/freertos/FreeRTOS-Kernel\"]\n\tpath = board/freertos/FreeRTOS-Kernel\n\turl = https://github.com/FreeRTOS/FreeRTOS-Kernel.git\n"
  },
  {
    "path": ".vscode/c_cpp_properties.json",
    "content": "{\n    \"configurations\": [\n        {\n            \"name\": \"Default\",\n            \"includePath\": [\n                \"${workspaceFolder}/board/project/**\",\n                \"${workspaceFolder}/build/**\",\n                \"${env:PICO_SDK_PATH}/src/**\",\n                \"${workspaceFolder}/board/freertos/FreeRTOS-Kernel/include/\",\n                \"${workspaceFolder}/board/freertos/FreeRTOS-Kernel/portable/ThirdParty/GCC/RP2040/include/**\",\n                \"${workspaceFolder}/board/freertos/\"\n            ],\n            \"intelliSenseMode\": \"gcc-arm\",\n            \"compilerPath\": \"/usr/bin/arm-none-eabi-gcc\",\n            \"cStandard\": \"c17\",\n            \"cppStandard\": \"c++14\"\n        }\n    ],\n    \"version\": 4\n}"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Pico Debug\",\n            \"cwd\": \"${workspaceRoot}\",\n            \"executable\": \"${command:cmake.launchTargetPath}\",\n            \"request\": \"launch\",\n            \"type\": \"cortex-debug\",\n            \"servertype\": \"openocd\",\n            //\"gdbPath\" : \"${env:HOME}/.platformio/packages/toolchain-gccarmnoneeabi/bin/arm-none-eabi-gdb\",\n            \"gdbPath\" : \"${env:HOME}/src/gcc-arm-none-eabi-10/bin/arm-none-eabi-gdb\",\n            \"device\": \"RP2040\",\n            \"configFiles\": [\n                \"interface/picoprobe.cfg\",\n                \"target/rp2040.cfg\"\n            ],\n            \"svdFile\": \"${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd\",\n            \"runToMain\": true,\n            // Work around for stopping at main on restart\n            \"postRestartCommands\": [\n                \"break main\",\n                \"continue\"\n            ],\n            \"searchDir\": [\"${env:HOME}/src/pico/openocd/tcl\"],\n            //\"showDevDebugOutput\": true,\n        }\n    ]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n    // These settings tweaks to the cmake plugin will ensure\n    // that you debug using cortex-debug instead of trying to launch\n    // a Pico binary on the host\n    \"C_Cpp.autoAddFileAssociations\" : false,\n    \"C_Cpp.default.configurationProvider\": \"ms-vscode.cmake-tools\",\n    \"cmake.options.statusBarVisibility\" : \"visible\",\n    \"cmake.buildBeforeRun\": true,\n    \"cmake.sourceDirectory\": \"${workspaceFolder}/board\",\n    \"cortex-debug.openocdPath\": \"${env:HOME}/src/pico/openocd/src/openocd\",\n    \"cortex-debug.armToolchainPath\": \"${env:HOME}/.platformio/packages/toolchain-gccarmnoneeabi/bin\",\n    \"cortex-debug.variableUseNaturalFormat\": true,\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "GNU GENERAL PUBLIC LICENSE\n\t Version 3, 29 June 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\nEveryone is permitted to copy and distribute verbatim copies\nof this license document, but changing it is not allowed.\n\n\t\t\t\tPreamble\n\nThe GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\nWhen we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nTo protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\nFor example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\nDevelopers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\nFor the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\nSome devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\nFinally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n\t TERMS AND CONDITIONS\n\n0. Definitions.\n\n\"This License\" refers to version 3 of the GNU General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\na) The work must carry prominent notices stating that you modified\nit, and giving a relevant date.\n\nb) The work must carry prominent notices stating that it is\nreleased under this License and any conditions added under section\n7.  This requirement modifies the requirement in section 4 to\n\"keep intact all notices\".\n\nc) You must license the entire work, as a whole, under this\nLicense to anyone who comes into possession of a copy.  This\nLicense will therefore apply, along with any applicable section 7\nadditional terms, to the whole of the work, and all its parts,\nregardless of how they are packaged.  This License gives no\npermission to license the work in any other way, but it does not\ninvalidate such permission if you have separately received it.\n\nd) If the work has interactive user interfaces, each must display\nAppropriate Legal Notices; however, if the Program has interactive\ninterfaces that do not display Appropriate Legal Notices, your\nwork need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n6. Conveying Non-Source Forms.\n\nYou may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\na) Convey the object code in, or embodied in, a physical product\n(including a physical distribution medium), accompanied by the\nCorresponding Source fixed on a durable physical medium\ncustomarily used for software interchange.\n\nb) Convey the object code in, or embodied in, a physical product\n(including a physical distribution medium), accompanied by a\nwritten offer, valid for at least three years and valid for as\nlong as you offer spare parts or customer support for that product\nmodel, to give anyone who possesses the object code either (1) a\ncopy of the Corresponding Source for all the software in the\nproduct that is covered by this License, on a durable physical\nmedium customarily used for software interchange, for a price no\nmore than your reasonable cost of physically performing this\nconveying of source, or (2) access to copy the\nCorresponding Source from a network server at no charge.\n\nc) Convey individual copies of the object code with a copy of the\nwritten offer to provide the Corresponding Source.  This\nalternative is allowed only occasionally and noncommercially, and\nonly if you received the object code with such an offer, in accord\nwith subsection 6b.\n\nd) Convey the object code by offering access from a designated\nplace (gratis or for a charge), and offer equivalent access to the\nCorresponding Source in the same way through the same place at no\nfurther charge.  You need not require recipients to copy the\nCorresponding Source along with the object code.  If the place to\ncopy the object code is a network server, the Corresponding Source\nmay be on a different server (operated by you or a third party)\nthat supports equivalent copying facilities, provided you maintain\nclear directions next to the object code saying where to find the\nCorresponding Source.  Regardless of what server hosts the\nCorresponding Source, you remain obligated to ensure that it is\navailable for as long as needed to satisfy these requirements.\n\ne) Convey the object code using peer-to-peer transmission, provided\nyou inform other peers where the object code and Corresponding\nSource of the work are being offered to the general public at no\ncharge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\na) Disclaiming warranty or limiting liability differently from the\nterms of sections 15 and 16 of this License; or\n\nb) Requiring preservation of specified reasonable legal notices or\nauthor attributions in that material or in the Appropriate Legal\nNotices displayed by works containing it; or\n\nc) Prohibiting misrepresentation of the origin of that material, or\nrequiring that modified versions of such material be marked in\nreasonable ways as different from the original version; or\n\nd) Limiting the use for publicity purposes of names of licensors or\nauthors of the material; or\n\ne) Declining to grant rights under trademark law for use of some\ntrade names, trademarks, or service marks; or\n\nf) Requiring indemnification of licensors and authors of that\nmaterial by anyone who conveys the material (or modified versions of\nit) with contractual assumptions of liability to the recipient, for\nany liability that these contractual assumptions directly impose on\nthose licensors and authors.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n13. Use with the GNU Affero General Public License.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n END OF TERMS AND CONDITIONS\n\nHow to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n<one line to give the program's name and a brief idea of what it does.>\nCopyright (C) <year>  <name of author>\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n<program>  Copyright (C) <year>  <name of author>\nThis program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\nThis is free software, and you are welcome to redistribute it\nunder certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\nYou should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\nThe GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# MSRC – Multi Sensor Telemetry for RC (RP2040)\n\n[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate/?business=9GKNBVGHKXFGW&no_recurring=0&currency_code=USD)\n\n**MSRC** is a lightweight, low-cost, and highly flexible telemetry system for RC models, based on the RP2040 microcontroller.\n\nIt allows you to collect data from multiple sensors and transmit it to your receiver using a wide range of telemetry protocols. MSRC is a DIY alternative to commercial solutions, offering significantly reduced weight and cost while maintaining high flexibility and expandability.\n\n---\n\n## Features\n\n- Multi-sensor telemetry system based on RP2040\n- Support for multiple receiver telemetry protocols\n- Support for various ESC telemetry protocols\n- Modular design (only use the sensors you need)\n- Fully configurable via PC using MSRC Link\n- Lightweight and cost-effective\n\n---\n\n## Supported Receiver Protocols\n\n- FrSky SmartPort\n- FrSky D\n- FrSky FPort\n- FrSky FBUS\n- Spektrum XBUS\n- Spektrum SRXL / SRXL2\n- FlySky IBUS\n- Futaba SBUS2\n- Multiplex Sensor Bus (MSB)\n- Jeti EX Bus\n- Jeti EX Sensor\n- Hitec\n- CRSF\n- Sanwa\n- HoTT\n- JR DMSS\n- GHST\n\n---\n\n## Supported Sensors\n\n### ESC Telemetry\n\n- Hobbywing V3 / V4 / V5 / FlyFun\n- Kontronik\n- KISS (APD F, BLHeli32, Summit X)\n- APD HV / UHV\n- OMP M4\n- ZTW\n- Castle Link\n- PWM / phase sensor ESCs\n- Spektrum Smart ESC & Battery\n\n### GPS\n\n- Serial GPS (NMEA)\n\n### Vario (I2C)\n\n- BMP180\n- BMP280\n- MS5611\n\n### Analog Sensors\n\n- Voltage\n- Temperature\n- Current\n- Airspeed (MPXV7002)\n\n### Fuel\n\n- Fuel flow meter (PWM pulses)\n- Fuel tank pressure (XGZP68XXD)\n\n### Other\n\n- Up to 6 digital switches (GPIO)\n\n---\n\n## Getting Started\n\n1. Download the latest firmware from the **Releases** section  \n2. Flash the firmware to your RP2040 board  \n3. Connect your sensors and receiver  \n4. Configure the device using **MSRC Link**  \n5. Check telemetry on your transmitter  \n\n👉 For detailed instructions, see the [Wiki](https://github.com/dgatf/msrc/wiki)\n\n---\n\n## Documentation\n\nFull documentation is available in the wiki:\n\n👉 https://github.com/dgatf/msrc/wiki\n\n---\n\n## Support\n\n- Report issues: https://github.com/dgatf/msrc/issues  \n- Discussion forums:\n  - https://www.rcgroups.com/forums/showthread.php?4088405-DIY-MSRC-Multi-sensor-telemetry-for-several-Rx-protocols#post48830585  \n  - https://www.helifreak.com/showthread.php?t=908371  \n  - https://www.openrcforums.com/forum/viewtopic.php?f=84&t=11911  \n\nIf you would like to add support for a new receiver protocol or sensor, feel free to open an issue.\n\n---\n\n## Donate\n\nIf you find this project useful, consider supporting its development:\n\n👉 https://www.paypal.com/donate/?business=9GKNBVGHKXFGW&no_recurring=0&currency_code=USD\n"
  },
  {
    "path": "board/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.17.0)\r\n\r\ninclude(pico_sdk_import.cmake)\r\n\r\nproject(MSRC-RP2040)\r\n\r\ninclude_directories(../include)\r\n\r\nexecute_process(\r\n    COMMAND git config --global --add safe.directory /github/workspace\r\n    COMMAND git describe --tags\r\n    OUTPUT_VARIABLE PROJECT_VERSION\r\n    OUTPUT_STRIP_TRAILING_WHITESPACE\r\n)\r\nmessage(STATUS \"MSRC ${PROJECT_VERSION}\") \r\nadd_compile_definitions(PROJECT_VERSION=\"${PROJECT_VERSION}\")\r\n\r\npico_sdk_init()\r\n\r\nadd_subdirectory(freertos)\r\nadd_subdirectory(project)\r\n"
  },
  {
    "path": "board/freertos/CMakeLists.txt",
    "content": "set(PICO_SDK_FREERTOS_SOURCE FreeRTOS-Kernel)\r\n\r\nadd_library(freertos\r\n    ${PICO_SDK_FREERTOS_SOURCE}/event_groups.c\r\n    ${PICO_SDK_FREERTOS_SOURCE}/list.c\r\n    ${PICO_SDK_FREERTOS_SOURCE}/queue.c\r\n    ${PICO_SDK_FREERTOS_SOURCE}/stream_buffer.c\r\n    ${PICO_SDK_FREERTOS_SOURCE}/tasks.c\r\n    ${PICO_SDK_FREERTOS_SOURCE}/timers.c\r\n    ${PICO_SDK_FREERTOS_SOURCE}/portable/MemMang/heap_3.c\r\n    ${PICO_SDK_FREERTOS_SOURCE}/portable/GCC/ARM_CM0/port.c\r\n)\r\n\r\ntarget_include_directories(freertos PUBLIC\r\n    .\r\n    ${PICO_SDK_FREERTOS_SOURCE}/include\r\n    ${PICO_SDK_FREERTOS_SOURCE}/portable/GCC/ARM_CM0\r\n)\r\n"
  },
  {
    "path": "board/freertos/FreeRTOSConfig.h",
    "content": "#ifndef FREERTOS_CONFIG_H\r\n#define FREERTOS_CONFIG_H\r\n\r\n/* Use Pico SDK ISR handlers */\r\n#define vPortSVCHandler         isr_svcall\r\n#define xPortPendSVHandler      isr_pendsv\r\n#define xPortSysTickHandler     isr_systick\r\n\r\n#define configUSE_PREEMPTION                    1\r\n#define configUSE_PORT_OPTIMISED_TASK_SELECTION 0\r\n#define configUSE_TICKLESS_IDLE                 0\r\n#define configCPU_CLOCK_HZ                      133000000\r\n#define configTICK_RATE_HZ                      500\r\n#define configMAX_PRIORITIES                    5\r\n#define configMINIMAL_STACK_SIZE                128\r\n#define configMAX_TASK_NAME_LEN                 16\r\n#define configUSE_16_BIT_TICKS                  0\r\n#define configIDLE_SHOULD_YIELD                 1\r\n#define configUSE_TASK_NOTIFICATIONS            1\r\n#define configTASK_NOTIFICATION_ARRAY_ENTRIES   3\r\n#define configUSE_MUTEXES                       1\r\n#define configUSE_RECURSIVE_MUTEXES             0\r\n#define configUSE_COUNTING_SEMAPHORES           0\r\n#define configQUEUE_REGISTRY_SIZE               10\r\n#define configUSE_QUEUE_SETS                    0\r\n#define configUSE_TIME_SLICING                  0\r\n#define configUSE_NEWLIB_REENTRANT              0\r\n#define configENABLE_BACKWARD_COMPATIBILITY     0\r\n#define configNUM_THREAD_LOCAL_STORAGE_POINTERS 5\r\n#define configSTACK_DEPTH_TYPE                  uint16_t\r\n#define configMESSAGE_BUFFER_LENGTH_TYPE        size_t\r\n\r\n/* Memory allocation related definitions. */\r\n#define configSUPPORT_STATIC_ALLOCATION         0\r\n#define configSUPPORT_DYNAMIC_ALLOCATION        1\r\n#define configAPPLICATION_ALLOCATED_HEAP        1\r\n\r\n/* Hook function related definitions. */\r\n#define configUSE_IDLE_HOOK                     0\r\n#define configUSE_TICK_HOOK                     0\r\n#define configCHECK_FOR_STACK_OVERFLOW          0\r\n#define configUSE_MALLOC_FAILED_HOOK            0\r\n#define configUSE_DAEMON_TASK_STARTUP_HOOK      0\r\n\r\n/* Run time and task stats gathering related definitions. */\r\n#define configGENERATE_RUN_TIME_STATS           0\r\n#define configUSE_TRACE_FACILITY                0\r\n#define configUSE_STATS_FORMATTING_FUNCTIONS    0\r\n\r\n/* Co-routine related definitions. */\r\n#define configUSE_CO_ROUTINES                   0\r\n#define configMAX_CO_ROUTINE_PRIORITIES         1\r\n\r\n/* Software timer related definitions. */\r\n#define configUSE_TIMERS                        1\r\n#define configTIMER_TASK_PRIORITY               3\r\n#define configTIMER_QUEUE_LENGTH                10\r\n#define configTIMER_TASK_STACK_DEPTH            configMINIMAL_STACK_SIZE\r\n\r\n/* Define to trap errors during development. */\r\n#define configASSERT( x )\r\n\r\n/* Optional functions - most linkers will remove unused functions anyway. */\r\n#define INCLUDE_vTaskPrioritySet                1\r\n#define INCLUDE_uxTaskPriorityGet               1\r\n#define INCLUDE_vTaskDelete                     1\r\n#define INCLUDE_vTaskSuspend                    1\r\n#define INCLUDE_xResumeFromISR                  1\r\n#define INCLUDE_vTaskDelayUntil                 1\r\n#define INCLUDE_vTaskDelay                      1\r\n#define INCLUDE_xTaskGetSchedulerState          1\r\n#define INCLUDE_xTaskGetCurrentTaskHandle       1\r\n#define INCLUDE_uxTaskGetStackHighWaterMark     1\r\n#define INCLUDE_xTaskGetIdleTaskHandle          0\r\n#define INCLUDE_eTaskGetState                   0\r\n#define INCLUDE_xEventGroupSetBitFromISR        1\r\n#define INCLUDE_xTimerPendFunctionCall          0\r\n#define INCLUDE_xTaskAbortDelay                 0\r\n#define INCLUDE_xTaskGetHandle                  0\r\n#define INCLUDE_xTaskResumeFromISR              1\r\n\r\n/* A header file that defines trace macro can be included here. */\r\n\r\n#endif /* FREERTOS_CONFIG_H */\r\n"
  },
  {
    "path": "board/pico_sdk_import.cmake",
    "content": "# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake\n\n# This can be dropped into an external project to help locate this SDK\n# It should be include()ed prior to project()\n\n# todo document\n\nif (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))\n    set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})\n    message(\"Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')\")\nendif ()\n\nif (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))\n    set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})\n    message(\"Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')\")\nendif ()\n\nif (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))\n    set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})\n    message(\"Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')\")\nendif ()\n\nset(PICO_SDK_PATH \"${PICO_SDK_PATH}\" CACHE PATH \"Path to the PICO SDK\")\nset(PICO_SDK_FETCH_FROM_GIT \"${PICO_SDK_FETCH_FROM_GIT}\" CACHE BOOL \"Set to ON to fetch copy of PICO SDK from git if not otherwise locatable\")\nset(PICO_SDK_FETCH_FROM_GIT_PATH \"${PICO_SDK_FETCH_FROM_GIT_PATH}\" CACHE FILEPATH \"location to download SDK\")\n\nif (NOT PICO_SDK_PATH)\n    if (PICO_SDK_FETCH_FROM_GIT)\n        include(FetchContent)\n        set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})\n        if (PICO_SDK_FETCH_FROM_GIT_PATH)\n            get_filename_component(FETCHCONTENT_BASE_DIR \"${PICO_SDK_FETCH_FROM_GIT_PATH}\" REALPATH BASE_DIR \"${CMAKE_SOURCE_DIR}\")\n        endif ()\n        FetchContent_Declare(\n                pico_sdk\n                GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk\n                GIT_TAG master\n        )\n        if (NOT pico_sdk)\n            message(\"Downloading PICO SDK\")\n            FetchContent_Populate(pico_sdk)\n            set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})\n        endif ()\n        set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})\n    else ()\n        message(FATAL_ERROR\n                \"PICO SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git.\"\n                )\n    endif ()\nendif ()\n\nget_filename_component(PICO_SDK_PATH \"${PICO_SDK_PATH}\" REALPATH BASE_DIR \"${CMAKE_BINARY_DIR}\")\nif (NOT EXISTS ${PICO_SDK_PATH})\n    message(FATAL_ERROR \"Directory '${PICO_SDK_PATH}' not found\")\nendif ()\n\nset(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)\nif (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})\n    message(FATAL_ERROR \"Directory '${PICO_SDK_PATH}' does not appear to contain the PICO SDK\")\nendif ()\n\nset(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH \"Path to the PICO SDK\" FORCE)\n\ninclude(${PICO_SDK_INIT_CMAKE_FILE})\n"
  },
  {
    "path": "board/project/CMakeLists.txt",
    "content": "include_directories(. pio protocol sensor)\r\n\r\nadd_executable(${PROJECT_NAME})\r\n\r\nadd_subdirectory(protocol)\r\nadd_subdirectory(sensor)\r\nadd_subdirectory(pio)\r\n\r\ntarget_sources(${PROJECT_NAME} PRIVATE\r\n    main.c\r\n    uart.c\r\n    common.c\r\n    led.c\r\n    config.c\r\n    sim_rx.c\r\n    uart_pio.c\r\n    usb.c\r\n    serial_monitor.c\r\n    dbg_rb.c\r\n    dbg_task.c\r\n)\r\n\r\ntarget_link_libraries(${PROJECT_NAME}\r\n    pico_stdlib\r\n    freertos\r\n    hardware_uart\r\n    hardware_irq\r\n    hardware_pwm\r\n    hardware_clocks\r\n    hardware_adc\r\n    hardware_pio\r\n    hardware_i2c\r\n    hardware_flash\r\n    pico_i2c_slave\r\n)\r\n\r\npico_add_extra_outputs(${PROJECT_NAME})\r\n\r\npico_enable_stdio_usb(${PROJECT_NAME} 1) # 1 normal, 0 probe debug \r\npico_enable_stdio_uart(${PROJECT_NAME} 0) # 0 normal, 1 probe debug\r\n"
  },
  {
    "path": "board/project/common.c",
    "content": "\n#include \"common.h\"\n\n#include <math.h>\n#include <stdlib.h>\n\n#include \"hardware/adc.h\"\n#include \"hardware/i2c.h\"\n#include \"pico/stdlib.h\"\n\nfloat get_average(float alpha, float prev_value, float new_value) {\n    if (isnan(new_value)) return prev_value;\n    if (alpha > 1)\n        return new_value;\n    else\n        return (1 - alpha) * prev_value + alpha * new_value;\n}\n\nfloat get_consumption(float current, uint16_t current_max, uint32_t *timestamp) {\n    if (!*timestamp) {\n        *timestamp = time_us_32();\n        return 0;\n    }\n    uint32_t now = time_us_32();                    // us\n    uint32_t interval = (now - *timestamp) / 1000;  // ms\n    float mah = current * interval / 3600.0;\n    *timestamp = now;\n    if (interval > 2000 || (current_max && (mah > current_max * interval / 3600.0))) return 0;\n    return mah;\n}\n\nfloat voltage_read(uint8_t adc_num) {\n    adc_select_input(adc_num);\n    return adc_read() * BOARD_VCC / ADC_RESOLUTION;\n}\n\nfloat get_altitude(float pressure, float temperature, float pressure_initial) {\n    if (pressure_initial == 0) return 0;\n    return (temperature + 273.15) * (1 - pow(pressure / pressure_initial, 1 / 5.256)) / 0.0065;\n}\n\nvoid get_vspeed(float *vspeed, float altitude, uint interval) {\n    static float altitude_prev = 0;\n    static uint ts = 0;\n    uint now = time_us_32();\n    if (now - ts >= interval * 1000) {\n        *vspeed = (altitude - altitude_prev) / ((now - ts) / 1000000.0);\n        altitude_prev = altitude;\n        ts = time_us_32();\n    }\n}\n\nvoid get_vspeed_gps(float *vspeed, float altitude, uint interval) {\n    static float altitude_prev = 0;\n    static uint ts = 0;\n    uint now = time_us_32();\n    if (now - ts >= interval * 1000) {\n        *vspeed = (altitude - altitude_prev) / ((now - ts) / 1000000.0);\n        altitude_prev = altitude;\n        ts = time_us_32();\n    }\n}\n\n/*\nvoid circular_buffer_add(buffer_node_t *node, void *item)\n{\n    buffer_node_t *new_node = malloc(sizeof(buffer_node_t));\n    new_node->item = item;\n    if (node == NULL)\n    {\n        node = new_node;\n        new_node->next = new_node;\n    }\n    else\n    {\n        new_node->next = node->next;\n        node->next = new_node;\n        node = new_node;\n    }\n}\n\nvoid *circular_buffer_current()\n{\n    if (node)\n        return node->item;\n    return NULL;\n}\n\nvoid circular_buffer_next(buffer_node_t *node)\n{\n    if (node)\n        node = node->next;\n}\n\nvoid circular_buffer_empty(buffer_node_t *node)\n{\n    if (node)\n    {\n        buffer_node_t *first_node, *next_node;\n        first_node = node;\n        do\n        {\n            next_node = node->next;\n            free(node->item);\n            free(node);\n            node = next_node;\n        } while (next_node != first_node);\n        node = NULL;\n    }\n}\n*/"
  },
  {
    "path": "board/project/common.h",
    "content": "#ifndef COMMON_H\n#define COMMON_H\n\n#include <FreeRTOS.h>\n#include <queue.h>\n#include <task.h>\n\n#include \"constants.h\"\n#include \"pico/stdlib.h\"\n#include \"pico/types.h\"\n#include \"shared.h\"\n#include \"dbg_task.h\"\n\n/*\n   Debug\n   Disconnect Vcc from the RC model to the board before connecting USB\n   Telemetry may not work properly in debug mode\n*/\n\n#define MSRC_DEBUG 0  // 0 = no debug, 1 = debug level 1, 2 = debug level 2\n\n// #define SIM_RX\n// #define SIM_SENSORS\n\n// #define SIM_SMARTPORT_SEND_CONFIG_LUA\n// #define SIM_SMARTPORT_RECEIVE_CONFIG_LUA\n// #define SIM_SMARTPORT_SEND_SENSOR_ID\n// #define SIM_SMARTPORT_RECEIVE_SENSOR_ID\n\n#define PI 3.14159265358979323846\n\n#define swap_16(value) (((value & 0xFF) << 8) | (value & 0xFF00) >> 8)\n#define swap_24(value) (((value & 0xFF) << 16) | (value & 0xFF00) | (value & 0xFF0000) >> 16)\n#define swap_32(value) \\\n    (((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0xFF0000) >> 8) | ((value & 0xFF000000) >> 24))\n\n#define debug(...) \\\n    do { \\\n        if (context.debug) dbg_write(__VA_ARGS__); \\\n    } while (0)\n\n#define debug_buffer(buffer, length, format) \\\n    do { \\\n        if (context.debug) \\\n            dbg_write_buffer((const uint8_t *)(buffer), (length), (format)); \\\n    } while (0)\n\n#define debug2(...) \\\n    if (context.debug == 2) printf(__VA_ARGS__)\n#define debug_buffer2(buffer, length, format) \\\n    if (context.debug == 2)                   \\\n        for (int i = 0; i < (length); i++) printf((format), (buffer)[i])\n\ntypedef struct context_t {\n    TaskHandle_t pwm_out_task_handle, uart0_notify_task_handle, uart1_notify_task_handle, uart_pio_notify_task_handle,\n        receiver_task_handle, led_task_handle, usb_task_handle;\n    QueueHandle_t uart0_queue_handle, uart1_queue_handle, uart_rx_pio_queue_handle, uart_tx_pio_queue_handle, tasks_queue_handle,\n        sensors_queue_handle;\n    alarm_pool_t *uart_alarm_pool;\n    uint8_t debug, led_cycles;\n    uint16_t led_cycle_duration;\n} context_t;\n\nfloat get_average(float alpha, float prev_value, float new_value);\nfloat get_consumption(float current, uint16_t current_max, uint32_t *timestamp);\nfloat voltage_read(uint8_t adc_num);\nfloat get_altitude(float pressure, float temperature, float P0);\nvoid get_vspeed(float *vspeed, float altitude, uint interval);\nvoid get_vspeed_gps(float *vspeed, float altitude, uint interval);\n\n#endif"
  },
  {
    "path": "board/project/config.c",
    "content": "#include \"config.h\"\n\n#include <hardware/flash.h>\n#include <hardware/sync.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"pico/stdlib.h\"\n\n#define CONFIG_OLD_FLASH_TARGET_OFFSET (512 * 1024)  // 512kB after start of flash for program (uf2) + 512kB for config\n\n/* Receiver protocol */\n#define RX_PROTOCOL \\\n    RX_SMARTPORT  // RX_SMARTPORT, RX_XBUS, RX_SRXL, RX_FRSKY_D, RX_IBUS, RX_SBUS, RX_MULTIPLEX, RX_JETIEX, RX_HITEC\n/* Sensors */\n#define ESC_PROTOCOL ESC_NONE  // ESC_NONE, ESC_HW3, ESC_HW4, ESC_PWM, ESC_CASTLE, ESC_KONTRONIK, ESC_APD_F, ESC_APD_HV\n#define ENABLE_GPS false\n#define GPS_BAUD_RATE 9600\n#define ENABLE_ANALOG_VOLTAGE false\n#define ENABLE_ANALOG_CURRENT false\n#define ENABLE_ANALOG_NTC false\n#define ENABLE_ANALOG_AIRSPEED false\n#define I2C1_TYPE I2C_NONE  // I2C_NONE, I2C_BMP280, I2C_MS5611, I2C_BMP180\n#define I2C1_ADDRESS 0x77   // 0x76 (BMP280), 0x77 (BMP180, MS5611)\n/* Pwm out */\n#define ENABLE_PWM_OUT false\n/* Refresh rate in ms (only Frsky) */\n#define REFRESH_RATE_RPM 1000\n#define REFRESH_RATE_VOLTAGE 1000\n#define REFRESH_RATE_CURRENT 1000\n#define REFRESH_RATE_TEMPERATURE 1000\n#define REFRESH_RATE_GPS 1000\n#define REFRESH_RATE_CONSUMPTION 1000\n#define REFRESH_RATE_VARIO 1000\n#define REFRESH_RATE_AIRSPEED 1000\n#define REFRESH_RATE_DEFAULT 1000\n/* Averaging elements (1 = no averaging) */\n#define AVERAGING_ELEMENTS_RPM 1\n#define AVERAGING_ELEMENTS_VOLTAGE 1\n#define AVERAGING_ELEMENTS_CURRENT 1\n#define AVERAGING_ELEMENTS_TEMPERATURE 1\n#define AVERAGING_ELEMENTS_VARIO 1\n#define AVERAGING_ELEMENTS_AIRSPEED 1\n\n/* Analog rate*/\n#define ANALOG_RATE 10\n/* Analog voltage sensors */\n#define ANALOG_VOLTAGE_MULTIPLIER 7.8  // 7.8 if using 68k and 10k as proposed in the docs\n/* Analog current sensor */\n#define ANALOG_CURRENT_MULTIPLIER 1  // current_multiplier = 1000 / sensitivity(mV/A)\n/* Zero current output voltage offset or quiescent voltage (voltage offset when I = 0, Viout)\n - All hall effect core sensors (Amploc) are bidirectional. Viout=Vs/2\n - Hall effect coreless sensors (IC) (ACS758) can be bidirectional or directional. Recommended to use directional for\n higher sensitivity. Viout defined in datasheet\n - If CURRENT_AUTO_OFFSET is true, then after 5 seconds, voltage read is set as offset. It is recommended to use auto\n offset\n*/\n#define ANALOG_CURRENT_OFFSET 0\n#define ANALOG_CURRENT_AUTO_OFFSET true\n/* Analog airspeed */\n#define AIRSPEED_OFFSET 0  // mV\n#define AIRSPEED_VCC 5     // V\n\n/* RPM multipliers (optional, this may be done in transmitter*/\n#define RPM_PAIR_OF_POLES 1\n#define RPM_PINION_TEETH 1  // For helis\n#define RPM_MAIN_TEETH 1    // For helis\n\n/* Vario */\n#define VARIO_AUTO_OFFSET false\n#define BMP280_FILTER 3  // BMP Filter. Higher filter = lower noise: 1 - low, 2 - medium, 3 - high\n\n/* Only smartport and opentx */\n#define SMARTPORT_SENSOR_ID 15    // Sensor Id\n#define SMARTPORT_DATA_ID 0x5100  // DataId (sensor type)\n\n/* Ibus */\n#define IBUS_GPS_ALTERNATIVE_COORDINATES false\n\n/* XBus */\n#define XBUS_CLOCK_STRECH_SWITCH false\n#define XBUS_ALTERNATIVE_VOLT_TEMP false\n\n/* Jeti Ex */\n#define JETI_GPS_SPEED_UNITS_KMH true\n\n/* Serial monitor */\n#define SERIAL_MONITOR_BAUDRATE 19200\n#define SERIAL_MONITOR_STOPBITS 1\n#define SERIAL_MONITOR_PARITY 0\n#define SERIAL_MONITOR_TIMEOUT_MS 1\n#define SERIAL_MONITOR_INVERTED false\n#define SERIAL_MONITOR_GPIO 5    // GPIO 1 or 5\n#define SERIAL_MONITOR_FORMAT 0  // 0 = hex, 1 = text\n\n/* Add init delay for FlyFun ESC. Enable if the ESC doesn't arm */\n#define ENABLE_ESC_INIT_DELAY false\n#define ESC_INIT_DELAY_DURATION 10000\n\n/* HW V4/V5 parameters */\n#define ESC_HW4_MANUAL_OFFSET 1\n#define ESC_HW4_CURRENT_OFFSET 0\n#define ESC_HW4_CURRENT_THRESHOLD 10\n#define ESC_HW4_VOLTAGE_MULTIPLIER 0.0088\n#define ESC_HW4_CURRENT_MULTIPLIER 0.3\n#define ESC_HW4_CURRENT_MAX 250\n\n/* Fuel flow sensor */\n#define ENABLE_FUEL_FLOW false\n#define FUEL_FLOW_ML_PER_MINUTE 0.01\n\n/* Fuel pressure */\n#define XGZP68XXD_K 64\n\n/* GPIO */\n#define GPIO_INTERVAL_MS 1000\n#define GPIO_MASK 0\n\n/* GPS */\n#define GPS_RATE 1\n\n/* Gyro */\n#define GYRO_ADDRESS 0x68    // 0x68 or 0x69\n#define ACCEL_SENSITIVITY 0  // 0 = 2g, 1 = 4g, 2 = 8g, 3 = 16g\n#define GYRO_SENSITIVITY 0   // 0 = 250 dps, 1 = 500 dps, 2 = 1000 dps, 3 = 2000 dps\n#define GYRO_WEIGHTING 96    // 0 - 100%\n#define GYRO_FILTER 0        // 0 = no filter, 1 = 184Hz, 2 = 92Hz, 3 = 41Hz, 4 = 20Hz, 5 = 10Hz, 6 = 5Hz\n\n/* INA3221 */\n#define INA3221_FILTER 1  // 1 - 1024\n#define INA3221_CELLS 3   // 1 to 12\n\n#define SENSOR_ID_SRXL2 1 // Sensor Id for SRXL2 protocol (if used). 1-15\n\n#define NTC_OFFSET 0\n\nconfig_t *config_read() {\n    uint16_t *version = (uint16_t *)(XIP_BASE + CONFIG_FLASH_TARGET_OFFSET);\n    if (*version != CONFIG_VERSION) {\n        uint16_t *version_old = (uint16_t *)(XIP_BASE + CONFIG_OLD_FLASH_TARGET_OFFSET);\n        if (*version_old == CONFIG_VERSION) {\n            // migrate old config\n            uint8_t flash[FLASH_PAGE_SIZE] = {0};\n            memcpy(flash, (uint8_t *)version_old, sizeof(config_t));\n            uint32_t ints = save_and_disable_interrupts();\n            flash_range_erase(CONFIG_FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE);\n            flash_range_program(CONFIG_FLASH_TARGET_OFFSET, flash, FLASH_PAGE_SIZE);\n            restore_interrupts(ints);\n        } else {\n            // write default config\n            config_forze_write();\n        }\n    }\n    return (config_t *)(XIP_BASE + CONFIG_FLASH_TARGET_OFFSET);\n}\n\nvoid config_write(config_t *config) {\n    uint8_t flash[FLASH_PAGE_SIZE] = {0};\n    memcpy(flash, (uint8_t *)config, sizeof(config_t));\n    uint32_t ints = save_and_disable_interrupts();\n    flash_range_erase(CONFIG_FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE);\n    flash_range_program(CONFIG_FLASH_TARGET_OFFSET, flash, FLASH_PAGE_SIZE);\n    restore_interrupts(ints);\n}\n\nvoid config_get(config_t *config) {\n    memcpy(config, (config_t *)(XIP_BASE + CONFIG_FLASH_TARGET_OFFSET), sizeof(config_t));\n    config->debug = MSRC_DEBUG;\n}\n\nvoid config_forze_write() {\n    config_t config = {0};\n    config.version = CONFIG_VERSION;\n    config.rx_protocol = RX_PROTOCOL;\n    config.esc_protocol = ESC_PROTOCOL;\n    config.enable_gps = ENABLE_GPS;\n    config.gps_baudrate = GPS_BAUD_RATE;\n    config.enable_analog_voltage = ENABLE_ANALOG_VOLTAGE;\n    config.enable_analog_current = ENABLE_ANALOG_CURRENT;\n    config.enable_analog_ntc = ENABLE_ANALOG_NTC;\n    config.enable_analog_airspeed = ENABLE_ANALOG_AIRSPEED;\n    config.i2c_module = I2C1_TYPE;\n    config.alpha_rpm = ALPHA(AVERAGING_ELEMENTS_RPM);\n    config.alpha_voltage = ALPHA(AVERAGING_ELEMENTS_VOLTAGE);\n    config.alpha_current = ALPHA(AVERAGING_ELEMENTS_CURRENT);\n    config.alpha_temperature = ALPHA(AVERAGING_ELEMENTS_TEMPERATURE);\n    config.alpha_vario = ALPHA(AVERAGING_ELEMENTS_VARIO);\n    config.alpha_airspeed = ALPHA(AVERAGING_ELEMENTS_AIRSPEED);\n    config.refresh_rate_rpm = REFRESH_RATE_RPM;\n    config.refresh_rate_voltage = REFRESH_RATE_VOLTAGE;\n    config.refresh_rate_current = REFRESH_RATE_CURRENT;\n    config.refresh_rate_temperature = REFRESH_RATE_TEMPERATURE;\n    config.refresh_rate_gps = REFRESH_RATE_GPS;\n    config.refresh_rate_consumption = REFRESH_RATE_CONSUMPTION;\n    config.refresh_rate_vario = REFRESH_RATE_VARIO;\n    config.refresh_rate_airspeed = REFRESH_RATE_AIRSPEED;\n    config.refresh_rate_default = REFRESH_RATE_DEFAULT;\n    config.analog_voltage_multiplier = ANALOG_VOLTAGE_MULTIPLIER;\n    config.analog_current_multiplier = ANALOG_CURRENT_MULTIPLIER;\n    config.analog_current_offset = ANALOG_CURRENT_OFFSET;\n    config.analog_current_autoffset = ANALOG_CURRENT_AUTO_OFFSET;\n    config.pairOfPoles = RPM_PAIR_OF_POLES;\n    config.mainTeeth = RPM_MAIN_TEETH;\n    config.pinionTeeth = RPM_PINION_TEETH;\n    config.rpm_multiplier = RPM_PINION_TEETH / (1.0 * RPM_MAIN_TEETH * RPM_PAIR_OF_POLES);\n    config.bmp280_filter = BMP280_FILTER;\n    config.enable_pwm_out = ENABLE_PWM_OUT;\n    config.smartport_sensor_id = SMARTPORT_SENSOR_ID;\n    config.smartport_data_id = SMARTPORT_DATA_ID;\n    config.vario_auto_offset = VARIO_AUTO_OFFSET;\n    config.xbus_clock_stretch = XBUS_CLOCK_STRECH_SWITCH;\n    config.jeti_gps_speed_units_kmh = JETI_GPS_SPEED_UNITS_KMH;\n    config.enable_esc_hw4_init_delay = ENABLE_ESC_INIT_DELAY;\n    config.esc_hw4_init_delay_duration = ESC_INIT_DELAY_DURATION;\n    config.esc_hw4_current_thresold = ESC_HW4_CURRENT_THRESHOLD;\n    config.esc_hw4_current_max = ESC_HW4_CURRENT_MAX;\n    config.esc_hw4_voltage_multiplier = ESC_HW4_VOLTAGE_MULTIPLIER;\n    config.esc_hw4_current_multiplier = ESC_HW4_CURRENT_MULTIPLIER;\n    config.ibus_alternative_coordinates = IBUS_GPS_ALTERNATIVE_COORDINATES;\n    config.debug = MSRC_DEBUG;\n    config.esc_hw4_is_manual_offset = ESC_HW4_MANUAL_OFFSET;\n    config.esc_hw4_offset = ESC_HW4_CURRENT_OFFSET;\n    config.xbus_use_alternative_volt_temp = XBUS_ALTERNATIVE_VOLT_TEMP;\n    config.serial_monitor_baudrate = SERIAL_MONITOR_BAUDRATE;\n    config.serial_monitor_stop_bits = SERIAL_MONITOR_STOPBITS;\n    config.serial_monitor_parity = SERIAL_MONITOR_PARITY;\n    config.serial_monitor_timeout_ms = SERIAL_MONITOR_TIMEOUT_MS;\n    config.serial_monitor_inverted = SERIAL_MONITOR_INVERTED;\n    config.airspeed_offset = AIRSPEED_OFFSET;\n    config.airspeed_vcc = AIRSPEED_VCC * 100;\n    config.fuel_flow_ml_per_pulse = FUEL_FLOW_ML_PER_MINUTE;\n    config.enable_fuel_flow = ENABLE_FUEL_FLOW;\n    config.enable_fuel_pressure = false;\n    config.xgzp68xxd_k = XGZP68XXD_K;\n    config.serial_monitor_format = SERIAL_MONITOR_FORMAT;\n    config.serial_monitor_gpio = SERIAL_MONITOR_GPIO;\n    config.gps_rate = GPS_RATE;\n    config.gpio_interval = GPIO_INTERVAL_MS;\n    config.gpio_mask = GPIO_MASK;\n    config.sbus_battery_slot = true;\n    config.fport_inverted = false;\n    config.esc_hw4_auto_detect = true;\n    config.enable_gyro = false;\n    config.mpu6050_acc_scale = ACCEL_SENSITIVITY;\n    config.mpu6050_gyro_scale = GYRO_SENSITIVITY;\n    config.mpu6050_gyro_weighting = GYRO_WEIGHTING;\n    //config.gyro_rate = GYRO_RATE;\n    config.ina3221_filter = INA3221_FILTER;\n    config.lipo_cells = INA3221_CELLS;\n    config.enable_lipo = false;\n    config.sensor_id_srxl2 = SENSOR_ID_SRXL2;\n    config.ntc_offset = NTC_OFFSET;\n    config_write(&config);\n}"
  },
  {
    "path": "board/project/config.h",
    "content": "#ifndef CONFIG_H\n#define CONFIG_H\n\n#include \"common.h\"\n\n#define CONFIG_FORZE_WRITE false\n#define CONFIG_VERSION 2\n#define CONFIG_FLASH_TARGET_OFFSET (768 * 1024)  // 768kB after start of flash. Program (uf2) max size 768kB\n\nextern context_t context;\n\nconfig_t *config_read();\nvoid config_write(config_t *config);\nvoid config_forze_write();\nvoid config_get(config_t *config);\n\n#endif"
  },
  {
    "path": "board/project/constants.h",
    "content": "#ifndef CONSTANTS_H\n#define CONSTANTS_H\n\n/* Pins */\n#define UART0_TX_GPIO 0       // receiver rx\n#define UART0_RX_GPIO 1       // receiver tx\n#define I2C1_SDA_GPIO 2       // receiver sda (with pull up)\n#define I2C1_SCL_GPIO 3       // receiver scl (with pull up)\n#define UART1_TX_GPIO 4       // not used\n#define UART1_RX_GPIO 5       // esc tx\n#define PWM_CAPTURE_GPIO 4    // pwm (rpm)\n#define CASTLE_PWM_GPIO 4     // receiver throttle signal\n#define CASTLE_ESC_GPIO 5     // esc throttle signal\n#define UART_TX_PIO_GPIO 14   // gps rx\n#define UART_RX_PIO_GPIO 6    // gps tx\n#define CLOCK_STRETCH_GPIO 7  // npn switch\n#define I2C0_SDA_GPIO 8       // i2c module (vario)\n#define I2C0_SCL_GPIO 9       // i2c module (vario)\n#define PWM_OUT_GPIO 10       // pwm out\n#define FUELMETER_CAPTURE_GPIO 11   // fuel meter (pwm in)\n#define ADC0_GPIO 26          // Voltage\n#define ADC1_GPIO 27          // Current\n#define ADC2_GPIO 28          // NTC\n#define ADC3_GPIO 29          // Airspeed\n#define SMART_ESC_PWM_GPIO 12 // receiver throttle signal\n#define RESTORE_GPIO 15\n\n/* UARTS */\n#define UART_RECEIVER uart0\n#define UART_RECEIVER_RX UART0_RX_GPIO\n#define UART_RECEIVER_TX UART0_TX_GPIO\n#define UART_ESC uart1\n#define UART_ESC_RX UART1_RX_GPIO\n#define UART_ESC_TX UART1_TX_GPIO\n\n// set receiver to uart1 when debugging with probe\n// #define UART_RECEIVER uart1\n// #define UART_RECEIVER_RX UART1_RX_GPIO\n// #define UART_RECEIVER_TX UART1_TX_GPIO\n\n/* ADC */\n#define ANALOG_SENSOR_INTERVAL_MS 500\n#define BOARD_VCC 3.3\n#define ADC_RESOLUTION 4096\n\n/* Stack */\n#define STACK_EXTRA 100\n\n#define STACK_RX_IBUS (200 + STACK_EXTRA)\n#define STACK_RX_FRSKY_D (654 + STACK_EXTRA)\n#define STACK_RX_MULTIPLEX (182 + STACK_EXTRA)\n#define STACK_RX_SMARTPORT (190 + STACK_EXTRA)\n#define STACK_RX_JETIEX (240 + STACK_EXTRA)\n#define STACK_RX_SBUS (220 + STACK_EXTRA)\n#define STACK_RX_HITEC (200 + STACK_EXTRA)\n#define STACK_RX_XBUS (200 + STACK_EXTRA)\n#define STACK_RX_SRXL (200 + STACK_EXTRA)\n#define STACK_RX_SRXL2 (300 + STACK_EXTRA)\n#define STACK_SERIAL_MONITOR (250 + STACK_EXTRA)\n#define STACK_RX_CRSF (292 + STACK_EXTRA)\n#define STACK_RX_HOTT (450 + STACK_EXTRA)\n#define STACK_RX_SANWA (200 + STACK_EXTRA)\n#define STACK_RX_JR_PROPO (300 + STACK_EXTRA)\n#define STACK_RX_FPORT (190 + STACK_EXTRA)\n#define STACK_RX_FBUS (190 + STACK_EXTRA)\n#define STACK_RX_GHST (300 + STACK_EXTRA)\n#define STACK_RX_JETIEX_SENSOR (240 + STACK_EXTRA)\n\n#define STACK_SIM_RX (160 + STACK_EXTRA)\n\n#define STACK_SEND_SBUS_PACKET (700 + STACK_EXTRA)\n\n#define STACK_SENSOR_FRSKY_D (164 + STACK_EXTRA)\n#define STACK_SENSOR_FRSKY_D_CELL (158 + STACK_EXTRA)\n#define STACK_SENSOR_SMARTPORT (180 + STACK_EXTRA)\n#define STACK_SENSOR_SMARTPORT_DOUBLE (166 + STACK_EXTRA)\n#define STACK_SENSOR_SMARTPORT_COORDINATE (164 + STACK_EXTRA)\n#define STACK_SENSOR_SMARTPORT_DATETIME (164 + STACK_EXTRA)\n#define STACK_SENSOR_SMARTPORT_CELL (166 + STACK_EXTRA)\n#define STACK_SMARTPORT_PACKET_TASK (160 + STACK_EXTRA)\n#define STACK_SMARTPORT_SENSOR_VOID_TASK (166 + STACK_EXTRA)\n\n#define STACK_ESC_HW3 (168 + STACK_EXTRA)\n#define STACK_ESC_HW4 (250 + STACK_EXTRA)\n#define STACK_ESC_HW5 (300 + STACK_EXTRA)\n#define STACK_ESC_PWM (160 + STACK_EXTRA)\n#define STACK_ESC_CASTLE (500 + STACK_EXTRA)\n#define STACK_ESC_KONTRONIK (212 + STACK_EXTRA)\n#define STACK_ESC_APD_F (194 + STACK_EXTRA)\n#define STACK_ESC_APD_HV (194 + STACK_EXTRA)\n#define STACK_SMART_ESC (232 + STACK_EXTRA)\n#define STACK_ESC_OMP_M4 (192 + STACK_EXTRA)\n#define STACK_ESC_ZTW (202 + STACK_EXTRA)\n#define STACK_ESC_OPENYGE (250 + STACK_EXTRA)\n#define STACK_GPS (322 + STACK_EXTRA)\n\n#define STACK_VOLTAGE (156 + STACK_EXTRA)\n#define STACK_CURRENT (166 + STACK_EXTRA)\n#define STACK_NTC (160 + STACK_EXTRA)\n#define STACK_AIRSPEED (184 + STACK_EXTRA)\n#define STACK_FUEL_METER (200 + STACK_EXTRA)\n#define STACK_FUEL_PRESSURE (200 + STACK_EXTRA)\n#define STACK_GPIO (150 + STACK_EXTRA)\n\n#define STACK_MPU6050 (252 + STACK_EXTRA)\n#define STACK_INA3221 (200 + STACK_EXTRA)\n\n#define STACK_BMP280 (228 + STACK_EXTRA)\n#define STACK_MS5611 (172 + STACK_EXTRA)\n#define STACK_BMP180 (174 + STACK_EXTRA)\n\n#define STACK_VSPEED (152 + STACK_EXTRA)\n#define STACK_DISTANCE (152 + STACK_EXTRA)\n#define STACK_CELL_COUNT (180 + STACK_EXTRA)\n#define STACK_AUTO_OFFSET (140 + STACK_EXTRA)\n#define STACK_PWM_OUT (200 + STACK_EXTRA)\n\n#define STACK_USB (196 + STACK_EXTRA)\n#define STACK_LED (186 + STACK_EXTRA)\n\n/* RPM multiplier */\n#define RPM_MULTIPLIER (RPM_PINION_TEETH / (1.0 * RPM_MAIN_TEETH * RPM_PAIR_OF_POLES))\n\n/* Averaging elements to alpha */\n#define ALPHA(ELEMENTS) (2.0 / ((ELEMENTS) + 1))\n/* Averaging alpha to elements */\n#define ELEMENTS(ALPHA) (uint) round((2.0 / (ALPHA) - 1))\n\n#endif"
  },
  {
    "path": "board/project/dbg_rb.c",
    "content": "#include \"dbg_rb.h\"\n\nstatic uint8_t *s_buf = NULL;\nstatic size_t   s_cap = 0;\nstatic volatile size_t s_head = 0; // write index\nstatic volatile size_t s_tail = 0; // read index\n\nstatic inline size_t rb_next(size_t idx) {\n    return (idx + 1) % s_cap;\n}\n\nbool dbg_rb_init(uint8_t *storage, size_t capacity) {\n    if (!storage || capacity < 64) return false;\n    s_buf = storage;\n    s_cap = capacity;\n    s_head = 0;\n    s_tail = 0;\n    return true;\n}\n\nsize_t dbg_rb_available(void) {\n    size_t head = s_head;\n    size_t tail = s_tail;\n    if (head >= tail) return head - tail;\n    return (s_cap - tail) + head;\n}\n\nsize_t dbg_rb_free(void) {\n    // One byte is left unused to distinguish full vs empty.\n    return (s_cap - 1) - dbg_rb_available();\n}\n\nsize_t dbg_rb_push(const uint8_t *data, size_t len) {\n    if (!s_buf || !data || len == 0) return 0;\n\n    size_t written = 0;\n    while (written < len) {\n        // If next head equals tail, buffer is full.\n        size_t next = rb_next(s_head);\n        if (next == s_tail) break;\n        s_buf[s_head] = data[written++];\n        s_head = next;\n    }\n    return written;\n}\n\nsize_t dbg_rb_pop(uint8_t *out, size_t len) {\n    if (!s_buf || !out || len == 0) return 0;\n\n    size_t read = 0;\n    while (read < len) {\n        if (s_tail == s_head) break; // empty\n        out[read++] = s_buf[s_tail];\n        s_tail = rb_next(s_tail);\n    }\n    return read;\n}"
  },
  {
    "path": "board/project/dbg_rb.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <stdint.h>\n#include <stdbool.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// Ring buffer init. capacity must be >= 64 and power-of-two recommended.\nbool dbg_rb_init(uint8_t *storage, size_t capacity);\n\n// Write raw bytes into RB. Returns number of bytes written (may be < len if full).\nsize_t dbg_rb_push(const uint8_t *data, size_t len);\n\n// Pop up to len bytes. Returns number of bytes popped.\nsize_t dbg_rb_pop(uint8_t *out, size_t len);\n\n// How many bytes currently stored.\nsize_t dbg_rb_available(void);\n\n// Free space remaining.\nsize_t dbg_rb_free(void);\n\n#ifdef __cplusplus\n}\n#endif"
  },
  {
    "path": "board/project/dbg_task.c",
    "content": "// dbg_task.c\n\n#include \"dbg_task.h\"\n#include \"dbg_rb.h\"\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <string.h>\n\n#include \"FreeRTOS.h\"\n#include \"task.h\"\n\n// -------- config --------\n#ifndef DBG_RB_CAPACITY\n#define DBG_RB_CAPACITY 2048u\n#endif\n\n#ifndef DBG_PRINTF_TMP\n#define DBG_PRINTF_TMP 256u\n#endif\n\n#ifndef DBG_FLUSH_CHUNK\n#define DBG_FLUSH_CHUNK 64u\n#endif\n// ------------------------\n\nstatic TaskHandle_t s_dbg_task = NULL;\n\n// Static RB storage\nstatic uint8_t s_rb_storage[DBG_RB_CAPACITY];\n\nstatic void dbg_notify(void) {\n    if (s_dbg_task) {\n        (void)xTaskNotifyGive(s_dbg_task);\n    }\n}\n\nvoid dbg_task_init(void) {\n    // Safe to call multiple times\n    static bool inited = false;\n    if (inited) return;\n    inited = true;\n    (void)dbg_rb_init(s_rb_storage, sizeof(s_rb_storage));\n}\n\nstatic void dbg_task_fn(void *arg) {\n    (void)arg;\n    uint8_t chunk[DBG_FLUSH_CHUNK];\n\n    for (;;) {\n        // Wait until something is written, but also wake periodically.\n        (void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(50));\n\n        // Drain RB\n        while (dbg_rb_available() > 0) {\n            size_t n = dbg_rb_pop(chunk, sizeof(chunk));\n            for (size_t i = 0; i < n; i++) {\n                // Use putchar to avoid heavy printf parsing.\n                putchar((int)chunk[i]);\n            }\n            // Optional: flush stdio if your backend needs it\n            // fflush(stdout);\n            taskYIELD();\n        }\n    }\n}\n\nvoid dbg_task_start(unsigned stack_words, unsigned priority) {\n    dbg_task_init();\n\n    if (s_dbg_task) return; // already started\n\n    (void)xTaskCreate(\n        dbg_task_fn,\n        \"dbg\",\n        (configSTACK_DEPTH_TYPE)stack_words,\n        NULL,\n        (UBaseType_t)priority,\n        &s_dbg_task\n    );\n}\n\nint dbg_write(const char *fmt, ...) {\n    if (!fmt) return 0;\n\n    char tmp[DBG_PRINTF_TMP];\n\n    va_list ap;\n    va_start(ap, fmt);\n    int n = vsnprintf(tmp, sizeof(tmp), fmt, ap);\n    va_end(ap);\n\n    if (n <= 0) return n;\n\n    // If truncated, n is the number that *would* have been written.\n    size_t to_write = (size_t)n;\n    if (to_write >= sizeof(tmp)) {\n        to_write = sizeof(tmp) - 1; // write truncated content\n    }\n\n    (void)dbg_rb_push((const uint8_t *)tmp, to_write);\n    dbg_notify();\n    return n;\n}\n\nvoid dbg_write_buffer(const uint8_t *buffer, size_t length, const char *format) {\n    if (!buffer || length == 0 || !format) return;\n\n    // Format each byte into a small temp then push.\n    // Keep this small to avoid big stack usage.\n    char tmp[32];\n\n    for (size_t i = 0; i < length; i++) {\n        int n = snprintf(tmp, sizeof(tmp), format, buffer[i]);\n        if (n > 0) {\n            size_t to_write = (size_t)n;\n            if (to_write >= sizeof(tmp)) to_write = sizeof(tmp) - 1;\n            (void)dbg_rb_push((const uint8_t *)tmp, to_write);\n        }\n    }\n\n    dbg_notify();\n}"
  },
  {
    "path": "board/project/dbg_task.h",
    "content": "#pragma once\n\n#include <stddef.h>\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n// Must be called once at boot, after scheduler init but before using debug macros is ok too.\nvoid dbg_task_init(void);\n\n// Start the debug task. Call once after scheduler start context is ready.\n// stack_words: FreeRTOS words, not bytes. priority: low (e.g. 1).\nvoid dbg_task_start(unsigned stack_words, unsigned priority);\n\n// printf-like write into ring buffer + notify debug task.\nint dbg_write(const char *fmt, ...);\n\n// Write an array into ring buffer with per-byte formatting (no prefix).\n// Example format: \"%02X \" or \"%u,\" etc. You control separators.\nvoid dbg_write_buffer(const uint8_t *buffer, size_t length, const char *format);\n\n#ifdef __cplusplus\n}\n#endif"
  },
  {
    "path": "board/project/led.c",
    "content": "#include \"led.h\"\n\n#include \"pico/stdlib.h\"\n#include \"ws2812.h\"\n\nvoid led_task() {\n    const uint WS2812_PIN = 16;\n    gpio_init(PICO_DEFAULT_LED_PIN);\n    gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT);\n    ws2812_init(pio0, WS2812_PIN, 800000);\n\n    while (1) {\n        for (uint8_t i = context.led_cycles; i > 0; i--) {\n            gpio_put(PICO_DEFAULT_LED_PIN, 1);\n            put_pixel_rgb(0, 0, 100);\n            vTaskDelay(context.led_cycle_duration / 2 / portTICK_PERIOD_MS);\n            gpio_put(PICO_DEFAULT_LED_PIN, 0);\n            put_pixel_rgb(0, 0, 0);\n            vTaskDelay(context.led_cycle_duration / 2 / portTICK_PERIOD_MS);\n        }\n        vTaskSuspend(NULL);\n    }\n}"
  },
  {
    "path": "board/project/led.h",
    "content": "#ifndef LED_H\n#define LED_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid led_task();\n\n#endif"
  },
  {
    "path": "board/project/main.c",
    "content": "#include <stdio.h>\r\n\r\n#include \"config.h\"\r\n#include \"frsky_d.h\"\r\n#include \"hitec.h\"\r\n#include \"ibus.h\"\r\n#include \"jetiex.h\"\r\n#include \"led.h\"\r\n#include \"multiplex.h\"\r\n#include \"sbus.h\"\r\n#include \"serial_monitor.h\"\r\n#include \"sim_rx.h\"\r\n#include \"smartport.h\"\r\n#include \"srxl.h\"\r\n#include \"srxl2.h\"\r\n#include \"usb.h\"\r\n#include \"xbus.h\"\r\n#include \"crsf.h\"\r\n#include \"hott.h\"\r\n#include \"sanwa.h\"\r\n#include \"jr_dmss.h\"\r\n#include \"fport.h\"\r\n#include \"fbus.h\"\r\n#include \"ghst.h\"\r\n#include \"jetiex_sensor.h\"\r\n#include \"dbg_task.h\"\r\n\r\ncontext_t context;\r\n\r\nvoid vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {\r\n    printf(\"stack overflow %p %s\\r\\n\", xTask, pcTaskName);\r\n    while (1)\r\n        ;\r\n}\r\n\r\nint main() {\r\n    stdio_init_all();\r\n\r\n    gpio_init(RESTORE_GPIO);\r\n    gpio_pull_up(RESTORE_GPIO);\r\n    if (CONFIG_FORZE_WRITE || !gpio_get(RESTORE_GPIO)) config_forze_write();\r\n    config_t *config = config_read();\r\n\r\n    context.debug = config->debug;\r\n    if (context.debug) sleep_ms(1000);\r\n\r\n    xTaskCreate(usb_task, \"usb_task\", STACK_USB, NULL, 1, &context.usb_task_handle);\r\n\r\n    dbg_task_init();\r\n    dbg_task_start(512, 1);\r\n\r\n    debug(\"\\n\\nMSRC init\");\r\n\r\n    context.led_cycle_duration = 200;\r\n    context.led_cycles = 3;\r\n    xTaskCreate(led_task, \"led_task\", STACK_LED, NULL, 1, &context.led_task_handle);\r\n\r\n    switch (config->rx_protocol) {\r\n        case RX_XBUS:\r\n            xTaskCreate(xbus_task, \"xbus_task\", STACK_RX_XBUS, NULL, 3, &context.receiver_task_handle);\r\n            break;\r\n        case RX_IBUS:\r\n            xTaskCreate(ibus_task, \"ibus_task\", STACK_RX_IBUS, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_FRSKY_D:\r\n            xTaskCreate(frsky_d_task, \"frsky_d_task\", STACK_RX_FRSKY_D, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_MULTIPLEX:\r\n            xTaskCreate(multiplex_task, \"multiplex_task\", STACK_RX_MULTIPLEX, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_SMARTPORT:\r\n            xTaskCreate(smartport_task, \"smartport_task\", STACK_RX_SMARTPORT, NULL, 4, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_JETIEX:\r\n            xTaskCreate(jetiex_task, \"jetiex_task\", STACK_RX_JETIEX, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_SBUS:\r\n            xTaskCreate(sbus_task, \"sbus_task\", STACK_RX_SBUS, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_HITEC:\r\n            xTaskCreate(hitec_task, \"hitec_task\", STACK_RX_HITEC, NULL, 3, &context.receiver_task_handle);\r\n            break;\r\n        case RX_SRXL:\r\n            xTaskCreate(srxl_task, \"srxl_task\", STACK_RX_SRXL, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_SRXL2:\r\n            xTaskCreate(srxl2_task, \"srxl2_task\", STACK_RX_SRXL2, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case SERIAL_MONITOR:\r\n            xTaskCreate(serial_monitor_task, \"serial_monitor\", STACK_SERIAL_MONITOR, NULL, 3,\r\n                        &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            context.uart1_notify_task_handle = context.receiver_task_handle;\r\n            context.uart_pio_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_CRSF:\r\n            xTaskCreate(crsf_task, \"crfs_task\", STACK_RX_CRSF, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_HOTT:\r\n            xTaskCreate(hott_task, \"hott_task\", STACK_RX_HOTT, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_SANWA:\r\n            xTaskCreate(sanwa_task, \"sanwa_task\", STACK_RX_SANWA, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_JR_PROPO:\r\n            xTaskCreate(jr_dmss_task, \"jr_dmss_task\", STACK_RX_JR_PROPO, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_FPORT:\r\n            xTaskCreate(fport_task, \"fport_task\", STACK_RX_FPORT, NULL, 4, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_FBUS:\r\n            xTaskCreate(fbus_task, \"fbus_task\", STACK_RX_FBUS, NULL, 4, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_GHST:\r\n            xTaskCreate(ghst_task, \"ghst_task\", STACK_RX_GHST, NULL, 3, &context.receiver_task_handle);\r\n            context.uart0_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n        case RX_JETIEX_SENSOR:\r\n            xTaskCreate(jetiex_sensor_task, \"jetiex_sensor_task\", STACK_RX_JETIEX_SENSOR, NULL, 3, &context.receiver_task_handle);\r\n            context.uart_pio_notify_task_handle = context.receiver_task_handle;\r\n            break;\r\n    }\r\n\r\n#ifdef SIM_RX\r\n    sim_rx_parameters_t parameter = {config->rx_protocol};\r\n    xTaskCreate(sim_rx_task, \"sim_rx_task\", STACK_SIM_RX, &parameter, 3, NULL);\r\n#endif\r\n\r\n    vTaskStartScheduler();\r\n\r\n    while (1)\r\n        ;\r\n}\r\n"
  },
  {
    "path": "board/project/pio/CMakeLists.txt",
    "content": "target_sources(${PROJECT_NAME} PRIVATE\r\n    capture_edge.c\r\n    castle_link.c\r\n    i2c_multi.c\r\n    uart_rx.c\r\n    ws2812.c\r\n    uart_tx.c\r\n)\r\n\r\ntarget_link_libraries(${PROJECT_NAME} \r\n    pico_stdlib\r\n    hardware_irq\r\n    hardware_pio\r\n    hardware_clocks\r\n    hardware_dma\r\n)\r\n\r\npico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/capture_edge.pio)\r\npico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/i2c_multi.pio)\r\npico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/uart_rx.pio)\r\npico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/uart_tx.pio)\r\npico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/castle_link.pio)\r\npico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio)\r\n"
  },
  {
    "path": "board/project/pio/capture_edge.c",
    "content": "/*\n * Copyright (c) 2022, Daniel Gorbea\n * All rights reserved.\n *\n * This source code is licensed under the MIT-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * Library for pin capture timer for RP2040\n */\n\n#include \"capture_edge.h\"\n\n#include <stdio.h>\n\n#include \"hardware/dma.h\"\n#include \"hardware/irq.h\"\n\n#define MAX_PIN_COUNT 2\n\nstatic uint pins_, counter_;\nstatic const uint reload_counter_ = 0xffffffff, reload_pins_ = 1;\nstatic uint sm_, offset_, dma_channel_write_pins_, dma_channel_write_counter_, dma_channel_counter_,\n    dma_channel_reload_counter_, dma_channel_reload_pins_, dma_channel_reload_write_counter_, pin_count_;\nstatic PIO pio_;\nstatic void (*handler_[MAX_PIN_COUNT])(uint counter, edge_type_t edge) = {NULL};\n\nstatic inline void handler_pio(void);\nstatic inline edge_type_t get_captured_edge(uint pin, uint pins, uint prev);\nstatic inline uint bit_value(uint pos);\n\nvoid capture_edge_init(PIO pio, uint pin_base, uint pin_count, float clk_div, uint irq) {\n    pio_ = pio;\n    pin_count_ = pin_count;\n\n    // pio capture\n    sm_ = pio_claim_unused_sm(pio_, true);\n    offset_ = pio_add_program(pio_, &capture_edge_program);\n    pio_sm_set_consecutive_pindirs(pio_, sm_, pin_base, pin_count, false);\n    pio_sm_config c = capture_edge_program_get_default_config(offset_);\n    sm_config_set_clkdiv(&c, clk_div);\n    sm_config_set_in_pins(&c, pin_base);\n    pio->instr_mem[offset_ + 3] = pio_encode_in(pio_pins, pin_count);\n    pio->instr_mem[offset_ + 4] = pio_encode_in(pio_null, 32 - pin_count);\n    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);\n    if (irq == PIO0_IRQ_0 || irq == PIO1_IRQ_0)\n        pio_set_irq0_source_enabled(pio_, (enum pio_interrupt_source)(pis_interrupt0 + CAPTURE_EDGE_IRQ_NUM), true);\n    else\n        pio_set_irq1_source_enabled(pio_, (enum pio_interrupt_source)(pis_interrupt0 + CAPTURE_EDGE_IRQ_NUM), true);\n    pio_interrupt_clear(pio_, CAPTURE_EDGE_IRQ_NUM);\n    pio_sm_init(pio_, sm_, offset_ + capture_edge_offset_start, &c);\n    irq_set_exclusive_handler(irq, handler_pio);\n    irq_set_enabled(irq, true);\n\n    // get dma channels\n    dma_channel_write_pins_ = dma_claim_unused_channel(true);\n    dma_channel_write_counter_ = dma_claim_unused_channel(true);\n    dma_channel_counter_ = dma_claim_unused_channel(true);\n    dma_channel_reload_counter_ = dma_claim_unused_channel(true);\n    dma_channel_reload_pins_ = dma_claim_unused_channel(true);\n    dma_channel_reload_write_counter_ = dma_claim_unused_channel(true);\n\n    // dma channel write pins\n    dma_channel_config config_dma_channel_write_pins = dma_channel_get_default_config(dma_channel_write_pins_);\n    channel_config_set_transfer_data_size(&config_dma_channel_write_pins, DMA_SIZE_32);\n    channel_config_set_write_increment(&config_dma_channel_write_pins, false);\n    channel_config_set_read_increment(&config_dma_channel_write_pins, false);\n    channel_config_set_dreq(&config_dma_channel_write_pins, pio_get_dreq(pio_, sm_, false));\n    channel_config_set_chain_to(&config_dma_channel_write_pins, dma_channel_reload_pins_);\n    dma_channel_configure(dma_channel_write_pins_, &config_dma_channel_write_pins,\n                          &pins_,           // write address\n                          &pio_->rxf[sm_],  // read address\n                          1, false);\n\n    // dma channel write counter\n    dma_channel_config config_dma_channel_write_counter = dma_channel_get_default_config(dma_channel_write_counter_);\n    channel_config_set_transfer_data_size(&config_dma_channel_write_counter, DMA_SIZE_32);\n    channel_config_set_write_increment(&config_dma_channel_write_counter, false);\n    channel_config_set_read_increment(&config_dma_channel_write_counter, false);\n    channel_config_set_dreq(&config_dma_channel_write_pins, pio_get_dreq(pio_, sm_, false));\n    channel_config_set_chain_to(&config_dma_channel_write_counter, dma_channel_reload_write_counter_);\n    dma_channel_configure(dma_channel_write_counter_, &config_dma_channel_write_counter,\n                          &counter_,                                         // write address\n                          &dma_hw->ch[dma_channel_counter_].transfer_count,  // read address\n                          1, false);\n\n    // dma channel counter\n    dma_channel_config config_dma_channel_counter = dma_channel_get_default_config(dma_channel_counter_);\n    channel_config_set_write_increment(&config_dma_channel_counter, false);\n    channel_config_set_read_increment(&config_dma_channel_counter, false);\n    uint dma_timer = dma_claim_unused_timer(true);\n    dma_timer_set_fraction(dma_timer, 1, COUNTER_CYCLES);\n    channel_config_set_dreq(&config_dma_channel_counter, dma_get_timer_dreq(dma_timer));\n    channel_config_set_chain_to(&config_dma_channel_counter, dma_channel_reload_counter_);\n    dma_channel_configure(dma_channel_counter_, &config_dma_channel_counter,\n                          NULL,  // write address\n                          NULL,  // read address\n                          0xffffffff, false);\n\n    // dma channel reload counter\n    dma_channel_config config_dma_channel_reload_counter = dma_channel_get_default_config(dma_channel_reload_counter_);\n    channel_config_set_transfer_data_size(&config_dma_channel_reload_counter, DMA_SIZE_32);\n    channel_config_set_write_increment(&config_dma_channel_reload_counter, false);\n    channel_config_set_read_increment(&config_dma_channel_reload_counter, false);\n    dma_channel_configure(dma_channel_reload_counter_, &config_dma_channel_reload_counter,\n                          &dma_hw->ch[dma_channel_counter_].al1_transfer_count_trig,  // write address\n                          &reload_counter_,                                           // read address\n                          1, false);\n\n    // dma channel reload pins\n    dma_channel_config config_dma_channel_reload_pins = dma_channel_get_default_config(dma_channel_reload_pins_);\n    channel_config_set_transfer_data_size(&config_dma_channel_reload_pins, DMA_SIZE_32);\n    channel_config_set_write_increment(&config_dma_channel_reload_pins, false);\n    channel_config_set_read_increment(&config_dma_channel_reload_pins, false);\n    dma_channel_configure(dma_channel_reload_pins_, &config_dma_channel_reload_pins,\n                          &dma_hw->ch[dma_channel_write_pins_].al1_transfer_count_trig,  // write address\n                          &reload_pins_,                                                 // read address\n                          1, false);\n\n    // dma channel reload write counter\n    dma_channel_config config_dma_channel_reload_write_counter =\n    dma_channel_get_default_config(dma_channel_reload_write_counter_);\n    channel_config_set_transfer_data_size(&config_dma_channel_reload_write_counter, DMA_SIZE_32);\n    channel_config_set_write_increment(&config_dma_channel_reload_write_counter, false);\n    channel_config_set_read_increment(&config_dma_channel_reload_write_counter, false);\n    dma_channel_configure(dma_channel_reload_write_counter_, &config_dma_channel_reload_write_counter,\n                          &dma_hw->ch[dma_channel_write_counter_].al1_transfer_count_trig,  // write address\n                          &reload_pins_,                                                    // read address\n                          1, false);\n\n    dma_start_channel_mask((1 << dma_channel_write_pins_) | (1 << dma_channel_write_counter_) |\n                           (1 << dma_channel_counter_));\n    pio_sm_set_enabled(pio_, sm_, true);\n}\n\nvoid capture_edge_set_handler(uint pin, capture_handler_t handler) {\n    if (pin < pin_count_) {\n        handler_[pin] = handler;\n    }\n}\n\nvoid capture_edge_remove(void) {\n    for (uint pin = 0; pin < pin_count_; pin++) capture_edge_set_handler(pin, NULL);\n    pio_remove_program(pio_, &capture_edge_program, offset_);\n    pio_sm_unclaim(pio_, sm_);\n    dma_channel_unclaim(dma_channel_write_pins_);\n    dma_channel_unclaim(dma_channel_write_counter_);\n    dma_channel_unclaim(dma_channel_counter_);\n    dma_channel_unclaim(dma_channel_reload_counter_);\n    dma_channel_unclaim(dma_channel_reload_pins_);\n    dma_channel_unclaim(dma_channel_reload_write_counter_);\n}\n\nstatic inline void handler_pio(void) {\n    static uint prev_pins = 0;\n    uint counter = ~counter_;\n    uint pins = pins_;\n    for (uint pin = 0; pin < pin_count_; pin++) {\n        edge_type_t edge = get_captured_edge(pin, pins, prev_pins);\n        if (edge && *handler_[pin]) handler_[pin](counter, edge);\n    }\n    prev_pins = pins;\n    pio_interrupt_clear(pio_, CAPTURE_EDGE_IRQ_NUM);\n}\n\nstatic inline edge_type_t get_captured_edge(uint pin, uint pins, uint prev) {\n    if ((bit_value(pin) & pins) ^ (bit_value(pin) & prev) && (bit_value(pin) & pins)) return EDGE_RISE;\n    if ((bit_value(pin) & pins) ^ (bit_value(pin) & prev) && !(bit_value(pin) & pins)) return EDGE_FALL;\n    return EDGE_NONE;\n}\n\nstatic inline uint bit_value(uint pos) { return 1 << pos; }\n"
  },
  {
    "path": "board/project/pio/capture_edge.h",
    "content": "/*\n * Copyright (c) 2022, Daniel Gorbea\n * All rights reserved.\n *\n * This source code is licensed under the MIT-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * Library for pin capture timer for RP2040\n */\n\n#ifndef CAPTURE_EDGE\n#define CAPTURE_EDGE\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#include \"capture_edge.pio.h\"\n#include \"hardware/pio.h\"\n\ntypedef enum edge_type_t { EDGE_NONE, EDGE_FALL, EDGE_RISE } edge_type_t;\n\ntypedef void (*capture_handler_t)(uint counter, edge_type_t edge);\n\nvoid capture_edge_init(PIO pio, uint pin_base, uint pin_count, float clk_div, uint irq);\nvoid capture_edge_set_handler(uint pin, capture_handler_t handler);\nvoid capture_edge_remove(void);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif\n"
  },
  {
    "path": "board/project/pio/capture_edge.pio",
    "content": "/*\n * Copyright (c) 2022, Daniel Gorbea\n * All rights reserved.\n *\n * This source code is licensed under the MIT-style license found in the\n * LICENSE file in the root directory of this source tree. \n *\n * Library for pin capture timer for RP2040\n */\n\n.define PUBLIC CAPTURE_EDGE_IRQ_NUM 3     // use 0 to 3\n\n.define PUBLIC COUNTER_CYCLES 5\n\n.program capture_edge\ncapture:\n    irq CAPTURE_EDGE_IRQ_NUM\n    push\n.wrap_target\npublic start:\n    mov y x                         // pins to prev\n    nop                             // in pins CAPTURE_EDGE_PIN_COUNT  // read pins\n    nop                             // in null ZERO_COUNT\n    mov x isr                       // pins to x\n    jmp x!=y capture                // capture\n.wrap"
  },
  {
    "path": "board/project/pio/castle_link.c",
    "content": "#include \"castle_link.h\"\n\n#include <math.h>\n\n#include \"hardware/irq.h\"\n#include \"hardware/pio.h\"\n#include \"pico/stdlib.h\"\n\nstatic uint sm_pulse_, sm_counter_, offset_pulse_, offset_counter_;\nstatic PIO pio_;\nstatic void (*handler_)(castle_link_telemetry_t packet) = {NULL};\n\nstatic inline void handler_pio();\n\nvoid castle_link_init(PIO pio, uint pin, uint irq) {\n    pio_ = pio;\n    sm_pulse_ = pio_claim_unused_sm(pio_, true);\n    offset_pulse_ = pio_add_program(pio_, &pulse_program);\n    pio_gpio_init(pio_, pin + 1);\n    pio_sm_set_consecutive_pindirs(pio_, sm_pulse_, pin, 2, false);\n    pio_sm_set_pins(pio, sm_pulse_, 0);\n\n    pio_sm_config c_pulse = pulse_program_get_default_config(offset_pulse_);\n    sm_config_set_clkdiv(&c_pulse, 10);\n    sm_config_set_in_pins(&c_pulse, pin);\n    sm_config_set_set_pins(&c_pulse, pin + 1, 1);\n\n    pio_sm_init(pio_, sm_pulse_, offset_pulse_, &c_pulse);\n    pio_sm_set_enabled(pio_, sm_pulse_, true);\n\n    sm_counter_ = pio_claim_unused_sm(pio_, true);\n    offset_counter_ = pio_add_program(pio_, &counter_program);\n\n    pio_sm_config c_counter = counter_program_get_default_config(offset_counter_);\n    sm_config_set_clkdiv(&c_counter, 5);\n    sm_config_set_in_pins(&c_counter, pin);\n    if (irq == PIO0_IRQ_0 || irq == PIO1_IRQ_0)\n        pio_set_irq0_source_enabled(pio_, (enum pio_interrupt_source)(pis_interrupt0 + CASTLE_LINK_IRQ_NUM), true);\n    else\n        pio_set_irq1_source_enabled(pio_, (enum pio_interrupt_source)(pis_interrupt0 + CASTLE_LINK_IRQ_NUM), true);\n    pio_interrupt_clear(pio_, CASTLE_LINK_IRQ_NUM);\n    pio_sm_init(pio_, sm_counter_, offset_counter_ + counter_offset_start, &c_counter);\n    pio_sm_set_enabled(pio_, sm_counter_, true);\n    irq_set_exclusive_handler(irq, handler_pio);\n    irq_set_enabled(irq, true);\n}\n\nvoid castle_link_set_handler(castle_link_handler_t handler) { handler_ = handler; }\n\nvoid castle_link_remove() {\n    castle_link_set_handler(NULL);\n    pio_remove_program(pio_, &pulse_program, offset_pulse_);\n    pio_remove_program(pio_, &counter_program, offset_counter_);\n    pio_sm_unclaim(pio_, sm_pulse_);\n    pio_sm_unclaim(pio_, sm_counter_);\n}\n\nstatic inline void handler_pio() {\n    static uint index = 0;\n    static const float scaler[11] = {0, 20, 4, 50, 1, 0.2502, 20416.7, 4, 4, 30, 63.8125};\n    static uint value[12];\n\n    pio_interrupt_clear(pio_, CASTLE_LINK_IRQ_NUM);\n    if (pio_sm_is_rx_fifo_full(pio_, sm_counter_)) {\n        pio_sm_clear_fifos(pio_, sm_counter_);\n        return;\n    }\n    uint data = pio_sm_get_blocking(pio_, sm_counter_);\n    if (data > 50000) {\n        index = 0;\n        // printf(\"%i \\n\", data);\n        return;\n    }\n    if (index > 10) return;\n    value[index] = data;\n    // printf(\"(%u)%u \", index, value[index]);\n    if (index == 10) {\n        uint calibration;\n        castle_link_telemetry_t packet;\n        if (value[9] < value[10]) {\n            calibration = value[0] / 2 + value[9];\n            packet.is_temp_ntc = true;\n            float temp_raw = ((float)value[10] - calibration / 2) * scaler[10] / calibration;\n            packet.temperature =\n                1 / (log(temp_raw * 10200.0 / (255.0 - temp_raw) / 10000.0) / 3455.0 + 1.0 / 298.0) - 273.0;\n        } else {\n            calibration = value[0] / 2 + value[10];\n            packet.is_temp_ntc = false;\n            packet.temperature = ((float)value[9] - calibration / 2) * scaler[9] / calibration;\n        }\n        packet.voltage = ((float)value[1] - calibration / 2) * scaler[1] / calibration;\n        packet.ripple_voltage = ((float)value[2] - calibration / 2) * scaler[2] / calibration;\n        packet.current = ((float)value[3] - calibration / 2) * scaler[3] / calibration;\n        packet.thr = ((float)value[4] - calibration / 2) * scaler[4] / calibration;\n        packet.output = ((float)value[5] - calibration / 2) * scaler[5] / calibration;\n        packet.rpm = ((float)value[6] - calibration / 2) * scaler[6] / calibration;\n        packet.voltage_bec = ((float)value[7] - calibration / 2) * scaler[7] / calibration;\n        packet.current_bec = ((float)value[8] - calibration / 2) * scaler[8] / calibration;\n        if (packet.voltage < 0) packet.voltage = 0;\n        if (packet.ripple_voltage < 0) packet.ripple_voltage = 0;\n        if (packet.current < 0) packet.current = 0;\n        if (packet.thr < 0) packet.thr = 0;\n        if (packet.output < 0) packet.output = 0;\n        if (packet.rpm < 0) packet.rpm = 0;\n        if (packet.voltage_bec < 0) packet.voltage_bec = 0;\n        if (packet.current_bec < 0) packet.current_bec = 0;\n        if (packet.temperature < 0) packet.temperature = 0;\n        handler_(packet);\n    }\n    index++;\n}"
  },
  {
    "path": "board/project/pio/castle_link.h",
    "content": "#ifndef PIO_CASTLE\n#define PIO_CASTLE\n\n#include \"castle_link.pio.h\"\n\n/*                 castle telemetry\n\n    index   element                          scaler\n    0       sync\n    1       calib 1 (1000us)\n    2       Volt (V)                         20\n    3       rippleVolt (V)                   4\n    4       Curr (A)                         50\n    5       Thr (0.1-0.2 ms esc pulse)       1\n    6       Output power (%)                 0.2502\n    7       rpm                              20416.7\n    8       becVolt (V)                      4\n    9       becCurr (A)                      4\n    10      temp (C) or calib 2 (500us)      30\n    11      temp ntc (C) or calib 2 (500us)  63.8125\n*/\n\ntypedef struct castle_link_telemetry_t {\n    float voltage;\n    float ripple_voltage;\n    float current;\n    float thr;\n    float output;\n    float rpm;\n    float voltage_bec;\n    float current_bec;\n    float temperature;\n    bool is_temp_ntc;\n} castle_link_telemetry_t;\n\ntypedef void (*castle_link_handler_t)(castle_link_telemetry_t packet);\n\nvoid castle_link_init(PIO pio, uint pin_base, uint irq);\nvoid castle_link_set_handler(castle_link_handler_t handler);\nvoid castle_link_remove();\n\n#endif\n"
  },
  {
    "path": "board/project/pio/castle_link.pio",
    "content": "/**\n * Copyright (c) 2022, Daniel Gorbea\n * All rights reserved.\n *\n * This source code is licensed under the MIT-style license found in the\n * LICENSE file in the root directory of this source tree. \n */\n \n.define PUBLIC CASTLE_LINK_IRQ_NUM 0        // use 0 to 3\n\n// 0 RX input\n// 1 PWM input/output\n// in base = 0\n// set base = 1\n\n.program pulse\n.wrap_target\n    wait 1 pin 0       // rx pulse start -> esc is output \n    set pindirs 1 [31] // set pin 0 output (1)\n    wait 0 pin 0       // end of pwm rx -> esc is input. start counter (irq 4)\n    set pindirs 0 [31] // set pin 0 input (0)\n    irq 4\n.wrap\n\n// 0 RX input\n// 1 PWM input\n// in base = 0\n\n.program counter  // counter increment every 5 cycles\ncapture:\n    mov isr ~y              // copy counter to isr\n    push noblock            // send counter to fifo\n    irq CASTLE_LINK_IRQ_NUM  // raise irq\n.wrap_target\npublic start:\n    mov y ~null       // init counter\n    wait irq 4\nloop:\n    in pins 2         // read pins\n    in null 31        // fill with zeros and shift to esc pin\n    mov x isr\n    jmp !x capture    // jmp if x=0\n    jmp y-- loop      // decrement counter\n.wrap"
  },
  {
    "path": "board/project/pio/i2c_multi.c",
    "content": "#include \"i2c_multi.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"hardware/irq.h\"\n#include \"hardware/pio.h\"\n#include \"i2c_multi.pio.h\"\n#include \"pico/stdlib.h\"\n\n#define CLK_DIV 16\n\nstatic i2c_multi_t *i2c_multi;\n\nstatic void (*receive_handler)(uint8_t data, bool is_address) = NULL;\nstatic void (*request_handler)(uint8_t address) = NULL;\nstatic void (*stop_handler)(uint8_t length) = NULL;\n\nstatic inline void start_condition_program_init(PIO pio, uint sm, uint offset, uint pin);\nstatic inline void stop_condition_program_init(PIO pio, uint sm, uint offset, uint pin);\nstatic inline void read_byte_program_init(PIO pio, uint sm, uint offset, uint pin);\nstatic inline void write_byte_program_init(PIO pio, uint sm, uint offset, uint pin);\nstatic inline void byte_handler_pio();\nstatic inline void stop_handler_pio();\nstatic inline uint8_t transpond_byte(uint8_t byte);\n\nvoid i2c_multi_init(PIO pio, uint pin) {\n    i2c_multi = (i2c_multi_t *)malloc(sizeof(i2c_multi_t));\n    i2c_multi->pio = pio;\n    i2c_multi->status = I2C_IDLE;\n    i2c_multi->pin = pin;\n    i2c_multi->bytes_count = 0;\n    i2c_multi_disable_all_addresses();\n    i2c_multi->buffer = NULL;\n    i2c_multi->buffer_start = NULL;\n    uint pio_irq0 = (pio == pio0 ? PIO0_IRQ_0 : PIO1_IRQ_0);\n    uint pio_irq1 = (pio == pio0 ? PIO0_IRQ_1 : PIO1_IRQ_1);\n    i2c_multi->length = -1;\n\n    pio_gpio_init(pio, pin);\n    pio_gpio_init(pio, pin + 1);\n\n    i2c_multi->offset_start = pio_add_program(pio, &start_condition_program);\n    i2c_multi->sm_start = pio_claim_unused_sm(pio, true);\n    start_condition_program_init(pio, i2c_multi->sm_start, i2c_multi->offset_start, pin);\n\n    i2c_multi->offset_stop = pio_add_program(pio, &stop_condition_program);\n    i2c_multi->sm_stop = pio_claim_unused_sm(pio, true);\n    stop_condition_program_init(pio, i2c_multi->sm_stop, i2c_multi->offset_stop, pin);\n\n    i2c_multi->offset_read = pio_add_program(pio, &read_byte_program);\n    i2c_multi->sm_read = pio_claim_unused_sm(pio, true);\n    read_byte_program_init(pio, i2c_multi->sm_read, i2c_multi->offset_read, pin);\n\n    i2c_multi->offset_write = pio_add_program(pio, &write_byte_program);\n    i2c_multi->sm_write = pio_claim_unused_sm(pio, true);\n    write_byte_program_init(pio, i2c_multi->sm_write, i2c_multi->offset_write, pin);\n\n    pio_sm_put(pio, i2c_multi->sm_read,\n               (((uint32_t)do_ack_program_instructions[1]) << 16) | do_ack_program_instructions[0]);\n    pio_sm_put(pio, i2c_multi->sm_read,\n               (((uint32_t)do_ack_program_instructions[3]) << 16) | do_ack_program_instructions[2]);\n\n    irq_set_exclusive_handler(pio_irq0, byte_handler_pio);\n    irq_set_enabled(pio_irq0, true);\n    irq_set_exclusive_handler(pio_irq1, stop_handler_pio);\n    irq_set_enabled(pio_irq1, true);\n}\n\nvoid i2c_multi_set_write_buffer(uint8_t *buffer) {\n    i2c_multi->buffer = buffer;\n    i2c_multi->buffer_start = buffer;\n}\n\nvoid i2c_multi_set_receive_handler(i2c_multi_receive_handler_t handler) { receive_handler = handler; }\n\nvoid i2c_multi_set_request_handler(i2c_multi_request_handler_t handler) { request_handler = handler; }\n\nvoid i2c_multi_set_stop_handler(i2c_multi_stop_handler_t handler) { stop_handler = handler; }\n\nvoid i2c_multi_enable_address(uint8_t address) { i2c_multi->address[address / 32] |= 1 << (address % 32); }\n\nvoid i2c_multi_disable_address(uint8_t address) { i2c_multi->address[address / 32] &= ~(1 << (address % 32)); }\n\nvoid i2c_multi_enable_all_addresses() {\n    i2c_multi->address[0] = 0xFFFFFFFF;\n    i2c_multi->address[1] = 0xFFFFFFFF;\n    i2c_multi->address[2] = 0xFFFFFFFF;\n    i2c_multi->address[3] = 0xFFFFFFFF;\n}\n\nvoid i2c_multi_disable_all_addresses() {\n    i2c_multi->address[0] = 0;\n    i2c_multi->address[1] = 0;\n    i2c_multi->address[2] = 0;\n    i2c_multi->address[3] = 0;\n}\n\nbool i2c_multi_is_address_enabled(uint8_t address) { return i2c_multi->address[address / 32] & (1 << (address % 32)); }\n\nvoid i2c_multi_disable() {\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_read, false);\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_write, false);\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_start, false);\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_stop, false);\n    pio_sm_clear_fifos(i2c_multi->pio, i2c_multi->sm_read);\n    pio_sm_clear_fifos(i2c_multi->pio, i2c_multi->sm_write);\n    gpio_set_input_enabled(i2c_multi->pin, true);\n    gpio_set_input_enabled(i2c_multi->pin + 1, true);\n    i2c_multi->bytes_count = 0;\n    i2c_multi->status = I2C_IDLE;\n    i2c_multi->buffer = i2c_multi->buffer_start;\n}\n\nvoid i2c_multi_restart() {\n    i2c_multi_disable();\n    pio_sm_restart(i2c_multi->pio, i2c_multi->sm_start);\n    pio_sm_restart(i2c_multi->pio, i2c_multi->sm_stop);\n    pio_sm_restart(i2c_multi->pio, i2c_multi->sm_read);\n    pio_sm_restart(i2c_multi->pio, i2c_multi->sm_write);\n    pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n               (((uint32_t)do_ack_program_instructions[1]) << 16) | do_ack_program_instructions[0]);\n    pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n               (((uint32_t)do_ack_program_instructions[3]) << 16) | do_ack_program_instructions[2]);\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_read, true);\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_write, true);\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_start, true);\n    pio_sm_set_enabled(i2c_multi->pio, i2c_multi->sm_stop, true);\n}\n\nvoid i2c_multi_remove() {\n    receive_handler = NULL;\n    request_handler = NULL;\n    stop_handler = NULL;\n    pio_set_irq0_source_enabled(i2c_multi->pio, pis_interrupt0, false);\n    pio_set_irq1_source_enabled(i2c_multi->pio, pis_interrupt1, false);\n    pio_clear_instruction_memory(i2c_multi->pio);\n    pio_sm_unclaim(i2c_multi->pio, i2c_multi->sm_start);\n    pio_sm_unclaim(i2c_multi->pio, i2c_multi->sm_stop);\n    pio_sm_unclaim(i2c_multi->pio, i2c_multi->sm_read);\n    pio_sm_unclaim(i2c_multi->pio, i2c_multi->sm_write);\n    i2c_multi->buffer = NULL;\n    i2c_multi->buffer_start = NULL;\n    i2c_multi->bytes_count = 0;\n    i2c_multi->status = I2C_IDLE;\n    gpio_set_input_enabled(i2c_multi->pin, true);\n    gpio_set_input_enabled(i2c_multi->pin + 1, true);\n    free(i2c_multi);\n}\n\nvoid i2c_multi_fixed_length(int16_t length) { i2c_multi->length = length; }\n\nstatic inline void start_condition_program_init(PIO pio, uint sm, uint offset, uint pin) {\n    pio_sm_config c = start_condition_program_get_default_config(offset);\n    sm_config_set_in_pins(&c, pin);\n    sm_config_set_clkdiv(&c, CLK_DIV);\n    sm_config_set_jmp_pin(&c, pin + 1);\n    pio_sm_init(pio, sm, offset + start_condition_offset_start, &c);\n    pio_sm_set_enabled(pio, sm, true);\n}\n\nstatic inline void stop_condition_program_init(PIO pio, uint sm, uint offset, uint pin) {\n    pio_sm_config c = stop_condition_program_get_default_config(offset);\n    sm_config_set_in_pins(&c, pin);\n    sm_config_set_clkdiv(&c, CLK_DIV);\n    sm_config_set_jmp_pin(&c, pin + 1);\n    pio_sm_init(pio, sm, offset + stop_condition_offset_start, &c);\n    pio_sm_set_enabled(pio, sm, true);\n    pio_set_irq1_source_enabled(pio, pis_interrupt1, true);\n    pio_interrupt_clear(pio, 1);\n}\n\nstatic inline void read_byte_program_init(PIO pio, uint sm, uint offset, uint pin) {\n    pio_sm_config c = read_byte_program_get_default_config(offset);\n    sm_config_set_in_pins(&c, pin);\n    sm_config_set_clkdiv(&c, CLK_DIV);\n    sm_config_set_out_shift(&c, true, true, 32);\n    pio_set_irq0_source_enabled(pio, pis_interrupt0, true);\n    pio_interrupt_clear(pio, 0);\n    sm_config_set_set_pins(&c, pin, 2);\n    sm_config_set_sideset_pins(&c, pin);\n    pio_sm_init(pio, sm, offset, &c);\n    pio_sm_set_enabled(pio, sm, true);\n}\n\nstatic inline void write_byte_program_init(PIO pio, uint sm, uint offset, uint pin) {\n    pio_sm_config c = write_byte_program_get_default_config(offset);\n    sm_config_set_in_pins(&c, pin);\n    sm_config_set_out_pins(&c, pin, 1);\n    sm_config_set_set_pins(&c, pin, 2);\n    sm_config_set_sideset_pins(&c, pin);\n    sm_config_set_clkdiv(&c, CLK_DIV);\n    sm_config_set_out_shift(&c, true, true, 32);\n    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);\n    sm_config_set_jmp_pin(&c, pin);\n    pio_sm_init(pio, sm, offset, &c);\n    pio_sm_set_enabled(pio, sm, true);\n}\n\nstatic inline void byte_handler_pio() {\n    uint8_t received = 0;\n    bool is_address = false;\n    i2c_multi->bytes_count++;\n    if (i2c_multi->status != I2C_WRITE) {\n        received = transpond_byte(pio_sm_get_blocking(i2c_multi->pio, i2c_multi->sm_read) >>\n                                  24);  // Do the bit-reverse here as PIO instructions are scarce\n    }\n    if (i2c_multi->status == I2C_IDLE) {\n        if (!i2c_multi_is_address_enabled(received >> 1)) {\n            i2c_multi->status = I2C_IDLE;\n            i2c_multi->bytes_count = 0;\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                       (((uint32_t)do_ack_program_instructions[10] + i2c_multi->offset_read) << 16) |\n                           do_ack_program_instructions[9]);\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                       (((uint32_t)do_ack_program_instructions[1]) << 16) | do_ack_program_instructions[0]);\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                       (((uint32_t)do_ack_program_instructions[3]) << 16) | do_ack_program_instructions[2]);\n            pio_interrupt_clear(i2c_multi->pio, 0);\n            return;\n        }\n        if (received & 1) {\n            i2c_multi->status = I2C_WRITE;\n        } else {\n            i2c_multi->status = I2C_READ;\n        }\n        is_address = true;\n    }\n    if (i2c_multi->status == I2C_READ) {\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                   (((uint32_t)do_ack_program_instructions[5]) << 16) | do_ack_program_instructions[4]);\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                   (((uint32_t)do_ack_program_instructions[7] + i2c_multi->offset_read) << 16) |\n                       do_ack_program_instructions[6]);\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                   (((uint32_t)do_ack_program_instructions[1]) << 16) | do_ack_program_instructions[0]);\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                   (((uint32_t)do_ack_program_instructions[3]) << 16) | do_ack_program_instructions[2]);\n        if (receive_handler) {\n            if (is_address) {\n                receive_handler(received >> 1, true);\n            } else\n                receive_handler(received, false);\n        }\n    }\n    if (i2c_multi->status == I2C_WRITE && is_address) {\n        if (request_handler) {\n            request_handler(received >> 1);\n        }\n        uint8_t value = 0;\n        if (i2c_multi->buffer) {\n            value = transpond_byte(*i2c_multi->buffer);\n            i2c_multi->buffer++;\n        }\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                   (((uint32_t)do_ack_program_instructions[5]) << 16) | do_ack_program_instructions[4]);\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                   (((uint32_t)do_ack_program_instructions[8] + i2c_multi->offset_read) << 16) |\n                       do_ack_program_instructions[6]);\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_read,\n                   (((uint32_t)do_ack_program_instructions[1]) << 16) | do_ack_program_instructions[0]);\n        pio_sm_put_blocking(i2c_multi->pio, i2c_multi->sm_read,\n                            (((uint32_t)do_ack_program_instructions[3]) << 16) | do_ack_program_instructions[2]);\n\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_write, value);\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_write,\n                   (((uint32_t)wait_ack_program_instructions[1]) << 16) | wait_ack_program_instructions[0]);\n        pio_sm_put(i2c_multi->pio, i2c_multi->sm_write,\n                   (((uint32_t)wait_ack_program_instructions[3]) << 16) | wait_ack_program_instructions[2]);\n    }\n    if (i2c_multi->status == I2C_WRITE && !is_address) {\n        if (i2c_multi->length == -1 || (i2c_multi->length > 0 && i2c_multi->bytes_count < i2c_multi->length + 1)) {\n            uint8_t value = 0;\n            if (i2c_multi->buffer) {\n                value = transpond_byte(*i2c_multi->buffer);\n                i2c_multi->buffer++;\n            }\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_write,\n                       (((uint32_t)wait_ack_program_instructions[5]) << 16) | wait_ack_program_instructions[4]);\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_write,\n                       (((uint32_t)wait_ack_program_instructions[7] + i2c_multi->offset_write) << 16) |\n                           (wait_ack_program_instructions[6]) + i2c_multi->offset_write);\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_write, value);\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_write,\n                       (((uint32_t)wait_ack_program_instructions[1]) << 16) | wait_ack_program_instructions[0]);\n            pio_sm_put(i2c_multi->pio, i2c_multi->sm_write,\n                       (((uint32_t)wait_ack_program_instructions[3]) << 16) | wait_ack_program_instructions[2]);\n        } else {\n            pio_sm_exec(i2c_multi->pio, i2c_multi->sm_read, i2c_multi->offset_read);\n            pio_sm_clear_fifos(i2c_multi->pio, i2c_multi->sm_write);\n            pio_sm_exec(i2c_multi->pio, i2c_multi->sm_write, wait_ack_program_instructions[8]);\n            pio_sm_exec(i2c_multi->pio, i2c_multi->sm_write,\n                        wait_ack_program_instructions[9] + i2c_multi->offset_write);\n            if (stop_handler) {\n                stop_handler(i2c_multi->bytes_count - 1);\n            }\n            i2c_multi->bytes_count = 0;\n            i2c_multi->status = I2C_IDLE;\n        }\n    }\n    pio_interrupt_clear(i2c_multi->pio, 0);\n}\n\nstatic inline void stop_handler_pio() {\n    pio_interrupt_clear(i2c_multi->pio, 1);\n    if (i2c_multi->status == I2C_IDLE) return;\n    pio_sm_exec(i2c_multi->pio, i2c_multi->sm_read, i2c_multi->offset_read);\n    pio_sm_clear_fifos(i2c_multi->pio, i2c_multi->sm_write);\n    pio_sm_exec(i2c_multi->pio, i2c_multi->sm_write, wait_ack_program_instructions[8]);\n    pio_sm_exec(i2c_multi->pio, i2c_multi->sm_write, wait_ack_program_instructions[9] + i2c_multi->offset_write);\n    i2c_multi->buffer = i2c_multi->buffer_start;\n    if (stop_handler) {\n        stop_handler(i2c_multi->bytes_count - 1);\n    }\n    i2c_multi->bytes_count = 0;\n    i2c_multi->status = I2C_IDLE;\n}\n\nstatic inline uint8_t transpond_byte(uint8_t byte) {\n    uint8_t transponded = ((byte & 0x1) << 7) | (((byte & 0x2) >> 1) << 6) | (((byte & 0x4) >> 2) << 5) |\n                          (((byte & 0x8) >> 3) << 4) | (((byte & 0x10) >> 4) << 3) | (((byte & 0x20) >> 5) << 2) |\n                          (((byte & 0x40) >> 6) << 1) | (((byte & 0x80) >> 7));\n    return transponded;\n}\n\n"
  },
  {
    "path": "board/project/pio/i2c_multi.h",
    "content": "#ifndef I2C_MULTI\n#define I2C_MULTI\n\n#include \"hardware/pio.h\"\n#include \"common.h\"\n\ntypedef enum i2c_multi_status_t { I2C_IDLE, I2C_READ, I2C_WRITE } i2c_multi_status_t;\n\ntypedef void (*i2c_multi_receive_handler_t)(uint8_t data, bool is_address);\ntypedef void (*i2c_multi_request_handler_t)(uint8_t address);\ntypedef void (*i2c_multi_stop_handler_t)(uint8_t length);\n\ntypedef struct i2c_multi_t {\n    PIO pio;\n    uint offset_read, offset_write, sm_read, sm_write, offset_start, offset_stop, sm_start, sm_stop, pin;\n    i2c_multi_status_t status;\n    uint8_t *buffer, *buffer_start;\n    uint8_t bytes_count;\n    int16_t length;\n    uint address[4];\n} i2c_multi_t;\n\nextern context_t context;\n\nvoid i2c_multi_init(PIO pio, uint pin);\nvoid i2c_multi_set_write_buffer(uint8_t *buffer);\nvoid i2c_multi_set_receive_handler(i2c_multi_receive_handler_t handler);\nvoid i2c_multi_set_request_handler(i2c_multi_request_handler_t handler);\nvoid i2c_multi_set_stop_handler(i2c_multi_stop_handler_t handler);\nvoid i2c_multi_enable_address(uint8_t address);\nvoid i2c_multi_disable_address(uint8_t address);\nvoid i2c_multi_enable_all_addresses();\nvoid i2c_multi_disable_all_addresses();\nbool i2c_multi_is_address_enabled(uint8_t address);\nvoid i2c_multi_disable();\nvoid i2c_multi_restart();\nvoid i2c_multi_remove();\nvoid i2c_multi_fixed_length(int16_t length);\n\n#endif\n"
  },
  {
    "path": "board/project/pio/i2c_multi.pio",
    "content": "/**\n * -------------------------------------------------------------------------------\n * \n * Copyright (c) 2022, Daniel Gorbea\n * All rights reserved.\n *\n * This source code is licensed under the MIT-style license found in the\n * LICENSE file in the root directory of this source tree. \n * \n * -------------------------------------------------------------------------------\n * \n *  I2C slave multi - answer to multiple addresses\n * \n *  SDA = pin\n *  SCL = pin + 1\n *\n *  Add external pull ups, 1k - 3.3k\n *\n *  Define handlers and write buffer\n * \n * -------------------------------------------------------------------------------\n */\n \n // sda 0, scl 1\n\n.program start_condition  // 5\ndo_irq:\n    irq 1 // Ensure a stop has been handled, in case this is a repeat start\n    irq 4\npublic start:\n.wrap_target\n    wait 1 pin 0\n    wait 0 pin 0\n    jmp pin do_irq\n.wrap\n\n.program stop_condition  // 4\ndo_irq:\n    irq 1\npublic start:\n.wrap_target\n    wait 0 pin 0\n    wait 1 pin 0\n    jmp pin do_irq\n.wrap\n\n.program read_byte  // 10\n.side_set 2 opt pindirs\n    wait irq 4\nread:\n    set x 7 side 0\nbit_loop:\n    wait 0 pin 1\n    wait 1 pin 1\n    in pins 1\n    jmp x-- bit_loop\n    push noblock\ndo_ack:\n    out exec 16\n    jmp do_ack\ndo_irq:\n    irq 5\n\n.program do_ack\n.side_set 2 opt pindirs\n    wait 0 pin 1\n    set pins 0 side 3\n    nop\n    irq wait 0\n\n    set pins 0 side 1\n    wait 1 pin 1\n    wait 0 pin 1\n\n    // read (receive request)\n    jmp 1 \n\n    // write (write request)\n    jmp 9\n\n    // address not enabled\n    set pindirs 0\n    jmp 0\n\n.program write_byte  // 9\n.side_set 2 opt pindirs\n    wait irq 5\nwrite:\n    set x 7 side 1\nbit_loop:\n    wait 0 pin 1\n    out pins 1\n    wait 1 pin 1\n    jmp x-- bit_loop\n    out null 32\nwait_ack:\n    out exec 16\n    jmp wait_ack\n\n.program wait_ack\n.side_set 2 opt pindirs\n    // clk stretch\n    wait 0 pin 1\n    nop //set pindirs 2\n    set pins 0 side 2\n    irq wait 0\n    \n    // check ack\n    nop\n    wait 1 pin 1 side 0\n    jmp pin 0\n    jmp 1\n\n    // stop\n    out null 32 side 0\n    jmp 0\n"
  },
  {
    "path": "board/project/pio/uart_rx.c",
    "content": "#include \"uart_rx.h\"\n\n#include \"hardware/clocks.h\"\n#include \"hardware/irq.h\"\n#include \"hardware/pio.h\"\n#include \"pico/stdlib.h\"\n\nstatic uint sm_, offset_;\nstatic PIO pio_;\nstatic void (*handler_)(uint8_t data) = NULL;\n\nstatic inline void handler_pio(void);\n\nuint uart_rx_init(PIO pio, uint pin, uint baudrate, uint irq) {\n    pio_ = pio;\n    sm_ = pio_claim_unused_sm(pio_, true);\n    pio_sm_set_consecutive_pindirs(pio_, sm_, pin, 1, false);\n    pio_gpio_init(pio_, pin);\n    gpio_pull_up(pin);\n\n    offset_ = pio_add_program(pio_, &uart_rx_program);\n    pio_sm_config c = uart_rx_program_get_default_config(offset_);\n    sm_config_set_in_pins(&c, pin);\n    sm_config_set_jmp_pin(&c, pin);\n    sm_config_set_in_shift(&c, true, false, 32);\n    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);\n    float div = (float)clock_get_hz(clk_sys) / (UART_RX_CYCLES_PER_BIT * baudrate);\n    sm_config_set_clkdiv(&c, div);\n\n    if (irq == PIO0_IRQ_0 || irq == PIO1_IRQ_0)\n        pio_set_irq0_source_enabled(pio_, (enum pio_interrupt_source)(pis_interrupt0 + UART_RX_IRQ_NUM), true);\n    else\n        pio_set_irq1_source_enabled(pio_, (enum pio_interrupt_source)(pis_interrupt0 + UART_RX_IRQ_NUM), true);\n    pio_interrupt_clear(pio_, UART_RX_IRQ_NUM);\n\n    pio_sm_init(pio_, sm_, offset_, &c);\n    pio_sm_set_enabled(pio_, sm_, true);\n    irq_set_exclusive_handler(irq, handler_pio);\n    irq_set_enabled(irq, true);\n\n    return sm_;\n}\n\nvoid uart_rx_set_handler(uart_rx_handler_t handler) { handler_ = handler; }\n\nvoid uart_rx_remove(void) {\n    uart_rx_set_handler(NULL);\n    pio_remove_program(pio_, &uart_rx_program, offset_);\n    pio_sm_unclaim(pio_, sm_);\n}\n\nstatic inline void handler_pio(void) {\n    pio_interrupt_clear(pio_, UART_RX_IRQ_NUM);\n    if (handler_) {\n        while (pio_sm_get_rx_fifo_level(pio_, sm_)) {\n            uint data = pio_sm_get_blocking(pio_, sm_);\n            handler_(data >> 24);\n        }\n    }\n}"
  },
  {
    "path": "board/project/pio/uart_rx.h",
    "content": "#ifndef UART_RX\n#define UART_RX\n\n#include \"uart_rx.pio.h\"\n\ntypedef void (*uart_rx_handler_t)(uint8_t data);\n\nuint uart_rx_init(PIO pio, uint pin, uint baudrate, uint irq);\nvoid uart_rx_set_handler(uart_rx_handler_t handler);\nvoid uart_rx_remove();\n\n#endif\n"
  },
  {
    "path": "board/project/pio/uart_rx.pio",
    "content": "/**\n * Copyright (c) 2022, Daniel Gorbea\n * All rights reserved.\n *\n * This source code is licensed under the MIT-style license found in the\n * LICENSE file in the root directory of this source tree. \n */\n\n.define PUBLIC UART_RX_IRQ_NUM 2  // use 0 to 3\n.define PUBLIC UART_RX_CYCLES_PER_BIT 16  // we want here resolution to reduce the error when sampling. more cycles less error\n\n.program uart_rx  // 1 bit every 16 cycles\nstart:\n    wait 0 pin 0\n    set x 7 [16+16/2-2]\nbit_loop:\n    in pins 1\n    jmp x-- bit_loop [16-2]\n    jmp pin good_stop\n    wait 1 pin 0 [4]\n    jmp start\ngood_stop:\n    push\n    irq UART_RX_IRQ_NUM\n"
  },
  {
    "path": "board/project/pio/uart_tx.c",
    "content": "#include \"uart_tx.h\"\n\n#include \"hardware/clocks.h\"\n#include \"hardware/pio.h\"\n#include \"pico/stdlib.h\"\n\nstatic uint sm_, offset_;\nstatic PIO pio_;\nstatic uint8_t data_bits_;\nstatic uint8_t parity_;\n\nuint uart_tx_init(PIO pio, uint pin, uint baudrate, uint data_bits, uint stop_bits, uint parity) {\n    pio_ = pio;\n    data_bits_ = data_bits;\n    parity_ = parity;\n    gpio_pull_up(pin);\n    sm_ = pio_claim_unused_sm(pio_, true);\n    pio_gpio_init(pio_, pin);\n    pio_sm_set_consecutive_pindirs(pio_, sm_, pin, 1, false);\n    pio_sm_set_pins(pio, sm_, 0xFFFF);\n    offset_ = pio_add_program(pio_, &uart_tx_program);\n    pio_->instr_mem[offset_] = pio_encode_set(pio_x, parity == UART_PARITY_NONE ? data_bits - 1 : data_bits);\n    pio_->instr_mem[offset_ + 6] = pio_encode_set(pio_pins, 1) | pio_encode_delay(16 * stop_bits - 1);\n    pio_sm_config c = uart_tx_program_get_default_config(offset_);\n    sm_config_set_out_pins(&c, pin, 1);\n    sm_config_set_set_pins(&c, pin, 1);\n    sm_config_set_in_shift(&c, true, false, 32);\n    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);\n    float div = (float)clock_get_hz(clk_sys) / (UART_TX_CYCLES_PER_BIT * baudrate);\n    sm_config_set_clkdiv(&c, div);\n    pio_sm_init(pio_, sm_, offset_, &c);\n    pio_sm_set_enabled(pio_, sm_, true);\n    return sm_;\n}\n\nvoid uart_tx_write(uint32_t c) {\n    if (parity_ != UART_PARITY_NONE) {\n        bool parity_bit = 0;\n        for (uint i = 0; i < data_bits_; i++) {\n            parity_bit ^= (c >> i) & 1;\n        }\n        c |= (parity_bit << (data_bits_));\n        if (parity_ == UART_PARITY_ODD) {\n            c ^= (1 << (data_bits_));\n        }\n    }\n    pio_sm_put_blocking(pio_, sm_, c);\n}\n\nvoid uart_tx_write_bytes(void *data, uint8_t length) {\n    for (uint8_t i = 0; i < length; i++) {\n        switch (data_bits_) {\n            case 1 ... 8:\n                uart_tx_write(((uint8_t *)data)[i]);\n                break;\n            case 9 ... 16:\n                uart_tx_write(((uint16_t *)data)[i]);\n                break;\n            case 17 ... 32:\n                uart_tx_write(((uint32_t *)data)[i]);\n                break;\n        }\n    }\n}\n\nvoid uart_tx_remove(void) {\n    pio_remove_program(pio_, &uart_tx_program, offset_);\n    pio_sm_unclaim(pio_, sm_);\n}"
  },
  {
    "path": "board/project/pio/uart_tx.h",
    "content": "#ifndef UART_TX\n#define UART_TX\n\n#include \"uart_tx.pio.h\"\n\nuint uart_tx_init(PIO pio, uint pin, uint baudrate, uint data_bits, uint stop_bits, uint parity);\nvoid uart_tx_write(uint32_t c);\nvoid uart_tx_write_bytes(void *data, uint8_t length);\nvoid uart_tx_remove(void);\n\n#endif"
  },
  {
    "path": "board/project/pio/uart_tx.pio",
    "content": "/**\n * Copyright (c) 2025, Daniel Gorbea\n * All rights reserved.\n *\n * This source code is licensed under the MIT-style license found in the\n * LICENSE file in the root directory of this source tree. \n */\n\n.define PUBLIC UART_TX_CYCLES_PER_BIT 16  // we want here resolution to reduce division error when writing. more cycles less error\n\n.program uart_tx  // 1 bit every 16 cycles\nstart:\n    nop // set x n (0 to 31). Number of data bits\n    pull block\n    set pindirs 1 // take control of pin\n    set pins 0 [16-2]  // start bit\nbit_loop:\n    out pins 1\n    jmp x-- bit_loop [16-2]\n    nop // set pins 1 [16*stop-1]. Stop bits (1-2)\n    nop [16]\n    set pindirs 0 // release pin"
  },
  {
    "path": "board/project/pio/ws2812.c",
    "content": "#include \"ws2812.h\"\n\n#include \"hardware/clocks.h\"\n#include \"hardware/pio.h\"\n#include \"pico/stdlib.h\"\n\nstatic uint sm_;\nstatic PIO pio_;\n\nstatic inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b);\n\nvoid ws2812_init(PIO pio, uint pin, float freq) {\n    uint offset = pio_add_program(pio, &ws2812_program);\n    pio_ = pio;\n    sm_ = pio_claim_unused_sm(pio, true);\n    pio_gpio_init(pio, pin);\n    pio_sm_set_consecutive_pindirs(pio, sm_, pin, 1, true);\n    pio_sm_config c = ws2812_program_get_default_config(offset);\n    sm_config_set_sideset_pins(&c, pin);\n    sm_config_set_out_shift(&c, false, true, 24);\n    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);\n\n    int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;\n    float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);\n    sm_config_set_clkdiv(&c, div);\n\n    pio_sm_init(pio, sm_, offset, &c);\n    pio_sm_set_enabled(pio, sm_, true);\n}\n\nvoid put_pixel_rgb(uint8_t r, uint8_t g, uint8_t b) { pio_sm_put_blocking(pio_, sm_, urgb_u32(r, g, b) << 8u); }\n\nstatic inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) {\n    return ((uint32_t)(r) << 8) | ((uint32_t)(g) << 16) | (uint32_t)(b);\n}"
  },
  {
    "path": "board/project/pio/ws2812.h",
    "content": "#ifndef WS2812_H\n#define WS2812_H\n\n#include \"ws2812.pio.h\"\n\nvoid ws2812_init(PIO pio, uint pin, float freq);\nvoid put_pixel_rgb(uint8_t r, uint8_t g, uint8_t b);\n\n#endif"
  },
  {
    "path": "board/project/pio/ws2812.pio",
    "content": ";\n; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.\n;\n; SPDX-License-Identifier: BSD-3-Clause\n;\n\n.program ws2812\n.side_set 1\n\n.define public T1 2\n.define public T2 5\n.define public T3 3\n\n.wrap_target\nbitloop:\n    out x, 1       side 0 [T3 - 1] ; Side-set still takes place when instruction stalls\n    jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse\ndo_one:\n    jmp  bitloop   side 1 [T2 - 1] ; Continue driving high, for a long pulse\ndo_zero:\n    nop            side 0 [T2 - 1] ; Or drive low, for a short pulse\n.wrap\n\n\n"
  },
  {
    "path": "board/project/protocol/CMakeLists.txt",
    "content": "target_sources(${PROJECT_NAME} PRIVATE\r\n    frsky_d.c\r\n    hitec.c\r\n    ibus.c\r\n    jetiex.c\r\n    multiplex.c\r\n    sbus.c\r\n    smartport.c\r\n    srxl.c\r\n    xbus.c\r\n    srxl2.c\r\n    crsf.c\r\n    hott.c\r\n    sanwa.c\r\n    jr_dmss.c\r\n    fport.c\r\n    fbus.c\r\n    ghst.c\r\n    jetiex_sensor.c\r\n)\r\n"
  },
  {
    "path": "board/project/protocol/crsf.c",
    "content": "#include \"crsf.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"gps.h\"\n#include \"ibus.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n#define CRSF_FRAMETYPE_GPS 0x02\n#define CRSF_FRAMETYPE_VARIO 0x07\n#define CRSF_FRAMETYPE_BATTERY_SENSOR 0x08\n#define CRSF_FRAMETYPE_BARO_ALTITUDE 0x09\n#define CRSF_FRAMETYPE_HEARTBEAT 0x0B\n#define CRSF_FRAMETYPE_LINK_STATISTICS 0x14\n#define CRSF_FRAMETYPE_RC_CHANNELS_PACKED 0x16\n#define CRSF_FRAMETYPE_SUBSET_RC_CHANNELS_PACKED 0x17\n#define CRSF_FRAMETYPE_LINK_STATISTICS_RX 0x1C\n#define CRSF_FRAMETYPE_LINK_STATISTICS_TX 0x1D\n#define CRSF_FRAMETYPE_ATTITUDE 0x1E\n#define CRSF_FRAMETYPE_FLIGHT_MODE 0x21\n#define CRSF_FRAMETYPE_AIRSPEED 0x0A\n#define CRSF_FRAMETYPE_RPM 0x0C\n#define CRSF_FRAMETYPE_TEMP 0x0D\n#define CRSF_FRAMETYPE_VOLTAGES 0x0E\n#define CRSF_FRAMETYPE_GPS_TIME 0x03\n#define CRSF_FRAMETYPE_GPS_EXTENDED 0x06\n\n#define CRSF_TIMEOUT_US 1000\n\ntypedef enum crsf_sensor_type_t {\n    TYPE_GPS,\n    TYPE_VARIO,\n    TYPE_BATERY,\n    TYPE_BARO,\n    TYPE_AIRSPEED,\n    TYPE_RPM,\n    TYPE_TEMP,\n    TYPE_VOLTAGES,\n    TYPE_GPS_TIME,\n    TYPE_GPS_EXTENDED,\n    TYPE_ATTITUDE,\n    MAX_SENSORS\n} crsf_sensor_type_t;\n\n/*\nCRSF frame has the structure:\n<Device address> <Frame length> <Type> <Payload> <CRC>\nDevice address: (uint8_t)\nFrame length:   length in  bytes including Type (uint8_t)\nType:           (uint8_t)\nCRC:            (uint8_t), crc of <Type> and <Payload>\n*/\n\ntypedef struct crsf_sensor_gps_formatted_t {\n    int32_t latitude;      // degree / 10,000,000 big endian\n    int32_t longitude;     // degree / 10,000,000 big endian\n    uint16_t groundspeed;  // km/h / 10 big endian\n    uint16_t heading;      // GPS heading, degree/100 big endian\n    uint16_t altitude;     // meters, +1000m big endian\n    uint8_t satellites;    // satellites\n} __attribute__((packed)) crsf_sensor_gps_formatted_t;\n\ntypedef struct crsf_sensor_vario_formatted_t {\n    int16_t vspeed;  // cm/s\n} __attribute__((packed)) crsf_sensor_vario_formatted_t;\n\ntypedef struct crsf_sensor_baro_formatted_t {\n    uint16_t altitude;\n    int16_t vspeed;  // cm/s\n} __attribute__((packed)) crsf_sensor_baro_formatted_t;\n\ntypedef struct crsf_sensor_battery_formatted_t {\n    uint16_t voltage;        // V * 10 big endian\n    uint16_t current;        // A * 10 big endian\n    uint32_t capacity : 24;  // used capacity mah big endian\n    uint8_t remaining;       // %\n} __attribute__((packed)) crsf_sensor_battery_formatted_t;\n\ntypedef struct crsf_sensor_airspeed_formatted_t {\n    uint16_t speed;  // V * 10 big endian\n} __attribute__((packed)) crsf_sensor_airspeed_formatted_t;\n\ntypedef struct crsf_sensor_rpm_formatted_t {\n    uint8_t source;\n    uint32_t rpm : 24;\n} __attribute__((packed)) crsf_sensor_rpm_formatted_t;\n\ntypedef struct crsf_sensor_temp_formatted_t {\n    uint8_t source;\n    int16_t temp;\n} __attribute__((packed)) crsf_sensor_temp_formatted_t;\n\ntypedef struct crsf_sensor_voltages_formatted_t {\n    uint8_t source;\n    uint16_t voltage;\n} __attribute__((packed)) crsf_sensor_voltages_formatted_t;\n\ntypedef struct crsf_sensor_gps_time_formatted_t {\n    uint16_t year;\n    uint8_t month;\n    uint8_t day;\n    uint8_t hour;\n    uint8_t minute;\n    uint8_t second;\n    uint16_t millisecond;\n} __attribute__((packed)) crsf_sensor_gps_time_formatted_t;\n\ntypedef struct crsf_sensor_gps_extended_formatted_t {\n    uint8_t fix_type;       // Current GPS fix quality\n    int16_t n_speed;        // Northward (north = positive) Speed [cm/sec]\n    int16_t e_speed;        // Eastward (east = positive) Speed [cm/sec]\n    int16_t v_speed;        // Vertical (up = positive) Speed [cm/sec]\n    int16_t h_speed_acc;    // Horizontal Speed accuracy cm/sec\n    int16_t track_acc;      // Heading accuracy in degrees scaled with 1e-1 degrees times 10)\n    int16_t alt_ellipsoid;  // Meters Height above GPS Ellipsoid (not MSL)\n    int16_t h_acc;          // horizontal accuracy in cm\n    int16_t v_acc;          // vertical accuracy in cm\n    uint8_t reserved;\n    uint8_t hDOP;  // Horizontal dilution of precision,Dimensionless in nits of.1.\n    uint8_t vDOP;  // vertical dilution of precision, Dimensionless in nits of .1.\n} __attribute__((packed)) crsf_sensor_gps_extended_formatted_t;\n\ntypedef struct crsf_sensor_gps_t {\n    float *latitude;     // degree / 10,000,000 big endian\n    float *longitude;    // degree / 10,000,000 big endian\n    float *groundspeed;  // km/h / 10 big endian\n    float *heading;      // GPS heading, degree/100 big endian\n    float *altitude;     // meters, +1000m big endian\n    float *satellites;   // satellites\n} crsf_sensor_gps_t;\n\ntypedef struct crsf_sensor_attitude_formatted_t {\n    int16_t pitch;  // angle ( rad / 10000 )\n    int16_t roll;   // angle ( rad / 10000 )\n    int16_t yaw;    // angle ( rad / 10000 )\n} __attribute__((packed)) crsf_sensor_attitude_formatted_t;\n\ntypedef struct crsf_sensor_vario_t {\n    float *vspeed;  // cm/s\n} crsf_sensor_vario_t;\n\ntypedef struct crsf_sensor_baro_t {\n    float *altitude;\n    float *vspeed;  // cm/s\n} crsf_sensor_baro_t;\n\ntypedef struct crsf_sensor_battery_t {\n    float *voltage;    // V * 10 big endian\n    float *current;    // A * 10 big endian\n    float *capacity;   // used capacity mah big endian\n    float *remaining;  // %\n} crsf_sensor_battery_t;\n\ntypedef struct crsf_sensor_airspeed_t {\n    float *speed;  // km/h * 10 big endian\n} crsf_sensor_airspeed_t;\n\ntypedef struct crsf_sensor_rpm_t {\n    float *rpm;\n} crsf_sensor_rpm_t;\n\ntypedef struct crsf_sensor_temp_t {\n    uint8_t temperature_count;\n    float *temperature[5];  // V * 10 big endian\n} crsf_sensor_temp_t;\n\ntypedef struct crsf_sensor_voltages_t {\n    bool is_cell_average;\n    uint8_t *cell_count;\n    float *cell[18];\n    uint8_t voltage_count;\n    float *voltage[10];\n} crsf_sensor_voltages_t;\n\ntypedef struct crsf_sensor_gps_time_t {\n    float *date;\n    float *time;\n} crsf_sensor_gps_time_t;\n\ntypedef struct crsf_sensor_gps_extended_t {\n    float *fix;\n    float *n_speed;\n    float *e_speed;\n    float *v_speed;\n    float *h_speed_acc;\n    float *track_acc;\n    float *alt_ellipsoid;\n    float *h_acc;\n    float *v_acc;\n    float *hdop;\n    float *vdop;\n} crsf_sensor_gps_extended_t;\n\ntypedef struct crsf_sensor_attitude_t {\n    float *pitch;\n    float *roll;\n    float *yaw;\n} crsf_sensor_attitude_t;\n\ntypedef struct crsf_sensors_t {\n    bool enabled_sensors[MAX_SENSORS];\n    crsf_sensor_gps_t gps;\n    crsf_sensor_vario_t vario;\n    crsf_sensor_battery_t battery;\n    crsf_sensor_baro_t baro;\n    crsf_sensor_airspeed_t airspeed;\n    crsf_sensor_rpm_t rpm;\n    crsf_sensor_temp_t temperature;\n    crsf_sensor_voltages_t voltages;\n    crsf_sensor_gps_time_t gps_time;\n    crsf_sensor_gps_extended_t gps_extended;\n    crsf_sensor_attitude_t attitude;\n} crsf_sensors_t;\n\nstatic uint8_t format_sensor(crsf_sensors_t *sensors, uint8_t type, uint8_t *buffer);\nstatic void send_packet(crsf_sensors_t *sensors);\nstatic uint8_t get_crc(const uint8_t *ptr, uint32_t len);\nstatic uint8_t crc8(uint8_t crc, unsigned char a);\nstatic void set_config(crsf_sensors_t *sensors);\nstatic inline uint sensor_count(crsf_sensors_t *sensors);\n\nvoid crsf_task(void *parameters) {\n    crsf_sensors_t sensors = {0};\n    set_config(&sensors);\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(416666L, UART_RECEIVER_TX, UART_RECEIVER_RX, CRSF_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n    debug(\"\\nCRSF init\");\n    while (1) {\n        vTaskDelay(20 / portTICK_PERIOD_MS);\n        send_packet(&sensors);\n    }\n}\n\nstatic uint8_t format_sensor(crsf_sensors_t *sensors, uint8_t type, uint8_t *buffer) {\n    // Packet format: [sync] [len (from type)] [type] [payload] [crc8 from type]\n    uint len = 0;\n    buffer[0] = 0xC8;\n    switch (type) {\n        case TYPE_GPS: {\n            buffer[1] = sizeof(crsf_sensor_gps_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_GPS;\n            crsf_sensor_gps_formatted_t sensor = {0};\n            if (sensors->gps.latitude) sensor.latitude = swap_32((int32_t)(*sensors->gps.latitude * 10000000L));\n            if (sensors->gps.longitude) sensor.longitude = swap_32((int32_t)(*sensors->gps.longitude * 10000000L));\n            if (sensors->gps.groundspeed)\n                sensor.groundspeed = swap_16((uint16_t)(fabs(*sensors->gps.groundspeed * 10)));\n            if (sensors->gps.satellites) sensor.satellites = *sensors->gps.satellites;\n            if (sensors->gps.heading) sensor.heading = swap_16((uint16_t)(*sensors->gps.heading * 100));\n            if (sensors->gps.altitude) {\n                float altitude = *sensors->gps.altitude + 1000;\n                if (altitude < 0) altitude = 0;\n                if (altitude > 65535) altitude = 65535;\n                sensor.altitude = swap_16((uint16_t)altitude);\n            }\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_gps_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_gps_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_gps_formatted_t) + 1);\n            len = sizeof(crsf_sensor_gps_formatted_t) + 4;\n            break;\n        }\n        case TYPE_VARIO: {\n            buffer[1] = sizeof(crsf_sensor_vario_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_VARIO;\n            crsf_sensor_vario_formatted_t sensor = {0};\n            if (sensors->vario.vspeed) sensor.vspeed = swap_16((int16_t)(*sensors->vario.vspeed * 100));\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_vario_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_vario_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_vario_formatted_t) + 1);\n            len = sizeof(crsf_sensor_vario_formatted_t) + 4;\n            break;\n        }\n        case TYPE_BATERY: {\n            buffer[1] = sizeof(crsf_sensor_battery_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_BATTERY_SENSOR;\n            crsf_sensor_battery_formatted_t sensor = {0};\n            if (sensors->battery.voltage) sensor.voltage = swap_16((uint16_t)(*sensors->battery.voltage * 10));\n            if (sensors->battery.current) sensor.current = swap_16((uint16_t)(*sensors->battery.current * 10));\n            if (sensors->battery.capacity) sensor.capacity = swap_24((uint32_t)*sensors->battery.capacity);\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_battery_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_battery_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_battery_formatted_t) + 1);\n            len = sizeof(crsf_sensor_battery_formatted_t) + 4;\n            break;\n        }\n        case TYPE_BARO: {\n            buffer[1] = sizeof(crsf_sensor_baro_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_BARO_ALTITUDE;\n            crsf_sensor_baro_formatted_t sensor = {0};\n            if (sensors->baro.vspeed) sensor.vspeed = swap_16((int16_t)(*sensors->baro.vspeed * 100));\n            if (sensors->baro.altitude) {\n                float altitude = *sensors->baro.altitude + 1000;\n                if (altitude < 0) altitude = 0;\n                if (altitude > 3276) altitude = 3276;\n                sensor.altitude = swap_16((uint16_t)(altitude * 10));\n            }\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_baro_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_baro_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_baro_formatted_t) + 1);\n            len = sizeof(crsf_sensor_baro_formatted_t) + 4;\n            break;\n        }\n        case TYPE_AIRSPEED: {\n            buffer[1] = sizeof(crsf_sensor_airspeed_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_AIRSPEED;\n            crsf_sensor_airspeed_formatted_t sensor = {0};\n            if (sensors->airspeed.speed) sensor.speed = swap_16((int16_t)(*sensors->airspeed.speed * 10));\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_airspeed_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_airspeed_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_airspeed_formatted_t) + 1);\n            len = sizeof(crsf_sensor_airspeed_formatted_t) + 4;\n            break;\n        }\n        case TYPE_RPM: {\n            buffer[1] = sizeof(crsf_sensor_rpm_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_RPM;\n            crsf_sensor_rpm_formatted_t sensor = {0};\n            if (sensors->rpm.rpm) sensor.rpm = swap_24((int32_t)(*sensors->rpm.rpm));\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_rpm_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_rpm_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_rpm_formatted_t) + 1);\n            len = sizeof(crsf_sensor_rpm_formatted_t) + 4;\n            break;\n        }\n        case TYPE_TEMP: {\n            static uint8_t count = 0;\n            buffer[1] = sizeof(crsf_sensor_temp_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_TEMP;\n            crsf_sensor_temp_formatted_t sensor = {0};\n            sensor.source = count;\n            if (sensors->temperature.temperature[count])\n                sensor.temp = swap_16((int16_t)(*sensors->temperature.temperature[count] * 10));\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_temp_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_temp_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_temp_formatted_t) + 1);\n            len = sizeof(crsf_sensor_temp_formatted_t) + 4;\n            count++;\n            if (count >= sensors->temperature.temperature_count) count = 0;\n            break;\n        }\n        case TYPE_VOLTAGES: {\n            static uint8_t cell_index = 0;\n            static uint8_t voltage_index = 0;\n            static bool send_cell = true;\n            if (*sensors->voltages.cell_count == 0) send_cell = false;\n            buffer[2] = CRSF_FRAMETYPE_VOLTAGES;\n            crsf_sensor_voltages_formatted_t sensor = {0};\n            buffer[1] = sizeof(crsf_sensor_voltages_formatted_t) + 2;\n            if (send_cell && *sensors->voltages.cell_count > 0) {\n                sensor.source = cell_index;\n                if (sensors->voltages.is_cell_average && sensors->voltages.cell[0]) {\n                    sensor.voltage = swap_16((uint16_t)(*sensors->voltages.cell[0] * 1000));\n                } else if (sensors->voltages.cell[cell_index]) {\n                    sensor.voltage = swap_16((uint16_t)(*sensors->voltages.cell[cell_index] * 1000));\n                }\n\n                memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_voltages_formatted_t));\n                buffer[3 + sizeof(crsf_sensor_voltages_formatted_t)] =\n                    get_crc(&buffer[2], sizeof(crsf_sensor_voltages_formatted_t) + 1);\n                len = sizeof(crsf_sensor_voltages_formatted_t) + 4;\n                buffer[1] = sizeof(crsf_sensor_voltages_formatted_t) + 2;\n\n                cell_index++;\n                if (cell_index >= *sensors->voltages.cell_count) {\n                    cell_index = 0;\n                    if (sensors->voltages.voltage_count) send_cell = false;\n                }\n            } else if (sensors->voltages.voltage_count > 0) {\n                sensor.source = voltage_index + 128;\n                sensor.voltage = swap_16((uint16_t)(*sensors->voltages.voltage[voltage_index] * 1000));\n\n                memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_voltages_formatted_t));\n                buffer[3 + sizeof(crsf_sensor_voltages_formatted_t)] =\n                    get_crc(&buffer[2], sizeof(crsf_sensor_voltages_formatted_t) + 1);\n                len = sizeof(crsf_sensor_voltages_formatted_t) + 4;\n                buffer[1] = sizeof(crsf_sensor_voltages_formatted_t) + 2;\n\n                voltage_index++;\n                if (voltage_index >= sensors->voltages.voltage_count) {\n                    voltage_index = 0;\n                    send_cell = true;\n                    if (*sensors->voltages.cell_count) send_cell = true;\n                }\n            }\n            break;\n        }\n        case TYPE_GPS_TIME: {\n            buffer[1] = sizeof(crsf_sensor_gps_time_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_GPS_TIME;\n            crsf_sensor_gps_time_formatted_t sensor = {0};\n            uint16_t year = *sensors->gps_time.date / 10000 + 2000;\n            sensor.year = swap_16(year);\n            sensor.month = ((uint)*sensors->gps_time.date - (year - 2000) * 10000) / 100;\n            sensor.day = (uint)*sensors->gps_time.date - (year - 2000) * 10000 - sensor.month * 100;\n            sensor.hour = (uint)*sensors->gps_time.time / 10000;\n            sensor.minute = ((uint)*sensors->gps_time.time - sensor.hour * 10000) / 100;\n            sensor.second = ((uint)*sensors->gps_time.time - sensor.hour * 10000 - sensor.minute * 100);\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_gps_time_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_gps_time_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_gps_time_formatted_t) + 1);\n            len = sizeof(crsf_sensor_gps_time_formatted_t) + 4;\n            break;\n        }\n        case TYPE_GPS_EXTENDED: {\n            buffer[1] = sizeof(crsf_sensor_gps_extended_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_GPS_EXTENDED;\n            crsf_sensor_gps_extended_formatted_t sensor = {0};\n            sensor.fix_type = *sensors->gps_extended.fix;\n            sensor.n_speed = *sensors->gps_extended.n_speed / 10;\n            sensor.e_speed = *sensors->gps_extended.e_speed / 10;\n            sensor.v_speed = *sensors->gps_extended.v_speed / 10;\n            sensor.h_speed_acc = *sensors->gps_extended.h_speed_acc / 10;\n            sensor.track_acc = *sensors->gps_extended.track_acc / 10;\n            sensor.alt_ellipsoid = *sensors->gps_extended.alt_ellipsoid / 10;\n            sensor.hDOP = *sensors->gps_extended.hdop * 10;\n            sensor.vDOP = *sensors->gps_extended.vdop * 10;\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_gps_extended_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_gps_extended_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_gps_extended_formatted_t) + 1);\n            len = sizeof(crsf_sensor_gps_extended_formatted_t) + 4;\n            break;\n        }\n        case TYPE_ATTITUDE: {\n            buffer[1] = sizeof(crsf_sensor_attitude_formatted_t) + 2;\n            buffer[2] = CRSF_FRAMETYPE_ATTITUDE;\n            crsf_sensor_attitude_formatted_t sensor = {0};\n            if (sensors->attitude.pitch) sensor.pitch = swap_16((int16_t)(*sensors->attitude.pitch * 10000));\n            if (sensors->attitude.roll) sensor.roll = swap_16((int16_t)(*sensors->attitude.roll * 10000));\n            if (sensors->attitude.yaw) sensor.yaw = swap_16((int16_t)(*sensors->attitude.yaw * 10000));\n            memcpy(&buffer[3], &sensor, sizeof(crsf_sensor_attitude_formatted_t));\n            buffer[3 + sizeof(crsf_sensor_attitude_formatted_t)] =\n                get_crc(&buffer[2], sizeof(crsf_sensor_attitude_formatted_t) + 1);\n            len = sizeof(crsf_sensor_attitude_formatted_t) + 4;\n            break;\n        }\n    }\n    return len;\n}\n\nstatic inline uint sensor_count(crsf_sensors_t *sensors) {\n    uint count = 0;\n    for (uint i = 0; i < MAX_SENSORS; i++)\n        if (sensors->enabled_sensors[i]) count++;\n    return count;\n}\n\nstatic void send_packet(crsf_sensors_t *sensors) {\n    if (!sensor_count(sensors)) return;\n    static uint type = 0;\n    uint8_t buffer[64] = {0};\n    while (!sensors->enabled_sensors[type % MAX_SENSORS]) type++;\n    uint len = format_sensor(sensors, type % MAX_SENSORS, buffer);\n    uart0_write_bytes(buffer, len);\n    type++;\n    debug(\"\\nCRSF (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(buffer, len, \"0x%X \");\n\n    // blink led\n    vTaskResume(context.led_task_handle);\n}\n\nstatic uint8_t get_crc(const uint8_t *ptr, uint32_t len) {\n    uint8_t crc = 0;\n    for (uint32_t i = 0; i < len; i++) {\n        crc = crc8(crc, *ptr++);\n    }\n    return crc;\n}\n\nstatic uint8_t crc8(uint8_t crc, unsigned char a) {\n    crc ^= a;\n    for (int ii = 0; ii < 8; ++ii) {\n        if (crc & 0x80) {\n            crc = (crc << 1) ^ 0xD5;\n        } else {\n            crc = crc << 1;\n        }\n    }\n    return crc;\n}\n\nstatic void set_config(crsf_sensors_t *sensors) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_fet;\n        sensors->temperature.temperature_count++;\n\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_bec;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_fet;\n        sensors->temperature.temperature_count++;\n\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_bec;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n\n        sensors->voltages.voltage[sensors->voltages.voltage_count] = parameter.voltage_bec;\n        sensors->voltages.voltage_count++;\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n\n        sensors->voltages.voltage[sensors->voltages.voltage_count] = parameter.voltage_bec;\n        sensors->voltages.voltage_count++;\n        sensors->voltages.voltage[sensors->voltages.voltage_count] = parameter.ripple_voltage;\n        sensors->voltages.voltage_count++;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_fet;\n        sensors->temperature.temperature_count++;\n\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_bec;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n\n        sensors->voltages.voltage[sensors->voltages.voltage_count] = parameter.voltage_bec;\n        sensors->voltages.voltage_count++;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_fet;\n        sensors->temperature.temperature_count++;\n\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_bec;\n        sensors->temperature.temperature_count++;\n\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temperature_bat;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = false;\n        sensors->voltages.cell_count = parameter.cells;\n        for (uint i = 0; i < 18; i++) sensors->voltages.cell[i] = parameter.cell[i];\n\n        sensors->voltages.voltage[sensors->voltages.voltage_count] = parameter.voltage_bec;\n        sensors->voltages.voltage_count++;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temp_esc;\n        sensors->temperature.temperature_count++;\n\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temp_motor;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.voltage = parameter.voltage;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        sensors->enabled_sensors[TYPE_RPM] = true;\n        sensors->rpm.rpm = parameter.rpm;\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temp_esc;\n        sensors->temperature.temperature_count++;\n\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.temp_motor;\n        sensors->temperature.temperature_count++;\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.is_cell_average = true;\n        sensors->voltages.cell_count = parameter.cell_count;\n        sensors->voltages.cell[0] = parameter.cell_voltage;\n\n        sensors->voltages.voltage[sensors->voltages.voltage_count] = parameter.bec_voltage;\n        sensors->voltages.voltage_count++;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_GPS] = true;\n        sensors->gps.latitude = parameter.lat;\n        sensors->gps.longitude = parameter.lon;\n        sensors->gps.groundspeed = parameter.spd_kmh;\n        sensors->gps.heading = parameter.cog;\n        sensors->gps.satellites = parameter.sat;\n        sensors->gps.altitude = parameter.alt;\n\n        /*sensors->enabled_sensors[TYPE_GPS_TIME] = true;\n        sensors->gps_time.date = parameter.date;\n        sensors->gps_time.time = parameter.time;\n\n        sensors->enabled_sensors[TYPE_GPS_EXTENDED] = true;\n        sensors->gps_extended.hdop = parameter.hdop;\n        sensors->gps_extended.fix = parameter.fix;\n        sensors->gps_extended.vdop = parameter.vdop;\n        sensors->gps_extended.n_speed = parameter.n_vel;\n        sensors->gps_extended.e_speed = parameter.e_vel;\n        sensors->gps_extended.v_speed = parameter.v_vel;\n        sensors->gps_extended.h_speed_acc = parameter.h_acc;\n        sensors->gps_extended.track_acc = parameter.track_acc;\n        sensors->gps_extended.alt_ellipsoid = parameter.alt_elipsiod;\n        sensors->gps_extended.h_acc = parameter.h_acc;\n        sensors->gps_extended.v_acc = parameter.v_acc;*/\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n        sensors->voltages.voltage[sensors->voltages.voltage_count] = parameter.voltage;\n        sensors->voltages.voltage_count++;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_BATERY] = true;\n        sensors->battery.current = parameter.current;\n        sensors->battery.capacity = parameter.consumption;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_TEMP] = true;\n        sensors->temperature.temperature[sensors->temperature.temperature_count] = parameter.ntc;\n        sensors->temperature.temperature_count++;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensors->enabled_sensors[TYPE_BARO] = true;\n        sensors->baro.altitude = parameter.altitude;\n        sensors->baro.vspeed = parameter.vspeed;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensors->enabled_sensors[TYPE_BARO] = true;\n        sensors->baro.altitude = parameter.altitude;\n        sensors->baro.vspeed = parameter.vspeed;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensors->enabled_sensors[TYPE_BARO] = true;\n        sensors->baro.altitude = parameter.altitude;\n        sensors->baro.vspeed = parameter.vspeed;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_AIRSPEED] = true;\n        sensors->airspeed.speed = parameter.airspeed;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_ATTITUDE] = true;\n        sensors->attitude.pitch = parameter.pitch;\n        sensors->attitude.roll = parameter.roll;\n        sensors->attitude.yaw = parameter.yaw;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_lipo) {\n        float *cell_prev = 0;\n        sensors->voltages.cell_count = malloc(sizeof(uint8_t));\n        uint8_t c1 = 0, c2 = 0;\n        if (config->lipo_cells > 0) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x40,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            c1 = parameter.cell_count;\n            *parameter.cell_prev = 0;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n            sensors->enabled_sensors[TYPE_VOLTAGES] = true;\n            for (uint i = 0; i < 3; i++) {\n                sensors->voltages.cell[i] = parameter.cell[i];\n            }\n        }\n        if (config->lipo_cells > 3) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x41,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells - 3, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            c2 = parameter.cell_count;\n            parameter.cell_prev = cell_prev;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n            for (uint i = 0; i < 3; i++) {\n                sensors->voltages.cell[i + 3] = parameter.cell[i];\n            }\n        }\n        *sensors->voltages.cell_count = c1 + c2;\n    }\n}"
  },
  {
    "path": "board/project/protocol/crsf.h",
    "content": "#ifndef CRSF_H\n#define CRSF_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid crsf_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/fbus.c",
    "content": "#include \"fbus.h\"\n\n#include <math.h>\n#include <semphr.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gpio.h\"\n#include \"gps.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"smartport.h\"\n#include \"stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\ntypedef struct fbus_packet_t {\n    uint8_t len;\n    uint8_t sensor_id;\n    uint8_t frame_id;\n    uint16_t data_id;\n    uint32_t value;\n    uint8_t crc;\n} __attribute__((packed)) fbus_packet_t;\n\nstatic SemaphoreHandle_t semaphore_sensor = NULL;\nstatic uint8_t sensor_id;\n\nstatic void process(smartport_parameters_t *parameter);\nstatic void sensor_task(void *parameters);\nstatic void sensor_void_task(void *parameters);\nstatic void sensor_double_task(void *parameters);\nstatic void sensor_coordinates_task(void *parameters);\nstatic void sensor_datetime_task(void *parameters);\nstatic void sensor_cell_task(void *parameters);\nstatic void sensor_cell_individual_task(void *parameters);\nstatic void sensor_gpio_task(void *parameters);\nstatic void send_packet(uint8_t frame_id, uint16_t data_id, uint32_t value);\nstatic void set_config(smartport_parameters_t *parameter);\n\nvoid fbus_task(void *parameters) {\n    smartport_parameters_t parameter;\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    semaphore_sensor = xSemaphoreCreateBinary();\n    xSemaphoreTake(semaphore_sensor, 0);\n    set_config(&parameter);\n    debug(\"\\nFbus init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void sensor_task(void *parameters) {\n    smartport_sensor_parameters_t parameter = *(smartport_sensor_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        int32_t data_formatted = smartport_format(parameter.data_id, *parameter.value);\n        debug(\"\\nFBUS. Sensor (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, parameter.data_id, data_formatted);\n    }\n}\n\nstatic void sensor_gpio_task(void *parameters) {\n    smartport_sensor_gpio_parameters_t parameter = *(smartport_sensor_gpio_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint cont = 0;\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (parameter.gpio_mask) {\n            while (!(parameter.gpio_mask & (1 << cont))) {\n                cont++;\n                if (cont == 6) cont = 0;\n            }\n            float value = *parameter.value & (1 << cont) ? 1 : 0;\n            uint16_t data_id = parameter.data_id + 17 + cont;\n            int32_t data_formatted = smartport_format(data_id, value);\n            debug(\"\\nFBUS. Sensor GPIO (%u) > GPIO: %u STATE: %u > \", uxTaskGetStackHighWaterMark(NULL), 17 + cont,\n                  (uint)value);\n            send_packet(0x10, data_id, data_formatted);\n            cont++;\n            if (cont == 6) cont = 0;\n        }\n    }\n}\n\nstatic void sensor_void_task(void *parameters) {\n    while (1) {\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (context.debug == 2) printf(\"\\nFBUS. Sensor void (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0, 0, 0);\n    }\n}\n\nstatic void sensor_double_task(void *parameters) {\n    smartport_sensor_double_parameters_t parameter = *(smartport_sensor_double_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        float v_l = parameter.value_l ? *parameter.value_l : 0.0f;\n        float v_h = parameter.value_h ? *parameter.value_h : 0.0f;\n        uint32_t data_formatted = smartport_format_double(parameter.data_id, v_l, v_h);\n        debug(\"\\nFBUS. Sensor double (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, parameter.data_id, data_formatted);\n    }\n}\n\nstatic void sensor_coordinates_task(void *parameters) {\n    smartport_sensor_coordinate_parameters_t parameter = *(smartport_sensor_coordinate_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        uint32_t data_formatted;\n        if (parameter.type == SMARTPORT_LATITUDE)\n            data_formatted = smartport_format_coordinate(parameter.type, *parameter.latitude);\n        else\n            data_formatted = smartport_format_coordinate(parameter.type, *parameter.longitude);\n        parameter.type = !parameter.type;\n        debug(\"\\nFBUS. Sensor coordinates (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, GPS_LONG_LATI_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_datetime_task(void *parameters) {\n    smartport_sensor_datetime_parameters_t parameter = *(smartport_sensor_datetime_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        uint32_t data_formatted;\n        if (parameter.type == SMARTPORT_DATE)\n            data_formatted = smartport_format_datetime(parameter.type, *parameter.date);\n        else\n            data_formatted = smartport_format_datetime(parameter.type, *parameter.time);\n        parameter.type = !parameter.type;\n        debug(\"\\nFBUS. Sensor datetime (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, GPS_TIME_DATE_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_cell_task(void *parameters) {\n    smartport_sensor_cell_parameters_t parameter = *(smartport_sensor_cell_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t cell_index = 0;\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (!*parameter.cell_count) return;\n        uint32_t data_formatted = smartport_format_cell(cell_index, *parameter.cell_voltage);\n        cell_index++;\n        if (cell_index > *parameter.cell_count - 1) cell_index = 0;\n        debug(\"\\nFBUS. Sensor cell (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, CELLS_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_cell_individual_task(void *parameters) {\n    smartport_sensor_cell_individual_parameters_t parameter =\n        *(smartport_sensor_cell_individual_parameters_t *)parameters;\n\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t cell_index = 0;\n\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n\n        // No cells configured → skip\n        if (!parameter.cell_count || *parameter.cell_count == 0) {\n            continue;\n        }\n\n        float value = 0.0f;\n\n        // Safety: check index and pointer before dereferencing\n        if (cell_index < *parameter.cell_count && parameter.cell_voltage[cell_index] != NULL) {\n            value = *parameter.cell_voltage[cell_index];\n        }\n\n        uint32_t data_formatted = smartport_format_cell(cell_index, value);\n\n        debug(\"\\nFBUS. Sensor cell (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, CELLS_FIRST_ID, data_formatted);\n\n        // Next cell\n        cell_index++;\n        if (cell_index >= *parameter.cell_count) {\n            cell_index = 0;\n        }\n    }\n}\n\nstatic void process(smartport_parameters_t *parameter) {\n    uint length = uart0_available();\n    if (length < 3 || length > 128) return;\n\n    uint8_t data[128];\n    uart0_read_bytes(data, MIN(length, 128));\n\n    debug(\"\\nFBUS (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(data, length, \"0x%X \");\n\n    uint idx = 0;\n    if (data[0] != 0x08) {\n        // Skip to next frame based on LEN\n        idx = data[0] + 2;\n        // Control frame (0xFF ...), RSSI byte present but not counted in LEN\n        if (data[1] == 0xFF) idx++;\n    }\n    if (idx + 10 != length)\n        return;  // Expecting exactly one frame with LEN=8 (plus LEN and CRC, ignoring RSSI if present)\n    if (data[idx] == 0x08 && data[idx + 1] == smartport_sensor_id_to_crc(sensor_id) && data[idx + 2] == 0x10) {\n        xSemaphoreGive(semaphore_sensor);\n        // FBUS requires a small turnaround delay before replying (RTOS tick = 2ms).\n        vTaskDelay(pdMS_TO_TICKS(2));\n        xSemaphoreTake(semaphore_sensor, 0);\n    }\n}\n\nstatic void send_packet(uint8_t frame_id, uint16_t data_id, uint32_t value) {\n    fbus_packet_t packet = {0};\n    packet.len = 0x08;\n    packet.sensor_id = smartport_sensor_id_to_crc(sensor_id);\n    packet.frame_id = 0x10;\n    packet.data_id = data_id;\n    packet.value = value;\n    packet.crc = smartport_get_crc(&packet.len, sizeof(packet) - 1);  // CRC over LEN to before CRC\n    uart0_write_bytes((uint8_t *)&packet, sizeof(packet));\n    debug_buffer((uint8_t *)&packet, sizeof(packet), \"0x%X \");\n    vTaskResume(context.led_task_handle);\n}\n\nstatic void set_config(smartport_parameters_t *parameter) {\n    config_t *config = config_read();\n    uart0_begin(460800, UART_RECEIVER_TX, UART_RECEIVER_RX, TIMEOUT_US, 8, 1, UART_PARITY_NONE, config->fbus_inverted,\n                true);\n    TaskHandle_t task_handle;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    parameter->sensor_id = config->smartport_sensor_id;\n    sensor_id = config->smartport_sensor_id;\n    parameter->data_id = 0x5100;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = NULL;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_task\", STACK_SENSOR_SMARTPORT_DOUBLE, (void *)&parameter_sensor_double,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = NULL;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 1;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 1;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.calc_consumption = config->smart_esc_calc_consumption;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_individual_parameters_t parameter_sensor_cell;\n        // rpm & consumption\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // voltage & current\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // bec. voltage & current\n        parameter_sensor_double.data_id = SBEC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_fet\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_bec\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_bat\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_bat;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // current_bat\n        parameter_sensor.data_id = CURR_FIRST_ID + 1;\n        parameter_sensor.value = parameter.current_bat;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // cells\n        parameter_sensor_cell.cell_count = parameter.cells;  // Pointer provided by ESC_SMART task\n        for (uint i = 0; i < 18; i++) {\n            parameter_sensor_cell.cell_voltage[i] = parameter.cell[i];  // One pointer per cell\n        }\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n\n        xTaskCreate(sensor_cell_individual_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL,\n                    (void *)&parameter_sensor_cell, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // cycles\n        /*parameter_sensor.data_id = DIY_FIRST_ID + 100;\n        parameter_sensor.value = parameter.cycles;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_cell_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);*/\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n\n        // RPM and Consumption sensor\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // Main voltage and current sensor\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // FET Temperature sensor\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // BEC Temperature sensor\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // BEC voltage and current sensor\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 2;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // Cell voltage sensor\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_coordinate_parameters_t parameter_sensor_coordinate;\n        parameter_sensor_coordinate.type = SMARTPORT_LATITUDE;\n        parameter_sensor_coordinate.latitude = parameter.lat;\n        parameter_sensor_coordinate.longitude = parameter.lon;\n        parameter_sensor_coordinate.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_coordinates_task, \"sensor_coordinates_task\", STACK_SENSOR_SMARTPORT,\n                    (void *)&parameter_sensor_coordinate, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_datetime_parameters_t parameter_sensor_datetime;\n        parameter_sensor_datetime.type = SMARTPORT_DATE;\n        parameter_sensor_datetime.date = parameter.date;\n        parameter_sensor_datetime.time = parameter.time;\n        parameter_sensor_datetime.rate = 1000;\n        xTaskCreate(sensor_datetime_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor_datetime, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = GPS_ALT_FIRST_ID;\n        parameter_sensor.value = parameter.alt;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GPS_SPEED_FIRST_ID;\n        parameter_sensor.value = parameter.spd;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GPS_COURS_FIRST_ID;\n        parameter_sensor.value = parameter.cog;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID + 1;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 3;\n        parameter_sensor.value = parameter.sat;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIST_FIRST_ID;\n        parameter_sensor.value = parameter.dist;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 5;\n        parameter_sensor.value = parameter.pdop;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = A3_FIRST_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = CURR_FIRST_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = NULL;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_double_task, \"sensor_task\", STACK_SENSOR_SMARTPORT_DOUBLE, (void *)&parameter_sensor_double,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.ntc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = AIR_SPEED_FIRST_ID;\n        parameter_sensor.value = parameter.airspeed;\n        parameter_sensor.rate = config->refresh_rate_airspeed;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_fuel_flow) {\n        fuel_meter_parameters_t parameter = {config->fuel_flow_ml_per_pulse, malloc(sizeof(float)),\n                                             malloc(sizeof(float))};\n        xTaskCreate(fuel_meter_task, \"fuel_meter_task\", STACK_FUEL_METER, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = GASSUIT_FLOW_FIRST_ID;\n        parameter_sensor.value = parameter.consumption_instant;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GASSUIT_RES_VOL_FIRST_ID;\n        parameter_sensor.value = parameter.consumption_total;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->gpio_mask) {\n        gpio_parameters_t parameter = {config->gpio_mask, config->gpio_interval, malloc(sizeof(float))};\n        xTaskCreate(gpio_task, \"gpio_task\", STACK_GPIO, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_gpio_parameters_t parameter_sensor;\n        parameter_sensor.data_id = DIY_FIRST_ID;\n        parameter_sensor.value = parameter.value;\n        parameter_sensor.rate = config->gpio_interval;\n        parameter_sensor.gpio_mask = config->gpio_mask;\n        xTaskCreate(sensor_gpio_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = DIY_FIRST_ID + 6;\n        parameter_sensor.value = parameter.pitch;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 7;\n        parameter_sensor.value = parameter.roll;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 8;\n        parameter_sensor.value = parameter.yaw;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCX_FIRST_ID;\n        parameter_sensor.value = parameter.acc_x;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCY_FIRST_ID;\n        parameter_sensor.value = parameter.acc_y;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCZ_FIRST_ID;\n        parameter_sensor.value = parameter.acc_z;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 9;\n        parameter_sensor.value = parameter.acc;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_lipo && config->lipo_cells > 0) {\n        smartport_sensor_cell_individual_parameters_t parameter_sensor_cell;\n        float *cell_prev = NULL;\n\n        // Maximum supported cells: 6 (two INA3221 devices)\n        uint8_t lipo_cells = MIN(config->lipo_cells, 6);\n\n        // Initialize all cell pointers to NULL (safety)\n        for (uint i = 0; i < 18; i++) {\n            parameter_sensor_cell.cell_voltage[i] = NULL;\n        }\n\n        // Configure task parameters\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        parameter_sensor_cell.cell_count = malloc(sizeof(uint8_t));\n        *parameter_sensor_cell.cell_count = lipo_cells;\n\n        // --- First INA3221: cells 0–2 -----------------------------------------\n        uint8_t cells_first = MIN(lipo_cells, 3);\n\n        if (cells_first > 0) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x40,\n                .filter = config->ina3221_filter,\n                .cell_count = cells_first,\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n\n            // First INA has no previous cell reference\n            *parameter.cell_prev = 0;\n            cell_prev = parameter.cell[2];\n\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n            // Store cell pointers for SmartPort\n            for (uint i = 0; i < cells_first; i++) {\n                parameter_sensor_cell.cell_voltage[i] = parameter.cell[i];\n            }\n        }\n\n        // --- Second INA3221: cells 3–5 ----------------------------------------\n        if (lipo_cells > 3) {\n            uint8_t cells_second = MIN((uint8_t)(lipo_cells - 3), (uint8_t)3);\n\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x41,\n                .filter = config->ina3221_filter,\n                .cell_count = cells_second,\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = cell_prev,  // Link to the last cell of the previous INA\n            };\n\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n            // Store cell pointers for SmartPort\n            for (uint i = 0; i < cells_second; i++) {\n                parameter_sensor_cell.cell_voltage[i + 3] = parameter.cell[i];\n            }\n        }\n\n        // --- Start SmartPort task: cycle through individual cells -------------\n        xTaskCreate(sensor_cell_individual_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL,\n                    (void *)&parameter_sensor_cell, 3, &task_handle);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}\n"
  },
  {
    "path": "board/project/protocol/fbus.h",
    "content": "#ifndef FBUS_H\n#define FBUS_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid fbus_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/fport.c",
    "content": "#include \"fport.h\"\n\n#include <math.h>\n#include <semphr.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gpio.h\"\n#include \"gps.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"smartport.h\"\n#include \"stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n#define FPORT_DELIM 0x7E\n#define FPORT_ESC 0x7D\n#define FPORT_XOR 0x20\n#define FPORT_FRAME_MAX 96\n\nstatic SemaphoreHandle_t semaphore_sensor = NULL;\n\nstatic void process(smartport_parameters_t *parameter);\nstatic void sensor_task(void *parameters);\nstatic void sensor_void_task(void *parameters);\nstatic void sensor_double_task(void *parameters);\nstatic void sensor_coordinates_task(void *parameters);\nstatic void sensor_datetime_task(void *parameters);\nstatic void sensor_cell_task(void *parameters);\nstatic void sensor_cell_individual_task(void *parameters);\nstatic void sensor_gpio_task(void *parameters);\nstatic void send_packet(uint8_t frame_id, uint16_t data_id, uint32_t value);\nstatic void set_config(smartport_parameters_t *parameter);\n\nvoid fport_task(void *parameters) {\n    smartport_parameters_t parameter;\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    semaphore_sensor = xSemaphoreCreateBinary();\n    xSemaphoreTake(semaphore_sensor, 0);\n    set_config(&parameter);\n    debug(\"\\nFport init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void sensor_task(void *parameters) {\n    smartport_sensor_parameters_t parameter = *(smartport_sensor_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        int32_t data_formatted = smartport_format(parameter.data_id, *parameter.value);\n        debug(\"\\nFPort. Sensor (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, parameter.data_id, data_formatted);\n    }\n}\n\nstatic void sensor_gpio_task(void *parameters) {\n    smartport_sensor_gpio_parameters_t parameter = *(smartport_sensor_gpio_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint cont = 0;\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (parameter.gpio_mask) {\n            while (!(parameter.gpio_mask & (1 << cont))) {\n                cont++;\n                if (cont == 6) cont = 0;\n            }\n            float value = *parameter.value & (1 << cont) ? 1 : 0;\n            uint16_t data_id = parameter.data_id + 17 + cont;\n            int32_t data_formatted = smartport_format(data_id, value);\n            debug(\"\\nFPort. Sensor GPIO (%u) > GPIO: %u STATE: %u > \", uxTaskGetStackHighWaterMark(NULL), 17 + cont,\n                  (uint)value);\n            send_packet(0x10, data_id, data_formatted);\n            cont++;\n            if (cont == 6) cont = 0;\n        }\n    }\n}\n\nstatic void sensor_void_task(void *parameters) {\n    while (1) {\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (context.debug == 2) printf(\"\\nFPort. Sensor void (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0, 0, 0);\n    }\n}\n\nstatic void sensor_double_task(void *parameters) {\n    smartport_sensor_double_parameters_t parameter = *(smartport_sensor_double_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        float v_l = parameter.value_l ? *parameter.value_l : 0.0f;\n        float v_h = parameter.value_h ? *parameter.value_h : 0.0f;\n        uint32_t data_formatted = smartport_format_double(parameter.data_id, v_l, v_h);\n        debug(\"\\nFPort. Sensor double (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, parameter.data_id, data_formatted);\n    }\n}\n\nstatic void sensor_coordinates_task(void *parameters) {\n    smartport_sensor_coordinate_parameters_t parameter = *(smartport_sensor_coordinate_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        uint32_t data_formatted;\n        if (parameter.type == SMARTPORT_LATITUDE)\n            data_formatted = smartport_format_coordinate(parameter.type, *parameter.latitude);\n        else\n            data_formatted = smartport_format_coordinate(parameter.type, *parameter.longitude);\n        parameter.type = !parameter.type;\n        debug(\"\\nFPort. Sensor coordinates (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, GPS_LONG_LATI_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_datetime_task(void *parameters) {\n    smartport_sensor_datetime_parameters_t parameter = *(smartport_sensor_datetime_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        uint32_t data_formatted;\n        if (parameter.type == SMARTPORT_DATE)\n            data_formatted = smartport_format_datetime(parameter.type, *parameter.date);\n        else\n            data_formatted = smartport_format_datetime(parameter.type, *parameter.time);\n        parameter.type = !parameter.type;\n        debug(\"\\nFPort. Sensor datetime (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, GPS_TIME_DATE_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_cell_task(void *parameters) {\n    smartport_sensor_cell_parameters_t parameter = *(smartport_sensor_cell_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t cell_index = 0;\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (!*parameter.cell_count) return;\n        uint32_t data_formatted = smartport_format_cell(cell_index, *parameter.cell_voltage);\n        cell_index++;\n        if (cell_index > *parameter.cell_count - 1) cell_index = 0;\n        debug(\"\\nFPort. Sensor cell (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, CELLS_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_cell_individual_task(void *parameters) {\n    smartport_sensor_cell_individual_parameters_t parameter =\n        *(smartport_sensor_cell_individual_parameters_t *)parameters;\n\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t cell_index = 0;\n\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n\n        // No cells configured → skip\n        if (!parameter.cell_count || *parameter.cell_count == 0) {\n            continue;\n        }\n\n        float value = 0.0f;\n\n        // Safety: check index and pointer before dereferencing\n        if (cell_index < *parameter.cell_count && parameter.cell_voltage[cell_index] != NULL) {\n            value = *parameter.cell_voltage[cell_index];\n        }\n\n        uint32_t data_formatted = smartport_format_cell(cell_index, value);\n\n        debug(\"\\nFPort. Sensor cell (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, CELLS_FIRST_ID, data_formatted);\n\n        // Next cell\n        cell_index++;\n        if (cell_index >= *parameter.cell_count) {\n            cell_index = 0;\n        }\n    }\n}\n\nstatic void process(smartport_parameters_t *parameter) {\n    uint length = uart0_available();\n    if (length < 3 || length > 128) return;\n\n    uint8_t data[128];\n    uart0_read_bytes(data, MIN(length, 128));\n    \n    // Byte stuffing in-place\n    uint8_t delta = 0;\n    uint i;\n    for (i = 0; i < length; i++) {\n        data[i] = data[i + delta];\n        if (data[i] == 0x7D) {\n            delta++;\n            data[i] = data[i + delta] ^ 0x20;\n        }\n    }\n    debug(\"\\nFPORT (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(data, length - delta, \"0x%X \");\n\n    uint idx = 0;\n    if (data[1] != 0x08) {\n        // Skip to next frame based on LEN\n        idx = data[1] + 5;\n    }\n    if (idx + 11 != length - delta)\n        return;  // Expecting exactly one frame with LEN=8 (plus LEN and CRC, ignoring RSSI if present)\n    if (data[idx] == 0x08 && data[idx + 2] == 0x10) {\n        xSemaphoreGive(semaphore_sensor);\n        // FBUS requires a small turnaround delay before replying (RTOS tick = 2ms).\n        vTaskDelay(pdMS_TO_TICKS(2));\n        xSemaphoreTake(semaphore_sensor, 0);\n    }\n}\n\nstatic void send_packet(uint8_t frame_id, uint16_t data_id, uint32_t value) {\n    uint16_t crc = 0;\n    uint8_t *u8p;\n    // len\n    smartport_send_byte(0x08, &crc);\n    // type\n    smartport_send_byte(0x01, &crc);\n    // frame_id\n    smartport_send_byte(frame_id, &crc);\n    // data_id\n    u8p = (uint8_t *)&data_id;\n    smartport_send_byte(u8p[0], &crc);\n    smartport_send_byte(u8p[1], &crc);\n    // value\n    u8p = (uint8_t *)&value;\n    smartport_send_byte(u8p[0], &crc);\n    smartport_send_byte(u8p[1], &crc);\n    smartport_send_byte(u8p[2], &crc);\n    smartport_send_byte(u8p[3], &crc);\n    // crc\n    smartport_send_byte(0xFF - (uint8_t)crc, NULL);\n    // blink\n    vTaskResume(context.led_task_handle);\n}\n\nstatic void set_config(smartport_parameters_t *parameter) {\n    config_t *config = config_read();\n    uart0_begin(115200, UART_RECEIVER_TX, UART_RECEIVER_RX, TIMEOUT_US, 8, 1, UART_PARITY_NONE, config->fport_inverted,\n                true);\n    TaskHandle_t task_handle;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    parameter->data_id = 0x5100;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = NULL;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_task\", STACK_SENSOR_SMARTPORT_DOUBLE, (void *)&parameter_sensor_double,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = NULL;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 1;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 1;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.calc_consumption = config->smart_esc_calc_consumption;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_individual_parameters_t parameter_sensor_cell;\n        // rpm & consumption\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // voltage & current\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // bec. voltage & current\n        parameter_sensor_double.data_id = SBEC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_fet\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_bec\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_bat\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_bat;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // current_bat\n        parameter_sensor.data_id = CURR_FIRST_ID + 1;\n        parameter_sensor.value = parameter.current_bat;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // cells\n        parameter_sensor_cell.cell_count = parameter.cells;  // Pointer provided by ESC_SMART task\n        for (uint i = 0; i < 18; i++) {\n            parameter_sensor_cell.cell_voltage[i] = parameter.cell[i];  // One pointer per cell\n        }\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n\n        xTaskCreate(sensor_cell_individual_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL,\n                    (void *)&parameter_sensor_cell, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // cycles\n        /*parameter_sensor.data_id = DIY_FIRST_ID + 100;\n        parameter_sensor.value = parameter.cycles;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_cell_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);*/\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n\n        // RPM and Consumption sensor\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // Main voltage and current sensor\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // FET Temperature sensor\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // BEC Temperature sensor\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // BEC voltage and current sensor\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 2;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // Cell voltage sensor\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_coordinate_parameters_t parameter_sensor_coordinate;\n        parameter_sensor_coordinate.type = SMARTPORT_LATITUDE;\n        parameter_sensor_coordinate.latitude = parameter.lat;\n        parameter_sensor_coordinate.longitude = parameter.lon;\n        parameter_sensor_coordinate.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_coordinates_task, \"sensor_coordinates_task\", STACK_SENSOR_SMARTPORT,\n                    (void *)&parameter_sensor_coordinate, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_datetime_parameters_t parameter_sensor_datetime;\n        parameter_sensor_datetime.type = SMARTPORT_DATE;\n        parameter_sensor_datetime.date = parameter.date;\n        parameter_sensor_datetime.time = parameter.time;\n        parameter_sensor_datetime.rate = 1000;\n        xTaskCreate(sensor_datetime_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor_datetime, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = GPS_ALT_FIRST_ID;\n        parameter_sensor.value = parameter.alt;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GPS_SPEED_FIRST_ID;\n        parameter_sensor.value = parameter.spd;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GPS_COURS_FIRST_ID;\n        parameter_sensor.value = parameter.cog;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID + 1;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 3;\n        parameter_sensor.value = parameter.sat;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIST_FIRST_ID;\n        parameter_sensor.value = parameter.dist;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 5;\n        parameter_sensor.value = parameter.pdop;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = A3_FIRST_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = CURR_FIRST_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = NULL;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_double_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor_double, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.ntc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = AIR_SPEED_FIRST_ID;\n        parameter_sensor.value = parameter.airspeed;\n        parameter_sensor.rate = config->refresh_rate_airspeed;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_fuel_flow) {\n        fuel_meter_parameters_t parameter = {config->fuel_flow_ml_per_pulse, malloc(sizeof(float)),\n                                             malloc(sizeof(float))};\n        xTaskCreate(fuel_meter_task, \"fuel_meter_task\", STACK_FUEL_METER, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = GASSUIT_FLOW_FIRST_ID;\n        parameter_sensor.value = parameter.consumption_instant;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GASSUIT_RES_VOL_FIRST_ID;\n        parameter_sensor.value = parameter.consumption_total;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->gpio_mask) {\n        gpio_parameters_t parameter = {config->gpio_mask, config->gpio_interval, malloc(sizeof(float))};\n        xTaskCreate(gpio_task, \"gpio_task\", STACK_GPIO, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_gpio_parameters_t parameter_sensor;\n        parameter_sensor.data_id = DIY_FIRST_ID;\n        parameter_sensor.value = parameter.value;\n        parameter_sensor.rate = config->gpio_interval;\n        parameter_sensor.gpio_mask = config->gpio_mask;\n        xTaskCreate(sensor_gpio_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = DIY_FIRST_ID + 6;\n        parameter_sensor.value = parameter.pitch;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 7;\n        parameter_sensor.value = parameter.roll;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 8;\n        parameter_sensor.value = parameter.yaw;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCX_FIRST_ID;\n        parameter_sensor.value = parameter.acc_x;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCY_FIRST_ID;\n        parameter_sensor.value = parameter.acc_y;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCZ_FIRST_ID;\n        parameter_sensor.value = parameter.acc_z;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 9;\n        parameter_sensor.value = parameter.acc;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_lipo && config->lipo_cells > 0) {\n        smartport_sensor_cell_individual_parameters_t parameter_sensor_cell;\n        float *cell_prev = NULL;\n\n        // Maximum supported cells: 6 (two INA3221 devices)\n        uint8_t lipo_cells = MIN(config->lipo_cells, 6);\n\n        // Initialize all cell pointers to NULL (safety)\n        for (uint i = 0; i < 18; i++) {\n            parameter_sensor_cell.cell_voltage[i] = NULL;\n        }\n\n        // Configure task parameters\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        parameter_sensor_cell.cell_count = malloc(sizeof(uint8_t));\n        *parameter_sensor_cell.cell_count = lipo_cells;\n\n        // --- First INA3221: cells 0–2 -----------------------------------------\n        uint8_t cells_first = MIN(lipo_cells, 3);\n\n        if (cells_first > 0) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x40,\n                .filter = config->ina3221_filter,\n                .cell_count = cells_first,\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n\n            // First INA has no previous cell reference\n            *parameter.cell_prev = 0;\n            cell_prev = parameter.cell[2];\n\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n            // Store cell pointers for SmartPort\n            for (uint i = 0; i < cells_first; i++) {\n                parameter_sensor_cell.cell_voltage[i] = parameter.cell[i];\n            }\n        }\n\n        // --- Second INA3221: cells 3–5 ----------------------------------------\n        if (lipo_cells > 3) {\n            uint8_t cells_second = MIN((uint8_t)(lipo_cells - 3), (uint8_t)3);\n\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x41,\n                .filter = config->ina3221_filter,\n                .cell_count = cells_second,\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = cell_prev,  // Link to the last cell of the previous INA\n            };\n\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n            // Store cell pointers for SmartPort\n            for (uint i = 0; i < cells_second; i++) {\n                parameter_sensor_cell.cell_voltage[i + 3] = parameter.cell[i];\n            }\n        }\n\n        // --- Start SmartPort task: cycle through individual cells -------------\n        xTaskCreate(sensor_cell_individual_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL,\n                    (void *)&parameter_sensor_cell, 3, &task_handle);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}\n"
  },
  {
    "path": "board/project/protocol/fport.h",
    "content": "#ifndef FPORT_H\n#define FPORT_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid fport_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/frsky_d.c",
    "content": "#include \"frsky_d.h\"\n\n#include <math.h>\n#include <semphr.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"gps.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n/* FrSky D Data Id */\n#define FRSKY_D_GPS_ALT_BP_ID 0x01\n#define FRSKY_D_TEMP1_ID 0x02\n#define FRSKY_D_RPM_ID 0x03\n#define FRSKY_D_FUEL_ID 0x04\n#define FRSKY_D_TEMP2_ID 0x05\n#define FRSKY_D_CELL_VOLT_ID 0x06\n#define FRSKY_D_GPS_ALT_AP_ID 0x09\n#define FRSKY_D_BARO_ALT_BP_ID 0x10\n#define FRSKY_D_GPS_SPEED_BP_ID 0x11\n#define FRSKY_D_GPS_LONG_BP_ID 0x12\n#define FRSKY_D_GPS_LAT_BP_ID 0x13\n#define FRSKY_D_GPS_COURS_BP_ID 0x14\n#define FRSKY_D_GPS_DAY_MONTH_ID 0x15\n#define FRSKY_D_GPS_YEAR_ID 0x16\n#define FRSKY_D_GPS_HOUR_MIN_ID 0x17\n#define FRSKY_D_GPS_SEC_ID 0x18\n#define FRSKY_D_GPS_SPEED_AP_ID 0x19\n#define FRSKY_D_GPS_LONG_AP_ID 0x1A\n#define FRSKY_D_GPS_LAT_AP_ID 0x1B\n#define FRSKY_D_GPS_COURS_AP_ID 0x1C\n#define FRSKY_D_BARO_ALT_AP_ID 0x21\n#define FRSKY_D_GPS_LONG_EW_ID 0x22\n#define FRSKY_D_GPS_LAT_NS_ID 0x23\n#define FRSKY_D_ACCEL_X_ID 0x24\n#define FRSKY_D_ACCEL_Y_ID 0x25\n#define FRSKY_D_ACCEL_Z_ID 0x26\n#define FRSKY_D_CURRENT_ID 0x28\n#define FRSKY_D_VARIO_ID 0x30\n#define FRSKY_D_VFAS_ID 0x39\n#define FRSKY_D_VOLTS_BP_ID 0x3A\n#define FRSKY_D_VOLTS_AP_ID 0x3B\n#define FRSKY_D_FRSKY_LAST_ID 0x3F\n#define FRSKY_D_D_RSSI_ID 0xF0\n#define FRSKY_D_D_A1_ID 0xF1\n#define FRSKY_D_D_A2_ID 0xF2\n\n#define FRSKY_D_INTERVAL 10\n\ntypedef struct frsky_d_sensor_parameters_t {\n    uint8_t data_id;\n    float *value;\n    uint16_t rate;\n\n} frsky_d_sensor_parameters_t;\n\ntypedef struct frsky_d_sensor_cell_parameters_t {\n    float *voltage;\n    uint8_t *count;\n    uint16_t rate;\n\n} frsky_d_sensor_cell_parameters_t;\n\nstatic SemaphoreHandle_t semaphore = NULL;\n\nstatic void sensor_task(void *parameters);\nstatic void send_packet(uint8_t dataId, uint16_t value);\nstatic void send_byte(uint8_t c, bool header);\nstatic uint16_t format(uint8_t data_id, float value);\nstatic void set_config();\n\nvoid frsky_d_task(void *parameters) {\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(9600, UART_RECEIVER_TX, UART_RECEIVER_RX, 0, 8, 1, UART_PARITY_NONE, true, false);\n    semaphore = xSemaphoreCreateMutex();\n    set_config();\n    debug(\"\\nFrsky D init\");\n    vTaskSuspend(NULL);\n    vTaskDelete(NULL);\n}\n\nstatic void sensor_task(void *parameters) {\n    frsky_d_sensor_parameters_t parameter = *(frsky_d_sensor_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore, portMAX_DELAY);\n        uint16_t data_formatted = format(parameter.data_id, *parameter.value);\n        debug(\"\\nFrSky D. Sensor (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(parameter.data_id, data_formatted);\n        xSemaphoreGive(semaphore);\n    }\n}\n\nstatic void sensor_cell_task(void *parameters) {\n    frsky_d_sensor_cell_parameters_t parameter = *(frsky_d_sensor_cell_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t cell_index = 0;\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore, portMAX_DELAY);\n        uint value = *parameter.voltage * 50;\n        uint16_t data_formatted = (cell_index << 4) | ((value & 0xF00) >> 8) | ((value & 0x0FF) << 8);\n        cell_index++;\n        cell_index %= *parameter.count;\n        debug(\"\\nFrSky D. Sensor cell (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(FRSKY_D_CELL_VOLT_ID, data_formatted);\n        xSemaphoreGive(semaphore);\n    }\n}\n\nstatic uint16_t format(uint8_t data_id, float value) {\n    if (data_id == FRSKY_D_GPS_ALT_BP_ID || data_id == FRSKY_D_BARO_ALT_BP_ID || data_id == FRSKY_D_GPS_SPEED_BP_ID ||\n        data_id == FRSKY_D_GPS_COURS_BP_ID)\n        return (int16_t)value;\n\n    if (data_id == FRSKY_D_GPS_ALT_AP_ID || data_id == FRSKY_D_BARO_ALT_AP_ID || data_id == FRSKY_D_GPS_SPEED_AP_ID ||\n        data_id == FRSKY_D_GPS_COURS_AP_ID)\n        return (abs(value) - (int16_t)abs(value)) * 10000;\n\n    if (data_id == FRSKY_D_GPS_LONG_AP_ID || data_id == FRSKY_D_GPS_LAT_AP_ID) {\n        float min = fabs(value) * 60;\n        return (min - (uint)min) * 10000;\n    }\n\n    if (data_id == FRSKY_D_VOLTS_BP_ID) return value * 2;\n\n    if (data_id == FRSKY_D_VOLTS_AP_ID) return ((value * 2) - (int16_t)(value * 2)) * 10000;\n\n    if (data_id == FRSKY_D_GPS_LONG_BP_ID || data_id == FRSKY_D_GPS_LAT_BP_ID) {\n        float coord = fabs(value);\n        uint8_t deg = coord;\n        uint8_t min = (coord - deg) * 60;\n        char buf[7];\n        sprintf(buf, \"%u%02u\", deg, min);  // (ddd)mm\n        return atoi(buf);\n    }\n\n    if (data_id == FRSKY_D_GPS_LONG_EW_ID) {\n        if (value >= 0) return 'E';\n        return 'O';\n    }\n\n    if (data_id == FRSKY_D_GPS_LAT_NS_ID) {\n        if (value >= 0) return 'N';\n        return 'S';\n    }\n\n    if (data_id == FRSKY_D_GPS_YEAR_ID) {\n        return ((uint)value % 100) + 2000;  // ddmmyy -> yyyy\n    }\n\n    if (data_id == FRSKY_D_GPS_DAY_MONTH_ID) {  // ddmmyy -> ddmm\n        return value / 100;\n    }\n\n    if (data_id == FRSKY_D_GPS_HOUR_MIN_ID) {  // hhmmss -> hhmm\n        return value / 100;\n    }\n\n    if (data_id == FRSKY_D_GPS_SEC_ID) {  // hhmmss -> ss\n        return (uint)value % 100;\n    }\n\n    if (data_id == FRSKY_D_CURRENT_ID || data_id == FRSKY_D_VFAS_ID) return round(value * 10);\n\n    if (data_id == FRSKY_D_RPM_ID) return value / 60;\n\n    return round(value);\n}\n\nstatic void send_byte(uint8_t c, bool header) {\n    if ((c == 0x5D || c == 0x5E) && !header) {\n        uart0_write(0x5D);\n        c ^= 0x60;\n    }\n    uart0_write(c);\n    debug(\"%X \", c);\n}\n\nstatic void send_packet(uint8_t data_id, uint16_t value) {\n    uint8_t *u8p;\n    // header\n    send_byte(0x5E, true);\n    // data_id\n    send_byte(data_id, false);\n    // value\n    u8p = (uint8_t *)&value;\n    send_byte(u8p[0], false);\n    send_byte(u8p[1], false);\n    // footer\n    send_byte(0x5E, true);\n\n    // blink\n    vTaskResume(context.led_task_handle);\n}\n\nstatic void set_config() {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    frsky_d_sensor_parameters_t parameter_sensor;\n    frsky_d_sensor_cell_parameters_t parameter_sensor_cell;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP2_ID;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP2_ID;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP2_ID;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        /*parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);*/\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP2_ID;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_TEMP2_ID;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        frsky_d_sensor_parameters_t parameter_sensor;\n        frsky_d_sensor_cell_parameters_t parameter_sensor_cell;\n\n        parameter_sensor.data_id = FRSKY_D_RPM_ID;\n        parameter_sensor.value = parameter.rpm;\n        parameter_sensor.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_TEMP2_ID;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor_cell.voltage = parameter.cell_voltage;\n        parameter_sensor_cell.count = parameter.cell_count;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_FRSKY_D_CELL, (void *)&parameter_sensor_cell, 2,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_FUEL_ID;\n        parameter_sensor.value = parameter.consumption;\n        parameter_sensor.rate = config->refresh_rate_consumption;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_GPS_LONG_BP_ID;\n        parameter_sensor.value = parameter.lon;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_LONG_AP_ID;\n        parameter_sensor.value = parameter.lon;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_LONG_EW_ID;\n        parameter_sensor.value = parameter.lon;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_LAT_BP_ID;\n        parameter_sensor.value = parameter.lat;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_LAT_AP_ID;\n        parameter_sensor.value = parameter.lat;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_LAT_NS_ID;\n        parameter_sensor.value = parameter.lat;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_ALT_BP_ID;\n        parameter_sensor.value = parameter.alt;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_ALT_AP_ID;\n        parameter_sensor.value = parameter.alt;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_SPEED_BP_ID;\n        parameter_sensor.value = parameter.spd;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_SPEED_AP_ID;\n        parameter_sensor.value = parameter.spd;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_COURS_BP_ID;\n        parameter_sensor.value = parameter.cog;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_COURS_AP_ID;\n        parameter_sensor.value = parameter.cog;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_YEAR_ID;\n        parameter_sensor.value = parameter.date;\n        parameter_sensor.rate = 1000;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_DAY_MONTH_ID;\n        parameter_sensor.value = parameter.date;\n        parameter_sensor.rate = 1000;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_HOUR_MIN_ID;\n        parameter_sensor.value = parameter.time;\n        parameter_sensor.rate = 1000;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_SEC_ID;\n        parameter_sensor.value = parameter.time;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VARIO_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_BP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VOLTS_AP_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_CURRENT_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        /**new_sensor = (sensor_frsky_d_t){FRSKY_D_FUEL_ID, parameter.consumption, config->refresh_rate_consumption};\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);*/\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_TEMP1_ID;\n        parameter_sensor.value = parameter.ntc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        parameter_sensor.data_id = FRSKY_D_BARO_ALT_BP_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_BARO_ALT_AP_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VARIO_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_BARO_ALT_BP_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_BARO_ALT_AP_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        parameter_sensor.data_id = FRSKY_D_VARIO_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        parameter_sensor.data_id = FRSKY_D_BARO_ALT_BP_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_BARO_ALT_AP_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_VARIO_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_GPS_SPEED_BP_ID;\n        parameter_sensor.value = parameter.airspeed;\n        parameter_sensor.rate = config->refresh_rate_airspeed;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_GPS_SPEED_AP_ID;\n        parameter_sensor.value = parameter.airspeed;\n        parameter_sensor.rate = config->refresh_rate_airspeed;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_FRSKY_D, (void *)&parameter_sensor, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = FRSKY_D_ACCEL_X_ID;\n        parameter_sensor.value = parameter.acc_x;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_ACCEL_Y_ID;\n        parameter_sensor.value = parameter.acc_y;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = FRSKY_D_ACCEL_Z_ID;\n        parameter_sensor.value = parameter.acc_z;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}\n"
  },
  {
    "path": "board/project/protocol/frsky_d.h",
    "content": "#ifndef FRSKY_D_H\n#define FRSKY_D_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid frsky_d_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/ghst.c",
    "content": "#include \"ghst.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"gps.h\"\n#include \"ibus.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n#define GHST_DL_PACK_STAT 0x23      // Battery Status\n#define GHST_DL_GPS_PRIMARY 0x25    // Primary GPS Data\n#define GHST_DL_GPS_SECONDARY 0x26  // Secondary GPS Data\n#define GHST_DL_MAGBARO 0x27        // Magnetometer, Barometer (and Vario) Data\n#define GHST_DL_MSP_RESP 0x28       // MSP Response\n\n#define GHST_ADDR_RX 0x89\n\n#define GHST_PACKET_LEN 0x0C\n\n#define GHST_TIMEOUT_US 500\n\n#define TYPE_PACK_STAT 0\n#define TYPE_GPS_PRIMARY 1\n#define TYPE_GPS_SECONDARY 2\n#define TYPE_MAGBARO 3\n\n#define MAX_SENSORS 4\n\ntypedef struct ghst_sensor_pack_stat_formatted_t {\n    uint16_t voltage;   // 10mV\n    uint16_t current;   // 10mA\n    uint16_t consumed;  // 10mAh\n    uint8_t rx_volt;    // 100mV\n} __attribute__((packed)) ghst_sensor_pack_stat_formatted_t;\n\ntypedef struct ghst_sensor_gps_primary_formatted_t {\n    uint32_t latitude;   // degree / 10,000,000\n    uint32_t longitude;  // degree / 10,000,000\n    int16_t altitude;    // meters\n} __attribute__((packed)) ghst_sensor_gps_primary_formatted_t;\n\ntypedef struct ghst_sensor_gps_secondary_formatted_t {\n    uint16_t groundspeed;  // cm/s\n    uint16_t heading;      // GPS heading, degree/10\n    uint8_t satellites;    // satellites\n    uint16_t distance;     // 10m\n    uint16_t homedir;      // degree/10\n    uint8_t flags;         // fix 0x01, fix home 0x02\n} __attribute__((packed)) ghst_sensor_gps_secondary_formatted_t;\n\ntypedef struct ghst_sensor_magbaro_formatted_t {\n    uint16_t heading;  // degree/10\n    int16_t altitude;  // m\n    int16_t vario;     // cm/s\n    uint8_t flags;     // maghead 0x01, baroalt 0x02, vario 0x04\n} __attribute__((packed)) ghst_sensor_magbaro_formatted_t;\n\ntypedef struct ghst_sensor_pack_stat_t {\n    float *voltage;\n    float *current;\n    float *consumed;\n    float *rx_volt;\n} ghst_sensor_pack_stat_t;\n\ntypedef struct ghst_sensor_gps_primary_t {\n    float *latitude;\n    float *longitude;\n    float *altitude;\n} ghst_sensor_gps_primary_t;\n\ntypedef struct ghst_sensor_gps_secondary_t {\n    float *groundspeed;\n    float *heading;\n    float *satellites;\n    float *distance;\n    float *homedir;\n    float *flags;\n    uint8_t *home_set;\n    uint8_t *fix_type;\n} ghst_sensor_gps_secondary_t;\n\ntypedef struct ghst_sensor_magbaro_t {\n    float *heading;\n    float *altitude;\n    float *vario;\n    float *flags;\n} ghst_sensor_magbaro_t;\n\ntypedef struct ghst_sensors_t {\n    bool enabled_sensors[MAX_SENSORS];\n    ghst_sensor_pack_stat_t pack_stat;\n    ghst_sensor_gps_primary_t gps_primary;\n    ghst_sensor_gps_secondary_t gps_secondary;\n    ghst_sensor_magbaro_t magbaro;\n} ghst_sensors_t;\n\nstatic void process(ghst_sensors_t *sensors);\nstatic uint8_t format_sensor(ghst_sensors_t *sensors, uint8_t type, uint8_t *buffer);\nstatic void send_packet(ghst_sensors_t *sensors);\nstatic uint8_t get_crc(const uint8_t *ptr, uint32_t len);\nstatic uint8_t crc8(uint8_t crc, unsigned char a);\nstatic void set_config(ghst_sensors_t *sensors);\nstatic inline uint sensor_count(ghst_sensors_t *sensors);\n\nvoid ghst_task(void *parameters) {\n    ghst_sensors_t sensors = {0};\n    set_config(&sensors);\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(420000L, UART_RECEIVER_TX, UART_RECEIVER_RX, GHST_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    debug(\"\\nGHST init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&sensors);\n    }\n}\n\nstatic void process(ghst_sensors_t *sensors) {\n    if (uart0_available() == GHST_PACKET_LEN + 2) {\n        uint8_t data[GHST_PACKET_LEN + 2];\n        uart0_read_bytes(data, GHST_PACKET_LEN + 2);\n        debug(\"\\nGHST (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n        debug_buffer(data, GHST_PACKET_LEN + 2, \"0x%X \");\n        uint8_t crc = get_crc(data + 2, GHST_PACKET_LEN - 1);  // crc from type, size 11\n        // send telemetry\n        if (data[0] == GHST_ADDR_RX) {\n            if (data[13] == crc)\n                send_packet(sensors);\n            else\n                debug(\"\\nGHST. Bad CRC 0x%X - 0x%X\", data[13], crc);\n        }\n    }\n}\n\nstatic uint8_t format_sensor(ghst_sensors_t *sensors, uint8_t type, uint8_t *buffer) {\n    // Packet format: [rx_addr] [len] [type] [payload] [crc8 from type]\n    buffer[0] = GHST_ADDR_RX;\n    buffer[1] = GHST_PACKET_LEN;\n    switch (type) {\n        case TYPE_PACK_STAT: {\n            buffer[2] = GHST_DL_PACK_STAT;\n            ghst_sensor_pack_stat_formatted_t sensor = {0};\n            if (sensors->pack_stat.voltage) sensor.voltage = *sensors->pack_stat.voltage * 100;\n            if (sensors->pack_stat.current) sensor.current = *sensors->pack_stat.current * 100;\n            if (sensors->pack_stat.consumed) sensor.consumed = *sensors->pack_stat.consumed / 10;\n            memcpy(&buffer[3], &sensor, sizeof(ghst_sensor_pack_stat_formatted_t));\n            buffer[GHST_PACKET_LEN + 1] = get_crc(&buffer[2], GHST_PACKET_LEN + 1);\n            break;\n        }\n        case TYPE_GPS_PRIMARY: {\n            buffer[2] = GHST_DL_GPS_PRIMARY;\n            ghst_sensor_gps_primary_formatted_t sensor = {0};\n            if (sensors->gps_primary.latitude) sensor.latitude = *sensors->gps_primary.latitude * 10000000L;\n            if (sensors->gps_primary.longitude) sensor.longitude = *sensors->gps_primary.longitude * 10000000L;\n            if (sensors->gps_primary.altitude) sensor.altitude = *sensors->gps_primary.altitude;\n            memcpy(&buffer[3], &sensor, sizeof(ghst_sensor_gps_primary_formatted_t));\n            buffer[GHST_PACKET_LEN + 1] = get_crc(&buffer[2], GHST_PACKET_LEN + 1);\n            break;\n        }\n        case TYPE_GPS_SECONDARY: {\n            buffer[2] = GHST_DL_GPS_SECONDARY;\n            ghst_sensor_gps_secondary_formatted_t sensor = {0};\n            if (sensors->gps_secondary.groundspeed)\n                sensor.groundspeed = fabs(*sensors->gps_secondary.groundspeed * 1000.0F / 36);\n            if (sensors->gps_secondary.heading) sensor.heading = *sensors->gps_secondary.heading * 10;\n            if (sensors->gps_secondary.satellites) sensor.satellites = *sensors->gps_secondary.satellites;\n            if (sensors->gps_secondary.distance) sensor.distance = *sensors->gps_secondary.distance / 10;\n            if (sensors->gps_secondary.homedir) sensor.homedir = *sensors->gps_secondary.homedir * 10;\n            sensor.flags = 0;\n            if (sensors->gps_secondary.fix_type && *sensors->gps_secondary.fix_type >= 1) {\n                sensor.flags |= 0x01;\n            }\n            if (sensors->gps_secondary.home_set && *sensors->gps_secondary.home_set) {\n                sensor.flags |= 0x02;\n            }\n            memcpy(&buffer[3], &sensor, sizeof(ghst_sensor_gps_secondary_formatted_t));\n            buffer[GHST_PACKET_LEN + 1] = get_crc(&buffer[2], GHST_PACKET_LEN + 1);\n            break;\n        }\n        case TYPE_MAGBARO: {\n            buffer[2] = GHST_DL_MAGBARO;\n            ghst_sensor_magbaro_formatted_t sensor = {0};\n            if (sensors->magbaro.vario) sensor.vario = *sensors->magbaro.vario * 100;\n            memcpy(&buffer[3], &sensor, sizeof(ghst_sensor_magbaro_formatted_t));\n            buffer[GHST_PACKET_LEN + 1] = get_crc(&buffer[2], GHST_PACKET_LEN + 1);\n            break;\n        }\n    }\n}\n\nstatic inline uint sensor_count(ghst_sensors_t *sensors) {\n    uint count = 0;\n    for (uint i = 0; i < MAX_SENSORS; i++)\n        if (sensors->enabled_sensors[i]) count++;\n    return count;\n}\n\nstatic void send_packet(ghst_sensors_t *sensors) {\n    if (!sensor_count(sensors)) return;\n    static uint type = 0;\n    uint8_t buffer[GHST_PACKET_LEN + 2] = {0};\n    while (!sensors->enabled_sensors[type % MAX_SENSORS]) type++;\n    format_sensor(sensors, type % MAX_SENSORS, buffer);\n    uart0_write_bytes(buffer, GHST_PACKET_LEN + 2);\n    type++;\n    debug(\"\\nGHST (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(buffer, GHST_PACKET_LEN + 2, \"0x%X \");\n\n    // blink led\n    vTaskResume(context.led_task_handle);\n}\n\nstatic uint8_t get_crc(const uint8_t *ptr, uint32_t len) {\n    uint8_t crc = 0;\n    for (uint32_t i = 0; i < len; i++) {\n        crc = crc8(crc, *ptr++);\n    }\n    return crc;\n}\n\nstatic uint8_t crc8(uint8_t crc, unsigned char a) {\n    crc ^= a;\n    for (int ii = 0; ii < 8; ++ii) {\n        if (crc & 0x80) {\n            crc = (crc << 1) ^ 0xD5;\n        } else {\n            crc = crc << 1;\n        }\n    }\n    return crc;\n}\n\nstatic void set_config(ghst_sensors_t *sensors) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_PACK_STAT] = true;\n        sensors->pack_stat.voltage = parameter.voltage;\n        sensors->pack_stat.current = parameter.current;\n        sensors->pack_stat.consumed = parameter.consumption;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n\n        sensors->enabled_sensors[TYPE_GPS_PRIMARY] = true;\n        sensors->gps_primary.latitude = parameter.lat;\n        sensors->gps_primary.longitude = parameter.lon;\n        sensors->gps_primary.altitude = parameter.alt;\n        sensors->enabled_sensors[TYPE_GPS_SECONDARY] = true;\n        sensors->gps_secondary.groundspeed = parameter.spd_kmh;\n        sensors->gps_secondary.heading = parameter.cog;\n        sensors->gps_secondary.satellites = parameter.sat;\n        sensors->gps_secondary.distance = parameter.dist;\n        sensors->gps_secondary.home_set = parameter.home_set;\n        sensors->gps_secondary.fix_type = parameter.fix_type;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_PACK_STAT] = true;\n        sensors->pack_stat.voltage = parameter.voltage;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_PACK_STAT] = true;\n        sensors->pack_stat.current = parameter.current;\n        sensors->pack_stat.consumed = parameter.consumption;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_MAGBARO] = true;\n        sensors->magbaro.altitude = parameter.altitude;\n        sensors->magbaro.vario = parameter.vspeed;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_MAGBARO] = true;\n        sensors->magbaro.altitude = parameter.altitude;\n        sensors->magbaro.vario = parameter.vspeed;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n        sensors->enabled_sensors[TYPE_MAGBARO] = true;\n        sensors->magbaro.altitude = parameter.altitude;\n        sensors->magbaro.vario = parameter.vspeed;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}"
  },
  {
    "path": "board/project/protocol/ghst.h",
    "content": "#ifndef GHST_H\n#define GHST_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid ghst_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/hitec.c",
    "content": "#include \"hitec.h\"\n\n#include <math.h>\n#include <pico/i2c_slave.h>\n#include <stdio.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"gps.h\"\n#include \"hardware/i2c.h\"\n#include \"hardware/irq.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pico/stdlib.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n#define I2C_INTR_MASK_RD_REQ 0x00000020\n\n#define I2C_ADDRESS 0x08\n#define TIMEOUT 1000\n#define FRAME_LENGTH 7\n\n#define FRAME_0X11 0\n#define FRAME_0X12 1\n#define FRAME_0X13 2\n#define FRAME_0X14 3\n#define FRAME_0X15 4\n#define FRAME_0X16 5\n#define FRAME_0X17 6\n#define FRAME_0X18 7\n#define FRAME_0X19 8\n#define FRAME_0X1A 9\n#define FRAME_0X1B 10\n#define FRAME_0X11_RX_BATT 0\n#define FRAME_0X12_GPS_LAT 0\n#define FRAME_0X12_TIME 1\n#define FRAME_0X13_GPS_LON 0\n#define FRAME_0X13_TEMP2 1\n#define FRAME_0X14_GPS_SPD 0\n#define FRAME_0X14_GPS_ALT 1\n#define FRAME_0X14_TEMP1 2\n#define FRAME_0X15_FUEL 0\n#define FRAME_0X15_RPM1 1\n#define FRAME_0X15_RPM2 2\n#define FRAME_0X16_DATE 0\n#define FRAME_0X16_TIME 1\n#define FRAME_0X17_COG 0\n#define FRAME_0X17_SATS 1\n#define FRAME_0X17_TEMP3 2\n#define FRAME_0X17_TEMP4 3\n#define FRAME_0X18_VOLT 0\n#define FRAME_0X18_AMP 1\n#define FRAME_0X19_AMP1 0\n#define FRAME_0X19_AMP2 1\n#define FRAME_0X19_AMP3 2\n#define FRAME_0X19_AMP4 3\n#define FRAME_0X1A_ASPD 0\n#define FRAME_0X1B_ALTU 0\n#define FRAME_0X1B_ALTF 1\n\ntypedef struct sensor_hitec_t {\n    bool is_enabled_frame[11];\n    float *frame_0x11[1];\n    float *frame_0x12[2];\n    float *frame_0x13[2];\n    float *frame_0x14[3];\n    float *frame_0x15[3];\n    float *frame_0x16[2];\n    float *frame_0x17[4];\n    float *frame_0x18[2];\n    float *frame_0x19[4];\n    float *frame_0x1A[1];\n    float *frame_0x1B[2];\n} sensor_hitec_t;\n\nstatic sensor_hitec_t *sensor;\nstatic uint8_t packet[FRAME_LENGTH] = {0};\nstatic volatile uint8_t cont = 0;\nstatic volatile alarm_id_t alarm_id_recovery = 0;  // For bus recovery timeout\nstatic volatile alarm_id_t alarm_id_reinit = 0;    // For mandatory deinit/init after each frame\n\nstatic void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event);\nstatic void set_config();\nstatic int64_t alarm_recovery(alarm_id_t id, void *user_data);  // I2C bus recovery alarm\nstatic int64_t alarm_reinit(alarm_id_t id, void *user_data);    // Periodic deinit/init after each frame\nstatic int next_frame(void);\nstatic void format_packet(uint8_t frame, uint8_t *buffer);\nstatic void i2c_bus_recovery(void);\n\nvoid hitec_task(void *parameters) {\n    sensor = malloc(sizeof(sensor_hitec_t));\n    *sensor =\n        (sensor_hitec_t){{0}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}, {NULL}};\n\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n\n    set_config();\n\n    i2c_bus_recovery();\n    // Arm initial I2C bus watchdog: if no requests arrive soon after startup,\n    // perform another bus recovery to handle a receiver that powers up later.\n    alarm_id_recovery = add_alarm_in_us(1000 * 1000, alarm_recovery, NULL, true);\n\n    debug(\"\\nHitec init\");\n\n    vTaskSuspend(NULL);\n}\n\nvoid hitec_i2c_handler(void) {\n    for (uint i = 0; i < FRAME_LENGTH + 1; i++) {\n        i2c_handler(i2c1, I2C_SLAVE_REQUEST);\n    }\n}\n\nstatic void i2c_handler(i2c_inst_t *i2c, i2c_slave_event_t event) {\n    switch (event) {\n        case I2C_SLAVE_REQUEST:\n            if (cont == 0) {\n                int frame = next_frame();\n                if (frame < 0) {\n                    return;\n                }\n                format_packet((uint8_t)frame, packet);\n            }\n\n            if (cont < FRAME_LENGTH) {\n                // Send next byte\n                i2c_write_byte_raw(i2c1, packet[cont++]);\n\n                // Refresh I2C bus watchdog timeout (1 s).\n                // As long as requests keep coming, the watchdog will never fire.\n                if (alarm_id_recovery != 0) {\n                    cancel_alarm(alarm_id_recovery);\n                }\n                alarm_id_recovery = add_alarm_in_us(1000 * 1000, alarm_recovery, NULL, true);\n\n                // If we have just sent the last valid byte of the frame,\n                // schedule a deinit/init after a short delay.\n                if (cont == FRAME_LENGTH) {\n                    // This handles the extra dummy read from Hitec master.\n                    if (alarm_id_reinit != 0) {\n                        cancel_alarm(alarm_id_reinit);\n                    }\n                    // 5 ms after frame end should be enough for master to finish its extra read\n                    alarm_id_reinit = add_alarm_in_us(5 * 1000, alarm_reinit, NULL, true);\n\n                    debug(\"\\nHitec (%u) > \", uxTaskGetStackHighWaterMark(context.receiver_task_handle));\n                    debug_buffer(packet, FRAME_LENGTH, \"0x%X \");\n                }\n            } else {\n                // Extra read from Hitec master: we just NACK and reset frame counter.\n                cont = 0;\n            }\n            break;\n    }\n    vTaskResume(context.led_task_handle);\n}\n\nstatic int64_t alarm_reinit(alarm_id_t id, void *user_data) {\n    // Mark current reinit alarm as consumed\n    alarm_id_reinit = 0;\n\n    // Short deinit/init sequence after each telemetry frame.\n    // This is required because Hitec master always performs one extra read,\n    // leaving the RP2040 I2C slave state machine in a bad state.\n    i2c_slave_deinit(i2c1);\n    sleep_us(5);  // Small delay to ensure peripheral is fully stopped\n    i2c_slave_init(i2c1, I2C_ADDRESS, i2c_handler);\n\n    // Reset frame counter just in case\n    cont = 0;\n\n    return 0;\n}\n\nstatic int64_t alarm_recovery(alarm_id_t id, void *user_data) {\n    // I2C bus watchdog callback.\n    // This is only reached if no I2C requests have been seen for the whole timeout\n    // interval (1 s), because each request cancels and re-arms the watchdog.\n    // While the bus is inactive/stuck, this function will keep running once per\n    // second until a new request arrives and the handler cancels the alarm.\n    i2c_bus_recovery();\n    return 1000 * 1000;  // Keep watchdog running: retry bus recovery every 1 s\n}\n\n\nstatic void i2c_bus_recovery(void) {\n    // Reset frame\n    cont = 0;\n\n    // Deinitialize I2C peripheral to release control of the bus\n    i2c_slave_deinit(i2c1);\n\n    // SCL as output, SDA as input with pull-up\n    gpio_init(I2C1_SCL_GPIO);\n    gpio_set_dir(I2C1_SCL_GPIO, GPIO_OUT);\n    gpio_put(I2C1_SCL_GPIO, 1);\n\n    gpio_init(I2C1_SDA_GPIO);\n    gpio_set_dir(I2C1_SDA_GPIO, GPIO_IN);\n    gpio_pull_up(I2C1_SDA_GPIO);\n\n    sleep_us(5);\n\n    // If SDA is low, someone is holding it: give up to 9 pulses on SCL\n    for (int i = 0; i < 9 && !gpio_get(I2C1_SDA_GPIO); i++) {\n        gpio_put(I2C1_SCL_GPIO, 0);\n        sleep_us(5);\n        gpio_put(I2C1_SCL_GPIO, 1);\n        sleep_us(5);\n    }\n\n    // Generate STOP: SDA goes high while SCL is high\n    // First ensure SDA is output and low\n    gpio_set_dir(I2C1_SDA_GPIO, GPIO_OUT);\n    gpio_put(I2C1_SDA_GPIO, 0);\n    gpio_put(I2C1_SCL_GPIO, 1);\n    sleep_us(5);\n    gpio_put(I2C1_SDA_GPIO, 1);\n    sleep_us(5);\n\n    // Reinitialize I2C peripheral after bus recovery\n    gpio_set_function(I2C1_SDA_GPIO, GPIO_FUNC_I2C);\n    gpio_set_function(I2C1_SCL_GPIO, GPIO_FUNC_I2C);\n    gpio_pull_up(I2C1_SDA_GPIO);\n    gpio_pull_up(I2C1_SCL_GPIO);\n\n    // Reinitialize the slave\n    i2c_slave_init(i2c1, I2C_ADDRESS, i2c_handler);\n}\n\nstatic int next_frame(void) {\n    static uint8_t frame = 0;\n    uint cont = 0;\n    frame++;\n    frame %= 11;\n    while (!sensor->is_enabled_frame[frame] && cont < 12) {\n        frame++;\n        frame %= 11;\n        cont++;\n    }\n    if (cont == 12) return -1;\n    return frame;\n}\n\nstatic void format_packet(uint8_t frame, uint8_t *buffer) {\n    int32_t valueS32;\n    uint16_t valueU16;\n    uint16_t valueS16;\n    uint8_t valueU8;\n    buffer[0] = frame + 0x11;\n    buffer[1] = 0;\n    buffer[2] = 0;\n    buffer[3] = 0;\n    buffer[4] = 0;\n    buffer[5] = 0;\n    buffer[6] = frame + 0x11;\n    switch (frame) {\n        case FRAME_0X11:\n            buffer[1] = 0xAF;\n            buffer[3] = 0x2D;\n            if (sensor->frame_0x11[FRAME_0X11_RX_BATT]) {\n                valueU16 = *sensor->frame_0x11[FRAME_0X11_RX_BATT] * 28;\n                buffer[4] = valueU16 >> 8;\n                buffer[5] = valueU16;\n            }\n            break;\n        case FRAME_0X12:\n            if (sensor->frame_0x12[FRAME_0X12_GPS_LAT]) {\n                double degrees = *sensor->frame_0x12[FRAME_0X12_GPS_LAT];\n                int8_t deg = degrees;\n                int8_t min = (degrees - deg) * 60;\n                double sec = ((degrees - deg) * 60 - min) * 60;\n                int16_t sec_x_100 = sec * 100;\n                int16_t deg_min = deg * 100 + min;\n                buffer[1] = sec_x_100 >> 8;\n                buffer[2] = sec_x_100;\n                buffer[3] = deg_min >> 8;\n                buffer[4] = deg_min;\n            }\n            if (sensor->frame_0x12[FRAME_0X12_TIME]) {\n                valueU8 = *sensor->frame_0x12[FRAME_0X12_TIME];\n            }\n            break;\n        case FRAME_0X13:\n            if (sensor->frame_0x13[FRAME_0X13_GPS_LON]) {\n                float degrees = *sensor->frame_0x13[FRAME_0X13_GPS_LON];\n                int8_t deg = degrees;\n                int8_t min = (degrees - deg) * 60;\n                float sec = ((degrees - deg) * 60 - min) * 60;\n                int16_t sec_x_100 = sec * 100;\n                int16_t deg_min = deg * 100 + min;\n                buffer[1] = sec_x_100 >> 8;\n                buffer[2] = sec_x_100;\n                buffer[3] = deg_min >> 8;\n                buffer[4] = deg_min;\n            }\n            if (sensor->frame_0x13[FRAME_0X13_TEMP2]) {\n                valueU8 = round(*sensor->frame_0x13[FRAME_0X13_TEMP2] + 40);\n                buffer[5] = valueU8;\n            }\n            break;\n        case FRAME_0X14:\n            if (sensor->frame_0x14[FRAME_0X14_GPS_SPD]) {\n                valueU16 = round(*sensor->frame_0x14[FRAME_0X14_GPS_SPD] * 1.852);\n                buffer[1] = valueU16 >> 8;\n                buffer[2] = valueU16;\n            }\n            if (sensor->frame_0x14[FRAME_0X14_GPS_ALT]) {\n                valueS16 = round(*sensor->frame_0x14[FRAME_0X14_GPS_ALT]);\n                buffer[3] = valueS16 >> 8;\n                buffer[4] = valueS16;\n            }\n            if (sensor->frame_0x14[FRAME_0X14_TEMP1]) {\n                valueU8 = round(*sensor->frame_0x14[FRAME_0X14_TEMP1] + 40);\n                buffer[5] = valueU8;\n            }\n            break;\n        case FRAME_0X15:\n            if (sensor->frame_0x15[FRAME_0X15_RPM1]) {\n                valueU16 = round(*sensor->frame_0x15[FRAME_0X15_RPM1]);\n                buffer[2] = valueU16;\n                buffer[3] = valueU16 >> 8;\n            }\n            if (sensor->frame_0x15[FRAME_0X15_RPM2]) {\n                valueU16 = round(*sensor->frame_0x15[FRAME_0X15_RPM2]);\n                buffer[4] = valueU16;\n                buffer[5] = valueU16 >> 8;\n            }\n            break;\n        case FRAME_0X16:\n            if (sensor->frame_0x16[FRAME_0X16_DATE]) {\n                valueS32 = *sensor->frame_0x16[FRAME_0X16_DATE];\n                buffer[3] = valueS32 / 10000;                                  // year\n                buffer[2] = (valueS32 - buffer[3] * 10000UL) / 100;            // month\n                buffer[1] = valueS32 - buffer[3] * 10000UL - buffer[2] * 100;  // day\n            }\n            if (sensor->frame_0x16[FRAME_0X16_TIME]) {\n                valueS32 = *sensor->frame_0x16[FRAME_0X16_TIME];\n                buffer[4] = valueS32 / 10000;                        // hour\n                buffer[5] = (valueS32 - buffer[4] * 10000UL) / 100;  // minute\n            }\n            break;\n        case FRAME_0X17:\n            if (sensor->frame_0x17[FRAME_0X17_COG]) {\n                valueU16 = round(*sensor->frame_0x17[FRAME_0X17_COG]);\n                buffer[1] = valueU16 >> 8;\n                buffer[2] = valueU16;\n            }\n            if (sensor->frame_0x17[FRAME_0X17_SATS]) {\n                valueU8 = *sensor->frame_0x17[FRAME_0X17_SATS];\n                buffer[3] = valueU8;\n            }\n            if (sensor->frame_0x17[FRAME_0X17_TEMP3]) {\n                valueU8 = round(*sensor->frame_0x17[FRAME_0X17_TEMP3] + 40);\n                buffer[4] = valueU8;\n            }\n            if (sensor->frame_0x17[FRAME_0X17_TEMP4]) {\n                valueU8 = round(*sensor->frame_0x17[FRAME_0X17_TEMP4] + 40);\n                buffer[5] = valueU8;\n            }\n            break;\n        case FRAME_0X18:\n            if (sensor->frame_0x18[FRAME_0X18_VOLT]) {\n                valueU16 = round((*sensor->frame_0x18[FRAME_0X18_VOLT] - 0.2) * 10);\n                buffer[1] = valueU16;\n                buffer[2] = valueU16 >> 8;\n            }\n            if (sensor->frame_0x18[FRAME_0X18_AMP]) {\n                /* value for stock transmitter (tbc) */\n                // valueU16 = (*sensor->frame_0x18[FRAME_0X18_AMP] + 114.875) * 1.441;\n\n                /* value for opentx transmitter  */\n                valueU16 = round(*sensor->frame_0x18[FRAME_0X18_AMP]);\n\n                buffer[3] = valueU16;\n                buffer[4] = valueU16 >> 8;\n            }\n            break;\n        case FRAME_0X19:\n            if (sensor->frame_0x19[FRAME_0X19_AMP1]) {\n                valueU8 = round(*sensor->frame_0x19[FRAME_0X19_AMP1] * 10);\n                buffer[5] = valueU8;\n            }\n            if (sensor->frame_0x19[FRAME_0X19_AMP2]) {\n                valueU8 = round(*sensor->frame_0x19[FRAME_0X19_AMP2] * 10);\n                buffer[5] = valueU8;\n            }\n            if (sensor->frame_0x19[FRAME_0X19_AMP3]) {\n                valueU8 = round(*sensor->frame_0x19[FRAME_0X19_AMP3] * 10);\n                buffer[5] = valueU8;\n            }\n            if (sensor->frame_0x19[FRAME_0X19_AMP4]) {\n                valueU8 = round(*sensor->frame_0x19[FRAME_0X19_AMP4] * 10);\n                buffer[5] = valueU8;\n            }\n            break;\n        case FRAME_0X1A:\n            if (sensor->frame_0x1A[FRAME_0X1A_ASPD]) {\n                valueU16 = round(*sensor->frame_0x1A[FRAME_0X1A_ASPD]);\n                buffer[3] = valueU16 >> 8;\n                buffer[4] = valueU16;\n            }\n            break;\n        case FRAME_0X1B:\n            if (sensor->frame_0x1B[FRAME_0X1B_ALTU]) {\n                valueU16 = round(*sensor->frame_0x1B[FRAME_0X1B_ALTU]);\n                buffer[1] = valueU16 >> 8;\n                buffer[2] = valueU16;\n            }\n            if (sensor->frame_0x1B[FRAME_0X1B_ALTF]) {\n                valueU16 = round(*sensor->frame_0x1B[FRAME_0X1B_ALTF]);\n                buffer[3] = valueU16 >> 8;\n                buffer[4] = valueU16;\n            }\n            break;\n    }\n}\n\nstatic void set_config(void) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temperature_fet;\n        sensor->frame_0x13[FRAME_0X13_TEMP2] = parameter.temperature_bec;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n        sensor->is_enabled_frame[FRAME_0X13] = true;\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temperature_fet;\n        sensor->frame_0x13[FRAME_0X13_TEMP2] = parameter.temperature_bec;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n        sensor->is_enabled_frame[FRAME_0X13] = true;\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temperature;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n        sensor->is_enabled_frame[FRAME_0X13] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temperature_fet;\n        sensor->frame_0x13[FRAME_0X13_TEMP2] = parameter.temperature_bec;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n        sensor->is_enabled_frame[FRAME_0X13] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temperature;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temperature;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temperature_fet;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temp_esc;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor->frame_0x15[FRAME_0X15_RPM1] = parameter.rpm;\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.temp_esc;\n        sensor->is_enabled_frame[FRAME_0X15] = true;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n\n        sensor->frame_0x17[FRAME_0X17_SATS] = parameter.sat;\n        sensor->frame_0x12[FRAME_0X12_GPS_LAT] = parameter.lat;\n        sensor->frame_0x13[FRAME_0X13_GPS_LON] = parameter.lon;\n        sensor->frame_0x14[FRAME_0X14_GPS_ALT] = parameter.alt;\n        sensor->frame_0x14[FRAME_0X14_GPS_SPD] = parameter.spd;\n        sensor->frame_0x17[FRAME_0X17_COG] = parameter.cog;\n        sensor->frame_0x16[FRAME_0X16_DATE] = parameter.date;\n        sensor->frame_0x16[FRAME_0X16_TIME] = parameter.time;\n        sensor->is_enabled_frame[FRAME_0X17] = true;\n        sensor->is_enabled_frame[FRAME_0X12] = true;\n        sensor->is_enabled_frame[FRAME_0X13] = true;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n        sensor->is_enabled_frame[FRAME_0X16] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n\n        sensor->frame_0x18[FRAME_0X18_VOLT] = parameter.voltage;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n\n        sensor->frame_0x18[FRAME_0X18_AMP] = parameter.current;\n        sensor->is_enabled_frame[FRAME_0X18] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n\n        sensor->frame_0x14[FRAME_0X14_TEMP1] = parameter.ntc;\n        sensor->is_enabled_frame[FRAME_0X14] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensor->frame_0x1B[FRAME_0X1B_ALTU] = parameter.altitude;\n        sensor->is_enabled_frame[FRAME_0X1B] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensor->frame_0x1B[FRAME_0X1B_ALTU] = parameter.altitude;\n        sensor->is_enabled_frame[FRAME_0X1B] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensor->frame_0x1B[FRAME_0X1B_ALTU] = parameter.altitude;\n        sensor->is_enabled_frame[FRAME_0X1B] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n\n        sensor->frame_0x1A[FRAME_0X1A_ASPD] = parameter.airspeed;\n        sensor->is_enabled_frame[FRAME_0X1A] = true;\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}"
  },
  {
    "path": "board/project/protocol/hitec.h",
    "content": "#ifndef HITEC_H\n#define HITEC_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid hitec_task(void *parameters);\nvoid hitec_i2c_handler(void);\n\n#endif"
  },
  {
    "path": "board/project/protocol/hott.c",
    "content": "#include \"hott.h\"\n\n#include <hardware/flash.h>\n#include <hardware/sync.h>\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"common.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gps.h\"\n#include \"hardware/i2c.h\"\n#include \"hardware/irq.h\"\n#include \"ibus.h\"\n#include \"ina3221.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pico/stdlib.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"stdlib.h\"\n#include \"string.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n#include \"xgzp68xxd.h\"\n\n#define HOTT_VARIO_MODULE_ID 0x89\n#define HOTT_GPS_MODULE_ID 0x8A\n#define HOTT_ESC_MODULE_ID 0x8C\n#define HOTT_GENERAL_AIR_MODULE_ID 0x8D\n#define HOTT_ELECTRIC_AIR_MODULE_ID 0x8E  // not used, for internal Hott telemetry\n\n#define HOTT_VARIO_TEXT_ID ((HOTT_VARIO_MODULE_ID << 4) & 0xFF)\n#define HOTT_GPS_TEXT_ID ((HOTT_GPS_MODULE_ID << 4) & 0xFF)\n#define HOTT_ESC_TEXT_ID ((HOTT_ESC_MODULE_ID << 4) & 0xFF)\n#define HOTT_GENERAL_AIR_TEXT_ID ((HOTT_GENERAL_AIR_MODULE_ID << 4) & 0xFF)\n#define HOTT_ELECTRIC_AIR_TEXT_ID ((HOTT_ELECTRIC_AIR_MODULE_ID << 4) & 0xFF)\n\n#define HOTT_BINARY_MODE_REQUEST_ID 0x80\n#define HOTT_TEXT_MODE_REQUEST_ID 0x7F\n\n#define HOTT_TIMEOUT_US 5000\n#define HOTT_INTERBYTE_DELAY_US 500\n#define HOTT_PACKET_LENGHT 2\n\n#define HOTT_START_BYTE 0x7C\n#define HOTT_END_BYTE 0x7D\n\n// TYPE\n#define HOTT_TYPE_VARIO 0\n#define HOTT_TYPE_ESC 1\n#define HOTT_TYPE_GENERAL 2\n#define HOTT_TYPE_GPS 3\n#define HOTT_TYPE_ELECTRIC 4\n\n// VARIO\n#define HOTT_VARIO_ALTITUDE 0\n#define HOTT_VARIO_M1S 1\n#define HOTT_VARIO_M3S 2\n#define HOTT_VARIO_M10S 3\n#define HOTT_VARIO_COUNT 4\n\n//  ESC\n#define HOTT_ESC_VOLTAGE 0\n#define HOTT_ESC_CONSUMPTION 1\n#define HOTT_ESC_TEMPERATURE 2\n#define HOTT_ESC_CURRENT 3\n#define HOTT_ESC_RPM 4\n#define HOTT_ESC_THROTTLE 5  // 0-100%\n#define HOTT_ESC_SPEED 6\n#define HOTT_ESC_BEC_VOLTAGE 7\n#define HOTT_ESC_BEC_CURRENT 8\n#define HOTT_ESC_BEC_TEMPERATURE 9\n#define HOTT_ESC_EXT_TEMPERATURE 10\n#define HOTT_ESC_COUNT 11\n\n// ELECTRIC - used by internal receiver vario (shouldnt be used)\n#define HOTT_ELECTRIC_EXT_TEMPERATURE 0\n#define HOTT_ELECTRIC_CELL_BAT_1_VOLTAGE 1\n#define HOTT_ELECTRIC_CELL_BAT_2_VOLTAGE 2\n#define HOTT_ELECTRIC_BAT_1_VOLTAGE 3\n#define HOTT_ELECTRIC_BAT_2_VOLTAGE 4\n#define HOTT_ELECTRIC_TEMPERATURE_1 5\n#define HOTT_ELECTRIC_TEMPERATURE_2 6\n#define HOTT_ELECTRIC_HEIGHT 7\n#define HOTT_ELECTRIC_CURRENT 8\n#define HOTT_ELECTRIC_CAPACITY 9\n#define HOTT_ELECTRIC_M2S 10\n#define HOTT_ELECTRIC_M3S 11\n#define HOTT_ELECTRIC_RPM 12\n#define HOTT_ELECTRIC_MINUTES 13\n#define HOTT_ELECTRIC_SECONDS 14\n#define HOTT_ELECTRIC_SPEED 15\n#define HOTT_ELECTRIC_COUNT 16\n\n// GPS\n#define HOTT_GPS_DIRECTION 0\n#define HOTT_GPS_SPEED 1\n#define HOTT_GPS_LATITUDE 2\n#define HOTT_GPS_LONGITUDE 3\n#define HOTT_GPS_DISTANCE 4\n#define HOTT_GPS_ALTITUDE 5\n#define HOTT_GPS_CLIMBRATE 6\n#define HOTT_GPS_CLIMBRATE3S 7\n#define HOTT_GPS_SATS 8\n#define HOTT_GPS_FIX 9\n#define HOTT_GPS_TIME 10\n#define HOTT_GPS_HOME_ALTITUDE 11\n#define HOTT_GPS_COUNT 12\n\n// GENERAL\n#define HOTT_GENERAL_CELL_1 0\n#define HOTT_GENERAL_CELL_2 1\n#define HOTT_GENERAL_CELL_3 2\n#define HOTT_GENERAL_CELL_4 3\n#define HOTT_GENERAL_CELL_5 4\n#define HOTT_GENERAL_CELL_6 5\n#define HOTT_GENERAL_BATTERY_1 6\n#define HOTT_GENERAL_BATTERY_2 7\n#define HOTT_GENERAL_TEMP_1 8\n#define HOTT_GENERAL_TEMP_2 9\n#define HOTT_GENERAL_FUEL 10  // ml\n#define HOTT_GENERAL_RPM_1 11\n#define HOTT_GENERAL_ALTITUDE 12\n#define HOTT_GENERAL_CLIMBRATE 13\n#define HOTT_GENERAL_CLIMBRATE3S 14\n#define HOTT_GENERAL_CURRENT 15\n#define HOTT_GENERAL_VOLTAGE 16\n#define HOTT_GENERAL_CAPACITY 17\n#define HOTT_GENERAL_SPEED 18\n#define HOTT_GENERAL_RPM_2 19\n#define HOTT_GENERAL_PRESSURE 20\n#define HOTT_GENERAL_COUNT 21\n\n#define HOTT_KEY_RIGHT 0xE\n#define HOTT_KEY_DOWN 0xB\n#define HOTT_KEY_UP 0xD\n#define HOTT_KEY_SET 0x9\n#define HOTT_KEY_LEFT 0x7\n\n#define ALARMS_FLASH_TARGET_OFFSET (CONFIG_FLASH_TARGET_OFFSET + 4096)  // after program + config\n\n// Missing: 0x01, 0x04, 0x05, 0x0C, 0x15\n#define ALARM_VOICE_NO_ALARM 0x00\n#define ALARM_VOICE_UNKNOWN1 0x01\n#define ALARM_VOICE_NEGATIVE_DIFFERENCE_2 0x02\n#define ALARM_VOICE_NEGATIVE_DIFFERENCE_1 0x03\n#define ALARM_VOICE_UNKNOWN2 0x04\n#define ALARM_VOICE_UNKNOWN3 0x05\n#define ALARM_VOICE_MIN_SENSOR_1_TEMP 0x06\n#define ALARM_VOICE_MIN_SENSOR_2_TEMP 0x07\n#define ALARM_VOICE_MAX_SENSOR_1_TEMP 0x08\n#define ALARM_VOICE_MAX_SENSOR_2_TEMP 0x09\n#define ALARM_VOICE_MAX_SENSOR_1_VOLTAGE 0x0A\n#define ALARM_VOICE_MAX_SENSOR_2_VOLTAGE 0x0B\n#define ALARM_VOICE_UNKNOWN4 0x0C\n#define ALARM_VOICE_POSITIVE_DIFFERENCE_2 0x0D\n#define ALARM_VOICE_POSITIVE_DIFFERENCE_1 0x0E\n#define ALARM_VOICE_MIN_ALTITUDE 0x0F\n#define ALARM_VOICE_MIN_POWER_VOLTAGE 0x10\n#define ALARM_VOICE_MIN_CELL_VOLTAGE 0x11\n#define ALARM_VOICE_MIN_SENSOR_1_VOLTAGE 0x12\n#define ALARM_VOICE_MIN_SENSOR_2_VOLTAGE 0x13\n#define ALARM_VOICE_MIN_RPM 0x14\n#define ALARM_VOICE_UNKNOWN5 0x15\n#define ALARM_VOICE_MAX_CAPACITY 0x16\n#define ALARM_VOICE_MAX_CURRENT 0x17\n#define ALARM_VOICE_MAX_POWER_VOLTAGE 0x18\n#define ALARM_VOICE_MAX_RPM 0x19\n#define ALARM_VOICE_MAX_ALTITUDE 0x1A\n\ntypedef enum alarm_vario_t {\n    ALARM_BITMASK_VARIO_ALTITUDE = 0,\n    ALARM_BITMASK_VARIO_MAX_ALTITUDE,\n    ALARM_BITMASK_VARIO_MIN_ALTITUDE,\n    ALARM_BITMASK_VARIO_M1S,\n    ALARM_BITMASK_VARIO_M3S,\n    ALARM_BITMASK_VARIO_M10S,\n} alarm_vario_t;\n\ntypedef enum alarm_airesc_t {\n    ALARM_BITMASK_AIRESC_VOLTAGE = 0,\n    ALARM_BITMASK_AIRESC_MIN_VOLTAGE,\n    ALARM_BITMASK_AIRESC_CAPACITY,\n    ALARM_BITMASK_AIRESC_TEMPERATURE,\n    ALARM_BITMASK_AIRESC_MAX_TEMPERATURE,\n    ALARM_BITMASK_AIRESC_CURRENT,\n    ALARM_BITMASK_AIRESC_MAX_CURRENT,\n    ALARM_BITMASK_AIRESC_RPM,\n    ALARM_BITMASK_AIRESC_MAX_RPM,\n    ALARM_BITMASK_AIRESC_THROTTLE,\n    ALARM_BITMASK_AIRESC_SPEED,\n    ALARM_BITMASK_AIRESC_MAX_SPEED,\n    ALARM_BITMASK_AIRESC_BEC_VOLTAGE,\n    ALARM_BITMASK_AIRESC_MIN_BEC_VOLTAGE,\n    ALARM_BITMASK_AIRESC_BEC_CURRENT,\n    ALARM_BITMASK_AIRESC_MIN_BEC_CURRENT,\n    ALARM_BITMASK_AIRESC_MAX_BEC_CURRENT,\n    ALARM_BITMASK_AIRESC_BEC_TEMPERATURE,\n    ALARM_BITMASK_AIRESC_MAX_BEC_TEMPERATURE,\n    ALARM_BITMASK_AIRESC_EXT_TEMPERATURE,\n    ALARM_BITMASK_AIRESC_MAX_EXT_TEMPERATURE,\n    ALARM_BITMASK_AIRESC_RPM_WITHOUT_GEAR,\n} alarm_airesc_t;\n\ntypedef enum alarm_general_air_t {\n    ALARM_BITMASK_GENERAL_AIR_CELL_VOLTAGE = 0,\n    ALARM_BITMASK_GENERAL_AIR_BATTERY_1,\n    ALARM_BITMASK_GENERAL_AIR_BATTERY_2,\n    ALARM_BITMASK_GENERAL_AIR_TEMPERATURE_1,\n    ALARM_BITMASK_GENERAL_AIR_TEMPERATURE_2,\n    ALARM_BITMASK_GENERAL_AIR_FUEL_PERCENT,\n    ALARM_BITMASK_GENERAL_AIR_FUEL_ML,\n    ALARM_BITMASK_GENERAL_AIR_RPM,\n    ALARM_BITMASK_GENERAL_AIR_ALTITUDE,\n    ALARM_BITMASK_GENERAL_AIR_CLIMBRATE,\n    ALARM_BITMASK_GENERAL_AIR_CLIMBRATE3S,\n    ALARM_BITMASK_GENERAL_AIR_CURRENT,\n    ALARM_BITMASK_GENERAL_AIR_VOLTAGE,\n    ALARM_BITMASK_GENERAL_AIR_CAPACITY,\n    ALARM_BITMASK_GENERAL_AIR_SPEED,\n    ALARM_BITMASK_GENERAL_AIR_MIN_CELL_VOLTAGE,\n    ALARM_BITMASK_GENERAL_AIR_MIN_CELL_VOLTAGE_NUM,\n    ALARM_BITMASK_GENERAL_AIR_RPM_2,\n} alarm_general_air_t;\n\ntypedef enum alarm_gps_t {\n    ALARM_BITMASK_GPS_FLIGHT_DIRECTION = 0,\n    ALARM_BITMASK_GPS_SPEED,\n    ALARM_BITMASK_GPS_LATITUDE,\n    ALARM_BITMASK_GPS_LONGITUDE,\n    ALARM_BITMASK_GPS_DISTANCE,\n    ALARM_BITMASK_GPS_ALTITUDE,\n    ALARM_BITMASK_GPS_CLIMBRATE,\n    ALARM_BITMASK_GPS_CLIMBRATE3S,\n    ALARM_BITMASK_GPS_SATS,\n} alarm_general_gps_t;\n\ntypedef struct hott_sensor_vario_t {\n    uint8_t startByte;  // 1\n    uint8_t sensorID;\n    uint8_t warningID;\n    uint8_t sensorTextID;\n    uint8_t alarmInverse;\n    uint16_t altitude;     // 6-7 value + 500 (e.g. 0m = 500)\n    uint16_t maxAltitude;  // 8-9\n    uint16_t minAltitude;\n    uint16_t m1s;   // 12-13 (value * 100) + 30000 (e.g. 10m = 31000)\n    uint16_t m3s;   // ?? idem\n    uint16_t m10s;  // ?? idem\n    uint8_t text[24];\n    uint8_t version;\n    uint8_t empty;\n    uint8_t endByte;\n    uint8_t checksum;\n} __attribute__((packed)) hott_sensor_vario_t;\n\ntypedef struct hott_sensor_airesc_t {\n    uint8_t startByte;                  // 1\n    uint8_t sensorID;                   // 2\n    uint8_t warningID;                  // 3\n    uint8_t sensorTextID;               // Byte 4\n    uint16_t alarmInverse;              // Byte 5, 6\n    uint16_t inputVolt;                 // Byte 7,8\n    uint16_t minInputVolt;              // Byte 9,10\n    uint16_t capacity;                  // Byte 11,12\n    uint8_t escTemperature;             // Byte 13\n    uint8_t maxEscTemperature;          // Byte 14\n    uint16_t current;                   // Byte 15,16\n    uint16_t maxCurrent;                // Byte 17,18\n    uint16_t RPM;                       // Byte 19,20\n    uint16_t maxRPM;                    // Byte 21,22\n    uint8_t throttlePercent;            // Byte 23\n    uint16_t speed;                     // Byte 24,25\n    uint16_t maxSpeed;                  // Byte 26,27\n    uint8_t BECVoltage;                 // Byte 28\n    uint8_t minBECVoltage;              // Byte 29\n    uint8_t BECCurrent;                 // Byte 30\n    uint8_t minBECCurrent;              // Byte 31\n    uint8_t maxBECCurrent;              // Byte 32\n    uint8_t PWM;                        // Byte 33\n    uint8_t BECTemperature;             // Byte 34\n    uint8_t maxBECTemperature;          // Byte 35\n    uint8_t motorOrExtTemperature;      // Byte 36\n    uint8_t maxMotorOrExtTemperature;   // Byte 37\n    uint16_t RPMWithoutGearOrExt;       // Byte 38,39\n    uint8_t timing;                     // Byte 40\n    uint8_t advancedTiming;             // Byte 41\n    uint8_t highestCurrentMotorNumber;  // Byte 42\n    uint8_t version;                    /* Byte 43: 00 version number */\n    uint8_t endByte;                    /* Byte 44: 0x7D Ende byte */\n    uint8_t checksum;                   /* Byte 45: Parity Byte */\n} __attribute__((packed)) hott_sensor_airesc_t;\n\n// used by internal receiver vario (shouldnt be used)\ntypedef struct hott_sensor_electric_air_t {\n    uint8_t startByte;      // 1\n    uint8_t sensorID;       // 2\n    uint8_t warningID;      // 3: Alarm\n    uint8_t sensorTextID;   // 4:\n    uint16_t alarmInverse;  // 5 6: alarm bitmask. Value is displayed inverted\n    uint8_t cell1L;         // 7: Low Voltage Cell 1 in 0,02 V steps\n    uint8_t cell2L;         // 8: Low Voltage Cell 2 in 0,02 V steps\n    uint8_t cell3L;         // 9: Low Voltage Cell 3 in 0,02 V steps\n    uint8_t cell4L;         // 10: Low Voltage Cell 4 in 0,02 V steps\n    uint8_t cell5L;         // 11: Low Voltage Cell 5 in 0,02 V steps\n    uint8_t cell6L;         // 12: Low Voltage Cell 6 in 0,02 V steps\n    uint8_t cell7L;         // 13: Low Voltage Cell 7 in 0,02 V steps\n    uint8_t cell1H;         // 14: High Voltage Cell 1 in 0.02 V steps\n    uint8_t cell2H;         // 15\n    uint8_t cell3H;         // 16\n    uint8_t cell4H;         // 17\n    uint8_t cell5H;         // 18\n    uint8_t cell6H;         // 19\n    uint8_t cell7H;         // 20\n    uint16_t battery1;      // 21 Battery 1 in 100mv steps; 50 == 5V\n    uint16_t battery2;      // 23 Battery 2 in 100mv steps; 50 == 5V\n    uint8_t temp1;          // 25 Temp 1; Offset of 20. 20 == 0C\n    uint8_t temp2;          // 26 Temp 2; Offset of 20. 20 == 0C\n    uint16_t height;        // 27 28 Height. Offset -500. 500 == 0\n    uint16_t current;       // 29 30 1 = 0.1A\n    uint16_t driveVoltage;  // 31\n    uint16_t capacity;      // 33 34 mAh\n    uint16_t m2s;           // 35 36  /* Steigrate m2s; 0x48 == 0\n    uint8_t m3s;            // 37  /* Steigrate m3s; 0x78 == 0\n    uint16_t rpm;           // 38 39 /* RPM. 10er steps; 300 == 3000rpm\n    uint8_t minutes;        // 40\n    uint8_t seconds;        // 41\n    uint8_t speed;          // 42\n    uint8_t version;        // 43\n    uint8_t endByte;        // 44\n    uint8_t checksum;       // 45\n} __attribute__((packed)) hott_sensor_electric_air_t;\n\ntypedef struct hott_sensor_general_air_t {\n    uint8_t startByte;             //#01 start byte constant value 0x7c\n    uint8_t sensorID;              //#02 EAM sensort id. constat value 0x8d=GENRAL AIR MODULE\n    uint8_t warningID;             //#03 1=A 2=B ... 0x1a=Z 0 = no alarm\n                                   /* VOICE OR BIP WARNINGS\n                           Alarme sonore A.. Z, octet correspondant 1 à 26\n                           0x00 00 0 No alarm\n                           0x01 01 A\n                           0x02 02 B Negative Difference 2 B\n                           0x03 03 C Negative Difference 1 C\n                           0x04 04 D\n                           0x05 05 E\n                           0x06 06 F Min. Sensor 1 temp. F\n                           0x07 07 G Min. Sensor 2 temp. G\n                           0x08 08 H Max. Sensor 1 temp. H\n                           0x09 09 I Max. Sensor 2 temp. I\n                           0xA 10 J Max. Sens. 1 voltage J\n                           0xB 11 K Max. Sens. 2 voltage K\n                           0xC 12 L\n                           0xD 13 M Positive Difference 2 M\n                           0xE 14 N Positive Difference 1 N\n                           0xF 15 O Min. Altitude O\n                           0x10 16 P Min. Power Voltage P // We use this one for Battery Warning\n                           0x11 17 Q Min. Cell voltage Q\n                           0x12 18 R Min. Sens. 1 voltage R\n                           0x13 19 S Min. Sens. 2 voltage S\n                           0x14 20 T Minimum RPM T\n                           0x15 21 U\n                           0x16 22 V Max. used capacity V\n                           0x17 23 W Max. Current W\n                           0x18 24 X Max. Power Voltage X\n                           0x19 25 Y Maximum RPM Y\n                           0x1A 26 Z Max. Altitude Z\n                           */\n    uint8_t sensorTextID;          //#04 constant value 0xd0\n    uint16_t alarmInverse;         //#05-06 alarm bitmask. Value is displayed inverted\n                                   // 0 all cell voltage\n                                   // 1 Battery 1\n                                   // 2 Battery 2\n                                   // 3 Temperature 1\n                                   // 4 Temperature 2\n                                   // 5 Fuel\n                                   // 6 mAh\n                                   // 7 Altitude\n                                   // 8 main power current\n                                   // 9 main power voltage\n                                   // 10 Altitude\n                                   // 11 m/s\n                                   // 12 m/3s\n                                   // 13 unknown\n                                   // 14 unknown\n                                   // 15 \"ON\" sign/text msg active\n    uint8_t cell[6];               //#7 Volt Cell 1 (in 2 mV increments, 210 == 4.20 V)\n                                   //#8 Volt Cell 2 (in 2 mV increments, 210 == 4.20 V)\n                                   //#9 Volt Cell 3 (in 2 mV increments, 210 == 4.20 V)\n                                   //#10 Volt Cell 4 (in 2 mV increments, 210 == 4.20 V)\n                                   //#11 Volt Cell 5 (in 2 mV increments, 210 == 4.20 V)\n                                   //#12 Volt Cell 6 (in 2 mV increments, 210 == 4.20 V)\n    uint16_t battery1;             //#13 LSB battery 1 voltage LSB value. 0.1V steps. 50 = 5.5V only pos. voltages\n                                   //#14 MSB\n    uint16_t battery2;             //#15 LSB battery 2 voltage LSB value. 0.1V steps. 50 = 5.5V only pos. voltages\n                                   //#16 MSB\n    uint8_t temperature1;          //#17 Temperature 1. Offset of 20. a value of 20 = 0°C\n    uint8_t temperature2;          //#18 Temperature 2. Offset of 20. a value of 20 = 0°C\n    uint8_t fuel_procent;          //#19 Fuel capacity in %. Values 0--100\n                                   // graphical display ranges: 0-100% with new firmwares of the radios MX12/MX20/...\n    uint16_t fuel_ml;              //#20 LSB Fuel in ml scale. Full = 65535!\n                                   //#21 MSB\n    uint16_t rpm;                  //#22 RPM in 10 RPM steps. 300 = 3000rpm\n                                   //#23 MSB\n    uint16_t altitude;             //#24 altitude in meters. offset of 500, 500 = 0m\n                                   //#25 MSB\n    uint16_t climbrate;            //#26 climb rate in 0.01m/s. Value of 30000 = 0.00 m/s\n                                   //#27 MSB\n    uint8_t climbrate3s;           //#28 climb rate in m/3sec. Value of 120 = 0m/3sec\n    uint16_t current;              //#29 current in 0.1A steps 100 == 10,0A\n                                   //#30 MSB current display only goes up to 99.9 A (continuous)\n    uint16_t main_voltage;         //#31 LSB Main power voltage using 0.1V steps 100 == 10,0V\n                                   //#32 MSB (Appears in GAM display right as alternate display.)\n    uint16_t batt_cap;             //#33 LSB used battery capacity in 10mAh steps\n                                   //#34 MSB\n    uint16_t speed;                //#35 LSB (air?) speed in km/h(?) we are using ground speed here per default\n                                   //#36 MSB speed\n    uint8_t min_cell_volt;         //#37 minimum cell voltage in 2mV steps. 124 = 2,48V\n    uint8_t min_cell_volt_num;     //#38 number of the cell with the lowest voltage\n    uint16_t rpm2;                 //#39 LSB 2nd RPM in 10 RPM steps. 100 == 1000rpm\n                                   //#40 MSB\n    uint8_t general_error_number;  //#41 General Error Number (Voice Error == 12) TODO: more documentation\n    uint8_t pressure;              //#42 High pressure up to 16bar. 0,1bar scale. 20 == 2.0 bar\n                                   // 1 bar = 10 hoch 5 Pa\n    uint8_t version;               //#43 version number (Bytes 35 .43 new but not yet in the record in the display!)\n    uint8_t endByte;               //#44 stop byte 0x7D\n    uint8_t checksum;              //#45 CHECKSUM CRC/Parity (calculated dynamicaly)\n} __attribute__((packed)) hott_sensor_general_air_t;\n\ntypedef struct hott_sensor_gps_t {\n    uint8_t startByte;     /* Byte 1: 0x7C = Start byte data */\n    uint8_t sensorID;      /* Byte 2: 0x8A = GPS Sensor */\n    uint8_t warningID;     /* Byte 3: 0…= warning beeps */\n    uint8_t sensorTextID;  /* Byte 4: 160 0xA0 Sensor ID Neu! */\n    uint16_t alarmInverse; /* Byte 5-6: 01 inverse status */\n    uint8_t\n        flightDirection; /* Byte 7: 119 = Flugricht./dir. 1 = 2°; 0° (North), 9 0° (East), 180° (South), 270° (West) */\n    uint16_t GPSSpeed;        /* Byte 8: 8 = Geschwindigkeit/GPS speed low byte 8km/h */\n    uint8_t LatitudeNS;       /* Byte 10: 000 = N = 48°39’988 */\n    uint16_t LatitudeDegMin;  /* Byte 11: 231 0xE7 = 0x12E7 = 4839 */\n    uint16_t LatitudeSec;     /* Byte 13: 171 220 = 0xDC = 0x03DC =0988 */\n    uint8_t longitudeEW;      /* Byte 15: 000  = E= 9° 25’9360 */\n    uint16_t longitudeDegMin; /* Byte 16: 150 157 = 0x9D = 0x039D = 0925 */\n    uint16_t longitudeSec;    /* Byte 18: 056 144 = 0x90 0x2490 = 9360*/\n    uint16_t distance;        /* Byte 20: 027 123 = Entfernung/distance low byte 6 = 6 m */\n    uint16_t altitude;        /* Byte 22: 243 244 = Höhe/Altitude low byte 500 = 0m */\n    uint16_t climbrate;       /* Byte 24: 48 = Low Byte m/s resolution 0.01m 48 = 30000 = 0.00m/s (1=0.01m/s) */\n    uint8_t climbrate3s;      /* Byte 26: climbrate in m/3s resolution, value of 120 = 0 m/3s*/\n    uint8_t GPSNumSat;        /* Byte 27: GPS.Satelites (number of satelites) (1 byte) */\n    uint8_t GPSFixChar;       /* Byte 28: GPS.FixChar. (GPS fix character. display, if DGPS, 2D oder 3D) (1 byte) */\n    uint8_t homeDirection;    /* Byte 29: HomeDirection (direction from starting point to Model position) (1 byte) */\n    uint8_t angleXdirection;  /* Byte 30: angle x-direction (1 byte) */\n    uint8_t angleYdirection;  /* Byte 31: angle y-direction (1 byte) */\n    uint8_t angleZdirection;  /* Byte 32: angle z-direction (1 byte) */\n    uint8_t gps_time_h;       //#33 UTC time hours\n    uint8_t gps_time_m;       //#34 UTC time minutes\n    uint8_t gps_time_s;       //#35 UTC time seconds\n    uint8_t gps_time_sss;     //#36 UTC time milliseconds\n    uint16_t msl_altitude;    //#37 mean sea level altitude\n    uint8_t vibration;        /* Byte 39: vibration (1 bytes) */\n    uint8_t Ascii4;           /* Byte 40: 00 ASCII Free Character [4] appears right to home distance */\n    uint8_t Ascii5;           /* Byte 41: 00 ASCII Free Character [5] appears right to home direction*/\n    uint8_t GPS_fix;          /* Byte 42: 00 ASCII Free Character [6], we use it for GPS FIX */\n    uint8_t version;          /* Byte 43: 00 version number */\n    uint8_t endByte;          /* Byte 44: 0x7D Ende byte */\n    uint8_t checksum;         /* Byte 45: Parity Byte */\n} __attribute__((packed)) hott_sensor_gps_t;\n\ntypedef struct hott_text_msg_t {\n    uint8_t start_byte;  //#01 Starting constant value == 0x7b\n    uint8_t esc;         //#02 Escape (higher-ranking menu in text mode or Text mode leave)\n                         // 0x00 to stay normal\n                         // 0x01 to exit\n                         // I will send 2 times, so the ESCAPE works really well, so two data frames with 0x01 in byte 2\n    uint8_t warning_beeps;  //#03 1=A 2=B ...\n    char text[8][21];       //#04...#171 168 ASCII text to display to\n                            // Bit 7 = 1 -> Inverse character display\n                            // Display 21x8\n    uint8_t stop_byte;      //#172 constant value 0x7d\n    uint8_t checksum;       //#173 Checksum / parity\n} __attribute__((packed)) hott_text_msg_t;\n\ntypedef struct hott_sensors_t {\n    bool is_enabled[4];\n    float *gps[HOTT_GPS_COUNT];\n    float *vario[HOTT_VARIO_COUNT];\n    float *esc[HOTT_ESC_COUNT];\n    float *general_air[HOTT_GENERAL_COUNT];\n} hott_sensors_t;\n\ntypedef struct trigger_t {\n    float value;\n    float max;\n    float incr;\n    char str[21];\n} trigger_t;\n\ntypedef enum gps_triggers_t {\n    TRIGGER_GPS_MIN_SPEED,\n    TRIGGER_GPS_MAX_SPEED,\n    TRIGGER_GPS_MIN_ALTITUDE,\n    TRIGGER_GPS_MAX_ALTITUDE,\n    TRIGGER_GPS_MAX_CLIMB,\n    TRIGGER_GPS_MIN_SATS,\n    TRIGGER_GPS_MAX_DISTANCE,\n    TRIGGERS_GPS\n} gps_triggers_t;\n\ntypedef enum vario_triggers_t {\n    TRIGGER_VARIO_MIN_ALTITUDE,\n    TRIGGER_VARIO_MAX_ALTITUDE,\n    TRIGGER_VARIO_VSPD,\n    TRIGGERS_VARIO\n} vario_triggers_t;\n\ntypedef enum esc_triggers_t {\n    TRIGGER_ESC_CONSUMPTION,\n    TRIGGER_ESC_TEMPERATURE,\n    TRIGGER_ESC_MIN_RPM,\n    TRIGGER_ESC_MAX_RPM,\n    TRIGGER_ESC_VOLTAGE,\n    TRIGGER_ESC_CURRENT,\n    TRIGGERS_ESC\n} esc_triggers_t;\n\ntypedef enum general_triggers_t {\n    TRIGGER_GENERAL_BATTERY,\n    TRIGGER_GENERAL_CAPACITY,\n    TRIGGER_GENERAL_TEMPERATURE,\n    TRIGGER_GENERAL_MIN_ALTITUDE,\n    TRIGGER_GENERAL_MAX_ALTITUDE,\n    TRIGGER_GENERAL_CURRENT,\n    TRIGGERS_GENERAL\n} general_triggers_t;\n\ntypedef struct triggers_value_t {\n    float gps[TRIGGERS_GPS];\n    float vario[TRIGGERS_VARIO];\n    float esc[TRIGGERS_ESC];\n    float general[TRIGGERS_GENERAL];\n} triggers_value_t;\n\ntypedef struct triggers_menu_t {\n    trigger_t gps[TRIGGERS_GPS];\n    trigger_t vario[TRIGGERS_VARIO];\n    trigger_t esc[TRIGGERS_ESC];\n    trigger_t general[TRIGGERS_GENERAL];\n} triggers_menu_t;\n\ntypedef struct triggers_t {\n    triggers_value_t *triggers;\n    triggers_menu_t *pages;\n} triggers_t;\n\ntypedef struct vario_alarm_parameters_t {\n    float *altitude;\n    float *vspd;\n    float m1s;\n    float m3s;\n    float m10s;\n} vario_alarm_parameters_t;\n\nvario_alarm_parameters_t vario_alarm_parameters;\nfloat *baro_temp = NULL, *baro_pressure = NULL;\n\nstatic void process(hott_sensors_t *sensors, triggers_t *alarms);\nstatic void format_binary_packet(triggers_t *alarms, hott_sensors_t *sensors, uint8_t address);\nstatic void format_text_packet(triggers_t *alarms, hott_sensors_t *sensors, uint8_t sensor_id, uint8_t key);\nstatic void send_packet(uint8_t *buffer, uint len);\nstatic void module_alarms_save(triggers_value_t *module_alarms);\nstatic triggers_value_t *alarms_read(void);\nstatic uint8_t get_crc(const uint8_t *buffer, uint len);\nstatic void set_config(hott_sensors_t *sensors);\nstatic int64_t interval_1000_callback(alarm_id_t id, void *parameters);\nstatic int64_t interval_3000_callback(alarm_id_t id, void *parameters);\nstatic int64_t interval_10000_callback(alarm_id_t id, void *parameters);\n\nvoid hott_task(void *parameters) {\n    hott_sensors_t sensors = {0};\n    triggers_menu_t pages = {.gps = {{.max = 200, .incr = 1, .str = \"Speed Min\"},\n                                     {.max = 200, .incr = 1, .str = \"Speed Max\"},\n                                     {.max = 5000, .incr = 1, .str = \"Alt Min\"},\n                                     {.max = 5000, .incr = 1, .str = \"Alt Max\"},\n                                     {.max = 200, .incr = 1, .str = \"Vspd Max\"},\n                                     {.max = 20, .incr = 1, .str = \"Sats Min\"},\n                                     {.max = 20000, .incr = 10, .str = \"Dist Max\"}},\n                             .vario = {{.max = 5000, .incr = 1, .str = \"Alt Min\"},\n                                       {.max = 5000, .incr = 1, .str = \"Alt Max\"},\n                                       {.max = 200, .incr = 1, .str = \"Vspd Max\"}},\n                             .esc = {{.max = 30000, .incr = 10, .str = \"Cons Max\"},\n                                     {.max = 100, .incr = 1, .str = \"Temp Max\"},\n                                     {.max = 20000, .incr = 10, .str = \"RPM Min\"},\n                                     {.max = 20000, .incr = 10, .str = \"RPM Max\"},\n                                     {.max = 100, .incr = 0.1, .str = \"Volt Min\"},\n                                     {.max = 300, .incr = 1, .str = \"Curr Max\"}},\n                             .general = {{.max = 100, .incr = 0.1, .str = \"Volt Min\"},\n                                         {.max = 100, .incr = 10, .str = \"Cons Max\"},\n                                         {.max = 100, .incr = 1, .str = \"Temp Max\"},\n                                         {.max = 5000, .incr = 1, .str = \"Alt Min\"},\n                                         {.max = 5000, .incr = 1, .str = \"Alt Max\"},\n                                         {.max = 300, .incr = 0.1, .str = \"Curr Max\"}}};\n    triggers_value_t triggers_value;\n    memcpy(&triggers_value, (uint8_t *)alarms_read(), sizeof(triggers_value_t));\n    triggers_t hott_alarms = {.triggers = &triggers_value, .pages = &pages};\n    set_config(&sensors);\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(19200, UART_RECEIVER_TX, UART_RECEIVER_RX, HOTT_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    debug(\"\\nHOTT init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&sensors, &hott_alarms);\n    }\n}\n\nstatic void process(hott_sensors_t *sensors, triggers_t *alarms) {\n    uint8_t len = uart0_available();\n    if (len == HOTT_PACKET_LENGHT || len == HOTT_PACKET_LENGHT + 1) {\n        uint8_t buffer[len];\n        uart0_read_bytes(buffer, len);\n        debug(\"\\nHOTT (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n        debug_buffer(buffer, len, \"0x%X \");\n        if (buffer[0] == HOTT_BINARY_MODE_REQUEST_ID)\n            format_binary_packet(alarms, sensors, buffer[1]);\n        else if (buffer[0] == HOTT_TEXT_MODE_REQUEST_ID)\n            format_text_packet(alarms, sensors, buffer[1] >> 4, buffer[1] & 0x0F);\n    }\n}\n\nstatic void format_text_packet(triggers_t *alarms, hott_sensors_t *sensors, uint8_t sensor_id, uint8_t key) {\n    static uint8_t item = 1;\n    static uint8_t last_sensor = 0;\n    static bool is_selected = false;\n    hott_text_msg_t packet = {0};\n    packet.start_byte = 0x7B;\n    packet.esc = 0x00;\n    packet.warning_beeps = 0x00;\n    uint size = 0;\n    trigger_t *module_alarms_pages = NULL;\n    float *module_alarms_triggers = NULL;\n    if (sensor_id != last_sensor) {\n        item = 1;\n        is_selected = false;\n    }\n    last_sensor = sensor_id;\n\n    // Select module alarms\n    switch (0x80 | sensor_id) {\n        case HOTT_GPS_MODULE_ID:\n            if (!sensors->is_enabled[HOTT_TYPE_GPS]) return;\n            size = TRIGGERS_GPS;\n            module_alarms_pages = alarms->pages->gps;\n            module_alarms_triggers = alarms->triggers->gps;\n            strcpy(packet.text[0], \"GPS sensor\");\n            break;\n        case HOTT_VARIO_MODULE_ID:\n            if (!sensors->is_enabled[HOTT_TYPE_VARIO]) return;\n            size = TRIGGERS_VARIO;\n            module_alarms_pages = alarms->pages->vario;\n            module_alarms_triggers = alarms->triggers->vario;\n            strcpy(packet.text[0], \"Vario sensor\");\n            break;\n        case HOTT_ESC_MODULE_ID:\n            if (!sensors->is_enabled[HOTT_TYPE_ESC]) return;\n            size = TRIGGERS_ESC;\n            module_alarms_pages = alarms->pages->esc;\n            module_alarms_triggers = alarms->triggers->esc;\n            strcpy(packet.text[0], \"ESC sensor\");\n            break;\n        case HOTT_GENERAL_AIR_MODULE_ID:\n            if (!sensors->is_enabled[HOTT_TYPE_GENERAL]) return;\n            size = TRIGGERS_GENERAL;\n            module_alarms_pages = alarms->pages->general;\n            module_alarms_triggers = alarms->triggers->general;\n            strcpy(packet.text[0], \"General sensor\");\n            break;\n        default:\n            break;\n    }\n    if (!module_alarms_triggers) return;\n\n    // Handle events\n    if (key == HOTT_KEY_LEFT) {\n        if (is_selected) {\n            module_alarms_triggers[item] -= 10 * module_alarms_pages[item].incr;\n            if (module_alarms_triggers[item] < 0) module_alarms_triggers[item] = 0;\n        } else {\n            packet.esc = 0x01;  // exit\n            strcat(packet.text[0], \" (Exit)\");\n            is_selected = false;\n            item = 1;\n        }\n    } else if (key == HOTT_KEY_RIGHT && is_selected) {\n        module_alarms_triggers[item] += 10 * module_alarms_pages[item].incr;\n        if (module_alarms_triggers[item] > module_alarms_pages[item].max)\n            module_alarms_triggers[item] = module_alarms_pages[item].max;\n    } else if (key == HOTT_KEY_UP) {\n        if (!is_selected) {\n            item++;\n            if (item > size - 1) item = size - 1;\n        } else {\n            module_alarms_triggers[item] += module_alarms_pages[item].incr;\n            if (module_alarms_triggers[item] > module_alarms_pages[item].max)\n                module_alarms_triggers[item] = module_alarms_pages[item].max;\n        }\n    } else if (key == HOTT_KEY_DOWN) {\n        if (!is_selected) {\n            item--;\n            if (item < 0) item = 0;\n        } else {\n            module_alarms_triggers[item] -= module_alarms_pages[item].incr;\n            if (module_alarms_triggers[item] < 0) module_alarms_triggers[item] = 0;\n        }\n    } else if (key == HOTT_KEY_SET) {\n        if (is_selected) {\n            module_alarms_save(alarms->triggers);\n            strcat(packet.text[0], \" (Saved)\");\n            debug(\"\\nHOTT (%u). Saved sensor config.\", uxTaskGetStackHighWaterMark(NULL));\n            is_selected = false;\n        } else {\n            is_selected = true;\n        }\n    } else {\n        debug(\"\\nHOTT (%u). Unknown key 0x%X.\", uxTaskGetStackHighWaterMark(NULL), key);\n    }\n\n    // Draw page\n    // 123456789012345678901\n    // <str          > 12345\n    if (size > 7) size = 7;\n    for (int i = 0; i < size; i++) {\n        if (module_alarms_triggers[i] < 0) module_alarms_triggers[i] = 0;\n        if (module_alarms_triggers[i] > module_alarms_pages[i].max)\n            module_alarms_triggers[i] = module_alarms_pages[i].max;\n        if (isinf(module_alarms_triggers[i])) module_alarms_triggers[i] = 0;\n        if (isnan(module_alarms_triggers[i])) module_alarms_triggers[i] = 0;\n        if (module_alarms_pages[i].incr == 1)\n            snprintf(packet.text[i + 1], 21, \" %-13s %5.0f\", module_alarms_pages[i].str, module_alarms_triggers[i]);\n        else\n            snprintf(packet.text[i + 1], 21, \" %-13s %5.1f\", module_alarms_pages[i].str, module_alarms_triggers[i]);\n    }\n    packet.text[item + 1][0] |= '>';\n    if (is_selected) {\n        for (int i = 16; i < 21; i++) packet.text[item + 1][i] |= 0x80;\n    }\n\n    // Send packet\n    packet.stop_byte = 0x7D;\n    packet.checksum = get_crc((uint8_t *)&packet, sizeof(packet) - 1);\n    debug(\"\\nHOTT (%u). Send sensor menu. Item %d Selected %s Key 0x%X Exit %d Value %.1f\",\n          uxTaskGetStackHighWaterMark(NULL), item, is_selected ? \"Yes\" : \"No\", key, packet.esc,\n          module_alarms_triggers[item]);\n    for (uint i = 0; i < size + 1; i++) {\n        debug(\"\\n%.21s\", packet.text[i]);\n    }\n    send_packet((uint8_t *)&packet, sizeof(packet));\n}\n\nstatic void format_binary_packet(triggers_t *alarms, hott_sensors_t *sensors, uint8_t address) {\n    // packet in little endian\n    switch (address) {\n        case HOTT_VARIO_MODULE_ID: {\n            static uint16_t max_altitude = 0, min_altitude = 0xFFFF;\n            if (!sensors->is_enabled[HOTT_TYPE_VARIO]) return;\n            hott_sensor_vario_t packet = {0};\n            packet.startByte = HOTT_START_BYTE;\n            packet.sensorID = HOTT_VARIO_MODULE_ID;\n            packet.sensorTextID = HOTT_VARIO_TEXT_ID;\n            if (*sensors->vario[HOTT_VARIO_ALTITUDE] < alarms->triggers->vario[TRIGGER_VARIO_MIN_ALTITUDE]) {\n                packet.warningID = ALARM_VOICE_MIN_ALTITUDE;\n                packet.alarmInverse |= 1 << ALARM_BITMASK_VARIO_ALTITUDE;\n                packet.alarmInverse |= 1 << ALARM_BITMASK_VARIO_MIN_ALTITUDE;\n            }\n            if (*sensors->vario[HOTT_VARIO_ALTITUDE] > alarms->triggers->vario[TRIGGER_VARIO_MAX_ALTITUDE]) {\n                packet.warningID = ALARM_VOICE_MAX_ALTITUDE;\n                packet.alarmInverse |= 1 << ALARM_BITMASK_VARIO_ALTITUDE;\n                packet.alarmInverse |= 1 << ALARM_BITMASK_VARIO_MAX_ALTITUDE;\n            }\n            if (*sensors->vario[HOTT_VARIO_M1S] < alarms->triggers->vario[TRIGGER_VARIO_VSPD]) {\n                packet.alarmInverse |= 1 << ALARM_BITMASK_VARIO_M1S;\n            }\n            packet.altitude = *sensors->vario[HOTT_VARIO_ALTITUDE] + 500;\n            if (max_altitude < packet.altitude) max_altitude = packet.altitude;\n            if (min_altitude > packet.altitude) min_altitude = packet.altitude;\n            packet.maxAltitude = max_altitude;\n            packet.minAltitude = min_altitude;\n            packet.m1s = *sensors->vario[HOTT_VARIO_M1S] * 100 + 30000;\n            packet.m3s = vario_alarm_parameters.m3s;\n            packet.m10s = vario_alarm_parameters.m10s;\n            packet.endByte = HOTT_END_BYTE;\n            packet.checksum = get_crc((uint8_t *)&packet, sizeof(packet) - 1);\n            send_packet((uint8_t *)&packet, sizeof(packet));\n            break;\n        }\n        case HOTT_ESC_MODULE_ID: {\n            if (!sensors->is_enabled[HOTT_TYPE_ESC]) return;\n            hott_sensor_airesc_t packet = {0};\n            static uint16_t minInputVolt = 0xFFFF;\n            static uint8_t maxEscTemperature = 0;\n            static uint16_t maxCurrent = 0;\n            static uint16_t maxRPM = 0;\n            static uint16_t maxSpeed = 0;\n            static uint16_t minBECCurrent = 0xFFFF;\n            static uint16_t maxBECCurrent = 0;\n            static uint16_t minBECVoltage = 0xFFFF;\n            static uint16_t maxBECTemperature = 0;\n            static uint16_t maxMotorOrExtTemperature = 0;\n            packet.startByte = HOTT_START_BYTE;\n            packet.sensorID = HOTT_ESC_MODULE_ID;\n            packet.sensorTextID = HOTT_ESC_TEXT_ID;\n            if (sensors->esc[HOTT_ESC_VOLTAGE]) {\n                packet.inputVolt = *sensors->esc[HOTT_ESC_VOLTAGE] * 10;\n                if (packet.inputVolt < minInputVolt) packet.minInputVolt = packet.inputVolt;\n                if (*sensors->esc[HOTT_ESC_VOLTAGE] < alarms->triggers->esc[TRIGGER_ESC_VOLTAGE]) {\n                    packet.warningID = ALARM_VOICE_MIN_POWER_VOLTAGE;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_MIN_VOLTAGE;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_VOLTAGE;\n                }\n            }\n            if (sensors->esc[HOTT_ESC_CONSUMPTION]) {\n                packet.capacity = *sensors->esc[HOTT_ESC_CONSUMPTION] / 10;\n                if (*sensors->esc[HOTT_ESC_CONSUMPTION] > alarms->triggers->esc[TRIGGER_ESC_CONSUMPTION]) {\n                    packet.warningID = ALARM_VOICE_MAX_CAPACITY;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_CAPACITY;\n                }\n            }\n            if (sensors->esc[HOTT_ESC_TEMPERATURE]) {\n                packet.escTemperature = *sensors->esc[HOTT_ESC_TEMPERATURE] + 20;\n                if (packet.escTemperature > maxEscTemperature) packet.maxEscTemperature = packet.escTemperature;\n                if (*sensors->esc[HOTT_ESC_TEMPERATURE] > alarms->triggers->esc[TRIGGER_ESC_TEMPERATURE]) {\n                    packet.warningID = ALARM_VOICE_MAX_SENSOR_1_TEMP;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_TEMPERATURE;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_MAX_TEMPERATURE;\n                }\n            } else {\n                packet.escTemperature = 20;\n            }\n            if (sensors->esc[HOTT_ESC_CURRENT]) {\n                packet.current = *sensors->esc[HOTT_ESC_CURRENT] * 10;\n                if (packet.current > maxCurrent) packet.maxCurrent = packet.current;\n                if (*sensors->esc[HOTT_ESC_CURRENT] > alarms->triggers->esc[TRIGGER_ESC_CURRENT]) {\n                    packet.warningID = ALARM_VOICE_MAX_CURRENT;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_MAX_CURRENT;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_CURRENT;\n                }\n            }\n            if (sensors->esc[HOTT_ESC_RPM]) {\n                packet.RPM = *sensors->esc[HOTT_ESC_RPM] / 10;\n                if (packet.RPM > maxRPM) packet.maxRPM = packet.RPM;\n                if (*sensors->esc[HOTT_ESC_RPM] < alarms->triggers->esc[TRIGGER_ESC_MIN_RPM]) {\n                    packet.warningID = ALARM_VOICE_MIN_RPM;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_RPM;\n                }\n                if (*sensors->esc[HOTT_ESC_RPM] > alarms->triggers->esc[TRIGGER_ESC_MAX_RPM]) {\n                    packet.warningID = ALARM_VOICE_MAX_RPM;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_RPM;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_AIRESC_MAX_RPM;\n                }\n            }\n            // uint8_t throttlePercent;            // Byte 22\n            if (sensors->esc[HOTT_ESC_SPEED]) {\n                packet.speed = *sensors->esc[HOTT_ESC_SPEED];\n                if (packet.speed > maxSpeed) packet.maxSpeed = packet.speed;\n            }\n            if (sensors->esc[HOTT_ESC_BEC_VOLTAGE]) {\n                packet.BECVoltage = *sensors->esc[HOTT_ESC_BEC_VOLTAGE] * 10;\n                if (packet.minBECVoltage < minBECVoltage) packet.minBECVoltage = packet.BECVoltage;\n            }\n            if (sensors->esc[HOTT_ESC_BEC_CURRENT]) {\n                packet.BECCurrent = *sensors->esc[HOTT_ESC_BEC_CURRENT] * 10;\n                if (packet.BECCurrent < minBECCurrent) packet.minBECCurrent = packet.BECCurrent;\n                if (packet.BECCurrent > maxBECCurrent) packet.maxBECCurrent = packet.BECCurrent;\n            }\n            // uint8_t PWM;                        // Byte 32\n            if (sensors->esc[HOTT_ESC_BEC_TEMPERATURE]) {\n                packet.BECTemperature = *sensors->esc[HOTT_ESC_BEC_TEMPERATURE] + 20;\n                if (packet.BECTemperature > maxBECTemperature) packet.maxBECTemperature = packet.BECTemperature;\n            }\n            if (sensors->esc[HOTT_ESC_EXT_TEMPERATURE]) {\n                packet.motorOrExtTemperature = *sensors->esc[HOTT_ESC_EXT_TEMPERATURE] + 20;\n                if (packet.motorOrExtTemperature > maxMotorOrExtTemperature)\n                    packet.maxMotorOrExtTemperature = packet.motorOrExtTemperature;\n            }\n            // uint16_t RPMWithoutGearOrExt;       // Byte 37\n            // uint8_t timing;                     // Byte 39\n            // uint8_t advancedTiming;             // Byte 40\n            // uint8_t highestCurrentMotorNumber;  // Byte 41\n            packet.endByte = HOTT_END_BYTE;\n            packet.checksum = get_crc((uint8_t *)&packet, sizeof(packet) - 1);\n            send_packet((uint8_t *)&packet, sizeof(packet));\n            break;\n        }\n        case HOTT_GENERAL_AIR_MODULE_ID: {\n            if (!sensors->is_enabled[HOTT_TYPE_GENERAL]) return;\n            hott_sensor_general_air_t packet = {0};\n            packet.startByte = HOTT_START_BYTE;\n            packet.sensorID = HOTT_GENERAL_AIR_MODULE_ID;\n            packet.sensorTextID = HOTT_GENERAL_AIR_TEXT_ID;\n            if (sensors->general_air[HOTT_GENERAL_BATTERY_1]) {\n                packet.battery1 = *sensors->general_air[HOTT_GENERAL_BATTERY_1] * 10;\n                if (*sensors->general_air[HOTT_GENERAL_BATTERY_1] <\n                    alarms->triggers->general[TRIGGER_GENERAL_BATTERY]) {\n                    packet.warningID = ALARM_VOICE_MIN_POWER_VOLTAGE;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_GENERAL_AIR_BATTERY_1;\n                }\n            }\n            if (sensors->general_air[HOTT_GENERAL_CURRENT]) {\n                packet.current = *sensors->general_air[HOTT_GENERAL_CURRENT] * 10;\n                if (*sensors->general_air[HOTT_GENERAL_CURRENT] < alarms->triggers->general[TRIGGER_GENERAL_CURRENT]) {\n                    packet.warningID = ALARM_VOICE_MAX_CURRENT;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_GENERAL_AIR_CURRENT;\n                }\n            }\n            if (sensors->general_air[HOTT_GENERAL_CAPACITY]) {\n                packet.batt_cap = *sensors->general_air[HOTT_GENERAL_CAPACITY] / 10;\n                if (*sensors->general_air[HOTT_GENERAL_CAPACITY] >\n                    alarms->triggers->general[TRIGGER_GENERAL_CAPACITY]) {\n                    packet.warningID = ALARM_VOICE_MAX_CAPACITY;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_GENERAL_AIR_CAPACITY;\n                }\n            }\n            if (sensors->general_air[HOTT_GENERAL_PRESSURE]) {\n                packet.pressure =\n                    *sensors->general_air[HOTT_GENERAL_PRESSURE] * 1e-5 * 10;  // Pa -> bar (in steps of 0.1 bar)\n            }\n            if (sensors->general_air[HOTT_GENERAL_ALTITUDE]) {\n                if (*sensors->vario[HOTT_GENERAL_ALTITUDE] < alarms->triggers->vario[TRIGGER_GENERAL_MIN_ALTITUDE]) {\n                    packet.warningID = ALARM_VOICE_MIN_ALTITUDE;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_GENERAL_AIR_ALTITUDE;\n                }\n                if (*sensors->vario[HOTT_GENERAL_ALTITUDE] > alarms->triggers->vario[TRIGGER_GENERAL_MAX_ALTITUDE]) {\n                    packet.warningID = ALARM_VOICE_MAX_ALTITUDE;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_GENERAL_AIR_ALTITUDE;\n                }\n                packet.altitude = *sensors->general_air[HOTT_GENERAL_ALTITUDE] + 500;\n            }\n            if (sensors->general_air[HOTT_GENERAL_CLIMBRATE]) {\n                packet.climbrate = *sensors->general_air[HOTT_GENERAL_CLIMBRATE] * 100 + 30000;\n            }\n            if (sensors->general_air[HOTT_GENERAL_CELL_1]) {\n                packet.cell[0] = *sensors->general_air[HOTT_GENERAL_CELL_1] * 50;\n            }\n            if (sensors->general_air[HOTT_GENERAL_CELL_2]) {\n                packet.cell[1] = *sensors->general_air[HOTT_GENERAL_CELL_2] * 50;\n            }\n            if (sensors->general_air[HOTT_GENERAL_CELL_3]) {\n                packet.cell[2] = *sensors->general_air[HOTT_GENERAL_CELL_3] * 50;\n            }\n            if (sensors->general_air[HOTT_GENERAL_CELL_4]) {\n                packet.cell[3] = *sensors->general_air[HOTT_GENERAL_CELL_4] * 50;\n            }\n            if (sensors->general_air[HOTT_GENERAL_CELL_5]) {\n                packet.cell[4] = *sensors->general_air[HOTT_GENERAL_CELL_5] * 50;\n            }\n            if (sensors->general_air[HOTT_GENERAL_CELL_6]) {\n                packet.cell[5] = *sensors->general_air[HOTT_GENERAL_CELL_6] * 50;\n            }\n            if (sensors->general_air[HOTT_GENERAL_TEMP_1]) {\n                packet.temperature1 = *sensors->general_air[HOTT_GENERAL_TEMP_1] + 20;\n                if (*sensors->general_air[HOTT_GENERAL_TEMP_1] >\n                    alarms->triggers->general[TRIGGER_GENERAL_TEMPERATURE]) {\n                    packet.warningID = ALARM_VOICE_MAX_SENSOR_1_TEMP;\n                    packet.alarmInverse |= 1 << ALARM_BITMASK_GENERAL_AIR_TEMPERATURE_1;\n                }\n            } else {\n                packet.temperature1 = 20;\n            }\n            if (sensors->general_air[HOTT_GENERAL_TEMP_2]) {\n                packet.temperature2 = *sensors->general_air[HOTT_GENERAL_TEMP_2] + 20;\n\n            } else {\n                packet.temperature2 = 20;\n            }\n            packet.endByte = HOTT_END_BYTE;\n            packet.checksum = get_crc((uint8_t *)&packet, sizeof(packet) - 1);\n            send_packet((uint8_t *)&packet, sizeof(packet));\n            break;\n        }\n        case HOTT_GPS_MODULE_ID: {\n            if (!sensors->is_enabled[HOTT_TYPE_GPS]) return;\n            hott_sensor_gps_t packet = {0};\n            if (*sensors->vario[HOTT_GPS_SPEED] < alarms->triggers->vario[TRIGGER_GPS_MIN_SPEED]) {\n                packet.alarmInverse |= 1 << ALARM_BITMASK_GPS_SPEED;\n            }\n            if (*sensors->vario[HOTT_GPS_SPEED] > alarms->triggers->vario[TRIGGER_GPS_MAX_SPEED]) {\n                packet.alarmInverse |= 1 << ALARM_BITMASK_GPS_SPEED;\n            }\n            if (*sensors->vario[HOTT_GPS_ALTITUDE] < alarms->triggers->vario[TRIGGER_GPS_MIN_ALTITUDE]) {\n                packet.warningID = ALARM_VOICE_MIN_ALTITUDE;\n                packet.alarmInverse |= 1 << ALARM_BITMASK_GPS_ALTITUDE;\n            }\n            if (*sensors->vario[HOTT_GPS_ALTITUDE] > alarms->triggers->vario[TRIGGER_GPS_MAX_ALTITUDE]) {\n                packet.warningID = ALARM_VOICE_MAX_ALTITUDE;\n                packet.alarmInverse |= 1 << ALARM_BITMASK_GPS_ALTITUDE;\n            }\n            if (*sensors->vario[HOTT_GPS_CLIMBRATE] < alarms->triggers->vario[TRIGGER_GPS_MAX_CLIMB]) {\n                packet.alarmInverse |= 1 << ALARM_BITMASK_GPS_CLIMBRATE;\n            }\n            if (*sensors->vario[HOTT_GPS_SATS] < alarms->triggers->vario[TRIGGER_GPS_MIN_SATS]) {\n                packet.alarmInverse |= 1 << ALARM_BITMASK_GPS_SATS;\n            }\n            packet.startByte = HOTT_START_BYTE;\n            packet.sensorID = HOTT_GPS_MODULE_ID;\n            packet.sensorTextID = HOTT_GPS_TEXT_ID;\n            packet.flightDirection = *sensors->gps[HOTT_GPS_DIRECTION] / 2;  // 2°\n            packet.GPSSpeed = *sensors->gps[HOTT_GPS_SPEED];                 // km/h\n\n            packet.LatitudeNS = (*sensors->gps[HOTT_GPS_LATITUDE] < 0) ? 1 : 0;\n            {\n                float lat = fabsf(*sensors->gps[HOTT_GPS_LATITUDE]);\n                uint16_t deg = (uint16_t)lat;\n                float min_f = (lat - (float)deg) * 60.0f;\n                uint16_t min_i = (uint16_t)min_f;\n                /* HoTT: DDMM */\n                packet.LatitudeDegMin = (uint16_t)(deg * 100u + min_i);\n                /* HoTT: minutes fraction * 10000 */\n                packet.LatitudeSec = (uint16_t)lroundf((min_f - (float)min_i) * 10000.0f);\n            }\n            packet.longitudeEW = (*sensors->gps[HOTT_GPS_LONGITUDE] < 0) ? 1 : 0;\n            {\n                float lon = fabsf(*sensors->gps[HOTT_GPS_LONGITUDE]);\n                uint16_t deg = (uint16_t)lon;\n                float min_f = (lon - (float)deg) * 60.0f;\n                uint16_t min_i = (uint16_t)min_f;\n                /* HoTT: DDMM */\n                packet.longitudeDegMin = (uint16_t)(deg * 100u + min_i);\n                /* HoTT: minutes fraction * 10000 */\n                packet.longitudeSec = (uint16_t)lroundf((min_f - (float)min_i) * 10000.0f);\n            }\n\n            packet.distance = *sensors->gps[HOTT_GPS_DISTANCE];\n            float climbrate = *sensors->gps[HOTT_GPS_CLIMBRATE] * 100 + 30000;\n            if (climbrate < 0) climbrate = 0;\n            packet.climbrate = climbrate;  // 30000, 0.00\n            // packet.climbrate3s = *sensors->gps[HOTT_GPS_ALTITUDE];  // 120, 0\n            packet.GPSNumSat = *sensors->gps[HOTT_GPS_SATS] == 0 ? 0xFF : *sensors->gps[HOTT_GPS_SATS];\n            uint8_t fix_type = *sensors->gps[HOTT_GPS_FIX];\n            if (fix_type == 0)\n                packet.GPSFixChar = ' ';\n            else if (fix_type == 1)\n                packet.GPSFixChar = '2';\n            else\n                packet.GPSFixChar = '3';\n            // (1 byte) uint8_t homeDirection;   // Byte 29: HomeDirection (direction from starting point to Model\n            // position) (1 byte)\n            uint hour = (uint)(*sensors->gps[HOTT_GPS_TIME]) / 10000;\n            uint min = (uint)(*sensors->gps[HOTT_GPS_TIME]) / 100 - hour * 100;\n            uint sec = (uint)(*sensors->gps[HOTT_GPS_TIME]) - hour * 10000 - min * 100;\n            packet.gps_time_h = hour;\n            packet.gps_time_m = min;\n            packet.gps_time_s = sec;\n            // uint8_t gps_time_sss;//#36 UTC time milliseconds\n            if (*sensors->gps[HOTT_GPS_ALTITUDE] < 0)\n                packet.msl_altitude = 0;\n            else\n                packet.msl_altitude = *sensors->gps[HOTT_GPS_ALTITUDE];\n            if (*sensors->gps[HOTT_GPS_ALTITUDE] - *sensors->gps[HOTT_GPS_HOME_ALTITUDE] + 500 < 0)\n                packet.altitude = 0;\n            else\n                packet.altitude = *sensors->gps[HOTT_GPS_ALTITUDE] - *sensors->gps[HOTT_GPS_HOME_ALTITUDE] + 500;\n            // uint8_t vibration; // Byte 39 vibrations level in %\n            // uint8_t Ascii4;    // Byte 40: 00 ASCII Free Character [4] appears right to home distance\n            // uint8_t Ascii5;    // Byte 41: 00 ASCII Free Character [5] appears right to home direction\n            packet.GPS_fix = (fix_type == 0) ? ' ' : (fix_type == 1) ? '2' : '3';\n            // uint8_t version;   // Byte 43: 00 version number\n            packet.endByte = HOTT_END_BYTE;\n            packet.checksum = get_crc((uint8_t *)&packet, sizeof(packet) - 1);\n            send_packet((uint8_t *)&packet, sizeof(packet));\n            break;\n        }\n    }\n}\n\nstatic void send_packet(uint8_t *buffer, uint len) {\n    for (uint i = 0; i < len; i++) {\n        uart0_write(*(buffer + i));\n        sleep_us(HOTT_INTERBYTE_DELAY_US);\n    }\n    vTaskResume(context.led_task_handle);\n    if (len < 100) {\n        debug(\"\\nHOTT (%u) %u > \", uxTaskGetStackHighWaterMark(NULL), len);\n        debug_buffer(buffer, len, \"0x%X \");\n    }\n}\n\nstatic triggers_value_t *alarms_read(void) {\n    triggers_value_t *alarms = (triggers_value_t *)(XIP_BASE + ALARMS_FLASH_TARGET_OFFSET);\n    return (triggers_value_t *)(XIP_BASE + ALARMS_FLASH_TARGET_OFFSET);\n}\n\nstatic void module_alarms_save(triggers_value_t *triggers_value) {\n    uint8_t flash[FLASH_PAGE_SIZE] = {0};\n    memcpy(flash, (uint8_t *)triggers_value, sizeof(triggers_value_t));\n    uint32_t ints = save_and_disable_interrupts();\n    flash_range_erase(ALARMS_FLASH_TARGET_OFFSET, FLASH_SECTOR_SIZE);\n    flash_range_program(ALARMS_FLASH_TARGET_OFFSET, flash, sizeof(triggers_value_t));\n    restore_interrupts(ints);\n    debug(\"\\nHOTT Alarms saved\");\n}\n\nstatic uint8_t get_crc(const uint8_t *buffer, uint len) {\n    uint16_t crc = 0;\n    for (uint i = 0; i < len; i++) {\n        crc += buffer[i];\n    }\n    // debug(\"\\n>CRC: 0x%X\", crc & 0xFF);\n    return crc;\n}\n\nstatic void set_config(hott_sensors_t *sensors) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temperature_fet;\n        sensors->esc[HOTT_ESC_BEC_TEMPERATURE] = parameter.temperature_bec;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temperature_fet;\n        sensors->esc[HOTT_ESC_BEC_TEMPERATURE] = parameter.temperature_bec;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_BEC_VOLTAGE] = parameter.voltage_bec;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_BEC_CURRENT] = parameter.current_bec;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n        sensors->esc[HOTT_ESC_EXT_TEMPERATURE] = parameter.temperature_motor;\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temperature;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_BEC_VOLTAGE] = parameter.voltage_bec;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_BEC_CURRENT] = parameter.current_bec;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n        sensors->esc[HOTT_ESC_EXT_TEMPERATURE] = parameter.consumption;\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temperature_fet;\n        sensors->esc[HOTT_ESC_BEC_TEMPERATURE] = parameter.temperature_bec;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_BEC_VOLTAGE] = parameter.voltage_bec;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_BEC_CURRENT] = parameter.current_bec;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temperature;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temperature;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temperature_fet;\n        sensors->esc[HOTT_ESC_BEC_TEMPERATURE] = parameter.temperature_bec;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_BEC_VOLTAGE] = parameter.voltage_bec;\n        sensors->esc[HOTT_ESC_BEC_CURRENT] = parameter.current_bec;\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_TEMP_1] = parameter.temperature_bat;\n        sensors->general_air[HOTT_GENERAL_CURRENT] = parameter.current_bat;\n        sensors->general_air[HOTT_GENERAL_CAPACITY] = parameter.consumption;\n        sensors->general_air[HOTT_GENERAL_CELL_1] = parameter.cell[0];\n        sensors->general_air[HOTT_GENERAL_CELL_2] = parameter.cell[1];\n        sensors->general_air[HOTT_GENERAL_CELL_3] = parameter.cell[2];\n        sensors->general_air[HOTT_GENERAL_CELL_4] = parameter.cell[3];\n        sensors->general_air[HOTT_GENERAL_CELL_5] = parameter.cell[4];\n        sensors->general_air[HOTT_GENERAL_CELL_6] = parameter.cell[5];\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_SMART_ESC, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temp_esc;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n        sensors->esc[HOTT_ESC_EXT_TEMPERATURE] = parameter.temp_motor;\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_RPM] = parameter.rpm;\n        sensors->esc[HOTT_ESC_TEMPERATURE] = parameter.temp_esc;\n        sensors->esc[HOTT_ESC_VOLTAGE] = parameter.voltage;\n        sensors->esc[HOTT_ESC_CURRENT] = parameter.current;\n        sensors->esc[HOTT_ESC_CONSUMPTION] = parameter.consumption;\n        sensors->esc[HOTT_ESC_EXT_TEMPERATURE] = parameter.temp_motor;\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_GPS] = true;\n        sensors->gps[HOTT_GPS_LATITUDE] = parameter.lat;\n        sensors->gps[HOTT_GPS_LONGITUDE] = parameter.lon;\n        sensors->gps[HOTT_GPS_SATS] = parameter.sat;\n        sensors->gps[HOTT_GPS_FIX] = parameter.fix;\n        sensors->gps[HOTT_GPS_ALTITUDE] = parameter.alt;\n        sensors->gps[HOTT_GPS_SPEED] = parameter.spd_kmh;\n        sensors->gps[HOTT_GPS_DIRECTION] = parameter.cog;\n        sensors->gps[HOTT_GPS_DISTANCE] = parameter.dist;\n        sensors->gps[HOTT_GPS_CLIMBRATE] = parameter.vspeed;\n        sensors->gps[HOTT_GPS_TIME] = parameter.time;\n        sensors->gps[HOTT_GPS_HOME_ALTITUDE] = parameter.alt_home;\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_BATTERY_1] = parameter.voltage;\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_CURRENT] = parameter.current;\n        sensors->general_air[HOTT_GENERAL_CAPACITY] = parameter.consumption;\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_TEMP_1] = parameter.ntc;\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_ESC] = true;\n        sensors->esc[HOTT_ESC_SPEED] = parameter.airspeed;\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensors->is_enabled[HOTT_TYPE_VARIO] = true;\n        sensors->vario[HOTT_VARIO_ALTITUDE] = parameter.altitude;\n        sensors->vario[HOTT_VARIO_M1S] = parameter.vspeed;\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_ALTITUDE] = parameter.altitude;\n        sensors->general_air[HOTT_GENERAL_CLIMBRATE] = parameter.vspeed;\n\n        vario_alarm_parameters.altitude = parameter.altitude;\n\n        add_alarm_in_ms(1000, interval_1000_callback, &vario_alarm_parameters, false);\n        add_alarm_in_ms(3000, interval_3000_callback, &vario_alarm_parameters, false);\n        add_alarm_in_ms(10000, interval_10000_callback, &vario_alarm_parameters, false);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensors->is_enabled[HOTT_TYPE_VARIO] = true;\n        sensors->vario[HOTT_VARIO_ALTITUDE] = parameter.altitude;\n        sensors->vario[HOTT_VARIO_M1S] = parameter.vspeed;\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_ALTITUDE] = parameter.altitude;\n        sensors->general_air[HOTT_GENERAL_CLIMBRATE] = parameter.vspeed;\n\n        vario_alarm_parameters.altitude = parameter.altitude;\n\n        add_alarm_in_ms(1000, interval_1000_callback, &vario_alarm_parameters, false);\n        add_alarm_in_ms(3000, interval_3000_callback, &vario_alarm_parameters, false);\n        add_alarm_in_ms(10000, interval_10000_callback, &vario_alarm_parameters, false);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensors->is_enabled[HOTT_TYPE_VARIO] = true;\n        sensors->vario[HOTT_VARIO_ALTITUDE] = parameter.altitude;\n        sensors->vario[HOTT_VARIO_M1S] = parameter.vspeed;\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_ALTITUDE] = parameter.altitude;\n        sensors->general_air[HOTT_GENERAL_CLIMBRATE] = parameter.vspeed;\n\n        vario_alarm_parameters.altitude = parameter.altitude;\n\n        add_alarm_in_ms(1000, interval_1000_callback, &vario_alarm_parameters, false);\n        add_alarm_in_ms(3000, interval_3000_callback, &vario_alarm_parameters, false);\n        add_alarm_in_ms(10000, interval_10000_callback, &vario_alarm_parameters, false);\n    }\n    if (config->enable_fuel_flow) {\n        fuel_meter_parameters_t parameter = {config->fuel_flow_ml_per_pulse, malloc(sizeof(float)),\n                                             malloc(sizeof(float))};\n        xTaskCreate(fuel_meter_task, \"fuel_meter_task\", STACK_FUEL_METER, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_FUEL] = parameter.consumption_total;\n    }\n    if (config->enable_fuel_pressure) {\n        xgzp68xxd_parameters_t parameter = {config->xgzp68xxd_k, malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(xgzp68xxd_task, \"fuel_pressure_task\", STACK_FUEL_PRESSURE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n        sensors->general_air[HOTT_GENERAL_PRESSURE] = parameter.pressure;\n    }\n    if (config->enable_lipo) {\n        float *cell_prev = 0;\n        if (config->lipo_cells > 0) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x40,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            *parameter.cell_prev = 0;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n            sensors->is_enabled[HOTT_TYPE_GENERAL] = true;\n            sensors->general_air[HOTT_GENERAL_CELL_1] = parameter.cell[0];\n            sensors->general_air[HOTT_GENERAL_CELL_2] = parameter.cell[1];\n            sensors->general_air[HOTT_GENERAL_CELL_3] = parameter.cell[2];\n        }\n        if (config->lipo_cells > 3) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x41,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells - 3, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            parameter.cell_prev = cell_prev;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n            sensors->general_air[HOTT_GENERAL_CELL_4] = parameter.cell[0];\n            sensors->general_air[HOTT_GENERAL_CELL_5] = parameter.cell[1];\n            sensors->general_air[HOTT_GENERAL_CELL_6] = parameter.cell[2];\n        }\n    }\n}\n\nstatic int64_t interval_1000_callback(alarm_id_t id, void *parameters) {\n    vario_alarm_parameters_t *parameter = (vario_alarm_parameters_t *)parameters;\n    static float prev = 0;\n    parameter->m1s = (*parameter->altitude - prev) * 100 + 30000;\n#ifdef SIM_SENSORS\n    vario_alarm_parameters.m1s = 12 * 100 + 30000;\n#endif\n    prev = *parameter->altitude;\n    return 1000000L;\n}\n\nstatic int64_t interval_3000_callback(alarm_id_t id, void *parameters) {\n    vario_alarm_parameters_t *parameter = (vario_alarm_parameters_t *)parameters;\n    static float prev = 0;\n    parameter->m3s = (*parameter->altitude - prev) * 100 + 30000;\n    *parameter->vspd = parameter->m3s / 3.0F;\n#ifdef SIM_SENSORS\n    vario_alarm_parameters.m3s = 34 * 100 + 30000;\n#endif\n    prev = *parameter->altitude;\n    return 3000000L;\n}\n\nstatic int64_t interval_10000_callback(alarm_id_t id, void *parameters) {\n    vario_alarm_parameters_t *parameter = (vario_alarm_parameters_t *)parameters;\n    static float prev = 0;\n    parameter->m10s = (*parameter->altitude - prev) * 100 + 30000;\n#ifdef SIM_SENSORS\n    vario_alarm_parameters.m10s = 56 * 100 + 30000;\n#endif\n    prev = *parameter->altitude;\n    return 10000000L;\n}\n"
  },
  {
    "path": "board/project/protocol/hott.h",
    "content": "#ifndef HOTT_H\n#define HOTT_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid hott_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/ibus.c",
    "content": "#include \"ibus.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"gps.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n/* Flysky IBUS Data Id */\n#define IBUS_ID_VOLTAGE 0x00       // Internal Voltage\n#define IBUS_ID_TEMPERATURE 0x01   // Temperature\n#define IBUS_ID_MOT 0x02           // RPM\n#define IBUS_ID_EXTV 0x03          // External Voltage\n#define IBUS_ID_CELL_VOLTAGE 0x04  // Avg Cell voltage\n#define IBUS_ID_BAT_CURR 0x05      // battery current A * 100\n#define IBUS_ID_FUEL 0x06          // remaining battery percentage / mah drawn otherwise or fuel level no unit!\n#define IBUS_ID_RPM 0x07           // throttle value / battery capacity\n#define IBUS_ID_CMP_HEAD 0x08      // Heading  0..360 deg 0north 2bytes\n#define IBUS_ID_CLIMB_RATE 0x09    // 2 bytes m/s *100 signed\n#define IBUS_ID_COG \\\n    0x0A  // 2 bytes  Course over ground(NOT heading but direction of movement) in degrees * 100 0.0..359.99 degrees.\n          // unknown max uint\n#define IBUS_ID_GPS_STATUS 0x0B      // 2 bytes\n#define IBUS_ID_ACC_X 0x0C           // 2 bytes m/s *100 signed\n#define IBUS_ID_ACC_Y 0x0D           // 2 bytes m/s *100 signed\n#define IBUS_ID_ACC_Z 0x0E           // 2 bytes m/s *100 signed\n#define IBUS_ID_ROLL 0x0F            // 2 bytes deg *100 signed\n#define IBUS_ID_PITCH 0x10           // 2 bytes deg *100 signed\n#define IBUS_ID_YAW 0x11             // 2 bytes deg *100 signed\n#define IBUS_ID_VERTICAL_SPEED 0x12  // 2 bytes m/s *100 signed\n#define IBUS_ID_GROUND_SPEED 0x13    // 2 bytes m/s *100 different unit than build-in sensor\n#define IBUS_ID_GPS_DIST 0x14        // 2 bytes distance from home m unsigned\n#define IBUS_ID_ARMED 0x15           // 2 bytes\n#define IBUS_ID_FLIGHT_MODE 0x16     // 2 bytes\n#define IBUS_ID_PRES 0x41            // Pressure\n#define IBUS_ID_ODO1 0x7C            // Odometer1\n#define IBUS_ID_ODO2 0x7D            // Odometer2\n#define IBUS_ID_SPE 0x7E             // Speed 2 bytes km/h * 100\n#define IBUS_ID_TX_V 0x7F            // TX Voltage\n#define IBUS_ID_GPS_LAT 0x80         // 4bytes signed WGS84 in degrees * 1E7\n#define IBUS_ID_GPS_LON 0x81         // 4bytes signed WGS84 in degrees * 1E7\n#define IBUS_ID_GPS_ALT 0x82         // 4bytes signed!!! GPS alt m*100\n#define IBUS_ID_ALT 0x83             // 4bytes signed!!! Alt m*100\n#define IBUS_ID_S84 0x84\n#define IBUS_ID_S85 0x85\n#define IBUS_ID_S86 0x86\n#define IBUS_ID_S87 0x87\n#define IBUS_ID_S88 0x88\n#define IBUS_ID_S89 0x89\n#define IBUS_ID_S8a 0x8A\n#define IBUS_ID_RX_SIG_AFHDS3 0xF7  // SIG\n#define IBUS_ID_RX_SNR_AFHDS3 0xF8  // SNR\n#define IBUS_ID_ALT_FLYSKY 0xF9     // Altitude 2 bytes signed in m - used in FlySky native TX\n#define IBUS_ID_RX_SNR 0xFA         // SNR\n#define IBUS_ID_RX_NOISE 0xFB       // Noise\n#define IBUS_ID_RX_RSSI 0xFC        // RSSI\n#define IBUS_ID_RX_ERR_RATE 0xFE    // Error rate\n#define IBUS_ID_END 0xFF\n// AC type telemetry with multiple values in one packet\n#define IBUS_ID_GPS_FULL 0xFD\n#define IBUS_ID_VOLT_FULL 0xF0\n#define IBUS_ID_ACC_FULL 0xEF\n#define IBUS_ID_TX_RSSI 0x200  // Pseudo id outside 1 byte range of FlySky sensors\n\n#define IBUS_TYPE_U16 0\n#define IBUS_TYPE_S16 1\n#define IBUS_TYPE_U32 2\n#define IBUS_TYPE_S32 3\n#define IBUS_TYPE_GPS 4\n\n#define IBUS_RECEIVED_NONE 0\n#define IBUS_RECEIVED_POLL 1\n\n#define IBUS_COMMAND_DISCOVER 0x8\n#define IBUS_COMMAND_TYPE 0x9\n#define IBUS_COMMAND_MEASURE 0xA\n\n#define IBUS_TIMEOUT_US 1000\n#define IBUS_PACKET_LENGHT 4\n\ntypedef struct sensor_ibus_t {\n    uint8_t data_id;\n    uint8_t type;\n    float *value;\n} sensor_ibus_t;\n\nstatic void process(sensor_ibus_t **sensor);\nstatic void send_packet(uint8_t command, uint8_t address, sensor_ibus_t *sensor_ibus);\nstatic void send_byte(uint8_t c, uint16_t *crc_p);\nstatic int32_t format(uint8_t data_id, float value);\nstatic bool check_crc(uint8_t *data);\nstatic void add_sensor(sensor_ibus_t *new_sensor, sensor_ibus_t **sensor, uint16_t sensormask);\nstatic void set_config(sensor_ibus_t **sensor, uint16_t sensormask);\n\nvoid ibus_task(void *parameters) {\n    sensor_ibus_t *sensor[16] = {NULL};\n    uint16_t sensor_mask = 0B1111111111111110;\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(115200, UART_RECEIVER_TX, UART_RECEIVER_RX, IBUS_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    set_config(sensor, sensor_mask);\n    debug(\"\\nIbus init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(sensor);\n    }\n}\n\nstatic void process(sensor_ibus_t **sensor) {\n    if (uart0_available() == IBUS_PACKET_LENGHT) {\n        uint8_t command = 0;\n        uint8_t address = 0;\n        uint8_t data[IBUS_PACKET_LENGHT];\n        uart0_read_bytes(data, IBUS_PACKET_LENGHT);\n        if (data[0] == IBUS_PACKET_LENGHT) {\n            debug(\"\\nIbus (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(data, data[0], \"0x%X \");\n            if (check_crc(data)) {\n                command = data[1] >> 4;\n                address = data[1] & 0x0F;\n                if (!sensor[address]) return;\n                if (command == IBUS_COMMAND_DISCOVER || command == IBUS_COMMAND_TYPE || IBUS_COMMAND_MEASURE)\n                    send_packet(command, address, sensor[address]);\n            } else\n                debug(\" - Bad CRC\");\n        }\n    }\n}\n\nstatic void send_packet(uint8_t command, uint8_t address, sensor_ibus_t *sensor) {\n    uint8_t *u8_p = NULL;\n    uint16_t crc = 0;\n    uint16_t type;\n    uint8_t lenght = 0;\n    int32_t value_formatted = 0;\n\n    switch (sensor->type) {\n        case IBUS_TYPE_S16:\n        case IBUS_TYPE_U16:\n            lenght = 2;\n            break;\n        case IBUS_TYPE_S32:\n            lenght = 4;\n            break;\n        case IBUS_TYPE_GPS:\n            lenght = 14;\n            break;\n    }\n    switch (command) {\n        case IBUS_COMMAND_DISCOVER:\n            lenght = 0;\n            break;\n        case IBUS_COMMAND_TYPE:\n            type = lenght << 8 | sensor->data_id;\n            u8_p = (uint8_t *)&type;\n            lenght = 2;\n            break;\n        case IBUS_COMMAND_MEASURE:\n            if (sensor->value) {\n                value_formatted = format(sensor->data_id, *sensor->value);\n            }\n            u8_p = (uint8_t *)&value_formatted;\n            break;\n    }\n    debug(\"\\nIbus (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n    // lenght\n    send_byte(4 + lenght, &crc);\n\n    // command & address\n    send_byte(command << 4 | address, &crc);\n\n    // value\n    for (uint8_t i = 0; i < lenght; i++) {\n        send_byte(u8_p[i], &crc);\n    }\n\n    // crc\n    crc = 0xFFFF - crc;\n    u8_p = (uint8_t *)&crc;\n    send_byte(u8_p[0], NULL);\n    send_byte(u8_p[1], NULL);\n\n    // blink led\n    vTaskResume(context.led_task_handle);\n}\n\nstatic void send_byte(uint8_t c, uint16_t *crc_p) {\n    if (crc_p != NULL) {\n        uint16_t crc = *crc_p;\n        crc += c;\n        *crc_p = crc;\n    }\n    uart0_write(c);\n    debug(\"%X \", c);\n}\n\nstatic bool check_crc(uint8_t *data) {\n    uint16_t crc = 0xFFFF;\n    uint8_t lenght = data[0];\n    for (uint8_t i = 0; i < lenght - 2; i++) crc -= data[i];\n    if (crc == (uint16_t)data[lenght - 2] << 8 || data[lenght - 1]) return true;\n    return false;\n}\n\nstatic void add_sensor(sensor_ibus_t *new_sensor, sensor_ibus_t **sensor, uint16_t sensor_mask) {\n    for (uint8_t i = 0; i < 16; i++) {\n        if (sensor[i] == NULL && sensor_mask & 1 << i) {\n            sensor[i] = new_sensor;\n            return;\n        }\n    }\n}\n\nstatic int32_t format(uint8_t data_id, float value) {\n    if (data_id == IBUS_ID_TEMPERATURE) return round((value + 40) * 10);\n\n    if (data_id == IBUS_ID_EXTV || data_id == IBUS_ID_CELL_VOLTAGE || data_id == IBUS_ID_BAT_CURR ||\n        data_id == IBUS_ID_CLIMB_RATE || data_id == IBUS_ID_COG || data_id == IBUS_ID_VERTICAL_SPEED ||\n        data_id == IBUS_ID_GROUND_SPEED || data_id == IBUS_ID_GPS_ALT || data_id == IBUS_ID_PRES ||\n        data_id == IBUS_ID_ALT || data_id == IBUS_ID_ACC_X || data_id == IBUS_ID_ACC_Y || data_id == IBUS_ID_ACC_Z ||\n        data_id == IBUS_ID_ROLL || data_id == IBUS_ID_PITCH || data_id == IBUS_ID_YAW)\n        return round(value * 100);\n\n    if (data_id == IBUS_ID_GPS_LAT || data_id == IBUS_ID_GPS_LON) return round(value * 1e7);\n\n    if (data_id == IBUS_ID_SPE) return round(value * 100 * 1.852);\n\n    if (data_id == IBUS_ID_GPS_STATUS) return value * 256;\n\n    if (data_id == IBUS_ID_S84 || data_id == IBUS_ID_S85) return value / 60 * 1e5;\n\n    return round(value);\n}\n\nstatic void set_config(sensor_ibus_t **sensor, uint16_t sensormask) {\n    /*\n    - Sensor at address 0x00 is reserved\n    - Sensor at address 0x01 is recerved in some receivers types. But the poll at address 0x01, if present, has to be\n    answered (so there is a dummy sensor at address 0x01), otherwise the sensor poll scan is stopped from receiver\n    - TODO: dinamically set the sensor address to allocate an additional sensor in receivers with only one sensor masked\n   */\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    sensor_ibus_t *new_sensor;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    new_sensor = malloc(sizeof(sensor_ibus_t));\n    *new_sensor = (sensor_ibus_t){IBUS_ID_END, 0, NULL};\n    add_sensor(new_sensor, sensor, sensormask);\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_fet};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_fet};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.ripple_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_fet};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_fet};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temp_esc};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temp_motor};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temp_esc};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temp_motor};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_MOT, IBUS_TYPE_U16, parameter.rpm};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_fet};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature_bec};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CELL_VOLTAGE, IBUS_TYPE_U16, parameter.cell_voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_GPS_STATUS, IBUS_TYPE_U16, parameter.sat};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_GPS_LAT, IBUS_TYPE_S32, parameter.lat};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_GPS_LON, IBUS_TYPE_S32, parameter.lon};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_GPS_ALT, IBUS_TYPE_S32, parameter.alt};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_SPE, IBUS_TYPE_U16, parameter.spd};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_COG, IBUS_TYPE_U16, parameter.cog};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CLIMB_RATE, IBUS_TYPE_S16, parameter.vspeed};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_GPS_DIST, IBUS_TYPE_U16, parameter.dist};\n        add_sensor(new_sensor, sensor, sensormask);\n        if (config->ibus_alternative_coordinates) {\n            new_sensor = malloc(sizeof(sensor_ibus_t));\n            *new_sensor = (sensor_ibus_t){IBUS_ID_S84, IBUS_TYPE_S32, parameter.lat};\n            add_sensor(new_sensor, sensor, sensormask);\n            new_sensor = malloc(sizeof(sensor_ibus_t));\n            *new_sensor = (sensor_ibus_t){IBUS_ID_S85, IBUS_TYPE_S32, parameter.lon};\n            add_sensor(new_sensor, sensor, sensormask);\n        }\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_EXTV, IBUS_TYPE_U16, parameter.voltage};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_BAT_CURR, IBUS_TYPE_U16, parameter.current};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_FUEL, IBUS_TYPE_U16, parameter.consumption};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.ntc};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_ALT, IBUS_TYPE_S32, parameter.altitude};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CLIMB_RATE, IBUS_TYPE_S16, parameter.vspeed};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_ALT, IBUS_TYPE_S32, parameter.altitude};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CLIMB_RATE, IBUS_TYPE_S16, parameter.vspeed};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_TEMPERATURE, IBUS_TYPE_U16, parameter.temperature};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_ALT, IBUS_TYPE_S32, parameter.altitude};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_CLIMB_RATE, IBUS_TYPE_S16, parameter.vspeed};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_SPE, IBUS_TYPE_U16, parameter.airspeed};\n        add_sensor(new_sensor, sensor, sensormask);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_ACC_X, IBUS_TYPE_S16, parameter.acc_x};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_ACC_Y, IBUS_TYPE_S16, parameter.acc_y};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_ACC_Z, IBUS_TYPE_S16, parameter.acc_z};\n        add_sensor(new_sensor, sensor, sensormask);\n\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_PITCH, IBUS_TYPE_S16, parameter.pitch};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_ROLL, IBUS_TYPE_S16, parameter.roll};\n        add_sensor(new_sensor, sensor, sensormask);\n        new_sensor = malloc(sizeof(sensor_ibus_t));\n        *new_sensor = (sensor_ibus_t){IBUS_ID_YAW, IBUS_TYPE_S16, parameter.yaw};\n        add_sensor(new_sensor, sensor, sensormask);\n    }\n}\n"
  },
  {
    "path": "board/project/protocol/ibus.h",
    "content": "#ifndef IBUS_H\n#define IBUS_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid ibus_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/jetiex.c",
    "content": "#include \"jetiex.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gps.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"stdlib.h\"\n#include \"string.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n#include \"xgzp68xxd.h\"\n\n#define JETIEX_WAIT 0\n#define JETIEX_SEND 1\n\n#define JETIEX_PACKET_LENGHT 8\n#define JETIEX_TIMEOUT_US 500\n#define JETIEX_BAUDRATE_TIMEOUT_MS 5000\n\nstatic void process(uint *baudrate, sensor_jetiex_t **sensor);\nstatic void send_packet(uint8_t packet_id, sensor_jetiex_t **sensor);\nstatic void add_sensor_text(uint8_t *buffer, uint8_t *buffer_index, uint8_t sensor_index, sensor_jetiex_t *sensor);\nstatic void add_sensor_value(uint8_t *buffer, uint8_t *buffer_index, uint8_t sensor_index, sensor_jetiex_t *sensor);\nstatic int64_t timeout_callback(alarm_id_t id, void *parameters);\nstatic uint8_t crc8(uint8_t *crc, uint8_t crc_length);\nstatic uint8_t update_crc8(uint8_t crc, uint8_t crc_seed);\nstatic uint16_t crc16(uint8_t *p, uint16_t len);\nstatic uint16_t update_crc16(uint16_t crc, uint8_t data);\n\nvoid jetiex_task(void *parameters) {\n    uint baudrate = 125000L;\n    sensor_jetiex_t *sensor[16] = {NULL};\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(baudrate, UART_RECEIVER_TX, UART_RECEIVER_RX, JETIEX_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    jeti_set_config(sensor);\n    debug(\"\\nJeti Ex init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&baudrate, sensor);\n    }\n}\n\nuint8_t jeti_create_telemetry_buffer(uint8_t *buffer, bool packet_type, sensor_jetiex_t **sensor) {\n    static uint8_t sensor_index_value = 0;\n    static int8_t sensor_index_text = -1;\n    uint8_t buffer_index = 7;\n    if (sensor[0] == NULL) return 0;\n    if (packet_type) {\n        buffer[1] = 0x40;\n\n        add_sensor_value(buffer, &buffer_index, sensor_index_value + 1, sensor[sensor_index_value]);\n        sensor_index_value++;\n        if (sensor_index_value >= 16 || sensor[sensor_index_value] == NULL)\n            sensor_index_value = 0;\n        else {\n            add_sensor_value(buffer, &buffer_index, sensor_index_value + 1, sensor[sensor_index_value]);\n            sensor_index_value++;\n            if (sensor_index_value >= 16 || sensor[sensor_index_value] == NULL) sensor_index_value = 0;\n        }\n    } else {\n        add_sensor_text(buffer, &buffer_index, sensor_index_text + 1,\n                        sensor_index_text == -1 ? NULL : sensor[sensor_index_text]);\n        sensor_index_text++;\n        if (sensor_index_text >= 16 || sensor[sensor_index_text] == NULL) sensor_index_text = -1;\n    }\n    buffer[0] = 0x0F;\n    buffer[1] |= buffer_index - 1;\n    buffer[2] = JETIEX_MFG_ID_LOW;\n    buffer[3] = JETIEX_MFG_ID_HIGH;\n    buffer[4] = JETIEX_DEV_ID_LOW;\n    buffer[5] = JETIEX_DEV_ID_HIGH;\n    buffer[6] = 0x00;\n    buffer[buffer_index] = crc8(buffer + 1, buffer_index - 1);\n    return buffer_index + 1;\n}\n\nvoid jeti_add_sensor(sensor_jetiex_t *new_sensor, sensor_jetiex_t **sensors) {\n    static uint8_t sensor_count = 0;\n    if (sensor_count < 15) {\n        sensors[sensor_count] = new_sensor;\n        new_sensor->data_id = sensor_count;\n        sensor_count++;\n    }\n}\n\nvoid jeti_set_config(sensor_jetiex_t **sensor) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    sensor_jetiex_t *new_sensor;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp FET\",\n                                        \"C\", parameter.temperature_fet};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp BEC\",\n                                        \"C\", parameter.temperature_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp FET\",\n                                        \"C\", parameter.temperature_fet};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp BEC\",\n                                        \"C\", parameter.temperature_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,          JETIEX_FORMAT_0_DECIMAL, \"Temp Motor\",\n                                        \"C\", parameter.temperature_motor};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Voltage BEC\", \"C\", parameter.voltage_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current BEC\", \"C\", parameter.current_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temperature\", \"C\", parameter.temperature};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,       JETIEX_FORMAT_2_DECIMAL, \"Ripple Voltage BEC\",\n                                        \"V\", parameter.ripple_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"BEC Voltage\", \"V\", parameter.voltage_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"BEC Current\", \"A\", parameter.current_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Current BEC\", \"A\", parameter.current_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage BEC\", \"V\", parameter.voltage_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp FET\",\n                                        \"C\", parameter.temperature_fet};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp BEC\",\n                                        \"C\", parameter.temperature_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temp\", \"C\", parameter.temperature};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temp\", \"C\", parameter.temperature};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temp ESC\", \"C\", parameter.temp_esc};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temp Motor\", \"C\", parameter.temp_motor};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage BEC\", \"V\", parameter.bec_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temp ESC\", \"C\", parameter.temp_esc};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temp Motor\", \"C\", parameter.temp_motor};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,     JETIEX_FORMAT_2_DECIMAL, \"Cell Voltage\",\n                                        \"V\", parameter.cell_voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"RPM\", \"RPM\", parameter.rpm};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current BEC\", \"A\", parameter.current_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage BEC\", \"V\", parameter.voltage_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp FET\",\n                                        \"C\", parameter.temperature_fet};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,        JETIEX_FORMAT_0_DECIMAL, \"Temp BEC\",\n                                        \"C\", parameter.temperature_bec};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT6, JETIEX_FORMAT_0_DECIMAL, \"Sats\", \"\", parameter.sat};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_COORDINATES, JETIEX_FORMAT_LAT, \"Latitude\", \"\", parameter.lat};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_COORDINATES, JETIEX_FORMAT_LON, \"Longitude\", \"\", parameter.lon};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_1_DECIMAL, \"Altitude\", \"m\", parameter.alt};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        if (config->jeti_gps_speed_units_kmh)\n            *new_sensor =\n                (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Speed\", \"km/h\", parameter.spd_kmh};\n        else\n            *new_sensor =\n                (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Speed\", \"kts\", parameter.spd};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"COG\", \"\", parameter.cog};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Vspeed\", \"m/s\", parameter.vspeed};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"Dist\", \"m\", parameter.dist};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_TIMEDATE, JETIEX_FORMAT_TIME, \"Time\", \"\", parameter.time};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_TIMEDATE, JETIEX_FORMAT_DATE, \"Date\", \"\", parameter.date};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"HDOP\", \"\", parameter.hdop};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"PDOP\", \"\", parameter.pdop};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_2_DECIMAL, \"Voltage\", \"V\", parameter.voltage};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Current\", \"A\", parameter.current};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,     JETIEX_TYPE_INT22,    JETIEX_FORMAT_0_DECIMAL, \"Consumption\",\n                                        \"mAh\", parameter.consumption};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Temperature\", \"C\", parameter.ntc};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,    JETIEX_FORMAT_0_DECIMAL, \"Air temperature\",\n                                        \"C\", parameter.temperature};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Altitude\", \"m\", parameter.altitude};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_2_DECIMAL, \"Vspeed\", \"m/s\", parameter.vspeed};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,    JETIEX_FORMAT_0_DECIMAL, \"Air temperature\",\n                                        \"C\", parameter.temperature};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Altitude\", \"m\", parameter.altitude};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_2_DECIMAL, \"Vspeed\", \"m/s\", parameter.vspeed};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,   JETIEX_TYPE_INT14,    JETIEX_FORMAT_0_DECIMAL, \"Air temperature\",\n                                        \"C\", parameter.temperature};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Altitude\", \"m\", parameter.altitude};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_2_DECIMAL, \"Vspeed\", \"m/s\", parameter.vspeed};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_1_DECIMAL, \"Air speed\", \"km/h\", parameter.airspeed};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_fuel_flow) {\n        fuel_meter_parameters_t parameter = {config->fuel_flow_ml_per_pulse, malloc(sizeof(float)),\n                                             malloc(sizeof(float))};\n        xTaskCreate(fuel_meter_task, \"fuel_meter_task\", STACK_FUEL_METER, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,\n                                        JETIEX_TYPE_INT14,\n                                        JETIEX_FORMAT_2_DECIMAL,\n                                        \"Instant consumption\",\n                                        \"ml/min\",\n                                        parameter.consumption_instant};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0,    JETIEX_TYPE_INT14,          JETIEX_FORMAT_1_DECIMAL, \"Total consumption\",\n                                        \"ml\", parameter.consumption_total};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_fuel_pressure) {\n        xgzp68xxd_parameters_t parameter = {config->xgzp68xxd_k, malloc(sizeof(float)), malloc(sizeof(float))};\n\n        xTaskCreate(xgzp68xxd_task, \"fuel_pressure_task\", STACK_FUEL_PRESSURE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor =\n            (sensor_jetiex_t){0, JETIEX_TYPE_INT22, JETIEX_FORMAT_0_DECIMAL, \"Tank pressure\", \"Pa\", parameter.pressure};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Pitch\", \"dps\", parameter.pitch};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Roll\", \"dps\", parameter.roll};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Yaw\", \"dps\", parameter.yaw};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Acc X\", \"g\", parameter.acc_x};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Acc Y\", \"g\", parameter.acc_y};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Acc Z\", \"g\", parameter.acc_z};\n        jeti_add_sensor(new_sensor, sensor);\n        new_sensor = malloc(sizeof(sensor_jetiex_t));\n        *new_sensor = (sensor_jetiex_t){0, JETIEX_TYPE_INT14, JETIEX_FORMAT_0_DECIMAL, \"Acc\", \"g\", parameter.acc};\n        jeti_add_sensor(new_sensor, sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_lipo) {\n        float *cell_prev = 0;\n        if (config->lipo_cells > 0) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x40,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            *parameter.cell_prev = 0;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            for (uint8_t i = 0; i < MIN(parameter.cell_count, 3); i++) {\n                new_sensor = malloc(sizeof(sensor_jetiex_t));\n                new_sensor->data_id = 0;\n                new_sensor->type = JETIEX_TYPE_INT14;\n                new_sensor->format = JETIEX_FORMAT_2_DECIMAL;\n                new_sensor->value = parameter.cell[i];\n                jeti_add_sensor(new_sensor, sensor);\n            }\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        }\n        if (config->lipo_cells > 3) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x41,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells - 3, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            parameter.cell_prev = cell_prev;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            for (uint8_t i = 0; i < parameter.cell_count; i++) {\n                new_sensor = malloc(sizeof(sensor_jetiex_t));\n                new_sensor->data_id = 0;\n                new_sensor->type = JETIEX_TYPE_INT14;\n                new_sensor->format = JETIEX_FORMAT_2_DECIMAL;\n                new_sensor->value = parameter.cell[i];\n                jeti_add_sensor(new_sensor, sensor);\n            }\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n    }\n}\n\nstatic void process(uint *baudrate, sensor_jetiex_t **sensor) {\n    static alarm_id_t timeout_alarm_id = 0;\n    uint8_t packetId;\n    uint8_t length = uart0_available();\n    if (length) {\n        uint8_t data[length];\n        uart0_read_bytes(data, length);\n        if (context.debug == 2) {\n            printf(\"\\nJeti Ex(%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            for (uint8_t i = 0; i < length; i++) {\n                printf(\"%X \", data[i]);\n            }\n        }\n        uint8_t packet[JETIEX_PACKET_LENGHT];\n        if (data[0] == 0x3E && data[1] == 0x3 && length - data[2] == JETIEX_PACKET_LENGHT) {\n            memcpy(packet, data + data[2], JETIEX_PACKET_LENGHT);\n            debug(\"\\nJeti Ex(%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(packet, JETIEX_PACKET_LENGHT, \"0x%X \");\n        } else if (length == JETIEX_PACKET_LENGHT) {\n            memcpy(packet, data, JETIEX_PACKET_LENGHT);\n        } else {\n            return;\n        }\n        if (crc16(packet, JETIEX_PACKET_LENGHT) == 0) {\n            if (packet[0] == 0x3D && packet[1] == 0x01 && packet[4] == 0x3A) {\n                if (timeout_alarm_id) cancel_alarm(timeout_alarm_id);\n                uint8_t packet_id = packet[3];\n                send_packet(packet_id, sensor);\n                timeout_alarm_id = add_alarm_in_ms(JETIEX_BAUDRATE_TIMEOUT_MS, timeout_callback, &baudrate, false);\n            }\n        }\n    }\n}\n\nstatic void send_packet(uint8_t packet_id, sensor_jetiex_t **sensor) {\n    static uint8_t packet_count = 0;\n    uint8_t ex_buffer[36] = {0};\n    uint8_t length_telemetry_buffer = jeti_create_telemetry_buffer(ex_buffer + 6, packet_count % 16, sensor);\n    ex_buffer[0] = 0x3B;\n    ex_buffer[1] = 0x01;\n    ex_buffer[2] = length_telemetry_buffer + 8;\n    ex_buffer[3] = packet_id;\n    ex_buffer[4] = 0x3A;\n    ex_buffer[5] = length_telemetry_buffer;\n    uint16_t crc = crc16(ex_buffer, length_telemetry_buffer + 6);\n    ex_buffer[length_telemetry_buffer + 6] = crc;\n    ex_buffer[length_telemetry_buffer + 7] = crc >> 8;\n    uart0_write_bytes(ex_buffer, length_telemetry_buffer + 8);\n    debug(\"\\nJeti Ex %s (%u) > \", packet_count % 16 ? \"Values \" : \"Text \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(ex_buffer, length_telemetry_buffer + 8, \"0x%X \");\n    packet_count++;\n\n    // blink led\n    vTaskResume(context.led_task_handle);\n}\n\nstatic void add_sensor_value(uint8_t *buffer, uint8_t *buffer_index, uint8_t sensor_index, sensor_jetiex_t *sensor) {\n    if (sensor) {\n        uint8_t format = sensor->format << 5;\n        if (sensor->type == JETIEX_TYPE_INT6) {\n            int8_t value = *sensor->value * pow(10, sensor->format);\n            if (value > 0x1F)\n                value = 0x1F;\n            else if (value < -0x1F)\n                value = -0x1F;\n            value &= ~(3 << 5);\n            value |= format;\n            *(buffer + *buffer_index) = sensor_index << 4 | sensor->type;\n            *(buffer + *buffer_index + 1) = value;\n            *buffer_index += 2;\n        } else if (sensor->type == JETIEX_TYPE_INT14) {\n            int16_t value = *sensor->value * pow(10, sensor->format);\n            if (value > 0x1FFF) value = 0x1FFF;\n            if (value < -0x1FFF) value = -0x1FFF;\n            value &= ~((uint16_t)3 << (5 + 8));\n            value |= (uint16_t)format << 8;\n            *(buffer + *buffer_index) = sensor_index << 4 | sensor->type;\n            *(buffer + *buffer_index + 1) = value;\n            *(buffer + *buffer_index + 2) = value >> 8;\n            *buffer_index += 3;\n        } else if (sensor->type == JETIEX_TYPE_INT22) {\n            int32_t value = *sensor->value * pow(10, sensor->format);\n            if (value > 0x1FFFFF)\n                value = 0x1FFFFF;\n            else if (value < -0x1FFFFF)\n                value = -0x1FFFFF;\n            value &= ~((uint32_t)3 << (5 + 16));\n            value |= (uint32_t)format << 16;\n            *(buffer + *buffer_index) = sensor_index << 4 | sensor->type;\n            *(buffer + *buffer_index + 1) = value;\n            *(buffer + *buffer_index + 2) = value >> 8;\n            *(buffer + *buffer_index + 3) = value >> 16;\n            *buffer_index += 4;\n        } else if (sensor->type == JETIEX_TYPE_INT30) {\n            int32_t value = *sensor->value * pow(10, sensor->format);\n            if (value > 0x1FFFFFFF)\n                value = 0x1FFFFFFF;\n            else if (value < -0x1FFFFFFF)\n                value = -0x1FFFFFFF;\n            value &= ~((uint32_t)3 << (5 + 24));\n            value |= (uint32_t)format << 24;\n            *(buffer + *buffer_index) = sensor_index << 4 | sensor->type;\n            *(buffer + *buffer_index + 1) = value;\n            *(buffer + *buffer_index + 2) = value >> 8;\n            *(buffer + *buffer_index + 3) = value >> 16;\n            *(buffer + *buffer_index + 4) = value >> 24;\n            *buffer_index += 5;\n        } else if (sensor->type == JETIEX_TYPE_TIMEDATE) {\n            // rawvalue: yymmdd/hhmmss\n            // byte 1: day/second\n            // byte 2: month/minute\n            // byte 3(bits 1-5): year/hour\n            // byte 3(bit 6): 0=time 1=date\n            uint32_t value = *sensor->value;\n            uint8_t hourYearFormat = format;\n            hourYearFormat |= value / 10000;                              // hour, year\n            uint8_t minuteMonth = (value / 100 - (value / 10000) * 100);  // minute, month\n            uint8_t secondDay = value - (value / 100) * 100;              // second, day\n            *(buffer + *buffer_index) = sensor_index << 4 | sensor->type;\n            *(buffer + *buffer_index + 1) = secondDay;\n            *(buffer + *buffer_index + 2) = minuteMonth;\n            *(buffer + *buffer_index + 3) = hourYearFormat;\n            *buffer_index += 4;\n        } else if (sensor->type == JETIEX_TYPE_COORDINATES) {\n            // rawvalue: minutes\n            // byte 1-2: MMmmm\n            // byte 3: DD\n            // byte 4(bit 6): 0=lat 1=lon\n            // byte 4(bit 7): 0=+(N,E), 1=-(S,W)\n            float value = *sensor->value;\n            if (value < 0) {\n                format |= 1 << 6;\n                value *= -1;\n            }\n            *(buffer + *buffer_index) = sensor_index << 4 | sensor->type;\n            uint8_t degrees = value;\n            uint16_t minutes = (value - degrees) * 60 * 1000;\n            *(buffer + *buffer_index + 1) = minutes;\n            *(buffer + *buffer_index + 2) = minutes >> 8;\n            *(buffer + *buffer_index + 3) = degrees;\n            *(buffer + *buffer_index + 4) = format;\n            *buffer_index += 5;\n        }\n    }\n}\n\nstatic void add_sensor_text(uint8_t *buffer, uint8_t *buffer_index, uint8_t sensor_index, sensor_jetiex_t *sensor) {\n    uint8_t lenText, lenUnit;\n    if (sensor) {\n        lenText = strlen(sensor->text);\n        lenUnit = strlen(sensor->unit);\n    } else {\n        lenText = strlen(\"MSRC\");\n        lenUnit = strlen(\"\");\n    }\n    *(buffer + *buffer_index) = sensor_index;\n    *(buffer + *buffer_index + 1) = lenText << 3 | lenUnit;\n    *buffer_index += 2;\n    strcpy((char *)buffer + *buffer_index, sensor ? sensor->text : \"MSRC\");\n    *buffer_index += lenText;\n    strcpy((char *)buffer + *buffer_index, sensor ? sensor->unit : \"\");\n    *buffer_index += lenUnit;\n}\n\nstatic int64_t timeout_callback(alarm_id_t id, void *parameters) {\n    float *baudrate = (float *)parameters;\n    if (*baudrate == 125000L)\n        *baudrate = 250000L;\n    else\n        *baudrate = 125000L;\n    uart0_begin(*baudrate, UART_RECEIVER_TX, UART_RECEIVER_RX, JETIEX_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    debug(\"\\nJeti Ex timeout. Baudrate\");\n    return 0;\n}\n\nstatic uint8_t crc8(uint8_t *crc, uint8_t crc_length) {\n    uint8_t crc_up = 0;\n    uint8_t c;\n    for (c = 0; c < crc_length; c++) {\n        crc_up = update_crc8(crc[c], crc_up);\n    }\n    return crc_up;\n}\n\nstatic uint8_t update_crc8(uint8_t crc, uint8_t crc_seed) {\n    uint8_t crc_u;\n    uint8_t i;\n    crc_u = crc;\n    crc_u ^= crc_seed;\n    for (i = 0; i < 8; i++) {\n        crc_u = (crc_u & 0x80) ? 0x07 ^ (crc_u << 1) : (crc_u << 1);\n    }\n    return crc_u;\n}\n\nstatic uint16_t crc16(uint8_t *p, uint16_t len) {\n    uint16_t crc16_data = 0;\n    while (len--) {\n        crc16_data = update_crc16(crc16_data, p[0]);\n        p++;\n    }\n    return (crc16_data);\n}\n\nstatic uint16_t update_crc16(uint16_t crc, uint8_t data) {\n    uint16_t ret_val;\n    data ^= (uint8_t)(crc) & (uint8_t)(0xFF);\n    data ^= data << 4;\n    ret_val = ((((uint16_t)data << 8) | ((crc & 0xFF00) >> 8)) ^ (uint8_t)(data >> 4) ^ ((uint16_t)data << 3));\n    return ret_val;\n}\n"
  },
  {
    "path": "board/project/protocol/jetiex.h",
    "content": "#ifndef JETIEX_H\n#define JETIEX_H\n\n#include \"common.h\"\n\n#define JETIEX_TYPE_INT6 0\n#define JETIEX_TYPE_INT14 1\n#define JETIEX_TYPE_INT22 4\n#define JETIEX_TYPE_TIMEDATE 5\n#define JETIEX_TYPE_INT30 8\n#define JETIEX_TYPE_COORDINATES 9\n\n#define JETIEX_FORMAT_0_DECIMAL 0\n#define JETIEX_FORMAT_1_DECIMAL 1\n#define JETIEX_FORMAT_2_DECIMAL 2\n#define JETIEX_FORMAT_3_DECIMAL 3\n#define JETIEX_FORMAT_DATE 1\n#define JETIEX_FORMAT_LON 1\n#define JETIEX_FORMAT_TIME 0\n#define JETIEX_FORMAT_LAT 0\n\n#define JETIEX_MFG_ID_LOW 0x00\n#define JETIEX_MFG_ID_HIGH 0xA4\n#define JETIEX_DEV_ID_LOW 0x00\n#define JETIEX_DEV_ID_HIGH 0xA4\n\ntypedef struct sensor_jetiex_t {\n    uint8_t data_id;\n    uint8_t type;\n    uint8_t format;\n    char text[32];\n    char unit[8];\n    float *value;\n} sensor_jetiex_t;\n\nextern context_t context;\n\nvoid jetiex_task(void *parameters);\nvoid jeti_add_sensor(sensor_jetiex_t *new_sensor, sensor_jetiex_t **sensors);\nuint8_t jeti_create_telemetry_buffer(uint8_t *buffer, bool packet_type, sensor_jetiex_t **sensor);\nvoid jeti_set_config(sensor_jetiex_t **sensor);\n\n#endif"
  },
  {
    "path": "board/project/protocol/jetiex_sensor.c",
    "content": "#include \"jetiex_sensor.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gps.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"stdlib.h\"\n#include \"string.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n#include \"xgzp68xxd.h\"\n\n#define JETIEX_SENSOR_INTERVAL_MS 150\n\nstatic int64_t alarm_packet(alarm_id_t id, void *user_data);\nstatic void process(uint *baudrate, sensor_jetiex_t **sensor);\nstatic void send_packet(sensor_jetiex_t **sensor);\nstatic int64_t timeout_callback(alarm_id_t id, void *parameters);\n\nvoid jetiex_sensor_task(void *parameters) {\n    uint baudrate = 9600;\n    sensor_jetiex_t *sensor[16] = {NULL};\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart_pio_begin(baudrate, UART_RECEIVER_TX, UART_GPIO_NONE, 0, pio1, PIO1_IRQ_0, 9, 2, UART_PARITY_ODD);\n    jeti_set_config(sensor);\n    add_alarm_in_us(0, alarm_packet, NULL, true);\n    debug(\"\\nJeti Ex Sensor init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        send_packet(sensor);\n    }\n}\n\nstatic void send_packet(sensor_jetiex_t **sensor) {\n    static uint8_t packet_count = 0;\n\n    // send telemetry frame\n    uint8_t buffer[36] = {0};\n    uint length_telemetry_buffer = jeti_create_telemetry_buffer(buffer, packet_count % 16, sensor);\n    uart_pio_write(0x7E);\n    for (uint8_t i = 0; i < length_telemetry_buffer; i++) uart_pio_write((uint32_t)buffer[i] | 0x100);\n\n    // send simple text frame\n    uart_pio_write(0xFE);\n    for (uint8_t i = 0; i < 32; i++) uart_pio_write(0x100);\n    uart_pio_write(0xFF);\n\n    debug(\"\\nJeti Ex Sensor %s (%u) > \", packet_count % 16 ? \"Values \" : \"Text \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(buffer, length_telemetry_buffer, \"0x%X \");\n    packet_count++;\n\n    // blink led\n    vTaskResume(context.led_task_handle);\n}\n\nstatic int64_t alarm_packet(alarm_id_t id, void *user_data) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    vTaskNotifyGiveIndexedFromISR(context.receiver_task_handle, 1, &xHigherPriorityTaskWoken);\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    return JETIEX_SENSOR_INTERVAL_MS * 1000;\n}\n"
  },
  {
    "path": "board/project/protocol/jetiex_sensor.h",
    "content": "#ifndef JETIEX_SENSOR_H\n#define JETIEX_SENSOR_H\n\n#include \"common.h\"\n#include \"jetiex.h\"\n\nextern context_t context;\n\nvoid jetiex_sensor_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/jr_dmss.c",
    "content": "#include \"jr_dmss.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pico/stdlib.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"stdlib.h\"\n#include \"uart.h\"\n#include \"voltage.h\"\n\n#define JR_DMSS_TEMPERATURE_SENSOR_ID 1\n#define JR_DMSS_RPM_SENSOR_ID 2\n#define JR_DMSS_VARIO_SENSOR_ID 3\n#define JR_DMSS_AIRSPEED_SENSOR_ID 5\n#define JR_DMSS_BATTERY_SENSOR_ID 8\n\n#define JR_DMSS_TEMPERATURE_TEMPERATURE_INDEX 0x0  // 1ºC\n#define JR_DMSS_RPM_RPM_INDEX 0x0                  // 1 rpm\n#define JR_DMSS_VARIO_ALTITUDE_INDEX 0x3           // 0.1m\n#define JR_DMSS_VARIO_VSPEED_INDEX 0x2             // 0.1 m/s\n#define JR_DMSS_VARIO_PRESSURE_INDEX 0x1           // 0.1 hPa\n#define JR_DMSS_AIRSPEED_AIRSPEED_INDEX 0x0        // 1 km/h\n#define JR_DMSS_BATTERY_VOLTAGE_INDEX 0x0          // 0.01 V\n#define JR_DMSS_BATTERY_CURRENT_INDEX 0x80         // 0.01 A\n#define JR_DMSS_BATTERY_CAPACITY_INDEX 0x8         // 1 mAh\n#define JR_DMSS_BATTERY_POWER_INDEX 0x90           // 0.1 W\n\n#define JR_DMSS_TIMEOUT_US 300\n#define JR_DMSS_PACKET_LENGHT 1\n\n#define PIN_BASE 12\n\ntypedef enum sensor_t {\n    TEMPERATURE,\n    RPM,\n    ALTITUDE,\n    VSPEED,\n    PRESSURE,\n    AIRSPEED,\n    VOLTAGE,\n    CURRENT,\n    CAPACITY,\n    POWER\n} sensor_t;\n\nstatic uint8_t s_crc_array[256] = {\n    0x00, 0x5e, 0xbc, 0xe2, 0x61, 0x3f, 0xdd, 0x83, 0xc2, 0x9c, 0x7e, 0x20, 0xa3, 0xfd, 0x1f, 0x41, 0x9d, 0xc3, 0x21,\n    0x7f, 0xfc, 0xa2, 0x40, 0x1e, 0x5f, 0x01, 0xe3, 0xbd, 0x3e, 0x60, 0x82, 0xdc, 0x23, 0x7d, 0x9f, 0xc1, 0x42, 0x1c,\n    0xfe, 0xa0, 0xe1, 0xbf, 0x5d, 0x03, 0x80, 0xde, 0x3c, 0x62, 0xbe, 0xe0, 0x02, 0x5c, 0xdf, 0x81, 0x63, 0x3d, 0x7c,\n    0x22, 0xc0, 0x9e, 0x1d, 0x43, 0xa1, 0xff, 0x46, 0x18, 0xfa, 0xa4, 0x27, 0x79, 0x9b, 0xc5, 0x84, 0xda, 0x38, 0x66,\n    0xe5, 0xbb, 0x59, 0x07, 0xdb, 0x85, 0x67, 0x39, 0xba, 0xe4, 0x06, 0x58, 0x19, 0x47, 0xa5, 0xfb, 0x78, 0x26, 0xc4,\n    0x9a, 0x65, 0x3b, 0xd9, 0x87, 0x04, 0x5a, 0xb8, 0xe6, 0xa7, 0xf9, 0x1b, 0x45, 0xc6, 0x98, 0x7a, 0x24, 0xf8, 0xa6,\n    0x44, 0x1a, 0x99, 0xc7, 0x25, 0x7b, 0x3a, 0x64, 0x86, 0xd8, 0x5b, 0x05, 0xe7, 0xb9, 0x8c, 0xd2, 0x30, 0x6e, 0xed,\n    0xb3, 0x51, 0x0f, 0x4e, 0x10, 0xf2, 0xac, 0x2f, 0x71, 0x93, 0xcd, 0x11, 0x4f, 0xad, 0xf3, 0x70, 0x2e, 0xcc, 0x92,\n    0xd3, 0x8d, 0x6f, 0x31, 0xb2, 0xec, 0x0e, 0x50, 0xaf, 0xf1, 0x13, 0x4d, 0xce, 0x90, 0x72, 0x2c, 0x6d, 0x33, 0xd1,\n    0x8f, 0x0c, 0x52, 0xb0, 0xee, 0x32, 0x6c, 0x8e, 0xd0, 0x53, 0x0d, 0xef, 0xb1, 0xf0, 0xae, 0x4c, 0x12, 0x91, 0xcf,\n    0x2d, 0x73, 0xca, 0x94, 0x76, 0x28, 0xab, 0xf5, 0x17, 0x49, 0x08, 0x56, 0xb4, 0xea, 0x69, 0x37, 0xd5, 0x8b, 0x57,\n    0x09, 0xeb, 0xb5, 0x36, 0x68, 0x8a, 0xd4, 0x95, 0xcb, 0x29, 0x77, 0xf4, 0xaa, 0x48, 0x16, 0xe9, 0xb7, 0x55, 0x0b,\n    0x88, 0xd6, 0x34, 0x6a, 0x2b, 0x75, 0x97, 0xc9, 0x4a, 0x14, 0xf6, 0xa8, 0x74, 0x2a, 0xc8, 0x96, 0x15, 0x4b, 0xa9,\n    0xf7, 0xb6, 0xe8, 0x0a, 0x54, 0xd7, 0x89, 0x6b, 0x35};\n\nstatic void process(float **sensor);\nstatic void send_packet(uint8_t address, float **sensor);\nstatic uint8_t crc_tab1e(uint8_t data, uint8_t crc);\nstatic uint8_t crc8(uint8_t *buffer, uint8_t length);\nstatic void set_config(float **sensor);\n\nvoid jr_dmss_task(void *parameters) {\n    float *sensor[10] = {NULL};\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(250000L, UART_RECEIVER_TX, UART_RECEIVER_RX, JR_DMSS_TIMEOUT_US, 8, 2, UART_PARITY_NONE, false, true);\n    set_config(sensor);\n    debug(\"\\nJR Propo init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(sensor);\n    }\n}\n\nstatic void process(float **sensor) {\n    uint8_t len = uart0_available();\n    uint8_t buffer[len];\n    uart0_read_bytes(buffer, len);\n    if (len == JR_DMSS_PACKET_LENGHT) {\n        // debug(\"\\nJR Propo (%u) < %X\", uxTaskGetStackHighWaterMark(NULL), buffer[0]);\n        send_packet(buffer[0], sensor);\n    }\n}\n\nstatic void send_packet(uint8_t address, float **sensor) {\n    uint8_t buffer[6];\n    switch (address) {\n        case JR_DMSS_TEMPERATURE_SENSOR_ID: {\n            if (sensor[TEMPERATURE] == NULL) return;\n            buffer[0] = JR_DMSS_TEMPERATURE_SENSOR_ID | 0xE0;\n            buffer[1] = 0x3;\n            buffer[2] = JR_DMSS_TEMPERATURE_TEMPERATURE_INDEX;\n            uint16_t value = *sensor[TEMPERATURE];\n            buffer[3] = value >> 8;\n            buffer[4] = value;\n            buffer[5] = crc8(buffer, 5);\n            uart0_write_bytes(buffer, sizeof(buffer));\n            vTaskResume(context.led_task_handle);\n            debug(\"\\nJR Propo (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(buffer), \"0x%X \");\n            break;\n        }\n        case JR_DMSS_RPM_SENSOR_ID: {\n            if (sensor[RPM] == NULL) return;\n            buffer[0] = JR_DMSS_RPM_SENSOR_ID | 0xE0;\n            buffer[1] = 0x3;\n            buffer[2] = JR_DMSS_RPM_RPM_INDEX;\n            uint16_t value = *sensor[RPM];\n            buffer[3] = value >> 8;\n            buffer[4] = value;\n            buffer[5] = crc8(buffer, 5);\n            uart0_write_bytes(buffer, sizeof(buffer));\n            vTaskResume(context.led_task_handle);\n            debug(\"\\nJR Propo (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(buffer), \"0x%X \");\n            break;\n        }\n        case JR_DMSS_VARIO_SENSOR_ID: {\n            {\n                static uint8_t index = 0;\n                buffer[0] = JR_DMSS_VARIO_SENSOR_ID | 0xE0;\n                buffer[1] = 0x3;\n                uint16_t value;\n                if ((index % 3) == 0) {\n                    if (sensor[ALTITUDE] == NULL) {\n                        index++;\n                        return;\n                    }\n                    buffer[2] = JR_DMSS_VARIO_ALTITUDE_INDEX;\n                    value = *sensor[ALTITUDE] * 10;\n                } else if ((index % 3) == 1) {\n                    if (sensor[VSPEED] == NULL) {\n                        index++;\n                        return;\n                    }\n                    buffer[2] = JR_DMSS_VARIO_VSPEED_INDEX;\n                    value = *sensor[VSPEED] * 10;\n                } else if ((index % 3) == 2) {\n                    if (sensor[PRESSURE] == NULL) {\n                        index++;\n                        return;\n                    }\n                    buffer[2] = JR_DMSS_VARIO_PRESSURE_INDEX;\n                    value = *sensor[PRESSURE] / 100 * 10;  // Pa -> 0.1 hPa\n                }\n                buffer[3] = value >> 8;\n                buffer[4] = value;\n                buffer[5] = crc8(buffer, 5);\n                uart0_write_bytes(buffer, sizeof(buffer));\n                vTaskResume(context.led_task_handle);\n                debug(\"\\nJR Propo (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n                debug_buffer(buffer, sizeof(buffer), \"0x%X \");\n                index++;\n                break;\n            }\n        }\n        case JR_DMSS_AIRSPEED_SENSOR_ID: {\n            if (sensor[AIRSPEED] == NULL) return;\n            buffer[0] = JR_DMSS_AIRSPEED_SENSOR_ID | 0xE0;\n            buffer[1] = 0x3;\n            buffer[2] = JR_DMSS_AIRSPEED_AIRSPEED_INDEX;\n            uint16_t value = *sensor[AIRSPEED];\n            buffer[3] = value >> 8;\n            buffer[4] = value;\n            buffer[5] = crc8(buffer, 5);\n            uart0_write_bytes(buffer, sizeof(buffer));\n            vTaskResume(context.led_task_handle);\n            debug(\"\\nJR Propo (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(buffer), \"0x%X \");\n            break;\n        }\n        case JR_DMSS_BATTERY_SENSOR_ID: {\n            static uint8_t index = 0;\n            buffer[0] = JR_DMSS_BATTERY_SENSOR_ID | 0xE0;\n            buffer[1] = 0x3;\n            uint16_t value;\n            if ((index % 3) == 0) {\n                if (sensor[VOLTAGE] == NULL) {\n                    index++;\n                    return;\n                }\n                buffer[2] = JR_DMSS_BATTERY_VOLTAGE_INDEX;\n                value = *sensor[VOLTAGE] * 100;\n            } else if ((index % 3) == 1) {\n                if (sensor[CURRENT] == NULL) {\n                    index++;\n                    return;\n                }\n                buffer[2] = JR_DMSS_BATTERY_CURRENT_INDEX;\n                value = *sensor[CURRENT] * 100;\n            } else if ((index % 3) == 2) {\n                if (sensor[CAPACITY] == NULL) {\n                    index++;\n                    return;\n                }\n                buffer[2] = JR_DMSS_BATTERY_CAPACITY_INDEX;\n                value = *sensor[CAPACITY];\n            }\n            buffer[3] = value >> 8;\n            buffer[4] = value;\n            buffer[5] = crc8(buffer, 5);\n            uart0_write_bytes(buffer, sizeof(buffer));\n            vTaskResume(context.led_task_handle);\n            debug(\"\\nJR Propo (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(buffer), \"0x%X \");\n            index++;\n            break;\n        }\n    }\n}\n\nstatic uint8_t crc_tab1e(uint8_t data, uint8_t crc) {\n    uint16_t index = (data ^ crc) & 0xff;\n    crc = s_crc_array[index];\n    return crc;\n}\n\nstatic uint8_t crc8(uint8_t *buffer, uint8_t length) {\n    uint8_t crc = 0;\n    while (length-- > 0) {\n        crc = crc_tab1e(*buffer++, crc);\n    }\n    return crc;\n}\n\nstatic void set_config(float **sensor) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    float *new_sensor;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[VOLTAGE] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature_fet;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature_fet;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature_fet;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature_fet;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temp_esc;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temp_esc;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.rpm;\n        sensor[RPM] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.temperature_fet;\n        sensor[TEMPERATURE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.voltage;\n        sensor[VOLTAGE] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.current;\n        sensor[CURRENT] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.consumption;\n        sensor[CAPACITY] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.ntc;\n        sensor[TEMPERATURE] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.altitude;\n        sensor[ALTITUDE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.vspeed;\n        sensor[VSPEED] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.pressure;\n        sensor[PRESSURE] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.altitude;\n        sensor[ALTITUDE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.vspeed;\n        sensor[VSPEED] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.pressure;\n        sensor[PRESSURE] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.altitude;\n        sensor[ALTITUDE] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.vspeed;\n        sensor[VSPEED] = new_sensor;\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.pressure;\n        sensor[PRESSURE] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(float));\n        new_sensor = parameter.airspeed;\n        sensor[AIRSPEED] = new_sensor;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}\n"
  },
  {
    "path": "board/project/protocol/jr_dmss.h",
    "content": "#ifndef JR_DMSS_H\n#define JR_DMSS_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid jr_dmss_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/multiplex.c",
    "content": "#include \"multiplex.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_pwm.h\"\n#include \"ms5611.h\"\n#include \"gps.h\"\n#include \"ntc.h\"\n#include \"pico/stdlib.h\"\n#include \"pwm_out.h\"\n#include \"stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n#include \"smart_esc.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_ztw.h\"\n\n/* Flysky MULTIPLEX FHSS Data Id */\n#define MULTIPLEX_VOLTAGE 1\n#define MULTIPLEX_CURRENT 2\n#define MULTIPLEX_VARIO 3\n#define MULTIPLEX_SPEED 4\n#define MULTIPLEX_RPM 5\n#define MULTIPLEX_TEMP 6\n#define MULTIPLEX_COURSE 7\n#define MULTIPLEX_ALTITUDE 8\n#define MULTIPLEX_LEVEL 9\n#define MULTIPLEX_RSSI 10\n#define MULTIPLEX_CONSUMPTION 11\n#define MULTIPLEX_FLUID 12\n#define MULTIPLEX_DISTANCE 13\n\n#define MULTIPLEX_RECEIVED_NONE 0\n#define MULTIPLEX_RECEIVED_POLL 1\n\n#define MULTIPLEX_COMMAND_DISCOVER 0x8\n#define MULTIPLEX_COMMAND_TYPE 0x9\n#define MULTIPLEX_COMMAND_MEASURE 0xA\n\n#define MULTIPLEX_TIMEOUT_US 1000\n#define MULTIPLEX_PACKET_LENGHT 1\n\ntypedef struct sensor_multiplex_t {\n    uint8_t data_id;\n    float *value;\n} sensor_multiplex_t;\n\nstatic void process(sensor_multiplex_t **sensor);\nstatic void send_packet(uint8_t address, sensor_multiplex_t *sensor);\nstatic int16_t format(uint8_t data_id, float value);\nstatic void add_sensor(sensor_multiplex_t *new_sensor, sensor_multiplex_t **sensors);\nstatic void set_config(sensor_multiplex_t **sensors);\n\nvoid multiplex_task(void *parameters) {\n    sensor_multiplex_t *sensor[16] = {NULL};\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(38400, UART_RECEIVER_TX, UART_RECEIVER_RX, MULTIPLEX_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    set_config(sensor);\n    debug(\"\\nMultiplex init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(sensor);\n    }\n}\n\nstatic void process(sensor_multiplex_t **sensor) {\n    uint8_t address = 0;\n    if (uart0_available() == MULTIPLEX_PACKET_LENGHT) {\n        uart0_read_bytes(&address, MULTIPLEX_PACKET_LENGHT);\n        debug(\"\\nMultiplex (%u) < %X\", uxTaskGetStackHighWaterMark(NULL), address);\n\n        if (address < 16) {\n            send_packet(address, sensor[address]);\n        }\n    }\n}\n\nstatic void send_packet(uint8_t address, sensor_multiplex_t *sensor) {\n    if (!sensor) return;\n    uint8_t sensor_id = address << 4 | sensor->data_id;\n    uart0_write(sensor_id);\n    int16_t value = format(sensor->data_id, *sensor->value);\n    uart0_write_bytes((uint8_t *)&value, 2);\n\n    vTaskResume(context.led_task_handle);\n\n    debug(\"\\nMultiplex (%u) > %X %X\", uxTaskGetStackHighWaterMark(NULL), sensor_id, value);\n}\n\nstatic void add_sensor(sensor_multiplex_t *new_sensor, sensor_multiplex_t **sensors) {\n    static uint8_t sensor_count = 0;\n    if (sensor_count < 16) {\n        sensors[sensor_count] = new_sensor;\n        sensor_count++;\n    }\n}\n\nstatic int16_t format(uint8_t data_id, float value) {\n    int16_t formatted;\n    if (data_id == MULTIPLEX_VOLTAGE || data_id == MULTIPLEX_CURRENT || data_id == MULTIPLEX_VARIO ||\n        data_id == MULTIPLEX_SPEED || data_id == MULTIPLEX_TEMP || data_id == MULTIPLEX_COURSE ||\n        data_id == MULTIPLEX_DISTANCE)\n        formatted = round(value * 10);\n    else if (data_id == MULTIPLEX_RPM)\n        formatted = round(value / 10);\n    else\n        formatted = round(value);\n    if (formatted > 16383) formatted = 16383;\n    if (formatted < -16383) formatted = -16383;\n    bool isNegative = false;\n    if (formatted < 0) isNegative = true;\n    formatted <<= 1;\n    if (isNegative) formatted |= 1 << 15;\n    return formatted;\n}\n\nstatic void set_config(sensor_multiplex_t **sensors) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    sensor_multiplex_t *new_sensor;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_fet};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_bec};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_fet};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_bec};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_motor};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage_bec};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current_bec};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage_bec};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current_bec};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_fet};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_bec};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature_fet};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temp_esc};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_RPM, parameter.rpm};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temp_esc};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.cell_voltage};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_ALTITUDE, parameter.alt};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_SPEED, parameter.spd};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VARIO, parameter.vspeed};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_DISTANCE, parameter.dist};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VOLTAGE, parameter.voltage};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CURRENT, parameter.current};\n        add_sensor(new_sensor, sensors);\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_CONSUMPTION, parameter.consumption};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.ntc};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_ALTITUDE, parameter.altitude};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VARIO, parameter.vspeed};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_ALTITUDE, parameter.altitude};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VARIO, parameter.vspeed};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_TEMP, parameter.temperature};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_ALTITUDE, parameter.altitude};\n        add_sensor(new_sensor, sensors);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_VARIO, parameter.vspeed};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_multiplex_t));\n        *new_sensor = (sensor_multiplex_t){MULTIPLEX_SPEED, parameter.airspeed};\n        add_sensor(new_sensor, sensors);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}\n"
  },
  {
    "path": "board/project/protocol/multiplex.h",
    "content": "#ifndef MULTIPLEX_H\n#define MULTIPLEX_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid multiplex_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/sanwa.c",
    "content": "#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"crsf.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_pwm.h\"\n#include \"hardware/clocks.h\"\n#include \"hardware/pwm.h\"\n#include \"ibus.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n#define SANWA_TIMEOUT_US 500\n\n#define TYPE_TEMP_MOTOR 0\n#define TYPE_TEMP_ESC 1\n#define TYPE_UNKNOWN 2\n#define TYPE_RPM1 3\n#define TYPE_RPM2 4\n#define TYPE_VOLT 5\n\n#define MAX_SENSORS 6\n\n#define TELEMETRY_PACKET_LENGHT 5\n#define CHANNEL_PACKET_LENGHT 10\n\n#define GPIO_PWM_CH1 10\n#define GPIO_PWM_CH2 11\n\n#define PWM_FREQ 20  // ms\n\ntypedef struct sanwa_sensor_formatted_t {\n    uint8_t header;\n    uint8_t type;\n    uint8_t msb;\n    uint8_t lsb;\n    uint8_t crc;\n} __attribute__((packed)) sanwa_sensor_formatted_t;\n\nstatic void process(float **sensors);\nstatic void format_sensor(float *sensor, uint8_t type, sanwa_sensor_formatted_t *sensor_formatted);\nstatic void send_packet(float **sensors);\nstatic uint8_t get_crc(const uint8_t *buffer, uint len);\nstatic void set_config(float **sensors);\n\nvoid sanwa_task(void *parameters) {\n    /*gpio_set_function(GPIO_PWM_CH1, GPIO_FUNC_PWM);\n    gpio_set_function(GPIO_PWM_CH2, GPIO_FUNC_PWM);\n    uint slice_num_ch1 = pwm_gpio_to_slice_num(GPIO_PWM_CH1);\n    uint slice_num_ch2 = pwm_gpio_to_slice_num(GPIO_PWM_CH2);\n    pwm_config config_ch1 = pwm_get_default_config();\n    pwm_config config_ch2 = pwm_get_default_config();\n    uint clk_div =\n        PWM_FREQ * clock_get_hz(clk_sys) / 1000 / 65536.0;  // clk_div = pulse_freq * clk_sys_hz / (1000 * 65536)\n    pwm_config_set_wrap(&config_ch1, 0xFFFF);\n    pwm_set_gpio_level(GPIO_PWM_CH1, 65536 / 20);  // set ch1 to lowest position\n    pwm_set_gpio_level(GPIO_PWM_CH2, 65536 / 20);  // set ch2 to lowest position*/\n\n    float *sensors[6] = {0};\n    set_config(sensors);\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(115200L, UART_RECEIVER_TX, UART_RECEIVER_RX, SANWA_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    debug(\"\\nSanwa init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(sensors);\n    }\n}\n\nstatic void process(float **sensors) {\n    if (uart0_available() == CHANNEL_PACKET_LENGHT) {\n        uint8_t buffer[CHANNEL_PACKET_LENGHT];\n        uart0_read_bytes(buffer, CHANNEL_PACKET_LENGHT);\n        debug(\"\\nSanwa (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n        debug_buffer(buffer, CHANNEL_PACKET_LENGHT, \"0x%X \");\n        if (buffer[0] == 0x01 && get_crc(buffer, CHANNEL_PACKET_LENGHT - 1) == buffer[CHANNEL_PACKET_LENGHT - 1]) {\n            // pwm_set_gpio_level(GPIO_PWM_CH1, (buffer[1] << 8) | buffer[2]);\n            // pwm_set_gpio_level(GPIO_PWM_CH2, (buffer[3] << 8) | buffer[4]);\n            send_packet(sensors);\n        } else {\n            debug(\"\\nSanwa. Bad header\");\n        }\n    }\n}\n\nstatic void send_packet(float **sensors) {\n    if (!sensors[TYPE_TEMP_MOTOR] && !sensors[TYPE_TEMP_ESC] && !sensors[TYPE_RPM1] && !sensors[TYPE_RPM2] &&\n        !sensors[TYPE_VOLT])\n        return;\n    static uint type = TYPE_TEMP_MOTOR;\n    sanwa_sensor_formatted_t sensor_formatted = {0};\n    while (!sensors[type % MAX_SENSORS]) type++;\n    format_sensor(sensors[type % MAX_SENSORS], type % MAX_SENSORS, &sensor_formatted);\n    uart0_write_bytes((uint8_t *)&sensor_formatted, TELEMETRY_PACKET_LENGHT);\n    type++;\n    debug(\"\\nSanwa (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer((uint8_t *)&sensor_formatted, TELEMETRY_PACKET_LENGHT, \"0x%X \");\n\n    // blink led\n    vTaskResume(context.led_task_handle);\n}\n\nstatic void format_sensor(float *sensor, uint8_t type, sanwa_sensor_formatted_t *sensor_formatted) {\n    // Packet format: [sync] [type] [msb] [lsb] [crc8]\n    static float rpm = 0;\n    sensor_formatted->header = 0x01;\n    sensor_formatted->type = type;\n    switch (type) {\n        case TYPE_TEMP_MOTOR: {\n            static uint8_t prev = 0;\n            sensor_formatted->msb = prev;\n            sensor_formatted->lsb = *sensor;\n            prev = sensor_formatted->lsb;\n            sensor_formatted->crc = get_crc((uint8_t *)sensor_formatted, TELEMETRY_PACKET_LENGHT - 1);\n            break;\n        }\n        case TYPE_TEMP_ESC: {\n            static uint8_t prev = 0;\n            sensor_formatted->msb = prev;\n            sensor_formatted->lsb = *sensor;\n            prev = sensor_formatted->lsb;\n            sensor_formatted->crc = get_crc((uint8_t *)sensor_formatted, TELEMETRY_PACKET_LENGHT - 1);\n            break;\n        }\n        case TYPE_RPM1: {\n            rpm = *sensor;\n            sensor_formatted->msb = (uint)(rpm / 2) >> 8;\n            sensor_formatted->lsb = rpm / 2;\n            sensor_formatted->crc = get_crc((uint8_t *)sensor_formatted, TELEMETRY_PACKET_LENGHT - 1);\n            break;\n        }\n        case TYPE_RPM2: {\n            sensor_formatted->msb = (uint)(rpm / 2) >> 8;\n            sensor_formatted->lsb = rpm / 2;\n            sensor_formatted->crc = get_crc((uint8_t *)sensor_formatted, TELEMETRY_PACKET_LENGHT - 1);\n            break;\n        }\n        case TYPE_VOLT: {\n            uint16_t volt = *sensor * 100;\n            sensor_formatted->msb = volt >> 8;\n            sensor_formatted->lsb = volt;\n            sensor_formatted->crc = get_crc((uint8_t *)sensor_formatted, TELEMETRY_PACKET_LENGHT - 1);\n            break;\n        }\n    }\n}\n\nstatic uint8_t get_crc(const uint8_t *buffer, uint len) {\n    uint8_t crc = 0;\n    for (uint i = 0; i < len; i++) crc += buffer[i];\n    return crc;\n}\n\nstatic void set_config(float **sensors) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_RPM1] = parameter.rpm;\n        sensors[TYPE_RPM2] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_RPM1] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        /*if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }*/\n        sensors[TYPE_TEMP_ESC] = parameter.temperature_fet;\n        sensors[TYPE_TEMP_MOTOR] = parameter.temperature_bec;  // note this is bec temp, not motor temp\n        sensors[TYPE_VOLT] = parameter.voltage;\n        sensors[TYPE_RPM1] = parameter.rpm;\n        sensors[TYPE_RPM2] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_TEMP_ESC] = parameter.temperature_fet;\n        sensors[TYPE_TEMP_MOTOR] = parameter.temperature_bec;  // note this is bec temp, not motor temp\n        sensors[TYPE_VOLT] = parameter.voltage;\n        sensors[TYPE_RPM1] = parameter.rpm;\n        sensors[TYPE_RPM2] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_TEMP_ESC] = parameter.temperature;\n        sensors[TYPE_VOLT] = parameter.voltage;\n        sensors[TYPE_RPM1] = parameter.rpm;\n        sensors[TYPE_RPM2] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_TEMP_ESC] = parameter.temperature_fet;\n        sensors[TYPE_TEMP_MOTOR] = parameter.temperature_bec;  // note this is bec temp, not motor temp\n        sensors[TYPE_VOLT] = parameter.voltage;\n        sensors[TYPE_RPM1] = parameter.rpm;\n        sensors[TYPE_RPM2] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_TEMP_ESC] = parameter.temperature;\n        sensors[TYPE_VOLT] = parameter.voltage;\n        sensors[TYPE_RPM1] = parameter.rpm;\n        sensors[TYPE_RPM2] = parameter.rpm;\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_TEMP_ESC] = parameter.temperature;\n        sensors[TYPE_VOLT] = parameter.voltage;\n        sensors[TYPE_RPM1] = parameter.rpm;\n        sensors[TYPE_RPM2] = parameter.rpm;\n    }\n\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_VOLT] = parameter.voltage;\n    }\n\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        sensors[TYPE_TEMP_MOTOR] = parameter.ntc;\n    }\n}\n"
  },
  {
    "path": "board/project/protocol/sanwa.h",
    "content": "#ifndef SANWA_H\n#define SANWA_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid sanwa_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/protocol/sbus.c",
    "content": "#include \"sbus.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"gps.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n#define SLOT_TEMP1 1\n#define SLOT_RPM 2\n#define SLOT_VARIO_SPEED 3\n#define SLOT_VARIO_ALT 4\n#define SLOT_VARIO_RESSURE 5\n#define SLOT_VOLT_V1 6\n#define SLOT_VOLT_V2 7\n#define SLOT_GPS_SPD 8\n#define SLOT_GPS_ALT 9\n#define SLOT_GPS_TIME 10\n#define SLOT_GPS_VARIO 11\n#define SLOT_GPS_LAT1 12\n#define SLOT_GPS_LAT2 13\n#define SLOT_GPS_LON1 14\n#define SLOT_GPS_LON2 15\n#define SLOT_AIR_SPEED 16\n#define SLOT_POWER_CURR3 21\n#define SLOT_POWER_VOLT3 22\n#define SLOT_POWER_CONS3 23\n#define SLOT_POWER_CURR1 24\n#define SLOT_POWER_VOLT1 25\n#define SLOT_POWER_CONS1 26\n#define SLOT_POWER_CURR2 27\n#define SLOT_POWER_VOLT2 28\n#define SLOT_POWER_CONS2 29\n#define SLOT_TEMP2 30\n\n#define TIMEOUT_US 500\n#define SLOT_0_DELAY 2000\n#define INTER_SLOT_DELAY 660\n#define PACKET_LENGHT 25\n#define SBUS_NEGATIVE_BIT 15\n#define SBUS_SOUTH_WEST_BIT 4\n#define SBUS_WAIT 0\n#define SBUS_SEND 1\n\n/* FASST Sbus Data Id */\n#define SBUS_NULL 0\n#define SBUS_TEMP 1\n#define SBUS_VOLT_V1 2\n#define SBUS_VOLT_V2 3\n#define SBUS_RPM 4\n#define SBUS_POWER_CURR 5\n#define SBUS_POWER_VOLT 6\n#define SBUS_POWER_CONS 7\n#define SBUS_VARIO_ALT 8  // F1672\n#define SBUS_VARIO_SPEED 9\n#define SBUS_GPS_SPEED 10  // F1675\n#define SBUS_GPS_ALTITUDE 11\n#define SBUS_GPS_TIME 12\n#define SBUS_GPS_VARIO_SPEED 13\n#define SBUS_GPS_LATITUDE1 14\n#define SBUS_GPS_LATITUDE2 15\n#define SBUS_GPS_LONGITUDE1 16\n#define SBUS_GPS_LONGITUDE2 17\n#define SBUS_AIR_SPEED 18\n\n/**\n * Slots sensor mapping for Futaba transmitters:\n *\n * | Slot   | Sensor                               |\n * | :----: | :----------------------------------: |\n * |0       | RX voltage (reserved)                |\n * |1       | Temperature 1 (SBS-01T/TE)           |\n * |2       | RPM (type magnet)(SBS-01RB/RM/RO)    |\n * |3-4     | Vario-F1672                          |\n * |6-7     | Voltage (SBS-01V)                    |\n * |8-15    | GPS-F1675                            |\n * |16      | Air speed (SBS-01TAS)                |\n * |17-21   | Unused                               |\n * |21-23   | Current 3 (SBS-01C)                  |\n * |24-26   | Current 1 (SBS-01C)                  |\n * |27-29(+)| Current 2 (SBS-01C)                  |\n * |30(+)   | Temperature 2 (SBS-01T/TE)           |\n * |31      | Unused                               |\n *\n * (+) Non default slots\n */\n\ntypedef struct sensor_sbus_t {\n    uint8_t data_id;\n    float *value;\n} sensor_sbus_t;\n\nstatic sensor_sbus_t *sbus_sensor[32] = {NULL};\nstatic uint8_t *gps_fix;\nstatic uint packet_id;\nstatic volatile uint slot = 0;\nstatic volatile bool slot_pending = false;\n\nstatic void process();\nstatic int64_t send_slot_callback(alarm_id_t id, void *parameters);\nstatic inline void send_slot(uint8_t slot);\nstatic uint16_t format(uint8_t data_id, float value);\nstatic void add_sensor(uint8_t slot, sensor_sbus_t *new_sensor);\nstatic void set_config(void);\nstatic uint8_t get_slot_id(uint8_t slot);\n\nvoid sbus_task(void *parameters) {\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    set_config();\n    uart0_begin(100000, UART_RECEIVER_TX, UART_RECEIVER_RX, TIMEOUT_US, 8, 2, UART_PARITY_EVEN, true, true);\n    debug(\"\\nSbus init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process();\n        if (slot_pending) {\n            send_slot(slot);\n            slot_pending = false;\n        }\n    }\n}\n\nstatic void process() {\n    if (uart0_available() == PACKET_LENGHT) {\n        uint8_t data[PACKET_LENGHT];\n        uart0_read_bytes(data, PACKET_LENGHT);\n        debug(\"\\nSbus (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n        debug_buffer(data, PACKET_LENGHT, \"0x%X \");\n        if (data[0] == 0x0F) {\n            if (data[24] == 0x04 || data[24] == 0x14 || data[24] == 0x24 || data[24] == 0x34) {\n                packet_id = data[24] >> 4;\n                add_alarm_in_us(SLOT_0_DELAY - uart0_get_time_elapsed(), send_slot_callback, NULL, true);\n                debug(\"\\nSbus (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n            }\n        }\n    }\n}\n\nstatic int64_t send_slot_callback(alarm_id_t id, void *parameters) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    static uint timestamp;\n    static uint8_t index = 0;\n    uint next_alarm;\n    slot = index + packet_id * 8;\n    if (context.debug == 2) {\n        if (slot == 0 || slot == 8 || slot == 16 || slot == 24)\n            printf(\"\\nT:%u\\n\", uart0_get_time_elapsed());\n        else\n            printf(\"\\nT:%u\\n\", time_us_32() - timestamp);\n    }\n    timestamp = time_us_32();\n    if (index < 7) {\n        index++;\n        next_alarm = INTER_SLOT_DELAY - (time_us_32() - timestamp);\n    } else {\n        index = 0;\n        next_alarm = 0;\n    }\n    vTaskResume(context.led_task_handle);\n    slot_pending = true;\n    vTaskNotifyGiveIndexedFromISR(context.uart0_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    return next_alarm;\n}\n\nstatic inline void send_slot(uint8_t slot) {\n    debug(\" (%u)\", slot);\n    uint16_t value = 0;\n    if (sbus_sensor[slot]) {\n        if (sbus_sensor[slot]->value) {\n            value = format(sbus_sensor[slot]->data_id, *sbus_sensor[slot]->value);\n        } else {\n            value = format(sbus_sensor[slot]->data_id, 0);\n        }\n        uint8_t data[3];\n        data[0] = get_slot_id(slot);\n        data[1] = value;\n        data[2] = value >> 8;\n        uart0_write_bytes(data, 3);\n        debug(\"%X:%X:%X \", data[0], data[1], data[2]);\n    }\n}\n\nstatic uint16_t format(uint8_t data_id, float value) {\n    static float coord = 0;\n    if (data_id == SBUS_RPM) {\n        return (uint16_t)round(value / 6);\n    }\n    if (data_id == SBUS_TEMP) {\n        return (uint16_t)round(value + 100) | 0X8000;\n    }\n    if (data_id == SBUS_VOLT_V1) {\n        return __builtin_bswap16((uint16_t)round(value * 10) | 0x8000);\n    }\n    if (data_id == SBUS_VOLT_V2) {\n        return __builtin_bswap16((uint16_t)round(value * 10));\n    }\n    if (data_id == SBUS_VARIO_SPEED) {\n        return __builtin_bswap16((int16_t)round(value * 100));\n    }\n    if (data_id == SBUS_VARIO_ALT) {\n        return __builtin_bswap16((int16_t)round(value) | 0x4000);\n    }\n    if (data_id == SBUS_POWER_CURR) {\n        return __builtin_bswap16((uint16_t)round(value * 100) | 0x4000);\n    }\n    if (data_id == SBUS_POWER_VOLT) {\n        return __builtin_bswap16((uint16_t)round((value)*100));\n    }\n    if (data_id == SBUS_AIR_SPEED) {\n        return __builtin_bswap16((uint16_t)round(value) | 0x4000);\n    }\n    if (data_id == SBUS_GPS_SPEED) {  \n        return __builtin_bswap16((uint16_t)round(value * 1.852) | (*gps_fix > 0 ? 0x4000 : 0x0000));\n    }\n    if (data_id == SBUS_GPS_VARIO_SPEED) {\n        return __builtin_bswap16((int16_t)round(value * 10));\n    }\n    if (data_id == SBUS_GPS_ALTITUDE) {\n        return __builtin_bswap16((int16_t)round(value) | (*gps_fix > 1 ? 0x4000 : 0x0000));\n    }\n    if (data_id == SBUS_GPS_LATITUDE1 || data_id == SBUS_GPS_LONGITUDE1) {\n        // bits 1-4: bits 17-20 from minutes precision 4 (minutes*10000 = 20 bits)\n        // bit 5: S/W bit\n        // bits 9-16: degrees\n        coord = value;\n        uint16_t lat_lon = 0;\n        if (coord < 0) {\n            lat_lon = 1 << SBUS_SOUTH_WEST_BIT;\n            coord *= -1;\n        }\n        uint8_t degrees = coord;\n        lat_lon |= degrees << 8;\n        uint32_t minutes = (coord - degrees) * 60 * 10000;  // minutes precision 4\n        lat_lon |= (minutes >> 16) & 0xf;\n        return __builtin_bswap16(lat_lon);\n    }\n    if (data_id == SBUS_GPS_LATITUDE2 || data_id == SBUS_GPS_LONGITUDE2) {\n        // bits 1-16 from minutes precision 4 (minutes*10000 = 20 bits)\n        uint32_t minutes = (coord - (int)coord) * 60 * 10000;  // minutes precision 4\n        return __builtin_bswap16(minutes);\n    }\n    if (data_id == SBUS_GPS_TIME) {\n        if (value > 120000) value -= 120000;\n        uint8_t hours = value / 10000;\n        uint8_t minutes = (uint8_t)(value / 100) - hours * 100;\n        uint8_t seconds = value - hours * 10000 - minutes * 100;\n        return __builtin_bswap16(hours * 3600 + minutes * 60 + seconds);\n    }\n    return __builtin_bswap16(round(value));\n}\n\nstatic uint8_t get_slot_id(uint8_t slot) {\n    uint8_t slot_id[32] = {0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53,\n                           0xD3, 0x33, 0xB3, 0x73, 0xF3, 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB,\n                           0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB};\n    return slot_id[slot];\n}\n\nstatic void add_sensor(uint8_t slot, sensor_sbus_t *new_sensor) { sbus_sensor[slot] = new_sensor; }\n\nstatic void set_config(void) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    sensor_sbus_t *new_sensor;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_fet};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_bec};\n        add_sensor(SLOT_TEMP2, new_sensor);\n        // new_sensor = malloc(sizeof(sensor_sbus_t));\n        //*new_sensor = (sensor_sbus_t){AFHDS2A_ID_CELL_VOLTAGE, parameter.cell_voltage};\n        // add_sensor(new_sensor);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_fet};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_bec};\n        add_sensor(SLOT_TEMP2, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage_bec};\n        add_sensor(SLOT_POWER_VOLT3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current_bec};\n        add_sensor(SLOT_POWER_CURR3, new_sensor);\n        // new_sensor = malloc(sizeof(sensor_sbus_t));\n        //*new_sensor = (sensor_sbus_t){AFHDS2A_ID_CELL_VOLTAGE, parameter.cell_voltage};\n        // add_sensor(new_sensor);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage_bec};\n        add_sensor(SLOT_POWER_VOLT3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current_bec};\n        add_sensor(SLOT_POWER_CURR3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        // new_sensor = malloc(sizeof(sensor_sbus_t));\n        //*new_sensor = (sensor_sbus_t){AFHDS2A_ID_CELL_VOLTAGE, parameter.cell_voltage};\n        // add_sensor(new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage_bec};\n        add_sensor(SLOT_POWER_VOLT3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current_bec};\n        add_sensor(SLOT_POWER_CURR3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_fet};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_bec};\n        add_sensor(SLOT_TEMP2, new_sensor);\n        // new_sensor = malloc(sizeof(sensor_sbus_t));\n        //*new_sensor = (sensor_sbus_t){AFHDS2A_ID_CELL_VOLTAGE, parameter.cell_voltage};\n        // add_sensor(new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        // new_sensor = malloc(sizeof(sensor_sbus_t));\n        //*new_sensor = (sensor_sbus_t){AFHDS2A_ID_CELL_VOLTAGE, parameter.cell_voltage};\n        // add_sensor(new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_fet};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temp_esc};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temp_motor};\n        add_sensor(SLOT_TEMP2, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temp_esc};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temp_motor};\n        add_sensor(SLOT_TEMP2, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_RPM, parameter.rpm};\n        add_sensor(SLOT_RPM, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage};\n        add_sensor(SLOT_POWER_VOLT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, parameter.voltage_bec};\n        add_sensor(SLOT_POWER_VOLT3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current_bec};\n        add_sensor(SLOT_POWER_CURR3, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_fet};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.temperature_bec};\n        add_sensor(SLOT_TEMP2, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_LATITUDE1, parameter.lat};\n        add_sensor(SLOT_GPS_LAT1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_LATITUDE2, parameter.lat};\n        add_sensor(SLOT_GPS_LAT2, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_LONGITUDE1, parameter.lon};\n        add_sensor(SLOT_GPS_LON1, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_LONGITUDE2, parameter.lon};\n        add_sensor(SLOT_GPS_LON2, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_ALTITUDE, parameter.alt};\n        add_sensor(SLOT_GPS_ALT, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_SPEED, parameter.spd};\n        add_sensor(SLOT_GPS_SPD, new_sensor);\n        gps_fix = parameter.fix_type;\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_VARIO_SPEED, parameter.vspeed};\n        add_sensor(SLOT_GPS_VARIO, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_GPS_TIME, parameter.time};\n        add_sensor(SLOT_GPS_TIME, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        if (config->sbus_battery_slot) {\n            new_sensor = malloc(sizeof(sensor_sbus_t));\n            *new_sensor = (sensor_sbus_t){SBUS_VOLT_V1, parameter.voltage};\n            add_sensor(SLOT_VOLT_V1, new_sensor);\n\n            new_sensor = malloc(sizeof(sensor_sbus_t));\n            *new_sensor = (sensor_sbus_t){SBUS_VOLT_V2, NULL};\n            add_sensor(SLOT_VOLT_V2, new_sensor);\n        } else {\n            new_sensor = malloc(sizeof(sensor_sbus_t));\n            *new_sensor = (sensor_sbus_t){SBUS_VOLT_V1, NULL};\n            add_sensor(SLOT_VOLT_V1, new_sensor);\n\n            new_sensor = malloc(sizeof(sensor_sbus_t));\n            *new_sensor = (sensor_sbus_t){SBUS_VOLT_V2, parameter.voltage};\n            add_sensor(SLOT_VOLT_V2, new_sensor);\n        }\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CURR, parameter.current};\n        add_sensor(SLOT_POWER_CURR2, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_CONS, parameter.consumption};\n        add_sensor(SLOT_POWER_CONS2, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_POWER_VOLT, NULL};\n        add_sensor(SLOT_POWER_VOLT2, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_TEMP, parameter.ntc};\n        add_sensor(SLOT_TEMP1, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_VARIO_ALT, parameter.altitude};\n        add_sensor(SLOT_VARIO_ALT, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_VARIO_SPEED, parameter.vspeed};\n        add_sensor(SLOT_VARIO_SPEED, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_VARIO_ALT, parameter.altitude};\n        add_sensor(SLOT_VARIO_ALT, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_VARIO_SPEED, parameter.vspeed};\n        add_sensor(SLOT_VARIO_SPEED, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_VARIO_ALT, parameter.altitude};\n        add_sensor(SLOT_VARIO_ALT, new_sensor);\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_VARIO_SPEED, parameter.vspeed};\n        add_sensor(SLOT_VARIO_SPEED, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n\n        new_sensor = malloc(sizeof(sensor_sbus_t));\n        *new_sensor = (sensor_sbus_t){SBUS_AIR_SPEED, parameter.airspeed};\n        add_sensor(SLOT_AIR_SPEED, new_sensor);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}"
  },
  {
    "path": "board/project/protocol/sbus.h",
    "content": "#ifndef SBUS_H\n#define SBUS_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid sbus_task(void *parameters);\n\n#endif\n"
  },
  {
    "path": "board/project/protocol/smartport.c",
    "content": "#include \"smartport.h\"\n\n#include <math.h>\n#include <semphr.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gpio.h\"\n#include \"gps.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pwm_out.h\"\n#include \"smart_esc.h\"\n#include \"stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n\n#define AIRCR_Register (*((volatile uint32_t *)(PPB_BASE + 0x0ED0C)))\n#define POLL_LENGHT 2\n#define PACKET_LENGHT 10\n\nstatic SemaphoreHandle_t semaphore_sensor = NULL;\nstatic bool is_maintenance_mode = false;\nstatic const uint8_t sensor_id_matrix[29] = {0x00, 0xA1, 0x22, 0x83, 0xE4, 0x45, 0xC6, 0x67, 0x48, 0xE9,\n                                             0x6A, 0xCB, 0xAC, 0xD,  0x8E, 0x2F, 0xD0, 0x71, 0xF2, 0x53,\n                                             0x34, 0x95, 0x16, 0xB7, 0x98, 0x39, 0xBA, 0x1B, 0x0};\nstatic TaskHandle_t packet_task_handle;\nstatic QueueHandle_t packet_queue_handle;\nstatic config_t *config_lua;\n\nstatic void sensor_task(void *parameters);\nstatic void sensor_void_task(void *parameters);\nstatic void sensor_double_task(void *parameters);\nstatic void sensor_coordinates_task(void *parameters);\nstatic void sensor_datetime_task(void *parameters);\nstatic void sensor_cell_task(void *parameters);\nstatic void sensor_cell_individual_task(void *parameters);\nstatic void sensor_gpio_task(void *parameters);\nstatic void packet_task(void *parameters);\nstatic void process(smartport_parameters_t *parameter);\nstatic void send_packet(uint8_t frame_id, uint16_t data_id, uint32_t value);\nstatic void set_config(smartport_parameters_t *parameter);\nstatic int64_t reboot_callback(alarm_id_t id, void *user_data);\n\nvoid smartport_task(void *parameters) {\n    smartport_parameters_t parameter;\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n    uart0_begin(57600, UART_RECEIVER_TX, UART_RECEIVER_RX, TIMEOUT_US, 8, 1, UART_PARITY_NONE, true, true);\n    semaphore_sensor = xSemaphoreCreateBinary();\n    xSemaphoreTake(semaphore_sensor, 0);\n    set_config(&parameter);\n    packet_queue_handle = xQueueCreate(32, sizeof(smartport_packet_t));\n    xTaskCreate(packet_task, \"packet_task\", STACK_SMARTPORT_PACKET_TASK, (void *)&parameter.data_id, 3,\n                &packet_task_handle);\n    // TaskHandle_t task_handle;\n    // xTaskCreate(sensor_void_task, \"sensor_void_task\", STACK_SMARTPORT_SENSOR_VOID_TASK, NULL, 2, &task_handle);\n    debug(\"\\nSmartport init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nsmartport_packet_t smartport_process_packet(smartport_parameters_t *parameter, uint8_t frame_id, uint16_t data_id,\n                                            uint32_t value) {\n    /*\n    Protocol:\n\n    cmd        | sender | frameId | dataId  | value (4B)    | sensorId\n    -----------|--------|---------|---------|---------------|---------\n    get var    | radio  | 0x30    | 0x51nn  | 0             |\n    set var    | radio  | 0x31    | 0x51nn  | value         |\n    start save | radio  | 0x31    | 0x5201  | 0             |\n    end save   | radio  | 0x31    | 0x5201  | 1             |\n    */\n\n    smartport_packet_t packet = {0};\n    // set maintenance mode on\n    if (frame_id == 0x21 && (data_id == 0xFFFF || data_id == parameter->data_id) && value == 0x80) {\n        is_maintenance_mode = true;\n        debug(\"\\nSmartport (%u). Maintenance mode ON \", uxTaskGetStackHighWaterMark(NULL));\n        return packet;\n    }\n\n    // set maintenance mode off\n    if (frame_id == 0x20 && (data_id == 0xFFFF || data_id == parameter->data_id) && value == 0x80) {\n        is_maintenance_mode = false;\n        debug(\"\\nSmartport (%u). Maintenance mode OFF \", uxTaskGetStackHighWaterMark(NULL));\n        return packet;\n    }\n\n    // send sensor id\n    if (frame_id == 0x30 && data_id == parameter->data_id && value == 0x01) {\n        smartport_packet_t packet;\n        packet.value = (parameter->sensor_id - 1) << 8 | 0x01;\n        packet.frame_id = 0x32;\n        packet.data_id = parameter->data_id;\n        debug(\"\\nSmartport (%u). Send sensorId %i (0x%X)\", uxTaskGetStackHighWaterMark(NULL), parameter->sensor_id,\n              smartport_sensor_id_to_crc(parameter->sensor_id));\n        return packet;\n    }\n\n    // send rate\n    if (frame_id == 0x30 && data_id == parameter->data_id && value == 0x22) {\n        smartport_packet_t packet;\n        packet.value = 0x122;\n        packet.frame_id = 0x32;\n        packet.data_id = parameter->data_id;\n        debug(\"\\nSmartport (%u). Send rate %i (0x%X)\", uxTaskGetStackHighWaterMark(NULL), packet.value >> 8,\n              packet.value);\n        return packet;\n    }\n\n    // send version\n    if (frame_id == 0x30 && data_id == parameter->data_id && value == 0x0C) {\n        packet.value = 0;\n        char version[] = PROJECT_VERSION;\n        memmove(version, version + 1, strlen(version));\n        char *token;\n        token = strtok(version, \" . \");\n        while (token != NULL) {\n            packet.value = packet.value << 4 | atoi(token);\n            token = strtok(NULL, \" . \");\n        }\n        packet.value = packet.value >> 4;\n        packet.value = (packet.value << 8) | 0xC;\n        packet.frame_id = 0x32;\n        packet.data_id = parameter->data_id;\n        debug(\"\\nSmartport (%u). Send version %s (0x%X)\", uxTaskGetStackHighWaterMark(NULL), PROJECT_VERSION,\n              packet.value);\n        return packet;\n    }\n\n    // change sensor id\n    if (frame_id == 0x31 && data_id == parameter->data_id && (uint8_t)value == 0x01) {\n        static bool is_changed = false;\n        if (is_changed == true) return packet;\n        uint8_t sensor_id = (value >> 8) + 1;\n        parameter->sensor_id = sensor_id;\n        config_lua = malloc(sizeof(config_t));\n        memcpy(config_lua, config_read(), sizeof(config_t));\n        config_lua->smartport_sensor_id = sensor_id;\n        config_write(config_lua);\n        sleep_ms(10);\n        free(config_lua);\n        is_changed = true;\n        debug(\"\\nSmartport (%u). Change sensorId %i (0x%X)\", uxTaskGetStackHighWaterMark(NULL),\n              config_lua->smartport_sensor_id, smartport_sensor_id_to_crc(config_lua->smartport_sensor_id));\n        return packet;\n    }\n\n    // send config\n    if (frame_id == 0x30 && (uint8_t)value == 0x01) {\n        config_t *config = config_read();\n        uint8_t frame_id_send = 0x32;\n        bool send = true;\n        switch (data_id) {\n            case 0x5101: {\n                packet.value = 0;\n                char version[] = PROJECT_VERSION;\n                memmove(version, version + 1, strlen(version));\n                char *token;\n                token = strtok(version, \" . \");\n                uint cont = 0;\n                while (token != NULL) {\n                    packet.value = packet.value << 8 | atoi(token);\n                    token = strtok(NULL, \" . \");\n                    cont++;\n                }\n                packet.value = packet.value << 8 * (3 - cont);\n                break;\n            }\n            case 0x5103:\n                packet.value = config->esc_protocol;\n                break;\n            case 0x5104:\n                packet.value = config->enable_gps;\n                break;\n            case 0x5105:\n                packet.value = config->gps_baudrate;\n                break;\n            case 0x5106:\n                packet.value = config->enable_analog_voltage;\n                break;\n            case 0x5107:\n                packet.value = config->enable_analog_current;\n                break;\n            case 0x5108:\n                packet.value = config->enable_analog_ntc;\n                break;\n            case 0x5109:\n                packet.value = config->enable_analog_airspeed;\n                break;\n            case 0x510A:\n                packet.value = config->i2c_module;\n                break;\n            case 0x510C:\n                packet.value = ELEMENTS(config->alpha_rpm);\n                break;\n            case 0x510D:\n                packet.value = ELEMENTS(config->alpha_voltage);\n                break;\n            case 0x510E:\n                packet.value = ELEMENTS(config->alpha_current);\n                break;\n            case 0x510F:\n                packet.value = ELEMENTS(config->alpha_temperature);\n                break;\n            case 0x5110:\n                packet.value = ELEMENTS(config->alpha_vario);\n                break;\n            case 0x5111:\n                packet.value = ELEMENTS(config->alpha_airspeed);\n                break;\n            case 0x513A:\n                packet.value = config->refresh_rate_rpm;\n                break;\n            case 0x5113:\n                packet.value = config->refresh_rate_voltage;\n                break;\n            case 0x5114:\n                packet.value = config->refresh_rate_current;\n                break;\n            case 0x5115:\n                packet.value = config->refresh_rate_temperature;\n                break;\n            case 0x5116:\n                packet.value = config->refresh_rate_gps;\n                break;\n            case 0x5117:\n                packet.value = config->refresh_rate_consumption;\n                break;\n            case 0x5118:\n                packet.value = config->refresh_rate_vario;\n                break;\n            case 0x5119:\n                packet.value = config->refresh_rate_airspeed;\n                break;\n            case 0x511A:\n                packet.value = config->refresh_rate_default;\n                break;\n            case 0x511B:\n                packet.value = config->analog_voltage_multiplier * 100;\n                break;\n            case 0x511C:\n                packet.value = config->analog_current_type;\n                break;\n            case 0x511D:\n                packet.value = config->gpio_interval;\n                break;\n            case 0x511E:\n                packet.value = config->analog_current_quiescent_voltage * 100;\n                break;\n            case 0x511F:\n                packet.value = config->analog_current_multiplier * 100;\n                break;\n            case 0x5120:\n                packet.value = config->analog_current_offset * 100;\n                break;\n            case 0x5121:\n                packet.value = config->analog_current_autoffset;\n                break;\n            case 0x5122:\n                packet.value = config->pairOfPoles;\n                break;\n            case 0x5123:\n                packet.value = config->mainTeeth;\n                break;\n            case 0x5124:\n                packet.value = config->pinionTeeth;\n                break;\n            case 0x5125:\n                packet.value = config->rpm_multiplier;\n                break;\n            case 0x5126:\n                packet.value = config->bmp280_filter;\n                break;\n            case 0x5127:\n                packet.value = config->enable_pwm_out;\n                break;\n            case 0x5128:\n                packet.value = config->smartport_sensor_id;\n                break;\n            case 0x512A:\n                packet.value = config->vario_auto_offset;\n                break;\n            case 0x512D:\n                packet.value = config->enable_esc_hw4_init_delay;\n                break;\n            case 0x512E:\n                packet.value = config->esc_hw4_init_delay_duration;\n                break;\n            case 0x512F:\n                packet.value = config->esc_hw4_current_thresold;\n                break;\n            case 0x5130:\n                packet.value = config->esc_hw4_current_max;\n                break;\n            case 0x5131:\n                packet.value = config->esc_hw4_voltage_multiplier * 100000;\n                break;\n            case 0x5132:\n                packet.value = config->esc_hw4_current_multiplier * 100000;\n                break;\n            case 0x5135:\n                packet.value = config->esc_hw4_is_manual_offset;\n                break;\n            case 0x5136:\n                packet.value = config->analog_rate;\n                break;\n            case 0x5138:\n                packet.value = config->gpio_mask;\n                break;\n            case 0x5139:\n                packet.value = config->esc_hw4_offset;\n                break;\n            case 0x513F:\n                packet.value = config->airspeed_offset + 1000;\n                break;\n            case 0x5140:\n                packet.value = config->airspeed_vcc;\n                break;\n            case 0x5141:\n                packet.value = config->fuel_flow_ml_per_pulse * 10000;\n                break;\n            case 0x5142:\n                packet.value = config->enable_fuel_flow;\n                break;\n            case 0x5143:\n                packet.value = config->xgzp68xxd_k;\n                break;\n            case 0x5144:\n                packet.value = config->enable_fuel_pressure;\n                break;\n            case 0x5145:\n                packet.value = config->smart_esc_calc_consumption;\n                break;\n            case 0x5147:\n                packet.value = config->gps_rate;\n                break;\n            case 0x5149:\n                packet.value = config->gps_protocol;\n                break;\n            case 0x514B:\n                packet.value = config->esc_hw4_auto_detect;\n                break;\n            case 0x514C:\n                packet.value = config->mpu6050_acc_scale;\n                break;\n            case 0x514D:\n                packet.value = config->mpu6050_gyro_scale;\n                break;\n            case 0x514E:\n                packet.value = config->mpu6050_gyro_weighting;\n                break;\n            case 0x514F:\n                packet.value = config->enable_gyro;\n                break;\n            case 0x5151:\n                packet.value = config->mpu6050_filter;\n                break;\n            default:\n                send = false;\n                debug(\"\\nSmartport. Unknown request frameId 0x%X dataId 0x%X\", frame_id, data_id);\n                break;\n        }\n        if (send) {\n            packet.frame_id = 0x32;\n            packet.data_id = data_id;\n            return packet;\n        }\n    }\n\n    // receive config\n    if (frame_id == 0x31 && data_id == 0x5201 && value == 0) {\n        config_lua = malloc(sizeof(config_t));\n        memcpy(config_lua, config_read(), sizeof(config_t));\n        debug(\"\\nSmartport. Start saving...\");\n        return packet;\n    }\n    if (frame_id == 0x31 && data_id == 0x5201 && value == 1) {\n        config_write(config_lua);\n        sleep_ms(10);\n        free(config_lua);\n        debug(\"\\nSmartport. Complete save config\");\n        return packet;\n    }\n    if (frame_id == 0x31) {\n        uint8_t frame_id_send = 0x32;\n        bool write = true;\n        switch (data_id) {\n            case 0x5103:\n                config_lua->esc_protocol = value;\n                break;\n            case 0x5104:\n                config_lua->enable_gps = value;\n                break;\n            case 0x5105:\n                config_lua->gps_baudrate = value;\n                break;\n            case 0x5106:\n                config_lua->enable_analog_voltage = value;\n                break;\n            case 0x5107:\n                config_lua->enable_analog_current = value;\n                break;\n            case 0x5108:\n                config_lua->enable_analog_ntc = value;\n                break;\n            case 0x5109:\n                config_lua->enable_analog_airspeed = value;\n                break;\n            case 0x510A:\n                config_lua->i2c_module = value;\n                break;\n            case 0x510C:\n                config_lua->alpha_rpm = ALPHA(value);\n                break;\n            case 0x510D:\n                config_lua->alpha_voltage = ALPHA(value);\n                break;\n            case 0x510E:\n                config_lua->alpha_current = ALPHA(value);\n                break;\n            case 0x510F:\n                config_lua->alpha_temperature = ALPHA(value);\n                break;\n            case 0x5110:\n                config_lua->alpha_vario = ALPHA(value);\n                break;\n            case 0x5111:\n                config_lua->alpha_airspeed = ALPHA(value);\n                break;\n            case 0x513A:\n                config_lua->refresh_rate_rpm = value;\n                break;\n            case 0x5113:\n                config_lua->refresh_rate_voltage = value;\n                break;\n            case 0x5114:\n                config_lua->refresh_rate_current = value;\n                break;\n            case 0x5115:\n                config_lua->refresh_rate_temperature = value;\n                break;\n            case 0x5116:\n                config_lua->refresh_rate_gps = value;\n                break;\n            case 0x5117:\n                config_lua->refresh_rate_consumption = value;\n                break;\n            case 0x5118:\n                config_lua->refresh_rate_vario = value;\n                break;\n            case 0x5119:\n                config_lua->refresh_rate_airspeed = value;\n                break;\n            case 0x511A:\n                config_lua->refresh_rate_default = value;\n                break;\n            case 0x511B:\n                config_lua->analog_voltage_multiplier = value / 100.0;\n                break;\n            case 0x511C:\n                config_lua->analog_current_type = value;\n                break;\n            case 0x511D:\n                config_lua->gpio_interval = value;\n                break;\n            case 0x511E:\n                config_lua->analog_current_quiescent_voltage = value / 100.0;\n                break;\n            case 0x511F:\n                config_lua->analog_current_multiplier = value / 100.0;\n                break;\n            case 0x5120:\n                config_lua->analog_current_offset = value / 100.0;\n                break;\n            case 0x5121:\n                config_lua->analog_current_autoffset = value;\n                break;\n            case 0x5122:\n                config_lua->pairOfPoles = value;\n                break;\n            case 0x5123:\n                config_lua->mainTeeth = value;\n                break;\n            case 0x5124:\n                config_lua->pinionTeeth = value;\n                break;\n            case 0x5125:\n                config_lua->rpm_multiplier = value;\n                break;\n            case 0x5126:\n                config_lua->bmp280_filter = value;\n                break;\n            case 0x5127:\n                config_lua->enable_pwm_out = value;\n                break;\n            case 0x5128:\n                config_lua->smartport_sensor_id = value;\n                break;\n            case 0x5129:\n                config_lua->smartport_data_id = value;\n                break;\n            case 0x512A:\n                config_lua->vario_auto_offset = value;\n                break;\n            case 0x512D:\n                config_lua->enable_esc_hw4_init_delay = value;\n                break;\n            case 0x512E:\n                config_lua->esc_hw4_init_delay_duration = value;\n                break;\n            case 0x512F:\n                config_lua->esc_hw4_current_thresold = value;\n                break;\n            case 0x5130:\n                config_lua->esc_hw4_current_max = value;\n                break;\n            case 0x5131:\n                config_lua->esc_hw4_voltage_multiplier = value / 100000.0;\n                break;\n            case 0x5132:\n                config_lua->esc_hw4_current_multiplier = value / 100000.0;\n                break;\n            case 0x5135:\n                config_lua->esc_hw4_is_manual_offset = value;\n                break;\n            case 0x5136:\n                config_lua->analog_rate = value;\n                break;\n            case 0x5138:\n                config_lua->gpio_mask = value;\n                break;\n            case 0x5139:\n                config_lua->esc_hw4_offset = value;\n                break;\n            case 0x513F:\n                config_lua->airspeed_offset = value - 1000;\n                break;\n            case 0x5140:\n                config_lua->airspeed_vcc = value;\n                break;\n            case 0x5141:\n                config_lua->fuel_flow_ml_per_pulse = value / 10000.0;\n                break;\n            case 0x5142:\n                config_lua->enable_fuel_flow = value;\n                break;\n            case 0x5143:\n                config_lua->xgzp68xxd_k = value;\n                break;\n            case 0x5144:\n                config_lua->enable_fuel_pressure = value;\n                break;\n            case 0x5145:\n                config_lua->smart_esc_calc_consumption = value;\n                break;\n            case 0x5147:\n                config_lua->gps_rate = value;\n                break;\n            case 0x5149:\n                config_lua->gps_protocol = value;\n                break;\n            case 0x514B:\n                config_lua->esc_hw4_auto_detect = value;\n                break;\n            case 0x514C:\n                config_lua->mpu6050_acc_scale = value;\n                break;\n            case 0x514D:\n                config_lua->mpu6050_gyro_scale = value;\n                break;\n            case 0x514E:\n                config_lua->mpu6050_gyro_weighting = value;\n                break;\n            case 0x514F:\n                config_lua->enable_gyro = value;\n                break;\n            case 0x5151:\n                config_lua->mpu6050_filter = value;\n                break;\n            default:\n                debug(\"\\nSmartport. Unknown save request. frameId 0x%X dataId 0x%X\", frame_id, data_id);\n                break;\n        }\n        debug(\"\\nSmartport. Store config. frameId 0x%X dataId 0x%X value %u\", frame_id, data_id, value);\n    }\n    return packet;\n}\n\nint32_t smartport_format(uint16_t data_id, float value) {\n    if ((data_id >= GPS_SPEED_FIRST_ID && data_id <= GPS_SPEED_LAST_ID) ||\n        (data_id >= RBOX_BATT1_FIRST_ID && data_id <= RBOX_BATT2_FIRST_ID))\n        return round(value * 1000);\n\n    if ((data_id >= ALT_FIRST_ID && data_id <= VARIO_LAST_ID) ||\n        (data_id >= VFAS_FIRST_ID && data_id <= VFAS_LAST_ID) ||\n        (data_id >= ACCX_FIRST_ID && data_id <= GPS_ALT_LAST_ID) ||\n        (data_id >= GPS_COURS_FIRST_ID && data_id <= GPS_COURS_LAST_ID) ||\n        (data_id >= A3_FIRST_ID && data_id <= A4_LAST_ID) || data_id == DIY_FIRST_ID + 5)\n        return round(value * 100);\n\n    if ((data_id >= CURR_FIRST_ID && data_id <= CURR_LAST_ID) ||\n        (data_id >= AIR_SPEED_FIRST_ID && data_id <= AIR_SPEED_LAST_ID) || data_id == A1_ID || data_id == A2_ID ||\n        data_id == RXBT_ID)\n        return round(value * 10);\n\n    return round(value);\n}\n\nuint32_t smartport_format_double(uint16_t data_id, float value_l, float value_h) {\n    if ((data_id >= ESC_POWER_FIRST_ID && data_id <= ESC_POWER_LAST_ID) ||\n        (data_id >= SBEC_POWER_FIRST_ID && data_id <= SBEC_POWER_LAST_ID))\n        return (uint32_t)round(value_h * 100) << 16 | (uint16_t)round(value_l * 100);\n\n    if (data_id >= ESC_RPM_CONS_FIRST_ID && data_id <= ESC_RPM_CONS_LAST_ID) {\n        return (uint32_t)round(value_h) << 16 | (uint16_t)round((value_l) / 100);\n    }\n\n    return (uint16_t)round(value_h * 500) << 8 | (uint16_t)value_l;\n}\n\nuint32_t smartport_format_coordinate(coordinate_type_t type, float value) {\n    uint32_t data = 0;\n    if (value < 0) {\n        data |= (uint32_t)1 << 30;\n        value *= -1;\n    }\n    if (type == SMARTPORT_LONGITUDE) {\n        data |= (uint32_t)1 << 31;\n    }\n    data |= (uint32_t)(value * 60 * 10000);  // deg to min * 10000\n    return data;\n}\n\nuint32_t smartport_format_datetime(uint8_t type, uint32_t value) {\n    uint8_t dayHour = value / 10000;\n    uint8_t monthMin = value / 100 - dayHour * 100;\n    uint8_t yearSec = value - (value / 100) * 100;\n    if (type == SMARTPORT_DATE) {\n        return (uint32_t)yearSec << 24 | (uint32_t)monthMin << 16 | dayHour << 8 | 0xFF;\n    }\n    return (uint32_t)dayHour << 24 | (uint32_t)monthMin << 16 | yearSec << 8;\n}\n\nuint32_t smartport_format_cell(uint8_t cell_index, float value) {\n    return cell_index | (uint16_t)round(value * 500) << 8;\n}\n\nuint8_t smartport_get_crc(uint8_t *data, uint len) {\n    uint16_t crc = 0;\n    for (uint8_t i = 2; i < len; i++) {\n        crc += data[i];\n        crc += crc >> 8;\n        crc &= 0x00FF;\n    }\n    return 0xFF - (uint8_t)crc;\n}\n\nvoid smartport_send_byte(uint8_t c, uint16_t *crcp) {\n    if (crcp != NULL) {\n        uint16_t crc = *crcp;\n        crc += c;\n        crc += crc >> 8;\n        crc &= 0x00FF;\n        *crcp = crc;\n    }\n    if (c == 0x7D || c == 0x7E) {\n        uart0_write(c);\n        c ^= 0x20;\n    }\n    uart0_write(c);\n    debug(\"0x%X \", c);\n}\n\nuint8_t smartport_sensor_id_to_crc(uint8_t sensor_id) {\n    if (sensor_id < 1 || sensor_id > 28) {\n        return 0;\n    }\n    return sensor_id_matrix[sensor_id - 1];\n}\n\nuint8_t smartport_sensor_crc_to_id(uint8_t sensor_id_crc) {\n    uint8_t cont = 0;\n    while (sensor_id_crc != sensor_id_matrix[cont] && cont < 28) {\n        cont++;\n    }\n    if (cont == 28) return 0;\n    return cont + 1;\n}\n\nstatic void process(smartport_parameters_t *parameter) {\n    uint lenght = uart0_available();\n    if (lenght) {\n        uint8_t data[lenght];\n        if (context.debug == 2 && lenght != 2) {\n            printf(\"\\n\");\n            debug_buffer2(data, lenght, \"0x%X \");\n        }\n        uart0_read_bytes(data, lenght);\n\n        // send telemetry\n        if (data[0] == 0x7E && data[1] == smartport_sensor_id_to_crc(parameter->sensor_id) && lenght == POLL_LENGHT &&\n            !is_maintenance_mode) {\n            debug(\"\\nSmartport (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(data, POLL_LENGHT, \"0x%X \");\n            xSemaphoreGive(semaphore_sensor);\n            vTaskDelay(4 / portTICK_PERIOD_MS);\n            xSemaphoreTake(semaphore_sensor, 0);\n        }\n\n        // send packet\n        if (data[0] == 0x7E && data[1] == smartport_sensor_id_to_crc(parameter->sensor_id) && lenght == POLL_LENGHT &&\n            is_maintenance_mode) {\n            debug(\"\\nSmartport (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(data, POLL_LENGHT, \"0x%X \");\n            if (uxQueueMessagesWaiting(packet_queue_handle)) xTaskNotifyGive(packet_task_handle);\n        }\n\n        // receive packet\n        if (lenght >= 10) {\n            uint8_t delta = 0;\n            uint i;\n            for (i = 0; i < lenght; i++) {\n                data[i] = data[i + delta];\n                if (data[i] == 0x7D) {\n                    delta++;\n                    data[i] = data[i + delta] ^ 0x20;\n                }\n            }\n            if (i - delta == PACKET_LENGHT) {\n                uint8_t crc = smartport_get_crc(data, PACKET_LENGHT - 1);\n                uint8_t sensor_id = data[1];\n                uint8_t frame_id = data[2];\n                uint16_t data_id = (uint16_t)data[4] << 8 | data[3];\n                // process no telemetry packets\n                if (crc == data[9] && frame_id != 0x10) {\n                    uint value = (uint32_t)data[8] << 24 | (uint32_t)data[7] << 16 | (uint16_t)data[6] << 8 | data[5];\n                    debug(\"\\nSmartport. Received packet (%u) sensorId 0x%X FrameId 0x%X DataId 0x%X Value 0x%X < \",\n                          uxTaskGetStackHighWaterMark(NULL), sensor_id, frame_id, data_id, value);\n                    debug_buffer(data, PACKET_LENGHT, \"0x%X \");\n                    smartport_packet_t packet = smartport_process_packet(parameter, frame_id, data_id, value);\n                    if (packet.data_id != 0) xQueueSendToBack(packet_queue_handle, &packet, 0);\n                }\n            }\n        }\n    }\n}\n\nstatic int64_t reboot_callback(alarm_id_t id, void *user_data) { AIRCR_Register = 0x5FA0004; }\n\nstatic void sensor_task(void *parameters) {\n    smartport_sensor_parameters_t parameter = *(smartport_sensor_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        int32_t data_formatted = smartport_format(parameter.data_id, *parameter.value);\n        debug(\"\\nSmartport. Sensor (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, parameter.data_id, data_formatted);\n    }\n}\n\nstatic void sensor_gpio_task(void *parameters) {\n    smartport_sensor_gpio_parameters_t parameter = *(smartport_sensor_gpio_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint cont = 0;\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (parameter.gpio_mask) {\n            while (!(parameter.gpio_mask & (1 << cont))) {\n                cont++;\n                if (cont == 6) cont = 0;\n            }\n            float value = *parameter.value & (1 << cont) ? 1 : 0;\n            uint16_t data_id = parameter.data_id + 17 + cont;\n            int32_t data_formatted = smartport_format(data_id, value);\n            debug(\"\\nSmartport. Sensor GPIO (%u) > GPIO: %u STATE: %u > \", uxTaskGetStackHighWaterMark(NULL), 17 + cont,\n                  (uint)value);\n            send_packet(0x10, data_id, data_formatted);\n            cont++;\n            if (cont == 6) cont = 0;\n        }\n    }\n}\n\nstatic void sensor_void_task(void *parameters) {\n    while (1) {\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (context.debug == 2) printf(\"\\nSmartport. Sensor void (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0, 0, 0);\n    }\n}\n\nstatic void sensor_double_task(void *parameters) {\n    smartport_sensor_double_parameters_t parameter = *(smartport_sensor_double_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        float v_l = parameter.value_l ? *parameter.value_l : 0.0f;\n        float v_h = parameter.value_h ? *parameter.value_h : 0.0f;\n        uint32_t data_formatted = smartport_format_double(parameter.data_id, v_l, v_h);\n        debug(\"\\nSmartport. Sensor double (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, parameter.data_id, data_formatted);\n    }\n}\n\nstatic void sensor_coordinates_task(void *parameters) {\n    smartport_sensor_coordinate_parameters_t parameter = *(smartport_sensor_coordinate_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        uint32_t data_formatted;\n        if (parameter.type == SMARTPORT_LATITUDE)\n            data_formatted = smartport_format_coordinate(parameter.type, *parameter.latitude);\n        else\n            data_formatted = smartport_format_coordinate(parameter.type, *parameter.longitude);\n        parameter.type = !parameter.type;\n        debug(\"\\nSmartport. Sensor coordinates (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, GPS_LONG_LATI_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_datetime_task(void *parameters) {\n    smartport_sensor_datetime_parameters_t parameter = *(smartport_sensor_datetime_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        uint32_t data_formatted;\n        if (parameter.type == SMARTPORT_DATE)\n            data_formatted = smartport_format_datetime(parameter.type, *parameter.date);\n        else\n            data_formatted = smartport_format_datetime(parameter.type, *parameter.time);\n        parameter.type = !parameter.type;\n        debug(\"\\nSmartport. Sensor datetime (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, GPS_TIME_DATE_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_cell_task(void *parameters) {\n    smartport_sensor_cell_parameters_t parameter = *(smartport_sensor_cell_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t cell_index = 0;\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n        if (!*parameter.cell_count) return;\n        uint32_t data_formatted = smartport_format_cell(cell_index, *parameter.cell_voltage);\n        cell_index++;\n        if (cell_index > *parameter.cell_count - 1) cell_index = 0;\n        debug(\"\\nSmartport. Sensor cell (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, CELLS_FIRST_ID, data_formatted);\n    }\n}\n\nstatic void sensor_cell_individual_task(void *parameters) {\n    smartport_sensor_cell_individual_parameters_t parameter =\n        *(smartport_sensor_cell_individual_parameters_t *)parameters;\n\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t cell_index = 0;\n\n    while (1) {\n        vTaskDelay(parameter.rate / portTICK_PERIOD_MS);\n        xSemaphoreTake(semaphore_sensor, portMAX_DELAY);\n\n        // No cells configured → skip\n        if (!parameter.cell_count || *parameter.cell_count == 0) {\n            continue;\n        }\n\n        float value = 0.0f;\n\n        // Safety: check index and pointer before dereferencing\n        if (cell_index < *parameter.cell_count && parameter.cell_voltage[cell_index] != NULL) {\n            value = *parameter.cell_voltage[cell_index];\n        }\n\n        uint32_t data_formatted = smartport_format_cell(cell_index, value);\n\n        debug(\"\\nSmartport. Sensor cell (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(0x10, CELLS_FIRST_ID, data_formatted);\n\n        // Next cell\n        cell_index++;\n        if (cell_index >= *parameter.cell_count) {\n            cell_index = 0;\n        }\n    }\n}\n\nstatic void packet_task(void *parameters) {\n    uint16_t data_id = *(uint16_t *)parameters;\n    smartport_packet_t packet;\n    while (1) {\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        xQueueReceive(packet_queue_handle, &packet, 0);\n        debug(\"\\nSmartport. Packet (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n        send_packet(packet.frame_id, packet.data_id, packet.value);\n        // vTaskDelay(1500 / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void set_config(smartport_parameters_t *parameter) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    parameter->sensor_id = config->smartport_sensor_id;\n    parameter->data_id = 0x5100;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = NULL;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_task\", STACK_SENSOR_SMARTPORT_DOUBLE, (void *)&parameter_sensor_double,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = NULL;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 1;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 1;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_SMART) {\n        smart_esc_parameters_t parameter;\n        parameter.calc_consumption = config->smart_esc_calc_consumption;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.temperature_bat = malloc(sizeof(float));\n        parameter.current_bat = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        for (uint i = 0; i < 18; i++) parameter.cell[i] = malloc(sizeof(float));\n        parameter.cells = malloc(sizeof(uint8_t));\n        parameter.cycles = malloc(sizeof(uint16_t));\n        xTaskCreate(smart_esc_task, \"smart_esc_task\", STACK_SMART_ESC, (void *)&parameter, 4, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_individual_parameters_t parameter_sensor_cell;\n        // rpm & consumption\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // voltage & current\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // bec. voltage & current\n        parameter_sensor_double.data_id = SBEC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_fet\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_bec\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 1;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // temp_bat\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_bat;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // current_bat\n        parameter_sensor.data_id = CURR_FIRST_ID + 1;\n        parameter_sensor.value = parameter.current_bat;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // cells\n        parameter_sensor_cell.cell_count = parameter.cells;  // Pointer provided by ESC_SMART task\n        for (uint i = 0; i < 18; i++) {\n            parameter_sensor_cell.cell_voltage[i] = parameter.cell[i];  // One pointer per cell\n        }\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n\n        xTaskCreate(sensor_cell_individual_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL,\n                    (void *)&parameter_sensor_cell, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        // cycles\n        /*parameter_sensor.data_id = DIY_FIRST_ID + 100;\n        parameter_sensor.value = parameter.cycles;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_cell_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);*/\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.bec_voltage = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temp_esc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temp_motor;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        smartport_sensor_cell_parameters_t parameter_sensor_cell;\n\n        // RPM and Consumption sensor\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.rpm;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_rpm;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // Main voltage and current sensor\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID;\n        parameter_sensor_double.value_l = parameter.voltage;\n        parameter_sensor_double.value_h = parameter.current;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // FET Temperature sensor\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.temperature_fet;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // BEC Temperature sensor\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID + 2;\n        parameter_sensor.value = parameter.temperature_bec;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // BEC voltage and current sensor\n        parameter_sensor_double.data_id = ESC_POWER_FIRST_ID + 2;\n        parameter_sensor_double.value_l = parameter.voltage_bec;\n        parameter_sensor_double.value_h = parameter.current_bec;\n        parameter_sensor_double.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_double_task, \"sensor_double_task\", STACK_SENSOR_SMARTPORT_DOUBLE,\n                    (void *)&parameter_sensor_double, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        // Cell voltage sensor\n        parameter_sensor_cell.cell_count = parameter.cell_count;\n        parameter_sensor_cell.cell_voltage = parameter.cell_voltage;\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_cell_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL, (void *)&parameter_sensor_cell,\n                    3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(double));\n        parameter.lon = malloc(sizeof(double));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_coordinate_parameters_t parameter_sensor_coordinate;\n        parameter_sensor_coordinate.type = SMARTPORT_LATITUDE;\n        parameter_sensor_coordinate.latitude = parameter.lat;\n        parameter_sensor_coordinate.longitude = parameter.lon;\n        parameter_sensor_coordinate.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_coordinates_task, \"sensor_coordinates_task\", STACK_SENSOR_SMARTPORT,\n                    (void *)&parameter_sensor_coordinate, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_datetime_parameters_t parameter_sensor_datetime;\n        parameter_sensor_datetime.type = SMARTPORT_DATE;\n        parameter_sensor_datetime.date = parameter.date;\n        parameter_sensor_datetime.time = parameter.time;\n        parameter_sensor_datetime.rate = 1000;\n        xTaskCreate(sensor_datetime_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor_datetime, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = GPS_ALT_FIRST_ID;\n        parameter_sensor.value = parameter.alt;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GPS_SPEED_FIRST_ID;\n        parameter_sensor.value = parameter.spd;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GPS_COURS_FIRST_ID;\n        parameter_sensor.value = parameter.cog;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID + 1;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GPS_SATE_FIRST_ID;\n        parameter_sensor.value = parameter.sat;\n        parameter_sensor.rate = 1000;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIST_FIRST_ID;\n        parameter_sensor.value = parameter.dist;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 5;\n        parameter_sensor.value = parameter.pdop;\n        parameter_sensor.rate = config->refresh_rate_gps;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = A3_FIRST_ID;\n        parameter_sensor.value = parameter.voltage;\n        parameter_sensor.rate = config->refresh_rate_voltage;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = CURR_FIRST_ID;\n        parameter_sensor.value = parameter.current;\n        parameter_sensor.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_double_parameters_t parameter_sensor_double;\n        parameter_sensor_double.data_id = ESC_RPM_CONS_FIRST_ID;\n        parameter_sensor_double.value_l = NULL;\n        parameter_sensor_double.value_h = parameter.consumption;\n        parameter_sensor_double.rate = config->refresh_rate_current;\n        xTaskCreate(sensor_double_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor_double, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ALT_FIRST_ID;\n        parameter_sensor.value = parameter.altitude;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = VARIO_FIRST_ID;\n        parameter_sensor.value = parameter.vspeed;\n        parameter_sensor.rate = config->refresh_rate_vario;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature, malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = ESC_TEMPERATURE_FIRST_ID;\n        parameter_sensor.value = parameter.ntc;\n        parameter_sensor.rate = config->refresh_rate_temperature;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = AIR_SPEED_FIRST_ID;\n        parameter_sensor.value = parameter.airspeed;\n        parameter_sensor.rate = config->refresh_rate_airspeed;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_fuel_flow) {\n        fuel_meter_parameters_t parameter = {config->fuel_flow_ml_per_pulse, malloc(sizeof(float)),\n                                             malloc(sizeof(float))};\n        xTaskCreate(fuel_meter_task, \"fuel_meter_task\", STACK_FUEL_METER, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = GASSUIT_FLOW_FIRST_ID;\n        parameter_sensor.value = parameter.consumption_instant;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = GASSUIT_RES_VOL_FIRST_ID;\n        parameter_sensor.value = parameter.consumption_total;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->gpio_mask) {\n        gpio_parameters_t parameter = {config->gpio_mask, config->gpio_interval, malloc(sizeof(float))};\n        xTaskCreate(gpio_task, \"gpio_task\", STACK_GPIO, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n        smartport_sensor_gpio_parameters_t parameter_sensor;\n        parameter_sensor.data_id = DIY_FIRST_ID;\n        parameter_sensor.value = parameter.value;\n        parameter_sensor.rate = config->gpio_interval;\n        parameter_sensor.gpio_mask = config->gpio_mask;\n        xTaskCreate(sensor_gpio_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3,\n                    &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        smartport_sensor_parameters_t parameter_sensor;\n        parameter_sensor.data_id = DIY_FIRST_ID + 6;\n        parameter_sensor.value = parameter.pitch;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 7;\n        parameter_sensor.value = parameter.roll;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 8;\n        parameter_sensor.value = parameter.yaw;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCX_FIRST_ID;\n        parameter_sensor.value = parameter.acc_x;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCY_FIRST_ID;\n        parameter_sensor.value = parameter.acc_y;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = ACCZ_FIRST_ID;\n        parameter_sensor.value = parameter.acc_z;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        parameter_sensor.data_id = DIY_FIRST_ID + 9;\n        parameter_sensor.value = parameter.acc;\n        parameter_sensor.rate = config->refresh_rate_default;\n        xTaskCreate(sensor_task, \"sensor_task\", STACK_SENSOR_SMARTPORT, (void *)&parameter_sensor, 3, &task_handle);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_lipo && config->lipo_cells > 0) {\n        smartport_sensor_cell_individual_parameters_t parameter_sensor_cell;\n        float *cell_prev = NULL;\n\n        // Maximum supported cells: 6 (two INA3221 devices)\n        uint8_t lipo_cells = MIN(config->lipo_cells, 6);\n\n        // Initialize all cell pointers to NULL (safety)\n        for (uint i = 0; i < 18; i++) {\n            parameter_sensor_cell.cell_voltage[i] = NULL;\n        }\n\n        // Configure task parameters\n        parameter_sensor_cell.rate = config->refresh_rate_voltage;\n        parameter_sensor_cell.cell_count = malloc(sizeof(uint8_t));\n        *parameter_sensor_cell.cell_count = lipo_cells;\n\n        // --- First INA3221: cells 0–2 -----------------------------------------\n        uint8_t cells_first = MIN(lipo_cells, 3);\n\n        if (cells_first > 0) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x40,\n                .filter = config->ina3221_filter,\n                .cell_count = cells_first,\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n\n            // First INA has no previous cell reference\n            *parameter.cell_prev = 0;\n            cell_prev = parameter.cell[2];\n\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n            // Store cell pointers for SmartPort\n            for (uint i = 0; i < cells_first; i++) {\n                parameter_sensor_cell.cell_voltage[i] = parameter.cell[i];\n            }\n        }\n\n        // --- Second INA3221: cells 3–5 ----------------------------------------\n        if (lipo_cells > 3) {\n            uint8_t cells_second = MIN((uint8_t)(lipo_cells - 3), (uint8_t)3);\n\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x41,\n                .filter = config->ina3221_filter,\n                .cell_count = cells_second,\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = cell_prev,  // Link to the last cell of the previous INA\n            };\n\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n\n            // Store cell pointers for SmartPort\n            for (uint i = 0; i < cells_second; i++) {\n                parameter_sensor_cell.cell_voltage[i + 3] = parameter.cell[i];\n            }\n        }\n\n        // --- Start SmartPort task: cycle through individual cells -------------\n        xTaskCreate(sensor_cell_individual_task, \"sensor_cell_task\", STACK_SENSOR_SMARTPORT_CELL,\n                    (void *)&parameter_sensor_cell, 3, &task_handle);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n}\n\nstatic void send_packet(uint8_t frame_id, uint16_t data_id, uint32_t value) {\n    uint16_t crc = 0;\n    uint8_t *u8p;\n    // frame_id\n    smartport_send_byte(frame_id, &crc);\n    // data_id\n    u8p = (uint8_t *)&data_id;\n    smartport_send_byte(u8p[0], &crc);\n    smartport_send_byte(u8p[1], &crc);\n    // value\n    u8p = (uint8_t *)&value;\n    smartport_send_byte(u8p[0], &crc);\n    smartport_send_byte(u8p[1], &crc);\n    smartport_send_byte(u8p[2], &crc);\n    smartport_send_byte(u8p[3], &crc);\n    // crc\n    smartport_send_byte(0xFF - (uint8_t)crc, NULL);\n    // blink\n    vTaskResume(context.led_task_handle);\n}\n"
  },
  {
    "path": "board/project/protocol/smartport.h",
    "content": "#ifndef SMARTPORT_H\n#define SMARTPORT_H\n\n#include \"common.h\"\n\n// FrSky Smartport Data Id\n\n#define ALT_FIRST_ID 0x0100  // 100 m\n#define ALT_LAST_ID 0x010f\n#define VARIO_FIRST_ID 0x0110  // 100 m/s\n#define VARIO_LAST_ID 0x011f\n#define CURR_FIRST_ID 0x0200  // 10 A\n#define CURR_LAST_ID 0x020f\n#define VFAS_FIRST_ID 0x0210  // 100 v\n#define VFAS_LAST_ID 0x021f\n#define CELLS_FIRST_ID 0x0300  //\n#define CELLS_LAST_ID 0x030f\n#define T1_FIRST_ID 0x0400  // 1 C\n#define T1_LAST_ID 0x040f\n#define T2_FIRST_ID 0x0410  // 1 C\n#define T2_LAST_ID 0x041f\n#define DIST_FIRST_ID 0x0420\n#define DIST_LAST_ID 0x042f\n#define RPM_FIRST_ID 0x0500  // 1 rpm\n#define RPM_LAST_ID 0x050f\n#define FUEL_FIRST_ID 0x0600  // 1 %\n#define FUEL_LAST_ID 0x060f\n#define ACCX_FIRST_ID 0x0700  // 100 g\n#define ACCX_LAST_ID 0x070f\n#define ACCY_FIRST_ID 0x0710  // 100 g\n#define ACCY_LAST_ID 0x071f\n#define ACCZ_FIRST_ID 0x0720  // 100 g\n#define ACCZ_LAST_ID 0x072f\n#define GPS_LONG_LATI_FIRST_ID 0x0800  // bit32(1<<31)=1=LON:=0=LAT, bit31(1<<30)=1=-:=0=+, escaler: 10000\n#define GPS_LONG_LATI_LAST_ID 0x080f\n#define GPS_ALT_FIRST_ID 0x0820  // 100 m\n#define GPS_ALT_LAST_ID 0x082f\n#define GPS_SPEED_FIRST_ID 0x0830  // 1000 kts\n#define GPS_SPEED_LAST_ID 0x083f\n#define GPS_COURS_FIRST_ID 0x0840  // 100 º\n#define GPS_COURS_LAST_ID 0x084f\n#define GPS_TIME_DATE_FIRST_ID 0x0850  // Date: Y M D 0xFF or Time: H M S 0x00\n#define GPS_TIME_DATE_LAST_ID 0x085f\n#define GPS_SATE_FIRST_ID 0x0860  // Satellites, max 32\n#define GPS_SATE_LAST_ID 0x086f\n#define A3_FIRST_ID 0x0900  // 100 v\n#define A3_LAST_ID 0x090f\n#define A4_FIRST_ID 0x0910  // 100 v\n#define A4_LAST_ID 0x091f\n#define AIR_SPEED_FIRST_ID 0x0a00  // 10 kts\n#define AIR_SPEED_LAST_ID 0x0a0f\n#define RBOX_BATT1_FIRST_ID 0x0b00  // 1000 v, 100 A\n#define RBOX_BATT1_LAST_ID 0x0b0f\n#define RBOX_BATT2_FIRST_ID 0x0b10  // 1000 v, 100 A\n#define RBOX_BATT2_LAST_ID 0x0b1f\n#define RBOX_STATE_FIRST_ID 0x0b20  // 1\n#define RBOX_STATE_LAST_ID 0x0b2f\n#define RBOX_CNSP_FIRST_ID 0x0b30  // 1 mAh (1), 1mAh (2)\n#define RBOX_CNSP_LAST_ID 0x0b3f\n#define SD1_FIRST_ID 0x0b40\n#define SD1_LAST_ID 0x0b4f\n#define ESC_POWER_FIRST_ID 0x0b50  // bytes 1,2: 100 V,  bytes 3,4: 100 A\n#define ESC_POWER_LAST_ID 0x0b5f\n#define ESC_RPM_CONS_FIRST_ID 0x0b60  // bytes 1,2: 0.01 rpm,  bytes 3,4: 1 mah\n#define ESC_RPM_CONS_LAST_ID 0x0b6f\n#define ESC_TEMPERATURE_FIRST_ID 0x0b70  // 1 C\n#define ESC_TEMPERATURE_LAST_ID 0x0b7f\n#define X8R_FIRST_ID 0x0c20\n#define X8R_LAST_ID 0x0c2f\n#define S6R_FIRST_ID 0x0c30\n#define S6R_LAST_ID 0x0c3f\n#define GASSUIT_TEMP1_FIRST_ID 0x0d00  // 1 C\n#define GASSUIT_TEMP1_LAST_ID 0x0d0f\n#define GASSUIT_TEMP2_FIRST_ID 0x0d10  // 1 C\n#define GASSUIT_TEMP2_LAST_ID 0x0d1f\n#define GASSUIT_SPEED_FIRST_ID 0x0d20  // 1 rpm\n#define GASSUIT_SPEED_LAST_ID 0x0d2f\n#define GASSUIT_RES_VOL_FIRST_ID 0x0d30  // 1 ml\n#define GASSUIT_RES_VOL_LAST_ID 0x0d3f\n#define GASSUIT_RES_PERC_FIRST_ID 0x0d40  // 1 %\n#define GASSUIT_RES_PERC_LAST_ID 0x0d4f\n#define GASSUIT_FLOW_FIRST_ID 0x0d50  // 1 ml/min\n#define GASSUIT_FLOW_LAST_ID 0x0d5f\n#define GASSUIT_MAX_FLOW_FIRST_ID 0x0d60  // 1 ml/min\n#define GASSUIT_MAX_FLOW_LAST_ID 0x0d6f\n#define GASSUIT_AVG_FLOW_FIRST_ID 0x0d70  // 1 ml/min\n#define GASSUIT_AVG_FLOW_LAST_ID 0x0d7f\n#define SBEC_POWER_FIRST_ID 0x0e50  // bytes 1,2: 100 V,  bytes 3,4: 100 A\n#define SBEC_POWER_LAST_ID 0x0e5f\n#define DIY_FIRST_ID 0x5100\n#define DIY_LAST_ID 0x52ff\n#define DIY_STREAM_FIRST_ID 0x5000\n#define DIY_STREAM_LAST_ID 0x50ff\n#define FACT_TEST_ID 0xf000\n#define RSSI_ID 0xf101\n#define A1_ID 0xf102  // 10 v\n#define A2_ID 0xf103  // 10 v\n#define SP2UART_A_ID 0xfd00\n#define SP2UART_B_ID 0xfd01\n#define RXBT_ID 0xf104  // 10 v\n#define RAS_ID 0xf105\n#define XJT_VERSION_ID 0xf106\n#define FUEL_QTY_FIRST_ID 0x0a10  // 100 ml\n#define FUEL_QTY_LAST_ID 0x0a1f\n\n#define TIMEOUT_US 500\n\ntypedef enum coordinate_type_t {\n    SMARTPORT_LATITUDE,\n    SMARTPORT_LONGITUDE,\n} coordinate_type_t;\n\ntypedef enum datetime_type_t {\n    SMARTPORT_DATE,\n    SMARTPORT_TIME,\n} datetime_type_t;\n\ntypedef struct smartport_parameters_t {\n    uint8_t sensor_id;\n    uint16_t data_id;\n} smartport_parameters_t;\n\ntypedef struct smartport_sensor_parameters_t {\n    uint16_t data_id;\n    float *value;\n    uint16_t rate;\n} smartport_sensor_parameters_t;\n\ntypedef struct smartport_sensor_gpio_parameters_t {\n    uint16_t data_id;\n    uint8_t gpio_mask;\n    uint8_t *value;\n    uint16_t rate;\n} smartport_sensor_gpio_parameters_t;\n\ntypedef struct smartport_sensor_double_parameters_t {\n    uint16_t data_id;\n    float *value_l;\n    float *value_h;\n    uint16_t rate;\n} smartport_sensor_double_parameters_t;\n\ntypedef struct smartport_sensor_coordinate_parameters_t {\n    coordinate_type_t type;\n    float *latitude;\n    float *longitude;\n    uint16_t rate;\n} smartport_sensor_coordinate_parameters_t;\n\ntypedef struct smartport_sensor_datetime_parameters_t {\n    datetime_type_t type;\n    float *date;\n    float *time;\n    uint16_t rate;\n} smartport_sensor_datetime_parameters_t;\n\ntypedef struct smartport_sensor_cell_parameters_t {\n    uint8_t *cell_count;\n    float *cell_voltage;\n    uint16_t rate;\n} smartport_sensor_cell_parameters_t;\n\ntypedef struct smartport_sensor_cell_individual_parameters_t {\n    uint8_t *cell_count;\n    float *cell_voltage[18];\n    uint16_t rate;\n} smartport_sensor_cell_individual_parameters_t;\n\ntypedef struct smartport_packet_parameters_t {\n    uint16_t data_id;\n    QueueHandle_t queue_handle;\n} smartport_packet_parameters_t;\n\ntypedef struct smartport_packet_t {\n    uint16_t frame_id;\n    uint16_t data_id;\n    uint32_t value;\n} smartport_packet_t;\n\nextern context_t context;\n\nvoid smartport_task(void *parameters);\nint32_t smartport_format(uint16_t data_id, float value);\nuint32_t smartport_format_double(uint16_t data_id, float value_l, float value_h);\nuint32_t smartport_format_coordinate(coordinate_type_t type, float value);\nuint32_t smartport_format_datetime(uint8_t type, uint32_t value);\nuint32_t smartport_format_cell(uint8_t cell_index, float value);\nuint8_t smartport_get_crc(uint8_t *data, uint len);\nsmartport_packet_t smartport_process_packet(smartport_parameters_t *parameter, uint8_t frame_id, uint16_t data_id,\n                                            uint32_t value);\nvoid smartport_send_byte(uint8_t c, uint16_t *crcp);\nuint8_t smartport_sensor_id_to_crc(uint8_t sensor_id);\nuint8_t smartport_sensor_crc_to_id(uint8_t sensor_id_crc);\n#endif"
  },
  {
    "path": "board/project/protocol/srxl.c",
    "content": "#include \"srxl.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gps.h\"\n#include \"hardware/i2c.h\"\n#include \"hardware/irq.h\"\n#include \"i2c_multi.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pico/stdlib.h\"\n#include \"pwm_out.h\"\n#include \"string.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n#include \"xgzp68xxd.h\"\n\n#define SRXL_HEADER 0xA5\n#define SRXL_FRAMELEN 18\n#define SRXL_TIMEOUT_US 1000\n\nstatic void process(void);\nstatic void send_packet(void);\nstatic void set_config(void);\n\nvoid srxl_task(void *parameters) {\n\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n\n    uart0_begin(115200, UART_RECEIVER_TX, UART_RECEIVER_RX, SRXL_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    xbus_set_config();\n    debug(\"\\nSRXL init\");\n    while (1) {\n        // ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process();\n    }\n}\n\nuint16_t srxl_get_crc(uint8_t *buffer, uint8_t length) {\n    uint16_t crc = 0;\n    for (uint8_t i = 0; i < length; ++i) {\n        crc = srxl_crc16(crc, buffer[i]);\n    }\n    return crc;\n}\n\nuint16_t srxl_crc16(uint16_t crc, uint8_t data) {\n    crc = crc ^ ((uint16_t)data << 8);\n    for (int i = 0; i < 8; ++i) {\n        if (crc & 0x8000)\n            crc = (crc << 1) ^ 0x1021;\n        else\n            crc = crc << 1;\n    }\n    return crc;\n}\n\nuint srxl_sensors_count(void) {\n    uint count = 0;\n    for (uint i = 0; i <= XBUS_STRU_TELE_DIGITAL_AIR; i++) {\n        if (sensor.is_enabled[i]) count++;\n    }\n    return count;\n}\n\nstatic void process(void) {\n    static bool mute = true;\n    uint8_t length = uart0_available();\n    if (length) {\n        uint8_t data[length];\n        uart0_read_bytes(data, length);\n        debug(\"\\nSRXL BUFFER:\");\n        debug_buffer(data, length, \" 0x%X\");\n        if (length == SRXL_FRAMELEN) {\n            // uint8_t data[SRXL_FRAMELEN];\n            if (data[0] == SRXL_HEADER) {\n                debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n                debug_buffer(data, SRXL_FRAMELEN, \"0x%X \");\n                if (!mute) send_packet();\n                mute = !mute;\n            }\n        }\n    }\n}\n\nstatic void send_packet(void) {\n    static uint cont = 0;\n    if (!srxl_sensors_count()) return;\n    while (!sensor.is_enabled[cont]) {\n        cont++;\n        if (cont > XBUS_ENERGY) cont = 0;\n    }\n    uint8_t buffer_header[3] = {SRXL_HEADER, 0x80, 0x15};\n    uart0_write_bytes(buffer_header, 3);\n    debug(\"\\nSRXL (%u) > %X %X %X\", uxTaskGetStackHighWaterMark(NULL), buffer_header[0], buffer_header[1], buffer_header[2]);\n    uint8_t buffer[16];\n    switch (cont) {\n        case XBUS_AIRSPEED:\n            xbus_format_sensor(XBUS_AIRSPEED_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_airspeed_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_airspeed_t), \"0x%X \");\n            break;\n        case XBUS_BATTERY:\n            xbus_format_sensor(XBUS_BATTERY_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_battery_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_battery_t), \"0x%X \");\n            break;\n        case XBUS_ESC:\n            xbus_format_sensor(XBUS_ESC_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_esc_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_esc_t), \"0x%X \");\n            break;\n        case XBUS_GPS_LOC:\n            xbus_format_sensor(XBUS_GPS_LOC_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_gps_loc_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_gps_loc_t), \"0x%X \");\n            break;\n        case XBUS_GPS_STAT:\n            xbus_format_sensor(XBUS_GPS_STAT_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_gps_stat_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_gps_stat_t), \"0x%X \");\n            break;\n        case XBUS_RPMVOLTTEMP:\n            xbus_format_sensor(XBUS_RPMVOLTTEMP_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_rpm_volt_temp_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_rpm_volt_temp_t), \"0x%X \");\n            break;\n        case XBUS_FUEL_FLOW:\n            xbus_format_sensor(XBUS_FUEL_FLOW_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_fuel_flow_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_fuel_flow_t), \"0x%X \");\n            break;\n        case XBUS_STRU_TELE_DIGITAL_AIR:\n            xbus_format_sensor(XBUS_STRU_TELE_DIGITAL_AIR_ID, buffer);\n            uart0_write_bytes(buffer, sizeof(xbus_stru_tele_digital_air_t));\n            debug(\"\\nSRXL (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(buffer, sizeof(xbus_stru_tele_digital_air_t), \"0x%X \");\n            break;\n    }\n    uint16_t crc;\n    crc = __builtin_bswap16(srxl_get_crc(buffer, 19));  // all bytes, including header\n    uart0_write_bytes((uint8_t *)&crc, 2);\n    debug(\"%X \", crc);\n    cont++;\n    vTaskResume(context.led_task_handle);\n}\n"
  },
  {
    "path": "board/project/protocol/srxl.h",
    "content": "#ifndef SRXL_H\n#define SRXL_H\n\n#include \"common.h\"\n#include \"xbus.h\"\n\nextern context_t context;\nextern xbus_sensor_t sensor;\n\nvoid srxl_task(void *parameters);\nuint16_t srxl_get_crc(uint8_t *buffer, uint8_t length);\nuint16_t srxl_crc16(uint16_t crc, uint8_t data);\nuint srxl_sensors_count(void);\n\n#endif"
  },
  {
    "path": "board/project/protocol/srxl2.c",
    "content": "#include \"srxl2.h\"\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gps.h\"\n#include \"hardware/i2c.h\"\n#include \"hardware/irq.h\"\n#include \"i2c_multi.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pico/stdlib.h\"\n#include \"pwm_out.h\"\n#include \"srxl.h\"\n#include \"string.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n#include \"xgzp68xxd.h\"\n\n#define SRXL2_DEVICE_PRIORITY 10\n#define SRXL2_DEVICE_BAUDRATE 1  // 0 = 115200. 1 = 400000\n#define SRXL2_DEVICE_INFO 0\n#define SRXL2_DEVICE_UID 0x12345678\n#define MAX_BAD_FRAMES 50\n\nstatic volatile uint8_t dest_id = 0xFF;\nstatic volatile uint8_t baudrate = 0;\nstatic alarm_id_t alarm_id;\nstatic volatile bool send_handshake = false;\nstatic uint8_t sensor_id_;\n\nstatic void process(void);\nstatic void send_packet(void);\nstatic void set_config(void);\nstatic int64_t alarm_50ms(alarm_id_t id, void *user_data);\n\nvoid srxl2_task(void *parameters) {\n    alarm_id = add_alarm_in_us(50000, alarm_50ms, NULL, true);\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n\n    uart0_begin(115200, UART_RECEIVER_TX, UART_RECEIVER_RX, SRXL2_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    config_t *config = config_read();\n    sensor_id_ = config->sensor_id_srxl2 | 0x30;  // Ensure it's between 0x31 and 0x3F\n    if (sensor_id_ < 0x31 || sensor_id_ > 0x3F) sensor_id_ = 0x31;\n    xbus_set_config();\n\n    debug(\"\\nSRXL2 init\");\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process();\n        if (send_handshake) {\n            send_handshake = false;\n            srxl2_send_handshake(uart0, sensor_id_, 0, SRXL2_DEVICE_PRIORITY, SRXL2_DEVICE_BAUDRATE, SRXL2_DEVICE_INFO,\n                                 SRXL2_DEVICE_UID);\n        }\n    }\n}\n\nvoid srxl2_send_handshake(uart_inst_t *uart, uint8_t source_id, uint8_t dest_id, uint8_t priority, uint8_t baudrate,\n                          uint8_t info, uint uid) {\n    srxl2_handshake_t handshake;\n    handshake.header = SRXL2_HEADER;\n    handshake.type = SRXL2_PACKET_TYPE_HANDSHAKE;\n    handshake.len = SRXL2_HANDSHAKE_LEN;\n    handshake.source_id = source_id;\n    handshake.dest_id = dest_id;\n    handshake.priority = priority;\n    handshake.baudrate = baudrate;\n    handshake.info = info;\n    handshake.uid = uid;\n    uint16_t crc = srxl_get_crc((uint8_t *)&handshake, SRXL2_HANDSHAKE_LEN - 2);\n    handshake.crc = swap_16(crc);\n    if (uart_get_index(uart))\n        uart1_write_bytes((uint8_t *)&handshake, SRXL2_HANDSHAKE_LEN);\n    else\n        uart0_write_bytes((uint8_t *)&handshake, SRXL2_HANDSHAKE_LEN);\n    debug(\"\\nSRXL2 (%u). Send Handshake >\", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer((uint8_t *)&handshake, SRXL2_HANDSHAKE_LEN, \" 0x%X\");\n}\n\nstatic void process(void) {\n    uint8_t length = uart0_available();\n    if (length) {\n        cancel_alarm(alarm_id);\n        alarm_id = add_alarm_in_us(50000, alarm_50ms, NULL, true);\n\n        // Check for bad frames\n        static uint bad_frames = 0;\n        if (bad_frames > MAX_BAD_FRAMES) {\n            if (baudrate) {\n                uart_set_baudrate(uart0, 115200);\n                baudrate = 0;\n                debug(\"\\nSRXL2. Autobaud 115200\");\n            } else {\n                uart_set_baudrate(uart0, 400000);\n                baudrate = 1;\n                debug(\"\\nSRXL2. Autobaud 400000\");\n            }\n            bad_frames = 0;\n            return;\n        }\n\n        if (length < 3 || length > 128) {\n            if (length == 1) return;\n            debug(\"\\nSRXL2. Bad Fr %u (len %u)\", bad_frames, length);\n            bad_frames++;\n            return;\n        }\n        uint8_t data[length];\n        uint16_t crc;\n        uart0_read_bytes(data, length);\n        debug(\"\\nSRXL2 (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n        debug_buffer(data, length, \" 0x%X\");\n        crc = srxl_get_crc(data, length - 2);\n        if ((crc >> 8) == data[length - 2] && (crc & 0xFF) == data[length - 1]) {\n            debug(\" -> CRC OK\");\n        } else {\n            debug(\" -> BAD CRC\");\n            debug(\" %X\", crc);\n            // Allow packets with wrong crc for handshake\n            if (dest_id != 0xFF ||\n                !(data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_HANDSHAKE && data[4] == sensor_id_)) {\n                bad_frames++;\n                return;\n            }\n        }\n        bad_frames = 0;\n\n        // Handshake received\n        if (data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_HANDSHAKE && data[4] == sensor_id_) {\n            dest_id = data[3];\n            debug(\"\\nSRXL2. Set dest_id 0x%X\", dest_id);\n            srxl2_send_handshake(uart0, sensor_id_, dest_id, SRXL2_DEVICE_PRIORITY, SRXL2_DEVICE_BAUDRATE,\n                                 SRXL2_DEVICE_INFO, SRXL2_DEVICE_UID);\n\n        }\n        // Send telemetry\n        else if (data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_CONTROL && data[4] == sensor_id_) {\n            send_packet();\n        }\n        // Set baudrate\n        else if (data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_HANDSHAKE && data[4] == 0xFF) {\n            baudrate = data[6];\n            if (baudrate)\n                uart_set_baudrate(uart0, 400000);\n            else\n                uart_set_baudrate(uart0, 115200);\n            debug(\"\\nSRXL2. Set baudrate %u\", baudrate);\n        }\n    }\n}\n\nstatic void send_packet(void) {\n    static uint cont = 0;\n    if (!srxl_sensors_count()) return;\n    while (!sensor.is_enabled[cont]) {\n        cont++;\n        if (cont > XBUS_STRU_TELE_DIGITAL_AIR) cont = 0;\n    }\n    srxl2_telemetry_t packet = {0};\n    packet.header = SRXL2_HEADER;\n    packet.type = SRXL2_PACKET_TYPE_TELEMETRY;\n    packet.len = SRXL2_TELEMETRY_LEN;\n    packet.dest_id = dest_id;\n    uint8_t buffer[16];\n    switch (cont) {\n        case XBUS_AIRSPEED:\n            xbus_format_sensor(XBUS_AIRSPEED_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, (uint8_t *)buffer, sizeof(xbus_airspeed_t));\n            break;\n        case XBUS_BATTERY:\n            xbus_format_sensor(XBUS_BATTERY_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, (uint8_t *)buffer, sizeof(xbus_battery_t));\n            break;\n        case XBUS_ESC:\n            xbus_format_sensor(XBUS_ESC_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, (uint8_t *)buffer, sizeof(xbus_esc_t));\n            break;\n        case XBUS_GPS_LOC:\n            xbus_format_sensor(XBUS_GPS_LOC_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, (uint8_t *)buffer, sizeof(xbus_gps_loc_t));\n            break;\n        case XBUS_GPS_STAT:\n            xbus_format_sensor(XBUS_GPS_STAT_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, (uint8_t *)buffer, sizeof(xbus_gps_stat_t));\n            break;\n        case XBUS_RPMVOLTTEMP:\n            xbus_format_sensor(XBUS_RPMVOLTTEMP_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, (uint8_t *)buffer, sizeof(xbus_rpm_volt_temp_t));\n            break;\n        case XBUS_FUEL_FLOW:\n            xbus_format_sensor(XBUS_FUEL_FLOW_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, (uint8_t *)buffer, sizeof(xbus_fuel_flow_t));\n            break;\n        case XBUS_STRU_TELE_DIGITAL_AIR:\n            xbus_format_sensor(XBUS_STRU_TELE_DIGITAL_AIR_ID, buffer);\n            memcpy((uint8_t *)&packet.xbus_packet, buffer, sizeof(xbus_stru_tele_digital_air_t));\n            break;\n    }\n    uint16_t crc = srxl_get_crc((uint8_t *)&packet, SRXL2_TELEMETRY_LEN - 2);\n    packet.crc = swap_16(crc);\n    uart0_write_bytes((uint8_t *)&packet, SRXL2_TELEMETRY_LEN);\n    cont++;\n    debug(\"\\nSRXL2 (%u) > \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer((uint8_t *)&packet, SRXL2_TELEMETRY_LEN, \"0x%X \");\n    vTaskResume(context.led_task_handle);\n}\n\nstatic int64_t alarm_50ms(alarm_id_t id, void *user_data) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    send_handshake = true;\n    if (baudrate) uart_set_baudrate(uart0, 115200);\n    baudrate = 0;\n    vTaskNotifyGiveIndexedFromISR(context.uart0_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    return 50000;\n}\n"
  },
  {
    "path": "board/project/protocol/srxl2.h",
    "content": "#ifndef SRXL2_H\n#define SRXL2_H\n\n#include \"common.h\"\n#include \"xbus.h\"\n\n#define SRXL2_TIMEOUT_US 500\n#define SRXL2_HEADER 0xA6\n#define SRXL2_HANDSHAKE_LEN 14\n#define SRXL2_TELEMETRY_LEN 22\n#define SRXL2_CONTROL_CMD_CHANNEL 0x00       // channel data\n#define SRXL2_PACKET_TYPE_HANDSHAKE 0x21\n#define SRXL2_PACKET_TYPE_CONTROL 0xCD\n#define SRXL2_PACKET_TYPE_TELEMETRY 0x80\n\nextern context_t context;\nextern xbus_sensor_t sensor;\n\ntypedef struct srxl2_handshake_t {\n    uint8_t header;\n    uint8_t type;\n    uint8_t len;\n    uint8_t source_id;\n    uint8_t dest_id;\n    uint8_t priority;\n    uint8_t baudrate;\n    uint8_t info;\n    uint32_t uid;\n    uint16_t crc;\n} __attribute__((packed)) srxl2_handshake_t;\n\ntypedef struct srxl2_telemetry_t {\n    uint8_t header;\n    uint8_t type;\n    uint8_t len;\n    uint8_t dest_id;\n    uint8_t xbus_packet[16];\n    uint16_t crc;\n} __attribute__((packed)) srxl2_telemetry_t;\n\nvoid srxl2_task(void *parameters);\nvoid srxl2_send_handshake(uart_inst_t *uart, uint8_t source_id, uint8_t dest_id, uint8_t priority, uint8_t baudrate,\n                          uint8_t info, uint uid);\n\n#endif"
  },
  {
    "path": "board/project/protocol/xbus.c",
    "content": "#include \"xbus.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"airspeed.h\"\n#include \"bmp180.h\"\n#include \"bmp280.h\"\n#include \"config.h\"\n#include \"current.h\"\n#include \"esc_apd_f.h\"\n#include \"esc_apd_hv.h\"\n#include \"esc_castle.h\"\n#include \"esc_hw3.h\"\n#include \"esc_hw4.h\"\n#include \"esc_hw5.h\"\n#include \"esc_kontronik.h\"\n#include \"esc_omp_m4.h\"\n#include \"esc_openyge.h\"\n#include \"esc_pwm.h\"\n#include \"esc_ztw.h\"\n#include \"fuel_meter.h\"\n#include \"gps.h\"\n#include \"hardware/i2c.h\"\n#include \"hardware/irq.h\"\n#include \"i2c_multi.h\"\n#include \"ina3221.h\"\n#include \"mpu6050.h\"\n#include \"ms5611.h\"\n#include \"ntc.h\"\n#include \"pico/stdlib.h\"\n#include \"pwm_out.h\"\n#include \"stdlib.h\"\n#include \"string.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n#include \"voltage.h\"\n#include \"xgzp68xxd.h\"\n\n#define XBUS_GPS_FIX (1 << 0)\n#define XBUS_GPS_3D (1 << 1)\n#define XBUS_GPS_HOME (1 << 4)\n\nstatic void i2c_request_handler(uint8_t address);\n// static void set_config(void);\nstatic uint8_t bcd8(float value, uint8_t precision);\nstatic uint16_t bcd16(float value, uint8_t precision);\nstatic uint32_t bcd32(float value, uint8_t precision);\nstatic int64_t interval_250_callback(alarm_id_t id, void *parameters);\nstatic int64_t interval_500_callback(alarm_id_t id, void *parameters);\nstatic int64_t interval_1000_callback(alarm_id_t id, void *parameters);\nstatic int64_t interval_1500_callback(alarm_id_t id, void *parameters);\nstatic int64_t interval_2000_callback(alarm_id_t id, void *parameters);\nstatic int64_t interval_3000_callback(alarm_id_t id, void *parameters);\n\nxbus_sensor_t sensor;\nstatic volatile int16_t delta_0250ms, delta_0500ms, delta_1000ms, delta_1500ms, delta_2000ms, delta_3000ms;\n\nvoid xbus_i2c_handler(uint8_t address) { i2c_request_handler(address); }\n\nvoid xbus_task(void *parameters) {\n    context.led_cycle_duration = 6;\n    context.led_cycles = 1;\n\n    PIO pio = pio1;\n    uint pin = I2C1_SDA_GPIO;\n\n    i2c_multi_init(pio, pin);\n    i2c_multi_set_request_handler(i2c_request_handler);\n\n    xbus_set_config();\n\n    debug(\"\\nXBUS init\");\n\n    vTaskSuspend(NULL);\n}\n\nvoid xbus_format_sensor(uint8_t address, uint8_t *buffer) {\n    static float alt = 0;\n    switch (address) {\n        case XBUS_AIRSPEED_ID: {\n            xbus_airspeed_t airspeed;\n            airspeed.identifier = XBUS_AIRSPEED_ID;\n            airspeed.airspeed = swap_16((uint16_t)(*sensor.airspeed[XBUS_AIRSPEED_AIRSPEED]));\n            if (swap_16((uint16_t)(airspeed.airspeed)) > swap_16((uint16_t)(airspeed.max_airspeed)))\n                airspeed.max_airspeed = airspeed.airspeed;\n            memcpy(buffer, &airspeed, sizeof(xbus_airspeed_t));\n            break;\n        }\n        case XBUS_GPS_LOC_ID: {\n            xbus_gps_loc_t gps_loc;\n            gps_loc.identifier = XBUS_GPS_LOC_ID;\n            uint8_t gps_flags = 0;\n            float lat = *sensor.gps_loc->latitude;\n            if (lat < 0)  // N=1,+, S=0,-\n                lat *= -1;\n            else\n                gps_flags |= 1 << XBUS_GPS_INFO_FLAGS_IS_NORTH_BIT;\n            uint deg = lat;\n            float min = (lat - deg) * 60;\n            gps_loc.latitude = ((uint32_t)bcd8(deg, 0) << 24) | bcd32(min, 4);\n            float lon = *sensor.gps_loc->longitude;\n            if (lon < 0)  // E=1,+, W=0,-\n                lon *= -1;\n            else\n                gps_flags |= 1 << XBUS_GPS_INFO_FLAGS_IS_EAST_BIT;\n            if (lon >= 100) {\n                gps_flags |= 1 << XBUS_GPS_INFO_FLAGS_LONG_GREATER_99_BIT;\n                lon -= 100;\n            }\n            deg = lon;\n            min = (lon - deg) * 60;\n            gps_loc.longitude = ((uint32_t)bcd8(deg, 0) << 24) | bcd32(min, 4);\n            gps_loc.course = bcd16(*sensor.gps_loc->course, 1);\n            gps_loc.hdop = bcd8(*sensor.gps_loc->hdop, 1);\n            alt = *sensor.gps_loc->altitude_low;\n            if (alt < 0) {\n                gps_flags |= 1 << XBUS_GPS_INFO_FLAGS_NEGATIVE_ALT_BIT;\n                alt *= -1;\n            }\n            gps_loc.gps_flags = gps_flags;\n            gps_loc.altitude_low = bcd16(fmod(alt, 1000), 1);\n            gps_loc.gps_flags |= (*sensor.gps_stat[XBUS_GPS_STAT_FIX_TYPE] > 0 ? 1 : 0) << XBUS_GPS_FIX;\n            gps_loc.gps_flags |= (*sensor.gps_stat[XBUS_GPS_STAT_HOME_SET] > 0 ? 1 : 0) << XBUS_GPS_HOME;\n            gps_loc.gps_flags |= (*sensor.gps_stat[XBUS_GPS_STAT_FIX_TYPE] == 2 ? 1 : 0) << XBUS_GPS_3D;\n            memcpy(buffer, &gps_loc, sizeof(xbus_gps_loc_t));\n            break;\n        }\n        case XBUS_GPS_STAT_ID: {\n            xbus_gps_stat_t gps_stat;\n            gps_stat.identifier = XBUS_GPS_STAT_ID;\n            gps_stat.speed = bcd16(*sensor.gps_stat[XBUS_GPS_STAT_SPEED], 1);\n            gps_stat.utc = bcd32(*sensor.gps_stat[XBUS_GPS_STAT_TIME], 0) << 8;\n            gps_stat.num_sats = bcd8(*sensor.gps_stat[XBUS_GPS_STAT_SATS], 0);\n            gps_stat.altitude_high = bcd8((uint8_t)(alt / 1000), 0);\n            memcpy(buffer, &gps_stat, sizeof(xbus_gps_stat_t));\n            break;\n        }\n        case XBUS_ENERGY_ID: {\n            xbus_energy_t energy;\n            energy.identifier = XBUS_ENERGY_ID;\n            if (sensor.energy[XBUS_ENERGY_CURRENT1])\n                energy.current_a = swap_16((int16_t)(*sensor.energy[XBUS_ENERGY_CURRENT1] * 100));\n            if (sensor.energy[XBUS_ENERGY_CONSUMPTION1])\n                energy.charge_used_a = swap_16((int16_t)(*sensor.energy[XBUS_ENERGY_CONSUMPTION1] * 10));\n            if (sensor.energy[XBUS_ENERGY_VOLTAGE1])\n                energy.volts_a = swap_16((uint16_t)(*sensor.energy[XBUS_ENERGY_VOLTAGE1] * 100));\n            if (sensor.energy[XBUS_ENERGY_CURRENT2])\n                energy.current_b = swap_16((int16_t)(*sensor.energy[XBUS_ENERGY_CURRENT2] * 10));\n            if (sensor.energy[XBUS_ENERGY_CONSUMPTION2])\n                energy.charge_used_b = swap_16((int16_t)(*sensor.energy[XBUS_ENERGY_CONSUMPTION2]));\n            if (sensor.energy[XBUS_ENERGY_VOLTAGE2])\n                energy.volts_b = swap_16((uint16_t)(*sensor.energy[XBUS_ENERGY_VOLTAGE2] * 100));\n            memcpy(buffer, &energy, sizeof(xbus_energy_t));\n            break;\n        }\n        case XBUS_ESC_ID: {\n            xbus_esc_t esc;\n            esc.identifier = XBUS_ESC_ID;\n            if (sensor.esc[XBUS_ESC_RPM]) esc.rpm = swap_16((uint16_t)(*sensor.esc[XBUS_ESC_RPM] / 10));\n            if (sensor.esc[XBUS_ESC_VOLTAGE])\n                esc.volts_input = swap_16((uint16_t)(*sensor.esc[XBUS_ESC_VOLTAGE] * 100));\n            if (sensor.esc[XBUS_ESC_TEMPERATURE_FET])\n                esc.temp_fet = swap_16((uint16_t)(*sensor.esc[XBUS_ESC_TEMPERATURE_FET] * 10));\n            if (sensor.esc[XBUS_ESC_CURRENT])\n                esc.current_motor = swap_16((uint16_t)(*sensor.esc[XBUS_ESC_CURRENT] * 100));\n            if (sensor.esc[XBUS_ESC_TEMPERATURE_BEC])\n                esc.temp_bec = swap_16((uint16_t)(*sensor.esc[XBUS_ESC_TEMPERATURE_BEC] * 10));\n            if (sensor.esc[XBUS_ESC_CURRENT_BEC]) esc.current_bec = *sensor.esc[XBUS_ESC_CURRENT_BEC] * 10;\n            if (sensor.esc[XBUS_ESC_VOLTAGE_BEC]) esc.voltage_bec = *sensor.esc[XBUS_ESC_VOLTAGE_BEC] * 20;\n            memcpy(buffer, &esc, sizeof(xbus_esc_t));\n            break;\n        }\n        case XBUS_BATTERY_ID: {\n            xbus_battery_t battery;\n            battery.identifier = XBUS_BATTERY_ID;\n            if (sensor.battery[XBUS_BATTERY_CURRENT1])\n                battery.current_a = swap_16((int16_t)(*sensor.battery[XBUS_BATTERY_CURRENT1] * 10));\n            if (sensor.battery[XBUS_BATTERY_CONSUMPTION1])\n                battery.charge_used_a = swap_16((int16_t)(*sensor.battery[XBUS_BATTERY_CONSUMPTION1]));\n            if (sensor.battery[XBUS_BATTERY_TEMP1]) {\n                float temp_f = *sensor.battery[XBUS_BATTERY_TEMP1];\n                temp_f = temp_f * 9.0 / 5.0 + 32;\n                battery.temp_a = swap_16((uint16_t)(temp_f * 10));\n            }\n            if (sensor.battery[XBUS_BATTERY_CURRENT2])\n                battery.current_b = swap_16((int16_t)(*sensor.battery[XBUS_BATTERY_CURRENT2] * 10));\n            if (sensor.battery[XBUS_BATTERY_CONSUMPTION2])\n                battery.charge_used_b = swap_16((int16_t)(*sensor.battery[XBUS_BATTERY_CONSUMPTION2]));\n            if (sensor.battery[XBUS_BATTERY_TEMP2]) {\n                float temp_f = *sensor.battery[XBUS_BATTERY_TEMP2];\n                temp_f = temp_f * 9.0 / 5.0 + 32;\n                battery.temp_b = swap_16((uint16_t)(temp_f * 10));\n            }\n            memcpy(buffer, &battery, sizeof(xbus_battery_t));\n            break;\n        }\n        case XBUS_VARIO_ID: {\n            xbus_vario_t vario;\n            vario.identifier = XBUS_VARIO_ID;\n            float altitude = *sensor.vario[XBUS_VARIO_ALTITUDE];\n            vario.altitude = swap_16((int16_t)(altitude * 10));\n#ifdef SIM_SENSORS\n            vario.delta_0250ms = swap_16((int16_t)(-10));\n            vario.delta_0500ms = swap_16((int16_t)(20));\n            vario.delta_1000ms = swap_16((int16_t)(-12.32));\n            vario.delta_1500ms = swap_16((int16_t)(15));\n            vario.delta_2000ms = swap_16((int16_t)(20));\n            vario.delta_3000ms = swap_16((int16_t)(-300));\n#endif\n            memcpy(buffer, &vario, sizeof(xbus_vario_t));\n            break;\n        }\n        case XBUS_RPMVOLTTEMP_ID: {\n            xbus_rpm_volt_temp_t rpm_volt_temp;\n            rpm_volt_temp.identifier = XBUS_RPMVOLTTEMP_ID;\n            if (sensor.rpm_volt_temp[XBUS_RPMVOLTTEMP_VOLT])\n                rpm_volt_temp.volts = swap_16((uint16_t)(*sensor.rpm_volt_temp[XBUS_RPMVOLTTEMP_VOLT] * 100));\n            if (sensor.rpm_volt_temp[XBUS_RPMVOLTTEMP_TEMP]) {\n                float temp_f = *sensor.rpm_volt_temp[XBUS_RPMVOLTTEMP_TEMP];\n                temp_f = temp_f * 9.0 / 5.0 + 32;\n                rpm_volt_temp.temperature = swap_16((int16_t)(temp_f));\n            }\n            memcpy(buffer, &rpm_volt_temp, sizeof(xbus_rpm_volt_temp_t));\n            break;\n        }\n        case XBUS_FUEL_FLOW_ID: {\n            xbus_fuel_flow_t fuel_flow;\n            fuel_flow.identifier = XBUS_FUEL_FLOW_ID;\n            if (sensor.fuel_flow[XBUS_FUEL_FLOW_CONSUMED])\n                fuel_flow.fuel_consumed_A = swap_16((uint16_t)(*sensor.fuel_flow[XBUS_FUEL_FLOW_CONSUMED] * 10));\n            if (sensor.fuel_flow[XBUS_FUEL_FLOW_RATE])\n                fuel_flow.flow_rate_A = swap_16((uint16_t)(*sensor.fuel_flow[XBUS_FUEL_FLOW_RATE] * 10));\n            memcpy(buffer, &fuel_flow, sizeof(xbus_fuel_flow_t));\n            break;\n        }\n        case XBUS_STRU_TELE_DIGITAL_AIR_ID: {\n            xbus_stru_tele_digital_air_t stru_tele_digital_air;\n            stru_tele_digital_air.identifier = XBUS_STRU_TELE_DIGITAL_AIR_ID;\n            if (sensor.stru_tele_digital_air[XBUS_DIGITAL_AIR_FUEL_PRESSURE])\n                stru_tele_digital_air.pressure =\n                    swap_16((uint16_t)(*sensor.stru_tele_digital_air[XBUS_DIGITAL_AIR_FUEL_PRESSURE] * 0.000145038 *\n                                       10));  // Pa to psi, precision 0.1 psi\n            memcpy(buffer, &stru_tele_digital_air, sizeof(xbus_stru_tele_digital_air_t));\n            break;\n        }\n        case XBUS_TELE_LIPOMON_ID: {\n            xbus_tele_lipomon_t tele_lipomon;\n            tele_lipomon.identifier = XBUS_TELE_LIPOMON_ID;\n            for (uint i = 0; i < 6; i++) {\n                if (*sensor.tele_lipomon[i] < 1 || !sensor.tele_lipomon[i])\n                    tele_lipomon.cell[i] = 0xFFFF;\n                else\n                    tele_lipomon.cell[i] = swap_16((uint16_t)(*sensor.tele_lipomon[i] * 100));\n            }\n            memcpy(buffer, &tele_lipomon, sizeof(xbus_tele_lipomon_t));\n            break;\n        }\n        case XBUS_TELE_G_METER_ID: {\n            xbus_tele_g_meter_t tele_g_meter;\n            tele_g_meter.identifier = XBUS_TELE_G_METER_ID;\n            if (sensor.tele_g_meter[XBUS_TELE_G_METER_X]) {\n                tele_g_meter.GForceX = swap_16((int16_t)(*sensor.tele_g_meter[XBUS_TELE_G_METER_X] * 100));\n                if (swap_16((int16_t)(fabs(tele_g_meter.GForceX))) > swap_16((int16_t)(tele_g_meter.maxGForceX))) {\n                    tele_g_meter.maxGForceX = tele_g_meter.GForceX;\n                }\n            }\n            if (sensor.tele_g_meter[XBUS_TELE_G_METER_Y]) {\n                tele_g_meter.GForceY = swap_16((int16_t)(*sensor.tele_g_meter[XBUS_TELE_G_METER_Y] * 100));\n                if (swap_16((int16_t)(fabs(tele_g_meter.GForceY))) > swap_16((uint16_t)(tele_g_meter.maxGForceY))) {\n                    tele_g_meter.maxGForceY = tele_g_meter.GForceY;\n                }\n            }\n            if (sensor.tele_g_meter[XBUS_TELE_G_METER_Z]) {\n                tele_g_meter.GForceZ = swap_16((int16_t)(*sensor.tele_g_meter[XBUS_TELE_G_METER_Z] * 100));\n                if (swap_16((int16_t)(tele_g_meter.GForceZ)) > swap_16((int16_t)(tele_g_meter.maxGForceZ))) {\n                    tele_g_meter.maxGForceZ = tele_g_meter.GForceZ;\n                }\n                if (swap_16((int16_t)(tele_g_meter.GForceZ)) < swap_16((int16_t)(tele_g_meter.minGForceZ))) {\n                    tele_g_meter.minGForceZ = tele_g_meter.GForceZ;\n                }\n            }\n            memcpy(buffer, &tele_g_meter, sizeof(xbus_tele_g_meter_t));\n            break;\n        }\n        case XBUS_TELE_GYRO_ID: {\n            xbus_tele_gyro_t tele_gyro;\n            tele_gyro.identifier = XBUS_TELE_GYRO_ID;\n            if (sensor.tele_gyro[XBUS_TELE_GYRO_ROLL])\n                tele_gyro.gyroX = swap_16((int16_t)(*sensor.tele_gyro[XBUS_TELE_GYRO_ROLL] * 10));\n            if (swap_16((int16_t)(fabs(tele_gyro.gyroX))) > swap_16((int16_t)(tele_gyro.maxGyroX))) {\n                tele_gyro.maxGyroX = tele_gyro.gyroX;\n            }\n            if (sensor.tele_gyro[XBUS_TELE_GYRO_PITCH])\n                tele_gyro.gyroY = swap_16((int16_t)(*sensor.tele_gyro[XBUS_TELE_GYRO_PITCH] * 10));\n            if (swap_16((int16_t)(fabs(tele_gyro.gyroY))) > swap_16((int16_t)(tele_gyro.maxGyroY))) {\n                tele_gyro.maxGyroY = tele_gyro.gyroY;\n            }\n            if (sensor.tele_gyro[XBUS_TELE_GYRO_YAW])\n                tele_gyro.gyroZ = swap_16((int16_t)(*sensor.tele_gyro[XBUS_TELE_GYRO_YAW] * 10));\n            if (swap_16((int16_t)(fabs(tele_gyro.gyroZ))) > swap_16((int16_t)(tele_gyro.maxGyroZ))) {\n                tele_gyro.maxGyroZ = tele_gyro.gyroZ;\n            }\n            memcpy(buffer, &tele_gyro, sizeof(xbus_tele_gyro_t));\n            break;\n        }\n        case XBUS_MULTICYL_ID: {\n            xbus_multi_cyl_t multicyl = {0};\n            multicyl.identifier = XBUS_MULTICYL_ID;\n\n            for (int i = 0; i < 9; i++) {\n                multicyl.temperature[i] = 0xFF;\n            }\n\n            if (sensor.multicyl[XBUS_MULTICYL_TEMP]) {\n                config_t *config = config_read();\n                int idx = config->sensor_id_srxl2 - 1;\n\n                if (idx >= 0 && idx < 9) {\n                    float temp_c = *sensor.multicyl[XBUS_MULTICYL_TEMP];\n\n                    if (temp_c < 30.0f)\n                        multicyl.temperature[idx] = 0x00;\n                    else if (temp_c > 284.0f)\n                        multicyl.temperature[idx] = 0xFE;\n                    else\n                        multicyl.temperature[idx] = (uint8_t)(temp_c - 30.0f);\n                }\n            }\n\n            if (sensor.multicyl[XBUS_MULTICYL_VOLTAGE]) {\n                multicyl.batteryV = (uint8_t)((*sensor.multicyl[XBUS_MULTICYL_VOLTAGE] - 3.5f) * 10.0f);\n            } else {\n                multicyl.batteryV = 0xFF;\n            }\n\n            memcpy(buffer, &multicyl, sizeof(xbus_multi_cyl_t));\n            break;\n        }\n    }\n}\n\nstatic void i2c_request_handler(uint8_t address) {\n    debug(\"\\nXBUS (%u) Address: %X Packet: \", uxTaskGetStackHighWaterMark(context.receiver_task_handle), address);\n    uint8_t buffer[16] = {0};\n    switch (address) {\n        case XBUS_AIRSPEED_ID:\n            if (!sensor.is_enabled[XBUS_AIRSPEED]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_airspeed_t), \"0x%X \");\n            break;\n        case XBUS_ALTIMETER_ID:\n            if (!sensor.is_enabled[XBUS_ALTIMETER]) break;\n            break;\n        case XBUS_GPS_LOC_ID:\n            if (!sensor.is_enabled[XBUS_GPS_LOC]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_gps_loc_t), \"0x%X \");\n            break;\n        case XBUS_GPS_STAT_ID:\n            if (!sensor.is_enabled[XBUS_GPS_STAT]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_gps_stat_t), \"0x%X \");\n            break;\n        case XBUS_ENERGY_ID:\n            if (!sensor.is_enabled[XBUS_ENERGY]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_energy_t), \"0x%X \");\n            break;\n        case XBUS_ESC_ID:\n            if (!sensor.is_enabled[XBUS_ESC]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_esc_t), \"0x%X \");\n            break;\n        case XBUS_BATTERY_ID:\n            if (!sensor.is_enabled[XBUS_BATTERY]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_battery_t), \"0x%X \");\n            break;\n        case XBUS_VARIO_ID:\n            if (!sensor.is_enabled[XBUS_VARIO]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_vario_t), \"0x%X \");\n            break;\n        case XBUS_RPMVOLTTEMP_ID:\n            if (!sensor.is_enabled[XBUS_RPMVOLTTEMP]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_rpm_volt_temp_t), \"0x%X \");\n            break;\n        case XBUS_FUEL_FLOW_ID:\n            if (!sensor.is_enabled[XBUS_FUEL_FLOW]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_fuel_flow_t), \"0x%X \");\n            break;\n        case XBUS_STRU_TELE_DIGITAL_AIR_ID:\n            if (!sensor.is_enabled[XBUS_STRU_TELE_DIGITAL_AIR]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_stru_tele_digital_air_t), \"0x%X \");\n            break;\n        case XBUS_TELE_LIPOMON_ID:\n            if (!sensor.is_enabled[XBUS_TELE_LIPOMON]) break;\n            xbus_format_sensor(address, buffer);\n            i2c_multi_set_write_buffer(buffer);\n            vTaskResume(context.led_task_handle);\n            debug_buffer(buffer, sizeof(xbus_tele_lipomon_t), \"0x%X \");\n            break;\n    }\n}\n\nvoid xbus_set_config(void) {\n    config_t *config = config_read();\n    TaskHandle_t task_handle;\n    float *baro_temp = NULL, *baro_pressure = NULL;\n    if (config->esc_protocol == ESC_PWM) {\n        esc_pwm_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_pwm_task, \"esc_pwm_task\", STACK_ESC_PWM, (void *)&parameter, 2, &task_handle);\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW3) {\n        esc_hw3_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm, malloc(sizeof(float))};\n        xTaskCreate(esc_hw3_task, \"esc_hw3_task\", STACK_ESC_HW3, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_HW4) {\n        esc_hw4_parameters_t parameter = {config->rpm_multiplier,\n                                          config->enable_pwm_out,\n                                          config->enable_esc_hw4_init_delay,\n                                          config->alpha_rpm,\n                                          config->alpha_voltage,\n                                          config->alpha_current,\n                                          config->alpha_temperature,\n                                          config->esc_hw4_voltage_multiplier,\n                                          config->esc_hw4_current_multiplier,\n                                          config->esc_hw4_current_thresold,\n                                          config->esc_hw4_current_max,\n                                          config->esc_hw4_is_manual_offset,\n                                          config->esc_hw4_auto_detect,\n                                          config->esc_hw4_offset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw4_task, \"esc_hw4_task\", STACK_ESC_HW4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        if (config->enable_pwm_out) {\n            xTaskCreate(pwm_out_task, \"pwm_out\", STACK_PWM_OUT, (void *)parameter.rpm, 2, &task_handle);\n            context.pwm_out_task_handle = task_handle;\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        }\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temperature_fet;\n        sensor.esc[XBUS_ESC_TEMPERATURE_BEC] = parameter.temperature_bec;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor->battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor->battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor.is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n    }\n    if (config->esc_protocol == ESC_HW5) {\n        esc_hw5_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_hw5_task, \"esc_hw5_task\", STACK_ESC_HW5, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temperature_fet;\n        sensor.esc[XBUS_ESC_TEMPERATURE_BEC] = parameter.temperature_bec;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor->battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor->battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor->is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n    }\n    if (config->esc_protocol == ESC_CASTLE) {\n        esc_castle_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                             config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                             malloc(sizeof(float)),  malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_castle_task, \"esc_castle_task\", STACK_ESC_CASTLE, (void *)&parameter, 2, &task_handle);\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temperature;\n        sensor.esc[XBUS_ESC_CURRENT_BEC] = parameter.current_bec;\n        sensor.esc[XBUS_ESC_VOLTAGE_BEC] = parameter.voltage_bec;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor->battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor->battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor->is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_KONTRONIK) {\n        esc_kontronik_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage,  config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)),  malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_kontronik_task, \"esc_kontronik_task\", STACK_ESC_KONTRONIK, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temperature_fet;\n        sensor.esc[XBUS_ESC_TEMPERATURE_BEC] = parameter.temperature_bec;\n        sensor.esc[XBUS_ESC_CURRENT_BEC] = parameter.current_bec;\n        sensor.esc[XBUS_ESC_VOLTAGE_BEC] = parameter.voltage_bec;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor.battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor.battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor.is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_F) {\n        esc_apd_f_parameters_t parameter = {config->rpm_multiplier, config->alpha_rpm,         config->alpha_voltage,\n                                            config->alpha_current,  config->alpha_temperature, malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(float)),\n                                            malloc(sizeof(float)),  malloc(sizeof(float)),     malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_f_task, \"esc_apd_f_task\", STACK_ESC_APD_F, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temperature;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor.battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor.battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor.is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_APD_HV) {\n        esc_apd_hv_parameters_t parameter = {\n            config->rpm_multiplier,    config->alpha_rpm,     config->alpha_voltage, config->alpha_current,\n            config->alpha_temperature, malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(float)),\n            malloc(sizeof(float)),     malloc(sizeof(float)), malloc(sizeof(float)), malloc(sizeof(uint8_t))};\n        xTaskCreate(esc_apd_hv_task, \"esc_apd_hv_task\", STACK_ESC_APD_HV, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temperature;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor->battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor->battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor->is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OMP_M4) {\n        esc_omp_m4_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_omp_m4_task, \"esc_omp_m4_task\", STACK_ESC_OMP_M4, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temp_esc;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor.battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor.battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor.is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_ZTW) {\n        esc_ztw_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temp_esc = malloc(sizeof(float));\n        parameter.temp_motor = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_ztw_task, \"esc_ztw_task\", STACK_ESC_ZTW, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temp_esc;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n        // sensor.battery[XBUS_BATTERY_CURRENT1] = parameter.current;\n        // sensor.battery[XBUS_BATTERY_CONSUMPTION1] = parameter.consumption;\n        // sensor.is_enabled[XBUS_BATTERY] = true;\n        // sensor_formatted->battery = malloc(sizeof(xbus_battery_t));\n        //*sensor_formatted->battery = (xbus_battery_t){XBUS_BATTERY_ID, 0, 0, 0, 0, 0, 0};\n        // i2c_multi_enable_address(XBUS_BATTERY_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->esc_protocol == ESC_OPENYGE) {\n        esc_openyge_parameters_t parameter;\n        parameter.rpm_multiplier = config->rpm_multiplier;\n        parameter.pwm_out = config->enable_pwm_out;\n        parameter.alpha_rpm = config->alpha_rpm;\n        parameter.alpha_voltage = config->alpha_voltage;\n        parameter.alpha_current = config->alpha_current;\n        parameter.alpha_temperature = config->alpha_temperature;\n        parameter.rpm = malloc(sizeof(float));\n        parameter.voltage = malloc(sizeof(float));\n        parameter.current = malloc(sizeof(float));\n        parameter.temperature_fet = malloc(sizeof(float));\n        parameter.temperature_bec = malloc(sizeof(float));\n        parameter.cell_voltage = malloc(sizeof(float));\n        parameter.consumption = malloc(sizeof(float));\n        parameter.voltage_bec = malloc(sizeof(float));\n        parameter.current_bec = malloc(sizeof(float));\n        parameter.throttle = malloc(sizeof(float));\n        parameter.pwm_percent = malloc(sizeof(float));\n        parameter.cell_count = malloc(sizeof(uint8_t));\n        xTaskCreate(esc_openyge_task, \"esc_openyge_task\", STACK_ESC_OPENYGE, (void *)&parameter, 2, &task_handle);\n        context.uart1_notify_task_handle = task_handle;\n\n        sensor.esc[XBUS_ESC_RPM] = parameter.rpm;\n        sensor.esc[XBUS_ESC_VOLTAGE] = parameter.voltage;\n        sensor.esc[XBUS_ESC_CURRENT] = parameter.current;\n        sensor.esc[XBUS_ESC_TEMPERATURE_FET] = parameter.temperature_fet;\n        sensor.is_enabled[XBUS_ESC] = true;\n        i2c_multi_enable_address(XBUS_ESC_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_gps) {\n        gps_parameters_t parameter;\n        parameter.protocol = config->gps_protocol;\n        parameter.baudrate = config->gps_baudrate;\n        parameter.rate = config->gps_rate;\n        parameter.lat = malloc(sizeof(float));\n        parameter.lon = malloc(sizeof(float));\n        parameter.alt = malloc(sizeof(float));\n        parameter.spd = malloc(sizeof(float));\n        parameter.cog = malloc(sizeof(float));\n        parameter.hdop = malloc(sizeof(float));\n        parameter.sat = malloc(sizeof(float));\n        parameter.time = malloc(sizeof(float));\n        parameter.date = malloc(sizeof(float));\n        parameter.vspeed = malloc(sizeof(float));\n        parameter.dist = malloc(sizeof(float));\n        parameter.spd_kmh = malloc(sizeof(float));\n        parameter.fix = malloc(sizeof(float));\n        parameter.vdop = malloc(sizeof(float));\n        parameter.speed_acc = malloc(sizeof(float));\n        parameter.h_acc = malloc(sizeof(float));\n        parameter.v_acc = malloc(sizeof(float));\n        parameter.track_acc = malloc(sizeof(float));\n        parameter.n_vel = malloc(sizeof(float));\n        parameter.e_vel = malloc(sizeof(float));\n        parameter.v_vel = malloc(sizeof(float));\n        parameter.alt_elipsiod = malloc(sizeof(float));\n        parameter.pdop = malloc(sizeof(float));\n        parameter.fix_type = malloc(sizeof(uint8_t));\n        parameter.home_set = malloc(sizeof(uint8_t));\n        parameter.alt_home = malloc(sizeof(float));\n        xTaskCreate(gps_task, \"gps_task\", STACK_GPS, (void *)&parameter, 2, &task_handle);\n        context.uart_pio_notify_task_handle = task_handle;\n\n        sensor.gps_loc = malloc(sizeof(xbus_sensor_gps_loc_t));\n        sensor.gps_loc->altitude_low = parameter.alt;\n        sensor.gps_loc->latitude = parameter.lat;\n        sensor.gps_loc->longitude = parameter.lon;\n        sensor.gps_loc->course = parameter.cog;\n        sensor.gps_loc->hdop = parameter.hdop;\n        sensor.gps_loc->fix_type = parameter.fix_type;\n        sensor.gps_loc->home_set = parameter.home_set;\n        sensor.gps_stat[XBUS_GPS_STAT_SPEED] = parameter.spd;\n        sensor.gps_stat[XBUS_GPS_STAT_TIME] = parameter.time;\n        sensor.gps_stat[XBUS_GPS_STAT_SATS] = parameter.sat;\n        sensor.gps_stat[XBUS_GPS_STAT_ALTITUDE] = parameter.alt;\n\n        sensor.is_enabled[XBUS_GPS_LOC] = true;\n        sensor.is_enabled[XBUS_GPS_STAT] = true;\n        i2c_multi_enable_address(XBUS_GPS_LOC_ID);\n        i2c_multi_enable_address(XBUS_GPS_STAT_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_voltage) {\n        voltage_parameters_t parameter = {0, config->analog_rate, config->alpha_voltage,\n                                          config->analog_voltage_multiplier, malloc(sizeof(float))};\n        xTaskCreate(voltage_task, \"voltage_task\", STACK_VOLTAGE, (void *)&parameter, 2, &task_handle);\n        if (!config->xbus_use_alternative_volt_temp) {\n            sensor.rpm_volt_temp[XBUS_RPMVOLTTEMP_VOLT] = parameter.voltage;\n            sensor.is_enabled[XBUS_RPMVOLTTEMP] = true;\n            i2c_multi_enable_address(XBUS_RPMVOLTTEMP_ID);\n        } else {\n            sensor.multicyl[XBUS_MULTICYL_VOLTAGE] = parameter.voltage;\n            sensor.is_enabled[XBUS_MULTICYL] = true;\n            i2c_multi_enable_address(XBUS_MULTICYL_ID);\n        }\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_current) {\n        current_parameters_t parameter = {1,\n                                          config->analog_rate,\n                                          config->alpha_current,\n                                          config->analog_current_multiplier,\n                                          config->analog_current_offset,\n                                          config->analog_current_autoffset,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(current_task, \"current_task\", STACK_CURRENT, (void *)&parameter, 2, &task_handle);\n\n        sensor.energy[XBUS_ENERGY_CURRENT1] = parameter.current;\n        sensor.energy[XBUS_ENERGY_CONSUMPTION1] = parameter.consumption;\n        if (!sensor.is_enabled[XBUS_ENERGY]) {\n            sensor.is_enabled[XBUS_ENERGY] = true;\n        }\n        i2c_multi_enable_address(XBUS_ENERGY_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_ntc) {\n        ntc_parameters_t parameter = {2, config->analog_rate, config->ntc_offset, config->alpha_temperature,\n                                      malloc(sizeof(float))};\n        xTaskCreate(ntc_task, \"ntc_task\", STACK_NTC, (void *)&parameter, 2, &task_handle);\n        if (!config->xbus_use_alternative_volt_temp) {\n            sensor.rpm_volt_temp[XBUS_RPMVOLTTEMP_TEMP] = parameter.ntc;\n            sensor.is_enabled[XBUS_RPMVOLTTEMP] = true;\n            i2c_multi_enable_address(XBUS_RPMVOLTTEMP_ID);\n        } else {\n            sensor.multicyl[XBUS_MULTICYL_TEMP] = parameter.ntc;\n            sensor.is_enabled[XBUS_MULTICYL] = true;\n            i2c_multi_enable_address(XBUS_MULTICYL_ID);\n        }\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP280) {\n        bmp280_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         config->bmp280_filter, malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(bmp280_task, \"bmp280_task\", STACK_BMP280, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        add_alarm_in_ms(250, interval_250_callback, NULL, false);\n        add_alarm_in_ms(500, interval_500_callback, NULL, false);\n        add_alarm_in_ms(1000, interval_1000_callback, NULL, false);\n        add_alarm_in_ms(1500, interval_1500_callback, NULL, false);\n        add_alarm_in_ms(2000, interval_2000_callback, NULL, false);\n        add_alarm_in_ms(3000, interval_3000_callback, NULL, false);\n        sensor.vario[XBUS_VARIO_ALTITUDE] = parameter.altitude;\n        sensor.is_enabled[XBUS_VARIO] = true;\n        i2c_multi_enable_address(XBUS_VARIO_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_MS5611) {\n        ms5611_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, 0,\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float)),\n                                         malloc(sizeof(float))};\n        xTaskCreate(ms5611_task, \"ms5611_task\", STACK_MS5611, (void *)&parameter, 2, &task_handle);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        add_alarm_in_ms(250, interval_250_callback, NULL, false);\n        add_alarm_in_ms(500, interval_500_callback, NULL, false);\n        add_alarm_in_ms(1000, interval_1000_callback, NULL, false);\n        add_alarm_in_ms(1500, interval_1500_callback, NULL, false);\n        add_alarm_in_ms(2000, interval_2000_callback, NULL, false);\n        add_alarm_in_ms(3000, interval_3000_callback, NULL, false);\n        sensor.vario[XBUS_VARIO_ALTITUDE] = parameter.altitude;\n        sensor.is_enabled[XBUS_VARIO] = true;\n        i2c_multi_enable_address(XBUS_VARIO_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->i2c_module == I2C_BMP180) {\n        bmp180_parameters_t parameter = {config->alpha_vario,   config->vario_auto_offset, malloc(sizeof(float)),\n                                         malloc(sizeof(float)), malloc(sizeof(float)),     malloc(sizeof(float))};\n        xTaskCreate(bmp180_task, \"bmp180_task\", STACK_BMP180, (void *)&parameter, 2, &task_handle);\n        add_alarm_in_ms(250, interval_250_callback, NULL, false);\n        add_alarm_in_ms(500, interval_500_callback, NULL, false);\n        add_alarm_in_ms(1000, interval_1000_callback, NULL, false);\n        add_alarm_in_ms(1500, interval_1500_callback, NULL, false);\n        add_alarm_in_ms(2000, interval_2000_callback, NULL, false);\n        add_alarm_in_ms(3000, interval_3000_callback, NULL, false);\n\n        if (config->enable_analog_airspeed) {\n            baro_temp = parameter.temperature;\n            baro_pressure = parameter.pressure;\n        }\n\n        sensor.vario[XBUS_VARIO_ALTITUDE] = parameter.altitude;\n        sensor.is_enabled[XBUS_VARIO] = true;\n        i2c_multi_enable_address(XBUS_VARIO_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_analog_airspeed) {\n        airspeed_parameters_t parameter = {3,\n                                           config->analog_rate,\n                                           config->alpha_airspeed,\n                                           (float)config->airspeed_offset / 1000,\n                                           (float)config->airspeed_vcc / 100,\n                                           baro_temp,\n                                           baro_pressure,\n                                           malloc(sizeof(float))};\n        xTaskCreate(airspeed_task, \"airspeed_task\", STACK_AIRSPEED, (void *)&parameter, 2, &task_handle);\n\n        sensor.airspeed[XBUS_AIRSPEED_AIRSPEED] = parameter.airspeed;\n        sensor.is_enabled[XBUS_AIRSPEED] = true;\n        i2c_multi_enable_address(XBUS_AIRSPEED_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_fuel_flow) {\n        fuel_meter_parameters_t parameter = {config->fuel_flow_ml_per_pulse, malloc(sizeof(float)),\n                                             malloc(sizeof(float))};\n        xTaskCreate(fuel_meter_task, \"fuel_meter_task\", STACK_FUEL_METER, (void *)&parameter, 2, &task_handle);\n\n        sensor.fuel_flow[XBUS_FUEL_FLOW_RATE] = parameter.consumption_instant;\n        sensor.fuel_flow[XBUS_FUEL_FLOW_CONSUMED] = parameter.consumption_total;\n        sensor.is_enabled[XBUS_FUEL_FLOW] = true;\n        i2c_multi_enable_address(XBUS_FUEL_FLOW_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_fuel_pressure) {\n        xgzp68xxd_parameters_t parameter = {config->xgzp68xxd_k, malloc(sizeof(float)), malloc(sizeof(float))};\n        xTaskCreate(xgzp68xxd_task, \"fuel_pressure_task\", STACK_FUEL_PRESSURE, (void *)&parameter, 2, &task_handle);\n\n        sensor.stru_tele_digital_air[XBUS_DIGITAL_AIR_FUEL_PRESSURE] = parameter.pressure;\n        sensor.is_enabled[XBUS_STRU_TELE_DIGITAL_AIR] = true;\n        i2c_multi_enable_address(XBUS_STRU_TELE_DIGITAL_AIR_ID);\n\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->xbus_clock_stretch) {\n        gpio_set_dir(CLOCK_STRETCH_GPIO, true);\n        gpio_put(CLOCK_STRETCH_GPIO, false);\n    }\n    if (config->enable_gyro) {\n        mpu6050_parameters_t parameter = {1,\n                                          0,\n                                          config->mpu6050_acc_scale,\n                                          config->mpu6050_gyro_scale,\n                                          config->mpu6050_gyro_weighting,\n                                          config->mpu6050_filter,\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float)),\n                                          malloc(sizeof(float))};\n        xTaskCreate(mpu6050_task, \"mpu6050_task\", STACK_MPU6050, (void *)&parameter, 2, &task_handle);\n        sensor.is_enabled[XBUS_TELE_G_METER] = true;\n        sensor.tele_g_meter[XBUS_TELE_G_METER_X] = parameter.acc_x;\n        sensor.tele_g_meter[XBUS_TELE_G_METER_Y] = parameter.acc_y;\n        sensor.tele_g_meter[XBUS_TELE_G_METER_Z] = parameter.acc_z;\n        i2c_multi_enable_address(XBUS_TELE_G_METER_ID);\n        sensor.is_enabled[XBUS_TELE_GYRO] = true;\n        sensor.tele_gyro[XBUS_TELE_GYRO_PITCH] = parameter.pitch;\n        sensor.tele_gyro[XBUS_TELE_GYRO_ROLL] = parameter.roll;\n        sensor.tele_gyro[XBUS_TELE_GYRO_YAW] = parameter.yaw;\n        i2c_multi_enable_address(XBUS_TELE_GYRO_ID);\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n    }\n    if (config->enable_lipo) {\n        float *cell_prev = 0;\n        sensor.is_enabled[XBUS_TELE_LIPOMON] = true;\n        i2c_multi_enable_address(XBUS_TELE_LIPOMON_ID);\n        if (config->lipo_cells > 0) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x40,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            *parameter.cell_prev = 0;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n            for (uint8_t i = 0; i < parameter.cell_count; i++) {\n                sensor.tele_lipomon[XBUS_TELE_LIPOMON_CELL1 + i] = parameter.cell[i];\n            }\n            for (uint8_t i = parameter.cell_count; i < 3; i++) {\n                *sensor.tele_lipomon[XBUS_TELE_LIPOMON_CELL1 + i] = 0x7FFF;\n            }\n        }\n        if (config->lipo_cells > 3) {\n            ina3221_parameters_t parameter = {\n                .i2c_address = 0x41,\n                .filter = config->ina3221_filter,\n                .cell_count = MIN(config->lipo_cells - 3, 3),\n                .cell[0] = malloc(sizeof(float)),\n                .cell[1] = malloc(sizeof(float)),\n                .cell[2] = malloc(sizeof(float)),\n                .cell_prev = malloc(sizeof(float)),\n            };\n            parameter.cell_prev = cell_prev;\n            cell_prev = parameter.cell[2];\n            xTaskCreate(ina3221_task, \"ina3221_task\", STACK_INA3221, (void *)&parameter, 2, &task_handle);\n            ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n            for (uint8_t i = 0; i < parameter.cell_count; i++) {\n                sensor.tele_lipomon[XBUS_TELE_LIPOMON_CELL4 + i] = parameter.cell[i];\n            }\n            for (uint8_t i = parameter.cell_count; i < 3; i++) {\n                *sensor.tele_lipomon[XBUS_TELE_LIPOMON_CELL4 + i] = 0x7FFF;\n            }\n        }\n    }\n}\n\nstatic uint8_t bcd8(float value, uint8_t precision) {\n    char buf[10] = {0};\n    uint8_t output = 0;\n    for (int i = 0; i < precision; i++) value = value * 10;\n    sprintf(buf, \"%02i\", (uint8_t)value);\n    for (int i = 0; i < 2; i++) output |= (buf[i] - 48) << ((1 - i) * 4);\n    return output;\n}\n\nstatic uint16_t bcd16(float value, uint8_t precision) {\n    char buf[10] = {0};\n    uint16_t output = 0;\n    for (int i = 0; i < precision; i++) value = value * 10;\n    sprintf(buf, \"%04i\", (uint16_t)value);\n    for (int i = 0; i < 4; i++) output |= (uint16_t)(buf[i] - 48) << ((3 - i) * 4);\n    return output;\n}\n\nstatic uint32_t bcd32(float value, uint8_t precision) {\n    char buf[10] = {0};\n    uint32_t output = 0;\n    for (int i = 0; i < precision; i++) value = value * 10;\n    sprintf(buf, \"%08li\", (uint32_t)value);\n    for (int i = 0; i < 8; i++) output |= (uint32_t)(buf[i] - 48) << ((7 - i) * 4);\n    return output;\n}\n\nstatic int64_t interval_250_callback(alarm_id_t id, void *parameters) {\n    static float prev = 0;\n    delta_0250ms = swap_16((int16_t)(round(*sensor.vario[XBUS_VARIO_ALTITUDE] - prev) * 10));\n    prev = *sensor.vario[XBUS_VARIO_ALTITUDE];\n    return 250000L;\n}\n\nstatic int64_t interval_500_callback(alarm_id_t id, void *parameters) {\n    static float prev = 0;\n    delta_0500ms = swap_16((int16_t)(round(*sensor.vario[XBUS_VARIO_ALTITUDE] - prev) * 10));\n    prev = *sensor.vario[XBUS_VARIO_ALTITUDE];\n    return 500000L;\n}\n\nstatic int64_t interval_1000_callback(alarm_id_t id, void *parameters) {\n    static float prev = 0;\n    delta_1000ms = swap_16((int16_t)round(*sensor.vario[XBUS_VARIO_ALTITUDE] - prev));\n    prev = *sensor.vario[XBUS_VARIO_ALTITUDE];\n    return 1000000L;\n}\n\nstatic int64_t interval_1500_callback(alarm_id_t id, void *parameters) {\n    static float prev = 0;\n    delta_1500ms = swap_16((int16_t)round(*sensor.vario[XBUS_VARIO_ALTITUDE] - prev));\n    prev = *sensor.vario[XBUS_VARIO_ALTITUDE];\n    return 1500000L;\n}\n\nstatic int64_t interval_2000_callback(alarm_id_t id, void *parameters) {\n    static float prev = 0;\n    delta_2000ms = swap_16((int16_t)round(*sensor.vario[XBUS_VARIO_ALTITUDE] - prev));\n    prev = *sensor.vario[XBUS_VARIO_ALTITUDE];\n    return 2000000L;\n}\n\nstatic int64_t interval_3000_callback(alarm_id_t id, void *parameters) {\n    static float prev = 0;\n    delta_3000ms = swap_16((int16_t)round(*sensor.vario[XBUS_VARIO_ALTITUDE] - prev));\n    prev = *sensor.vario[XBUS_VARIO_ALTITUDE];\n    return 3000000L;\n}\n"
  },
  {
    "path": "board/project/protocol/xbus.h",
    "content": "#ifndef XBUS_H\n#define XBUS_H\n\n#include \"common.h\"\n\n#define XBUS_AIRSPEED_ID 0x11\n#define XBUS_ALTIMETER_ID 0x12\n#define XBUS_GPS_LOC_ID 0x16\n#define XBUS_GPS_STAT_ID 0x17\n#define XBUS_ENERGY_ID 0x18\n#define XBUS_ESC_ID 0x20\n#define XBUS_BATTERY_ID 0x34\n#define XBUS_VARIO_ID 0X40\n#define XBUS_RPMVOLTTEMP_ID 0x7E\n#define XBUS_FUEL_FLOW_ID 0x22\n#define XBUS_STRU_TELE_DIGITAL_AIR_ID 0x36\n#define XBUS_TELE_LIPOMON_ID 0x3A  // 6S Cell Monitor\n#define XBUS_TELE_G_METER_ID 0x3B  // 3-axis accelerometer\n#define XBUS_TELE_GYRO_ID 0x1A     // 3D gyro sensor\n#define XBUS_MULTICYL_ID 0x59      // Multi-cylinder sensor\n#define XBUS_GPS_INFO_FLAGS_IS_NORTH_BIT 0\n#define XBUS_GPS_INFO_FLAGS_IS_EAST_BIT 1\n#define XBUS_GPS_INFO_FLAGS_LONG_GREATER_99_BIT 2\n#define XBUS_GPS_INFO_FLAGS_NEGATIVE_ALT_BIT 7\n\ntypedef enum xbus_sensors_t {\n    XBUS_AIRSPEED,\n    XBUS_ALTIMETER,\n    XBUS_GPS_LOC,\n    XBUS_GPS_STAT,\n    XBUS_ESC,\n    XBUS_BATTERY,\n    XBUS_VARIO,\n    XBUS_RPMVOLTTEMP,\n    XBUS_ENERGY,\n    XBUS_FUEL_FLOW,\n    XBUS_STRU_TELE_DIGITAL_AIR,\n    XBUS_TELE_LIPOMON,\n    XBUS_TELE_G_METER,\n    XBUS_TELE_GYRO,\n    XBUS_MULTICYL,\n    XBUS_SENSORS_COUNT\n} xbus_sensors_t;\n\ntypedef enum xbus_airspeed_enum_t { XBUS_AIRSPEED_AIRSPEED, XBUS_AIRSPEED_COUNT } xbus_airspeed_enum_t;\n\ntypedef enum xbus_altitude_enum_t { XBUS_ALTITUDE, XBUS_ALTITUDE_COUNT } xbus_altitude_enum_t;\n\ntypedef enum xbus_gps_loc_enum_t {\n    XBUS_GPS_LOC_ALTITUDE,\n    XBUS_GPS_LOC_LATITUDE,\n    XBUS_GPS_LOC_LONGITUDE,\n    XBUS_GPS_LOC_COURSE,\n    XBUS_GPS_LOC_HDOP,\n    XBUS_GPS_LOC_COUNT\n} xbus_gps_loc_enum_t;\n\ntypedef enum xbus_gps_stat_enum_t {\n    XBUS_GPS_STAT_SPEED,\n    XBUS_GPS_STAT_TIME,\n    XBUS_GPS_STAT_SATS,\n    XBUS_GPS_STAT_ALTITUDE,\n    XBUS_GPS_STAT_FIX_TYPE,\n    XBUS_GPS_STAT_HOME_SET,\n    XBUS_GPS_STAT_COUNT\n} xbus_gps_stat_enum_t;\n\ntypedef enum xbus_energy_enum_t {\n    XBUS_ENERGY_CURRENT1,\n    XBUS_ENERGY_CONSUMPTION1,\n    XBUS_ENERGY_VOLTAGE1,\n    XBUS_ENERGY_CURRENT2,\n    XBUS_ENERGY_CONSUMPTION2,\n    XBUS_ENERGY_VOLTAGE2,\n    XBUS_ENERGY_COUNT\n} xbus_energy_enum_t;\n\ntypedef enum xbus_esc_enum_t {\n    XBUS_ESC_RPM,\n    XBUS_ESC_VOLTAGE,\n    XBUS_ESC_CURRENT,\n    XBUS_ESC_TEMPERATURE_FET,\n    XBUS_ESC_TEMPERATURE_BEC,\n    XBUS_ESC_VOLTAGE_BEC,\n    XBUS_ESC_CURRENT_BEC,\n    XBUS_ESC_COUNT\n} xbus_esc_enum_t;\n\ntypedef enum xbus_battery_enum_t {\n    XBUS_BATTERY_CURRENT1,\n    XBUS_BATTERY_CONSUMPTION1,\n    XBUS_BATTERY_TEMP1,\n    XBUS_BATTERY_CURRENT2,\n    XBUS_BATTERY_CONSUMPTION2,\n    XBUS_BATTERY_TEMP2,\n    XBUS_BATTERY_COUNT\n} xbus_battery_enum_t;\n\ntypedef enum xbus_vario_enum_t { XBUS_VARIO_ALTITUDE, XBUS_VARIO_COUNT } xbus_vario_enum_t;\n\ntypedef enum xbus_rpm_volt_temp_enum_t {\n    XBUS_RPMVOLTTEMP_MS,\n    XBUS_RPMVOLTTEMP_VOLT,\n    XBUS_RPMVOLTTEMP_TEMP,\n    XBUS_RPMVOLTTEMP_COUNT\n} xbus_rpm_volt_temp_enum_t;\n\ntypedef enum xbus_fuel_flow_enum_t {\n    XBUS_FUEL_FLOW_CONSUMED,\n    XBUS_FUEL_FLOW_RATE,\n    XBUS_FUEL_FLOW_COUNT\n} xbus_fuel_flow_enum_t;\n\ntypedef enum xbus_stru_tele_digital_air_enum_t {\n    XBUS_DIGITAL_AIR_FUEL_PRESSURE,\n    XBUS_DIGITAL_AIR_COUNT\n} xbus_stru_tele_digital_air_enum_t;\n\ntypedef enum xbus_tele_lipomon_enum_t {\n    XBUS_TELE_LIPOMON_CELL1,\n    XBUS_TELE_LIPOMON_CELL2,\n    XBUS_TELE_LIPOMON_CELL3,\n    XBUS_TELE_LIPOMON_CELL4,\n    XBUS_TELE_LIPOMON_CELL5,\n    XBUS_TELE_LIPOMON_CELL6,\n    XBUS_TELE_LIPOMON_COUNT\n} xbus_tele_lipomon_enum_t;\n\ntypedef enum xbus_tele_g_meter_enum_t {\n    XBUS_TELE_G_METER_X,\n    XBUS_TELE_G_METER_Y,\n    XBUS_TELE_G_METER_Z,\n    XBUS_TELE_G_METER_MAX_X,\n    XBUS_TELE_G_METER_MAX_Y,\n    XBUS_TELE_G_METER_MAX_Z,\n    XBUS_TELE_G_METER_MIN_Z,\n    XBUS_TELE_G_METER_COUNT\n} xbus_tele_g_meter_enum_t;\n\ntypedef enum xbus_tele_gyro_enum_t {\n    XBUS_TELE_GYRO_PITCH,\n    XBUS_TELE_GYRO_ROLL,\n    XBUS_TELE_GYRO_YAW,\n    XBUS_TELE_GYRO_COUNT\n} xbus_tele_gyro_enum_t;\n\ntypedef enum xbus_multicyl_enum_t {\n    XBUS_MULTICYL_TEMP,\n    XBUS_MULTICYL_VOLTAGE,\n    XBUS_MULTICYL_COUNT\n} xbus_multicyl_enum_t;\n\ntypedef struct xbus_airspeed_t {\n    uint8_t identifier;     // Source device 0x11\n    uint8_t s_id;           // Secondary ID\n    uint16_t airspeed;      // 1 km/h increments\n    uint16_t max_airspeed;  // 1 km/h increments\n} xbus_airspeed_t;\n\ntypedef struct xbus_altitude_t {\n    uint8_t identifier;    // Source device 0x12\n    uint8_t s_id;          // Secondary ID\n    int16_t altitude;      // .1m increments\n    int16_t max_altitude;  // .1m increments\n} xbus_altitude_t;\n\ntypedef struct xbus_gps_loc_t {\n    uint8_t identifier;     // Source device 0x16\n    uint8_t s_id;           // Secondary ID\n    uint16_t altitude_low;  // BCD, meters, format 3.1 (Low bits of alt)\n    uint32_t latitude;      // BCD, format 4.4, // Degrees * 100 + minutes, < 100 degrees 1234.1234\n    uint32_t longitude;     // BCD, format 4.4, // Degrees * 100 + minutes, flag --> > 99deg\n    uint16_t course;        // BCD, 3.1\n    uint8_t hdop;           // BCD, format 1.1\n    uint8_t gps_flags;      // see definitions below\n} xbus_gps_loc_t;\n\ntypedef struct xbus_gps_stat_t {\n    uint8_t identifier;     // Source device 0x17\n    uint8_t s_id;           // Secondary ID\n    uint16_t speed;         // BCD, knots, format 3.1\n    uint32_t utc;           // BCD, format HH:MM:SS.SS, format 6.2\n    uint8_t num_sats;       // BCD, 0-99\n    uint8_t altitude_high;  // BCD, meters, format 2.0 (High bits alt)\n} xbus_gps_stat_t;\n\ntypedef struct xbus_energy_t {\n    uint8_t identifier;     // Source device 0x18\n    uint8_t s_id;           // Secondary ID\n    int16_t current_a;      // Instantaneous current, 0.01A (0-327.7A)\n    int16_t charge_used_a;  // Integrated mAh used, 0.1mAh (0-3276.6mAh)\n    uint16_t volts_a;       // Voltage, 0.01VC (0-16.00V)\n    int16_t current_b;      // Instantaneous current, 0.01A (0-327.7A)\n    int16_t charge_used_b;  // Integrated mAh used, 0.1mAh (0-3276.6mAh)\n    uint16_t volts_b;       // Voltage, 0.01VC (0-16.00V)\n} xbus_energy_t;\n\ntypedef struct xbus_esc_t {\n    uint8_t identifier;      // Source device 0x20\n    uint8_t s_id;            // Secondary ID\n    uint16_t rpm;            // RPM, 10RPM (0-655340 RPM).0xFFFF -->\n    uint16_t volts_input;    // Volts, 0.01v (0-655.34V).0xFFFF -->\n    uint16_t temp_fet;       // Temperature, 0.1C (0-999.8C)0xFFFF -->\n    uint16_t current_motor;  // Current, 10mA (0-655.34A).0xFFFF -->\n    uint16_t temp_bec;       // Temperature, 0.1C (0-999.8C)0x7FFF -->\n    uint8_t current_bec;     // BEC Current, 100mA (0-25.4A). 0xFF ---->\n    uint8_t voltage_bec;     // BEC Volts, 0.05V (0-12.70V). 0xFF ---->\n    uint8_t throttle;        // 0.5% (0-127%). 0xFF ---->\n    uint8_t power_out;       // Power Output, 0.5% (0-127%). 0xFF ---->\n} xbus_esc_t;\n\ntypedef struct xbus_battery_t {\n    uint8_t identifier;     // Source device 0x34\n    uint8_t s_id;           // Secondary ID\n    int16_t current_a;      // Instantaneous current, 0.1A (0-3276.8A)\n    int16_t charge_used_a;  // Integrated mAh used, 1mAh (0-32.766Ah)\n    uint16_t temp_a;        // Temperature, 0.1C (0-150.0C, // 0x7FFF indicates not populated)\n    int16_t current_b;      // Instantaneous current, 0.1A (0-6553.4A)\n    int16_t charge_used_b;  // Integrated mAh used, 1mAh (0-65.534Ah)\n    uint16_t temp_b;        // Temperature, 0.1C (0-150.0C,// 0x7FFF indicates not populated)\n} xbus_battery_t;\n\ntypedef struct xbus_vario_t {\n    uint8_t identifier;    // Source device 0x40\n    uint8_t s_id;          // Secondary ID\n    int16_t altitude;      // .1m increments\n    int16_t delta_0250ms,  // delta last 250ms, 0.1m/s increments\n        delta_0500ms,      // delta last 500ms, 0.1m/s increments\n        delta_1000ms,      // delta last 1.0 seconds\n        delta_1500ms,      // delta last 1.5 seconds\n        delta_2000ms,      // delta last 2.0 seconds\n        delta_3000ms;      // delta last 3.0 seconds\n} xbus_vario_t;\n\ntypedef struct xbus_rpm_volt_temp_t {\n    uint8_t identifier;  // Source device 0x7E\n    uint8_t s_id;\n    uint16_t microseconds;  // microseconds between pulse leading edges\n    uint16_t volts;         // 0.01V increments\n    int16_t temperature;    // degrees F\n} xbus_rpm_volt_temp_t;\n\ntypedef struct xbus_fuel_flow_t {\n    uint8_t identifier;        // Source device = 0x22\n    uint8_t s_id;              // Secondary ID\n    uint16_t fuel_consumed_A;  // Integrated fuel consumption, 0.1mL\n    uint16_t flow_rate_A;      // Instantaneous consumption, 0.01mL/min\n    uint16_t temp_A;           // Temperature, 0.1C (0-655.34C)\n    uint16_t fuel_consumed_B;  // Integrated fuel consumption, 0.1mL\n    uint16_t flow_rate_B;      // Instantaneous consumption, 0.01mL/min\n    uint16_t temp_B;           // Temperature, 0.1C (0-655.34C)\n    uint16_t spare;            // Not used\n} xbus_fuel_flow_t;\n\ntypedef struct xbus_stru_tele_digital_air_t {\n    uint8_t identifier;  // Source device = 0x36\n    uint8_t sID;         // Secondary ID\n    uint16_t digital;    // Digital inputs (bit per input)\n    uint16_t pressure;   // Tank pressure, 0.1PSI (0-6553.4PSI)\n} xbus_stru_tele_digital_air_t;\n\ntypedef struct xbus_tele_lipomon_t {\n    uint8_t identifier;  // Source device = 0x3A\n    uint8_t sID;         // Secondary ID\n    uint16_t cell[6];    // Voltage across cell 1, .01V steps 0x7FFF --> cell not present\n    uint8_t temp;        // Temperature, 0.1C (0-655.34C)\n} xbus_tele_lipomon_t;\n\ntypedef struct xbus_tele_g_meter_t {\n    uint8_t identifier;  // Source device = 0x14\n    uint8_t sID;         // Secondary ID\n    int16_t GForceX;     // force is reported as .01G increments\n    int16_t GForceY;     // \t\tRange = +/-4000 (+/- 40G) in Pro model\n    int16_t GForceZ;     // \t\tRange = +/-800 (+/- 8G) in Standard model\n    int16_t maxGForceX;  // abs(max G X-axis)   FORE/AFT\n    int16_t maxGForceY;  // abs (max G Y-axis)  LEFT/RIGHT\n    int16_t maxGForceZ;  // max G Z-axis        WING SPAR LOAD\n    int16_t minGForceZ;  // min G Z-axis        WING SPAR LOAD\n} xbus_tele_g_meter_t;\n\ntypedef struct xbus_tele_gyro_t {\n    uint8_t identifier;  // Source device = 0x1A\n    uint8_t sID;         // Secondary ID\n    int16_t gyroX;  // Units are 0.1 deg/sec  - Rate is about the X Axis which is defined out the nose of the vehicle.\n    int16_t gyroY;  // Rate is about the Y Axis which is define out the right wing of the vehicle.\n    int16_t gyroZ;  // Rate is about the Z axis which is defined down from the vehicle.\n    int16_t maxGyroX;  // Max rates (absolute value)\n    int16_t maxGyroY;\n    int16_t maxGyroZ;\n} xbus_tele_gyro_t;\n\ntypedef struct xbus_multi_cyl_t {\n    uint8_t identifier;  // Source device = TELE_DEVICE_MULTICYLINDER = 0x7F\n    uint8_t sID;         // Secondary ID\n    uint8_t\n        temperature[9];   // Temperature, 1C increments, Offset = 30C, 0xFF = NO DATA\n                          // 0x00 = 30C\t\t(86F)\n                          // 0x01 = 31C ...\t(88F)\n                          // 0xFE = 284C\t\t(543F)\n                          // 0xFF = NO SENSOR ATTACHED.  Note that sensors must be installed cylinder 1-9 in sequence!\n    uint8_t throttlePct;  // Throttle percent (0-100% typical, 0xFF = NO DATA)\n    uint16_t RPM;         // 4 RPM increments, Offset = 400RPM, range 404-16776.\n                          // 0x000 = 0 RPM\n                          // 0x001 = 404 RPM\n                          // 0x002 = 408 RPM\n                          // 0xFFE = 16776 RPM\n                          // 0xFFF = NO SENSOR ATTACHED\n                          // NOTE:  HI NYBBLE RESERVED, set to 0xF to mark \"NO DATA\" for now\n    uint8_t batteryV;     // Voltage, 0.1V increments, Offset = 3.5V, 0xFF = NO DATA\n                          // 0x00 = 3.5V\n                          // 0x01 = 3.6V\n                          // 0xFE = 28.9V\n                          // 0xFF = NO SENSOR ATTACHED\n    uint8_t spare;        // 0xFF --> no data\n} xbus_multi_cyl_t;\n\ntypedef struct xbus_sensor_gps_loc_t {\n    float *altitude_low;\n    float *latitude;\n    float *longitude;\n    float *course;\n    float *hdop;\n    uint8_t *fix_type;\n    uint8_t *home_set;\n} xbus_sensor_gps_loc_t;\n\ntypedef struct xbus_sensor_t {\n    bool is_enabled[XBUS_SENSORS_COUNT];\n    float *airspeed[XBUS_AIRSPEED_COUNT];\n    float *altimeter[XBUS_ALTITUDE_COUNT];\n    xbus_sensor_gps_loc_t *gps_loc;\n    float *gps_stat[XBUS_GPS_STAT_COUNT];\n    float *esc[XBUS_ESC_COUNT];\n    float *battery[XBUS_BATTERY_COUNT];\n    float *vario[XBUS_VARIO_COUNT];\n    float *rpm_volt_temp[XBUS_RPMVOLTTEMP_COUNT];\n    float *energy[XBUS_ENERGY_COUNT];\n    float *fuel_flow[XBUS_FUEL_FLOW_COUNT];\n    float *stru_tele_digital_air[XBUS_DIGITAL_AIR_COUNT];\n    float *tele_lipomon[XBUS_TELE_LIPOMON_COUNT];\n    float *tele_g_meter[XBUS_TELE_G_METER_COUNT];\n    float *tele_gyro[XBUS_TELE_GYRO_COUNT];\n    float *multicyl[XBUS_MULTICYL_COUNT];\n} xbus_sensor_t;\n\nextern context_t context;\nextern xbus_sensor_t sensor;\n\nvoid xbus_task(void *parameters);\nvoid xbus_format_sensor(uint8_t address, uint8_t *buffer);\nvoid xbus_set_config(void);\n#ifdef SIM_RX\nvoid xbus_i2c_handler(uint8_t address);\n#endif\n\n#endif"
  },
  {
    "path": "board/project/sensor/CMakeLists.txt",
    "content": "target_sources(${PROJECT_NAME} PRIVATE\r\n    airspeed.c\r\n    esc_hw3.c\r\n    voltage.c\r\n    esc_hw4.c\r\n    esc_kontronik.c\r\n    esc_apd_f.c\r\n    esc_apd_hv.c\r\n    gps.c\r\n    vspeed.c\r\n    distance.c\r\n    esc_pwm.c\r\n    bmp280.c\r\n    ms5611.c\r\n    bmp180.c\r\n    cell_count.c\r\n    auto_offset.c\r\n    esc_hw5.c\r\n    esc_castle.c\r\n    pwm_out.c\r\n    ntc.c\r\n    current.c\r\n    fuel_meter.c\r\n    xgzp68xxd.c\r\n    gpio.c\r\n    smart_esc.c\r\n    esc_omp_m4.c\r\n    esc_ztw.c\r\n    esc_openyge.c\r\n    mpu6050.c\r\n    ina3221.c\r\n    )\r\n"
  },
  {
    "path": "board/project/sensor/airspeed.c",
    "content": "#include \"airspeed.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"hardware/adc.h\"\n#include \"pico/stdlib.h\"\n\n/* If there is not barometer sensor installed, values used to calculate air density are:\n   Nominal pressure at sea level 101325 hPa.\n   Defalut temperature value 20ºC\n*/\n\n#define KNOT_TO_KMH 1.94384\n#define AIR_CONSTANT_R 287.05  // J/(kg.K)\n\nvoid airspeed_task(void *parameters) {\n    airspeed_parameters_t parameter = *(airspeed_parameters_t *)parameters;\n    adc_init();\n    adc_gpio_init(parameter.adc_num + 26);\n    // gpio_pull_down(parameter.adc_num + 26);\n    *parameter.airspeed = 0;\n    xTaskNotifyGive(context.receiver_task_handle);\n    float temperature, pressure, delta_pressure, air_density, airspeed;\n    static float voltage = 0;\n\n    while (1) {\n        if (!parameter.temperature)\n            temperature = 20;\n        else\n            temperature = *parameter.temperature;  // ºC\n        if (!parameter.pressure)\n            pressure = 101325;\n        else\n            pressure = *parameter.pressure;  // Pa\n        voltage = voltage_read(parameter.adc_num);\n        if (pressure < 1000) pressure = 101325;\n        air_density = pressure / (AIR_CONSTANT_R * (temperature + 273.15));                // kg/m3\n        delta_pressure = ((voltage + parameter.offset) / parameter.vcc - 0.5) * 5 * 1000;  // Pa\n        // Formula: speed = sqrt(2*P/air_dens) -> Units: P (Pa=N/m2), air_dens (kg/m3), N (kg*m/s2) -> speed =\n        // sqrt(kg*m/s2/m2*m3/kg) = sqrt(m2/s2) = m/s\n        if (delta_pressure < 0)\n            airspeed = -1 * sqrt(-2 * delta_pressure / air_density);\n        else\n            airspeed = sqrt(2 * delta_pressure / air_density);\n        airspeed *= 3.6;  // m/s to km/h\n        *parameter.airspeed = get_average(parameter.alpha, *parameter.airspeed, airspeed);\n#ifdef SIM_SENSORS\n        *parameter.airspeed = 123.34;\n#endif\n        debug(\"\\nAirspeed (%u): %.2f (Vcc %.2f Offset %.0f Vout %.3f Press %.0f Temp %.2f AirDens %.2f dPress %.0f)\",\n              uxTaskGetStackHighWaterMark(NULL), *parameter.airspeed, parameter.vcc, parameter.offset * 1000, voltage,\n              pressure, temperature, air_density, delta_pressure);\n\n        vTaskDelay(1000 / parameter.rate / portTICK_PERIOD_MS);\n    }\n}\n"
  },
  {
    "path": "board/project/sensor/airspeed.h",
    "content": "#ifndef AIRSPEED_H\n#define AIRSPEED_H\n\n#include \"common.h\"\n\ntypedef struct airspeed_parameters_t {\n    uint8_t adc_num;\n    uint8_t rate;\n    float alpha;\n    float offset;        // accuracy = ±6.25% VFSS\n    float vcc;           // nominal is 5V\n    float *temperature;  // air temp (input from baro)\n    float *pressure;     // air pressure (input from baro)\n    float *airspeed;\n} airspeed_parameters_t;\n\nextern context_t context;\n\nvoid airspeed_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/auto_offset.c",
    "content": "#include \"auto_offset.h\"\n\n#include <stdio.h>\n\n#include \"pico/stdlib.h\"\n\nvoid auto_offset_float_task(void *parameters) {\n    auto_offset_float_parameters_t parameter = *(auto_offset_float_parameters_t *)parameters;\n    vTaskDelay(parameter.delay / portTICK_PERIOD_MS);\n    *parameter.offset = *parameter.value;\n    debug(\"\\nAuto offset float (%u): %.2f\", uxTaskGetStackHighWaterMark(NULL), *parameter.offset);\n    vTaskDelete(NULL);\n}\n\nvoid auto_offset_int_task(void *parameters) {\n    auto_offset_int_parameters_t parameter = *(auto_offset_int_parameters_t *)parameters;\n    vTaskDelay(parameter.delay / portTICK_PERIOD_MS);\n    *parameter.offset = *parameter.value;\n    int val = *parameter.value;\n    debug(\"\\nAuto offset int (%u): %d\", uxTaskGetStackHighWaterMark(NULL), *parameter.offset);\n    vTaskDelete(NULL);\n}"
  },
  {
    "path": "board/project/sensor/auto_offset.h",
    "content": "#ifndef AUTO_OFFSET_H\n#define AUTO_OFFSET_H\n\n#include \"common.h\"\n\ntypedef struct auto_offset_float_parameters_t {\n    uint delay;\n    float *value;\n    float *offset;\n\n} auto_offset_float_parameters_t;\n\ntypedef struct auto_offset_int_parameters_t {\n    uint delay;\n    int *value;\n    int *offset;\n\n} auto_offset_int_parameters_t;\n\nextern context_t context;\n\nvoid auto_offset_float_task(void *parameters);\nvoid auto_offset_int_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/bmp180.c",
    "content": "#include \"bmp180.h\"\n\n#include <stdio.h>\n\n#include \"auto_offset.h\"\n#include \"hardware/i2c.h\"\n#include \"pico/stdlib.h\"\n#include \"vspeed.h\"\n\n#define REGISTER_DIG_AC1 0xAA\n#define REGISTER_DIG_AC2 0xAC\n#define REGISTER_DIG_AC3 0xAD\n#define REGISTER_DIG_AC4 0xB0\n#define REGISTER_DIG_AC5 0xB2\n#define REGISTER_DIG_AC6 0xB4\n#define REGISTER_DIG_B1 0xB6\n#define REGISTER_DIG_B2 0xB8\n#define REGISTER_DIG_MB 0xBA\n#define REGISTER_DIG_MC 0xBC\n#define REGISTER_DIG_MD 0xBE\n#define REGISTER_CONTROL 0xF4\n#define REGISTER_CHIPID 0xD0\n#define REGISTER_SOFTRESET 0xE0\n#define REGISTER_DATA 0xF6\n#define READ_TEMPERATURE 0x2E\n#define READ_PRESSURE 0x34\n#define OVERSAMPLING_0 0\n#define OVERSAMPLING_1 1\n#define OVERSAMPLING_2 2\n#define OVERSAMPLING_3 3\n#define READ_PRESSURE_OVERSAMPLING_1 0x74\n#define READ_PRESSURE_OVERSAMPLING_2 0xB4\n#define READ_PRESSURE_OVERSAMPLING_3 0xF4\n\n#define I2C_ADDRESS 0x77\n#define PRESSURE_INTERVAL_MS 40     // min 30\n#define TEMPERATURE_INTERVAL_MS 20  // min 10\n#define VSPEED_INTERVAL_MS 500\n\nstatic void read(bmp180_parameters_t *parameter, bmp180_calibration_t *calibration);\nstatic void begin(bmp180_parameters_t *parameter, bmp180_calibration_t *calibration);\n\nvoid bmp180_task(void *parameters) {\n    bmp180_parameters_t parameter = *(bmp180_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    *parameter.altitude = 0;\n    *parameter.vspeed = 0;\n    *parameter.temperature = 0;\n    *parameter.pressure = 0;\n\n    TaskHandle_t task_handle;\n\n    vTaskDelay(500 / portTICK_PERIOD_MS);\n    bmp180_calibration_t calibration;\n    begin(&parameter, &calibration);\n    while (1) {\n        read(&parameter, &calibration);\n        debug(\"\\nBMP180 (%u) < Temp: %.2f Pressure: %.0f Altitude: %.2f Vspeed: %.2f\",\n              uxTaskGetStackHighWaterMark(NULL), *parameter.temperature, *parameter.pressure, *parameter.altitude,\n              *parameter.vspeed);\n    }\n}\n\nstatic void read(bmp180_parameters_t *parameter, bmp180_calibration_t *calibration) {\n    uint8_t register_address, register_value;\n    uint8_t data[3];\n    int32_t X1, X2, X3, B5, T, UT, UP, B6, B3, p;\n    uint32_t B4, B7;\n    static float pressure_initial = 0;\n    static uint discard_readings = 5;\n\n    data[0] = REGISTER_CONTROL;\n    data[1] = READ_TEMPERATURE;\n    i2c_write_blocking(i2c0, I2C_ADDRESS, data, 2, false);\n    vTaskDelay(TEMPERATURE_INTERVAL_MS / portTICK_PERIOD_MS);\n    data[0] = REGISTER_DATA;\n    i2c_write_blocking(i2c0, I2C_ADDRESS, data, 1, true);\n    i2c_read_blocking(i2c0, I2C_ADDRESS, data, 2, false);\n    UT = data[0] << 8 | data[1];\n    X1 = ((UT - calibration->AC6) * calibration->AC5) >> 15;\n    X2 = (calibration->MC << 11) / (X1 + calibration->MD);\n    B5 = X1 + X2;\n    T = (B5 + 8) >> 4;\n    *parameter->temperature =\n        (float)T / 10;  // C     calcAverage((float)alphaTemp_ / 100, temperature_, (float)T / 10);\n\n    data[0] = REGISTER_CONTROL;\n    data[1] = READ_PRESSURE | (OVERSAMPLING_3 << 6);\n    i2c_write_blocking(i2c0, I2C_ADDRESS, data, 2, false);\n    vTaskDelay(PRESSURE_INTERVAL_MS / portTICK_PERIOD_MS);\n    data[0] = REGISTER_DATA;\n    i2c_write_blocking(i2c0, I2C_ADDRESS, data, 1, true);\n    i2c_read_blocking(i2c0, I2C_ADDRESS, data, 3, false);\n    UP = ((uint32_t)data[0] << 16 | (uint16_t)data[1] << 8 | data[2]) >> (8 - OVERSAMPLING_3);\n    B6 = B5 - 4000;\n    X1 = (calibration->B2 * ((B6 * B6) >> 12)) >> 11;\n    X2 = (calibration->AC2 * B6) >> 11;\n    X3 = X1 + X2;\n    B3 = (((calibration->AC1 * 4 + X3) << OVERSAMPLING_3) + 2) / 4;\n    X1 = (calibration->AC3 * B6) >> 13;\n    X2 = (calibration->B1 * ((B6 * B6) >> 12)) >> 16;\n    X3 = ((X1 + X2) + 2) >> 2;\n    B4 = calibration->AC4 * (uint32_t)(X3 + 32768) >> 15;\n    B7 = (uint32_t)(UP - B3) * (50000 >> OVERSAMPLING_3);\n    if (B7 < 0x80000000) {\n        p = B7 * 2 / B4;\n    } else {\n        p = B7 * B4 / 2;\n    }\n    X1 = (p >> 8) * (p >> 8);\n    X1 = (X1 * 3038) >> 16;\n    X2 = (-7357 * p) >> 16;\n    p = p + ((X1 + X2 + 3791) >> 4);\n\n    *parameter->pressure = p;  // Pa    calcAverage((float)alphaVario_ / 100, pressure_, p);\n    if (pressure_initial == 0 && discard_readings == 0) pressure_initial = *parameter->pressure;\n    *parameter->altitude = get_altitude(*parameter->pressure, *parameter->temperature, pressure_initial);\n    get_vspeed(parameter->vspeed, *parameter->altitude, VSPEED_INTERVAL_MS);\n    if (discard_readings > 0) discard_readings--;\n    debug(\"\\nBMP180 P0: %.0f\", pressure_initial);\n#ifdef SIM_SENSORS\n    *parameter->temperature = 12.34;\n    *parameter->pressure = 1234.56;\n    *parameter->altitude = 2345;\n#endif\n}\n\nstatic void begin(bmp180_parameters_t *parameter, bmp180_calibration_t *calibration) {\n    i2c_init(i2c0, 100 * 1000);\n    gpio_set_function(I2C0_SDA_GPIO, GPIO_FUNC_I2C);\n    gpio_set_function(I2C0_SCL_GPIO, GPIO_FUNC_I2C);\n    gpio_pull_up(I2C0_SDA_GPIO);\n    gpio_pull_up(I2C0_SCL_GPIO);\n\n    uint8_t data[22];\n    data[0] = REGISTER_DIG_AC1;\n    i2c_write_blocking(i2c0, I2C_ADDRESS, data, 1, true);\n    i2c_read_blocking(i2c0, I2C_ADDRESS, data, 22, false);\n    calibration->AC1 = ((uint16_t)data[0] << 8) | data[1];\n    calibration->AC2 = ((uint16_t)data[2] << 8) | data[3];\n    calibration->AC3 = ((uint16_t)data[4] << 8) | data[5];\n    calibration->AC4 = ((uint16_t)data[6] << 8) | data[7];\n    calibration->AC5 = ((uint16_t)data[8] << 8) | data[9];\n    calibration->AC6 = ((uint16_t)data[10] << 8) | data[11];\n    calibration->B1 = ((uint16_t)data[12] << 8) | data[13];\n    calibration->B2 = ((uint16_t)data[14] << 8) | data[15];\n    calibration->MB = ((uint16_t)data[16] << 8) | data[17];\n    calibration->MC = ((uint16_t)data[18] << 8) | data[19];\n    calibration->MD = ((uint16_t)data[20] << 8) | data[21];\n}\n"
  },
  {
    "path": "board/project/sensor/bmp180.h",
    "content": "#ifndef BMP180_H\n#define BMP180_H\n\n#include \"common.h\"\n\ntypedef struct bmp180_parameters_t {\n    float alpha_vario;\n    bool auto_offset;\n    float *temperature, *pressure, *altitude, *vspeed;\n} bmp180_parameters_t;\n\ntypedef struct bmp180_calibration_t {\n    int16_t AC1, AC2, AC3, B1, B2, MB, MC, MD;\n    uint16_t AC4, AC5, AC6;\n} bmp180_calibration_t;\n\nextern context_t context;\n\nvoid bmp180_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/bmp280.c",
    "content": "#include \"bmp280.h\"\n\n#include <stdio.h>\n\n#include \"auto_offset.h\"\n#include \"hardware/i2c.h\"\n#include \"pico/stdlib.h\"\n#include \"vspeed.h\"\n\n#define REGISTER_DIG_T1 0x88\n#define REGISTER_DIG_T2 0x8A\n#define REGISTER_DIG_T3 0x8C\n#define REGISTER_DIG_P1 0x8E\n#define REGISTER_DIG_P2 0x90\n#define REGISTER_DIG_P3 0x92\n#define REGISTER_DIG_P4 0x94\n#define REGISTER_DIG_P5 0x96\n#define REGISTER_DIG_P6 0x98\n#define REGISTER_DIG_P7 0x9A\n#define REGISTER_DIG_P8 0x9C\n#define REGISTER_DIG_P9 0x9E\n#define REGISTER_CHIPID 0xD0\n#define REGISTER_VERSION 0xD1\n#define REGISTER_SOFTRESET 0xE0\n#define REGISTER_CAL26 0xE1\n#define REGISTER_STATUS 0xF3\n#define REGISTER_CONTROL 0xF4\n#define REGISTER_CONFIG 0xF5\n#define REGISTER_PRESSUREDATA 0xF7\n#define REGISTER_TEMPDATA 0xFA\n\n#define OVERSAMPLING_X0 0\n#define OVERSAMPLING_X1 1\n#define OVERSAMPLING_X2 2\n#define OVERSAMPLING_X4 3\n#define OVERSAMPLING_X8 4\n#define OVERSAMPLING_X16 5\n\n#define SLEEP 0\n#define FORCED 1\n#define NORMAL 3\n\n#define FILTER_OFF 0\n#define FILTER_X2 1\n#define FILTER_X4 2\n#define FILTER_X8 3\n#define FILTER_X16 4\n\n#define STANDBY_MS_1 0x00\n#define STANDBY_MS_63 0x01\n#define STANDBY_MS_125 0x02\n#define STANDBY_MS_250 0x03\n#define STANDBY_MS_500 0x04\n#define STANDBY_MS_1000 0x05\n#define STANDBY_MS_2000 0x06\n#define STANDBY_MS_4000 0x07\n\n#define I2C_ADDRESS_1 0x76\n#define I2C_ADDRESS_2 0x77\n#define SENSOR_INTERVAL_MS 40  // min 30\n#define VSPEED_INTERVAL_MS 500\n\nstatic void read(bmp280_parameters_t *parameter, bmp280_calibration_t *calibration);\nstatic void begin(bmp280_parameters_t *parameter, bmp280_calibration_t *calibration);\n\nvoid bmp280_task(void *parameters) {\n    bmp280_parameters_t parameter = *(bmp280_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    *parameter.altitude = 0;\n    *parameter.vspeed = 0;\n    *parameter.temperature = 0;\n    *parameter.pressure = 0;\n\n    TaskHandle_t task_handle;\n\n    bmp280_calibration_t calibration;\n    vTaskDelay(500 / portTICK_PERIOD_MS);\n    \n    begin(&parameter, &calibration);\n    while (1) {\n        read(&parameter, &calibration);\n        debug(\"\\nBMP280 (%u) < Temp: %.2f Pressure: %.0f Altitude: %0.2f Vspeed: %.2f\",\n              uxTaskGetStackHighWaterMark(NULL), *parameter.temperature, *parameter.pressure, *parameter.altitude,\n              *parameter.vspeed);\n        vTaskDelay(SENSOR_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void read(bmp280_parameters_t *parameter, bmp280_calibration_t *calibration) {\n    int64_t var1, var2, p;\n    uint8_t data[3];\n    uint32_t adc_T, adc_P, t_fine, t;\n    static float pressure_initial = 0;\n    static uint discard_readings = 5;\n\n    data[0] = REGISTER_TEMPDATA;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    // vTaskDelay(50 / portTICK_PERIOD_MS);\n    i2c_read_blocking(i2c0, parameter->address, data, 3, false);\n    adc_T = (((uint32_t)data[0] << 16) | ((uint16_t)data[1] << 8) | data[2]) >> 4;\n    var1 = ((((adc_T >> 3) - ((int32_t)calibration->T1 << 1))) * ((int32_t)calibration->T2)) >> 11;\n    var2 = (int64_t)(((((adc_T >> 4) - ((int32_t)calibration->T1)) * ((adc_T >> 4) - ((int32_t)calibration->T1))))) *\n               (calibration->T3) >>\n           26;\n    t_fine = var1 + var2;\n    t = (t_fine * 5 + 128) >> 8;\n    *parameter->temperature = (float)t / 100;\n\n    data[0] = REGISTER_PRESSUREDATA;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 3, false);\n    adc_P = (((uint32_t)data[0] << 16) | ((uint16_t)data[1] << 8) | data[2]) >> 4;\n    var1 = ((int64_t)t_fine) - 128000;\n    var2 = var1 * var1 * (int64_t)calibration->P6;\n    var2 = var2 + ((var1 * (int64_t)calibration->P5) << 17);\n    var2 = var2 + (((int64_t)calibration->P4) << 35);\n    var1 = ((var1 * var1 * (int64_t)calibration->P3) >> 8) + ((var1 * (int64_t)calibration->P2) << 12);\n    var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)calibration->P1) >> 33;\n\n    if (var1 != 0) {\n        p = 1048576 - adc_P;\n        p = (((p << 31) - var2) * 3125) / var1;\n        var1 = (((int64_t)calibration->P9) * (p >> 13) * (p >> 13)) >> 25;\n        var2 = (((int64_t)calibration->P8) * p) >> 19;\n        p = ((p + var1 + var2) >> 8) + (((int64_t)calibration->P7) << 4);\n        *parameter->pressure = (float)p / 256;  // Pa\n        // pressure_ = calcAverage((float)alphaVario_ / 100, pressure_, (float)p / 256);\n    }\n\n    if (pressure_initial == 0 && discard_readings == 0) pressure_initial = *parameter->pressure;\n    *parameter->altitude = get_altitude(*parameter->pressure, *parameter->temperature, pressure_initial);\n    get_vspeed(parameter->vspeed, *parameter->altitude, VSPEED_INTERVAL_MS);\n    if (discard_readings > 0) discard_readings--;\n    debug(\"\\nBMP280 P0: %.0f\", pressure_initial);\n#ifdef SIM_SENSORS\n    *parameter->temperature = 12.34;\n    *parameter->pressure = 1234.56;\n    *parameter->altitude = 2345;\n#endif\n}\n\nstatic void begin(bmp280_parameters_t *parameter, bmp280_calibration_t *calibration) {\n    i2c_init(i2c0, 100 * 1000);\n    gpio_set_function(I2C0_SDA_GPIO, GPIO_FUNC_I2C);\n    gpio_set_function(I2C0_SCL_GPIO, GPIO_FUNC_I2C);\n    gpio_pull_up(I2C0_SDA_GPIO);\n    gpio_pull_up(I2C0_SCL_GPIO);\n\n    uint8_t data[24] = {0};\n\n    // Find sensor address\n    parameter->address = I2C_ADDRESS_1;\n    if (i2c_write_blocking(i2c0, I2C_ADDRESS_1, data, 1, false) == PICO_ERROR_GENERIC) {\n        parameter->address = I2C_ADDRESS_2;\n    }\n\n    // Configure sensor\n    data[0] = REGISTER_CONTROL;\n    data[1] = (OVERSAMPLING_X2 << 5) | (OVERSAMPLING_X16 << 2) | NORMAL;\n    i2c_write_blocking(i2c0, parameter->address, data, 2, false);\n\n    data[0] = REGISTER_DIG_T1;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 24, false);\n    calibration->T1 = ((uint16_t)data[1] << 8) | data[0];\n    calibration->T2 = ((uint16_t)data[3] << 8) | data[2];\n    calibration->T3 = ((uint16_t)data[5] << 8) | data[4];\n    calibration->P1 = ((uint16_t)data[7] << 8) | data[6];\n    calibration->P2 = ((uint16_t)data[9] << 8) | data[8];\n    calibration->P3 = ((uint16_t)data[11] << 8) | data[10];\n    calibration->P4 = ((uint16_t)data[13] << 8) | data[12];\n    calibration->P5 = ((uint16_t)data[15] << 8) | data[14];\n    calibration->P6 = ((uint16_t)data[17] << 8) | data[16];\n    calibration->P7 = ((uint16_t)data[19] << 8) | data[18];\n    calibration->P8 = ((uint16_t)data[21] << 8) | data[20];\n    calibration->P9 = ((uint16_t)data[23] << 8) | data[22];\n}\n"
  },
  {
    "path": "board/project/sensor/bmp280.h",
    "content": "#ifndef BMP280_H\n#define BMP280_H\n\n#include \"common.h\"\n\ntypedef struct bmp280_parameters_t {\n    float alpha_vario;\n    bool auto_offset;\n    uint8_t address;\n    uint8_t filter;\n    float *temperature, *pressure, *altitude, *vspeed;\n} bmp280_parameters_t;\n\ntypedef struct bmp280_calibration_t {\n    uint16_t T1, P1;\n    int16_t T2, T3, P2, P3, P4, P5, P6, P7, P8, P9;\n} bmp280_calibration_t;\n\nextern context_t context;\n\nvoid bmp280_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/cell_count.c",
    "content": "#include \"cell_count.h\"\n\n#include <stdio.h>\n\n#include \"pico/stdlib.h\"\n\nvoid cell_count_task(void *parameters) {\n    cell_count_parameters_t parameter = *(cell_count_parameters_t *)parameters;\n    vTaskDelay(parameter.delay / portTICK_PERIOD_MS);\n\n    float level[] = {0, 4.35, 8.7, 13.05, 17.4, 21.75, 26.1, 30.45, 34.8, 34.8, 43.5, 43.5};\n    int cont = 11;\n\n    float voltage = *parameter.voltage;\n    while (voltage < level[cont] && cont > 0) {\n        cont--;\n    }\n    *parameter.cell_count = cont + 1;\n    debug(\"\\nCell count (%u): (%.2fV) %i\", uxTaskGetStackHighWaterMark(NULL), voltage, *parameter.cell_count);\n    vTaskDelete(NULL);\n}"
  },
  {
    "path": "board/project/sensor/cell_count.h",
    "content": "#ifndef CELL_COUNT_H\n#define CELL_COUNT_H\n\n#include \"common.h\"\n\ntypedef struct cell_count_parameters_t {\n    uint delay;\n    float *voltage;\n    uint8_t *cell_count;\n} cell_count_parameters_t;\n\nextern context_t context;\n\nvoid cell_count_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/current.c",
    "content": "\n#include \"current.h\"\n\n#include <stdio.h>\n\n#include \"auto_offset.h\"\n#include \"hardware/adc.h\"\n#include \"pico/stdlib.h\"\n\nvoid current_task(void *parameters) {\n    static uint32_t timestamp = 0;\n    current_parameters_t parameter = *(current_parameters_t *)parameters;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.consumption = 0;\n    xTaskNotifyGive(context.receiver_task_handle);\n    adc_init();\n    adc_gpio_init(parameter.adc_num + 26);\n    //gpio_pull_down(parameter.adc_num + 26);\n    if (parameter.auto_offset) {\n        parameter.offset = -1;\n        auto_offset_float_parameters_t parameter_auto_offset = {5000, parameter.voltage, &parameter.offset};\n        TaskHandle_t task_handle;\n        xTaskCreate(auto_offset_float_task, \"analog_current_auto_offset_task\", STACK_AUTO_OFFSET,\n                    &parameter_auto_offset, 2, &task_handle);\n    }\n\n    while (1) {\n        *parameter.voltage = voltage_read(parameter.adc_num);\n        if (parameter.offset != -1)\n            *parameter.current = get_average(parameter.alpha, *parameter.current,\n                                             (*parameter.voltage - parameter.offset) * parameter.multiplier);\n\n        if (time_us_32() > 6000000) {\n            *parameter.consumption += get_consumption(*parameter.current, 0, &timestamp);\n        }\n#ifdef SIM_SENSORS\n        *parameter.current = 12.34;\n        *parameter.consumption = 120.34;\n#endif\n        debug(\"\\nCurrent (%u): Curr %.2f Cons %.2f Volt %.2f VoltOffs %.2f Multip %.2f\",\n              uxTaskGetStackHighWaterMark(NULL), *parameter.current, *parameter.consumption, *parameter.voltage,\n              parameter.offset, parameter.multiplier);\n        vTaskDelay(1000 / parameter.rate / portTICK_PERIOD_MS);\n    }\n}\n\nfloat current_read(uint8_t adc_num) {\n    adc_select_input(adc_num);\n    return adc_read() * BOARD_VCC / ADC_RESOLUTION;\n}\n"
  },
  {
    "path": "board/project/sensor/current.h",
    "content": "#ifndef CURRENT_H\n#define CURRENT_H\n\n#include \"common.h\"\n\ntypedef struct current_parameters_t {\n    uint8_t adc_num;\n    uint8_t rate;\n    float alpha, multiplier, offset;\n    bool auto_offset;\n    float *current, *consumption, *voltage;\n} current_parameters_t;\n\nextern context_t context;\n\nvoid current_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/distance.c",
    "content": "#include \"distance.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"pico/stdlib.h\"\n\n#define INIT_DELAY_MS 1000\n#define INTERVAL_MS 500\n#define MIN_SATS 4\n#define MAX_HDOP 5.0f\n\nstatic float degrees_to_radians(float degrees);\nstatic float get_distance_to_home_2d(float lat, float lon, float lat_init, float lon_init);\n\nstatic inline bool coord_valid(float lat, float lon) {\n    /* Reject NaN/Inf and the common invalid (0,0) case */\n    if (!isfinite(lat) || !isfinite(lon)) return false;\n    if (lat == 0.0f && lon == 0.0f) return false;\n    return true;\n}\n\nvoid distance_task(void *parameters) {\n    distance_parameters_t parameter = *(distance_parameters_t *)parameters;\n    *parameter.distance = 0;\n    float latitude_init = 0, longitude_init = 0;\n\n    vTaskDelay(INIT_DELAY_MS / portTICK_PERIOD_MS);\n\n    /* Home設定 */\n    while (1) {\n        if (*parameter.fix &&\n            *parameter.sat >= MIN_SATS &&\n            *parameter.hdop <= MAX_HDOP &&\n            *parameter.hdop > 0.0f &&\n            coord_valid(*parameter.latitude, *parameter.longitude)) {\n            latitude_init = *parameter.latitude;\n            longitude_init = *parameter.longitude;\n            *parameter.home_set = true;\n            debug(\"\\nDistance (%u). Set home: Lat: %.6f, Lon: %.6f, Sats: %.0f, HDOP: %.2f\",\n                  uxTaskGetStackHighWaterMark(NULL), latitude_init, longitude_init, *parameter.sat, *parameter.hdop);\n            break;\n        } else {\n            debug(\"\\nDistance (%u): Invalid GPS data (Fix: %u, Sats: %.0f, HDOP: %.2f, Lat: %.6f, Lon: %.6f)\",\n                  uxTaskGetStackHighWaterMark(NULL), (unsigned int)*parameter.fix, *parameter.sat, *parameter.hdop,\n                  *parameter.latitude, *parameter.longitude);\n        }\n        vTaskDelay(INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n\n    /* 距離更新 */\n    while (1) {\n        if (*parameter.fix &&\n            coord_valid(*parameter.latitude, *parameter.longitude)) {\n            *parameter.distance =\n                get_distance_to_home_2d(*parameter.latitude, *parameter.longitude, latitude_init, longitude_init);\n#ifdef SIM_SENSORS\n            *parameter.distance = 1234.56f;\n#endif\n            debug(\n                \"\\nDistance (%u). Distance: %.2f, Lat: %.6f, Lon: %.6f, Sats: %.0f, HDOP: %.2f, Home Lat: %.6f, Home \"\n                \"Lon: %.6f\",\n                uxTaskGetStackHighWaterMark(NULL), *parameter.distance, *parameter.latitude, *parameter.longitude,\n                *parameter.sat, *parameter.hdop, latitude_init, longitude_init);\n        } else {\n            debug(\"\\nDistance (%u): GPS invalid during distance (Fix:%u Sats:%.0f HDOP:%.2f Lat:%.6f Lon:%.6f)\",\n                  uxTaskGetStackHighWaterMark(NULL), (unsigned)*parameter.fix, *parameter.sat, *parameter.hdop,\n                  *parameter.latitude, *parameter.longitude);\n        }\n        vTaskDelay(INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic float degrees_to_radians(float degrees) { return degrees * (float)PI / 180.0f; }\n\nstatic float get_distance_to_home_2d(float lat, float lon, float lat_init, float lon_init) {\n    const float earth_radius_m = 6371000.0f;\n\n    float lat1 = degrees_to_radians(lat_init);\n    float lat2 = degrees_to_radians(lat);\n    float dlat = degrees_to_radians(lat - lat_init);\n    float dlon = degrees_to_radians(lon - lon_init);\n\n    float s1 = sinf(dlat * 0.5f);\n    float s2 = sinf(dlon * 0.5f);\n\n    float a = s1 * s1 + cosf(lat1) * cosf(lat2) * s2 * s2;\n    float c = 2.0f * atan2f(sqrtf(a), sqrtf(1.0f - a));\n\n    return earth_radius_m * c;\n}"
  },
  {
    "path": "board/project/sensor/distance.h",
    "content": "#ifndef DISTANCE_H\n#define DISTANCE_H\n\n#include \"common.h\"\n\ntypedef struct distance_parameters_t {\n    float *distance, *latitude, *longitude, *altitude, *sat, *hdop;\n    uint8_t *fix, *home_set; // fix internal msrc: 0 no fix, 1 2D fix, 2 3D fix\n\n} distance_parameters_t;\n\nextern context_t context;\n\nvoid distance_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_apd_f.c",
    "content": "#include \"esc_apd_f.h\"\n\n#include <stdio.h>\n\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n\n#define APD_F_TIMEOUT_US 1000\n#define APD_F_PACKET_LENGHT 12\n#define KISS_PACKET_LENGHT 10\n\nstatic void process(esc_apd_f_parameters_t *parameter);\nstatic uint8_t update_crc8(uint8_t crc, uint8_t crc_seed);\nstatic uint8_t get_crc8(uint8_t *buffer, uint8_t lenght);\n\nvoid esc_apd_f_task(void *parameters) {\n    esc_apd_f_parameters_t parameter = *(esc_apd_f_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.temperature = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.temperature = 12.34;\n    *parameter.voltage = 12.34;\n    *parameter.current = 12.34;\n    *parameter.consumption = 12.34;\n    *parameter.rpm = 12345.67;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, APD_F_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_apd_f_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    uint8_t lenght = uart1_available();\n    if (lenght == APD_F_PACKET_LENGHT || lenght == KISS_PACKET_LENGHT) {\n        uint8_t data[KISS_PACKET_LENGHT];\n        uart1_read_bytes(data, KISS_PACKET_LENGHT);\n        if (get_crc8(data, KISS_PACKET_LENGHT - 1) == data[9]) {\n            float temperature = data[0];\n            float voltage = ((uint16_t)data[1] << 8 | data[2]) / 100.0;\n            float current = ((uint16_t)data[3] << 8 | data[4]) / 100.0;\n            float consumption = ((uint16_t)data[5] << 8 | data[6]);\n            float rpm = ((uint16_t)data[7] << 8 | data[8]) * 100.0;\n            rpm *= parameter->rpm_multiplier;\n            *parameter->temperature = get_average(parameter->alpha_temperature, *parameter->temperature, temperature);\n            *parameter->voltage = get_average(parameter->alpha_voltage, *parameter->voltage, voltage);\n            *parameter->current = get_average(parameter->alpha_current, *parameter->current, current);\n            *parameter->consumption = get_average(parameter->alpha_voltage, *parameter->consumption, consumption);\n            *parameter->rpm = get_average(parameter->alpha_rpm, *parameter->rpm, rpm);\n            *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n            debug(\"\\nApd F (%u) < Rpm: %.0f Volt: %0.2f Curr: %.2f Temp: %.0f Cons: %.0f CellV: %.2f\",\n                  uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, *parameter->current,\n                  *parameter->temperature, *parameter->consumption, *parameter->cell_voltage);\n        }\n    }\n}\n\nstatic uint8_t update_crc8(uint8_t crc, uint8_t crc_seed) {\n    uint8_t crc_u, i;\n    crc_u = crc;\n    crc_u ^= crc_seed;\n    for (i = 0; i < 8; i++) crc_u = (crc_u & 0x80) ? 0x7 ^ (crc_u << 1) : (crc_u << 1);\n    return (crc_u);\n}\n\nstatic uint8_t get_crc8(uint8_t *buffer, uint8_t lenght) {\n    uint8_t crc = 0, i;\n    for (i = 0; i < lenght; i++) crc = update_crc8(buffer[i], crc);\n    return crc;\n}\n"
  },
  {
    "path": "board/project/sensor/esc_apd_f.h",
    "content": "#ifndef ESC_APD_F_H\n#define ESC_APD_F_H\n\n#include \"common.h\"\n\ntypedef struct esc_apd_f_parameters_t {\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temperature, *cell_voltage, *consumption;\n    uint8_t *cell_count;\n} esc_apd_f_parameters_t;\n\nextern context_t context;\n\nvoid esc_apd_f_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_apd_hv.c",
    "content": "#include \"esc_apd_hv.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n\n#define ESC_KISS_PACKET_LENGHT 10\n#define ESC_APD_HV_TIMEOUT_US 1000\n#define ESC_APD_HV_PACKET_LENGHT 22\n\nstatic void process(esc_apd_hv_parameters_t *parameter);\nstatic float get_temperature(uint16_t raw);\nstatic uint16_t get_crc16(uint8_t *buffer);\n\nvoid esc_apd_hv_task(void *parameters) {\n    esc_apd_hv_parameters_t parameter = *(esc_apd_hv_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.temperature = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.temperature = 12.34;\n    *parameter.voltage = 12.34;\n    *parameter.current = 12.34;\n    *parameter.consumption = 12.34;\n    *parameter.rpm = 12345.67;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, ESC_APD_HV_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_apd_hv_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    if (uart1_available() == ESC_APD_HV_PACKET_LENGHT) {\n        uint8_t data[ESC_APD_HV_PACKET_LENGHT];\n        uart1_read_bytes(data, ESC_APD_HV_PACKET_LENGHT);\n        if (get_crc16(data) == (((uint16_t)data[19] << 8) | data[18])) {\n            float voltage = ((uint16_t)data[1] << 8 | data[0]) / 100.0;\n            float temp = get_temperature((uint16_t)data[3] << 8 | data[2]);\n            float current = ((uint16_t)data[5] << 8 | data[4]) / 12.5;\n            float rpm = (uint32_t)data[11] << 24 | (uint32_t)data[10] << 16 | (uint16_t)data[9] << 8 | data[8];\n            rpm *= parameter->rpm_multiplier;\n            *parameter->temperature = get_average(parameter->alpha_temperature, *parameter->temperature, temp);\n            *parameter->voltage = get_average(parameter->alpha_voltage, *parameter->voltage, voltage);\n            *parameter->current = get_average(parameter->alpha_current, *parameter->current, current);\n            *parameter->rpm = get_average(parameter->alpha_rpm, *parameter->rpm, rpm);\n            *parameter->consumption += get_consumption(*parameter->current, 0, &timestamp);\n            *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n            debug(\"\\nApd HV (%u) < Rpm: %.0f Volt: %0.2f Curr: %.2f Temp: %.0f Cons: %.0f CellV: %.2f\",\n                  uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, *parameter->current,\n                  *parameter->temperature, *parameter->consumption, *parameter->cell_voltage);\n        }\n    }\n}\n\nstatic float get_temperature(uint16_t raw) {\n    uint16_t SERIESRESISTOR = 10000;\n    uint16_t NOMINAL_RESISTANCE = 10000;\n    uint8_t NOMINAL_TEMPERATURE = 25;\n    uint16_t BCOEFFICIENT = 3455;\n\n    // convert value to resistance\n    float Rntc = (4096 / (float)raw) - 1;\n    Rntc = SERIESRESISTOR / Rntc;\n\n    // Get the temperature\n    float temperature = Rntc / (float)NOMINAL_RESISTANCE;  // (R/Ro)\n    temperature = (float)log(temperature);                 // ln(R/Ro)\n    temperature /= BCOEFFICIENT;                           // 1/B * ln(R/Ro)\n\n    temperature += (float)1.0 / ((float)NOMINAL_TEMPERATURE + (float)273.15);  // + (1/To)\n    temperature = (float)1.0 / temperature;                                    // Invert\n    temperature -= (float)273.15;\n    return temperature;\n}\n\nstatic uint16_t get_crc16(uint8_t *buffer) {\n    uint16_t fCCRC16;\n    uint16_t c0 = 0;\n    uint16_t c1 = 0;\n\n    // Calculate checksum intermediate bytesUInt16\n    for (uint8_t i = 0; i < 18; i++)  // Check only first 18 bytes, skip crc bytes\n    {\n        c0 = (uint16_t)(c0 + (buffer[i])) % 255;\n        c1 = (uint16_t)(c1 + c0) % 255;\n    }\n    // Assemble the 16-bit checksum value\n    fCCRC16 = (c1 << 8) | c0;\n    return fCCRC16;\n}"
  },
  {
    "path": "board/project/sensor/esc_apd_hv.h",
    "content": "#ifndef ESC_APD_HV_H\n#define ESC_APD_HV_H\n\n#include \"common.h\"\n\ntypedef struct esc_apd_hv_parameters_t {\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temperature, *cell_voltage, *consumption;\n    uint8_t *cell_count;\n} esc_apd_hv_parameters_t;\n\nextern context_t context;\n\nvoid esc_apd_hv_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_castle.c",
    "content": "#include \"esc_castle.h\"\n\n#include <semphr.h>\n#include <stdio.h>\n\n#include \"cell_count.h\"\n#include \"hardware/clocks.h\"\n#include \"hardware/irq.h\"\n#include \"hardware/pio.h\"\n#include \"pico/stdlib.h\"\n\nstatic volatile esc_castle_parameters_t parameter;\n\nstatic void castle_link_handler(castle_link_telemetry_t packet);\n\nvoid esc_castle_task(void *parameters) {\n    parameter = *(esc_castle_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.ripple_voltage = 0;\n    *parameter.current = 0;\n    *parameter.thr = 0;\n    *parameter.output = 0;\n    *parameter.rpm = 0;\n    *parameter.consumption = 0;\n    *parameter.voltage_bec = 0;\n    *parameter.current_bec = 0;\n    *parameter.temperature = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.voltage = 12.34;\n    *parameter.ripple_voltage = 1.23;\n    *parameter.current = 23.45;\n    *parameter.thr = 50;\n    *parameter.output = 80;\n    *parameter.rpm = 12345.67;\n    *parameter.consumption = 345.67;\n    *parameter.voltage_bec = 4.56;\n    *parameter.current_bec = 5.67;\n    *parameter.temperature = 29.9;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    castle_link_init(pio0, CASTLE_PWM_GPIO, PIO0_IRQ_0);\n    castle_link_set_handler(castle_link_handler);\n    debug(\"\\nCastle init\");\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n    vTaskSuspend(NULL);\n    vTaskDelete(NULL);\n}\n\nstatic void castle_link_handler(castle_link_telemetry_t packet) {\n    *parameter.voltage = packet.voltage;\n    *parameter.ripple_voltage = packet.ripple_voltage;\n    *parameter.current = packet.current;\n    *parameter.thr = packet.thr;\n    *parameter.output = packet.output;\n    *parameter.rpm = packet.rpm * parameter.rpm_multiplier;\n    *parameter.voltage_bec = packet.voltage_bec;\n    *parameter.current_bec = packet.current_bec;\n    *parameter.temperature = packet.temperature;\n    debug(\n        \"\\nCastle (%u) < Volt(V): %.2f Ripple volt(V): %.2f Curr(A): %.2f Thr: %.0f Out: %.0f Rpm: %.0f Bec volt(V): \"\n        \"%.2f Bec curr(A): %.2f Temp(C): %.0f %s\",\n        uxTaskGetStackHighWaterMark(NULL), packet.voltage, packet.ripple_voltage, packet.current, packet.thr,\n        packet.output, packet.rpm, packet.voltage_bec, packet.current_bec, packet.temperature,\n        packet.is_temp_ntc ? \" NTC\" : \" Linear\");\n}\n"
  },
  {
    "path": "board/project/sensor/esc_castle.h",
    "content": "#ifndef ESC_CASTLE_H\n#define ESC_CASTLE_H\n\n#include \"castle_link.h\"\n#include \"common.h\"\n\n/* castleTelemetry:\n    index   element                          scaler\n    0       calib 1 (1000us)                 0\n    1       Volt (V)                         20\n    2       rippleVolt (V)                   4\n    3       Curr (A)                         50\n    4       Thr (0.1-0.2 ms esc pulse)       1\n    5       Output power (%)                 0.2502\n    6       rpm                              20416.7\n    7       becVolt (V)                      4\n    8       becCurr (A)                      4\n    9       temp (C) or calib 2 (500us)      30\n    10      temp ntc (C) or calib 2 (500us)  63.8125\n*/\n\ntypedef struct esc_castle_parameters_t {\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *voltage, *ripple_voltage, *current, *thr, *output, *rpm, *consumption, *voltage_bec, *current_bec,\n        *temperature, *cell_voltage;\n    uint8_t *cell_count;\n} esc_castle_parameters_t;\n\nextern context_t context;\n\nvoid esc_castle_task(void *parameters);\n\n#endif\n"
  },
  {
    "path": "board/project/sensor/esc_hw3.c",
    "content": "#include \"esc_hw3.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n\n#define TIMEOUT_US 1000\n#define PACKET_LENGHT 10\n#define NO_SIGNAL_TIMEOUT_MS 500\n\nstatic void process(esc_hw3_parameters_t *parameter);\nstatic int64_t timeout_callback(alarm_id_t id, void *parameters);\n\nvoid esc_hw3_task(void *parameters) {\n    debug(\"\\nHW3 init\");\n    esc_hw3_parameters_t parameter = *(esc_hw3_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    *parameter.rpm = 0;\n#ifdef SIM_SENSORS\n    *parameter.rpm = 12345.67;\n#endif\n    uart1_begin(19200, UART1_TX_GPIO, UART_ESC_RX, TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_hw3_parameters_t *parameter) {\n    static alarm_id_t timeout_alarm_id = 0;\n    if (uart1_available() == PACKET_LENGHT) {\n        if (timeout_alarm_id) cancel_alarm(timeout_alarm_id);\n        uint8_t data[PACKET_LENGHT];\n        uart1_read_bytes(data, PACKET_LENGHT);\n        if (data[0] == 0x9B && data[4] == 0 && data[6] == 0) {\n            uint16_t rpmCycle = (uint16_t)data[8] << 8 | data[9];\n            if (rpmCycle <= 0) rpmCycle = 1;\n            float rpm = 60000000.0 / rpmCycle * parameter->multiplier;\n            *parameter->rpm = get_average(parameter->alpha, *parameter->rpm, rpm);\n            uint32_t packet = (uint32_t)data[1] << 16 | (uint16_t)data[2] << 8 | data[3];\n            debug(\"\\nEsc HW3 (%u) < Packet: %i Rpm: %.0f\", uxTaskGetStackHighWaterMark(NULL), packet, *parameter->rpm);\n        }\n        timeout_alarm_id = add_alarm_in_ms(NO_SIGNAL_TIMEOUT_MS, timeout_callback, parameter->rpm, false);\n    }\n}\n\nstatic int64_t timeout_callback(alarm_id_t id, void *parameters) {\n    float *parameter = (float *)parameters;\n    *parameter = 0;\n    debug(\"\\nEsc HW3 signal timeout. Rpm: 0\");\n    return 0;\n}"
  },
  {
    "path": "board/project/sensor/esc_hw3.h",
    "content": "#ifndef ESC_HW3_H\n#define ESC_HW3_H\n\n#include \"common.h\"\n\ntypedef struct esc_hw3_parameters_t {\n    float multiplier;\n    float alpha;\n    float *rpm;\n} esc_hw3_parameters_t;\n\nextern context_t context;\n\nvoid esc_hw3_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_hw4.c",
    "content": "#include \"esc_hw4.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"auto_offset.h\"\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n\n#define TIMEOUT_US 2000\n#define PACKET_LENGHT 19\n#define SIGNATURE_LENGHT 13\n#define CURRENT_OFFSET_DELAY 15000\n#define NTC_BETA 3950.0\n#define NTC_R1 10000.0\n#define NTC_R_REF 47000.0\n#define DIFFAMP_SHUNT (0.25 / 1000)\n#define V_REF 3.3\n#define ADC_RES 4096.0\n\nfloat current_offset_ = -1;\n\nstatic void process(esc_hw4_parameters_t *parameter, uint *current_raw);\nfloat get_voltage(uint16_t voltage_raw, esc_hw4_parameters_t *parameter);\nfloat get_temperature(uint16_t temperature_raw);\nfloat get_current(uint raw, int offset, float multiplier);\n\nvoid esc_hw4_task(void *parameters) {\n    esc_hw4_parameters_t parameter = *(esc_hw4_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.temperature_fet = 0;\n    *parameter.temperature_bec = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.rpm = 12345.67;\n    *parameter.consumption = 123.4;\n    *parameter.voltage = 12.34;\n    *parameter.current = 5.678;\n    *parameter.temperature_fet = 12.34;\n    *parameter.temperature_bec = 23.45;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    if (parameter.init_delay) vTaskDelay(15000 / portTICK_PERIOD_MS);\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n\n    uint current_raw = 0;\n    if (!parameter.auto_detect && !parameter.current_is_manual_offset) {\n        uint current_delay = CURRENT_OFFSET_DELAY;\n        auto_offset_int_parameters_t current_offset_parameters = {current_delay, &current_raw,\n                                                                  &parameter.current_offset};\n        xTaskCreate(auto_offset_int_task, \"esc_hw4_current_offset_task\", STACK_AUTO_OFFSET, &current_offset_parameters,\n                    1, &task_handle);\n\n    }\n\n    uart1_begin(19200, UART1_TX_GPIO, UART_ESC_RX, TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter, &current_raw);\n    }\n}\n\nstatic void process(esc_hw4_parameters_t *parameter, uint *current_raw) {\n    uint16_t pwm, throttle;\n    static uint32_t timestamp = 0;\n    uint8_t lenght = uart1_available();\n    uint8_t data[lenght];\n    uart1_read_bytes(data, lenght);\n    debug(\"\\nEsc HW4 (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(data, lenght, \"0x%X \");\n    if (lenght == 13 && parameter->auto_detect) {\n        if (data[1] == 0x9B && data[4] == 0x01 && data[12] == 0xB9) {\n            parameter->voltage_multiplier = (float)data[5] / (float)data[6] / 10.0;\n            parameter->current_multiplier = (float)data[7] / (float)data[8];\n            if (parameter->current_multiplier != 0) {\n                parameter->current_offset = (float)data[9] / parameter->current_multiplier;\n            }\n            else\n                parameter->current_offset = 0;\n            debug(\"\\nEsc HW4 signature (%u): VoltMult: %.0f CurrMult: %.0f CurrOff: %u\",\n                  uxTaskGetStackHighWaterMark(NULL), parameter->voltage_multiplier * 100000, parameter->current_multiplier * 100000,\n                  parameter->current_offset);\n        } else {\n            debug(\"\\nEsc HW4 signature error (%u): \", uxTaskGetStackHighWaterMark(NULL));\n        }\n    }\n    if (lenght == PACKET_LENGHT || lenght == PACKET_LENGHT + 1) {\n        throttle = (uint16_t)data[4] << 8 | data[5];  // 0-1024\n        pwm = (uint16_t)data[6] << 8 | data[7];       // 0-1024\n        float rpm = (uint32_t)data[8] << 16 | (uint16_t)data[9] << 8 | data[10];\n        // try to filter invalid data frames\n        /*if (throttle < 1024 && pwm < 1024 && rpm < 200000 && data[11] <= 0xF && data[13] <= 0xF && data[15] <= 0xF &&\n            data[17] <= 0xF)*/\n        if (data[0] == 0x9B) {\n            *current_raw = (uint16_t)data[13] << 8 | data[14];\n            float voltage = get_voltage((uint16_t)data[11] << 8 | data[12], parameter);\n            float current = 0;\n            if (throttle) current = get_current(*current_raw, parameter->current_offset, parameter->current_multiplier);\n            float temperature_fet = get_temperature((uint16_t)data[15] << 8 | data[16]);\n            float temperature_bec = get_temperature((uint16_t)data[17] << 8 | data[18]);\n            rpm *= parameter->rpm_multiplier;\n            if (parameter->pwm_out) xTaskNotifyGive(context.pwm_out_task_handle);\n            *parameter->rpm = get_average(parameter->alpha_rpm, *parameter->rpm, rpm);\n            *parameter->consumption += get_consumption(*parameter->current, parameter->current_max, &timestamp);\n            *parameter->voltage = get_average(parameter->alpha_voltage, *parameter->voltage, voltage);\n            *parameter->current = get_average(parameter->alpha_current, *parameter->current, current);\n            *parameter->temperature_fet =\n                get_average(parameter->alpha_temperature, *parameter->temperature_fet, temperature_fet);\n            *parameter->temperature_bec =\n                get_average(parameter->alpha_temperature, *parameter->temperature_bec, temperature_bec);\n            *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n            uint32_t packet = (uint32_t)data[1] << 16 | (uint16_t)data[2] << 8 | data[3];\n            debug(\n                \"\\nEsc HW4 (%u) < Packet: %i Thr: %u Rpm: %.0f Volt: %0.2f Curr: %.2f TempFet: %.0f TempBec: %.0f Cons: %.0f \"\n                \"CellV: %.2f CRaw: %i CRawOffset: %u CurrMult: %.0f VoltMult: %.0f\",\n                uxTaskGetStackHighWaterMark(NULL), packet, throttle, *parameter->rpm, *parameter->voltage, *parameter->current,\n                *parameter->temperature_fet, *parameter->temperature_bec, *parameter->consumption,\n                *parameter->cell_voltage, *current_raw, parameter->current_offset, parameter->current_multiplier * 100000,\n                parameter->voltage_multiplier * 100000);\n        } else {\n            debug(\"\\nEsc HW4 packet error (%u) 0x%X\", uxTaskGetStackHighWaterMark(NULL), data[0]);\n        }\n    }\n}\n\nfloat get_voltage(uint16_t voltage_raw, esc_hw4_parameters_t *parameter) {\n    return voltage_raw * parameter->voltage_multiplier;\n}\n\nfloat get_temperature(uint16_t temperature_raw) {\n    float voltage = temperature_raw * V_REF / ADC_RES;\n    float ntcR_Rref = (voltage * NTC_R1 / (V_REF - voltage)) / NTC_R_REF;\n    if (ntcR_Rref < 0.001) return 0;\n    float temperature = 1 / (log(ntcR_Rref) / NTC_BETA + 1 / 298.15) - 273.15;\n    if (temperature < 0) return 0;\n    return temperature;\n}\n\nfloat get_current(uint raw, int offset, float multiplier) {\n    // float current = (*parameter->current_raw - *parameter->current_offset) * V_REF / (parameter->ampgain *\n    // DIFFAMP_SHUNT * ADC_RES);\n    if ((int)raw - offset < 0) return 0;\n    return ((int)raw - offset) * multiplier;\n}\n"
  },
  {
    "path": "board/project/sensor/esc_hw4.h",
    "content": "#ifndef ESC_HW4_H\n#define ESC_HW4_H\n\n#include \"common.h\"\n\ntypedef struct esc_hw4_parameters_t {\n    float rpm_multiplier;\n    bool pwm_out;\n    bool init_delay;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float voltage_multiplier, current_multiplier, current_thresold, current_max;\n    bool current_is_manual_offset, auto_detect;\n    uint current_offset;\n    float *rpm, *voltage, *current, *temperature_fet, *temperature_bec, *cell_voltage, *consumption;\n    uint8_t *cell_count;\n} esc_hw4_parameters_t;\n\nextern context_t context;\n\nvoid esc_hw4_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_hw5.c",
    "content": "#include \"esc_hw5.h\"\n\n#include <stdio.h>\n\n#include \"auto_offset.h\"\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n\n#define TIMEOUT_US 2000\n#define PACKET_LENGHT 32\n\nstatic uint8_t *CRCH_, *CRCL_;\nstatic void process(esc_hw5_parameters_t *parameter);\nstatic uint16_t calculate_crc16(uint8_t const *buffer, uint lenght);\n\nvoid esc_hw5_task(void *parameters) {\n    esc_hw5_parameters_t parameter = *(esc_hw5_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.temperature_fet = 0;\n    *parameter.temperature_bec = 0;\n    *parameter.temperature_motor = 0;\n    *parameter.voltage_bec = 0;\n    *parameter.current_bec = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n    uint8_t CRCH[] = {0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n                      0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n                      0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n                      0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40};\n    uint8_t CRCL[] = {0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04,\n                      0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8,\n                      0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC,\n                      0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10,\n                      0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,\n                      0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38,\n                      0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C,\n                      0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0,\n                      0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4,\n                      0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,\n                      0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C,\n                      0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0,\n                      0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54,\n                      0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98,\n                      0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,\n                      0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40};\n    CRCL_ = CRCL;\n    CRCH_ = CRCH;\n#ifdef SIM_SENSORS\n    *parameter.rpm = 12345.67;\n    *parameter.consumption = 123.4;\n    *parameter.voltage = 12.34;\n    *parameter.current = 5.678;\n    *parameter.temperature_fet = 12.34;\n    *parameter.temperature_bec = 23.45;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_hw5_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    uint8_t lenght = uart1_available();\n    if (lenght) {\n        uint8_t data[lenght];\n        uart1_read_bytes(data, lenght);\n        if (lenght == PACKET_LENGHT) {\n            uint16_t packet_crc = (data[31] << 8) | data[30];\n            uint16_t crc = calculate_crc16(data, PACKET_LENGHT - 2);\n            if (crc == packet_crc) {\n                uint16_t throttle;\n                throttle = data[7 + 2];\n                *parameter->rpm = (data[7 + 6] | (data[7 + 7] << 8)) * 10 * parameter->rpm_multiplier;\n                *parameter->voltage = (data[7 + 8] | (data[7 + 9] << 8)) / 10.0;\n                *parameter->current = (data[7 + 10] | (data[7 + 11] << 8)) / 10.0;\n                if (data[7 + 12] != 0xFF) *parameter->temperature_fet = data[7 + 12];\n                if (data[7 + 13] != 0xFF) *parameter->temperature_bec = data[7 + 13];\n                if (data[7 + 14] != 0xFF) *parameter->temperature_motor = data[7 + 14];\n                if (data[7 + 15] != 0xFF) *parameter->voltage_bec = data[7 + 15];\n                if (data[7 + 16] != 0xFF) *parameter->current_bec = data[7 + 16];\n                *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n                *parameter->consumption += get_consumption(*parameter->current, 0, &timestamp);\n\n                debug(\n                    \"\\nEsc HW5 (%u) < Rpm: %.0f Volt: %0.2f Curr: %.2f TempFet: %.0f TempBec: %.0f TempMotor: %.0f \"\n                    \"Vbec: %.1f Cbec: %.1f Cons: %.0f CellCount: %u CellV: %.2f\",\n                    uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, *parameter->current,\n                    *parameter->temperature_fet, *parameter->temperature_bec, *parameter->temperature_motor,\n                    *parameter->voltage_bec, *parameter->current_bec, *parameter->consumption, *parameter->cell_count,\n                    *parameter->cell_voltage, *parameter->cell_voltage);\n            } else {\n                debug(\"\\nEsc HW5 CRC error (%u): \", uxTaskGetStackHighWaterMark(NULL));\n                debug_buffer(data, lenght, \"0x%X \");\n                debug(\"\\nCalculated CRC: %u\", crc);\n            }\n        } else {\n            debug(\"\\nEsc HW5 packet error (%u): \", uxTaskGetStackHighWaterMark(NULL));\n            debug_buffer(data, lenght, \"0x%X \");\n        }\n    }\n}\n\nstatic uint16_t calculate_crc16(uint8_t const *buffer, uint lenght) {\n    // CRC16-MODBUS\n    // 8005(x16+x15+x2+1)\n    uint8_t crc_high = 0xFF;\n    uint8_t crc_low = 0xFF;\n    uint8_t index;\n    while (lenght--) {\n        index = crc_low ^ (*(buffer++));\n        crc_low = crc_high ^ CRCH_[index];\n        crc_high = CRCL_[index];\n    }\n    return (uint16_t)((uint16_t)(crc_high << 8) | crc_low);\n}\n"
  },
  {
    "path": "board/project/sensor/esc_hw5.h",
    "content": "#ifndef ESC_HW5_H\n#define ESC_HW5_H\n\n#include \"common.h\"\n\ntypedef struct esc_hw5_parameters_t {\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temperature_fet, *temperature_bec, *temperature_motor, *voltage_bec, *current_bec,\n        *cell_voltage, *consumption;\n    uint8_t *cell_count;\n} esc_hw5_parameters_t;\n\nextern context_t context;\n\nvoid esc_hw5_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_kontronik.c",
    "content": "#include \"esc_kontronik.h\"\n\n#include <stdio.h>\n\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n\n#define TIMEOUT_US 1000\n#define PACKET_LENGHT 35\n\nstatic void process(esc_kontronik_parameters_t *parameter);\n\nvoid esc_kontronik_task(void *parameters) {\n    esc_kontronik_parameters_t parameter = *(esc_kontronik_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.voltage_bec = 0;\n    *parameter.current_bec = 0;\n    *parameter.temperature_fet = 0;\n    *parameter.temperature_bec = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.rpm = 12345.67;\n    *parameter.consumption = 123.4;\n    *parameter.voltage = 12.34;\n    *parameter.current = 12.34;\n    *parameter.temperature_fet = 12.34;\n    *parameter.temperature_bec = 12.34;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, TIMEOUT_US, 8, 1, UART_PARITY_EVEN, false, false);\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_kontronik_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    if (uart1_available() == PACKET_LENGHT) {\n        uint8_t data[PACKET_LENGHT];\n        uart1_read_bytes(data, PACKET_LENGHT);\n        if (data[0] == 0x4B && data[1] == 0x4F && data[2] == 0x44 && data[3] == 0x4C) {\n            float rpm = (uint32_t)data[7] << 24 | (uint32_t)data[6] << 16 | (uint16_t)data[5] << 8 | data[4];\n            rpm *= parameter->rpm_multiplier;\n            float voltage = ((uint16_t)data[9] << 8 | data[8]) / 100.0;\n            float current = ((uint16_t)data[11] << 8 | data[10]) / 10.0;\n            float consumption = ((uint16_t)data[17] << 8 | data[16]);\n            float current_bec = ((uint16_t)data[19] << 8 | data[18]) / 1000.0;\n            float voltage_bec = ((uint16_t)data[21] << 8 | data[20]) / 1000.0;\n            float temperature_fet = data[26];\n            float temperature_bec = data[27];\n            *parameter->rpm = get_average(parameter->alpha_rpm, *parameter->rpm, rpm);\n            *parameter->consumption = consumption;\n            *parameter->voltage = get_average(parameter->alpha_voltage, *parameter->voltage, voltage);\n            *parameter->current = get_average(parameter->alpha_current, *parameter->current, current);\n            *parameter->voltage_bec = get_average(parameter->alpha_voltage, *parameter->voltage_bec, voltage_bec);\n            *parameter->current_bec = get_average(parameter->alpha_current, *parameter->current_bec, current_bec);\n            *parameter->temperature_fet =\n                get_average(parameter->alpha_temperature, *parameter->temperature_fet, temperature_fet);\n            *parameter->temperature_bec =\n                get_average(parameter->alpha_temperature, *parameter->temperature_bec, temperature_bec);\n            *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n            debug(\n                \"\\nKontronic (%u) < Rpm: %.0f Volt: %0.2f Curr: %.2f V Bec: %0.2f C Bec: %.2f TempFet: %.0f TempBec: \"\n                \"%.0f Cons: %.0f CellV: %.2f\",\n                uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, *parameter->current,\n                *parameter->voltage_bec, *parameter->current_bec, *parameter->temperature_fet,\n                *parameter->temperature_bec, *parameter->consumption, *parameter->cell_voltage);\n        }\n    }\n}"
  },
  {
    "path": "board/project/sensor/esc_kontronik.h",
    "content": "#ifndef ESC_KONTRONIK_H\n#define ESC_KONTRONIK_H\n\n#include \"common.h\"\n\ntypedef struct esc_kontronik_parameters_t {\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *voltage_bec, *current_bec, *temperature_fet, *temperature_bec, *cell_voltage,\n        *consumption;\n    uint8_t *cell_count;\n} esc_kontronik_parameters_t;\n\nextern context_t context;\n\nvoid esc_kontronik_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_omp_m4.c",
    "content": "#include \"esc_omp_m4.h\"\n\n#include <stdio.h>\n\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n\n#define OMP_M4_TIMEOUT_US 1000\n#define OMP_M4_PACKET_LENGHT 32\n#define OMP_M4_PACKET_HEADER 0xDD\n\ntypedef struct esc_omp_m4_packet_t {\n    uint8_t header;\n    uint8_t version;\n    uint8_t length;\n    uint16_t voltage;\n    uint16_t current;\n    uint8_t throttle;\n    uint16_t rpm;\n    uint8_t temp_esc;\n    uint8_t temp_motor;\n    uint8_t pwm;\n    uint16_t status;\n    uint16_t consumption;\n    uint8_t unused[31 - 17 + 1];\n} __attribute((packed)) esc_omp_m4_packet_t;\n\nstatic void process(esc_omp_m4_parameters_t *parameter);\n\nvoid esc_omp_m4_task(void *parameters) {\n    esc_omp_m4_parameters_t parameter = *(esc_omp_m4_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.temp_esc = 0;\n    *parameter.temp_motor = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.temp_esc = 12.34;\n    *parameter.temp_motor = 23.45;\n    *parameter.voltage = 12.34;\n    *parameter.current = 12.34;\n    *parameter.consumption = 12.34;\n    *parameter.rpm = 12345.67;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, OMP_M4_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_omp_m4_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    uint8_t lenght = uart1_available();\n    if (lenght == OMP_M4_PACKET_LENGHT) {\n        esc_omp_m4_packet_t packet;\n        uart1_read_bytes((uint8_t *)&packet, OMP_M4_PACKET_LENGHT);\n        if (packet.header != OMP_M4_PACKET_HEADER) return;\n        float temp_esc = packet.temp_esc;\n        float temp_motor = packet.temp_motor;\n        float voltage = swap_16(packet.voltage) / 10.0;\n        float current = swap_16(packet.current) / 10.0;\n        float consumption = swap_16(packet.consumption);\n        float rpm = swap_16(packet.rpm) * 10.0;\n        rpm *= parameter->rpm_multiplier;\n        *parameter->temp_esc = get_average(parameter->alpha_temperature, *parameter->temp_esc, temp_esc);\n        *parameter->temp_motor = get_average(parameter->alpha_temperature, *parameter->temp_motor, temp_motor);\n        *parameter->voltage = get_average(parameter->alpha_voltage, *parameter->voltage, voltage);\n        *parameter->current = get_average(parameter->alpha_current, *parameter->current, current);\n        *parameter->consumption = get_average(parameter->alpha_voltage, *parameter->consumption, consumption);\n        *parameter->rpm = get_average(parameter->alpha_rpm, *parameter->rpm, rpm);\n        *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n        debug(\"\\nOMP M4 (%u) < Rpm: %.0f Volt: %.1f Curr: %.1f Temp esc: %.0f Temp motor: %.0f Cons: %.0f CellV: %.2f\",\n              uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, *parameter->current,\n              *parameter->temp_esc, *parameter->temp_motor, *parameter->consumption, *parameter->cell_voltage);\n    }\n}\n"
  },
  {
    "path": "board/project/sensor/esc_omp_m4.h",
    "content": "#ifndef ESC_OMP_M4_H\n#define ESC_OMP_M4_H\n\n#include \"common.h\"\n\ntypedef struct esc_omp_m4_parameters_t {\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temp_esc, *temp_motor, *cell_voltage, *consumption;\n    uint8_t *cell_count;\n} esc_omp_m4_parameters_t;\n\nextern context_t context;\n\nvoid esc_omp_m4_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_openyge.c",
    "content": "#include \"esc_openyge.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n\n#define TIMEOUT_US 2000\n#define FRAME_START 0xA5\n#define CRC_POLYNOMIAL 0x1021  // CRC-16-CCITT\n\n// CRC mode tracking (based on your Arduino code)\nstatic crc_mode_t g_crc_mode = CRC_MODE_UNKNOWN;\n\nstatic uint16_t calculate_crc16_with_seed(uint8_t *data, uint8_t length, uint16_t seed);\nstatic crc_mode_t detect_crc_mode(uint8_t *frame, uint8_t frame_len);\nstatic bool validate_crc(uint8_t *frame, uint8_t frame_len, crc_mode_t mode);\nstatic void process(esc_openyge_parameters_t *parameter);\n\nvoid esc_openyge_task(void *parameters) {\n    esc_openyge_parameters_t parameter = *(esc_openyge_parameters_t *)parameters;\n\n    // Initialize all output variables\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.temperature_fet = 0;\n    *parameter.temperature_bec = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.voltage_bec = 0;\n    *parameter.current_bec = 0;\n    *parameter.throttle = 0;\n    *parameter.pwm_percent = 0;\n    *parameter.cell_count = 1;\n    parameter.crc_errors = 0;\n    parameter.crc_mode = CRC_MODE_UNKNOWN;\n\n    xTaskNotifyGive(context.receiver_task_handle);\n\n#ifdef SIM_SENSORS\n    *parameter.rpm = 12345.67;\n    *parameter.consumption = 123.4;\n    *parameter.voltage = 22.2;\n    *parameter.current = 15.5;\n    *parameter.temperature_fet = 45.6;\n    *parameter.temperature_bec = 38.2;\n    *parameter.cell_voltage = 3.7;\n    *parameter.voltage_bec = 5.1;\n    *parameter.current_bec = 0.8;\n    *parameter.throttle = 75;\n    *parameter.pwm_percent = 80;\n#endif\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n\n    // Initialize UART for OpenYGE protocol (115200 baud, 8N1, inverted = false)\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n\n    debug(\"\\nOpenYGE ESC init\");\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_openyge_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    uint8_t length = uart1_available();\n\n    if (length < 8) {  // Minimum frame size\n        return;\n    }\n\n    uint8_t data[length];\n    uart1_read_bytes(data, length);\n\n    debug(\"\\nOpenYGE (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n    debug_buffer(data, length, \"0x%02X \");\n\n    // Look for frame start in the received data\n    for (int i = 0; i <= length - 8; i++) {  // Minimum 8 bytes needed\n        if (data[i] == FRAME_START) {\n            uint8_t *frame = &data[i];\n            uint8_t frame_len = (i + 4 < length) ? frame[3] : 0;\n\n            // Check if we have enough data for complete frame\n            if (frame_len == 0 || (i + frame_len) > length || frame_len < 8) {\n                continue;\n            }\n\n            // Validate frame structure - version 3, type 0\n            if (frame[1] != 3 || frame[2] != 0 || frame[3] != frame_len) {\n                continue;\n            }\n\n            // Auto-detect CRC mode if unknown\n            if (parameter->crc_mode == CRC_MODE_UNKNOWN) {\n                crc_mode_t detected_mode = detect_crc_mode(frame, frame_len);\n                if (detected_mode != CRC_MODE_UNKNOWN) {\n                    parameter->crc_mode = detected_mode;\n                    debug(\"\\nOpenYGE CRC mode auto-detected: %d\", detected_mode);\n                }\n            }\n\n            // Validate CRC using detected or known mode\n            if (parameter->crc_mode != CRC_MODE_UNKNOWN && \n                validate_crc(frame, frame_len, parameter->crc_mode)) {\n                // Parse telemetry data using correct byte positions from your working code\n\n                // FET Temperature - byte 7, subtract 40 for actual temperature\n                int temp_fet_raw = (int)frame[7] - 40;\n                float temperature_fet = (temp_fet_raw < -40 || temp_fet_raw > 215) ? 0 : temp_fet_raw;\n\n                // Voltage - bytes 8-9 (little endian), scale by 0.01 \n                uint16_t voltage_raw = (uint16_t)frame[8] | ((uint16_t)frame[9] << 8);\n                float voltage = voltage_raw * 0.01f;  // V_SCALE from your code\n\n                // Current - bytes 10-11 (little endian), scale by 0.01\n                uint16_t current_raw = (uint16_t)frame[10] | ((uint16_t)frame[11] << 8);\n                float current = current_raw * 0.01f;  // A_SCALE from your code\n\n                // Consumption - bytes 12-13 (little endian), direct mAh\n                uint16_t consumption_raw = (uint16_t)frame[12] | ((uint16_t)frame[13] << 8);\n\n                // eRPM - bytes 14-15 (little endian), scale by 0.1, then apply pole pairs\n                uint16_t erpm_raw = (uint16_t)frame[14] | ((uint16_t)frame[15] << 8);\n                float erpm = erpm_raw * 0.1f;  // ERPM_SCALE from your code\n                float rpm = (erpm / 2.0f) * parameter->rpm_multiplier;  // Assuming 2 pole pairs like your code\n\n                // PWM - byte 16, direct percentage\n                float pwm_percent = frame[16];\n\n                // Throttle - byte 17, direct percentage  \n                float throttle = frame[17];\n\n                // BEC voltage - bytes 18-19 (little endian), in mV\n                uint16_t bec_voltage_raw = (uint16_t)frame[18] | ((uint16_t)frame[19] << 8);\n                float voltage_bec = bec_voltage_raw / 1000.0f;\n\n                // BEC current - bytes 20-21 (little endian), in mA\n                uint16_t bec_current_raw = (uint16_t)frame[20] | ((uint16_t)frame[21] << 8);\n                float current_bec = bec_current_raw / 1000.0f;\n\n                // BEC/Cap Temperature - byte 22 or 24, subtract 40 for actual temperature\n                int temp_bec_raw = (int)frame[22] - 40;  // Try byte 22 first\n                float temperature_bec = (temp_bec_raw < -40 || temp_bec_raw > 215) ? 0 : temp_bec_raw;\n\n                // Update outputs with averaging\n                if (parameter->pwm_out) xTaskNotifyGive(context.pwm_out_task_handle);\n\n                *parameter->rpm = get_average(parameter->alpha_rpm, *parameter->rpm, rpm);\n                *parameter->consumption += get_consumption(*parameter->current, 65535, &timestamp);\n                *parameter->voltage = get_average(parameter->alpha_voltage, *parameter->voltage, voltage);\n                *parameter->current = get_average(parameter->alpha_current, *parameter->current, current);\n                *parameter->temperature_fet = get_average(parameter->alpha_temperature, *parameter->temperature_fet, temperature_fet);\n                *parameter->temperature_bec = get_average(parameter->alpha_temperature, *parameter->temperature_bec, temperature_bec);\n                *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n                *parameter->voltage_bec = voltage_bec;\n                *parameter->current_bec = current_bec;\n                *parameter->throttle = throttle;\n                *parameter->pwm_percent = pwm_percent;\n\n                debug(\n                    \"\\nOpenYGE (%u) < RPM: %.0f Volt: %.2fV Curr: %.2fA TempFET: %.1f°C \"\n                    \"TempBEC: %.1f°C Cons: %.0fmAh CellV: %.2fV BECVolt: %.2fV BECCurr: %.3fA Thr: %.0f%% PWM: %.0f%%\",\n                    uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, \n                    *parameter->current, *parameter->temperature_fet, *parameter->temperature_bec, *parameter->consumption,\n                    *parameter->cell_voltage, *parameter->voltage_bec, *parameter->current_bec, *parameter->throttle, *parameter->pwm_percent);\n\n                return; // Successfully processed frame\n            } else {\n                parameter->crc_errors++;\n\n                if (parameter->crc_mode == CRC_MODE_UNKNOWN) {\n                    debug(\"\\nOpenYGE CRC mode unknown, trying to detect...\");\n                } else {\n                    debug(\"\\nOpenYGE CRC error with mode %d\", parameter->crc_mode);\n                }\n\n                // Debug frame contents for first few errors\n                if (parameter->crc_errors <= 3) {\n                    debug(\" Frame[%d]: \", frame_len);\n                    for (int j = 0; j < frame_len && j < 12; j++) {\n                        debug(\"%02X \", frame[j]);\n                    }\n                }\n            }\n        }\n    }\n}\n\nstatic uint16_t calculate_crc16_with_seed(uint8_t *data, uint8_t length, uint16_t seed) {\n    uint16_t crc = seed;\n\n    for (uint8_t i = 0; i < length; i++) {\n        crc ^= ((uint16_t)data[i] << 8);\n\n        for (uint8_t bit = 0; bit < 8; bit++) {\n            if (crc & 0x8000) {\n                crc = (crc << 1) ^ CRC_POLYNOMIAL;\n            } else {\n                crc <<= 1;\n            }\n        }\n    }\n\n    return crc;\n}\n\nstatic crc_mode_t detect_crc_mode(uint8_t *frame, uint8_t frame_len) {\n    if (frame_len < 4) return CRC_MODE_UNKNOWN;\n\n    // Extract CRC from frame in both byte orders\n    uint16_t rx_be = ((uint16_t)frame[frame_len-2] << 8) | frame[frame_len-1];  // Big-endian\n    uint16_t rx_le = ((uint16_t)frame[frame_len-1] << 8) | frame[frame_len-2];  // Little-endian\n\n    // Try all CRC variants\n    struct {\n        crc_mode_t mode;\n        uint8_t start_offset;\n        uint16_t seed;\n        bool is_big_endian;\n    } variants[] = {\n        {CRC_MODE_INC_SYNC_SEED_FFFF_BE,  0, 0xFFFF, true},\n        {CRC_MODE_INC_SYNC_SEED_FFFF_LE,  0, 0xFFFF, false},\n        {CRC_MODE_SKIP_SYNC_SEED_FFFF_BE, 1, 0xFFFF, true},\n        {CRC_MODE_SKIP_SYNC_SEED_FFFF_LE, 1, 0xFFFF, false},\n        {CRC_MODE_INC_SYNC_SEED_0000_BE,  0, 0x0000, true},\n        {CRC_MODE_INC_SYNC_SEED_0000_LE,  0, 0x0000, false},\n        {CRC_MODE_SKIP_SYNC_SEED_0000_BE, 1, 0x0000, true},\n        {CRC_MODE_SKIP_SYNC_SEED_0000_LE, 1, 0x0000, false},\n    };\n\n    for (int i = 0; i < 8; i++) {\n        if (frame_len <= (2 + variants[i].start_offset)) continue;\n\n        uint16_t calc_crc = calculate_crc16_with_seed(\n            frame + variants[i].start_offset, \n            frame_len - 2 - variants[i].start_offset, \n            variants[i].seed\n        );\n\n        uint16_t expected_crc = variants[i].is_big_endian ? rx_be : rx_le;\n\n        if (calc_crc == expected_crc) {\n            debug(\"\\nOpenYGE CRC mode detected: %d\", variants[i].mode);\n            return variants[i].mode;\n        }\n    }\n\n    return CRC_MODE_UNKNOWN;\n}\n\nstatic bool validate_crc(uint8_t *frame, uint8_t frame_len, crc_mode_t mode) {\n    if (frame_len < 4) return false;\n\n    uint16_t rx_be = ((uint16_t)frame[frame_len-2] << 8) | frame[frame_len-1];\n    uint16_t rx_le = ((uint16_t)frame[frame_len-1] << 8) | frame[frame_len-2];\n\n    uint8_t start_offset;\n    uint16_t seed;\n    bool is_big_endian;\n\n    switch (mode) {\n        case CRC_MODE_INC_SYNC_SEED_FFFF_BE:  start_offset = 0; seed = 0xFFFF; is_big_endian = true; break;\n        case CRC_MODE_INC_SYNC_SEED_FFFF_LE:  start_offset = 0; seed = 0xFFFF; is_big_endian = false; break;\n        case CRC_MODE_SKIP_SYNC_SEED_FFFF_BE: start_offset = 1; seed = 0xFFFF; is_big_endian = true; break;\n        case CRC_MODE_SKIP_SYNC_SEED_FFFF_LE: start_offset = 1; seed = 0xFFFF; is_big_endian = false; break;\n        case CRC_MODE_INC_SYNC_SEED_0000_BE:  start_offset = 0; seed = 0x0000; is_big_endian = true; break;\n        case CRC_MODE_INC_SYNC_SEED_0000_LE:  start_offset = 0; seed = 0x0000; is_big_endian = false; break;\n        case CRC_MODE_SKIP_SYNC_SEED_0000_BE: start_offset = 1; seed = 0x0000; is_big_endian = true; break;\n        case CRC_MODE_SKIP_SYNC_SEED_0000_LE: start_offset = 1; seed = 0x0000; is_big_endian = false; break;\n        default: return false;\n    }\n\n    uint16_t calc_crc = calculate_crc16_with_seed(\n        frame + start_offset, \n        frame_len - 2 - start_offset, \n        seed\n    );\n\n    uint16_t expected_crc = is_big_endian ? rx_be : rx_le;\n    return calc_crc == expected_crc;\n}\n\n// Temperature function no longer needed - OpenYGE sends direct Celsius values"
  },
  {
    "path": "board/project/sensor/esc_openyge.h",
    "content": "#ifndef ESC_OPENYGE_H\n#define ESC_OPENYGE_H\n\n#include \"common.h\"\n\ntypedef enum {\n    CRC_MODE_UNKNOWN = 0,\n    CRC_MODE_INC_SYNC_SEED_FFFF_BE,\n    CRC_MODE_INC_SYNC_SEED_FFFF_LE,\n    CRC_MODE_SKIP_SYNC_SEED_FFFF_BE,\n    CRC_MODE_SKIP_SYNC_SEED_FFFF_LE,\n    CRC_MODE_INC_SYNC_SEED_0000_BE,\n    CRC_MODE_INC_SYNC_SEED_0000_LE,\n    CRC_MODE_SKIP_SYNC_SEED_0000_BE,\n    CRC_MODE_SKIP_SYNC_SEED_0000_LE\n} crc_mode_t;\n\ntypedef struct esc_openyge_parameters_t {\n    float rpm_multiplier;\n    bool pwm_out;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temperature_fet, *temperature_bec, *cell_voltage, *consumption;\n    float *voltage_bec, *current_bec, *throttle, *pwm_percent;\n    uint8_t *cell_count;\n    uint32_t crc_errors;\n    crc_mode_t crc_mode;\n} esc_openyge_parameters_t;\n\nextern context_t context;\n\nvoid esc_openyge_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_pwm.c",
    "content": "#include \"esc_pwm.h\"\n\n#include <stdio.h>\n\n#include \"capture_edge.h\"\n#include \"esc_hw3.h\"\n#include \"hardware/clocks.h\"\n#include \"hardware/irq.h\"\n#include \"hardware/pio.h\"\n#include \"pico/stdlib.h\"\n\n#define SIGNAL_TIMEOUT_MS 1000\n#define INTERVAL_MS 100\n#define CLOCK_DIV 1\n\nstatic volatile bool is_timedout = false;\nstatic volatile uint pwm_cycles = 0;\n\nstatic void read(esc_pwm_parameters_t *parameter);\nstatic void capture_pin_0_handler(uint counter, edge_type_t edge);\nstatic int64_t timeout_callback(alarm_id_t id, void *user_data);\n\nvoid esc_pwm_task(void *parameters) {\n    esc_pwm_parameters_t parameter = *(esc_pwm_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    xTaskNotifyGive(context.receiver_task_handle);\n\n    gpio_pull_up(PWM_CAPTURE_GPIO);\n\n    capture_edge_init(pio0, PWM_CAPTURE_GPIO, 1, CLOCK_DIV, PIO0_IRQ_0);\n    capture_edge_set_handler(0, capture_pin_0_handler);\n\n    while (1) {\n        read(&parameter);\n        debug(\"\\nEsc PWM (%u) < Rpm: %.0f\", uxTaskGetStackHighWaterMark(NULL), *parameter.rpm);\n        vTaskDelay(INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void read(esc_pwm_parameters_t *parameter) {\n    if (is_timedout) {\n        *parameter->rpm = 0;\n        return;\n    }\n    if (pwm_cycles > 1) {\n        float pwm_duration = (float)pwm_cycles / clock_get_hz(clk_sys) * CLOCK_DIV * COUNTER_CYCLES;  // seconds\n        float rpm = 60 / pwm_duration * parameter->multiplier;\n        *parameter->rpm = rpm;  // get_average(parameter->alpha / 100.0F, *parameter->rpm, rpm);\n    }\n#ifdef SIM_SENSORS\n    *parameter->rpm = 12345.67;\n#endif\n}\n\nstatic void capture_pin_0_handler(uint counter, edge_type_t edge) {\n    static uint counter_edge_rise = 0, counter_previous = 0;\n    static alarm_id_t timeout_alarm_id = 0;\n    if (timeout_alarm_id) cancel_alarm(timeout_alarm_id);\n    is_timedout = false;\n\n    if (edge == EDGE_RISE) {\n        pwm_cycles = counter - counter_edge_rise;\n        counter_edge_rise = counter;\n        // printf(\"\\nPwm cycles: %u\", pwm_cycles);\n    }\n    timeout_alarm_id = add_alarm_in_ms(SIGNAL_TIMEOUT_MS, timeout_callback, NULL, false);\n}\n\nstatic int64_t timeout_callback(alarm_id_t id, void *parameters) {\n    is_timedout = true;\n    debug(\"\\nEsc PWM signal timeout. Rpm: 0\");\n    return 0;\n}\n"
  },
  {
    "path": "board/project/sensor/esc_pwm.h",
    "content": "#ifndef ESC_PWM_H\n#define ESC_PWM_H\n\n#include \"esc_hw3.h\"\n\ntypedef esc_hw3_parameters_t esc_pwm_parameters_t;\n\nvoid esc_pwm_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/esc_ztw.c",
    "content": "#include \"esc_ztw.h\"\n\n#include <stdio.h>\n\n#include \"cell_count.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n\n#define ZTW_TIMEOUT_US 1000\n#define ZTW_PACKET_LENGHT 32\n#define ZTW_PACKET_HEADER 0xDD\n\ntypedef struct esc_ztw_packet_t {\n    uint8_t header;\n    uint8_t version;\n    uint8_t length;\n    uint16_t voltage;\n    uint16_t current;\n    uint8_t throttle;\n    uint16_t rpm;\n    uint8_t temp_esc;\n    uint8_t temp_motor;\n    uint8_t pwm;\n    uint16_t status;\n    uint16_t consumption;\n    uint8_t serial_throttle;\n    uint8_t can_throttle;\n    uint8_t bec_voltage;\n    uint8_t unused[29 - 20 + 1];\n    uint16_t crc;\n} __attribute((packed)) esc_ztw_packet_t;\n\nstatic void process(esc_ztw_parameters_t *parameter);\n\nvoid esc_ztw_task(void *parameters) {\n    esc_ztw_parameters_t parameter = *(esc_ztw_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.bec_voltage = 0;\n    *parameter.current = 0;\n    *parameter.temp_esc = 0;\n    *parameter.temp_motor = 0;\n    *parameter.cell_voltage = 0;\n    *parameter.consumption = 0;\n    *parameter.cell_count = 1;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.temp_esc = 12.34;\n    *parameter.temp_motor = 23.45;\n    *parameter.voltage = 12.34;\n    *parameter.bec_voltage = 4.56;\n    *parameter.current = 12.34;\n    *parameter.consumption = 12.34;\n    *parameter.rpm = 12345.67;\n    *parameter.cell_voltage = 3.75;\n#endif\n\n    TaskHandle_t task_handle;\n    uint cell_count_delay = 15000;\n    cell_count_parameters_t cell_count_parameters = {cell_count_delay, parameter.voltage, parameter.cell_count};\n    xTaskCreate(cell_count_task, \"cell_count_task\", STACK_CELL_COUNT, (void *)&cell_count_parameters, 1, &task_handle);\n\n\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, ZTW_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, false);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(esc_ztw_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    uint8_t lenght = uart1_available();\n    if (lenght == ZTW_PACKET_LENGHT) {\n        esc_ztw_packet_t packet;\n        uart1_read_bytes((uint8_t *)&packet, ZTW_PACKET_LENGHT);\n        if (packet.header != ZTW_PACKET_HEADER) return;\n        float temp_esc = packet.temp_esc;\n        float temp_motor = packet.temp_motor;\n        float voltage = swap_16(packet.voltage) / 10.0;\n        float bec_voltage = packet.bec_voltage / 10.0;\n        float current = swap_16(packet.current) / 10.0;\n        float consumption = swap_16(packet.consumption);\n        float rpm = swap_16(packet.rpm) * 10.0;\n        rpm *= parameter->rpm_multiplier;\n        *parameter->temp_esc = get_average(parameter->alpha_temperature, *parameter->temp_esc, temp_esc);\n        *parameter->temp_motor = get_average(parameter->alpha_temperature, *parameter->temp_motor, temp_motor);\n        *parameter->voltage = get_average(parameter->alpha_voltage, *parameter->voltage, voltage);\n        *parameter->bec_voltage = get_average(parameter->alpha_voltage, *parameter->bec_voltage, bec_voltage);\n        *parameter->current = get_average(parameter->alpha_current, *parameter->current, current);\n        *parameter->consumption = get_average(parameter->alpha_voltage, *parameter->consumption, consumption);\n        *parameter->rpm = get_average(parameter->alpha_rpm, *parameter->rpm, rpm);\n        *parameter->cell_voltage = *parameter->voltage / *parameter->cell_count;\n        debug(\"\\nZTW (%u) < Rpm: %.0f Volt: %.1f Curr: %.1f Volt BEC: %.1f Temp esc: %.0f Temp motor: %.0f Cons: %.0f CellV: %.2f\",\n              uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, *parameter->current, *parameter->bec_voltage, \n              *parameter->temp_esc, *parameter->temp_motor, *parameter->consumption, *parameter->cell_voltage);\n    }\n}\n"
  },
  {
    "path": "board/project/sensor/esc_ztw.h",
    "content": "#ifndef ESC_ZTW_H\n#define ESC_ZTW_H\n\n#include \"common.h\"\n\ntypedef struct esc_ztw_parameters_t {\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temp_esc, *temp_motor, *bec_voltage, *cell_voltage, *consumption;\n    uint8_t *cell_count;\n} esc_ztw_parameters_t;\n\nextern context_t context;\n\nvoid esc_ztw_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/fuel_meter.c",
    "content": "#include \"fuel_meter.h\"\n\n#include <stdio.h>\n\n#include \"capture_edge.h\"\n#include \"hardware/clocks.h\"\n#include \"hardware/irq.h\"\n#include \"hardware/pio.h\"\n#include \"pico/stdlib.h\"\n\n#define INSTANT_INTERVAL_MS 100\n#define CLOCK_DIV 5\n\nstatic volatile uint pwm_cycles_instant = 0;\nstatic volatile uint pwm_cycles_total = 0;\n\nstatic void read(fuel_meter_parameters_t *parameter);\nstatic void capture_pin_0_handler(uint counter, edge_type_t edge);\n\nvoid fuel_meter_task(void *parameters) {\n    fuel_meter_parameters_t parameter = *(fuel_meter_parameters_t *)parameters;\n    *parameter.consumption_instant = 0;  // ml/min\n    *parameter.consumption_total = 0;    // ml\n    xTaskNotifyGive(context.receiver_task_handle);\n\n    gpio_pull_up(FUELMETER_CAPTURE_GPIO);\n\n    capture_edge_init(pio0, FUELMETER_CAPTURE_GPIO, 1, CLOCK_DIV, PIO0_IRQ_0);\n    capture_edge_set_handler(0, capture_pin_0_handler);\n\n    while (1) {\n        read(&parameter);\n        debug(\"\\nFuel sensor (%u) < ml/min %.3f ml %.3f ml/pulse %.3f pulses %u\", uxTaskGetStackHighWaterMark(NULL), *parameter.consumption_instant,\n              *parameter.consumption_total, parameter.ml_per_pulse, pwm_cycles_total);\n        vTaskDelay(INSTANT_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void read(fuel_meter_parameters_t *parameter) {\n    *parameter->consumption_total = pwm_cycles_total * parameter->ml_per_pulse;  // ml\n    *parameter->consumption_instant =\n        pwm_cycles_instant * parameter->ml_per_pulse * 1000 / INSTANT_INTERVAL_MS * 60;  // ml/min\n    pwm_cycles_instant = 0;\n#ifdef SIM_SENSORS\n    *parameter->consumption_instant = 12.23;\n    *parameter->consumption_total = 1223;\n#endif\n}\n\nstatic void capture_pin_0_handler(uint counter, edge_type_t edge) {\n    if (edge == EDGE_RISE) {\n        pwm_cycles_total++;\n        pwm_cycles_instant++;\n    }\n}\n\n"
  },
  {
    "path": "board/project/sensor/fuel_meter.h",
    "content": "#ifndef FUEL_METER_H\n#define FUEL_METER_H\n\n#include \"common.h\"\n\ntypedef struct fuel_meter_parameters_t {\n    float ml_per_pulse;\n    //float alpha;\n    float *consumption_instant; // ml/min\n    float *consumption_total; // ml\n} fuel_meter_parameters_t;\n\nextern context_t context;\n\nvoid fuel_meter_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/gpio.c",
    "content": "#include \"gpio.h\"\n\n#include <stdio.h>\n\n#include \"pico/stdlib.h\"\n\n#define GPIO_MASK (1 << 17)\n\nvoid gpio_task(void *parameters) {\n    gpio_parameters_t parameter = *(gpio_parameters_t *)parameters;\n    gpio_init_mask(GPIO_MASK);\n    gpio_set_dir_in_masked(GPIO_MASK);\n    xTaskNotifyGive(context.receiver_task_handle);\n\n    while (1) {\n        uint8_t value = 0;\n        for (uint i = 0; i < 6; i++) {\n            if (parameter.mask & (1 << i)) value |= gpio_get(i + 17) << i;\n        }\n#ifdef SIM_SENSORS\n        value = 0B101;\n#endif\n        *parameter.value = value;\n\n        debug(\"\\nGPIO (%u): 0x%X\", uxTaskGetStackHighWaterMark(NULL), value);\n\n        vTaskDelay(parameter.interval / portTICK_PERIOD_MS);\n    }\n}\n"
  },
  {
    "path": "board/project/sensor/gpio.h",
    "content": "#ifndef GPIO_SENSOR_H\n#define GPIO_SENSOR_H\n\n#include \"common.h\"\n\ntypedef struct gpio_parameters_t {\n    uint8_t mask;\n    uint16_t interval;\n    uint8_t *value;\n} gpio_parameters_t;\n\nextern context_t context;\n\nvoid gpio_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/gps.c",
    "content": "#include \"gps.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"distance.h\"\n#include \"pico/stdlib.h\"\n#include \"stdlib.h\"\n#include \"uart_pio.h\"\n#include \"vspeed.h\"\n\n#define COMMAND_GGA 0\n#define COMMAND_RMC 1\n#define COMMAND_GSA 2\n#define COMMAND_UNK 3\n\n#define COMMAND_VTG 4\n#define COMMAND_GLL 5\n\n#define NMEA_LON 1\n#define NMEA_ALT 2\n#define NMEA_SPD 3\n#define NMEA_COG 4\n#define NMEA_FIX 5\n#define NMEA_SAT 6\n#define NMEA_DATE 7\n#define NMEA_TIME 8\n#define NMEA_LAT_SIGN 9\n#define NMEA_LON_SIGN 10\n#define NMEA_END 11\n#define NMEA_LAT 12\n#define NMEA_GSA_FIX 13  // 1,2,3\n#define NMEA_GSA_PDOP 14\n#define NMEA_GSA_HDOP 15\n#define NMEA_GSA_VDOP 16\n\n#define TIMEOUT_US 5000\n#define VSPEED_INTERVAL_MS 2000\n\ntypedef struct ublox_msg_info_t {\n    uint8_t class;\n    uint8_t id;\n    uint16_t len;\n} __attribute__((packed)) ublox_msg_info_t;\n\ntypedef struct ublox_navpvt_t {\n    uint32_t iTOW;\n    uint16_t year;\n    uint8_t month;\n    uint8_t day;\n    uint8_t hour;\n    uint8_t min;\n    uint8_t sec;\n    uint8_t valid;\n    uint32_t tAcc;\n    int32_t nano;\n    uint8_t fixType;\n    uint8_t flags;\n    uint8_t flags2;\n    uint8_t numSV;\n    int32_t lon;\n    int32_t lat;\n    int32_t height;  // mm\n    int32_t hMSL;\n    uint32_t hAcc;   // mm\n    uint32_t vAcc;   // mm\n    int32_t velN;    // mm/s\n    int32_t velE;    // mm/s\n    int32_t velD;    // mm/s\n    int32_t gSpeed;  // mm/s\n    int32_t headMot;\n    uint32_t sAcc;     // mm\n    uint32_t headAcc;  // deg * 10000\n    uint16_t pDOP;\n    uint16_t flags3;\n    uint32_t reserved1;\n    int32_t headVeh;\n    int16_t magDec;\n    uint16_t magAcc;\n    uint8_t crc_a;\n    uint8_t crc_b;\n} __attribute__((packed)) ublox_navpvt_t;\n\ntypedef struct ublox_navdop_t {\n    uint32_t iTOW;\n    uint16_t gDOP;  // DOP * 100\n    uint16_t pDOP;  // DOP * 100\n    uint16_t tDOP;  // DOP * 100\n    uint16_t vDOP;  // DOP * 100\n    uint16_t hDOP;  // DOP * 100\n    uint16_t nDOP;  // DOP * 100\n    uint16_t eDOP;  // DOP * 100\n    uint8_t crc_a;\n    uint8_t crc_b;\n} __attribute__((packed)) ublox_navdop_t;\n\ntypedef struct alarm_parameters_t {\n    bool is_ublox;\n    uint rate;\n} alarm_parameters_t;\n\n// static alarm_id_t alarm_id_ublox = 0, alarm_id_nmea = 0;\n// static alarm_parameters_t alarm_parameters;\n\nstatic void process(gps_parameters_t *parameter);\nstatic void parser(uint8_t nmea_cmd, uint8_t cmd_field, uint8_t *buffer, gps_parameters_t *parameter);\nstatic void send_ublox_message(uint8_t *buf, uint len);\nstatic void set_baudrate(uint baudrate);\nstatic void set_nmea_config(uint rate);\nstatic void set_ublox_config(uint rate);\nstatic void nmea_msg(char *cmd, bool enable);\nstatic void ubx_cfg_msg(uint8_t class, uint8_t id, bool enable);\nstatic void ubx_cfg_rate(uint16_t rate);\nstatic void ubx_cfg_cfg(void);\nstatic bool set_home_altitude(uint fix_type);\n// static int64_t alarm_nmea_timeout(alarm_id_t id, void *parameters);\n// static int64_t alarm_ublox_timeout(alarm_id_t id, void *parameters);\n\nvoid gps_task(void *parameters) {\n    gps_parameters_t parameter = *(gps_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    *parameter.lat = 0;\n    *parameter.lon = 0;\n    *parameter.alt = 0;\n    *parameter.spd = 0;\n    *parameter.cog = 0;\n    *parameter.hdop = 0;\n    *parameter.vdop = 0;\n    *parameter.sat = 0;\n    *parameter.time = 0;\n    *parameter.date = 0;\n    *parameter.vspeed = 0;\n    *parameter.dist = 0;\n    *parameter.spd_kmh = 0;\n    *parameter.fix = 0;       // raw fix: nmea or ublox\n    *parameter.fix_type = 0;  // internal fix MSRC. 0 no fix, 1 2D fix, 2 3D fix\n    *parameter.home_set = false;\n    *parameter.n_vel = 0;\n    *parameter.e_vel = 0;\n    *parameter.v_vel = 0;\n    *parameter.speed_acc = 0;\n    *parameter.track_acc = 0;\n    *parameter.alt_elipsiod = 0;\n    *parameter.h_acc = 0;\n    *parameter.v_acc = 0;\n    *parameter.alt_home = 0;\n#ifdef SIM_SENSORS\n    *parameter.lat = 123.456789;   // deg * 1e7  11º32'45.67\" +N, -S\n    *parameter.lon = -123.456789;  //-deg + 10e7 1251.964833333; // 20º51'57.89\" +E, -W\n    *parameter.alt = 1283;         // m\n    *parameter.spd = 158;          // kts\n    *parameter.cog = 123.45;       // º\n    *parameter.sat = 10;           //\n    *parameter.date = 141012;      // yymmdd\n    *parameter.time = 162302;      // hhmmss\n    *parameter.hdop = 12.35;       //\n    *parameter.dist = 1000;\n    *parameter.spd_kmh = 123;\n#endif\n    TaskHandle_t task_handle;\n\n    static distance_parameters_t parameters_distance;\n    parameters_distance.distance = parameter.dist;\n    parameters_distance.altitude = parameter.alt;\n    parameters_distance.sat = parameter.sat;\n    parameters_distance.latitude = parameter.lat;\n    parameters_distance.longitude = parameter.lon;\n    parameters_distance.fix = parameter.fix_type;\n    parameters_distance.hdop = parameter.hdop;\n    parameters_distance.home_set = parameter.home_set;\n    xTaskCreate(distance_task, \"distance_task\", STACK_DISTANCE, (void *)&parameters_distance, 2, &task_handle);\n\n    /* Change GPS config. For ublox compatible devices */\n\n    vTaskDelay(1000 / portTICK_PERIOD_MS);\n    set_baudrate(parameter.baudrate);\n    uart_pio_begin(parameter.baudrate, UART_TX_PIO_GPIO, UART_RX_PIO_GPIO, TIMEOUT_US, pio0, PIO0_IRQ_1, 8, 1,\n                   UART_PARITY_NONE);\n    if (parameter.protocol == UBLOX)\n        set_ublox_config(parameter.rate);\n    else\n        set_nmea_config(parameter.rate);\n\n    // alarm_parameters.is_ublox = true;\n    // alarm_id_ublox = add_alarm_in_ms(20 * 1000L, alarm_ublox_timeout, &alarm_parameters, false);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n    }\n}\n\nstatic void process(gps_parameters_t *parameter) {\n    static char buffer[20] = {0};\n    if (parameter->protocol == NMEA) {\n        static uint8_t nmea_cmd = 0;\n        static uint8_t cmd_field = 0;\n        while (uart_pio_available()) {\n            static uint8_t data_pos = 0;\n            char data = uart_pio_read();\n            switch (data) {\n                case '$':\n                    cmd_field = 0;\n                    data_pos = 0;\n                    nmea_cmd = COMMAND_UNK;\n                    break;\n                case '\\r':\n                    // If we were ignoring checksum, reset cleanly\n                    if (cmd_field == 255) {\n                        cmd_field = 0;\n                        data_pos = 0;\n                        nmea_cmd = COMMAND_UNK;\n                        break;\n                    }\n                case ',':\n                    if (cmd_field == 255) {  // ignoring checksum\n                        data_pos = 0;\n                        buffer[0] = 0;\n                        break;\n                    }\n                    if (cmd_field == 0) {\n                        if (memcmp(buffer + 2, \"GGA\", 3) == 0) {\n                            nmea_cmd = COMMAND_GGA;\n                        } else if (memcmp(buffer + 2, \"RMC\", 3) == 0) {\n                            nmea_cmd = COMMAND_RMC;\n                        } else if (memcmp(buffer + 2, \"GSA\", 3) == 0) {\n                            nmea_cmd = COMMAND_GSA;\n                        }\n                        if (nmea_cmd != COMMAND_UNK) {\n                            // cancel_alarm(alarm_id_nmea);\n                            // alarm_id_nmea = add_alarm_in_ms(2000, alarm_nmea_timeout, &alarm_parameters, false);\n                            debug(\"\\nGPS (%u) < %s(%i): \", uxTaskGetStackHighWaterMark(NULL), buffer + 2, nmea_cmd);\n                        }\n                    } else {\n                        if (nmea_cmd != COMMAND_UNK) parser(nmea_cmd, cmd_field, buffer, parameter);\n                    }\n\n                    cmd_field++;\n                    data_pos = 0;\n                    buffer[0] = 0;\n                    break;\n                case '\\n':\n                    break;\n                case '*':\n                    // End current field; ignore checksum bytes until '\\r'\n                    if (nmea_cmd != COMMAND_UNK && cmd_field != 0) {\n                        parser(nmea_cmd, cmd_field, (uint8_t *)buffer, parameter);\n                    }\n                    cmd_field = 255;  // special: ignore until '\\r'\n                    data_pos = 0;\n                    buffer[0] = 0;\n                    break;\n                default:\n                    if (data_pos < 19) {\n                        buffer[data_pos] = data;\n                        data_pos++;\n                        buffer[data_pos] = 0;\n                    }\n            }\n        }\n    } else {\n        while (uart_pio_available()) {\n            while (uart_pio_available() && uart_pio_read() != 0xB5)\n                ;\n            while (uart_pio_available() && uart_pio_read() != 0x62)\n                ;\n            if (uart_pio_available() < sizeof(ublox_msg_info_t)) return;\n            ublox_msg_info_t msg_info;\n            uart_pio_read_bytes((uint8_t *)&msg_info, sizeof(ublox_msg_info_t));\n            debug(\"\\nGPS UBLOX MSG. Class: %u Id: %u Len: %u Avail: %u\", msg_info.class, msg_info.id, msg_info.len,\n                  uart_pio_available());\n            if (uart_pio_available() < msg_info.len + 2) return;\n            if (msg_info.class == 0x01 && msg_info.id == 0x07 && msg_info.len == sizeof(ublox_navpvt_t) - 2) {\n                // cancel_alarm(alarm_id_ublox);\n                // alarm_id_ublox = add_alarm_in_ms(2000, alarm_ublox_timeout, &alarm_parameters, false);\n                ublox_navpvt_t navpvt;\n                uart_pio_read_bytes((uint8_t *)&navpvt, sizeof(ublox_navpvt_t));\n                *parameter->alt = navpvt.hMSL / 1000.0F;\n                *parameter->lat = navpvt.lat * 1.0e-7;\n                *parameter->lon = navpvt.lon * 1.0e-7;\n                *parameter->alt = navpvt.hMSL / 1000.0F;\n                *parameter->cog = navpvt.headMot / 100000.0F;\n                *parameter->sat = navpvt.numSV;\n                *parameter->time = navpvt.hour * 10000L + navpvt.min * 100 + navpvt.sec;\n                *parameter->date = navpvt.day * 10000L + navpvt.month * 100 + (navpvt.year - 2000);\n                *parameter->vspeed = -navpvt.velD / 1000.0F;\n                *parameter->spd_kmh = navpvt.gSpeed * 3600.0F / 1000000.0F;\n                *parameter->spd = navpvt.gSpeed * 0.001943844F;  // 1 mm/s = 0.001943844 Knot\n                uint8_t fix = navpvt.fixType;\n                *parameter->fix = fix;\n                if (fix == 2)\n                    *parameter->fix_type = 1;  // 2D\n                else if (fix == 3 || fix == 4)\n                    *parameter->fix_type = 2;  // 3D\n                else\n                    *parameter->fix_type = 0;  // no fix\n                if (!(navpvt.flags & 0x01)) *parameter->fix_type = 0;\n                *parameter->n_vel = navpvt.velN / 1000.0F;\n                *parameter->e_vel = navpvt.velE / 1000.0F;\n                *parameter->v_vel = -navpvt.velD / 1000.0F;\n                *parameter->speed_acc = navpvt.sAcc / 1000.0F;\n                *parameter->track_acc = navpvt.headAcc * 1e-5f;\n                *parameter->alt_elipsiod = navpvt.height / 1000.0F;\n                *parameter->h_acc = navpvt.hAcc / 1000.0F;\n                *parameter->v_acc = navpvt.vAcc / 1000.0F;\n                *parameter->pdop = navpvt.pDOP / 100.0F;\n                if (set_home_altitude(*parameter->fix_type)) {\n                    *parameter->alt_home = *parameter->alt;\n                }\n                debug(\n                    \"\\nGPS (%u) < NAV-PTV: Date: %.0f Time: %.0f Fix: %.0f Sat: %.0f Lon: %.5f Lat: %.5f Alt: %.2f \"\n                    \"Vspeed: %.2f Speed: mm/s %i knots %.2f kmh %.2f Pdop: %.2f, Alt home: %.2f\",\n                    uxTaskGetStackHighWaterMark(NULL), *parameter->date, *parameter->time, *parameter->fix,\n                    *parameter->sat, *parameter->lon, *parameter->lat, *parameter->alt, *parameter->vspeed,\n                    navpvt.gSpeed, *parameter->spd, *parameter->spd_kmh, *parameter->pdop, *parameter->alt_home);\n            } else if (msg_info.class == 0x01 && msg_info.id == 0x04 && msg_info.len == sizeof(ublox_navdop_t) - 2) {\n                // cancel_alarm(alarm_id_ublox);\n                // alarm_id_ublox = add_alarm_in_ms(2000, alarm_ublox_timeout, &alarm_parameters, false);\n                ublox_navdop_t navdop;\n                uart_pio_read_bytes((uint8_t *)&navdop, sizeof(ublox_navdop_t));\n                *parameter->hdop = navdop.hDOP / 100.0F;\n                *parameter->vdop = navdop.vDOP / 100.0F;\n                debug(\"\\nGPS (%u) < NAV-DOP: h: %.2f v: %.2f\", uxTaskGetStackHighWaterMark(NULL), *parameter->hdop,\n                      *parameter->vdop);\n            }\n        }\n    }\n}\n\nstatic bool set_home_altitude(uint fix_type) {\n    static bool altitude_offset_set = false;\n    if (!altitude_offset_set) {\n        static uint cont = 0;\n        if (fix_type == 2) {\n            cont++;\n            if (cont > 5) {\n                altitude_offset_set = true;\n                return true;\n            }\n        } else\n            cont = 0;\n    }\n    return false;\n}\n\nstatic void parser(uint8_t nmea_cmd, uint8_t cmd_field, uint8_t *buffer, gps_parameters_t *parameter) {\n#define NMEA_MAX_FIELDS 18  // 0..17 (field 1..17 plus dummy 0)\n\n    uint8_t nmea_field[3][NMEA_MAX_FIELDS] = {\n        // GGA: time, lat, N/S, lon, E/W, fix, sats, hdop, alt\n        {0, NMEA_TIME, NMEA_LAT, NMEA_LAT_SIGN, NMEA_LON, NMEA_LON_SIGN, NMEA_FIX, NMEA_SAT, 0, NMEA_ALT, 0, 0, 0, 0, 0,\n         0, 0, 0},\n\n        // RMC: time, status, lat, N/S, lon, E/W, spd, cog, date\n        {0, NMEA_TIME, 0, NMEA_LAT, NMEA_LAT_SIGN, NMEA_LON, NMEA_LON_SIGN, NMEA_SPD, NMEA_COG, NMEA_DATE, 0, 0, 0, 0,\n         0, 0, 0, 0},\n\n        // GSA: mode1, fix, sat1..sat12, PDOP, HDOP, VDOP\n        {0, 0, NMEA_GSA_FIX, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  // sat1..sat12 ignored (fields 3..14)\n         NMEA_GSA_PDOP, NMEA_GSA_HDOP, NMEA_GSA_VDOP}};\n    static int8_t lat_dir = 1, lon_dir = 1;\n    static uint32_t timestamp_vspeed = 0, timestamp_dist = 0;\n    if (strlen(buffer)) {\n        if (nmea_field[nmea_cmd][cmd_field] == NMEA_TIME) {\n            *parameter->time = atof(buffer);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_LAT) {\n            char degrees[3] = {0};\n            float minutes = 0;\n            strncpy(degrees, buffer, 2);\n            minutes = atof(buffer + 2);\n            *parameter->lat = atoi(degrees) + minutes / 60;\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_LON) {\n            char degrees[4] = {0};\n            float minutes = 0;\n            strncpy(degrees, buffer, 3);\n            minutes = atof(buffer + 3);\n            *parameter->lon = atoi(degrees) + minutes / 60;\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_ALT) {\n            *parameter->alt = atof(buffer);\n            get_vspeed_gps(parameter->vspeed, *parameter->alt, VSPEED_INTERVAL_MS);\n            if (set_home_altitude(*parameter->fix_type)) {\n                *parameter->alt_home = *parameter->alt;\n            }\n            debug(\"(alt home %.2f),\", *parameter->alt_home);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_SPD) {\n            *parameter->spd = atof(buffer);\n            *parameter->spd_kmh = *parameter->spd * 1.852;\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_COG) {\n            *parameter->cog = atof(buffer);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_DATE) {\n            *parameter->date = atof(buffer);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_SAT) {\n            *parameter->sat = atof(buffer);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_LAT_SIGN) {\n            lat_dir = (buffer[0] == 'N') ? 1 : -1;\n            *parameter->lat = fabsf(*parameter->lat) * lat_dir;\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_LON_SIGN) {\n            lon_dir = (buffer[0] == 'E') ? 1 : -1;\n            *parameter->lon = fabsf(*parameter->lon) * lon_dir;\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_GSA_HDOP) {\n            *parameter->hdop = atof(buffer);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_GSA_VDOP) {\n            *parameter->vdop = atof(buffer);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_GSA_PDOP) {\n            *parameter->pdop = atof(buffer);\n        } else if (nmea_field[nmea_cmd][cmd_field] == NMEA_GSA_FIX) {\n            uint fix = atoi((char *)buffer);\n            if (fix >= 1 && fix <= 3) {\n                *parameter->fix = fix;\n                if (fix == 2)\n                    *parameter->fix_type = 1;  // 2D\n                else if (fix == 3)\n                    *parameter->fix_type = 2;  // 3D\n                else\n                    *parameter->fix_type = 0;  // no fix\n            }\n        }\n        debug(\"%s(%i),\", buffer, nmea_field[nmea_cmd][cmd_field]);\n    }\n}\n\nstatic void set_ublox_config(uint rate) {\n    nmea_msg(\"GLL\", false);\n    nmea_msg(\"GSV\", false);\n    nmea_msg(\"GSA\", false);\n    nmea_msg(\"VTG\", false);\n    nmea_msg(\"ZDA\", false);\n    nmea_msg(\"GGA\", false);\n    nmea_msg(\"RMC\", false);\n    ubx_cfg_msg(0x01, 0x07, true);  // Enable message UBX-NAV-PVT\n    ubx_cfg_msg(0x01, 0x04, true);  // Enable message UBX-NAV-DOP\n    ubx_cfg_rate(rate);             // Set messages rate (UBX-CFG-RATE (0x06 0x08))\n    ubx_cfg_cfg();                  // Save changes\n}\n\nstatic void set_nmea_config(uint rate) {\n    nmea_msg(\"GLL\", false);\n    nmea_msg(\"GSA\", true);\n    nmea_msg(\"GSV\", false);\n    nmea_msg(\"VTG\", false);\n    nmea_msg(\"ZDA\", false);\n    nmea_msg(\"GGA\", true);\n    nmea_msg(\"RMC\", true);\n    ubx_cfg_msg(0x01, 0x07, false);  // Disable message UBX-NAV-PVT\n    ubx_cfg_msg(0x01, 0x04, false);  // Disable message UBX-NAV-DOP\n    ubx_cfg_rate(rate);              // Set messages rate (UBX-CFG-RATE (0x06 0x08))\n    ubx_cfg_cfg();                   // Save changes\n}\n\nstatic void set_baudrate(uint baudrate) {\n    char msg[300];\n    uint baudrates[] = {115200, 57600, 38400, 9600};\n    sprintf(msg, \"$PUBX,41,1,3,3,%u,0\\r\\n\", baudrate);\n    for (uint i = 0; i < sizeof(baudrates) / sizeof(uint); i++) {\n        uart_pio_begin(baudrates[i], UART_TX_PIO_GPIO, UART_RX_PIO_GPIO, TIMEOUT_US, pio0, PIO0_IRQ_1, 8, 1,\n                       UART_PARITY_NONE);\n        vTaskDelay(10 / portTICK_PERIOD_MS);\n        uart_pio_write_bytes(msg, strlen(msg));\n        vTaskDelay(200 / portTICK_PERIOD_MS);\n        uart_pio_remove();\n    }\n}\n\nstatic void nmea_msg(char *cmd, bool enable) {\n    char msg[30];\n    sprintf(msg, \"$PUBX,40,%s,0,%u,0,0,0,0\\r\\n\", cmd, enable);\n    uart_pio_write_bytes(msg, strlen(msg));\n}\n\nstatic void ubx_cfg_msg(uint8_t class, uint8_t id, bool enable) {\n    uint8_t msg[] = {0x06, 0x01, 0x03, 0x00, class, id, enable};\n    send_ublox_message(msg, sizeof(msg));\n}\n\nstatic void ubx_cfg_rate(uint16_t rate) {\n    uint16_t ms = 1000 / rate;\n    uint8_t msg[] = {0x06, 0x08, 0x06, 0x00, ms, ms >> 8, 0x01, 0x00, 0x01, 0x00};\n    send_ublox_message(msg, sizeof(msg));\n}\n\nstatic void ubx_cfg_cfg(void) {\n    uint8_t msg[] = {0x06, 0x09, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,\n                     0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03};\n    send_ublox_message(msg, sizeof(msg));\n}\n\nstatic inline void send_ublox_message(uint8_t *buf, uint len) {\n    uint8_t a = 0, b = 0;\n    uart_pio_write(0xB5);\n    uart_pio_write(0x62);\n    for (uint i = 0; i < len; i++) {\n        a += buf[i];\n        b += a;\n        uart_pio_write(buf[i]);\n    }\n    uart_pio_write(a);\n    uart_pio_write(b);\n}\n\n/*static int64_t alarm_nmea_timeout(alarm_id_t id, void *parameters) {\n    alarm_parameters_t *parameter;\n    parameter = (alarm_parameters_t *)parameters;\n    parameter->is_ublox = true;\n    set_ublox_config(parameter->rate);\n    return 10000 * 1000L;\n}\n\nstatic int64_t alarm_ublox_timeout(alarm_id_t id, void *parameters) {\n    alarm_parameters_t *parameter;\n    parameter = (alarm_parameters_t *)parameters;\n    parameter->is_ublox = false;\n    //set_nmea_config(parameter->rate);\n    set_ublox_config(parameter->rate);\n    return 10000 * 1000L;\n}*/"
  },
  {
    "path": "board/project/sensor/gps.h",
    "content": "#ifndef GPS_H\n#define GPS_H\n\n#include \"common.h\"\n\ntypedef struct gps_parameters_t {\n    gps_protocol_t protocol;\n    uint baudrate, rate;\n    float *lat, *lon;\n    float *alt, *spd, *cog, *hdop, *sat, *time, *date, *vspeed, *dist, *spd_kmh, *fix, *vdop, *speed_acc, *h_acc,\n        *v_acc, *track_acc, *n_vel, *e_vel, *v_vel, *alt_elipsiod, *pdop, *alt_home;\n    uint8_t *fix_type;\n    uint8_t *home_set;\n} gps_parameters_t;\n\nextern context_t context;\n\nvoid gps_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/ina3221.c",
    "content": "#include \"ina3221.h\"\n\n#include <stdio.h>\n\n#include \"hardware/i2c.h\"\n#include \"pico/stdlib.h\"\n\n//  REGISTERS\n#define INA3221_CONFIGURATION (0x00)\n#define INA3221_SHUNT_VOLTAGE(x) (0x01 + (x * 2))\n#define INA3221_BUS_VOLTAGE(x) (0x02 + (x * 2))\n#define INA3221_CRITICAL_ALERT(x) (0x07 + (x * 2))\n#define INA3221_WARNING_ALERT(x) (0x08 + (x * 2))\n#define INA3221_SHUNT_VOLTAGE_SUM (0x0D)\n#define INA3221_SHUNT_VOLTAGE_LIMIT (0x0E)\n#define INA3221_MASK_ENABLE (0x0F)\n#define INA3221_POWER_VALID_UPPER (0x10)\n#define INA3221_POWER_VALID_LOWER (0x11)\n#define INA3221_MANUFACTURER (0xFE)\n#define INA3221_DIE_ID (0xFF)\n\n#define MODE_VOLTAGE_CONTINUOUS 0x110        // Bus continuous\n#define VOLTAGE_CONVERSION_TIME (0x11 << 6)  // 588us\n//#define AVG 0x7     // 128 samples\n//#define CH 0x7      // Enable all channels\n#define RST 0x8000  // Reset bit\n\n#define I2C_ADDRESS 0x40\n#define SENSOR_INTERVAL_MS 20  // 10ms min for 1024 filter. 1ms min for 0B11 filter\n\nstatic void begin(ina3221_parameters_t *parameter);\nstatic void read(ina3221_parameters_t *parameter);\n\nvoid ina3221_task(void *parameters) {\n    ina3221_parameters_t parameter = *(ina3221_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    for (uint8_t i = 0; i < parameter.cell_count; i++) {\n        *parameter.cell[i] = 0;\n    }\n\n    vTaskDelay(500 / portTICK_PERIOD_MS);\n\n    begin(&parameter);\n\n    while (1) {\n        read(&parameter);\n        debug(\"\\nINA3221 (%u) < Addr: 0x%02X\", uxTaskGetStackHighWaterMark(NULL), parameter.i2c_address);\n        for (uint8_t i = 0; i < parameter.cell_count; i++) {\n            debug(\" Cell %u: %.2fV\", i + 1, *parameter.cell[i]);\n        }\n        vTaskDelay(SENSOR_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void begin(ina3221_parameters_t *parameter) {\n    i2c_init(i2c0, 100 * 1000);\n    gpio_set_function(I2C0_SDA_GPIO, GPIO_FUNC_I2C);\n    gpio_set_function(I2C0_SCL_GPIO, GPIO_FUNC_I2C);\n    gpio_pull_up(I2C0_SDA_GPIO);\n    gpio_pull_up(I2C0_SCL_GPIO);\n\n    uint8_t data[2] = {0};\n\n    vTaskDelay(1000 / portTICK_PERIOD_MS);\n    while (i2c_write_blocking_until(i2c0, parameter->i2c_address, data, 1, false, make_timeout_time_ms(1000)) ==\n           PICO_ERROR_GENERIC) {\n        debug(\"\\nINA3221 not found at address 0x%02X. Connect and reboot\", parameter->i2c_address);\n        vTaskDelay(1000 / portTICK_PERIOD_MS);\n    }\n\n    // Configure sensor\n    data[0] = INA3221_CONFIGURATION;\n    data[1] = MODE_VOLTAGE_CONTINUOUS | VOLTAGE_CONVERSION_TIME | (parameter->filter << 9);\n    if (parameter->cell_count > 3) parameter->cell_count = 3;\n    if (parameter->cell_count < 1) parameter->cell_count = 1;\n    for (uint8_t i = 1; i < parameter->cell_count; i++) {\n        data[1] |= (1 << (i + 12));\n    }\n    i2c_write_blocking(i2c0, parameter->i2c_address, data, 2, false);\n}\n\nstatic void read(ina3221_parameters_t *parameter) {\n    uint8_t data[3];\n    int16_t bus_voltage;\n\n    float cell_total[3] = {0};\n    for (uint8_t channel = 0; channel < parameter->cell_count; channel++) {\n        // Read bus voltage\n        data[0] = INA3221_BUS_VOLTAGE(channel);\n        i2c_write_blocking(i2c0, parameter->i2c_address, data, 1, true);\n        i2c_read_blocking(i2c0, parameter->i2c_address, data, 2, false);\n        bus_voltage = ((int16_t)data[0] << 8) | data[1];\n        // Calculate voltage in volts\n        cell_total[channel] = (float)bus_voltage * 0.001f;  // Bus voltage LSB = 1mV\n#ifdef SIM_SENSORS\n        *parameter->cell[channel] = 3 + channel * 0.01;\n#endif\n    }\n    for (uint8_t i = 0; i < parameter->cell_count; i++) {\n        if (i == 0) {\n            *parameter->cell[i] = cell_total[i] - *parameter->cell_prev;\n        } else {\n            *parameter->cell[i] = cell_total[i] - cell_total[i - 1];\n        }\n    }\n}"
  },
  {
    "path": "board/project/sensor/ina3221.h",
    "content": "#ifndef INA3221_H\n#define INA3221_H\n\n#include \"common.h\"\n\ntypedef struct ina3221_parameters_t {\n    uint8_t i2c_address;\n    uint8_t filter;\n    uint8_t conversion_time;\n    uint8_t cell_count;\n    float *cell[3];\n    float *cell_prev;\n} ina3221_parameters_t;\n\nextern context_t context;\n\nvoid ina3221_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/mpu6050.c",
    "content": "#include \"mpu6050.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"hardware/i2c.h\"\n#include \"pico/stdlib.h\"\n\n#define REGISTER_PWR_MGMT_1 0x6B\n#define REGISTER_SMPLRT_DIV 0x19\n#define REGISTER_CONFIG 0x1A\n#define REGISTER_GYRO_CONFIG 0x1B\n#define REGISTER_ACCEL_CONFIG 0x1C\n#define REGISTER_INT_ENABLE 0x38\n#define REGISTER_ACCEL_XOUT_H 0x3B\n#define REGISTER_ACCEL_XOUT_L 0x3C\n#define REGISTER_ACCEL_YOUT_H 0x3D\n#define REGISTER_ACCEL_YOUT_L 0x3E\n#define REGISTER_ACCEL_ZOUT_H 0x3F\n#define REGISTER_ACCEL_ZOUT_L 0x40\n#define REGISTER_TEMP_OUT_H 0x41\n#define REGISTER_TEMP_OUT_L 0x42\n#define REGISTER_GYRO_XOUT_H 0x43\n#define REGISTER_GYRO_XOUT_L 0x44\n#define REGISTER_GYRO_YOUT_H 0x45\n#define REGISTER_GYRO_YOUT_L 0x46\n#define REGISTER_GYRO_ZOUT_H 0x47\n#define REGISTER_GYRO_ZOUT_L 0x48\n#define REGISTER_WHO_AM_I 0x75\n\n#define I2C_ADDRESS_1 0x68\n#define I2C_ADDRESS_2 0x69\n#define SENSOR_READ_INTERVAL_MS 30\n#define CALIBRATION_READS 50\n\ntypedef struct mpu6050_calibration_t {\n    float acc_error_x, acc_error_y;\n    float gyro_error_x, gyro_error_y, gyro_error_z;\n} mpu6050_calibration_t;\n\ntypedef struct mpu6050_acceleration_t {\n    float x, y, z;\n} mpu6050_acceleration_t;\n\ntypedef struct mpu6050_gyro_t {\n    float x, y, z;\n} mpu6050_gyro_t;\n\nstatic void read(mpu6050_parameters_t *parameter, mpu6050_calibration_t *calibration, uint accel_divider,\n                 uint gyro_divider);\nstatic mpu6050_acceleration_t read_acc(uint8_t address, float accel_divider);\nstatic mpu6050_gyro_t read_gyro(uint8_t address, float gyro_divider);\nstatic void begin(mpu6050_parameters_t *parameter, mpu6050_calibration_t *calibration, float accel_divider,\n                  float gyro_divider);\nstatic void calibrate_imu(uint8_t address, mpu6050_calibration_t *calibration, float accel_divider, float gyro_divider);\n\nvoid mpu6050_task(void *parameters) {\n    mpu6050_parameters_t parameter = *(mpu6050_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    *parameter.roll = 0;\n    *parameter.pitch = 0;\n    *parameter.yaw = 0;\n    *parameter.acc_x = 0;\n    *parameter.acc_y = 0;\n    *parameter.acc_z = 0;\n    *parameter.acc = 0;\n\n    mpu6050_calibration_t calibration = {0};\n    vTaskDelay(500 / portTICK_PERIOD_MS);\n\n    uint accel_divider, gyro_divider;\n    switch (parameter.acc_scale) {\n        case 0:\n            accel_divider = 16384;  // +-2g\n            break;\n        case 1:\n            accel_divider = 8192;  // +-4g\n            break;\n        case 2:\n            accel_divider = 4096;  // +-8g\n            break;\n        case 3:\n            accel_divider = 2048;  // +-16g\n            break;\n        default:\n            accel_divider = 16384;  // Default to +-2g\n            break;\n    }\n\n    switch (parameter.gyro_scale) {\n        case 0:\n            gyro_divider = 131;  // 250deg/s\n            break;\n        case 1:\n            gyro_divider = 65.5;  // 500deg/s\n            break;\n        case 2:\n            gyro_divider = 32.8;  // 1000deg/s\n            break;\n        case 3:\n            gyro_divider = 16.4;  // 2000deg/s\n            break;\n        default:\n            gyro_divider = 131;  // Default to 250deg/s\n            break;\n    }\n\n    begin(&parameter, &calibration, accel_divider, gyro_divider);\n\n    while (1) {\n        read(&parameter, &calibration, accel_divider, gyro_divider);\n        debug(\n            \"\\nMPU6050 (%u) < Roll: %.2f Pitch: %.2f Yaw: %.2f\",\n            uxTaskGetStackHighWaterMark(NULL), calibration.acc_error_x, calibration.acc_error_y,\n           calibration.gyro_error_x, calibration.gyro_error_y, calibration.gyro_error_z, *parameter.roll,\n           *parameter.pitch, *parameter.yaw);\n        vTaskDelay(SENSOR_READ_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void read(mpu6050_parameters_t *parameter, mpu6050_calibration_t *calibration, uint accel_divider,\n                 uint gyro_divider) {\n    uint8_t data[6] = {0};\n    float acc_angle_x, acc_angle_y;\n    static float gyro_angle_x = 0, gyro_angle_y = 0;\n    static uint32_t lastTimestamp = 0;\n\n    // Read accelerometer data\n    mpu6050_acceleration_t acc = read_acc(parameter->address, accel_divider);\n    acc_angle_x = (atan(acc.y / sqrt(pow(acc.x, 2) + pow(acc.z, 2))) * 180 / PI);\n    acc_angle_y = (atan(-1 * acc.x / sqrt(pow(acc.y, 2) + pow(acc.z, 2))) * 180 / PI);\n    acc_angle_x -= calibration->acc_error_x;\n    acc_angle_y -= calibration->acc_error_y;\n    *parameter->acc_x = acc.x;\n    *parameter->acc_y = acc.y;\n    *parameter->acc_z = acc.z;\n    *parameter->acc = sqrt(acc.x * acc.x + acc.y * acc.y + acc.z * acc.z);\n\n    // Read gyroscope data\n    mpu6050_gyro_t gyro = read_gyro(parameter->address, gyro_divider);\n    gyro.x -= calibration->gyro_error_x;\n    gyro.y -= calibration->gyro_error_y;\n    gyro.z -= calibration->gyro_error_z;\n    // Currently the raw values are in degrees per seconds, deg/s, so we need to multiply by seconds (s) to get the\n    // angle in degrees\n    uint32_t now = time_us_32();\n    float elapsedTime = (now - lastTimestamp) / 1000000.0;\n    gyro_angle_x += gyro.x * elapsedTime;\n    gyro_angle_y += gyro.y * elapsedTime;\n\n    // Combine accelerometer and gyro angle values\n    *parameter->roll =\n        parameter->gyro_weighting / 100.0 * gyro_angle_x + (100 - parameter->gyro_weighting) / 100.0 * acc_angle_x;\n    *parameter->pitch =\n        parameter->gyro_weighting / 100.0 * gyro_angle_y + (100 - parameter->gyro_weighting) / 100.0 * acc_angle_y;\n    *parameter->yaw += gyro.z * elapsedTime;\n\n    lastTimestamp = now;\n\n#ifdef SIM_SENSORS\n    *parameter->roll = 12.34;\n    *parameter->pitch = 23.56;\n    *parameter->yaw = 45.67;\n#endif\n}\n\nstatic mpu6050_acceleration_t read_acc(uint8_t address, float accel_divider) {\n    uint8_t data[6] = {0};\n    mpu6050_acceleration_t acc;\n\n    // Read accelerometer data\n    data[0] = REGISTER_ACCEL_XOUT_H;\n    i2c_write_blocking(i2c0, address, data, 1, true);\n    i2c_read_blocking(i2c0, address, data, 6, false);\n    acc.x = (int16_t)(data[0] << 8 | data[1]) / accel_divider;\n    acc.y = (int16_t)(data[2] << 8 | data[3]) / accel_divider;\n    acc.z = (int16_t)(data[4] << 8 | data[5]) / accel_divider;\n\n    // debug(\"\\nAcc: X: %5.2f Y: %5.2f Z: %5.2f\", acc.x, acc.y, acc.z);\n\n    return acc;\n}\n\nstatic mpu6050_gyro_t read_gyro(uint8_t address, float gyro_divider) {\n    uint8_t data[6] = {0};\n    mpu6050_gyro_t gyro;\n\n    // Read gyroscope data\n    data[0] = REGISTER_GYRO_XOUT_H;\n    i2c_write_blocking(i2c0, address, data, 1, true);\n    i2c_read_blocking(i2c0, address, data, 6, false);\n    gyro.x = (int16_t)(data[0] << 8 | data[1]) / gyro_divider;\n    gyro.y = (int16_t)(data[2] << 8 | data[3]) / gyro_divider;\n    gyro.z = (int16_t)(data[4] << 8 | data[5]) / gyro_divider;\n\n    // debug(\"\\nGyro: X: %5.2f Y: %5.2f Z: %5.2f\", gyro.x, gyro.y, gyro.z);\n\n    return gyro;\n}\n\nstatic void begin(mpu6050_parameters_t *parameter, mpu6050_calibration_t *calibration, float accel_divider,\n                  float gyro_divider) {\n    i2c_init(i2c0, 400 * 1000);\n    gpio_set_function(I2C0_SDA_GPIO, GPIO_FUNC_I2C);\n    gpio_set_function(I2C0_SCL_GPIO, GPIO_FUNC_I2C);\n    gpio_pull_up(I2C0_SDA_GPIO);\n    gpio_pull_up(I2C0_SCL_GPIO);\n\n    // Reset\n    uint8_t data[2] = {REGISTER_PWR_MGMT_1, 0};\n    i2c_write_blocking(i2c0, parameter->address, data, 2, true);\n    vTaskDelay(100 / portTICK_PERIOD_MS);\n\n    // Find sensor address\n    parameter->address = I2C_ADDRESS_1;\n    if (i2c_write_blocking(i2c0, I2C_ADDRESS_1, data, 1, false) == PICO_ERROR_GENERIC) {\n        parameter->address = I2C_ADDRESS_2;\n    }\n\n    // Configure Accelerometer Sensitivity\n    data[0] = REGISTER_ACCEL_CONFIG;\n    data[1] = parameter->acc_scale << 3;\n    i2c_write_blocking(i2c0, parameter->address, data, 2, true);\n\n    // Configure Gyroscope Sensitivity\n    data[0] = REGISTER_GYRO_CONFIG;\n    data[1] = parameter->gyro_scale << 3;\n    i2c_write_blocking(i2c0, parameter->address, data, 2, true);\n\n    // Set sample rate to 1kHz by writing SMPLRT_DIV\n    // data[0] = REGISTER_SMPLRT_DIV;\n    // data[1] = 0x07;  // Use a 1kHz gyroscope rate, set SMPLRT_DIV to 7 for a 125Hz sample rate\n    // i2c_write_blocking(i2c0, parameter->address, data, 2, true);\n\n    // Set Digital Low Pass Filter\n    data[0] = REGISTER_CONFIG;\n    data[1] = parameter->filter;\n    i2c_write_blocking(i2c0, parameter->address, data, 2, true);\n\n    // Calibrate IMU\n    calibrate_imu(parameter->address, calibration, accel_divider, gyro_divider);\n}\n\nstatic void calibrate_imu(uint8_t address, mpu6050_calibration_t *calibration, float accel_divider,\n                          float gyro_divider) {\n    // Read accelerometer values\n    for (uint i = 0; i < CALIBRATION_READS; i++) {\n        mpu6050_acceleration_t acc = read_acc(address, accel_divider);\n        calibration->acc_error_x += ((atan((acc.y) / sqrt(pow((acc.x), 2) + pow((acc.z), 2))) * 180 / PI));\n        calibration->acc_error_y += ((atan(-1 * (acc.x) / sqrt(pow((acc.y), 2) + pow((acc.z), 2))) * 180 / PI));\n        vTaskDelay(SENSOR_READ_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n    // Divide the sum to get the error value\n    calibration->acc_error_x /= CALIBRATION_READS;\n    calibration->acc_error_y /= CALIBRATION_READS;\n\n    // Read gyro values\n    for (uint i = 0; i < CALIBRATION_READS; i++) {\n        mpu6050_gyro_t gyro = read_gyro(address, gyro_divider);\n        calibration->gyro_error_x += gyro.x;\n        calibration->gyro_error_y += gyro.y;\n        calibration->gyro_error_z += gyro.z;\n        vTaskDelay(SENSOR_READ_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n    // Divide the sum to get the error value\n    calibration->gyro_error_x /= CALIBRATION_READS;\n    calibration->gyro_error_y /= CALIBRATION_READS;\n    calibration->gyro_error_z /= CALIBRATION_READS;\n\n    debug(\"\\nMPU6050. Calibration. AccX: %.2f AccY: %.2f GyroX: %.2f GyroY: %.2f GyroZ: %.2f\", calibration->acc_error_x, calibration->acc_error_y,\n          calibration->gyro_error_x, calibration->gyro_error_y, calibration->gyro_error_z);\n}"
  },
  {
    "path": "board/project/sensor/mpu6050.h",
    "content": "#ifndef MPU6050_H\n#define MPU6050_H\n\n#include \"common.h\"\n\ntypedef struct mpu6050_parameters_t {\n    float alpha_gyro;\n    uint8_t address, acc_scale, gyro_scale, gyro_weighting, filter;\n    float *acc_x, *acc_y, *acc_z, *acc;\n    float *roll, *pitch, *yaw;\n} mpu6050_parameters_t;\n\nextern context_t context;\n\nvoid mpu6050_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/ms5611.c",
    "content": "#include \"ms5611.h\"\n\n#include <stdio.h>\n\n#include \"auto_offset.h\"\n#include \"hardware/i2c.h\"\n#include \"pico/stdlib.h\"\n#include \"stdlib.h\"\n#include \"vspeed.h\"\n\n#define CMD_ADC_READ 0x00\n#define CMD_RESET 0x1E\n#define CMD_CONV_D1 0x40\n#define CMD_CONV_D2 0x50\n\n// 0 0xA0,0xA1 - reserved\n// 1 0xA2,0xA3 - C1\n// 2 0xA4,0xA5 - C2\n// 3 0xA6,0xA7 - C3\n// 4 0xA8,0xA9 - C4\n// 5 0xAA,0xAB - C5\n// 6 0xAC,0xAD - C6\n// 7 0xAE - CRC (4bits)\n\n#define CMD_READ_PROM 0xA0\n\n#define OVERSAMPLING_4096 0x08\n#define OVERSAMPLING_2048 0x06\n#define OVERSAMPLING_1024 0x04\n#define OVERSAMPLING_512 0x02\n#define OVERSAMPLING_256 0x00\n\n#define I2C_ADDRESS_1 0x77\n#define I2C_ADDRESS_2 0x76\n\n#define SENSOR_INTERVAL_MS 20  // min 10\n#define VSPEED_INTERVAL_MS 500\n\nstatic void read(ms5611_parameters_t *parameter, ms5611_calibration_t *calibration);\nstatic void begin(ms5611_parameters_t *parameter, ms5611_calibration_t *calibration);\n\nvoid ms5611_task(void *parameters) {\n    ms5611_parameters_t parameter = *(ms5611_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    *parameter.altitude = 0;\n    *parameter.vspeed = 0;\n    *parameter.temperature = 0;\n    *parameter.pressure = 0;\n\n    TaskHandle_t task_handle;\n\n    vTaskDelay(500 / portTICK_PERIOD_MS);\n    ms5611_calibration_t calibration;\n    begin(&parameter, &calibration);\n    while (1) {\n        read(&parameter, &calibration);\n        debug(\"\\nMS5611 (%u) < Temp: %.2f Pressure: %.0f Altitude: %0.2f Vspeed: %.2f\",\n              uxTaskGetStackHighWaterMark(NULL), *parameter.temperature, *parameter.pressure, *parameter.altitude,\n              *parameter.vspeed);\n    }\n}\n\nstatic void read(ms5611_parameters_t *parameter, ms5611_calibration_t *calibration) {\n    static float pressure_initial = 0;\n    static uint discard_readings = 5;\n    /* Read sensor data */\n    uint32_t D1, D2;\n    uint8_t data[3];\n    data[0] = CMD_CONV_D1 + OVERSAMPLING_4096;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, false);\n    vTaskDelay(SENSOR_INTERVAL_MS / portTICK_PERIOD_MS);\n    data[0] = CMD_ADC_READ;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 3, false);\n    D1 = (uint32_t)data[0] << 16 | (uint16_t)data[1] << 8 | data[2];\n    data[0] = CMD_CONV_D2 + OVERSAMPLING_4096;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, false);\n    vTaskDelay(SENSOR_INTERVAL_MS / portTICK_PERIOD_MS);\n    data[0] = CMD_ADC_READ;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 3, false);\n    D2 = (uint32_t)data[0] << 16 | (uint16_t)data[1] << 8 | data[2];\n\n    /* Calculation */\n    int32_t dT, TEMP, T2 = 0;\n    int64_t OFF, SENS, OFF2 = 0, SENS2 = 0;\n    dT = D2 - ((uint32_t)calibration->C5 << 8);\n    TEMP = 2000 + ((int64_t)dT * calibration->C6 >> 23);\n    OFF = ((int64_t)calibration->C2 << 16) + (((int64_t)calibration->C4 * dT) >> 7);\n    SENS = ((int64_t)calibration->C1 << 15) + (((int64_t)calibration->C3 * dT) >> 8);\n\n    if (TEMP < 2000) {\n        T2 = (dT * dT) >> 31;\n        OFF2 = 5 * (TEMP - 2000) * (TEMP - 2000) / 2;\n        SENS2 = 5 * (TEMP - 2000) * (TEMP - 2000) / 4;\n    }\n    if (TEMP < -1500) {\n        OFF2 = OFF2 + 7 * (TEMP + 1500) * (TEMP + 1500);\n        SENS2 = SENS2 + 11 * (TEMP + 1500) * (TEMP + 1500) / 2;\n    }\n    TEMP = TEMP - T2;\n    OFF = OFF - OFF2;\n    SENS = SENS - SENS2;\n    int32_t P = (((D1 * SENS) >> 21) - OFF) >> 15;\n    *parameter->temperature = (float)TEMP / 100;  // °C\n    *parameter->pressure = (float)P;              // Pa\n    if (pressure_initial == 0 && discard_readings == 0) pressure_initial = *parameter->pressure;\n    *parameter->altitude = get_altitude(*parameter->pressure, *parameter->temperature, pressure_initial);\n    get_vspeed(parameter->vspeed, *parameter->altitude, VSPEED_INTERVAL_MS);\n    if (discard_readings > 0) discard_readings--;\n    debug(\"\\nMS5611 P0: %.0f\", pressure_initial);\n#ifdef SIM_SENSORS\n    *parameter->temperature = 12.34;\n    *parameter->pressure = 1234.56;\n    *parameter->altitude = 2345;\n#endif\n}\n\nstatic void begin(ms5611_parameters_t *parameter, ms5611_calibration_t *calibration) {\n    i2c_init(i2c0, 100 * 1000);\n    gpio_set_function(I2C0_SDA_GPIO, GPIO_FUNC_I2C);\n    gpio_set_function(I2C0_SCL_GPIO, GPIO_FUNC_I2C);\n    gpio_pull_up(I2C0_SDA_GPIO);\n    gpio_pull_up(I2C0_SCL_GPIO);\n\n    uint8_t data[12];\n\n    // Find sensor address\n    parameter->address = I2C_ADDRESS_1;\n    if (i2c_write_blocking(i2c0, I2C_ADDRESS_1, data, 1, false) == PICO_ERROR_GENERIC) {\n        parameter->address = I2C_ADDRESS_2;\n    }\n\n    // Read calibration data\n    data[0] = CMD_RESET;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, false);\n    vTaskDelay(SENSOR_INTERVAL_MS / portTICK_PERIOD_MS);\n    data[0] = CMD_READ_PROM + 2;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 2, false);\n    calibration->C1 = ((uint16_t)data[0] << 8) | data[1];\n    data[0] = CMD_READ_PROM + 4;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 2, false);\n    calibration->C2 = ((uint16_t)data[0] << 8) | data[1];\n    data[0] = CMD_READ_PROM + 6;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 2, false);\n    calibration->C3 = ((uint16_t)data[0] << 8) | data[1];\n    data[0] = CMD_READ_PROM + 8;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 2, false);\n    calibration->C4 = ((uint16_t)data[0] << 8) | data[1];\n    data[0] = CMD_READ_PROM + 10;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 2, false);\n    calibration->C5 = ((uint16_t)data[0] << 8) | data[1];\n    data[0] = CMD_READ_PROM + 12;\n    i2c_write_blocking(i2c0, parameter->address, data, 1, true);\n    i2c_read_blocking(i2c0, parameter->address, data, 2, false);\n    calibration->C6 = ((uint16_t)data[0] << 8) | data[1];\n}\n"
  },
  {
    "path": "board/project/sensor/ms5611.h",
    "content": "#ifndef MS5611_H\n#define MS5611_H\n\n#include \"common.h\"\n\ntypedef struct ms5611_parameters_t {\n    float alpha_vario;\n    bool auto_offset;\n    uint8_t address;\n    float *temperature, *pressure, *altitude, *vspeed;\n} ms5611_parameters_t;\n\ntypedef struct ms5611_calibration_t {\n    uint16_t C1, C2, C3, C4, C5, C6;\n} ms5611_calibration_t;\n\nextern context_t context;\n\nvoid ms5611_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/ntc.c",
    "content": "#include \"ntc.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"hardware/adc.h\"\n\n// Thermistors (NTC 100k, R1 10k)\n#define NTC_R_REF 100000UL\n#define NTC_R1 10000\n#define NTC_BETA 4190\n// #define NTC_A1 3.35E-03\n// #define NTC_B1 2.46E-04\n// #define NTC_C1 3.41E-06\n// #define NTC_D1 1.03E-07\n\nvoid ntc_task(void *parameters) {\n    ntc_parameters_t parameter = *(ntc_parameters_t *)parameters;\n    adc_init();\n    adc_gpio_init(parameter.adc_num + 26);\n    //gpio_pull_down(parameter.adc_num + 26);\n    *parameter.ntc = 0;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        float voltage = voltage_read(parameter.adc_num);\n        float ntcR_Rref = (voltage * NTC_R1 / (BOARD_VCC - voltage)) / NTC_R_REF;\n        if (ntcR_Rref < 0.0001) ntcR_Rref = 0.0001;\n        float temperature = 1 / (log(ntcR_Rref) / NTC_BETA + 1 / 298.15) - 273.15;\n        *parameter.ntc = get_average(parameter.alpha, *parameter.ntc, temperature);\n#ifdef SIM_SENSORS\n        *parameter.ntc = 12.34;\n#endif\n        *parameter.ntc += parameter.offset;\n        debug(\"\\nTemperature (%u): %.2f (offset %d)\", uxTaskGetStackHighWaterMark(NULL), *parameter.ntc, parameter.offset);\n        vTaskDelay(1000 / parameter.rate / portTICK_PERIOD_MS);\n    }\n}"
  },
  {
    "path": "board/project/sensor/ntc.h",
    "content": "#ifndef NTC_H\n#define NTC_H\n\n#include \"common.h\"\n\ntypedef struct ntc_parameters_t {\n    uint8_t adc_num;\n    uint8_t rate;\n    int8_t offset;\n    float alpha;\n    float *ntc;\n\n} ntc_parameters_t;\n\nextern context_t context;\n\nvoid ntc_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/pwm_out.c",
    "content": "#include \"pwm_out.h\"\n\n#include <stdio.h>\n\n#include \"hardware/clocks.h\"\n#include \"hardware/pwm.h\"\n\nvoid pwm_out_task(void *parameters) {\n    float *rpm = (float *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n\n    const float clock_div = 100;\n    gpio_set_function(PWM_OUT_GPIO, GPIO_FUNC_PWM);\n    uint slice_num = pwm_gpio_to_slice_num(PWM_OUT_GPIO);\n    uint wrap = 0;\n    pwm_config c = pwm_get_default_config();\n    pwm_config_set_clkdiv(&c, clock_div);\n    pwm_init(slice_num, &c, true);\n\n    while (1) {\n        ulTaskNotifyTake(pdTRUE, portMAX_DELAY);\n        if (*rpm < 1300)\n            pwm_set_enabled(slice_num, false);\n        else {\n            wrap = 60 / *rpm * clock_get_hz(clk_sys) / clock_div;\n            pwm_set_enabled(slice_num, true);\n            pwm_set_wrap(slice_num, wrap);\n            pwm_set_gpio_level(PWM_OUT_GPIO, wrap / 2);\n        }\n        debug(\"\\nPWM out (%u) > Rpm %.2f Wrap %i\", uxTaskGetStackHighWaterMark(NULL), *rpm, wrap);\n    }\n}\n"
  },
  {
    "path": "board/project/sensor/pwm_out.h",
    "content": "#ifndef PWM_OUT_H\n#define PWM_OUT_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid pwm_out_task(void *parameters);\n\n#endif\n"
  },
  {
    "path": "board/project/sensor/smart_esc.c",
    "content": "#include \"smart_esc.h\"\n\n#include <stdio.h>\n\n#include \"auto_offset.h\"\n#include \"capture_edge.h\"\n#include \"cell_count.h\"\n#include \"hardware/clocks.h\"\n#include \"pico/stdlib.h\"\n#include \"srxl.h\"\n#include \"srxl2.h\"\n#include \"string.h\"\n#include \"uart.h\"\n\n#define SRXL2_RECEIVER_ID 0x21\n#define SRXL2_ESC_ID 0x40\n#define SRXL2_RECEIVER_PRIORITY 0xA\n#define SRXL2_RECEIVER_BAUDRATE 1\n#define SRXL2_RECEIVER_INFO 0x7\n#define SRXL2_INTERVAL_MS 10\n#define SRXL2_RECEIVER_UID 0x27A2C29C  // 0x12345678\n\n#define XBUS_SMART_BAT 0x42\n#define XBUS_SMART_BAT_REALTIME 0x00\n#define XBUS_SMART_BAT_CELLS_1 0x10\n#define XBUS_SMART_BAT_CELLS_2 0x20\n#define XBUS_SMART_BAT_CELLS_3 0x30\n#define XBUS_SMART_BAT_ID 0x80\n#define XBUS_SMART_BAT_LIMITS 0x90\n\n#define PWM_TIMEOUT_MS 100\n#define CLOCK_DIV 1\n\ntypedef struct srxl2_smart_bat_realtime_t {\n    uint8_t identifier;  // Source device 0x42\n    uint8_t s_id;        // Secondary ID\n    uint8_t type;        // 0x00\n    int8_t temp;\n    uint32_t current;      // A\n    uint16_t consumption;  // mAh\n    uint16_t min_cel;\n    uint16_t max_cel;\n} __attribute__((packed)) srxl2_smart_bat_realtime_t;\n\ntypedef struct srxl2_smart_bat_cells_1_t {\n    uint8_t identifier;  // Source device 0x42\n    uint8_t s_id;        // Secondary ID\n    uint8_t type;        // 0x10\n    int8_t temp;\n    uint16_t cell_1;  // V * 1000\n    uint16_t cell_2;\n    uint16_t cell_3;\n    uint16_t cell_4;\n    uint16_t cell_5;\n    uint16_t cell_6;\n} __attribute__((packed)) srxl2_smart_bat_cells_1_t;\n\ntypedef struct srxl2_smart_bat_cells_2_t {\n    uint8_t identifier;  // Source device 0x42\n    uint8_t s_id;        // Secondary ID\n    uint8_t type;        // 0x20\n    int8_t temp;\n    uint16_t cell_7;\n    uint16_t cell_8;\n    uint16_t cell_9;\n    uint16_t cell_10;\n    uint16_t cell_11;\n    uint16_t cell_12;\n} __attribute__((packed)) srxl2_smart_bat_cells_2_t;\n\ntypedef struct srxl2_smart_bat_cells_3_t {\n    uint8_t identifier;  // Source device 0x42\n    uint8_t s_id;        // Secondary ID\n    uint8_t type;        // 0x30\n    int8_t temp;\n    uint16_t cell_13;\n    uint16_t cell_14;\n    uint16_t cell_15;\n    uint16_t cell_16;\n    uint16_t cell_17;\n    uint16_t cell_18;\n} __attribute__((packed)) srxl2_smart_bat_cells_3_t;\n\ntypedef struct srxl2_smart_bat_id_t {\n    uint8_t identifier;  // Source device 0x42\n    uint8_t s_id;        // Secondary ID\n    uint8_t type;        // 0x80\n    uint8_t chemistery;\n    uint8_t cells;\n    uint8_t mfg_code;\n    uint16_t cycles;\n    uint8_t uid;\n} __attribute__((packed)) srxl2_smart_bat_id_t;\n\ntypedef struct srxl2_smart_bat_limits_t {\n    uint8_t identifier;  // Source device 0x42\n    uint8_t s_id;        // Secondary ID\n    uint8_t type;        // 0x90\n    uint8_t rfu;\n    uint16_t capacity;\n    uint16_t discharge_rate;\n    uint16_t overdischarge;\n    uint16_t zero_capacity;\n    uint16_t fully_charged;\n    uint8_t min_temp;\n    uint8_t mex_temp;\n} __attribute__((packed)) srxl2_smart_bat_limits_t;\n\ntypedef struct srxl2_channel_data_t {\n    int8_t rssi;\n    uint16_t frame_losses;\n    uint32_t channel_mask;\n    uint16_t channel_data_ch1;  // throttle\n    uint16_t channel_data_ch7;  // reverse\n} __attribute__((packed)) srxl2_channel_data_t;\n\ntypedef struct srxl2_control_packet_t {\n    uint8_t header;\n    uint8_t type;\n    uint8_t len;\n    uint8_t command;\n    uint8_t reply_id;\n    srxl2_channel_data_t channel_data;\n    uint16_t crc;\n} __attribute__((packed)) srxl2_control_packet_t;\n\n#define SRXL2_CONTROL_LEN_CHANNEL (5 + sizeof(srxl2_channel_data_t) + 2)  // header + channel data + crc\n\nstatic volatile uint8_t esc_id = 0, esc_priority = 10;\nstatic volatile uint16_t throttle = 0, reverse = 0;\nstatic volatile bool packet_pending = false;\n\nstatic void process(smart_esc_parameters_t *parameter);\nstatic void read_packet(uint8_t *buffer, smart_esc_parameters_t *parameter);\nstatic void send_packet(void);\nstatic void capture_pwm_throttle_handler(uint counter, edge_type_t edge);\nstatic void capture_pwm_reverse_handler(uint counter, edge_type_t edge);\nstatic int64_t timeout_throttle_callback(alarm_id_t id, void *user_data);\nstatic int64_t timeout_reverse_callback(alarm_id_t id, void *user_data);\nstatic int64_t alarm_packet(alarm_id_t id, void *user_data);\n\nvoid smart_esc_task(void *parameters) {\n    smart_esc_parameters_t parameter = *(smart_esc_parameters_t *)parameters;\n    *parameter.rpm = 0;\n    *parameter.voltage = 0;\n    *parameter.current = 0;\n    *parameter.temperature_fet = 0;\n    *parameter.temperature_bec = 0;\n    *parameter.voltage_bec = 0;\n    *parameter.current_bec = 0;\n    *parameter.current_bat = 0;\n    *parameter.consumption = 0;\n    xTaskNotifyGive(context.receiver_task_handle);\n#ifdef SIM_SENSORS\n    *parameter.rpm = 12345.67;\n    *parameter.consumption = 123.4;\n    *parameter.voltage = 12.34;\n    *parameter.current = 5.678;\n    *parameter.temperature_fet = 12.34;\n    *parameter.temperature_bec = 23.45;\n#endif\n    capture_edge_init(pio0, SMART_ESC_PWM_GPIO, 2, CLOCK_DIV, PIO0_IRQ_0);\n    capture_edge_set_handler(0, capture_pwm_throttle_handler);\n    capture_edge_set_handler(1, capture_pwm_reverse_handler);\n    gpio_pull_up(SMART_ESC_PWM_GPIO);\n    gpio_pull_up(SMART_ESC_PWM_GPIO + 1);\n    uart1_begin(115200, UART1_TX_GPIO, UART_ESC_RX, SRXL2_TIMEOUT_US, 8, 1, UART_PARITY_NONE, false, true);\n    add_alarm_in_us(0, alarm_packet, NULL, true);\n\n    while (1) {\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(&parameter);\n        if (packet_pending) {\n            packet_pending = false;\n            send_packet();\n        }\n    }\n}\n\nstatic void process(smart_esc_parameters_t *parameter) {\n    static uint32_t timestamp = 0;\n    uint8_t length = uart1_available();\n    if (length) {\n        uint8_t data[length];\n        uart1_read_bytes(data, length);\n        debug(\"\\nSmart ESC (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n        debug_buffer(data, length, \" 0x%X\");\n        uint16_t crc = srxl_get_crc(data, length - 2);\n        if ((crc >> 8) != data[length - 2] || (crc & 0xFF) != data[length - 1]) {\n            debug(\" -> BAD CRC 0x%X\", crc);\n            return;\n        }\n        // Request for handshake to enable srxl2 mode\n        if (data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_HANDSHAKE && data[4] == 0) {\n            debug(\"\\nSmart ESC. Handshake request from 0x%X\", data[3]);\n            if (data[3] == SRXL2_ESC_ID)\n                srxl2_send_handshake(uart1, SRXL2_RECEIVER_ID, SRXL2_ESC_ID, SRXL2_RECEIVER_PRIORITY,\n                                     SRXL2_RECEIVER_BAUDRATE, SRXL2_RECEIVER_INFO, SRXL2_RECEIVER_UID);\n        }\n        // Handshake confirmation\n        else if (data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_HANDSHAKE && data[3] == SRXL2_ESC_ID) {\n            esc_id = data[3];\n            esc_priority = data[5];\n            debug(\"\\nSmart ESC. Handshake with 0x%X completed \", esc_id);\n        }\n        // Telemetry packet\n        else if (data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_TELEMETRY && data[3] == SRXL2_RECEIVER_ID) {\n            read_packet(data + 4, parameter);\n        }\n        // Re-handshake request\n        else if (data[0] == SRXL2_HEADER && data[1] == SRXL2_PACKET_TYPE_TELEMETRY && data[3] == 0xFF) {\n            srxl2_send_handshake(uart1, SRXL2_RECEIVER_ID, SRXL2_ESC_ID, SRXL2_RECEIVER_PRIORITY,\n                                 SRXL2_RECEIVER_BAUDRATE, SRXL2_RECEIVER_INFO, SRXL2_RECEIVER_UID);\n        }\n    }\n}\n\nstatic void read_packet(uint8_t *buffer, smart_esc_parameters_t *parameter) {\n    if (buffer[0] == XBUS_ESC_ID) {\n        xbus_esc_t esc;\n        static uint32_t timestamp = 0;\n        memcpy(&esc, buffer, sizeof(xbus_esc_t));\n        *parameter->rpm = swap_16(esc.rpm) * 10 * parameter->rpm_multiplier;\n        *parameter->voltage = swap_16(esc.volts_input) / 100.0;\n        *parameter->current = swap_16(esc.current_motor) / 100.0;\n        *parameter->voltage_bec = esc.voltage_bec == 0xFF ? 0 : esc.voltage_bec / 2.0;\n        *parameter->current_bec = esc.current_bec == 0xFF ? 0 : esc.current_bec / 100.0;\n        *parameter->temperature_fet = esc.temp_fet == 0xFFFF ? 0 : (swap_16(esc.temp_fet) / 10.0);\n        *parameter->temperature_bec = esc.temp_bec == 0xFFFF ? 0 : swap_16(esc.temp_bec) / 10.0;\n        if (parameter->calc_consumption) *parameter->consumption += get_consumption(*parameter->current, 0, &timestamp);\n        debug(\"\\nSmart ESC (%u) < Rpm: %.0f Volt: %0.2f Curr: %.2f TempFet: %.0f TempBec: %.0f Vbec: %.1f Cbec: %.1f \",\n              uxTaskGetStackHighWaterMark(NULL), *parameter->rpm, *parameter->voltage, *parameter->current,\n              *parameter->temperature_fet, *parameter->temperature_bec, *parameter->voltage_bec,\n              *parameter->current_bec);\n    } else if (buffer[0] == XBUS_SMART_BAT) {\n        switch (buffer[2]) {\n            case XBUS_SMART_BAT_REALTIME: {\n                srxl2_smart_bat_realtime_t bat_realtime;\n                memcpy(&bat_realtime, buffer, sizeof(srxl2_smart_bat_realtime_t));\n                *parameter->temperature_bat = bat_realtime.temp;\n                *parameter->current_bat = bat_realtime.current / 1000.0;\n                if (!parameter->calc_consumption) *parameter->consumption = bat_realtime.consumption / 10.0;\n                debug(\"\\nSmart ESC (Bat 0x%X) (%u) < Temp: %.0f Curr: %0.2f Cons: %.2f \", XBUS_SMART_BAT_REALTIME,\n                      uxTaskGetStackHighWaterMark(NULL), *parameter->temperature_bat, *parameter->current_bat,\n                      *parameter->consumption);\n                break;\n            }\n            case XBUS_SMART_BAT_CELLS_1: {\n                srxl2_smart_bat_cells_1_t bat_cells_1;\n                memcpy(&bat_cells_1, buffer, sizeof(srxl2_smart_bat_cells_1_t));\n                *parameter->temperature_bat = bat_cells_1.temp;\n                *parameter->cell[1] = bat_cells_1.cell_1 / 1000.0;\n                *parameter->cell[2] = bat_cells_1.cell_2 / 1000.0;\n                *parameter->cell[3] = bat_cells_1.cell_3 / 1000.0;\n                *parameter->cell[4] = bat_cells_1.cell_4 / 1000.0;\n                *parameter->cell[5] = bat_cells_1.cell_5 / 1000.0;\n                *parameter->cell[6] = bat_cells_1.cell_6 / 1000.0;\n                debug(\"\\nSmart ESC (Bat 0x%X) (%u) < Temp: %.0f C1: %.3f C2: %.3f C3: %.3f C4: %.3f C5: %.3f C6: %.3f \",\n                      XBUS_SMART_BAT_CELLS_1, uxTaskGetStackHighWaterMark(NULL), *parameter->temperature_bat,\n                      *parameter->cell[1], *parameter->cell[2], *parameter->cell[3], *parameter->cell[4],\n                      *parameter->cell[5], *parameter->cell[6]);\n                break;\n            }\n            case XBUS_SMART_BAT_CELLS_2: {\n                srxl2_smart_bat_cells_2_t bat_cells_2;\n                memcpy(&bat_cells_2, buffer, sizeof(srxl2_smart_bat_cells_2_t));\n                *parameter->temperature_bat = bat_cells_2.temp;\n                *parameter->cell[7] = bat_cells_2.cell_7 / 1000.0;\n                *parameter->cell[8] = bat_cells_2.cell_8 / 1000.0;\n                *parameter->cell[9] = bat_cells_2.cell_9 / 1000.0;\n                *parameter->cell[10] = bat_cells_2.cell_10 / 1000.0;\n                *parameter->cell[11] = bat_cells_2.cell_11 / 1000.0;\n                *parameter->cell[12] = bat_cells_2.cell_12 / 1000.0;\n                debug(\n                    \"\\nSmart ESC (Bat 0x%X) (%u) < Temp: %.0f C7: %.3f C8: %.3f C9: %.3f C10: %.3f C11: %.3f C12: \"\n                    \"%.3f \",\n                    XBUS_SMART_BAT_CELLS_2, uxTaskGetStackHighWaterMark(NULL), *parameter->temperature_bat,\n                    *parameter->cell[7], *parameter->cell[8], *parameter->cell[9], *parameter->cell[10],\n                    *parameter->cell[11], *parameter->cell[12]);\n                break;\n            }\n            case XBUS_SMART_BAT_CELLS_3: {\n                srxl2_smart_bat_cells_3_t bat_cells_3;\n                memcpy(&bat_cells_3, buffer, sizeof(srxl2_smart_bat_cells_3_t));\n                *parameter->temperature_bat = bat_cells_3.temp;\n                *parameter->cell[13] = bat_cells_3.cell_13 / 1000.0;\n                *parameter->cell[14] = bat_cells_3.cell_14 / 1000.0;\n                *parameter->cell[15] = bat_cells_3.cell_15 / 1000.0;\n                *parameter->cell[16] = bat_cells_3.cell_16 / 1000.0;\n                *parameter->cell[17] = bat_cells_3.cell_17 / 1000.0;\n                *parameter->cell[18] = bat_cells_3.cell_18 / 1000.0;\n                debug(\n                    \"\\nSmart ESC (Bat 0x%X) (%u) < Temp: %.0f C13: %.3f C14: %.3f C15: %.3f C16: %.3f C17: %.3f C18: \"\n                    \"%.3f \",\n                    XBUS_SMART_BAT_CELLS_3, uxTaskGetStackHighWaterMark(NULL), *parameter->temperature_bat,\n                    *parameter->cell[13], *parameter->cell[14], *parameter->cell[15], *parameter->cell[16],\n                    *parameter->cell[17], *parameter->cell[18]);\n                break;\n            }\n            case XBUS_SMART_BAT_ID: {\n                srxl2_smart_bat_id_t bat_id;\n                memcpy(&bat_id, buffer, sizeof(srxl2_smart_bat_id_t));\n                *parameter->cells = bat_id.cells;\n                *parameter->cycles = bat_id.cycles;\n                debug(\"\\nSmart ESC (Bat 0x%X) (%u) < Cells: %u Cycles: %u \", XBUS_SMART_BAT_ID,\n                      uxTaskGetStackHighWaterMark(NULL), *parameter->cells, *parameter->cycles);\n                break;\n            }\n        }\n    }\n}\n\nstatic void send_packet(void) {\n    static uint cont = 0;\n    if (!esc_id) {\n        srxl2_send_handshake(uart1, SRXL2_RECEIVER_ID, SRXL2_ESC_ID, SRXL2_RECEIVER_PRIORITY, SRXL2_RECEIVER_BAUDRATE,\n                             SRXL2_RECEIVER_INFO, SRXL2_RECEIVER_UID);\n    } else {\n        srxl2_control_packet_t packet;\n        packet.header = SRXL2_HEADER;\n        packet.type = SRXL2_PACKET_TYPE_CONTROL;\n        packet.len = SRXL2_CONTROL_LEN_CHANNEL;\n        packet.command = SRXL2_CONTROL_CMD_CHANNEL;\n        packet.reply_id = 0;\n        if (!(cont % 10)) packet.reply_id = esc_id;\n        srxl2_channel_data_t channel_data;\n        channel_data.rssi = 0x64;\n        channel_data.frame_losses = 0;\n        channel_data.channel_mask = 0B1000001;  // ch1 throttle, ch7 reverse\n        channel_data.channel_data_ch1 = throttle;\n        channel_data.channel_data_ch7 = reverse;\n        packet.channel_data = channel_data;\n        uint16_t crc = srxl_get_crc((uint8_t *)&packet, sizeof(packet) - 2);\n        packet.crc = swap_16(crc);\n        uart1_write_bytes((uint8_t *)&packet, sizeof(packet));\n        cont++;\n        static uint32_t last_time = 0;\n        uint32_t now = time_us_32();\n        debug(\"\\nSmart ESC (%u) Thr: %u Rev: %u (interval us %i)\", uxTaskGetStackHighWaterMark(NULL), throttle, reverse, now - last_time);\n        debug_buffer((uint8_t *)&packet, sizeof(packet), \" 0x%X\");\n        last_time = now;    \n    }\n}\n\nstatic void capture_pwm_throttle_handler(uint counter, edge_type_t edge) {\n    static uint counter_edge_rise = 0;\n    static alarm_id_t timeout_throttle_alarm_id = 0;\n    if (timeout_throttle_alarm_id) cancel_alarm(timeout_throttle_alarm_id);\n    if (edge == EDGE_RISE) {\n        counter_edge_rise = counter;\n    } else if (edge == EDGE_FALL && counter_edge_rise) {\n        uint pulse = (float)(counter - counter_edge_rise) / clock_get_hz(clk_sys) * COUNTER_CYCLES * 1000000;\n        int delta = pulse - 1000;\n        if (delta < 0) delta = 0;\n        if (delta > 1000) delta = 1000;\n        throttle = delta / 1000.0F * 65532;  // 0us->0% 1000us->100%\n\n        static uint32_t last_time = 0;\n        uint32_t now = time_us_32();\n        debug(\"\\nSmart Esc. Thr (%.0f%%) %u us %u interval us %i\", throttle / 65532.0F * 100, throttle, pulse, now - last_time);\n        last_time = now;\n    }\n    timeout_throttle_alarm_id = add_alarm_in_ms(PWM_TIMEOUT_MS, timeout_throttle_callback, NULL, true);\n}\n\nstatic void capture_pwm_reverse_handler(uint counter, edge_type_t edge) {\n    static uint counter_edge_rise = 0;\n    static alarm_id_t timeout_reverse_alarm_id = 0;\n    if (timeout_reverse_alarm_id) cancel_alarm(timeout_reverse_alarm_id);\n    if (edge == EDGE_RISE) {\n        counter_edge_rise = counter;\n    } else if (edge == EDGE_FALL && counter_edge_rise) {\n        uint pulse = (float)(counter - counter_edge_rise) / clock_get_hz(clk_sys) * COUNTER_CYCLES * 1000000;\n        int delta = pulse - 1000;\n        if (delta < 0) delta = 0;\n        if (delta > 1000) delta = 1000;\n        reverse = delta / 1000.0F * 65532;  // 0us->0% 1000us->100%\n        // debug(\"\\nSmart Esc. Rev (%.0f%%) %u us %u\", reverse / 65532.0F * 100, reverse, pulse);\n    }\n    timeout_reverse_alarm_id = add_alarm_in_ms(PWM_TIMEOUT_MS, timeout_reverse_callback, NULL, true);\n}\n\nstatic int64_t timeout_throttle_callback(alarm_id_t id, void *user_data) {\n    throttle = 0;\n    debug(\"\\nSmart Esc. Signal timeout. Throttle 0\");\n    return 0;\n}\n\nstatic int64_t timeout_reverse_callback(alarm_id_t id, void *user_data) {\n    reverse = 0;\n    debug(\"\\nSmart Esc. Signal timeout. Reverse 0\");\n    return 0;\n}\n\nstatic int64_t alarm_packet(alarm_id_t id, void *user_data) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    packet_pending = true;\n    vTaskNotifyGiveIndexedFromISR(context.uart1_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    return SRXL2_INTERVAL_MS * 1000;\n}\n"
  },
  {
    "path": "board/project/sensor/smart_esc.h",
    "content": "#ifndef SMART_ESC_H\n#define SMART_ESC_H\n\n#include \"common.h\"\n\ntypedef struct smart_esc_parameters_t {\n    bool calc_consumption;\n    float rpm_multiplier;\n    float alpha_rpm, alpha_voltage, alpha_current, alpha_temperature;\n    float *rpm, *voltage, *current, *temperature_fet, *temperature_bec, *voltage_bec,\n        *current_bec;                                    // esc\n    float *temperature_bat, *current_bat, *consumption;  // bat realtime\n    float *cell[18];                                     // cells\n    uint8_t *cells;                                      // bat id\n    uint16_t *cycles;                                    // bat id\n} smart_esc_parameters_t;\n\nextern context_t context;\n\nvoid smart_esc_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/voltage.c",
    "content": "#include \"voltage.h\"\n\n#include <stdio.h>\n\n#include \"hardware/adc.h\"\n#include \"pico/stdlib.h\"\n\nvoid voltage_task(void *parameters) {\n    voltage_parameters_t parameter = *(voltage_parameters_t *)parameters;\n    adc_init();\n    adc_gpio_init(parameter.adc_num + 26);\n    //gpio_pull_down(parameter.adc_num + 26);\n    *parameter.voltage = 0;\n    xTaskNotifyGive(context.receiver_task_handle);\n    while (1) {\n        *parameter.voltage =\n            get_average(parameter.alpha, *parameter.voltage, voltage_read(parameter.adc_num) * parameter.multiplier);\n#ifdef SIM_SENSORS\n        *parameter.voltage = 12.34;\n#endif\n        debug(\"\\nVoltage (%u): %.2f\", uxTaskGetStackHighWaterMark(NULL), *parameter.voltage);\n        vTaskDelay(1000 / parameter.rate / portTICK_PERIOD_MS);\n    }\n}\n"
  },
  {
    "path": "board/project/sensor/voltage.h",
    "content": "#ifndef VOLTAGE_H\n#define VOLTAGE_H\n\n#include \"common.h\"\n\ntypedef struct voltage_parameters_t {\n    uint8_t adc_num;\n    uint8_t rate;\n    float alpha, multiplier;\n    float *voltage;\n} voltage_parameters_t;\n\nextern context_t context;\n\nvoid voltage_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/vspeed.c",
    "content": "#include \"vspeed.h\"\n\n#include <stdio.h>\n\n#include \"common.h\"\n#include \"pico/stdlib.h\"\n\n#define VSPEED_INIT_DELAY_MS 5000\n#define VSPEED_INTERVAL_MS 1000\n\nvoid vspeed_task(void *parameters) {\n    vspeed_parameters_t *parameter = (vspeed_parameters_t *)parameters;\n    *parameter->vspeed = 0;\n    vTaskDelay(VSPEED_INIT_DELAY_MS / portTICK_PERIOD_MS);\n    float altitude = *parameter->altitude;\n    while (1) {\n        *parameter->vspeed = (*parameter->altitude - altitude) / VSPEED_INTERVAL_MS * 1000;\n        altitude = *parameter->altitude;\n#ifdef SIM_SENSORS\n        *parameter->vspeed = 12.34;\n#endif\n        debug(\"\\nVspeed (%u): %.2f\", uxTaskGetStackHighWaterMark(NULL), *parameter->vspeed);\n        vTaskDelay(parameter->interval / portTICK_PERIOD_MS);\n    }\n}"
  },
  {
    "path": "board/project/sensor/vspeed.h",
    "content": "#ifndef VSPEED_H\n#define VSPEED_H\n\n#include \"common.h\"\n\ntypedef struct vspeed_parameters_t {\n    uint interval;\n    float *altitude, *vspeed;\n} vspeed_parameters_t;\n\nextern context_t context;\n\nvoid vspeed_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sensor/xgzp68xxd.c",
    "content": "#include \"xgzp68xxd.h\"\n\n#include <stdio.h>\n\n#include \"hardware/i2c.h\"\n#include \"pico/stdlib.h\"\n#include \"stdlib.h\"\n\n#define I2C_ADDRESS 0X6D\n\n#define REG_CMD 0x30\n#define REG_DATA 0X06\n\n#define CONTROL_SINGLE 0b10\n#define CONTROL_CONT 0b11\n\n#define SLEEP_TIME_62 0b0001   // 62.5ms\n#define SLEEP_TIME_125 0b0010  // 125 ms\n#define SLEEP_TIME_187 0b0011  // 187.5ms\n\n#define SENSOR_INTERVAL_MS 100  // min 62.5ms\n\nstatic void read(xgzp68xxd_parameters_t *parameter);\nstatic void begin(void);\n\nvoid xgzp68xxd_task(void *parameters) {\n    xgzp68xxd_parameters_t parameter = *(xgzp68xxd_parameters_t *)parameters;\n    xTaskNotifyGive(context.receiver_task_handle);\n    *parameter.temperature = 0;\n    *parameter.pressure = 0;\n    TaskHandle_t task_handle;\n\n    vTaskDelay(500 / portTICK_PERIOD_MS);\n    begin();\n    while (1) {\n        read(&parameter);\n        debug(\"\\nXGZP68XXD (%u) < Temp(C): %.2f Pressure(kPa): %.2f\", uxTaskGetStackHighWaterMark(NULL),\n              *parameter.temperature, *parameter.pressure / 1000);\n        vTaskDelay(SENSOR_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void read(xgzp68xxd_parameters_t *parameter) {\n    int16_t temperature_raw;\n    int32_t pressure_raw;\n    uint8_t data[5];\n    uint8_t reg[1] = {REG_DATA};\n\n    i2c_write_blocking(i2c0, I2C_ADDRESS, reg, 1, true);\n    i2c_read_blocking(i2c0, I2C_ADDRESS, data, 5, false);\n\n    pressure_raw = (((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) | ((uint32_t)data[2] << 8)) >> 8;\n    temperature_raw = ((uint16_t)data[3] << 8) | data[4];\n    *parameter->temperature = temperature_raw / 256.0;          // C\n    *parameter->pressure = (float)pressure_raw / parameter->k;  // Pa\n#ifdef SIM_SENSORS\n    *parameter->temperature = 12.34;  // C\n    *parameter->pressure = 101325;    // Pa\n#endif\n}\n\nstatic void begin(void) {\n    i2c_init(i2c0, 400 * 1000);\n    gpio_set_function(I2C0_SDA_GPIO, GPIO_FUNC_I2C);\n    gpio_set_function(I2C0_SCL_GPIO, GPIO_FUNC_I2C);\n    gpio_pull_up(I2C0_SDA_GPIO);\n    gpio_pull_up(I2C0_SCL_GPIO);\n\n    // Set continuous mode at 62.5ms interval\n    uint8_t data[2];\n    data[0] = REG_CMD;\n    data[1] = SLEEP_TIME_62;\n    i2c_write_blocking(i2c0, I2C_ADDRESS, data, 2, false);\n\n    // Wait for first reading\n    vTaskDelay(20 / portTICK_PERIOD_MS);\n}\n"
  },
  {
    "path": "board/project/sensor/xgzp68xxd.h",
    "content": "#ifndef XGZP68XXD_H\n#define XGZP68XXD_H\n\n#include \"common.h\"\n\ntypedef struct xgzp68xxd_parameters_t {\n    uint16_t k;\n    float *temperature, *pressure;\n} xgzp68xxd_parameters_t;\n\nextern context_t context;\n\nvoid xgzp68xxd_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/serial_monitor.c",
    "content": "#include \"serial_monitor.h\"\n\n#include <math.h>\n#include <stdio.h>\n\n#include \"config.h\"\n#include \"pico/stdlib.h\"\n#include \"uart.h\"\n#include \"uart_pio.h\"\n\nstatic void process(config_t *config);\n\nvoid serial_monitor_task(void *parameters) {\n    xTaskNotifyGive(context.receiver_task_handle);\n    config_t *config = config_read();\n    debug(\"\\nSerial monitor init. GPIO: %uBaudrate: %u Stop bits: %u Parity: %u Inverted: %u Timeout (ms): %u\",\n          config->serial_monitor_gpio, config->serial_monitor_baudrate, config->serial_monitor_stop_bits,\n          config->serial_monitor_parity, config->serial_monitor_inverted, config->serial_monitor_timeout_ms);\n    switch (config->serial_monitor_gpio) {\n        case 1:\n            uart0_begin(config->serial_monitor_baudrate, 0, 1, config->serial_monitor_timeout_ms * 1000, 8,\n                        config->serial_monitor_stop_bits, config->serial_monitor_parity,\n                        config->serial_monitor_inverted, false);\n            break;\n        case 5:\n            uart1_begin(config->serial_monitor_baudrate, 4, 5, config->serial_monitor_timeout_ms * 1000, 8,\n                        config->serial_monitor_stop_bits, config->serial_monitor_parity,\n                        config->serial_monitor_inverted, false);\n            break;\n        case 6:\n            uart_pio_begin(config->serial_monitor_baudrate, UART_GPIO_NONE, 6, config->serial_monitor_timeout_ms * 1000,\n                           pio0, PIO0_IRQ_0, 8, config->serial_monitor_stop_bits, config->serial_monitor_parity);\n    }\n\n    while (1) {\n        // debug(\"\\n>> %u\", config->serial_monitor_gpio);\n        // vTaskDelay(1000 / portTICK_PERIOD_MS);\n        ulTaskNotifyTakeIndexed(1, pdTRUE, portMAX_DELAY);\n        process(config);\n    }\n}\n\nstatic void process(config_t *config) {\n    static uint ts = 0;\n    uint length;\n    uint8_t data[512];\n    switch (config->serial_monitor_gpio) {\n        case 1:\n            length = uart0_available();\n            if (!length) return;\n            uart0_read_bytes(data, length);\n            break;\n        case 5:\n            length = uart1_available();\n            if (!length) return;\n            uart1_read_bytes(data, length);\n            break;\n        case 6:\n            length = uart_pio_available();\n            if (!length) return;\n            uart_pio_read_bytes(data, length);\n            break;\n    }\n    if (config->serial_monitor_timeout_ms)\n        debug(\"\\nSerial monitor (%u). GPIO: %u Length: %u (%u ms): \", uxTaskGetStackHighWaterMark(NULL),\n              config->serial_monitor_gpio, length, (time_us_32() - ts) / 1000);\n    if (config->serial_monitor_format == FORMAT_HEX) {\n        debug_buffer(data, length, \" 0x%X\");\n    } else if (context.debug)\n        debug_buffer(data, length, \"%c\");\n}"
  },
  {
    "path": "board/project/serial_monitor.h",
    "content": "#ifndef SERIAL_MONITOR_H\n#define SERIAL_MONITOR_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid serial_monitor_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/sim_rx.c",
    "content": "#include \"sim_rx.h\"\n\n#include <stdio.h>\n\n#include \"config.h\"\n#include \"hitec.h\"\n#include \"uart.h\"\n#include \"xbus.h\"\n\n#define SIM_RX_INTERVAL_MS 1000  // ms\n#define SIM_RX_TIMEOUT_MS 1      // ms\n#define IBUS_COMMAND_DISCOVER 0x8\n#define IBUS_COMMAND_TYPE 0x9\n#define IBUS_COMMAND_MEASURE 0xA\n\nstatic uint8_t sim_rx_status = 0;\nstatic QueueHandle_t uart_queue_handle;\n\nstatic void process(rx_protocol_t rx_protocol);\nstatic void ibus_send_data(uint8_t command, uint8_t address);\nstatic void ibus_send_byte(uint8_t c, uint16_t *crcP);\nstatic uint16_t jetiex_crc16(uint8_t *p, uint16_t len);\nstatic uint16_t jetiex_update_crc16(uint16_t crc, uint8_t data);\nstatic uint8_t smartport_get_crc(uint8_t *data);\n\nvoid sim_rx_task(void *parameters) {\n    sim_rx_parameters_t *parameter = (sim_rx_parameters_t *)parameters;\n    vTaskDelay(2000 / portTICK_PERIOD_MS);\n    if (UART_RECEIVER == uart0)\n        uart_queue_handle = context.uart0_queue_handle;\n    else\n        uart_queue_handle = context.uart1_queue_handle;\n    debug(\"\\nSim Rx init\");\n    while (1) {\n        vTaskDelay(SIM_RX_INTERVAL_MS / portTICK_PERIOD_MS);\n        process(parameter->rx_protocol);\n    }\n}\n\nstatic void process(rx_protocol_t rx_protocol) {\n#ifdef SIM_RX\n    // printf(\"\\nSim (%u) < \", uxTaskGetStackHighWaterMark(NULL));\n    xQueueReset(uart_queue_handle);\n    if (rx_protocol == RX_SMARTPORT) {\n        vTaskResume(context.led_task_handle);\n        uint8_t c[10] = {0};\n        c[0] = 0x7E;\n        c[1] = 0x71;  // sensor id 18 = 0x71 (10 = 0xE9)\n        xQueueSendToBack(uart_queue_handle, &c[0], 0);\n        xQueueSendToBack(uart_queue_handle, &c[1], 0);\n#ifdef SIM_SMARTPORT_SEND_CONFIG_LUA\n        if (sim_rx_status == 0)  // maintenance mode on\n        {\n            c[2] = 0x21;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        } else if (sim_rx_status == 1)  // request config\n        {\n            c[2] = 0x30;  // type_id\n            c[3] = 0x00;  // data_id\n            c[4] = 0x50;\n            c[5] = 0x00;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        } else if (sim_rx_status == 6)  // maintenance mode off\n        {\n            c[2] = 0x20;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        sim_rx_status++;\n#endif\n#ifdef SIM_SMARTPORT_RECEIVE_CONFIG_LUA\n        if (sim_rx_status == 0)  // maintenance mode on\n        {\n            c[2] = 0x21;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        if (sim_rx_status == 1)  // packet 1\n        {\n            c[2] = 0x31;  // type_id\n            c[3] = 0x00;  // data_id\n            c[4] = 0x50;\n            c[5] = 0xF1;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        if (sim_rx_status == 2)  // packet 2\n        {\n            c[2] = 0x31;  // type_id\n            c[3] = 0x00;  // data_id\n            c[4] = 0x50;\n            c[5] = 0xF2;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        if (sim_rx_status == 3)  // packet 3\n        {\n            c[2] = 0x31;  // type_id\n            c[3] = 0x00;  // data_id\n            c[4] = 0x50;\n            c[5] = 0xF3;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        if (sim_rx_status == 5)  // maintenance mode off\n        {\n            c[2] = 0x20;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        sim_rx_status++;\n#endif\n#ifdef SIM_SMARTPORT_SEND_SENSOR_ID\n        if (sim_rx_status == 0)  // maintenance mode on\n        {\n            c[2] = 0x21;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        } else if (sim_rx_status == 1)  // request sensor id\n        {\n            c[2] = 0x30;  // type_id\n            c[3] = 0x00;  // data_id\n            c[4] = 0x50;\n            c[5] = 0x01;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        } else if (sim_rx_status == 3)  // maintenance mode off\n        {\n            c[2] = 0x20;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        sim_rx_status++;\n#endif\n#ifdef SIM_SMARTPORT_RECEIVE_SENSOR_ID\n        if (sim_rx_status == 0)  // maintenance mode on\n        {\n            c[2] = 0x21;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        } else if (sim_rx_status == 1)  // change sensor id\n        {\n            c[2] = 0x31;  // type_id\n            c[3] = 0x00;  // data_id\n            c[4] = 0x50;\n            c[5] = 0x01;    // value\n            c[6] = 10 - 1;  // sensor id = 10 -> lua sensor id = 9\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        } else if (sim_rx_status == 3)  // maintenance mode off\n        {\n            c[2] = 0x20;  // type_id\n            c[3] = 0xFF;  // data_id\n            c[4] = 0xFF;\n            c[5] = 0x80;  // value\n            c[6] = 0x00;\n            c[7] = 0x00;\n            c[8] = 0x00;\n            c[9] = smartport_get_crc(c);\n            for (uint i = 2; i < 10; i++) xQueueSendToBack(uart_queue_handle, &c[i], 0);\n        }\n        sim_rx_status++;\n#endif\n    }\n\n    else if (rx_protocol == RX_XBUS) {\n        uint8_t sensor_id[] = {\n            XBUS_AIRSPEED_ID, XBUS_ALTIMETER_ID, XBUS_GPS_LOC_ID,     XBUS_GPS_STAT_ID,  XBUS_ESC_ID,\n            XBUS_BATTERY_ID,  XBUS_VARIO_ID,     XBUS_RPMVOLTTEMP_ID, XBUS_FUEL_FLOW_ID, XBUS_STRU_TELE_DIGITAL_AIR_ID};\n        static uint8_t sensor_index = 0;\n        xbus_i2c_handler(sensor_id[sensor_index]);\n        sensor_index++;\n        if (sensor_index > sizeof(sensor_id)) sensor_index = 0;\n    }\n\n    else if (rx_protocol == RX_SRXL) {\n        static uint8_t data[] = {0xA5, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};\n        for (uint8_t i = 0; i < sizeof(data); i++) {\n            xQueueSendToBack(uart_queue_handle, &data[i], 0);\n        }\n    }\n\n    else if (rx_protocol == RX_SRXL2) {\n        static uint8_t status = 0;\n        static uint8_t handshake_request[] = {0xA6, 0x21, 0xE,  0x10, 0x30, 0xA,  0x1,\n                                              0x1,  0xFC, 0x96, 0x8C, 0x4B, 0x30, 0xD9};\n        static uint8_t telemetry_request[] = {0xA6, 0xCD, 0x14, 0x0,  0x30, 0xEC, 0x0, 0x0,  0x91, 0x0,\n                                              0x0,  0x0,  0x60, 0x80, 0x0,  0x80, 0x0, 0x80, 0xB2, 0xA0};\n        if (status < 10) {\n            for (uint8_t i = 0; i < sizeof(handshake_request); i++) {\n                xQueueSendToBack(uart_queue_handle, &handshake_request[i], 0);\n            }\n            status++;\n        } else if (status == 10) {\n            for (uint8_t i = 0; i < sizeof(telemetry_request); i++) {\n                xQueueSendToBack(uart_queue_handle, &telemetry_request[i], 0);\n            }\n        }\n    }\n\n    else if (rx_protocol == RX_FRSKY_D) {\n    }\n\n    else if (rx_protocol == RX_IBUS) {\n        static uint8_t command = IBUS_COMMAND_DISCOVER;\n        static uint8_t address = 0;\n        command = IBUS_COMMAND_MEASURE;\n        ibus_send_data(command, address);\n        address++;\n        if (address == 16) {\n            address = 0;\n            if (command < IBUS_COMMAND_MEASURE) command++;\n        }\n    }\n\n    else if (rx_protocol == RX_SBUS) {\n        static uint8_t data[] = {0x0F, 0x0F, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,\n                                 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04};\n        for (uint8_t i = 0; i < sizeof(data); i++) {\n            xQueueSendToBack(uart_queue_handle, &data[i], 0);\n        }\n        data[24] += 0x10;\n        if (data[24] == 0x44) data[24] = 0x04;\n    }\n\n    else if (rx_protocol == RX_MULTIPLEX) {\n        static uint8_t cont = 0;\n        xQueueSendToBack(uart_queue_handle, &cont, 0);\n        cont++;\n        cont = cont % 16;\n    }\n\n    else if (rx_protocol == RX_JETIEX) {\n        uint8_t data[] = {0x3E, 0x3,  0x28, 0x2,  0x31, 0x20, 0x80, 0x3E, 0xDD, 0x2E, 0xEB, 0x2E,\n                          0xEC, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E,\n                          0xE0, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E, 0xE0, 0x2E,\n                          0xE0, 0x2E, 0xC5, 0xFE, 0x3D, 0x1,  0x8,  0x2,  0x3A, 0x0,  0xF9, 0xE2};\n        for (uint8_t i = 0; i < sizeof(data); i++) {\n            xQueueSendToBack(uart_queue_handle, &data[i], 0);\n        }\n    }\n\n    else if (rx_protocol == RX_HITEC) {\n        hitec_i2c_handler();\n    }\n\n    else if (rx_protocol == RX_CRSF) {\n        // uint8_t data[] = {0xC8, 0x06, 0x2C, 0xEE, 0xEF, 0x01, 0x00, 0x76};\n        // uint8_t data[] = {0xC8, 0x4, 0x7, 0x0, 0x5, 0x8};\n        uint8_t data[] = {0xC8, 0x18, 0x16, 0xDB, 0xE3, 0x62, 0xF9, 0xB6, 0xF7, 0x8B, 0xF2, 0x95, 0xAF,\n                          0x7C, 0xE5, 0x2B, 0x5F, 0xF9, 0xCA, 0x7,  0x0,  0x0,  0x4C, 0x7C, 0xE2, 0xA7};\n        /*uint8_t data[] = {0xC8, 0xC,  0x14, 0x11, 0x0,  0x64, 0xB,  0x0,  0x9,  0x7,  0x0,  0x0,  0x0,  0xF8,\n                          0xC8, 0x18, 0x16, 0xDB, 0xE3, 0x62, 0xF9, 0xB6, 0xF7, 0x8B, 0xF2, 0x95, 0xAF, 0x7C,\n                          0xE5, 0x2B, 0x5F, 0xF9, 0xCA, 0x7,  0x0,  0x0,  0x4C, 0x7C, 0xE2, 0xA7};*/\n        for (uint8_t i = 0; i < sizeof(data); i++) {\n            xQueueSendToBack(uart_queue_handle, &data[i], 0);\n        }\n    }\n\n    else if (rx_protocol == RX_SANWA) {\n        static uint8_t type = 0;\n        static const uint8_t data[5][10] = {{0x1, 0x4, 0x82, 0x2, 0xFF, 0x0, 0x3, 0x3, 0xFF, 0x8D},\n                                            {0x1, 0x1, 0x30, 0x4, 0xA7, 0x3, 0xFF, 0x3, 0xFF, 0xE1},\n                                            {0x1, 0x1, 0x31, 0x4, 0xA7, 0x0, 0x3, 0x3, 0xFF, 0xE3},\n                                            {0x1, 0x1, 0x30, 0x4, 0xA7, 0x3, 0xFF, 0x3, 0xFF, 0xE1},\n                                            {0x1, 0x1, 0x31, 0x4, 0xA8, 0x0, 0x3, 0x3, 0xFF, 0xE4}};\n        for (uint8_t i = 0; i < sizeof(data[type % 5]); i++) xQueueSendToBack(uart_queue_handle, &data[type % 5][i], 0);\n        type++;\n    }\n\n    else if (rx_protocol == RX_HOTT) {\n        uint8_t address[5] = {0x89, 0x8A, 0x8C, 0x8D, 0x8E};\n        static uint index = 0;\n        uint8_t type = 0x80;\n        xQueueSendToBack(uart_queue_handle, &type, 0);\n        xQueueSendToBack(uart_queue_handle, &address[index % 5], 0);\n        index++;\n    }\n\n    else if (rx_protocol == RX_JR_PROPO) {\n        static uint address = 0;\n        xQueueSendToBack(uart_queue_handle, &address, 0);\n        address++;\n        if (address > 10) address = 0;\n    }\n\n    else if (rx_protocol == RX_FPORT) {\n        uint8_t data[] = {0x7E, 0x19, 0x00, 0xAB, 0x00, 0x1F, 0x2B, 0xA2, 0x07, 0x3E, 0xF0, 0x81, 0xAF, 0x7C, 0x08, 0x40, 0x00, 0x02, 0x10, 0x80, 0x00, 0x04, 0x20, 0x00, 0x01, 0x00, 0x64, 0x06, 0x7E, 0x7E, 0x08, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x7E};\n        for (uint8_t i = 0; i < sizeof(data); i++) {\n            xQueueSendToBack(uart_queue_handle, &data[i], 0);\n        }\n    }\n\n    else if (rx_protocol == RX_FBUS) {\n               //          len    1     2     3     4     5     6     7     8     9     10   11    12    13    14     15    16   17     18   19    20    21    22    23    24    25    crc\n        uint8_t data[] = {0x18, 0xFF, 0x02, 0x07, 0x1F, 0x2B, 0xA2, 0x07, 0x3E, 0xF0, 0x81, 0xAF, 0x7C, 0xDC, 0x03, 0x1F, 0xF8, 0xC0, 0x07, 0x3E, 0xF0, 0x09, 0x7C, 0x15, 0x00, 0x64, 0x38, 0x08, 0x2F, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0};\n        for (uint8_t i = 0; i < sizeof(data); i++) {\n            xQueueSendToBack(uart_queue_handle, &data[i], 0);\n        }\n    }\n\n    else if (rx_protocol == RX_GHST) {\n        uint8_t data[] = {0x89, 0x0C, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xEA};\n        for (uint8_t i = 0; i < sizeof(data); i++) {\n            xQueueSendToBack(uart_queue_handle, &data[i], 0);\n        }\n    }\n\n    uart0_set_timestamp();\n    vTaskDelay(SIM_RX_TIMEOUT_MS / portTICK_PERIOD_MS);\n    xTaskNotifyGiveIndexed(context.uart0_notify_task_handle, 1);\n#endif\n}\n\nstatic void ibus_send_data(uint8_t command, uint8_t address) {\n    uint16_t crc = 0;\n    uint8_t *u8P;\n\n    // lenght\n    ibus_send_byte(4, &crc);\n\n    // command & address\n    ibus_send_byte(command << 4 | address, &crc);\n\n    // crc\n    crc = 0xFFFF - crc;\n    u8P = (uint8_t *)&crc;\n    ibus_send_byte(u8P[0], NULL);\n    ibus_send_byte(u8P[1], NULL);\n}\n\nvoid ibus_send_byte(uint8_t c, uint16_t *crcP) {\n    if (crcP != NULL) {\n        uint16_t crc = *crcP;\n        crc += c;\n        *crcP = crc;\n    }\n    xQueueSendToBack(uart_queue_handle, &c, 0);\n}\n\nstatic uint16_t jetiex_crc16(uint8_t *p, uint16_t len) {\n    uint16_t crc16_data = 0;\n    while (len--) {\n        crc16_data = jetiex_update_crc16(crc16_data, p[0]);\n        p++;\n    }\n    return (crc16_data);\n}\n\nstatic uint16_t jetiex_update_crc16(uint16_t crc, uint8_t data) {\n    uint16_t ret_val;\n    data ^= (uint8_t)(crc) & (uint8_t)(0xFF);\n    data ^= data << 4;\n    ret_val = ((((uint16_t)data << 8) | ((crc & 0xFF00) >> 8)) ^ (uint8_t)(data >> 4) ^ ((uint16_t)data << 3));\n    return ret_val;\n}\n\nstatic uint8_t smartport_get_crc(uint8_t *data) {\n    uint16_t crc = 0;\n    for (uint8_t i = 2; i < 9; i++) {\n        crc += data[i];\n        crc += crc >> 8;\n        crc &= 0x00FF;\n    }\n    return 0xFF - (uint8_t)crc;\n}"
  },
  {
    "path": "board/project/sim_rx.h",
    "content": "#ifndef SIM_RX_H\n#define SIM_RX_H\n\n#include \"common.h\"\n\nextern context_t context;\n\ntypedef struct sim_rx_parameters_t {\n    rx_protocol_t rx_protocol;\n} sim_rx_parameters_t;\n\nvoid sim_rx_task(void *parameters);\n\n#endif"
  },
  {
    "path": "board/project/uart.c",
    "content": "#include \"uart.h\"\n\n#include <stdio.h>\n\n#include \"hardware/irq.h\"\n#include \"hardware/uart.h\"\n\n#define UART0_BUFFER_SIZE 512\n#define UART1_BUFFER_SIZE 512\n\n#define enable_rx(UART) hw_set_bits(&uart_get_hw(UART)->cr, 0x00000200)\n#define disable_rx(UART) hw_clear_bits(&uart_get_hw(UART)->cr, 0x00000200)\n\nstatic volatile uint uart0_timeout, uart1_timeout, uart0_timestamp, uart1_timestamp;\nstatic volatile bool uart0_is_timedout = true, uart1_is_timedout = true;\nstatic bool half_duplex0, half_duplex1, inverted0, inverted1;\nstatic int gpio_tx0, gpio_rx0, gpio_tx1, gpio_rx1;\nstatic int64_t uart0_timeout_callback(alarm_id_t id, void *user_data);\nstatic int64_t uart1_timeout_callback(alarm_id_t id, void *user_data);\nstatic void uart0_rx_handler();\nstatic void uart1_rx_handler();\nstatic void uart_reset_queue_from_isr(QueueHandle_t queue_handle, BaseType_t *xHigherPriorityTaskWoken);\n\nvoid uart0_begin(uint baudrate, uint gpio_tx, uint gpio_rx, uint timeout, uint databits, uint stopbits,\n                 uart_parity_t parity, bool inverted, bool half_duplex) {\n    half_duplex0 = half_duplex;\n    gpio_tx0 = gpio_tx;\n    gpio_rx0 = gpio_rx;\n    inverted0 = inverted;\n    if (context.uart_alarm_pool == NULL) context.uart_alarm_pool = alarm_pool_create(2, 10);\n    uart_init(uart0, baudrate);\n    uart_set_fifo_enabled(uart0, false);\n    gpio_set_function(gpio_rx, GPIO_FUNC_UART);\n    if (!half_duplex) gpio_set_function(gpio_tx, GPIO_FUNC_UART);\n    if (!inverted) {\n        gpio_pull_up(gpio_tx);\n        gpio_pull_up(gpio_rx);\n    } else {\n        gpio_pull_down(gpio_tx);\n        gpio_pull_down(gpio_rx);\n        gpio_set_outover(gpio_tx, GPIO_OVERRIDE_INVERT);\n        gpio_set_inover(gpio_rx, GPIO_OVERRIDE_INVERT);\n    }\n    uart_set_format(uart0, databits, stopbits, parity);\n    irq_set_exclusive_handler(UART0_IRQ, uart0_rx_handler);\n    irq_set_enabled(UART0_IRQ, true);\n    uart0_timeout = timeout;\n    context.uart0_queue_handle = xQueueCreate(UART0_BUFFER_SIZE, sizeof(uint8_t));\n    uart_set_irq_enables(uart0, true, false);\n}\n\nvoid uart1_begin(uint baudrate, uint gpio_tx, uint gpio_rx, uint timeout, uint databits, uint stopbits,\n                 uart_parity_t parity, bool inverted, bool half_duplex) {\n    half_duplex1 = half_duplex;\n    gpio_tx1 = gpio_tx;\n    gpio_rx1 = gpio_rx;\n    inverted1 = inverted;\n    if (context.uart_alarm_pool == NULL) context.uart_alarm_pool = alarm_pool_create(2, 10);\n    uart_init(uart1, baudrate);\n    uart_set_fifo_enabled(uart1, false);\n    gpio_set_function(gpio_rx, GPIO_FUNC_UART);\n    if (!half_duplex) gpio_set_function(gpio_tx, GPIO_FUNC_UART);\n    if (!inverted) {\n        gpio_pull_up(gpio_tx);\n        gpio_pull_up(gpio_rx);\n    } else {\n        gpio_pull_down(gpio_tx);\n        gpio_pull_down(gpio_rx);\n        gpio_set_outover(gpio_tx, GPIO_OVERRIDE_INVERT);\n        gpio_set_inover(gpio_rx, GPIO_OVERRIDE_INVERT);\n    }\n    uart_set_format(uart1, databits, stopbits, parity);\n    irq_set_exclusive_handler(UART1_IRQ, uart1_rx_handler);\n    irq_set_enabled(UART1_IRQ, true);\n    uart1_timeout = timeout;\n    context.uart1_queue_handle = xQueueCreate(UART1_BUFFER_SIZE, sizeof(uint8_t));\n    uart_set_irq_enables(uart1, true, false);\n}\n\nstatic int64_t uart0_timeout_callback(alarm_id_t id, void *user_data) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    uart0_is_timedout = true;\n    vTaskNotifyGiveIndexedFromISR(context.uart0_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    return 0;\n}\n\nstatic int64_t uart1_timeout_callback(alarm_id_t id, void *user_data) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    uart1_is_timedout = true;\n    vTaskNotifyGiveIndexedFromISR(context.uart1_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    return 0;\n}\n\nstatic void uart_reset_queue_from_isr(QueueHandle_t queue_handle, BaseType_t *xHigherPriorityTaskWoken) {\n    uint8_t discarded;\n    while (uxQueueMessagesWaitingFromISR(queue_handle) > 0) {\n        xQueueReceiveFromISR(queue_handle, &discarded, xHigherPriorityTaskWoken);\n    }\n}\n\nstatic void uart0_rx_handler() {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    static alarm_id_t uart0_timeout_alarm_id = 0;\n    if (uart0_timeout_alarm_id) alarm_pool_cancel_alarm(context.uart_alarm_pool, uart0_timeout_alarm_id);\n    if (uart0_is_timedout) {\n        uart_reset_queue_from_isr(context.uart0_queue_handle, &xHigherPriorityTaskWoken);\n        uart0_is_timedout = false;\n    }\n    while (uart_is_readable(uart0)) {\n        uint8_t data = uart_getc(uart0);\n        // debug(\"-%X-\", data);\n        xQueueSendToBackFromISR(context.uart0_queue_handle, &data, &xHigherPriorityTaskWoken);\n        if (uart0_timeout == 0) {\n            vTaskNotifyGiveIndexedFromISR(context.uart0_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n            portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n        }\n    }\n    if (uart0_timeout) {\n        uart0_timeout_alarm_id =\n            alarm_pool_add_alarm_in_us(context.uart_alarm_pool, uart0_timeout, uart0_timeout_callback, NULL, true);\n    }\n    uart0_timestamp = time_us_32();\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n}\n\nstatic void uart1_rx_handler() {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    static alarm_id_t uart1_timeout_alarm_id = 0;\n    if (uart1_timeout_alarm_id) {\n        alarm_pool_cancel_alarm(context.uart_alarm_pool, uart1_timeout_alarm_id);\n    }\n    if (uart1_is_timedout) {\n        uart_reset_queue_from_isr(context.uart1_queue_handle, &xHigherPriorityTaskWoken);\n        uart1_is_timedout = false;\n    }\n    while (uart_is_readable(uart1)) {\n        uint8_t data = uart_getc(uart1);\n        // debug(\"%X \", data);\n        xQueueSendToBackFromISR(context.uart1_queue_handle, &data, &xHigherPriorityTaskWoken);\n        if (uart1_timeout == 0) {\n            vTaskNotifyGiveIndexedFromISR(context.uart1_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n            portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n        }\n    }\n    if (uart1_timeout) {\n        uart1_timeout_alarm_id =\n            alarm_pool_add_alarm_in_us(context.uart_alarm_pool, uart1_timeout, uart1_timeout_callback, NULL, true);\n    }\n    uart1_timestamp = time_us_32();\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n}\n\nuint8_t uart0_read() {\n    uint8_t value = 0;\n    xQueueReceive(context.uart0_queue_handle, &value, 0);\n    return value;\n}\n\nuint8_t uart1_read() {\n    uint8_t value = 0;\n    xQueueReceive(context.uart1_queue_handle, &value, 0);\n    return value;\n}\n\nvoid uart0_read_bytes(uint8_t *data, uint8_t lenght) {\n    for (uint8_t i = 0; i < lenght; i++) xQueueReceive(context.uart0_queue_handle, data + i, 0);\n}\n\nvoid uart1_read_bytes(uint8_t *data, uint8_t lenght) {\n    for (uint8_t i = 0; i < lenght; i++) xQueueReceive(context.uart1_queue_handle, data + i, 0);\n}\n\nvoid uart0_write(uint8_t data) {\n    if (half_duplex0) {\n        disable_rx(uart0);\n        gpio_set_function(gpio_tx0, GPIO_FUNC_UART);\n        if (inverted0) gpio_set_outover(gpio_tx0, GPIO_OVERRIDE_INVERT);\n    }\n    uart_putc_raw(uart0, data);\n    if (half_duplex0) {\n        uart_tx_wait_blocking(uart0);\n        enable_rx(uart0);\n        gpio_set_function(gpio_tx0, GPIO_FUNC_NULL);\n    }\n}\n\nvoid uart1_write(uint8_t data) {\n    if (half_duplex1) {\n        disable_rx(uart1);\n        gpio_set_function(gpio_tx1, GPIO_FUNC_UART);\n        if (inverted1) gpio_set_outover(gpio_tx1, GPIO_OVERRIDE_INVERT);\n    }\n    uart_putc_raw(uart1, data);\n    if (half_duplex1) {\n        uart_tx_wait_blocking(uart1);\n        enable_rx(uart1);\n        gpio_set_function(gpio_tx1, GPIO_FUNC_NULL);\n    }\n}\n\nvoid uart0_write_bytes(uint8_t *data, uint8_t lenght) {\n    if (half_duplex0) {\n        disable_rx(uart0);\n        gpio_set_function(gpio_tx0, GPIO_FUNC_UART);\n        if (inverted0) gpio_set_outover(gpio_tx0, GPIO_OVERRIDE_INVERT);\n    }\n    uart_write_blocking(uart0, data, lenght);\n    if (half_duplex0) {\n        uart_tx_wait_blocking(uart0);\n        enable_rx(uart0);\n        gpio_set_function(gpio_tx0, GPIO_FUNC_NULL);\n    }\n}\n\nvoid uart1_write_bytes(uint8_t *data, uint8_t lenght) {\n    if (half_duplex1) {\n        disable_rx(uart1);\n        gpio_set_function(gpio_tx1, GPIO_FUNC_UART);\n        if (inverted1) gpio_set_outover(gpio_tx1, GPIO_OVERRIDE_INVERT);\n    }\n    uart_write_blocking(uart1, data, lenght);\n    if (half_duplex1) {\n        uart_tx_wait_blocking(uart1);\n        enable_rx(uart1);\n        gpio_set_function(gpio_tx1, GPIO_FUNC_NULL);\n    }\n}\n\nuint8_t uart0_available() { return uxQueueMessagesWaiting(context.uart0_queue_handle); }\n\nuint8_t uart1_available() { return uxQueueMessagesWaiting(context.uart1_queue_handle); }\n\nuint uart0_get_time_elapsed() { return time_us_32() - uart0_timestamp; }\n\nuint uart1_get_time_elapsed() { return time_us_32() - uart1_timestamp; }\n\n/* Use with sim rx */\n\nvoid uart0_set_timestamp() { uart0_timestamp = time_us_32(); }\n\nvoid uart1_set_timestamp() { uart1_timestamp = time_us_32(); }"
  },
  {
    "path": "board/project/uart.h",
    "content": "#ifndef UART_H\n#define UART_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid uart0_begin(uint baudrate, uint gpio_tx, uint gpio_rx, uint timeout, uint databits, uint stopbits, uart_parity_t parity,\n                 bool inverted, bool half_duplex);\nuint8_t uart0_read();\nvoid uart0_read_bytes(uint8_t *data, uint8_t lenght);\nuint8_t uart0_available();\nuint uart0_get_time_elapsed();\nvoid uart0_write(uint8_t data);\nvoid uart0_write_bytes(uint8_t *data, uint8_t lenght);\n\nvoid uart1_begin(uint baudrate, uint gpio_tx, uint gpio_rx, uint timeout, uint databits, uint stopbits, uart_parity_t parity,\n                 bool inverted, bool half_duplex);\nuint8_t uart1_read();\nvoid uart1_read_bytes(uint8_t *data, uint8_t lenght);\nuint8_t uart1_available();\nuint uart1_get_time_elapsed();\nvoid uart1_write(uint8_t data);\nvoid uart1_write_bytes(uint8_t *data, uint8_t lenght);\n\nvoid uart0_set_timestamp();\nvoid uart1_set_timestamp();\n\n#endif"
  },
  {
    "path": "board/project/uart_pio.c",
    "content": "#include \"uart_pio.h\"\n\n#include <stdio.h>\n\n#include \"common.h\"\n#include \"hardware/irq.h\"\n#include \"hardware/pio.h\"\n\nstatic volatile uint uart_pio_timeout, uart_pio_timestamp;\nstatic volatile bool uart_pio_is_timedout = true;\nstatic uint uart_pio_sm;\n\nstatic int64_t uart_pio_timeout_callback(alarm_id_t id, void *user_data);\nstatic void uart_pio_handler(uint8_t data);\nstatic void uart_pio_reset_queue_from_isr(BaseType_t *xHigherPriorityTaskWoken);\n\nvoid uart_pio_begin(uint baudrate, int gpio_tx, int gpio_rx, uint timeout, PIO pio, uint irq, uint8_t data_bits,\n                    uint8_t stop_bits, uint8_t parity) {\n    if (gpio_rx != UART_GPIO_NONE) {\n        if (context.uart_alarm_pool == NULL) context.uart_alarm_pool = alarm_pool_create(2, 10);\n        uart_pio_sm = uart_rx_init(pio, gpio_rx, baudrate, irq);\n        uart_rx_set_handler(uart_pio_handler);\n        uart_pio_timeout = timeout;\n        context.uart_rx_pio_queue_handle = xQueueCreate(UART_PIO_BUFFER_SIZE, sizeof(uint8_t));\n    }\n    if (gpio_tx != UART_GPIO_NONE) {\n        uart_pio_sm = uart_tx_init(pio, gpio_tx, baudrate, data_bits, stop_bits, parity);\n    }\n}\n\nstatic int64_t uart_pio_timeout_callback(alarm_id_t id, void *user_data) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    uart_pio_is_timedout = true;\n    // printf(\"\\n\");\n    vTaskNotifyGiveIndexedFromISR(context.uart_pio_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    return 0;\n}\n\nstatic void uart_pio_reset_queue_from_isr(BaseType_t *xHigherPriorityTaskWoken) {\n    uint8_t discarded;\n    while (uxQueueMessagesWaitingFromISR(context.uart_rx_pio_queue_handle) > 0) {\n        xQueueReceiveFromISR(context.uart_rx_pio_queue_handle, &discarded, xHigherPriorityTaskWoken);\n    }\n}\n\nstatic void uart_pio_handler(uint8_t data) {\n    BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n    static alarm_id_t uart_pio_timeout_alarm_id = 0;\n    if (uart_pio_timeout_alarm_id) alarm_pool_cancel_alarm(context.uart_alarm_pool, uart_pio_timeout_alarm_id);\n    if (uart_pio_is_timedout) {\n        uart_pio_reset_queue_from_isr(&xHigherPriorityTaskWoken);\n        uart_pio_is_timedout = false;\n    }\n    // debug(\"-%X-\", data);\n    xQueueSendToBackFromISR(context.uart_rx_pio_queue_handle, &data, &xHigherPriorityTaskWoken);\n    if (uart_pio_timeout == 0) {\n        BaseType_t xHigherPriorityTaskWoken = pdFALSE;\n        vTaskNotifyGiveIndexedFromISR(context.uart_pio_notify_task_handle, 1, &xHigherPriorityTaskWoken);\n        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n    }\n    if (uart_pio_timeout) {\n        uart_pio_timeout_alarm_id = alarm_pool_add_alarm_in_us(context.uart_alarm_pool, uart_pio_timeout,\n                                                               uart_pio_timeout_callback, NULL, true);\n    }\n    uart_pio_timestamp = time_us_32();\n    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);\n}\n\nuint8_t uart_pio_read(void) {\n    uint8_t value = 0;\n    xQueueReceive(context.uart_rx_pio_queue_handle, &value, 0);\n    return value;\n}\n\nvoid uart_pio_read_bytes(uint8_t *data, uint8_t lenght) {\n    for (uint8_t i = 0; i < lenght; i++) {\n        xQueueReceive(context.uart_rx_pio_queue_handle, data + i, 0);\n    }\n}\n\nvoid uart_pio_write(uint32_t c) { uart_tx_write(c); }\n\nvoid uart_pio_write_bytes(void *data, uint8_t lenght) { uart_tx_write_bytes(data, lenght); }\n\nuint8_t uart_pio_available(void) { return uxQueueMessagesWaiting(context.uart_rx_pio_queue_handle); }\n\nuint8_t uart_pio_tx_available(void) {\n    return UART_PIO_BUFFER_SIZE - uxQueueMessagesWaiting(context.uart_rx_pio_queue_handle);\n}\n\nuint uart_pio_get_time_elapsed(void) { return time_us_32() - uart_pio_timestamp; }\n\nvoid uart_pio_remove(void) {\n    uart_rx_remove();\n    uart_tx_remove();\n}"
  },
  {
    "path": "board/project/uart_pio.h",
    "content": "#ifndef UART_PIO_H\n#define UART_PIO_H\n\n#include \"common.h\"\n#include \"uart_rx.h\"\n#include \"uart_tx.h\"\n\n#define UART_PIO_BUFFER_SIZE 2048\n#define UART_GPIO_NONE -1\n\nextern context_t context;\n\nvoid uart_pio_begin(uint baudrate, int gpio_tx, int gpio_rx, uint timeout, PIO pio, uint irq, uint8_t data_bits,\n                    uint8_t stop_bits, uint8_t parity);\nuint8_t uart_pio_read(void);\nvoid uart_pio_read_bytes(uint8_t *data, uint8_t lenght);\nvoid uart_pio_write(uint32_t c);\nvoid uart_pio_write_bytes(void *data, uint8_t lenght);\nuint8_t uart_pio_available(void);\nuint8_t uart_pio_tx_available(void);\nvoid uart_pio_remove(void);\n\n#endif"
  },
  {
    "path": "board/project/usb.c",
    "content": "#include \"usb.h\"\n\n#include <queue.h>\n#include <stdio.h>\n\n#include \"config.h\"\n#include \"pico/stdlib.h\"\n#include \"string.h\"\n\n#define AIRCR_Register (*((volatile uint32_t *)(PPB_BASE + 0x0ED0C)))\n\n#define USB_BUFFER_LENGTH 256\n#define USB_INTERVAL_MS 1000\n\nstatic uint8_t buffer_rx[USB_BUFFER_LENGTH];\n\nstatic int read_usb();\nstatic void process_usb(int lenght);\n\nvoid usb_task() {\n    while (1) {\n        int length = read_usb();\n        if (length) process_usb(length);\n        debug(\"\\nUSB (%u)\", uxTaskGetStackHighWaterMark(NULL));\n        vTaskDelay(USB_INTERVAL_MS / portTICK_PERIOD_MS);\n    }\n}\n\nstatic void process_usb(int lenght) {\n    debug(\"\\nUSB. Processing (%i) 0x%X 0x%X\", lenght, buffer_rx[0], buffer_rx[1]);\n\n    /*\n    header - 0x30\n    command - 0x30 - read config from usb\n              0x31 - request to send config to usb\n              0x32 - answer to send config\n              0x33 - debug on\n              0x34 - debug off\n    */\n\n    if (buffer_rx[0] == 0x30) {\n        if (buffer_rx[1] == 0x30 &&\n            lenght == sizeof(config_t) + 2) {  // read config from usb, write to flash and reboot\n\n            config_t *config = (config_t *)(buffer_rx + 2);\n            if (config->version == CONFIG_VERSION) {\n                config->rpm_multiplier = config->pinionTeeth / (1.0 * config->mainTeeth * config->pairOfPoles);\n                context.led_cycles = 3;\n                context.led_cycle_duration = 1000;\n                vTaskResume(context.led_task_handle);\n                vTaskDelay(3000 / portTICK_PERIOD_MS);\n                context.led_cycles = 1;\n                context.led_cycle_duration = 6;\n                vTaskResume(context.led_task_handle);\n                config_write(config);\n                debug(\"\\nUSB. Updated config\");\n                // sleep_ms(1000);\n                // AIRCR_Register = 0x5FA0004;\n            } else\n                debug(\"\\nUSB. Incompatible config version\");\n        } else if (buffer_rx[1] == 0x31 && lenght == 2) {  // send config\n            uint8_t debug_state = context.debug;\n            context.debug = 0;\n            context.led_cycles = 2;\n            context.led_cycle_duration = 1000;\n            vTaskResume(context.led_task_handle);\n            vTaskDelay(2000 / portTICK_PERIOD_MS);\n            context.led_cycles = 1;\n            context.led_cycle_duration = 6;\n            vTaskResume(context.led_task_handle);\n            config_t *config = config_read();\n            putchar_raw(0x30);\n            putchar_raw(0x32);\n            uint8_t data[sizeof(config_t)];\n            memcpy(data, config, sizeof(config_t));\n            for (int i = 0; i < sizeof(config_t); i++) putchar_raw(data[i]);\n            if (debug_state) vTaskDelay(1000 / portTICK_PERIOD_MS);\n            context.debug = debug_state;\n            debug(\"\\nUSB. Send config\");\n        } else if (buffer_rx[1] == 0x33 && lenght == 2) {  // debug enable\n            context.debug = 1;\n            debug(\"\\nUSB. Debug enabled. MSRC %s\", PROJECT_VERSION);\n        } else if (buffer_rx[1] == 0x34 && lenght == 2) {  // debug disable\n            context.debug = 0;\n            debug(\"\\nUSB. Debug disabled\");\n        } else if (buffer_rx[1] == 0x35 && lenght == 2) {  // force save default config to flash\n            config_forze_write();\n            debug(\"\\nUSB. Default config saved to flash\");\n        }\n    }\n}\n\nstatic int read_usb() {\n    int buffer_index = 0;\n    while (1) {\n        int c = getchar_timeout_us(1000);\n        if (c != PICO_ERROR_TIMEOUT && buffer_index < USB_BUFFER_LENGTH) {\n            buffer_rx[buffer_index++] = (c & 0xFF);\n        } else {\n            break;\n        }\n    }\n    return buffer_index;\n}\n"
  },
  {
    "path": "board/project/usb.h",
    "content": "#ifndef USB_H\n#define USB_H\n\n#include \"common.h\"\n\nextern context_t context;\n\nvoid usb_task();\n\nextern context_t context;\n\n#endif\n"
  },
  {
    "path": "case/MSRC_case_zero-Lower case.stl",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:706d1712a05c26d363e756b3c37a33e08091c530aa5a3a4d4ae283afff7a6ff0\nsize 123084\n"
  },
  {
    "path": "case/MSRC_case_zero-Upper case.stl",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:9e29e04059e759b96d55ae8b1698df50249703a28a3ffe5457d4ccf6f9c29b4c\nsize 160284\n"
  },
  {
    "path": "case/MSRC_case_zero.3mf",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:df72a711ca0eb78f81886c5d42da3f04503d07448e117a83678408008134164a\nsize 84013\n"
  },
  {
    "path": "case/MSRC_case_zero.FCStd",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:f958637abcdd52ebfe8e2f99acd9b631e951af492a2a80fb3f67ae941ddf843e\nsize 3487729\n"
  },
  {
    "path": "case/MSRC_case_zero_PLA_1h27m.gcode",
    "content": "version https://git-lfs.github.com/spec/v1\noid sha256:11042343aad94bb6fef96a9717a1ab6ead31a9674e0f2fc6b89eadc720c977dd\nsize 2216642\n"
  },
  {
    "path": "include/shared.h",
    "content": "#ifndef SHARED_H\n#define SHARED_H\n\n#include <stdint.h>\n\n#ifdef __cplusplus\n\ntypedef enum rx_protocol_t : uint8_t {\n    RX_SMARTPORT,\n    RX_FRSKY_D,\n    RX_XBUS,\n    RX_SRXL,\n    RX_IBUS,\n    RX_SBUS,\n    RX_MULTIPLEX,\n    RX_JETIEX,\n    RX_HITEC,\n    RX_SRXL2,\n    SERIAL_MONITOR,\n    RX_CRSF,\n    RX_HOTT,\n    RX_SANWA,\n    RX_JR_PROPO,\n    RX_FPORT,\n    RX_FBUS,\n    RX_GHST,\n    RX_JETIEX_SENSOR\n} rx_protocol_t;\n\ntypedef enum esc_protocol_t : uint8_t {\n    ESC_NONE,\n    ESC_HW3,\n    ESC_HW4,\n    ESC_PWM,\n    ESC_CASTLE,\n    ESC_KONTRONIK,\n    ESC_APD_F,\n    ESC_APD_HV,\n    ESC_HW5,\n    ESC_SMART,\n    ESC_OMP_M4,\n    ESC_ZTW,\n    ESC_OPENYGE\n} esc_protocol_t;\n\ntypedef enum i2c_module_t : uint8_t { I2C_NONE, I2C_BMP280, I2C_MS5611, I2C_BMP180 } i2c_module_t;\n\ntypedef enum analog_current_type_t : uint8_t { CURRENT_TYPE_HALL, CURRENT_TYPE_SHUNT } analog_current_type_t;\n\ntypedef enum serial_monitor_format_t : uint8_t { FORMAT_HEX, FORMAT_STRING } serial_monitor_format_t;\n\ntypedef enum gps_protocol_t : uint8_t { UBLOX, NMEA } gps_protocol_t;\n\n#else\n\ntypedef enum rx_protocol_t {\n    RX_SMARTPORT,\n    RX_FRSKY_D,\n    RX_XBUS,\n    RX_SRXL,\n    RX_IBUS,\n    RX_SBUS,\n    RX_MULTIPLEX,\n    RX_JETIEX,\n    RX_HITEC,\n    RX_SRXL2,\n    SERIAL_MONITOR,\n    RX_CRSF,\n    RX_HOTT,\n    RX_SANWA,\n    RX_JR_PROPO,\n    RX_FPORT,\n    RX_FBUS,\n    RX_GHST,\n    RX_JETIEX_SENSOR\n} rx_protocol_t;\n\ntypedef enum esc_protocol_t {\n    ESC_NONE,\n    ESC_HW3,\n    ESC_HW4,\n    ESC_PWM,\n    ESC_CASTLE,\n    ESC_KONTRONIK,\n    ESC_APD_F,\n    ESC_APD_HV,\n    ESC_HW5,\n    ESC_SMART,\n    ESC_OMP_M4,\n    ESC_ZTW,\n    ESC_OPENYGE\n} esc_protocol_t;\n\ntypedef enum i2c_module_t { I2C_NONE, I2C_BMP280, I2C_MS5611, I2C_BMP180 } i2c_module_t;\n\ntypedef enum analog_current_type_t { CURRENT_TYPE_HALL, CURRENT_TYPE_SHUNT } analog_current_type_t;\n\ntypedef enum serial_monitor_format_t { FORMAT_HEX, FORMAT_STRING } serial_monitor_format_t;\n\ntypedef enum gps_protocol_t { UBLOX, NMEA } gps_protocol_t;\n\n#endif\n\ntypedef struct config_t {                            // smartport data_id\n    uint16_t version;                                // 0x5101\n    enum rx_protocol_t rx_protocol;                  // 0x5102\n    enum esc_protocol_t esc_protocol;                // 0x5103\n    bool enable_gps;                                 // 0x5104\n    uint32_t gps_baudrate;                           // 0x5105\n    bool enable_analog_voltage;                      // 0x5106\n    bool enable_analog_current;                      // 0x5107\n    bool enable_analog_ntc;                          // 0x5108\n    bool enable_analog_airspeed;                     // 0x5109\n    enum i2c_module_t i2c_module;                    // 0x510A\n    uint8_t ina3221_filter;                          // 0x510B\n    float alpha_rpm;                                 // 0x510C\n    float alpha_voltage;                             // 0x510D\n    float alpha_current;                             // 0x510E\n    float alpha_temperature;                         // 0x510F\n    float alpha_vario;                               // 0x5110\n    float alpha_airspeed;                            // 0x5111\n    uint16_t refresh_rate_rpm;                       // 0x5112\n    uint16_t refresh_rate_voltage;                   // 0x5113\n    uint16_t refresh_rate_current;                   // 0x5114\n    uint16_t refresh_rate_temperature;               // 0x5115\n    uint16_t refresh_rate_gps;                       // 0x5116\n    uint16_t refresh_rate_consumption;               // 0x5117\n    uint16_t refresh_rate_vario;                     // 0x5118\n    uint16_t refresh_rate_airspeed;                  // 0x5119\n    uint16_t refresh_rate_default;                   // 0x511A\n    float analog_voltage_multiplier;                 // 0x511B\n    enum analog_current_type_t analog_current_type;  // 0x511C\n    uint16_t gpio_interval;                          // 0x511D\n    float analog_current_quiescent_voltage;          // 0x511E\n    float analog_current_multiplier;                 // 0x511F\n    float analog_current_offset;                     // 0x5120\n    bool analog_current_autoffset;                   // 0x5121\n    uint8_t pairOfPoles;                             // 0x5122\n    uint8_t mainTeeth;                               // 0x5123\n    uint8_t pinionTeeth;                             // 0x5124\n    float rpm_multiplier;                            // 0x5125\n    uint8_t bmp280_filter;                           // 0x5126\n    bool enable_pwm_out;                             // 0x5127\n    uint8_t smartport_sensor_id;                     // 0x5128\n    uint16_t smartport_data_id;                      // 0x5129\n    bool vario_auto_offset;                          // 0x512A\n    bool xbus_clock_stretch;                         // 0x512B\n    bool jeti_gps_speed_units_kmh;                   // 0x512C\n    bool enable_esc_hw4_init_delay;                  // 0x512D\n    uint16_t esc_hw4_init_delay_duration;            // 0x512E\n    uint8_t esc_hw4_current_thresold;                // 0x512F\n    uint16_t esc_hw4_current_max;                    // 0x5130\n    float esc_hw4_voltage_multiplier;                // 0x5131\n    float esc_hw4_current_multiplier;                // 0x5132\n    bool ibus_alternative_coordinates;               // 0x5133\n    uint8_t debug;                                   // 0x5134\n    bool esc_hw4_is_manual_offset;                   // 0x5135\n    uint8_t analog_rate;                             // 0x5136\n    bool xbus_use_alternative_volt_temp;             // 0x5137\n    uint8_t gpio_mask;                               // 0x5138\n    float esc_hw4_offset;                            // 0x5139\n    uint32_t serial_monitor_baudrate;                // 0x513A\n    uint8_t serial_monitor_stop_bits;                // 0x513B\n    uint8_t serial_monitor_parity;                   // 0x513C\n    uint16_t serial_monitor_timeout_ms;              // 0x513D\n    bool serial_monitor_inverted;                    // 0x513E\n    bool esc_hw4_auto_detect;                        // 0x514B\n    int16_t airspeed_vcc;                            // 0x5140\n    float fuel_flow_ml_per_pulse;                    // 0x5141\n    bool enable_fuel_flow;                           // 0x5142\n    uint16_t xgzp68xxd_k;                            // 0x5143\n    uint8_t enable_fuel_pressure;                    // 0x5144\n    bool smart_esc_calc_consumption;                 // 0x5145\n    uint8_t serial_monitor_gpio;                     // 0x5146\n    uint8_t gps_rate;                                // 0x5147\n    serial_monitor_format_t serial_monitor_format;   // 0x5148\n    uint8_t gps_protocol;                            // 0x5149\n    bool sbus_battery_slot;                          // 0x514A\n    bool fport_inverted;\n    bool fbus_inverted;\n    int16_t airspeed_offset;                         // 0x513F\n    uint8_t mpu6050_acc_scale;                       // 0x514C\n    uint8_t mpu6050_gyro_scale;                      // 0x514D\n    uint8_t mpu6050_gyro_weighting;                  // 0x514E\n    bool enable_gyro;                                // 0x514F\n    uint8_t enable_lipo;                             // 0x5150\n    uint8_t mpu6050_filter;                          // 0x5151\n    uint8_t lipo_cells;                              // 0x5152\n    uint8_t sensor_id_srxl2;\n    int8_t ntc_offset;                               // 0x5153\n    uint8_t spare7;\n    uint32_t spare8;\n    uint32_t spare9;\n    uint32_t spare10;\n    uint32_t spare11;\n    uint32_t spare12;\n    uint32_t spare13;\n    uint32_t spare14;\n    uint32_t spare15;\n    uint32_t spare16;\n    uint32_t spare17;\n    uint32_t spare18;\n    uint32_t spare19;\n    uint32_t spare20;\n} config_t;\n\n#endif"
  },
  {
    "path": "lua/MSRC.lua",
    "content": "local toolName = \"TNS|MSRC config|TNE\"\n\nlocal scriptVersion = \"v1.4\"\n\nlocal statusEnum = {\n\tstart = 1,\n\tgetConfig = 2,\n\tconfig = 3,\n\texitScr = 4,\n\tsaveConfig = 5,\n\tstartSave = 6,\n\tmaintOff = 7,\n\texit = 8,\n}\nlocal pageEnum = {\n\tsensorId = 1,\n\trate = 2,\n\tavg = 3,\n\tesc = 4,\n\tgps = 5,\n\tvario = 6,\n\tfuelmeter = 7,\n\tgpio = 8,\n\tanalogRate = 9,\n\tanalogTemp = 10,\n\tanalogVolt = 11,\n\tanalogCurr = 12,\n\tanalogAirspeed = 13,\n\tgyro = 14,\n}\nlocal varEnum = {\n\tstr = 1,\n\tval = 2,\n\tmin = 3,\n\tmax = 4,\n\tincr = 5,\n\tdataId = 6,\n\tlist = 7,\n}\nlocal firmwareVersion\nlocal sensorIdTx = 18\nlocal page = 0\nlocal pageLong = false\nlocal pageItem = 1\nlocal isSelected = false\nlocal status = statusEnum.start\nlocal saveChanges = true\nlocal ts = 0\nlocal vars = {}\n\nlocal onOffStr = { \"Off\", \"On\" }\n\nlocal pageName = {\n\t\"Sensor Id\",\n\t\"Refresh rate\",\n\t\"Average elements\",\n\t\"ESC\",\n\t\"GPS\",\n\t\"Vario\",\n\t\"Fuel meter\",\n\t\"GPIO\",\n\t\"Analog rate\",\n\t\"Temperature analog\",\n\t\"Voltage analog\",\n\t\"Current analog\",\n\t\"Airspeed analog\",\n    \"Gyro\",\n}\n\n-- Page 1 - SensorId\nlocal sensorId = { \"Sensor Id\", nil, 1, 28, 1, 0x5128 } -- str, val, min, max, incr, dataId\nvars[pageEnum.sensorId] = { sensorId }\n\n-- Page 2 - Refresh interval (ms 1-2000)\nlocal rateRpm = { \"RPM\", nil, 1, 2000, 1, 0x513A }\nlocal rateVolt = { \"Voltage\", nil, 1, 2000, 1, 0x5113 }\nlocal rateCurr = { \"Current\", nil, 1, 2000, 1, 0x5114 }\nlocal rateTemp = { \"Temperature\", nil, 1, 2000, 1, 0x5115 }\nlocal rateGps = { \"GPS\", nil, 1, 2000, 1, 0x5116 }\nlocal rateCons = { \"Consumption\", nil, 1, 2000, 1, 0x5117 }\nlocal rateVario = { \"Vario\", nil, 1, 2000, 1, 0x5118 }\nlocal rateAirspeed = { \"Airspeed\", nil, 1, 2000, 1, 0x5119 }\nvars[pageEnum.rate] = { rateRpm, rateVolt, rateCurr, rateTemp, rateGps, rateCons, rateVario, rateAirspeed }\n\n-- Page 3 - Averaging elements (1-16)\nlocal avgRpm = { \"RPM\", nil, 1, 16, 1, 0x510C }\nlocal avgVolt = { \"Voltage\", nil, 1, 16, 1, 0x510D }\nlocal avgCurr = { \"Current\", nil, 1, 16, 1, 0x510E }\nlocal avgTemp = { \"Temperature\", nil, 1, 16, 1, 0x510F }\nlocal avgVario = { \"Vario\", nil, 1, 16, 1, 0x5110 }\nlocal avgAirspeed = { \"Airspeed\", nil, 1, 16, 1, 0x5111 }\nvars[pageEnum.avg] = { avgRpm, avgVolt, avgCurr, avgTemp, avgVario, avgAirspeed }\n\n-- Page 4 - ESC\nlocal pairPoles = { \"Pair of Poles\", nil, 1, 20, 1, 0x5122 }\nlocal mainGear = { \"Main Gear\", nil, 1, 1000, 1, 0x5123 }\nlocal pinionGear = { \"Pinion Gear\", nil, 1, 1000, 1, 0x5124 }\nlocal escProtocolStr = {\n\t\"None\",\n\t\"Hobbywing V3\",\n\t\"Hobbywing V4\",\n\t\"PWM\",\n\t\"Castle Link\",\n\t\"Kontronik\",\n\t\"Kiss\",\n\t\"APD HV\",\n\t\"HobbyWing V5\",\n\t\"Smart ESC/BAT\",\n\t\"OMP M4\",\n\t\"ZTW\",\n    \"OpenYGE\",\n}\nlocal escProtocol = { \"Protocol\", nil, 0, 11, 1, 0x5103, escProtocolStr }\nlocal hw4InitDelay = { \"Init Delay\", nil, 0, 1, 1, 0x512E, onOffStr }\nlocal hw4AutoDetect = { \"Auto detect\", nil, 0, 1, 1, 0x514B, onOffStr }\nlocal hw4VoltMult = { \"Volt mult\", nil, 0, 100000, 1, 0x5131 }\nlocal hw4CurrMult = { \"Curr mult\", nil, 0, 100000, 1, 0x5132 }\nlocal hw4AutoOffset = { \"Auto offset\", nil, 0, 1, 1, 0x5135, onOffStr }\nlocal hw4Offset = { \"Curr offset\", nil, 0, 2000, 1, 0x5139 }\nlocal smartEscConsumption = { \"Calc cons\", nil, 0, 1, 1, 0x5145, onOffStr }\nvars[pageEnum.esc] = {\n\tpairPoles,\n\tmainGear,\n\tpinionGear,\n\tescProtocol,\n\thw4InitDelay,\n\thw4AutoDetect,\n\thw4VoltMult,\n\thw4CurrMult,\n\thw4AutoOffset,\n\thw4Offset,\n\tsmartEscConsumption,\n}\n\n-- Page 5 - GPS\nlocal gpsEnable = { \"Enable\", nil, 0, 1, 1, 0x5104, onOffStr }\nlocal gpsProtocolStr = { \"UBLOX\", \"NMEA\" }\nlocal gpsProtocol = { \"Protocol\", nil, 0, 1, 1, 0x5149, gpsProtocolStr }\nlocal gpsBaudrateVal = { 9600, 38400, 57600, 115200 }\nlocal gpsBaudrate = { \"Baudrate\", nil, 0, 3, 1, 0x5105, gpsBaudrateVal }\nlocal gpsRateVal = { 1, 5, 10, 20 }\nlocal gpsRate = { \"Rate\", nil, 0, 3, 1, 0x5147, gpsRateVal }\nvars[pageEnum.gps] = { gpsEnable, gpsProtocol, gpsBaudrate, gpsRate }\n\n-- Page 6 - Vario\nlocal varioModelStr = { \"None\", \"BMP280\", \"MS5611\", \"BMP180\" }\nlocal varioModel = { \"Model\", nil, 0, 3, 1, 0x510A, varioModelStr }\nlocal varioFilterStr = { \"Low\", \"Medium\", \"High\" }\nlocal varioFilter = { \"Filter\", nil, 1, 3, 1, 0x5126, varioFilterStr }\nvars[pageEnum.vario] = { varioModel, varioFilter }\n\n-- Page 7 - Fuel meter\nlocal fuelMeter = { \"Enable\", nil, 0, 1, 1, 0x5142, onOffStr }\nlocal mlPulse = { \"ml/pulse\", nil, 0, 1, 0.0001, 0x5141 }\nvars[pageEnum.fuelmeter] = { fuelMeter, mlPulse }\n\n-- Page 8 - GPIO\nlocal gpioInterval = { \"Interval(ms)\", nil, 10, 10000, 1, 0x511D }\nlocal gpio = { \"\", nil, 0, 0x3F, 1, 0x5138 }\nlocal gpio17 = { \"17\", 0, 0, 1, 1, 0, onOffStr }\nlocal gpio18 = { \"18\", 0, 0, 1, 1, 0, onOffStr }\nlocal gpio19 = { \"19\", 0, 0, 1, 1, 0, onOffStr }\nlocal gpio20 = { \"20\", 0, 0, 1, 1, 0, onOffStr }\nlocal gpio21 = { \"21\", 0, 0, 1, 1, 0, onOffStr }\nlocal gpio22 = { \"22\", 0, 0, 1, 1, 0, onOffStr }\nvars[pageEnum.gpio] = { gpioInterval, gpio }\n\n-- Page 9 - Analog rate\nlocal analogRate = { \"Rate(Hz)\", nil, 1, 100, 1, 0x5136 }\nvars[pageEnum.analogRate] = { analogRate }\n\n-- Page 10 - Temperature analog\nlocal analogTemp = { \"Enable\", nil, 0, 1, 1, 0x5108, onOffStr }\nvars[pageEnum.analogTemp] = { analogTemp }\n\n-- Page 11 - Voltage analog\nlocal analogVolt = { \"Enable\", nil, 0, 1, 1, 0x5106, onOffStr }\nlocal analogVoltMult = { \"Multiplier\", nil, 1, 1000, 0.01, 0x511B }\nvars[pageEnum.analogVolt] = { analogVolt, analogVoltMult }\n\n-- Page 12 - Current analog\nlocal analogCurr = { \"Enable\", nil, 0, 1, 1, 0x5107, onOffStr }\nlocal analogCurrTypeStr = { \"Hall Effect\", \"Shunt Resistor\" }\nlocal analogCurrType = { \"Type\", nil, 0, 1, 1, 0x511C, analogCurrTypeStr }\nlocal analogCurrMult = { \"Mult\", nil, 0, 100, 0.01, 0x511F }\nlocal analogCurrSens = { \"Sens(mV/A)\", 0, 0, 100, 0.01, 0 }\nlocal analogCurrAutoOffset = { \"Auto Offset\", nil, 0, 1, 1, 0x5121, onOffStr }\nlocal analogCurrOffset = { \"Offset\", nil, 0, 3.3, 0.01, 0x5120 }\nvars[pageEnum.analogCurr] = { analogCurr, analogCurrType, analogCurrMult, analogCurrAutoOffset, analogCurrOffset }\n\n-- Page 13 - Airspeed analog\nlocal analogAirspeed = { \"Enable\", nil, 0, 1, 1, 0x5109, onOffStr }\nlocal analogAirspeedVcc = { \"Vcc(V)\", nil, 3, 6, 0.01, 0x5140 }\nlocal analogAirspeedOffset = { \"Offset(mV)\", nil, -1000, 1000, 1, 0x513F }\nvars[pageEnum.analogAirspeed] = { analogAirspeed, analogAirspeedVcc, analogAirspeedOffset }\n\n-- Page 14 - Gyro\nlocal gyro = { \"Enable\", nil, 0, 1, 1, 0x514F, onOffStr }\nlocal gyroAccSens = { \"Acc sens\", nil, 0, 3, 1, 0x514C, { 2, 4, 8, 16 } }\nlocal gyroGyroSens = { \"Gyro sens\", nil, 0, 3, 1, 0x514D, { 250, 500, 1000, 2000 } }\nlocal gyroGyroWeight = { \"Gyro weight\", nil, 0, 100, 1, 0x514E }\nlocal gyroFilter = { \"Filter\", nil, 0, 6, 1, 0x5151 }\nvars[pageEnum.gyro] = { gyro, gyroAccSens, gyroGyroSens, gyroGyroWeight, gyroFilter }\n\nlocal function getTextFlags(item)\n\tlocal value = 0\n\tif item == pageItem then\n\t\tvalue = INVERS\n\t\tif isSelected == true then\n\t\t\tvalue = value + BLINK\n\t\tend\n\tend\n\treturn value\nend\n\nlocal function changeValue(isIncremented)\n\tlocal mult = 1\n\tif getRotEncSpeed() == ROTENC_MIDSPEED then\n\t\tmult = 10\n\telseif getRotEncSpeed() == ROTENC_HIGHSPEED then\n\t\tmult = 100\n\tend\n\tif isIncremented == true then\n\t\tvars[page][pageItem][varEnum.val] = vars[page][pageItem][varEnum.val]\n\t\t\t+ vars[page][pageItem][varEnum.incr] * mult\n\t\tif vars[page][pageItem][varEnum.val] > vars[page][pageItem][varEnum.max] then\n\t\t\tvars[page][pageItem][varEnum.val] = vars[page][pageItem][varEnum.max]\n\t\tend\n\telse\n\t\tvars[page][pageItem][varEnum.val] = vars[page][pageItem][varEnum.val]\n\t\t\t- vars[page][pageItem][varEnum.incr] * mult\n\t\tif vars[page][pageItem][varEnum.val] < vars[page][pageItem][varEnum.min] then\n\t\t\tvars[page][pageItem][varEnum.val] = vars[page][pageItem][varEnum.min]\n\t\tend\n\tend\nend\n\nlocal function getValue(list, index) -- index starts at 1\n\tif index > #list then\n\t\treturn list[1]\n\tend\n\treturn list[index]\nend\n\nlocal function getIndex(list, value)\n\tfor i = 1, #list do\n\t\tif value == list[i] then\n\t\t\treturn i\n\t\tend\n\tend\n\treturn 1\nend\n\nlocal function handleEvents(event)\n\t-- Check one-time script\n\tif event == nil then\n\t\treturn 2\n\tend\n\t-- Handle events\n\tif page == 0 then\n\t\tif event == EVT_PAGE_BREAK or event == 513 then\n\t\t\tpage = 1\n\t\t\tstatus = statusEnum.getConfig\n\t\tend\n\telseif event == EVT_EXIT_BREAK then\n\t\tif isSelected == true then\n\t\t\tisSelected = false\n\t\telse\n\t\t\tstatus = statusEnum.exitScr\n\t\tend\n\telseif (event == EVT_PAGE_BREAK or event == 513) and (status ~= statusEnum.exitScr) then\n\t\tif pageLong then\n\t\t\tpage = page - 1\n\t\telse\n\t\t\tpage = page + 1\n\t\tend\n\t\tif page > #vars then\n\t\t\tpage = 1\n\t\telseif page < 1 then\n\t\t\tpage = #vars\n\t\tend\n\t\tpageLong = false\n\t\tpageItem = 1\n\t\tisSelected = false\n\t\tstatus = statusEnum.getConfig\n\t\tts = 0\n\telseif event == EVT_PAGE_LONG or event == 2049 then\n\t\tpageLong = true\n\telseif event == EVT_ROT_RIGHT then\n\t\tif status == statusEnum.exitScr then\n\t\t\tsaveChanges = not saveChanges\n\t\telseif isSelected == false then\n\t\t\tpageItem = pageItem + 1\n\t\t\tif pageItem > #vars[page] then\n\t\t\t\tpageItem = 1\n\t\t\tend\n\t\telse\n\t\t\tchangeValue(true)\n\t\tend\n\telseif event == EVT_ROT_LEFT then\n\t\tif status == statusEnum.exitScr then\n\t\t\tsaveChanges = not saveChanges\n\t\tend\n\t\tif isSelected == false then\n\t\t\tpageItem = pageItem - 1\n\t\t\tif pageItem < 1 then\n\t\t\t\tpageItem = #vars[page]\n\t\t\tend\n\t\telse\n\t\t\tchangeValue(false)\n\t\tend\n\telseif event == EVT_ROT_BREAK then\n\t\tif status == statusEnum.exitScr then\n\t\t\tpageItem = 1\n\t\t\tif saveChanges == true then\n\t\t\t\tstatus = statusEnum.saveConfig\n\t\t\t\tpage = 1\n\t\t\t\tvars[pageEnum.gpio] = { gpioInterval, gpio }\n\t\t\telse\n\t\t\t\tsaveChanges = true\n\t\t\t\tstatus = statusEnum.getConfig\n\t\t\tend\n\t\telse\n\t\t\tisSelected = not isSelected\n\t\tend\n\tend\nend\n\nlocal function getConfig()\n\tlocal sensor, frameId, dataId, value = sportTelemetryPop()\n\tif firmwareVersion == nil then\n\t\tif dataId ~= nil and dataId == 0x5101 then\n\t\t\tfirmwareVersion = \"v\"\n\t\t\t\t.. bit32.rshift(value, 16)\n\t\t\t\t.. \".\"\n\t\t\t\t.. bit32.band(bit32.rshift(value, 8), 0xF)\n\t\t\t\t.. \".\"\n\t\t\t\t.. bit32.band(value, 0xF)\n\t\t\tstatus = statusEnum.config\n\t\telseif (getTime() - ts > 200) and sportTelemetryPush(sensorIdTx - 1, 0x30, 0x5101, 1) then\n\t\t\tts = getTime()\n\t\tend\n\telseif dataId ~= nil and dataId == vars[page][pageItem][varEnum.dataId] then\n\t\tif dataId == 0x511E or dataId == 0x5140 or dataId == 0x511B or dataId == 0x5120 then\n\t\t\tvalue = value / 100.0\n\t\telseif dataId == 0x5141 then\n\t\t\tvalue = value / 10000.0\n\t\telseif dataId == 0x511F then\n\t\t\tvalue = value / 100.0\n\t\t\tif analogCurrTypeStr[analogCurrType[varEnum.val] + 1] == \"Hall Effect\" then\n\t\t\t\tvalue = 1000.0 / value\n\t\t\t\tanalogCurrSens[varEnum.val] = math.floor(value * 100) / 100.0\n\t\t\tend\n\t\telseif dataId == 0x5105 then\n\t\t\tvalue = getIndex(gpsBaudrateVal, value) - 1\n\t\telseif dataId == 0x5147 then\n\t\t\tvalue = getIndex(gpsRateVal, value) - 1\n\t\telseif dataId == 0x5138 then\n\t\t\tgpio17[varEnum.val] = bit32.extract(value, 0)\n\t\t\tgpio18[varEnum.val] = bit32.extract(value, 1)\n\t\t\tgpio19[varEnum.val] = bit32.extract(value, 2)\n\t\t\tgpio20[varEnum.val] = bit32.extract(value, 3)\n\t\t\tgpio21[varEnum.val] = bit32.extract(value, 4)\n\t\t\tgpio22[varEnum.val] = bit32.extract(value, 5)\n\t\telseif dataId == 0x5126 then\n\t\t\tvalue = value - 1\n\t\telseif dataId == 0x5135 then\n\t\t\tif value == 0 then\n\t\t\t\tvalue = 1\n\t\t\telse\n\t\t\t\tvalue = 0\n\t\t\tend\n\t\telseif dataId == 0x513F then\n\t\t\tvalue = value - 1000\n\t\tend\n\t\tif value < vars[page][pageItem][varEnum.min] then\n\t\t\tvalue = vars[page][pageItem][varEnum.min]\n\t\tend\n\t\tif value > vars[page][pageItem][varEnum.max] then\n\t\t\tvalue = vars[page][pageItem][varEnum.max]\n\t\tend\n\t\tvars[page][pageItem][varEnum.val] = value\n\t\tpageItem = pageItem + 1\n\t\tts = 0\n\t\tif pageItem > #vars[page] then\n\t\t\tpageItem = 1\n\t\t\tstatus = statusEnum.config\n\t\tend\n\telseif vars[page][pageItem][varEnum.val] ~= nil then\n\t\tpageItem = pageItem + 1\n\t\tts = 0\n\t\tif pageItem > #vars[page] then\n\t\t\tpageItem = 1\n\t\t\tstatus = statusEnum.config\n\t\tend\n\telseif\n\t\t(getTime() - ts > 200) and sportTelemetryPush(sensorIdTx - 1, 0x30, vars[page][pageItem][varEnum.dataId], 1)\n\tthen\n\t\tts = getTime()\n\tend\n\tif page == pageEnum.gpio and status == statusEnum.config then\n\t\tvars[pageEnum.gpio] = { gpioInterval, gpio17, gpio18, gpio19, gpio20, gpio21, gpio22 }\n\tend\nend\n\nlocal function saveConfig()\n\tif status == statusEnum.saveConfig then\n\t\tif sportTelemetryPush(sensorIdTx - 1, 0x31, 0x5201, 0) then\n\t\t\tstatus = statusEnum.startSave\n\t\tend\n\t\treturn\n\tend\n\tif page > #vars then\n\t\tif sportTelemetryPush(sensorIdTx - 1, 0x31, 0x5201, 1) then\n\t\t\tstatus = statusEnum.maintOff\n\t\tend\n\t\treturn\n\tend\n\tlocal value = vars[page][pageItem][varEnum.val]\n\tlocal dataId = vars[page][pageItem][varEnum.dataId]\n\tif value ~= nil and dataId ~= 0 then\n\t\tif dataId == 0x511E or dataId == 0x511F or dataId == 0x5140 or dataId == 0x511B then\n\t\t\tvalue = value * 100\n\t\telseif dataId == 0x5141 then\n\t\t\tvalue = value * 10000\n\t\telseif dataId == 0x5105 then\n\t\t\tvalue = getValue(gpsBaudrateVal, gpsBaudrate[varEnum.val] + 1)\n\t\telseif dataId == 0x5147 then\n\t\t\tvalue = getValue(gpsRateVal, gpsRate[varEnum.val] + 1)\n\t\telseif dataId == 0x5138 then\n\t\t\tvalue = gpio17[varEnum.val] -- bit 1\n\t\t\tvalue = bit32.bor(value, bit32.lshift(gpio18[varEnum.val], 1)) -- bit 2\n\t\t\tvalue = bit32.bor(value, bit32.lshift(gpio19[varEnum.val], 2)) -- bit 3\n\t\t\tvalue = bit32.bor(value, bit32.lshift(gpio20[varEnum.val], 3)) -- bit 4\n\t\t\tvalue = bit32.bor(value, bit32.lshift(gpio21[varEnum.val], 4)) -- bit 5\n\t\t\tvalue = bit32.bor(value, bit32.lshift(gpio22[varEnum.val], 5)) -- bit 6\n\t\telseif dataId == 0x5126 then\n\t\t\tvalue = vars[page][pageItem][varEnum.val] + 1\n\t\telseif dataId == 0x5135 then\n\t\t\tif vars[page][pageItem][varEnum.val] == 1 then\n\t\t\t\tvalue = 0\n\t\t\telse\n\t\t\t\tvalue = 1\n\t\t\tend\n\t\telseif dataId == 0x513F then\n\t\t\tvalue = vars[page][pageItem][varEnum.val] + 1000\n\t\tend\n\t\tvalue = math.floor(value)\n\t\tif sportTelemetryPush(sensorIdTx - 1, 0x31, dataId, value) then\n\t\t\tpageItem = pageItem + 1\n\t\tend\n\telse\n\t\tpageItem = pageItem + 1\n\tend\n\tif pageItem > #vars[page] then\n\t\tpage = page + 1\n\t\tpageItem = 1\n\tend\nend\n\nlocal function setPageItems()\n\tif page == pageEnum.esc then\n\t\tif escProtocol[varEnum.val] + 1 > #escProtocolStr then\n\t\t\tescProtocol[varEnum.val] = 0\n\t\tend\n\t\tif hw4AutoDetect[varEnum.val] + 1 > #onOffStr then\n\t\t\thw4AutoDetect[varEnum.val] = 0\n\t\tend\n\t\tif hw4AutoOffset[varEnum.val] + 1 > #onOffStr then\n\t\t\thw4AutoOffset[varEnum.val] = 0\n\t\tend\n\t\tvars[page] = { pairPoles, mainGear, pinionGear, escProtocol }\n\t\tif escProtocolStr[escProtocol[varEnum.val] + 1] == \"Hobbywing V4\" then\n\t\t\tvars[page] = {\n\t\t\t\tpairPoles,\n\t\t\t\tmainGear,\n\t\t\t\tpinionGear,\n\t\t\t\tescProtocol,\n\t\t\t\thw4InitDelay,\n\t\t\t\thw4AutoDetect,\n\t\t\t}\n\t\t\tif getValue(onOffStr, hw4AutoDetect[varEnum.val] + 1) == \"Off\" then\n\t\t\t\tvars[page] = {\n\t\t\t\t\tpairPoles,\n\t\t\t\t\tmainGear,\n\t\t\t\t\tpinionGear,\n\t\t\t\t\tescProtocol,\n\t\t\t\t\thw4InitDelay,\n\t\t\t\t\thw4AutoDetect,\n\t\t\t\t\thw4VoltMult,\n\t\t\t\t\thw4CurrMult,\n\t\t\t\t\thw4AutoOffset,\n\t\t\t\t}\n\t\t\t\tif getValue(onOffStr, hw4AutoOffset[varEnum.val] + 1) == \"Off\" then\n\t\t\t\t\tvars[page] = {\n\t\t\t\t\t\tpairPoles,\n\t\t\t\t\t\tmainGear,\n\t\t\t\t\t\tpinionGear,\n\t\t\t\t\t\tescProtocol,\n\t\t\t\t\t\thw4InitDelay,\n\t\t\t\t\t\thw4AutoDetect,\n\t\t\t\t\t\thw4VoltMult,\n\t\t\t\t\t\thw4CurrMult,\n\t\t\t\t\t\thw4AutoOffset,\n\t\t\t\t\t\thw4Offset,\n\t\t\t\t\t}\n\t\t\t\tend\n\t\t\tend\n\t\telseif escProtocolStr[escProtocol[varEnum.val] + 1] == \"Smart ESC/BAT\" then\n\t\t\tvars[page] = { pairPoles, mainGear, pinionGear, escProtocol, smartEscConsumption }\n\t\tend\n\telseif page == pageEnum.vario then\n\t\tif varioModel[varEnum.val] + 1 > #varioModelStr then\n\t\t\tvarioModel[varEnum.val] = 0\n\t\tend\n\t\tif varioModelStr[varioModel[varEnum.val] + 1] == \"BMP280\" then\n\t\t\tvars[page] = { varioModel, varioFilter }\n\t\telse\n\t\t\tvars[page] = { varioModel }\n\t\tend\n\telseif page == pageEnum.analogCurr then\n\t\tif analogCurrType[varEnum.val] + 1 > #analogCurrTypeStr then\n\t\t\tanalogCurrType[varEnum.val] = 0\n\t\tend\n\t\tif analogCurrAutoOffset[varEnum.val] + 1 > #onOffStr then\n\t\t\tanalogCurrAutoOffset[varEnum.val] = 0\n\t\tend\n\t\tif analogCurrTypeStr[analogCurrType[varEnum.val] + 1] == \"Hall Effect\" then\n\t\t\tvars[page] = { analogCurr, analogCurrType, analogCurrSens, analogCurrAutoOffset }\n\t\t\tif getValue(onOffStr, analogCurrAutoOffset[varEnum.val] + 1) == \"Off\" then\n\t\t\t\tvars[page] = { analogCurr, analogCurrType, analogCurrSens, analogCurrAutoOffset, analogCurrOffset }\n\t\t\tend\n\t\telse\n\t\t\tvars[page] = { analogCurr, analogCurrType, analogCurrMult }\n\t\tend\n\tend\nend\n\nlocal function drawTitle(str, page, pages)\n\tif LCD_H == 64 then\n\t\tlcd.drawScreenTitle(str, page, pages)\n\telse\n\t\tlcd.drawText(1, 1, str)\n\t\tif page ~= 0 and pages ~= 0 then\n\t\t\tlcd.drawText(200, 1, page .. \"/\" .. pages)\n\t\tend\n\tend\nend\n\nlocal function drawPage()\n\tlcd.clear()\n\tif page == 0 then\n\t\tdrawTitle(\"MSRC \" .. scriptVersion, 0, 0)\n\t\tif firmwareVersion ~= nil then\n\t\t\tlcd.drawText(1, 20, \"Firmware \" .. firmwareVersion, SMLSIZE)\n\t\t\tlcd.drawText(1, 35, \"Press Page\", SMLSIZE)\n\t\tend\n\telseif status == statusEnum.getConfig then\n\t\tdrawTitle(pageName[page], page, #vars)\n\t\tlcd.drawText(60, 30, pageItem .. \"/\" .. #vars[page], 0)\n\telseif status == statusEnum.config then\n\t\tdrawTitle(pageName[page], page, #vars)\n\t\tlocal scroll, fileHeight, fileStart\n\t\tif LCD_H == 64 then\n\t\t\tscroll = pageItem - 8\n\t\t\tif scroll < 0 then\n\t\t\t\tscroll = 0\n\t\t\tend\n\t\t\tfileStart = 9\n\t\t\tfileHeight = 7\n\t\telse\n\t\t\tscroll = 0\n\t\t\tfileStart = 20\n\t\t\tfileHeight = 15\n\t\tend\n\t\tfor i = 1, #vars[page] - scroll do\n\t\t\tlcd.drawText(1, fileStart + fileHeight * (i - 1), vars[page][i + scroll][1], SMLSIZE)\n\t\t\tif #vars[page][i + scroll] == 6 then\n\t\t\t\tlocal val = vars[page][i + scroll][varEnum.val]\n\t\t\t\tif val == nil then\n\t\t\t\t\tval = -1\n\t\t\t\tend\n\t\t\t\tlcd.drawText(LCD_W / 2, fileStart + fileHeight * (i - 1), val, SMLSIZE + getTextFlags(i + scroll))\n\t\t\telseif #vars[page][i + scroll] == 7 then\n\t\t\t\tlocal val = 1\n\t\t\t\tif vars[page][i + scroll][varEnum.val] ~= nil then\n\t\t\t\t\tval = vars[page][i + scroll][varEnum.val] + 1\n\t\t\t\tend\n\t\t\t\tlcd.drawText(\n\t\t\t\t\tLCD_W / 2,\n\t\t\t\t\tfileStart + fileHeight * (i - 1),\n\t\t\t\t\tgetValue(vars[page][i + scroll][varEnum.list], val),\n\t\t\t\t\tSMLSIZE + getTextFlags(i + scroll)\n\t\t\t\t)\n\t\t\tend\n\t\tend\n\telseif status == statusEnum.exitScr then\n\t\tdrawTitle(\"Exit\", 0, 0)\n\t\tlcd.drawText(1, 20, \"Save changes?\", SMLSIZE)\n\t\tlocal flag_yes = 0\n\t\tlocal flag_cancel = 0\n\t\tif saveChanges == true then\n\t\t\tflag_yes = INVERS\n\t\telse\n\t\t\tflag_cancel = INVERS\n\t\tend\n\t\tlcd.drawText(1, 30, \"Yes\", SMLSIZE + flag_yes)\n\t\tlcd.drawText(60, 30, \"Cancel\", SMLSIZE + flag_cancel)\n\telseif status == statusEnum.startSave then\n\t\tdrawTitle(\"Saving \", 0, 0)\n\t\tlcd.drawText(1, 20, \"Update \" .. page .. \"/\" .. #vars, SMLSIZE)\n\telseif status == statusEnum.exit then\n\t\tdrawTitle(\"MSRC \" .. scriptVersion, 0, 0)\n\t\tlcd.drawText(1, 20, \"Completed!\", SMLSIZE)\n\t\tlcd.drawText(1, 40, \"Reboot MSRC to apply changes.\", SMLSIZE)\n\tend\nend\n\nlocal function run_func(event)\n\tif status == statusEnum.start then\n\t\tif sportTelemetryPush(sensorIdTx - 1, 0x21, 0xFFFF, 0x80) then\n\t\t\tstatus = statusEnum.getConfig\n\t\tend\n\telseif status == statusEnum.getConfig then\n\t\thandleEvents(event)\n\t\tgetConfig()\n\telseif status == statusEnum.config or status == statusEnum.exitScr then\n\t\thandleEvents(event)\n\t\tif status == statusEnum.config then\n\t\t\tsetPageItems()\n\t\tend\n\telseif status == statusEnum.saveConfig or status == statusEnum.startSave then\n\t\tsaveConfig()\n\telseif status == statusEnum.maintOff then\n\t\tif sportTelemetryPush(sensorIdTx - 1, 0x20, 0xFFFF, 0x80) then\n\t\t\tstatus = statusEnum.exit\n\t\tend\n\tend\n\tdrawPage()\n\treturn 0\nend\n\nreturn { run = run_func }\n"
  },
  {
    "path": "msrc_gui/appdir/msrc.desktop",
    "content": "[Desktop Entry]\nType=Application\nName=MSRC\nExec=msrc_gui %F\nIcon=msrc\nComment=Sensors for RC\nTerminal=false\nCategories = Utility;\n"
  },
  {
    "path": "msrc_gui/circuitdialog.cpp",
    "content": "#include \"circuitdialog.h\"\n#include \"ui_circuitdialog.h\"\n\nCircuitDialog::CircuitDialog(QWidget *parent) :\n    QDialog(parent),\n    ui(new Ui::CircuitDialog)\n{\n    ui->setupUi(this);\n    this->setWindowFlags(Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint);\n    ui->saCircuit->takeWidget();\n    ui->saCircuit->setWidget(ui->lbCircuit);\n}\n\nCircuitDialog::~CircuitDialog()\n{\n    delete ui;\n}\n\nvoid CircuitDialog::on_btClose_clicked()\n{\n    this->hide();\n}\n\nvoid CircuitDialog::resizeEvent(QResizeEvent* event)\n{\n    Q_UNUSED(event);\n    if (ui->lbCircuit->height() < ui->saCircuit->height()) ui->lbCircuit->setFixedHeight(ui->saCircuit->height());\n    if (ui->lbCircuit->width() < ui->saCircuit->width()) ui->lbCircuit->setFixedWidth(ui->saCircuit->width());\n    mainWindow->generateCircuit(ui->lbCircuit);\n}\n\nvoid CircuitDialog::on_btZoomIn_released()\n{\n     ui->lbCircuit->setFixedHeight(ui->lbCircuit->height() * 1.2);\n     ui->lbCircuit->setFixedWidth(ui->lbCircuit->width() * 1.2);\n     mainWindow->generateCircuit(ui->lbCircuit);\n}\n\nvoid CircuitDialog::on_btZoomOut_released()\n{\n     ui->lbCircuit->setFixedHeight(ui->lbCircuit->height() / 1.2);\n     ui->lbCircuit->setFixedWidth(ui->lbCircuit->width() / 1.2);\n     if (ui->lbCircuit->height() < ui->saCircuit->height()) ui->lbCircuit->setFixedHeight(ui->saCircuit->height());\n     if (ui->lbCircuit->width() < ui->saCircuit->width()) ui->lbCircuit->setFixedWidth(ui->saCircuit->width());\n     mainWindow->generateCircuit(ui->lbCircuit);\n}\n"
  },
  {
    "path": "msrc_gui/circuitdialog.h",
    "content": "#ifndef CIRCUITDIALOG_H\n#define CIRCUITDIALOG_H\n\n#include <QDialog>\n#include <QLabel>\n#include <QDebug>\n#include \"mainwindow.h\"\n\nnamespace Ui {\nclass CircuitDialog;\n}\n\nclass CircuitDialog : public QDialog\n{\n    Q_OBJECT\n\npublic:\n    MainWindow *mainWindow;\n\n    explicit CircuitDialog(QWidget *parent = nullptr);\n    ~CircuitDialog();\n    void resizeEvent(QResizeEvent* event);\n\nprivate slots:\n    void on_btClose_clicked();\n    void on_btZoomIn_released();\n    void on_btZoomOut_released();\n\nprivate:\n    Ui::CircuitDialog *ui;\n};\n\n#endif // CIRCUITDIALOG_H\n"
  },
  {
    "path": "msrc_gui/circuitdialog.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>CircuitDialog</class>\n <widget class=\"QDialog\" name=\"CircuitDialog\">\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>763</width>\n    <height>559</height>\n   </rect>\n  </property>\n  <property name=\"minimumSize\">\n   <size>\n    <width>600</width>\n    <height>400</height>\n   </size>\n  </property>\n  <property name=\"windowTitle\">\n   <string>Circuit</string>\n  </property>\n  <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n   <item>\n    <widget class=\"QScrollArea\" name=\"saCircuit\">\n     <property name=\"widgetResizable\">\n      <bool>true</bool>\n     </property>\n     <widget class=\"QWidget\" name=\"scrollAreaWidgetContents\">\n      <property name=\"geometry\">\n       <rect>\n        <x>0</x>\n        <y>0</y>\n        <width>743</width>\n        <height>506</height>\n       </rect>\n      </property>\n      <widget class=\"QLabel\" name=\"lbCircuit\">\n       <property name=\"geometry\">\n        <rect>\n         <x>0</x>\n         <y>0</y>\n         <width>121</width>\n         <height>71</height>\n        </rect>\n       </property>\n       <property name=\"text\">\n        <string>TextLabel</string>\n       </property>\n      </widget>\n     </widget>\n    </widget>\n   </item>\n   <item>\n    <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n     <item>\n      <widget class=\"QPushButton\" name=\"btZoomIn\">\n       <property name=\"text\">\n        <string>+</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"btZoomOut\">\n       <property name=\"text\">\n        <string>-</string>\n       </property>\n      </widget>\n     </item>\n     <item>\n      <widget class=\"QPushButton\" name=\"btClose\">\n       <property name=\"text\">\n        <string>Close</string>\n       </property>\n      </widget>\n     </item>\n    </layout>\n   </item>\n  </layout>\n </widget>\n <resources/>\n <connections/>\n</ui>\n"
  },
  {
    "path": "msrc_gui/main.cpp",
    "content": "#include \"mainwindow.h\"\n\n#include <QApplication>\n\nint main(int argc, char *argv[])\n{ \n    QApplication a(argc, argv);\n    MainWindow w;\n    w.show();\n    return a.exec();\n}\n"
  },
  {
    "path": "msrc_gui/mainwindow.cpp",
    "content": "﻿#include \"mainwindow.h\"\n\n#include \"circuitdialog.h\"\n#include \"qobject.h\"\n#include \"ui_mainwindow.h\"\n\nMainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), serial(new QSerialPort()) {\n    ui->setupUi(this);\n\n    this->setWindowTitle(QString::asprintf(\"MSRC Link %s\", PROJECT_VERSION));\n    ui->tbViews->setCurrentIndex(0);\n    ui->ptDebug->ensureCursorVisible();\n    ui->tbConfig->setEnabled(true);\n    ui->tbConfig->setCurrentIndex(0);\n    ui->btDebug->setDisabled(true);\n    ui->btUpdate->setDisabled(true);\n    enableWidgets(ui->scrollAreaWidgetContentsReceiver, false);\n    enableWidgets(ui->scrollAreaWidgetContentsSensors, false);\n    ui->cbEsc->addItems({\"Hobbywing V3\", \"Hobbywing V4/Flyfun (not VBAR firmware)\", \"PWM\", \"Castle Link\", \"Kontronic\",\n                         \"Kiss\", \"APD HV\", \"HobbyWing V5\", \"Smart ESC/BAT\", \"OMP M4\", \"ZTW\", \"OpenYGE\"});\n\n    ui->cbGpsBaudrate->addItems({\"115200\", \"57600\", \"38400\", \"9600\"});\n    ui->cbGpsBaudrate->setCurrentIndex(5);\n    ui->cbReceiver->addItem(\"Frsky Smartport\", RX_SMARTPORT);\n    ui->cbReceiver->addItem(\"Frsky D\", RX_FRSKY_D);\n    ui->cbReceiver->addItem(\"Frsky FPort\", RX_FPORT);\n    ui->cbReceiver->addItem(\"Frsky FBUS\", RX_FBUS);\n    ui->cbReceiver->addItem(\"Spektrum XBUS\", RX_XBUS);\n    ui->cbReceiver->addItem(\"Spektrum SRXL\", RX_SRXL);\n    ui->cbReceiver->addItem(\"Spektrum SRXL2\", RX_SRXL2);\n    ui->cbReceiver->addItem(\"Flysky IBUS\", RX_IBUS);\n    ui->cbReceiver->addItem(\"Futaba SBUS2\", RX_SBUS);\n    ui->cbReceiver->addItem(\"Jeti Ex Bus\", RX_JETIEX);\n    ui->cbReceiver->addItem(\"Jeti Ex Sensor\", RX_JETIEX_SENSOR);\n    ui->cbReceiver->addItem(\"Multiplex Sensor Bus\", RX_MULTIPLEX);\n    ui->cbReceiver->addItem(\"ELRS/CRSF\", RX_CRSF);\n    ui->cbReceiver->addItem(\"Sanwa\", RX_SANWA);\n    ui->cbReceiver->addItem(\"HOTT\", RX_HOTT);\n    ui->cbReceiver->addItem(\"Hitec\", RX_HITEC);\n    ui->cbReceiver->addItem(\"JR Propo\", RX_JR_PROPO);\n    ui->cbReceiver->addItem(\"GHST\", RX_GHST);\n    ui->cbReceiver->addItem(\"Serial Monitor\", SERIAL_MONITOR);\n    ui->sbEscOffset->setVisible(false);\n    ui->cbCurrentSensorType->addItems({\"Hall effect\", \"Shunt resistor\"});\n    ui->cbCurrentAutoOffset->setChecked(true);\n    ui->cbBarometerType->addItems({\"BMP280\", \"MS5611\", \"BMP180\"});\n    ui->cbAltitudeFilter->addItems({\"Low\", \"Medium\", \"High\"});\n    ui->cbAltitudeFilter->setCurrentIndex(2);\n    ui->cbGpsRate->addItems({\"1\", \"5\", \"10\", \"20\"});\n    ui->cbGpsProtocol->addItems({\"UBLOX\", \"NMEA\"});\n    ui->cbSpeedUnitsGps->addItems({\"km/h\", \"kts\"});\n    ui->lbQuiescentVoltage->setText(\"Zero current output voltage, V<sub>IOUT</sub> (V)\");\n    ui->cbVarioAutoOffset->setVisible(false);\n    ui->cbSerialMonitorGpio->addItems({\"1\", \"5\", \"6\"});\n    ui->cbBaudrate->addItems({\"115200\", \"57600\", \"38400\", \"19200\", \"9600\", \"4800\"});\n    ui->cbStopbits->addItems({\"1\", \"2\"});\n    ui->cbParity->addItems({\"None\", \"Odd\", \"Even\"});\n    ui->cbSerialFormat->addItems({\"Hex\", \"String\"});\n    ui->cbLipoType->addItem({\"INA3221\"});\n    ui->cbIna3221Filter->addItem(\"1\", 0B000);\n    ui->cbIna3221Filter->addItem(\"4\", 0B001);\n    ui->cbIna3221Filter->addItem(\"16\", 0B010);\n    ui->cbIna3221Filter->addItem(\"64\", 0B011);\n    ui->cbIna3221Filter->addItem(\"128\", 0B100);\n    ui->cbIna3221Filter->addItem(\"256\", 0B101);\n    ui->cbIna3221Filter->addItem(\"512\", 0B110);\n    ui->cbIna3221Filter->addItem(\"1024\", 0B111);\n    ui->cbMaxPressure->addItems({\"< 1 kPa (K = 8192)\", \"< 2 kPa (K = 4096)\", \"< 4 kPa (K = 2048)\", \"< 8 kPa (K = 1024)\",\n                                 \"< 16 kPa (K = 512)\", \"< 32 kPa (K = 256)\", \"< 65 kPa (K = 128)\", \"< 130 kPa (K = 64)\",\n                                 \"< 260 kPa (K = 32)\", \"< 500 kPa (K = 16)\", \"< 1000 kPa (K = 8)\",\n                                 \"> 1000 kPa (K = 4)\"});\n\n    ui->cbGyroAccSens->addItems({\"2\", \"4\", \"8\", \"16\"});\n    ui->cbGyroSens->addItems({\"250\", \"500\", \"1000\", \"2000\"});\n    ui->cbGyroSamplerate->addItems({\"1000\", \"500\", \"333\", \"250\", \"200\", \"167\", \"144\", \"125\"});\n    ui->cbGyroSamplerate->setVisible(false);\n    ui->lbGyroSamplerate->setVisible(false);\n\n    ui->lbConnections->setText(\n        \"| Sensor/Receiver                           | Board GPIO|\"\n        \"\\n| :---:                                     | :---:            |\"\n        \"\\n| 3.3-5v                                    | 5v               |\"\n        \"\\n| GND                                       | GND              |\"\n        \"\\n| Smartport, SBUS, SRXL, IBUS, SB, Jeti Ex, Sanwa, Hott, SRXL2, FPort, FBUS, GHST  | 0<sup>(1)</sup> & 1 |\"\n        \"\\n| Frsky D, ELRS/CRSF Rx                     | 0                |\"\n        \"\\n| Serial monitor                            | 1                |\"\n        \"\\n| Hitec, XBUS SDA                           | 2<sup>(2)</sup>  |\"\n        \"\\n| Hitec, XBUS SCL                           | 3<sup>(2)</sup>  |\"\n        \"\\n| ESC serial, Serial monitor, Smart ESC     | 5                |\"\n        \"\\n| Phase sensor (PWM in), Smart ESC          | 4                |\"\n        \"\\n| Castle. Receiver signal                   | 4                |\"\n        \"\\n| Castle. ESC signal                        | 5<sup>(2)</sup>  |\"\n        \"\\n| GPS Tx                                    | 6                |\"\n        \"\\n| GPS Rx (3)(4)                             | 14               |\"\n        \"\\n| XBUS. NPN clock stretch<sup>(3)</sup>     | 7                |\"\n        \"\\n| Sensor SDA                                | 8<sup>(2)</sup>  |\"\n        \"\\n| Sensor SCL                                | 9<sup>(2)</sup>  |\"\n        \"\\n| PWM out                                   | 10               |\"\n        \"\\n| Fuel meter (PWM in)                       | 11               |\"\n        \"\\n| Throttle PWM (Smart ESC)                  | 12               |\"\n        \"\\n| Reverse PWM (Smart ESC)                   | 13               |\"\n        \"\\n| Rsteore default config                    | 15               |\"\n        \"\\n| Voltage                                   | 26               |\"\n        \"\\n| Current                                   | 27               |\"\n        \"\\n| NTC                                       | 28               |\"\n        \"\\n| Airspeed                                  | 29               |\"\n        \"\\n| GPIOs                                     | 17 to 22         |\"\n        \"\\n  \"\n        \"\\n(1) with 100Ω resistor. This resistor is optional as RP2040 has internal protection resistors for GPIOs  \"\n        \"\\n(2) Pullups. See [6.3 Spektrum \"\n        \"XBUS](https://github.com/dgatf/msrc/wiki/06.-Receiver-protocol#63-spektrum-xbus) and [6.9 \"\n        \"Hitec](https://github.com/dgatf/msrc/wiki/06.-Receiver-protocol#69-hitec)  \"\n        \"\\n(3) Optional  \"\n        \"\\n(4) Only UBLOX compatible  \");\n    connect(ui->btConnect, SIGNAL(released()), this, SLOT(buttonSerialPort()));\n    connect(ui->btDebug, SIGNAL(released()), this, SLOT(buttonDebug()));\n    connect(ui->btClearDebug, SIGNAL(released()), this, SLOT(buttonClearDebug()));\n    connect(ui->actionExit, SIGNAL(triggered()), this, SLOT(exitApp()));\n    connect(serial, &QSerialPort::readyRead, this, &MainWindow::readSerial);\n    connect(ui->btUpdate, SIGNAL(released()), this, SLOT(writeSerialConfig()));\n    connect(ui->actionUpdateConfig, SIGNAL(triggered()), this, SLOT(writeSerialConfig()));\n\n    connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(openConfig()));\n    connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(saveConfig()));\n    connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(showAbout()));\n    connect(ui->actionDefaultConfig, SIGNAL(triggered()), this, SLOT(defaultConfig()));\n\n    ui->lbCircuit->resize(600, 400);  //(ui->lbCircuit->parentWidget()->width(),\n    // ui->lbCircuit->parentWidget()->height());\n    generateCircuit(ui->lbCircuit);\n\n    portsList = fillPortsInfo();\n\n    statusBar()->showMessage(\"Not connected\");\n\n    QTimer *timer = new QTimer(this);\n    connect(timer, &QTimer::timeout, this, &MainWindow::checkPorts);\n    timer->start(1000);\n}\n\nMainWindow::~MainWindow() { delete ui; }\n\nvoid MainWindow::generateCircuit(QLabel *label) {\n    QSize *size = new QSize(label->width() - 10, label->height() - 10);\n    QPixmap *pix = new QPixmap(*size);\n    QPainter *paint = new QPainter(pix);\n    QImage image;\n\n    paint->fillRect(0, 0, size->width(), size->height(), label->palette().color(QPalette::Base));\n    image.load(\":/res/rp2040_zero.png\");\n    paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n\n    if (ui->cbReceiver->currentText() != \"Serial Monitor\") {\n        if (ui->gbCurrent->isChecked()) {\n            image.load(\":/res/current_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbVoltage1->isChecked()) {\n            image.load(\":/res/voltage_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbTemp1->isChecked()) {\n            image.load(\":/res/ntc_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbAirspeed->isChecked()) {\n            image.load(\":/res/airspeed_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbGps->isChecked()) {\n            image.load(\":/res/gps_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbEsc->isChecked()) {\n            // ui->lbEsc->setEnabled(true);\n            // ui->cbEsc->setEnabled(true);\n            // ui->lbEscModel->setEnabled(true);\n            // ui->lbEscModel->setEnabled(true);\n            // ui->gbRpmMultipliers->setEnabled(true);\n            if (ui->cbEsc->currentText() == \"Hobbywing V3\" ||\n                ui->cbEsc->currentText() == \"Hobbywing V4/Flyfun (not VBAR firmware)\" ||\n                ui->cbEsc->currentText() == \"Kontronic\" || ui->cbEsc->currentText() == \"Kiss\" ||\n                ui->cbEsc->currentText() == \"APD HV\" || ui->cbEsc->currentText() == \"HobbyWing V5\" ||\n                ui->cbEsc->currentText() == \"OMP M4\" || ui->cbEsc->currentText() == \"ZTW\" ||\n                ui->cbEsc->currentText() == \"OpenYGE\")\n                image.load(\":/res/esc_rp2040_zero.png\");\n            else if (ui->cbEsc->currentText() == \"PWM\")\n                image.load(\":/res/pwm_rp2040_zero.png\");\n            else if (ui->cbEsc->currentText() == \"Castle Link\")\n                image.load(\":/res/castle_rp2040_zero.png\");\n            else if (ui->cbEsc->currentText() == \"Smart ESC/BAT\")\n                image.load(\":/res/smart_esc.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        } else {\n        }\n\n        if (ui->gbAltitude->isChecked()) {\n            image.load(\":/res/vario_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbGyro->isChecked()) {\n            image.load(\":/res/vario_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbFuelPressure->isChecked()) {\n            image.load(\":/res/fuel_pressure.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbFuelmeter->isChecked()) {\n            image.load(\":/res/fuel_meter.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->gbLipo->isChecked()) {\n            image.load(\":/res/vario_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n\n        if (ui->cbReceiver->currentText() == \"Frsky D\" || ui->cbReceiver->currentText() == \"ELRS/CRSF\" || ui->cbReceiver->currentText() == \"Jeti Ex Sensor\") {\n            image.load(\":/res/receiver_frsky_d_rp2040_zero.png\");\n        } else if (ui->cbReceiver->currentText() == \"Spektrum XBUS\") {\n            image.load(\":/res/receiver_xbus_rp2040_zero.png\");\n        } else if (ui->cbReceiver->currentText() == \"Hitec\") {\n            image.load(\":/res/receiver_hitec_rp2040_zero.png\");\n        } else {\n            image.load(\":/res/receiver_serial_rp2040_zero.png\");\n        }\n        if (ui->cbReceiver->currentText() == \"Spektrum XBUS\" && ui->cbClockStretch->isChecked() == true) {\n            image.load(\":/res/clock_stretch_xbus_rp2040_zero.png\");\n            paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n        }\n    }\n    paint->drawImage(QPoint(0, 0), image.scaled(*size, Qt::IgnoreAspectRatio));\n\n    label->setPixmap(*pix);\n}\n\nvoid MainWindow::openConfig() {\n    QString filename = QFileDialog::getOpenFileName(this, \"Open Config\", \"\", \"Config Files (*.cfg)\");\n    QFile file(filename);\n\n    if (file.open(QIODevice::ReadOnly)) {\n        file.read((char *)&config, sizeof(config_t));\n        file.close();\n        if (config.version > CONFIG_VERSION) {\n            QMessageBox::warning(this, tr(\"Information\"),\n                                 tr(\"Firmware config version is \") + QString::number(config.version) +\n                                     \". mscr_gui config version is \" + QString::number(CONFIG_VERSION) +\n                                     \". Download latest msrc_gui. Please save the config in case the conversion fails.\",\n                                 QMessageBox::Close);\n            saveConfig();\n            closeSerialPort();\n            return;\n        }\n        if (config.version < CONFIG_VERSION) {\n            QMessageBox::warning(this, tr(\"Information\"),\n                                 tr(\"Firmware config version is \") + QString::number(config.version) +\n                                     \". mscr_gui config version is \" + QString::number(CONFIG_VERSION) +\n                                     \". Converting config version to \" + QString::number(CONFIG_VERSION) +\n                                     \"\\nPlease press Update button to update MSRC config with new config \"\n                                     \"version.\\nAlso is needed to update MSRC firmware, if not already done.\",\n                                 QMessageBox::Close);\n            config.version = CONFIG_VERSION;\n        }\n        setUiFromConfig();\n    }\n}\n\nvoid MainWindow::saveConfig() {\n    QFileDialog dialog(this, \"Save Config\", QString(), \"Config Files (*.cfg)\");\n    dialog.setDefaultSuffix(\".cfg\");\n    dialog.setAcceptMode(QFileDialog::AcceptSave);\n    if (dialog.exec()) {\n        QString filename = dialog.selectedFiles().front();\n        QFile file(filename);\n        if (file.open(QIODevice::WriteOnly)) {\n            getConfigFromUi();\n            file.write((char *)&config, sizeof(config_t));\n        }\n    }\n}\n\nvoid MainWindow::showAbout() {\n    QMessageBox::information(this, tr(\"About\"),\n                             QString::asprintf(\"MSRC Link %s\\n\\rDaniel Gorbea © 2020/2025\", PROJECT_VERSION),\n                             QMessageBox::Close);\n}\n\nvoid MainWindow::buttonSerialPort() {\n    if (isConnected)\n        closeSerialPort();\n    else\n        openSerialPort();\n}\n\nvoid MainWindow::buttonDebug() {\n    if (ui->btDebug->text() == \"Enable Log\") {\n        if (!isConnected) return;\n        ui->btDebug->setText(\"Disable Log\");\n        serial->readAll();\n        char header = 0x30;\n        serial->write(&header, 1);\n        char command = 0x33;\n        serial->write(&command, 1);\n        isDebug = true;\n    } else if (ui->btDebug->text() == \"Disable Log\") {\n        if (!isConnected) return;\n        ui->btDebug->setText(\"Enable Log\");\n        char header = 0x30;\n        serial->write(&header, 1);\n        char command = 0x34;\n        serial->write(&command, 1);\n        isDebug = false;\n    }\n}\n\nvoid MainWindow::buttonClearDebug() { ui->ptDebug->clear(); }\n\nvoid MainWindow::openSerialPort() {\n    QString portName = ui->cbPortList->currentData().toString();\n    serial->setPortName(portName);\n    serial->setBaudRate(QSerialPort::BaudRate::Baud115200);\n    serial->setDataBits(QSerialPort::DataBits::Data8);\n    serial->setParity(QSerialPort::Parity::NoParity);\n    serial->setStopBits(QSerialPort::StopBits::OneStop);\n    serial->setFlowControl(QSerialPort::FlowControl::NoFlowControl);\n    if (serial->open(QIODevice::ReadWrite)) {\n        statusBar()->showMessage(\"Connected \" + ui->cbPortList->currentText());\n        isConnected = true;\n        requestSerialConfig();\n        serial->setDataTerminalReady(true);\n        ui->btUpdate->setEnabled(true);\n        ui->btConnect->setText(\"Disconnect\");\n        ui->actionUpdateConfig->setEnabled(true);\n        ui->actionDefaultConfig->setEnabled(true);\n        enableWidgets(ui->scrollAreaWidgetContentsReceiver, true);\n        enableWidgets(ui->scrollAreaWidgetContentsSensors, true);\n        ui->cbPortList->setDisabled(true);\n        ui->btDebug->setEnabled(true);\n        ui->btDebug->setText(\"Enable Log\");\n    } else {\n        statusBar()->showMessage(\"Not connected: \" + serial->errorString());\n        isConnected = false;\n        ui->btUpdate->setEnabled(false);\n        ui->btConnect->setText(\"Connect\");\n        ui->actionUpdateConfig->setEnabled(false);\n        ui->actionDefaultConfig->setEnabled(false);\n        enableWidgets(ui->scrollAreaWidgetContentsReceiver, false);\n        enableWidgets(ui->scrollAreaWidgetContentsSensors, false);\n        ui->btDebug->setEnabled(false);\n        ui->btDebug->setText(\"Enable Log\");\n    }\n}\n\nvoid MainWindow::closeSerialPort() {\n    serial->close();\n    statusBar()->showMessage(\"Not connected\");\n    isConnected = false;\n    ui->btUpdate->setEnabled(false);\n    ui->btConnect->setText(\"Connect\");\n    ui->actionUpdateConfig->setEnabled(false);\n    ui->actionDefaultConfig->setEnabled(false);\n    enableWidgets(ui->scrollAreaWidgetContentsReceiver, false);\n    enableWidgets(ui->scrollAreaWidgetContentsSensors, false);\n    ui->cbPortList->setDisabled(false);\n    ui->btDebug->setDisabled(true);\n    ui->btDebug->setText(\"Enable Log\");\n}\n\nvoid MainWindow::readSerial() {\n    /*\n       header - 0x30\n       command - 0x30 - msrc to read config from usb\n                 0x31 - msrc to request to send config to usb\n                 0x32 - msrc answer to send config\n                 0x33 - debug on\n                 0x34 - debug off\n                 0x35 - save default config to rp2040 flash\n    */\n\n    if (isDebug == false) {\n        if (serial->bytesAvailable() == sizeof(config_t) + 2) {\n            data = serial->readAll();\n            if (data.at(0) == 0x30 && data.at(1) == 0x32) {\n                memcpy(&config, data.data() + 2, sizeof(config_t));\n\n                if (config.version > CONFIG_VERSION) {\n                    QMessageBox::warning(\n                        this, tr(\"Information\"),\n                        tr(\"Firmware config version is \") + QString::number(config.version) +\n                            \". mscr_gui config version is \" + QString::number(CONFIG_VERSION) +\n                            \". Download latest msrc_gui. Please save the config in case the conversion fails.\",\n                        QMessageBox::Close);\n                    saveConfig();\n                    closeSerialPort();\n                    return;\n                }\n                if (config.version < CONFIG_VERSION) {\n                    QMessageBox::warning(\n                        this, tr(\"Information\"),\n                        tr(\"Firmware config version is \") + QString::number(config.version) +\n                            \". mscr_gui config version is \" + QString::number(CONFIG_VERSION) +\n                            \". Converting config version to \" + QString::number(CONFIG_VERSION) +\n                            \"\\nIt is needed to update MSRC firmware first or you will lose your config to the default \"\n                            \"values. Press Update button to update MSRC config with new config version only if MSRC \"\n                            \"firmware is updated to latest version first.\",\n                        QMessageBox::Close);\n                    config.version = CONFIG_VERSION;\n                }\n\n                setUiFromConfig();\n            }\n        }\n    } else {\n        data = serial->readAll();\n        ui->ptDebug->insertPlainText(data);\n        if (autoscroll) ui->ptDebug->ensureCursorVisible();\n    }\n}\n\nvoid MainWindow::writeSerialConfig() {\n    if (!isConnected) return;\n    getConfigFromUi();\n    char header = 0x30;\n    serial->write(&header, 1);\n    char command = 0x30;\n    serial->write(&command, 1);\n    serial->write((char *)&config, sizeof(config_t));\n    /*QMessageBox msgBox;\n    msgBox.setText(\"Reset RP2040 to apply settings.\");\n    msgBox.exec();*/\n    QMessageBox::warning(this, tr(\"Information\"), tr(\"Reset RP2040 to apply settings.\"), QMessageBox::Close);\n}\n\nvoid MainWindow::defaultConfig() {\n    if (!isConnected) return;\n    char header = 0x30;\n    serial->write(&header, 1);\n    char command = 0x35;\n    serial->write(&command, 1);\n    QMessageBox::warning(this, tr(\"Information\"), tr(\"Reset RP2040 to apply settings.\"), QMessageBox::Close);\n}\n\nvoid MainWindow::setUiFromConfig() {\n    /* Receiver protocol */\n\n    int index = ui->cbReceiver->findData(config.rx_protocol);\n    ui->cbReceiver->setCurrentIndex(index);\n\n    /* Serial Monitor */\n\n    if (config.serial_monitor_gpio == 1)\n        ui->cbSerialMonitorGpio->setCurrentText(\"1\");\n    else if (config.serial_monitor_gpio == 5)\n        ui->cbSerialMonitorGpio->setCurrentText(\"5\");\n    else\n        ui->cbSerialMonitorGpio->setCurrentText(\"6\");\n    int item = ui->cbBaudrate->findText(QString::number(config.serial_monitor_baudrate));\n    if (item == -1)\n        ui->cbBaudrate->setCurrentText(QString::number(config.serial_monitor_baudrate));\n    else\n        ui->cbBaudrate->setCurrentIndex(item);\n    if (config.serial_monitor_parity > 2) config.serial_monitor_parity = 0;\n    ui->cbParity->setCurrentIndex(config.serial_monitor_parity);\n    if (config.serial_monitor_stop_bits > 2 || config.serial_monitor_stop_bits < 1) config.serial_monitor_stop_bits = 1;\n    ui->cbStopbits->setCurrentIndex(config.serial_monitor_stop_bits - 1);\n    if (config.serial_monitor_timeout_ms > 100) config.serial_monitor_timeout_ms = 100;\n    ui->sbTimeout->setValue(config.serial_monitor_timeout_ms);\n    ui->cbInverted->setChecked(config.serial_monitor_inverted);\n    if (config.serial_monitor_format == FORMAT_HEX)\n        ui->cbSerialFormat->setCurrentText(\"Hex\");\n    else\n        ui->cbSerialFormat->setCurrentText(\"String\");\n\n    /* Sensors */\n\n    // ESC\n\n    if (config.esc_protocol == esc_protocol_t::ESC_NONE)\n        ui->gbEsc->setChecked(false);\n    else {\n        ui->gbEsc->setChecked(true);\n        ui->cbEsc->setCurrentIndex(config.esc_protocol - 1);\n    }\n\n    // GPS\n\n    ui->gbGps->setChecked(config.enable_gps);\n    ui->cbGpsBaudrate->setCurrentText(QString::number(config.gps_baudrate));\n    ui->cbGpsRate->setCurrentText(QString::number(config.gps_rate));\n    ui->cbGpsProtocol->setCurrentIndex(config.gps_protocol);\n\n    // Analog rate\n\n    ui->sbAnalogRate->setValue(config.analog_rate);\n\n    // Voltage\n\n    ui->gbVoltage1->setChecked(config.enable_analog_voltage);\n    if (config.sbus_battery_slot == true) {\n        ui->ckSbusBattery->setChecked(true);\n        ui->ckSbusExtVolt->setChecked(false);\n    } else {\n        ui->ckSbusBattery->setChecked(false);\n        ui->ckSbusExtVolt->setChecked(true);\n    }\n\n    // Temperature\n\n    ui->gbTemp1->setChecked(config.enable_analog_ntc);\n    ui->sbTempOffset->setValue(config.ntc_offset);\n\n    // Current\n\n    ui->gbCurrent->setChecked(config.enable_analog_current);\n    ui->cbCurrentSensorType->setCurrentIndex(config.analog_current_type);\n    ui->cbCurrentAutoOffset->setChecked(config.analog_current_autoffset);\n    ui->sbQuiescentVoltage->setValue(config.analog_current_quiescent_voltage);\n    if (config.analog_current_type == analog_current_type_t::CURRENT_TYPE_HALL)\n        ui->sbCurrentSens->setValue(1000 / config.analog_current_multiplier);\n    else\n        ui->sbAnalogCurrentMultiplier->setValue(config.analog_current_multiplier);\n\n    // Airspeed\n\n    ui->gbAirspeed->setChecked(config.enable_analog_airspeed);\n    ui->sbAirspeedVcc->setValue(config.airspeed_vcc / 100.0);\n    ui->sbAirspeedOffset->setValue(config.airspeed_offset);\n\n    // Vario\n\n    if (config.i2c_module == i2c_module_t::I2C_NONE)\n        ui->gbAltitude->setChecked(false);\n    else\n        ui->gbAltitude->setChecked(true);\n    ui->cbBarometerType->setCurrentIndex(config.i2c_module - 1);\n    ui->cbAltitudeFilter->setCurrentIndex(config.bmp280_filter - 1);\n    ui->cbVarioAutoOffset->setChecked(config.vario_auto_offset);\n\n    // Refresh rate\n\n    ui->sbRpmRate->setValue(config.refresh_rate_rpm);\n    ui->sbVoltageRate->setValue(config.refresh_rate_voltage);\n    ui->sbCurrentRate->setValue(config.refresh_rate_current);\n    ui->sbTemperatureRate->setValue(config.refresh_rate_temperature);\n    ui->sbGpsRate->setValue(config.refresh_rate_gps);\n    ui->sbConsumptionRate->setValue(config.refresh_rate_consumption);\n    ui->sbVarioRate->setValue(config.refresh_rate_vario);\n    ui->sbAirspeedRate->setValue(config.refresh_rate_airspeed);\n    // ui->sbDefRate->setValue(config.refresh_rate_def);\n\n    // Averaging\n\n    ui->sbRpmAvg->setValue(qRound(2 / config.alpha_rpm - 1));\n    ui->sbVoltageAvg->setValue(qRound(2 / config.alpha_voltage - 1));\n    ui->sbCurrentAvg->setValue(qRound(2 / config.alpha_current - 1));\n    ui->sbTemperatureAvg->setValue(qRound(2 / config.alpha_temperature - 1));\n    ui->sbVarioAvg->setValue(qRound(2 / config.alpha_vario - 1));\n    ui->sbAirspeedAvg->setValue(qRound(2 / config.alpha_airspeed - 1));\n\n    // Analog voltage multipliers\n\n    ui->sbVoltage1Mult->setValue(config.analog_voltage_multiplier);\n    // config.analog_voltage2_multiplier = ui->sbVoltage2Mult->value();\n\n    // RPM Multipliers\n\n    ui->sbPairOfPoles->setValue(config.pairOfPoles);\n    ui->sbMainTeeth->setValue(config.mainTeeth);\n    ui->sbPinionTeeth->setValue(config.pinionTeeth);\n\n    // PWM out\n\n    ui->cbPwmOut->setChecked(config.enable_pwm_out);\n\n    // Smartport\n\n    // config.smartport_data_id;\n    ui->sbSensorId->setValue(config.smartport_sensor_id);\n\n    // XBUS Clock stretch\n\n    ui->cbClockStretch->setChecked(config.xbus_clock_stretch);\n    ui->cbAlternativePacket->setChecked(config.xbus_use_alternative_volt_temp);\n\n    // Ibus\n\n    ui->cbAlternativeCoordinates->setChecked(config.ibus_alternative_coordinates);\n\n    // Jeti Ex\n\n    if (config.jeti_gps_speed_units_kmh == true)\n        ui->cbSpeedUnitsGps->setCurrentIndex(0);\n    else\n        ui->cbSpeedUnitsGps->setCurrentIndex(1);\n\n    // FPort\n\n    ui->cbFPortInverted->setChecked(config.fport_inverted);\n\n    // FBUS\n\n    ui->cbFbusInverted->setChecked(config.fbus_inverted);\n\n    // HW V4/V5 parameters\n\n    ui->cbInitDelay->setChecked(config.enable_esc_hw4_init_delay);\n    ui->cbEscAutoOffset->setChecked(!config.esc_hw4_is_manual_offset);\n    ui->sbEscOffset->setValue(config.esc_hw4_offset);\n    // config.esc_hw4_init_delay_duration = 10000;\n    ui->sbVoltageMultiplier->setValue(config.esc_hw4_voltage_multiplier * 100000);\n    ui->sbCurrentMultiplier->setValue(config.esc_hw4_current_multiplier * 100000);\n    ui->cbHw4AutoDetect->setChecked(config.esc_hw4_auto_detect);\n\n    // Smart esc\n\n    ui->cbCalculateConsumption->setChecked(config.smart_esc_calc_consumption);\n\n    // Fuel flow\n\n    ui->gbFuelmeter->setChecked(config.enable_fuel_flow);\n    ui->sbMlPulse->setValue(config.fuel_flow_ml_per_pulse);\n\n    // Fuel pressure\n\n    ui->gbFuelPressure->setChecked(config.enable_fuel_pressure);\n    if (config.xgzp68xxd_k == 8192)\n        ui->cbMaxPressure->setCurrentIndex(0);\n    else if (config.xgzp68xxd_k == 4096)\n        ui->cbMaxPressure->setCurrentIndex(1);\n    else if (config.xgzp68xxd_k == 2048)\n        ui->cbMaxPressure->setCurrentIndex(2);\n    else if (config.xgzp68xxd_k == 1024)\n        ui->cbMaxPressure->setCurrentIndex(3);\n    else if (config.xgzp68xxd_k == 512)\n        ui->cbMaxPressure->setCurrentIndex(4);\n    else if (config.xgzp68xxd_k == 256)\n        ui->cbMaxPressure->setCurrentIndex(5);\n    else if (config.xgzp68xxd_k == 128)\n        ui->cbMaxPressure->setCurrentIndex(6);\n    else if (config.xgzp68xxd_k == 64)\n        ui->cbMaxPressure->setCurrentIndex(7);\n    else if (config.xgzp68xxd_k == 32)\n        ui->cbMaxPressure->setCurrentIndex(8);\n    else if (config.xgzp68xxd_k == 16)\n        ui->cbMaxPressure->setCurrentIndex(9);\n    else if (config.xgzp68xxd_k == 8)\n        ui->cbMaxPressure->setCurrentIndex(10);\n    else\n        ui->cbMaxPressure->setCurrentIndex(11);\n\n    // GPIOs\n    ui->cbGpio17->setChecked(config.gpio_mask & 1);\n    ui->cbGpio18->setChecked(config.gpio_mask & (1 << 1));\n    ui->cbGpio19->setChecked(config.gpio_mask & (1 << 2));\n    ui->cbGpio20->setChecked(config.gpio_mask & (1 << 3));\n    ui->cbGpio21->setChecked(config.gpio_mask & (1 << 4));\n    ui->cbGpio22->setChecked(config.gpio_mask & (1 << 5));\n    ui->sbGpioInterval->setValue(config.gpio_interval);\n\n    // Gyro MPU6050\n    ui->gbGyro->setChecked(config.enable_gyro);\n    ui->cbGyroAccSens->setCurrentIndex(config.mpu6050_acc_scale);\n    ui->cbGyroSens->setCurrentIndex(config.mpu6050_gyro_scale);\n    ui->sbGyroWeight->setValue(config.mpu6050_gyro_weighting);\n    ui->sbGyroFilter->setValue(config.mpu6050_filter);\n    // ui->cbGpsRate->setValue\n\n    // INA3221 (lipo)\n    ui->cbIna3221Filter->currentData(config.ina3221_filter);\n    ui->sbLipoCells->setValue(config.lipo_cells);\n    ui->gbLipo->setChecked(config.enable_lipo);\n\n    // SRXL2\n    uint sensor_id_srxl2 = config.sensor_id_srxl2 ;\n    if (sensor_id_srxl2 < 0x01 || sensor_id_srxl2 > 0x0F) sensor_id_srxl2 = 0x01;\n    ui->sbSensorIdSrxl2->setValue(sensor_id_srxl2);\n}\n\nvoid MainWindow::getConfigFromUi() {\n    /* Config version  */\n\n    config.version = CONFIG_VERSION;\n\n    /* Receiver protocol */\n\n    config.rx_protocol = (rx_protocol_t)ui->cbReceiver->currentData().toInt();\n\n    /* Serial Monitor */\n\n    config.serial_monitor_baudrate = ui->cbBaudrate->currentText().toInt();\n    config.serial_monitor_gpio = ui->cbSerialMonitorGpio->currentText().toInt();\n    config.serial_monitor_stop_bits = ui->cbStopbits->currentText().toInt();\n    if (ui->cbParity->currentText() == \"None\")\n        config.serial_monitor_parity = 0;\n    else if (ui->cbParity->currentText() == \"Odd\")\n        config.serial_monitor_parity = 1;\n    else\n        config.serial_monitor_parity = 2;\n    config.serial_monitor_timeout_ms = ui->sbTimeout->value();\n    config.serial_monitor_inverted = ui->cbInverted->isChecked();\n    config.serial_monitor_format = ui->cbSerialFormat->currentText() == \"Hex\" ? FORMAT_HEX : FORMAT_STRING;\n\n    /* Sensors */\n\n    // ESC\n\n    if (ui->gbEsc->isChecked())\n        config.esc_protocol = (esc_protocol_t)(ui->cbEsc->currentIndex() + 1);\n    else\n        config.esc_protocol = esc_protocol_t::ESC_NONE;\n\n    // GPS\n\n    config.enable_gps = ui->gbGps->isChecked();\n    config.gps_baudrate = ui->cbGpsBaudrate->currentText().toInt();\n    config.gps_rate = ui->cbGpsRate->currentText().toInt();\n    config.gps_protocol = ui->cbGpsProtocol->currentIndex();\n\n    // Voltage\n\n    config.enable_analog_voltage = ui->gbVoltage1->isChecked();\n    config.analog_voltage_multiplier = ui->sbVoltage1Mult->value();\n    config.sbus_battery_slot = ui->ckSbusBattery->isChecked();\n\n    // Current\n\n    config.enable_analog_current = ui->gbCurrent->isChecked();\n    config.analog_current_type = (analog_current_type_t)ui->cbCurrentSensorType->currentIndex();\n    config.analog_current_quiescent_voltage = ui->sbQuiescentVoltage->value();\n    if (ui->cbCurrentSensorType->currentIndex() == analog_current_type_t::CURRENT_TYPE_HALL) {\n        if (ui->cbCurrentAutoOffset->isChecked()) {\n            config.analog_current_autoffset = true;\n            config.analog_current_offset = 0;\n        } else {\n            config.analog_current_autoffset = false;\n            config.analog_current_offset = ui->sbQuiescentVoltage->value();\n        }\n        config.analog_current_multiplier = 1000 / ui->sbCurrentSens->value();\n    } else if (ui->cbCurrentSensorType->currentIndex() == analog_current_type_t::CURRENT_TYPE_SHUNT) {\n        config.analog_current_autoffset = false;\n        config.analog_current_offset = 0;\n        config.analog_current_multiplier = ui->sbAnalogCurrentMultiplier->value();\n    }\n\n    // Temperature\n\n    config.enable_analog_ntc = ui->gbTemp1->isChecked();\n    config.ntc_offset = ui->sbTempOffset->value();\n\n    // Airspeed\n\n    config.enable_analog_airspeed = ui->gbAirspeed->isChecked();\n    config.airspeed_vcc = ui->sbAirspeedVcc->value() * 100;\n    config.airspeed_offset = ui->sbAirspeedOffset->value();\n\n    // Vario\n\n    if (ui->gbAltitude->isChecked())\n        config.i2c_module = (i2c_module_t)(ui->cbBarometerType->currentIndex() + 1);\n    else\n        config.i2c_module = i2c_module_t::I2C_NONE;\n    config.bmp280_filter = ui->cbAltitudeFilter->currentIndex() + 1;\n    config.vario_auto_offset = ui->cbVarioAutoOffset->isChecked();\n\n    // Refresh rate\n\n    config.refresh_rate_rpm = ui->sbRpmRate->value();\n    config.refresh_rate_voltage = ui->sbVoltageRate->value();\n    config.refresh_rate_current = ui->sbCurrentRate->value();\n    config.refresh_rate_temperature = ui->sbTemperatureRate->value();\n    config.refresh_rate_gps = ui->sbGpsRate->value();\n    config.refresh_rate_consumption = ui->sbConsumptionRate->value();\n    config.refresh_rate_vario = ui->sbVarioRate->value();\n    config.refresh_rate_airspeed = ui->sbAirspeedRate->value();\n\n    // Averaging\n\n    config.alpha_rpm = 2.0 / (ui->sbRpmAvg->value() + 1);\n    config.alpha_voltage = 2.0 / (ui->sbVoltageAvg->value() + 1);\n    config.alpha_current = 2.0 / (ui->sbCurrentAvg->value() + 1);\n    config.alpha_temperature = 2.0 / (ui->sbTemperatureAvg->value() + 1);\n    config.alpha_vario = 2.0 / (ui->sbVarioAvg->value() + 1);\n    config.alpha_airspeed = 2.0 / (ui->sbAirspeedAvg->value() + 1);\n\n    // Analog rate\n\n    config.analog_rate = ui->sbAnalogRate->value();\n\n    // RPM Multipliers\n\n    config.pairOfPoles = ui->sbPairOfPoles->value();\n    config.mainTeeth = ui->sbMainTeeth->value();\n    config.pinionTeeth = ui->sbPinionTeeth->value();\n\n    // PWM out\n\n    config.enable_pwm_out = ui->cbPwmOut->isChecked();\n\n    // Smartport\n\n    // config.smartport_data_id = 0x5000;\n    config.smartport_sensor_id = ui->sbSensorId->value();\n\n    // XBUS Clock stretch\n\n    config.xbus_clock_stretch = ui->cbClockStretch->isChecked();\n    config.xbus_use_alternative_volt_temp = ui->cbAlternativePacket->isChecked();\n\n    // Jeti Ex\n\n    if (ui->cbSpeedUnitsGps->currentText() == \"km/h\")\n        config.jeti_gps_speed_units_kmh = true;\n    else\n        config.jeti_gps_speed_units_kmh = false;\n\n    // Ibus\n\n    config.ibus_alternative_coordinates = ui->cbAlternativeCoordinates->isChecked();\n\n    // FPort\n\n    config.fport_inverted = ui->cbFPortInverted->isChecked();\n\n    // FBUS\n\n    config.fbus_inverted = ui->cbFbusInverted->isChecked();\n\n    // HW V4/V5 parameters\n\n    config.enable_esc_hw4_init_delay = ui->cbInitDelay->isChecked();\n    config.esc_hw4_is_manual_offset = !ui->cbEscAutoOffset->isChecked();\n    config.esc_hw4_offset = ui->sbEscOffset->value();\n    config.esc_hw4_voltage_multiplier = ui->sbVoltageMultiplier->value() / 100000.0;\n    config.esc_hw4_current_multiplier = ui->sbCurrentMultiplier->value() / 100000.0;\n    config.esc_hw4_auto_detect = ui->cbHw4AutoDetect->isChecked();\n\n    // Smart esc\n\n    config.smart_esc_calc_consumption = ui->cbCalculateConsumption->isChecked();\n\n    // Fuel flow\n\n    config.enable_fuel_flow = ui->gbFuelmeter->isChecked();\n    config.fuel_flow_ml_per_pulse = ui->sbMlPulse->value();\n\n    // Fuel pressure\n\n    config.enable_fuel_pressure = ui->gbFuelPressure->isChecked();\n    if (ui->cbMaxPressure->currentIndex() == 0)\n        config.xgzp68xxd_k = 8192;\n    else if (ui->cbMaxPressure->currentIndex() == 1)\n        config.xgzp68xxd_k = 4096;\n    else if (ui->cbMaxPressure->currentIndex() == 2)\n        config.xgzp68xxd_k = 2048;\n    else if (ui->cbMaxPressure->currentIndex() == 3)\n        config.xgzp68xxd_k = 1024;\n    else if (ui->cbMaxPressure->currentIndex() == 4)\n        config.xgzp68xxd_k = 512;\n    else if (ui->cbMaxPressure->currentIndex() == 5)\n        config.xgzp68xxd_k = 256;\n    else if (ui->cbMaxPressure->currentIndex() == 6)\n        config.xgzp68xxd_k = 128;\n    else if (ui->cbMaxPressure->currentIndex() == 7)\n        config.xgzp68xxd_k = 64;\n    else if (ui->cbMaxPressure->currentIndex() == 8)\n        config.xgzp68xxd_k = 32;\n    else if (ui->cbMaxPressure->currentIndex() == 9)\n        config.xgzp68xxd_k = 16;\n    else if (ui->cbMaxPressure->currentIndex() == 10)\n        config.xgzp68xxd_k = 8;\n    else\n        config.xgzp68xxd_k = 4;\n\n    // GPIOs\n    config.gpio_mask = ui->cbGpio17->isChecked();\n    config.gpio_mask |= ui->cbGpio18->isChecked() << 1;\n    config.gpio_mask |= ui->cbGpio19->isChecked() << 2;\n    config.gpio_mask |= ui->cbGpio20->isChecked() << 3;\n    config.gpio_mask |= ui->cbGpio21->isChecked() << 4;\n    config.gpio_mask |= ui->cbGpio22->isChecked() << 5;\n    config.gpio_interval = ui->sbGpioInterval->value();\n\n    // Gyro MPU6050\n    config.enable_gyro = ui->gbGyro->isChecked();\n    config.mpu6050_acc_scale = ui->cbGyroAccSens->currentIndex();\n    config.mpu6050_gyro_scale = ui->cbGyroSens->currentIndex();\n    config.mpu6050_gyro_weighting = ui->sbGyroWeight->value();\n    config.mpu6050_filter = ui->sbGyroFilter->value();\n    // config.mpu6050_rate = ui->cbGyroSamplerate->currentText().toInt();\n\n    // INA3221 (lipo)\n    config.ina3221_filter = ui->cbIna3221Filter->itemData(ui->cbIna3221Filter->currentIndex()).toUInt();\n    config.lipo_cells = ui->sbLipoCells->value();\n    config.enable_lipo = ui->gbLipo->isChecked();\n\n    // SRXL2\n    config.sensor_id_srxl2 = ui->sbSensorIdSrxl2->value();\n    if (config.sensor_id_srxl2 < 0x01 || config.sensor_id_srxl2 > 0x0F) config.sensor_id_srxl2 = 0x01;\n\n    // Debug\n\n    config.debug = 0;  // disabled from msrc_gui\n}\n\nvoid MainWindow::requestSerialConfig() {\n    // disable debug\n    char header = 0x30;\n    serial->write(&header, 1);\n    char command = 0x34;\n    serial->write(&command, 1);\n    isDebug = false;\n    QTimer::singleShot(2000, this, SLOT(readSerialConfig()));\n}\n\nvoid MainWindow::readSerialConfig() {\n    serial->readAll();\n    // request config\n    char header = 0x30;\n    serial->write(&header, 1);\n    char command = 0x31;\n    serial->write(&command, 1);\n}\n\nQStringList MainWindow::fillPortsInfo() {\n    const QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts();\n    QStringList list;\n    ui->cbPortList->clear();\n    for (const QSerialPortInfo &info : infos) {\n        list.append(info.portName() + \" (\" + info.manufacturer() + \" \" + info.description() + \")\");\n        ui->cbPortList->addItem(info.portName() + \" (\" + info.manufacturer() + \" \" + info.description() + \")\",\n                                info.portName());\n    }\n    return list;\n}\n\nvoid MainWindow::checkPorts() {\n    const QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts();\n    QStringList list;\n    for (const QSerialPortInfo &info : infos) {\n        list.append(info.portName() + \" (\" + info.manufacturer() + \" \" + info.description() + \")\");\n    }\n\n    std::sort(portsList.begin(), portsList.end());\n    std::sort(list.begin(), list.end());\n\n    if (portsList != list) {\n        QString currentPort = ui->cbPortList->currentText();\n        if (isConnected && !list.contains(currentPort)) closeSerialPort();\n        portsList = fillPortsInfo();\n        ui->cbPortList->setCurrentIndex(ui->cbPortList->findText(currentPort));\n        if (ui->cbPortList->currentIndex() == -1 && ui->cbPortList->count()) ui->cbPortList->setCurrentIndex(0);\n    }\n}\n\nvoid MainWindow::exitApp() { QApplication::quit(); }\n\nvoid MainWindow::enableWidgets(QWidget *widget, bool enable) {\n    QList<QWidget *> widgets = widget->findChildren<QWidget *>();\n    QWidget *child;\n    foreach (child, widgets) child->setEnabled(enable);\n}\n\nvoid MainWindow::on_cbReceiver_currentTextChanged(const QString &arg1) {\n    if (arg1 == \"Spektrum XBUS\") {\n        ui->cbClockStretch->setVisible(true);\n    } else {\n        ui->cbClockStretch->setVisible(false);\n    }\n\n    if (arg1 == \"Spektrum SRXL2\") {\n        ui->lbSensorIdSrxl2->setVisible(true);\n        ui->sbSensorIdSrxl2->setVisible(true);\n    } else {\n        ui->lbSensorIdSrxl2->setVisible(false);\n        ui->sbSensorIdSrxl2->setVisible(false);\n    }\n\n    if (arg1 == \"Spektrum XBUS\" || arg1 == \"Spektrum SRXL\" || arg1 == \"Spektrum SRXL2\") {\n        ui->cbAlternativePacket->setVisible(true);\n    } else {\n        ui->cbAlternativePacket->setVisible(false);\n    }\n\n    if (arg1 == \"Frsky Smartport\" || arg1 == \"Frsky D\" || arg1 == \"Frsky FPort\" || arg1 == \"Frsky FBUS\") {\n        ui->gbRate->setVisible(true);\n    } else {\n        ui->gbRate->setVisible(false);\n    }\n\n    if (arg1 == \"Frsky FPort\") {\n        ui->cbFPortInverted->setVisible(true);\n    } else {\n        ui->cbFPortInverted->setVisible(false);\n    }\n\n    if (arg1 == \"Frsky FBUS\") {\n        ui->cbFbusInverted->setVisible(true);\n    } else {\n        ui->cbFbusInverted->setVisible(false);\n    }\n\n    if (arg1 == \"Frsky Smartport\" || arg1 == \"Frsky FBUS\") {\n        ui->lbSensorId->setVisible(true);\n        ui->sbSensorId->setVisible(true);\n    } else {\n        ui->lbSensorId->setVisible(false);\n        ui->sbSensorId->setVisible(false);\n    }\n\n    if (arg1 == \"Flysky IBUS\") {\n        ui->cbAlternativeCoordinates->setVisible(true);\n    } else {\n        ui->cbAlternativeCoordinates->setVisible(false);\n    }\n\n    if (arg1 == \"Jeti Ex Bus\" || arg1 == \"Jeti Ex Sensor\") {\n        ui->cbSpeedUnitsGps->setVisible(true);\n        ui->lbSpeedUnitsGps->setVisible(true);\n    } else {\n        ui->cbSpeedUnitsGps->setVisible(false);\n        ui->lbSpeedUnitsGps->setVisible(false);\n    }\n\n    if (arg1 == \"Futaba SBUS2\") {\n        ui->ckSbusBattery->setVisible(true);\n        ui->ckSbusExtVolt->setVisible(true);\n    } else {\n        ui->ckSbusBattery->setVisible(false);\n        ui->ckSbusExtVolt->setVisible(false);\n    }\n\n    if (arg1 == \"Serial Monitor\") {\n        ui->cbBaudrate->setVisible(true);\n        ui->cbStopbits->setVisible(true);\n        ui->cbParity->setVisible(true);\n        ui->sbTimeout->setVisible(true);\n        ui->cbInverted->setVisible(true);\n        ui->lbBaudrate->setVisible(true);\n        ui->lbStopbits->setVisible(true);\n        ui->lbParity->setVisible(true);\n        ui->lbTimeout->setVisible(true);\n        ui->lbSerialFormat->setVisible(true);\n        ui->cbSerialFormat->setVisible(true);\n        ui->gbSensors->setVisible(false);\n        ui->gbAverage->setVisible(false);\n        ui->lbSerialMonitorGpio->setVisible(true);\n        ui->cbSerialMonitorGpio->setVisible(true);\n    } else {\n        ui->cbBaudrate->setVisible(false);\n        ui->cbStopbits->setVisible(false);\n        ui->cbParity->setVisible(false);\n        ui->sbTimeout->setVisible(false);\n        ui->cbInverted->setVisible(false);\n        ui->lbBaudrate->setVisible(false);\n        ui->lbStopbits->setVisible(false);\n        ui->lbParity->setVisible(false);\n        ui->lbTimeout->setVisible(false);\n        ui->lbSerialFormat->setVisible(false);\n        ui->cbSerialFormat->setVisible(false);\n        ui->gbSensors->setVisible(true);\n        ui->gbAverage->setVisible(true);\n        ui->lbSerialMonitorGpio->setVisible(false);\n        ui->cbSerialMonitorGpio->setVisible(false);\n    }\n\n    // Fuel meter\n    if (arg1 == \"Frsky Smartport\" || arg1 == \"Jeti Ex Bus\" || arg1 == \"Jeti Ex Sensor\" || arg1 == \"Spektrum XBUS\" ||\n        arg1 == \"HOTT\" || arg1 == \"Frsky FPort\" || arg1 == \"Frsky FBUS\") {\n        ui->gbFuelmeter->setVisible(true);\n    } else {\n        ui->gbFuelmeter->setVisible(false);\n    }\n\n    // Fuel pressure\n    if (arg1 == \"Spektrum SRXL\" || arg1 == \"Spektrum SRXL2\" || arg1 == \"Jeti Ex Bus\" || arg1 == \"Jeti Ex Sensor\" ||\n        arg1 == \"Spektrum XBUS\" || arg1 == \"HOTT\") {\n        ui->gbFuelPressure->setVisible(true);\n    } else {\n        ui->gbFuelPressure->setVisible(false);\n    }\n\n    // GPIO\n    if (arg1 == \"Frsky Smartport\" || arg1 == \"Frsky FPort\" || arg1 == \"Frsky FBUS\") {\n        ui->gbGpio->setVisible(true);\n    } else {\n        ui->gbGpio->setVisible(false);\n    }\n\n    // Airspeed\n    if (arg1 == \"Sanwa\" || arg1 == \"GHST\") {\n        ui->gbAirspeed->setVisible(false);\n    } else {\n        ui->gbAirspeed->setVisible(true);\n    }\n\n    // Temperature\n    if (arg1 == \"GHST\") {\n        ui->gbTemp1->setVisible(false);\n    } else {\n        ui->gbTemp1->setVisible(true);\n    }\n\n    // GPS, current, vario\n    if (arg1 == \"Sanwa\") {\n        ui->gbGps->setVisible(false);\n        ui->gbCurrent->setVisible(false);\n        ui->gbAltitude->setVisible(false);\n    } else {\n        ui->gbGps->setVisible(true);\n        ui->gbCurrent->setVisible(true);\n        ui->gbAltitude->setVisible(true);\n    }\n\n    // Lipo\n    if (arg1 == \"ELRS/CRSF\" || arg1 == \"Frsky Smartport\" || arg1 == \"Frsky FPort\" || arg1 == \"Frsky FBUS\" || arg1 == \"HOTT\" || arg1 == \"JetiEx Bus\" || arg1 == \"JetiEx Bus\" || arg1 == \"JetiEx Sensor\" || arg1 == \"Spektrum SRXL\" || arg1 == \"Spektrum SRXL2\") {\n        ui->gbLipo->setVisible(true);\n    } else {\n        ui->gbLipo->setVisible(false);\n    }\n\n    // Average elements\n    if (arg1 == \"Sanwa\") {\n        ui->lbRpmAvg->setVisible(true);\n        ui->sbRpmAvg->setVisible(true);\n        ui->lbVoltageAvg->setVisible(true);\n        ui->sbVoltageAvg->setVisible(true);\n        ui->lbCurrentAvg->setVisible(false);\n        ui->sbCurrentAvg->setVisible(false);\n        ui->lbTemperatureAvg->setVisible(true);\n        ui->sbTemperatureAvg->setVisible(true);\n        ui->lbVarioAvg->setVisible(false);\n        ui->sbVarioAvg->setVisible(false);\n        ui->lbAirspeedAvg->setVisible(false);\n        ui->sbAirspeedAvg->setVisible(false);\n    } else if (arg1 == \"GHST\") {\n        ui->lbRpmAvg->setVisible(false);\n        ui->sbRpmAvg->setVisible(false);\n        ui->lbVoltageAvg->setVisible(true);\n        ui->sbVoltageAvg->setVisible(true);\n        ui->lbCurrentAvg->setVisible(true);\n        ui->sbCurrentAvg->setVisible(true);\n        ui->lbTemperatureAvg->setVisible(false);\n        ui->sbTemperatureAvg->setVisible(false);\n        ui->lbVarioAvg->setVisible(true);\n        ui->sbVarioAvg->setVisible(true);\n        ui->lbAirspeedAvg->setVisible(false);\n        ui->sbAirspeedAvg->setVisible(false);\n    } else {\n        ui->lbRpmAvg->setVisible(true);\n        ui->sbRpmAvg->setVisible(true);\n        ui->lbVoltageAvg->setVisible(true);\n        ui->sbVoltageAvg->setVisible(true);\n        ui->lbCurrentAvg->setVisible(true);\n        ui->sbCurrentAvg->setVisible(true);\n        ui->lbTemperatureAvg->setVisible(true);\n        ui->sbTemperatureAvg->setVisible(true);\n        ui->lbVarioAvg->setVisible(true);\n        ui->sbVarioAvg->setVisible(true);\n        ui->lbAirspeedAvg->setVisible(true);\n        ui->sbAirspeedAvg->setVisible(true);\n    }\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_cbEsc_currentTextChanged(const QString &arg1) {\n    if (arg1 == \"Smart ESC/BAT\")\n        ui->cbCalculateConsumption->setVisible(true);\n    else\n        ui->cbCalculateConsumption->setVisible(false);\n    if (arg1 == \"Hobbywing V4/Flyfun (not VBAR firmware)\") {\n        ui->gbEscParameters->setVisible(true);\n        ui->cbPwmOut->setVisible(true);\n        ui->cbHw4AutoDetect->setVisible(true);\n    } else {\n        ui->gbEscParameters->setVisible(false);\n        ui->cbPwmOut->setVisible(false);\n        ui->cbHw4AutoDetect->setVisible(false);\n    }\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_gbEsc_toggled(bool enabled) {\n    enableWidgets(ui->gbEsc, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_gbVoltage1_toggled(bool enabled) {\n    enableWidgets(ui->gbVoltage1, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_gbTemp1_toggled(bool checked) {\n    Q_UNUSED(checked);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_gbAltitude_toggled(bool enabled) {\n    enableWidgets(ui->gbAltitude, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_gbCurrent_toggled(bool enabled) {\n    enableWidgets(ui->gbCurrent, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_cbBarometerType_currentTextChanged(const QString &arg1) {\n    if (arg1 == \"BMP280\") {\n        ui->cbAltitudeFilter->setVisible(true);\n        ui->lbAltitudeFilter->setVisible(true);\n    } else {\n        ui->cbAltitudeFilter->setVisible(false);\n        ui->lbAltitudeFilter->setVisible(false);\n    }\n    // Q_UNUSED(arg1);\n    // generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_btCircuit_clicked() {\n    CircuitDialog circuitDialog;\n    circuitDialog.setModal(true);\n    circuitDialog.mainWindow = this;\n    circuitDialog.exec();\n}\n\nvoid MainWindow::on_gbGps_toggled(bool enabled) {\n    enableWidgets(ui->gbGps, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_gbAirspeed_toggled(bool enabled) {\n    enableWidgets(ui->gbAirspeed, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_gbFuelmeter_toggled(bool enabled) {\n    enableWidgets(ui->gbFuelmeter, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_cbCurrentAutoOffset_toggled(bool checked) {\n    if (checked) {\n        ui->lbQuiescentVoltage->setVisible(false);\n        ui->sbQuiescentVoltage->setVisible(false);\n    } else if (ui->cbCurrentSensorType->currentIndex() == analog_current_type_t::CURRENT_TYPE_HALL) {\n        ui->lbQuiescentVoltage->setVisible(true);\n        ui->sbQuiescentVoltage->setVisible(true);\n    }\n}\n\nvoid MainWindow::on_cbCurrentSensorType_currentTextChanged(const QString &arg1) {\n    if (arg1 == \"Hall effect\") {\n        ui->cbCurrentAutoOffset->setVisible(true);\n        ui->lbCurrentSens->setVisible(true);\n        ui->sbCurrentSens->setVisible(true);\n        ui->lbAnalogCurrentMultiplier->setVisible(false);\n        ui->sbAnalogCurrentMultiplier->setVisible(false);\n        if (ui->cbCurrentAutoOffset->isChecked()) {\n            ui->lbQuiescentVoltage->setVisible(false);\n            ui->sbQuiescentVoltage->setVisible(false);\n        } else {\n            ui->lbQuiescentVoltage->setVisible(true);\n            ui->sbQuiescentVoltage->setVisible(true);\n        }\n    }\n    if (arg1 == \"Shunt resistor\") {\n        ui->cbCurrentAutoOffset->setVisible(false);\n        ui->lbCurrentSens->setVisible(false);\n        ui->sbCurrentSens->setVisible(false);\n        ui->lbQuiescentVoltage->setVisible(false);\n        ui->sbQuiescentVoltage->setVisible(false);\n        ui->lbAnalogCurrentMultiplier->setVisible(true);\n        ui->sbAnalogCurrentMultiplier->setVisible(true);\n    }\n}\n\nvoid MainWindow::on_cbClockStretch_toggled(bool checked) {\n    Q_UNUSED(checked);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_cbEscAutoOffset_stateChanged(int arg1) {\n    if (arg1)\n        ui->sbEscOffset->setVisible(false);\n    else\n        ui->sbEscOffset->setVisible(true);\n}\n\nvoid MainWindow::on_btScroll_clicked() {\n    autoscroll = !autoscroll;\n    if (autoscroll)\n        ui->btScroll->setText(\"No scroll\");\n    else\n        ui->btScroll->setText(\"Autoscroll\");\n}\n\nvoid MainWindow::on_gbFuelPressure_toggled(bool enabled) {\n    enableWidgets(ui->gbFuelPressure, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n\nvoid MainWindow::on_ckSbusBattery_toggled(bool checked) { ui->ckSbusExtVolt->setChecked(!checked); }\n\nvoid MainWindow::on_ckSbusExtVolt_toggled(bool checked) { ui->ckSbusBattery->setChecked(!checked); }\n\nvoid MainWindow::on_cbHw4AutoDetect_toggled(bool checked) {\n    if (checked) {\n        ui->gbEscParameters->setVisible(false);\n    } else {\n        ui->gbEscParameters->setVisible(true);\n    }\n}\n\nvoid MainWindow::on_gbGyro_toggled(bool enabled) {\n    enableWidgets(ui->gbGyro, enabled);\n    generateCircuit(ui->lbCircuit);\n}\n"
  },
  {
    "path": "msrc_gui/mainwindow.h",
    "content": "#ifndef MAINWINDOW_H\n#define MAINWINDOW_H\n\n#define CONFIG_VERSION 2\n\n#include <QDebug>\n#include <QFileDialog>\n#include <QJsonDocument>\n#include <QJsonObject>\n#include <QLabel>\n#include <QMainWindow>\n#include <QMessageBox>\n#include <QPainter>\n#include <QSerialPort>\n#include <QSerialPortInfo>\n#include <QTimer>\n\n#include \"shared.h\"\n\nQT_BEGIN_NAMESPACE\nnamespace Ui {\nclass MainWindow;\n}\nQT_END_NAMESPACE\n\nclass MainWindow : public QMainWindow {\n    Q_OBJECT\n\n   public:\n    MainWindow(QWidget *parent = nullptr);\n    ~MainWindow();\n\n    void generateCircuit(QLabel *label);\n\n   private:\n    Ui::MainWindow *ui;\n    QSerialPort *serial = nullptr;\n    QByteArray data;\n    bool isConnected = false;\n    QStringList portsList;\n    config_t config;\n    bool isDebug = false;\n    bool autoscroll = true;\n\n    void requestSerialConfig();\n    void getConfigFromUi();\n    void setUiFromConfig();\n    void openSerialPort();\n    void closeSerialPort();\n    void enableWidgets(QWidget *widget, bool enable);\n\n   private slots:\n    void buttonSerialPort();\n    void buttonDebug();\n    void buttonClearDebug();\n    void readSerial();\n    void readSerialConfig();\n    QStringList fillPortsInfo();\n    void checkPorts();\n    void writeSerialConfig();\n    void defaultConfig();\n    void openConfig();\n    void saveConfig();\n    void showAbout();\n    void exitApp();\n    void on_cbReceiver_currentTextChanged(const QString &arg1);\n    void on_cbEsc_currentTextChanged(const QString &arg1);\n    void on_gbEsc_toggled(bool arg1);\n    void on_gbVoltage1_toggled(bool arg1);\n    void on_gbTemp1_toggled(bool checked);\n    void on_gbAltitude_toggled(bool arg1);\n    void on_gbCurrent_toggled(bool arg1);\n    void on_cbBarometerType_currentTextChanged(const QString &arg1);\n    void on_btCircuit_clicked();\n    void on_gbGps_toggled(bool arg1);\n    void on_cbCurrentAutoOffset_toggled(bool checked);\n    void on_cbCurrentSensorType_currentTextChanged(const QString &arg1);\n    void on_cbClockStretch_toggled(bool checked);\n    void on_cbEscAutoOffset_stateChanged(int arg1);\n    void on_gbAirspeed_toggled(bool arg1);\n    void on_gbFuelmeter_toggled(bool arg1);\n    void on_btScroll_clicked();\n    void on_gbFuelPressure_toggled(bool arg1);\n    void on_ckSbusBattery_toggled(bool checked);\n    void on_ckSbusExtVolt_toggled(bool checked);\n    void on_cbHw4AutoDetect_toggled(bool checked);\n    void on_gbGyro_toggled(bool arg1);\n};\n#endif  // MAINWINDOW_H\n"
  },
  {
    "path": "msrc_gui/mainwindow.ui",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>MainWindow</class>\n <widget class=\"QMainWindow\" name=\"MainWindow\">\n  <property name=\"enabled\">\n   <bool>true</bool>\n  </property>\n  <property name=\"geometry\">\n   <rect>\n    <x>0</x>\n    <y>0</y>\n    <width>1267</width>\n    <height>765</height>\n   </rect>\n  </property>\n  <property name=\"windowTitle\">\n   <string>MSRC Link</string>\n  </property>\n  <property name=\"windowIcon\">\n   <iconset resource=\"resources.qrc\">\n    <normaloff>:/res/msrc.png</normaloff>:/res/msrc.png</iconset>\n  </property>\n  <property name=\"locale\">\n   <locale language=\"English\" country=\"UnitedStates\"/>\n  </property>\n  <widget class=\"QWidget\" name=\"centralwidget\">\n   <layout class=\"QHBoxLayout\" name=\"horizontalLayout_3\">\n    <item>\n     <layout class=\"QHBoxLayout\" name=\"horizontalLayout\">\n      <item>\n       <layout class=\"QVBoxLayout\" name=\"vlLeft\">\n        <property name=\"sizeConstraint\">\n         <enum>QLayout::SetDefaultConstraint</enum>\n        </property>\n        <item>\n         <layout class=\"QHBoxLayout\" name=\"horizontalLayout_2\">\n          <item>\n           <widget class=\"QLabel\" name=\"lbPort\">\n            <property name=\"enabled\">\n             <bool>true</bool>\n            </property>\n            <property name=\"text\">\n             <string>Port</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QComboBox\" name=\"cbPortList\">\n            <property name=\"enabled\">\n             <bool>true</bool>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QPushButton\" name=\"btConnect\">\n            <property name=\"enabled\">\n             <bool>true</bool>\n            </property>\n            <property name=\"text\">\n             <string>Connect</string>\n            </property>\n           </widget>\n          </item>\n          <item>\n           <widget class=\"QPushButton\" name=\"btUpdate\">\n            <property name=\"enabled\">\n             <bool>true</bool>\n            </property>\n            <property name=\"text\">\n             <string>Update config</string>\n            </property>\n           </widget>\n          </item>\n         </layout>\n        </item>\n        <item>\n         <widget class=\"QTabWidget\" name=\"tbConfig\">\n          <property name=\"currentIndex\">\n           <number>1</number>\n          </property>\n          <widget class=\"QWidget\" name=\"tbReceiver\">\n           <attribute name=\"title\">\n            <string>Receiver</string>\n           </attribute>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout\">\n            <item>\n             <widget class=\"QScrollArea\" name=\"scrollAreaReceiver\">\n              <property name=\"widgetResizable\">\n               <bool>true</bool>\n              </property>\n              <widget class=\"QWidget\" name=\"scrollAreaWidgetContentsReceiver\">\n               <property name=\"geometry\">\n                <rect>\n                 <x>0</x>\n                 <y>0</y>\n                 <width>581</width>\n                 <height>774</height>\n                </rect>\n               </property>\n               <layout class=\"QGridLayout\" name=\"gridLayout_14\">\n                <item row=\"0\" column=\"1\">\n                 <widget class=\"QComboBox\" name=\"cbReceiver\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"10\" column=\"1\">\n                 <widget class=\"QComboBox\" name=\"cbStopbits\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"16\" column=\"0\" colspan=\"2\">\n                 <widget class=\"QGroupBox\" name=\"gbRate\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"title\">\n                   <string>Refresh rate (ms)</string>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_13\">\n                   <item row=\"4\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_25\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>GPS</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"6\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_28\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Vario</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbCurrentRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_16\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>RPM</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_18\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Current</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"7\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_30\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Airspeed</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"6\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbVarioRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbVoltageRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbRpmRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"3\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_19\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Temperature</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"7\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbAirspeedRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"5\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbConsumptionRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"4\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbGpsRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"3\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbTemperatureRate\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>2000</number>\n                     </property>\n                     <property name=\"singleStep\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_17\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Voltage</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"5\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_5\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Consumption</string>\n                     </property>\n                    </widget>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item row=\"17\" column=\"0\">\n                 <spacer name=\"verticalSpacer_5\">\n                  <property name=\"orientation\">\n                   <enum>Qt::Vertical</enum>\n                  </property>\n                  <property name=\"sizeHint\" stdset=\"0\">\n                   <size>\n                    <width>20</width>\n                    <height>40</height>\n                   </size>\n                  </property>\n                 </spacer>\n                </item>\n                <item row=\"11\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbParity\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Parity</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"13\" column=\"0\">\n                 <widget class=\"QCheckBox\" name=\"cbInverted\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Inverted</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"4\" column=\"0\">\n                 <widget class=\"QCheckBox\" name=\"cbFPortInverted\">\n                  <property name=\"text\">\n                   <string>Inverted</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"9\" column=\"1\">\n                 <widget class=\"QComboBox\" name=\"cbBaudrate\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"editable\">\n                   <bool>true</bool>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"8\" column=\"1\">\n                 <widget class=\"QComboBox\" name=\"cbSerialMonitorGpio\"/>\n                </item>\n                <item row=\"2\" column=\"0\">\n                 <widget class=\"QCheckBox\" name=\"cbAlternativeCoordinates\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Alternative coordinates</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"9\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbBaudrate\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Baudrate</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"0\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"label\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Protocol</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"5\" column=\"1\">\n                 <widget class=\"QSpinBox\" name=\"sbSensorId\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"minimum\">\n                   <number>1</number>\n                  </property>\n                  <property name=\"maximum\">\n                   <number>30</number>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"12\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbTimeout\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Timeout (ms)</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"15\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbSerialFormat\">\n                  <property name=\"text\">\n                   <string>Output format</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"11\" column=\"1\">\n                 <widget class=\"QComboBox\" name=\"cbParity\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"15\" column=\"1\">\n                 <widget class=\"QComboBox\" name=\"cbSerialFormat\"/>\n                </item>\n                <item row=\"12\" column=\"1\">\n                 <widget class=\"QSpinBox\" name=\"sbTimeout\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"maximum\">\n                   <number>100</number>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"14\" column=\"0\">\n                 <widget class=\"QCheckBox\" name=\"cbFbusInverted\">\n                  <property name=\"text\">\n                   <string>Inverted</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"10\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbStopbits\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Stop bits</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"1\" column=\"0\">\n                 <widget class=\"QCheckBox\" name=\"cbClockStretch\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Clock stretch</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"5\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbSensorId\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Sensor ID</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"3\" column=\"0\">\n                 <widget class=\"QCheckBox\" name=\"cbAlternativePacket\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"text\">\n                   <string>Alternative packet</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"8\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbSerialMonitorGpio\">\n                  <property name=\"text\">\n                   <string>GPIO</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"6\" column=\"0\">\n                 <widget class=\"QLabel\" name=\"lbSensorIdSrxl2\">\n                  <property name=\"text\">\n                   <string>Sensor ID (0x3n)</string>\n                  </property>\n                 </widget>\n                </item>\n                <item row=\"6\" column=\"1\">\n                 <widget class=\"QSpinBox\" name=\"sbSensorIdSrxl2\">\n                  <property name=\"minimum\">\n                   <number>1</number>\n                  </property>\n                  <property name=\"maximum\">\n                   <number>15</number>\n                  </property>\n                 </widget>\n                </item>\n               </layout>\n              </widget>\n             </widget>\n            </item>\n            <item>\n             <spacer name=\"verticalSpacer_3\">\n              <property name=\"orientation\">\n               <enum>Qt::Vertical</enum>\n              </property>\n              <property name=\"sizeHint\" stdset=\"0\">\n               <size>\n                <width>20</width>\n                <height>40</height>\n               </size>\n              </property>\n             </spacer>\n            </item>\n           </layout>\n          </widget>\n          <widget class=\"QWidget\" name=\"tbSensors\">\n           <attribute name=\"title\">\n            <string>Sensors</string>\n           </attribute>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_6\">\n            <item>\n             <widget class=\"QScrollArea\" name=\"scrollAreaSensors\">\n              <property name=\"widgetResizable\">\n               <bool>true</bool>\n              </property>\n              <widget class=\"QWidget\" name=\"scrollAreaWidgetContentsSensors\">\n               <property name=\"geometry\">\n                <rect>\n                 <x>0</x>\n                 <y>-1230</y>\n                 <width>581</width>\n                 <height>2242</height>\n                </rect>\n               </property>\n               <layout class=\"QVBoxLayout\" name=\"verticalLayout_5\">\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbEsc\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"minimumSize\">\n                   <size>\n                    <width>0</width>\n                    <height>0</height>\n                   </size>\n                  </property>\n                  <property name=\"title\">\n                   <string>ESC</string>\n                  </property>\n                  <property name=\"checkable\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"checked\">\n                   <bool>false</bool>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_4\">\n                   <item row=\"6\" column=\"0\" colspan=\"2\">\n                    <widget class=\"QGroupBox\" name=\"gbRpmMultipliers\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"title\">\n                      <string>RPM multipliers</string>\n                     </property>\n                     <layout class=\"QGridLayout\" name=\"gridLayout\">\n                      <item row=\"2\" column=\"1\">\n                       <widget class=\"QSpinBox\" name=\"sbMainTeeth\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"minimum\">\n                         <number>1</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <number>1000</number>\n                        </property>\n                        <property name=\"value\">\n                         <number>1</number>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"2\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbMainTeeth\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Main gear teeth</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbPoles\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Pair of Poles</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"3\" column=\"1\">\n                       <widget class=\"QSpinBox\" name=\"sbPinionTeeth\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"minimum\">\n                         <number>1</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <number>1000</number>\n                        </property>\n                        <property name=\"value\">\n                         <number>1</number>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"1\">\n                       <widget class=\"QSpinBox\" name=\"sbPairOfPoles\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"minimum\">\n                         <number>1</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <number>20</number>\n                        </property>\n                        <property name=\"value\">\n                         <number>1</number>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"3\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbPinionTeeth\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Pinion gear teeth</string>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                   <item row=\"7\" column=\"0\">\n                    <widget class=\"QCheckBox\" name=\"cbPwmOut\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>PWM out</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbEsc\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>   Protocol</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"0\">\n                    <widget class=\"QCheckBox\" name=\"cbHw4AutoDetect\">\n                     <property name=\"text\">\n                      <string>Auto detect</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"3\" column=\"0\" colspan=\"2\">\n                    <widget class=\"QGroupBox\" name=\"gbEscParameters\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"title\">\n                      <string>Parameters</string>\n                     </property>\n                     <layout class=\"QGridLayout\" name=\"gridLayout_15\">\n                      <item row=\"4\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbCurrentMultiplier\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"decimals\">\n                         <number>0</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <double>100000.000000000000000</double>\n                        </property>\n                        <property name=\"value\">\n                         <double>1.000000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"4\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbCurrentMultiplier\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Current multiplier (x1e5)</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"3\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbVoltageMultiplier\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"decimals\">\n                         <number>0</number>\n                        </property>\n                        <property name=\"minimum\">\n                         <double>0.000000000000000</double>\n                        </property>\n                        <property name=\"maximum\">\n                         <double>100000.000000000000000</double>\n                        </property>\n                        <property name=\"value\">\n                         <double>1.000000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"0\">\n                       <widget class=\"QCheckBox\" name=\"cbEscAutoOffset\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Auto offset</string>\n                        </property>\n                        <property name=\"checked\">\n                         <bool>true</bool>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"3\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbVoltageDivisor\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Voltage multiplier (x1e5)</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbEscOffset\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"suffix\">\n                         <string/>\n                        </property>\n                        <property name=\"decimals\">\n                         <number>0</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <double>2000.000000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbEsc\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"5\" column=\"0\">\n                    <widget class=\"QCheckBox\" name=\"cbCalculateConsumption\">\n                     <property name=\"text\">\n                      <string>Calculate consumption (no Smart Battery)</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"4\" column=\"0\">\n                    <widget class=\"QCheckBox\" name=\"cbInitDelay\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Init delay</string>\n                     </property>\n                    </widget>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbGps\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"minimumSize\">\n                   <size>\n                    <width>0</width>\n                    <height>0</height>\n                   </size>\n                  </property>\n                  <property name=\"title\">\n                   <string>GPS</string>\n                  </property>\n                  <property name=\"checkable\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"checked\">\n                   <bool>false</bool>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_8\">\n                   <item row=\"4\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbSpeedUnitsGps\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"4\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbSpeedUnitsGps\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Speed units</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbGpsRate\"/>\n                   </item>\n                   <item row=\"2\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbGpsRate\">\n                     <property name=\"text\">\n                      <string>Rate (Hz)</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbGpsBaudrate\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbGpsBaudrate\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Baud rate</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbGpsProtocol\">\n                     <property name=\"text\">\n                      <string>Protocol</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbGpsProtocol\"/>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbAltitude\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"minimumSize\">\n                   <size>\n                    <width>0</width>\n                    <height>0</height>\n                   </size>\n                  </property>\n                  <property name=\"maximumSize\">\n                   <size>\n                    <width>16777215</width>\n                    <height>16777215</height>\n                   </size>\n                  </property>\n                  <property name=\"title\">\n                   <string>Vario</string>\n                  </property>\n                  <property name=\"checkable\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"checked\">\n                   <bool>false</bool>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_7\">\n                   <item row=\"3\" column=\"1\">\n                    <widget class=\"QCheckBox\" name=\"cbVarioAutoOffset\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Auto offset</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"2\">\n                    <widget class=\"QComboBox\" name=\"cbBarometerType\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"2\">\n                    <widget class=\"QComboBox\" name=\"cbAltitudeFilter\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"1\">\n                    <widget class=\"QLabel\" name=\"lbAltitudeFilter\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Filter</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"1\">\n                    <widget class=\"QLabel\" name=\"label_26\">\n                     <property name=\"enabled\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Model</string>\n                     </property>\n                    </widget>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbFuelPressure\">\n                  <property name=\"title\">\n                   <string>Fuel pressure (XGZP68XXD)</string>\n                  </property>\n                  <property name=\"checkable\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"checked\">\n                   <bool>false</bool>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_12\">\n                   <item row=\"0\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_8\">\n                     <property name=\"text\">\n                      <string>Max pressure (kPa)</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbMaxPressure\"/>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbGyro\">\n                  <property name=\"title\">\n                   <string>Gyro (MPU6050)</string>\n                  </property>\n                  <property name=\"checkable\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"checked\">\n                   <bool>false</bool>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_2\">\n                   <item row=\"4\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbGyroSamplerate\">\n                     <property name=\"text\">\n                      <string>Sample rate</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"3\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbGyroFilter\">\n                     <property name=\"maximum\">\n                      <number>6</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbGyroSens\"/>\n                   </item>\n                   <item row=\"3\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_12\">\n                     <property name=\"text\">\n                      <string>Low pass filter (0 none, 6 max)</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbGyroAccSens\"/>\n                   </item>\n                   <item row=\"0\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_4\">\n                     <property name=\"text\">\n                      <string>Accel sensitivity (g)</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_10\">\n                     <property name=\"text\">\n                      <string>Gyro sensitivity (°/s)</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_11\">\n                     <property name=\"text\">\n                      <string>Gyro weight (%)</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"4\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbGyroSamplerate\"/>\n                   </item>\n                   <item row=\"2\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbGyroWeight\">\n                     <property name=\"maximum\">\n                      <number>100</number>\n                     </property>\n                    </widget>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbLipo\">\n                  <property name=\"title\">\n                   <string>Lipo</string>\n                  </property>\n                  <property name=\"checkable\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"checked\">\n                   <bool>false</bool>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_5\">\n                   <item row=\"2\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbIna3221Filter\"/>\n                   </item>\n                   <item row=\"0\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_13\">\n                     <property name=\"text\">\n                      <string>Type</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"1\">\n                    <widget class=\"QComboBox\" name=\"cbLipoType\"/>\n                   </item>\n                   <item row=\"2\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_14\">\n                     <property name=\"text\">\n                      <string>Filter</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"label_15\">\n                     <property name=\"text\">\n                      <string>Cells</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbLipoCells\">\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>6</number>\n                     </property>\n                    </widget>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbSensors\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"minimumSize\">\n                   <size>\n                    <width>0</width>\n                    <height>0</height>\n                   </size>\n                  </property>\n                  <property name=\"title\">\n                   <string>Analog</string>\n                  </property>\n                  <property name=\"checkable\">\n                   <bool>false</bool>\n                  </property>\n                  <layout class=\"QVBoxLayout\" name=\"verticalLayout_2\">\n                   <item>\n                    <layout class=\"QHBoxLayout\" name=\"loAnalogRate\">\n                     <item>\n                      <widget class=\"QLabel\" name=\"lbAnalogRate\">\n                       <property name=\"enabled\">\n                        <bool>true</bool>\n                       </property>\n                       <property name=\"text\">\n                        <string>Analog rate (Hz)</string>\n                       </property>\n                      </widget>\n                     </item>\n                     <item>\n                      <widget class=\"QSpinBox\" name=\"sbAnalogRate\">\n                       <property name=\"enabled\">\n                        <bool>true</bool>\n                       </property>\n                       <property name=\"minimum\">\n                        <number>1</number>\n                       </property>\n                       <property name=\"maximum\">\n                        <number>100</number>\n                       </property>\n                       <property name=\"value\">\n                        <number>10</number>\n                       </property>\n                      </widget>\n                     </item>\n                    </layout>\n                   </item>\n                   <item>\n                    <widget class=\"QGroupBox\" name=\"gbTemp1\">\n                     <property name=\"title\">\n                      <string>Temperature</string>\n                     </property>\n                     <property name=\"checkable\">\n                      <bool>true</bool>\n                     </property>\n                     <layout class=\"QHBoxLayout\" name=\"horizontalLayout_5\">\n                      <item>\n                       <widget class=\"QLabel\" name=\"lbTempOffset\">\n                        <property name=\"text\">\n                         <string>Offset</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item>\n                       <widget class=\"QSpinBox\" name=\"sbTempOffset\">\n                        <property name=\"minimum\">\n                         <number>-10</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <number>10</number>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                   <item>\n                    <widget class=\"QGroupBox\" name=\"gbVoltage1\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"title\">\n                      <string>Voltage</string>\n                     </property>\n                     <property name=\"checkable\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"checked\">\n                      <bool>false</bool>\n                     </property>\n                     <layout class=\"QGridLayout\" name=\"gridLayout_11\">\n                      <item row=\"0\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbVoltage1Mult\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"maximum\">\n                         <double>1000.000000000000000</double>\n                        </property>\n                        <property name=\"value\">\n                         <double>7.800000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"0\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"label_3\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"toolTip\">\n                         <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;(R1+R2)/R2&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>\n                        </property>\n                        <property name=\"text\">\n                         <string>Multiplier</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"0\">\n                       <widget class=\"QCheckBox\" name=\"ckSbusBattery\">\n                        <property name=\"text\">\n                         <string>Battery</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"1\">\n                       <widget class=\"QCheckBox\" name=\"ckSbusExtVolt\">\n                        <property name=\"text\">\n                         <string>Ext-volt</string>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                   <item>\n                    <widget class=\"QGroupBox\" name=\"gbCurrent\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"title\">\n                      <string>Current</string>\n                     </property>\n                     <property name=\"checkable\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"checked\">\n                      <bool>false</bool>\n                     </property>\n                     <layout class=\"QGridLayout\" name=\"gridLayout_9\">\n                      <item row=\"3\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbQuiescentVoltage\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Zero current output voltage, Viout (V)</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"0\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"label_29\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Type</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"3\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbQuiescentVoltage\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"decimals\">\n                         <number>2</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <double>3.300000000000000</double>\n                        </property>\n                        <property name=\"singleStep\">\n                         <double>0.010000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"2\" column=\"0\">\n                       <widget class=\"QCheckBox\" name=\"cbCurrentAutoOffset\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Auto offset</string>\n                        </property>\n                        <property name=\"checked\">\n                         <bool>false</bool>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"0\" column=\"1\">\n                       <widget class=\"QComboBox\" name=\"cbCurrentSensorType\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"4\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbAnalogCurrentMultiplier\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Multiplier</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"lbCurrentSens\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"text\">\n                         <string>Sensitivity (mV/A)</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"4\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbAnalogCurrentMultiplier\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"singleStep\">\n                         <double>0.010000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbCurrentSens\">\n                        <property name=\"enabled\">\n                         <bool>false</bool>\n                        </property>\n                        <property name=\"decimals\">\n                         <number>2</number>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                   <item>\n                    <widget class=\"QGroupBox\" name=\"gbAirspeed\">\n                     <property name=\"title\">\n                      <string>Airspeed</string>\n                     </property>\n                     <property name=\"checkable\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"checked\">\n                      <bool>false</bool>\n                     </property>\n                     <layout class=\"QGridLayout\" name=\"gridLayout_3\">\n                      <item row=\"0\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"label_2\">\n                        <property name=\"text\">\n                         <string>Vcc</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"0\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbAirspeedVcc\">\n                        <property name=\"decimals\">\n                         <number>2</number>\n                        </property>\n                        <property name=\"minimum\">\n                         <double>3.000000000000000</double>\n                        </property>\n                        <property name=\"maximum\">\n                         <double>6.000000000000000</double>\n                        </property>\n                        <property name=\"singleStep\">\n                         <double>0.010000000000000</double>\n                        </property>\n                        <property name=\"value\">\n                         <double>5.000000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"label_6\">\n                        <property name=\"text\">\n                         <string>Offset (mV)</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"1\">\n                       <widget class=\"QDoubleSpinBox\" name=\"sbAirspeedOffset\">\n                        <property name=\"decimals\">\n                         <number>0</number>\n                        </property>\n                        <property name=\"minimum\">\n                         <double>-1000.000000000000000</double>\n                        </property>\n                        <property name=\"maximum\">\n                         <double>1000.000000000000000</double>\n                        </property>\n                        <property name=\"singleStep\">\n                         <double>1.000000000000000</double>\n                        </property>\n                        <property name=\"value\">\n                         <double>0.000000000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                   <item>\n                    <widget class=\"QGroupBox\" name=\"gbFuelmeter\">\n                     <property name=\"title\">\n                      <string>Fuel meter</string>\n                     </property>\n                     <property name=\"checkable\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"checked\">\n                      <bool>false</bool>\n                     </property>\n                     <layout class=\"QHBoxLayout\" name=\"horizontalLayout_4\">\n                      <item>\n                       <widget class=\"QLabel\" name=\"label_7\">\n                        <property name=\"text\">\n                         <string>ml/pulse</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item>\n                       <widget class=\"QDoubleSpinBox\" name=\"sbMlPulse\">\n                        <property name=\"decimals\">\n                         <number>4</number>\n                        </property>\n                        <property name=\"singleStep\">\n                         <double>0.000100000000000</double>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                   <item>\n                    <widget class=\"QGroupBox\" name=\"gbGpio\">\n                     <property name=\"title\">\n                      <string>GPIOs 17-22</string>\n                     </property>\n                     <property name=\"checkable\">\n                      <bool>false</bool>\n                     </property>\n                     <property name=\"checked\">\n                      <bool>false</bool>\n                     </property>\n                     <layout class=\"QGridLayout\" name=\"gridLayout_10\">\n                      <item row=\"2\" column=\"0\">\n                       <widget class=\"QCheckBox\" name=\"cbGpio21\">\n                        <property name=\"text\">\n                         <string>21</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"3\" column=\"1\">\n                       <widget class=\"QSpinBox\" name=\"sbGpioInterval\">\n                        <property name=\"minimum\">\n                         <number>10</number>\n                        </property>\n                        <property name=\"maximum\">\n                         <number>10000</number>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"0\" column=\"0\">\n                       <widget class=\"QCheckBox\" name=\"cbGpio17\">\n                        <property name=\"text\">\n                         <string>17</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"3\" column=\"0\">\n                       <widget class=\"QLabel\" name=\"label_9\">\n                        <property name=\"text\">\n                         <string>Interval (ms)</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"0\">\n                       <widget class=\"QCheckBox\" name=\"cbGpio19\">\n                        <property name=\"text\">\n                         <string>19</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"1\" column=\"1\">\n                       <widget class=\"QCheckBox\" name=\"cbGpio20\">\n                        <property name=\"text\">\n                         <string>20</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"0\" column=\"1\">\n                       <widget class=\"QCheckBox\" name=\"cbGpio18\">\n                        <property name=\"text\">\n                         <string>18</string>\n                        </property>\n                       </widget>\n                      </item>\n                      <item row=\"2\" column=\"1\">\n                       <widget class=\"QCheckBox\" name=\"cbGpio22\">\n                        <property name=\"text\">\n                         <string>22</string>\n                        </property>\n                       </widget>\n                      </item>\n                     </layout>\n                    </widget>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n                <item>\n                 <widget class=\"QGroupBox\" name=\"gbAverage\">\n                  <property name=\"enabled\">\n                   <bool>true</bool>\n                  </property>\n                  <property name=\"title\">\n                   <string>Averaging elements</string>\n                  </property>\n                  <layout class=\"QGridLayout\" name=\"gridLayout_6\">\n                   <item row=\"3\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbTemperatureAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Temperature</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbVoltageAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Voltage</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"4\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbVarioAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>16</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbRpmAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>RPM</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"5\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbAirspeedAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>16</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"5\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbAirspeedAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Airspeed</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"4\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbVarioAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Vario</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbCurrentAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>16</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>1</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"1\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbVoltageAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>16</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>1</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"0\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbRpmAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>16</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>1</number>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"2\" column=\"0\">\n                    <widget class=\"QLabel\" name=\"lbCurrentAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"text\">\n                      <string>Current</string>\n                     </property>\n                    </widget>\n                   </item>\n                   <item row=\"3\" column=\"1\">\n                    <widget class=\"QSpinBox\" name=\"sbTemperatureAvg\">\n                     <property name=\"enabled\">\n                      <bool>true</bool>\n                     </property>\n                     <property name=\"minimum\">\n                      <number>1</number>\n                     </property>\n                     <property name=\"maximum\">\n                      <number>16</number>\n                     </property>\n                     <property name=\"value\">\n                      <number>1</number>\n                     </property>\n                    </widget>\n                   </item>\n                  </layout>\n                 </widget>\n                </item>\n               </layout>\n              </widget>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n         </widget>\n        </item>\n       </layout>\n      </item>\n      <item>\n       <layout class=\"QVBoxLayout\" name=\"vlRight\">\n        <property name=\"sizeConstraint\">\n         <enum>QLayout::SetDefaultConstraint</enum>\n        </property>\n        <item>\n         <widget class=\"QTabWidget\" name=\"tbViews\">\n          <property name=\"enabled\">\n           <bool>true</bool>\n          </property>\n          <property name=\"currentIndex\">\n           <number>1</number>\n          </property>\n          <widget class=\"QWidget\" name=\"tab\">\n           <attribute name=\"title\">\n            <string>Circuit</string>\n           </attribute>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_4\">\n            <item>\n             <widget class=\"QLabel\" name=\"lbCircuit\">\n              <property name=\"enabled\">\n               <bool>true</bool>\n              </property>\n              <property name=\"sizePolicy\">\n               <sizepolicy hsizetype=\"Preferred\" vsizetype=\"Preferred\">\n                <horstretch>0</horstretch>\n                <verstretch>0</verstretch>\n               </sizepolicy>\n              </property>\n              <property name=\"minimumSize\">\n               <size>\n                <width>500</width>\n                <height>0</height>\n               </size>\n              </property>\n              <property name=\"text\">\n               <string>Circuit image</string>\n              </property>\n              <property name=\"scaledContents\">\n               <bool>true</bool>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <widget class=\"QPushButton\" name=\"btCircuit\">\n              <property name=\"enabled\">\n               <bool>true</bool>\n              </property>\n              <property name=\"text\">\n               <string>Open in window</string>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n          <widget class=\"QWidget\" name=\"tab_3\">\n           <attribute name=\"title\">\n            <string>Conections</string>\n           </attribute>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_7\">\n            <item>\n             <widget class=\"QLabel\" name=\"lbConnections\">\n              <property name=\"font\">\n               <font>\n                <family>Ubuntu</family>\n                <pointsize>10</pointsize>\n               </font>\n              </property>\n              <property name=\"text\">\n               <string>connections</string>\n              </property>\n              <property name=\"textFormat\">\n               <enum>Qt::MarkdownText</enum>\n              </property>\n              <property name=\"wordWrap\">\n               <bool>false</bool>\n              </property>\n             </widget>\n            </item>\n           </layout>\n          </widget>\n          <widget class=\"QWidget\" name=\"tab_2\">\n           <attribute name=\"title\">\n            <string>Log</string>\n           </attribute>\n           <layout class=\"QVBoxLayout\" name=\"verticalLayout_3\">\n            <item>\n             <widget class=\"QPlainTextEdit\" name=\"ptDebug\">\n              <property name=\"font\">\n               <font>\n                <family>Ubuntu Mono</family>\n               </font>\n              </property>\n              <property name=\"readOnly\">\n               <bool>true</bool>\n              </property>\n             </widget>\n            </item>\n            <item>\n             <layout class=\"QHBoxLayout\" name=\"horizontalLayout_6\">\n              <item>\n               <widget class=\"QPushButton\" name=\"btDebug\">\n                <property name=\"text\">\n                 <string>Enable Log</string>\n                </property>\n               </widget>\n              </item>\n              <item>\n               <widget class=\"QPushButton\" name=\"btClearDebug\">\n                <property name=\"text\">\n                 <string>Clear Log</string>\n                </property>\n               </widget>\n              </item>\n              <item>\n               <widget class=\"QPushButton\" name=\"btScroll\">\n                <property name=\"text\">\n                 <string>No scroll</string>\n                </property>\n               </widget>\n              </item>\n             </layout>\n            </item>\n           </layout>\n          </widget>\n         </widget>\n        </item>\n       </layout>\n      </item>\n     </layout>\n    </item>\n   </layout>\n  </widget>\n  <widget class=\"QMenuBar\" name=\"menubar\">\n   <property name=\"geometry\">\n    <rect>\n     <x>0</x>\n     <y>0</y>\n     <width>1267</width>\n     <height>22</height>\n    </rect>\n   </property>\n   <widget class=\"QMenu\" name=\"menuMSRC\">\n    <property name=\"title\">\n     <string>File</string>\n    </property>\n    <addaction name=\"actionOpen\"/>\n    <addaction name=\"actionSave\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionUpdateConfig\"/>\n    <addaction name=\"actionDefaultConfig\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"separator\"/>\n    <addaction name=\"actionExit\"/>\n   </widget>\n   <widget class=\"QMenu\" name=\"menuAbout\">\n    <property name=\"title\">\n     <string>About</string>\n    </property>\n    <addaction name=\"actionAbout\"/>\n   </widget>\n   <addaction name=\"menuMSRC\"/>\n   <addaction name=\"menuAbout\"/>\n  </widget>\n  <widget class=\"QStatusBar\" name=\"statusbar\"/>\n  <action name=\"actionUpdateConfig\">\n   <property name=\"enabled\">\n    <bool>false</bool>\n   </property>\n   <property name=\"text\">\n    <string>Update config</string>\n   </property>\n  </action>\n  <action name=\"actionExit\">\n   <property name=\"text\">\n    <string>Exit</string>\n   </property>\n  </action>\n  <action name=\"actionOpen\">\n   <property name=\"text\">\n    <string>Open config...</string>\n   </property>\n  </action>\n  <action name=\"actionSave\">\n   <property name=\"text\">\n    <string>Save config...</string>\n   </property>\n  </action>\n  <action name=\"actionAbout\">\n   <property name=\"text\">\n    <string>About...</string>\n   </property>\n  </action>\n  <action name=\"actionDefaultConfig\">\n   <property name=\"enabled\">\n    <bool>false</bool>\n   </property>\n   <property name=\"text\">\n    <string>Restore default config</string>\n   </property>\n  </action>\n </widget>\n <resources>\n  <include location=\"resources.qrc\"/>\n </resources>\n <connections/>\n</ui>\n"
  },
  {
    "path": "msrc_gui/msrc_gui.pro",
    "content": "QT       += core gui serialport\n\ngreaterThan(QT_MAJOR_VERSION, 4): QT += widgets\n\nsystem(git describe --tags > VERSION)\nPROJECT_VERSION = \"\\\\\\\"$$cat(VERSION)\"\\\\\\\"\nsystem(rm VERSION)\nmessage($${PROJECT_VERSION})\nDEFINES += PROJECT_VERSION=$${PROJECT_VERSION}\n\nQMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64\n\nCONFIG += c++11\n\n# You can make your code fail to compile if it uses deprecated APIs.\n# In order to do so, uncomment the following line.\n#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0\n\nINCLUDEPATH += ../include\n\nSOURCES += \\\n    circuitdialog.cpp \\\n    main.cpp \\\n    mainwindow.cpp\n\nHEADERS += \\\n    circuitdialog.h \\\n    mainwindow.h\n\nFORMS += \\\n    circuitdialog.ui \\\n    mainwindow.ui\n\n# Default rules for deployment.\nqnx: target.path = /tmp/$${TARGET}/bin\nelse: unix:!android: target.path = /opt/$${TARGET}/bin\n!isEmpty(target.path): INSTALLS += target\n\nRESOURCES += \\\n    resources.qrc\n"
  },
  {
    "path": "msrc_gui/resources.qrc",
    "content": "<RCC>\n    <qresource prefix=\"/\">\n        <file>res/castle_rp2040_zero.png</file>\n        <file>res/gps_rp2040_zero.png</file>\n        <file>res/ntc_rp2040_zero.png</file>\n        <file>res/pwm_rp2040_zero.png</file>\n        <file>res/receiver_frsky_d_rp2040_zero.png</file>\n        <file>res/receiver_serial_rp2040_zero.png</file>\n        <file>res/rp2040_zero.png</file>\n        <file>res/vario_rp2040_zero.png</file>\n        <file>res/voltage_rp2040_zero.png</file>\n        <file>res/esc_rp2040_zero.png</file>\n        <file>res/current_rp2040_zero.png</file>\n        <file>res/airspeed_rp2040_zero.png</file>\n        <file>res/receiver_xbus_rp2040_zero.png</file>\n        <file>res/receiver_hitec_rp2040_zero.png</file>\n        <file>res/clock_stretch_xbus_rp2040_zero.png</file>\n        <file>res/msrc.png</file>\n        <file>res/msrc.ico</file>\n        <file>res/smart_esc.png</file>\n        <file>res/fuel_pressure.png</file>\n        <file>res/fuel_meter.png</file>\n    </qresource>\n</RCC>\n"
  },
  {
    "path": "msrc_gui_web/README.md",
    "content": "# MSRC Link Web\n\nA web-based alternative for the Qt desktop configurator (`msrc_gui/`), built with vanilla JavaScript, the **Web Serial API**, and **PWA** offline support. Inspired by the Betaflight Configurator approach.\n\n## Why\n\n- No native app installation required — runs in Chrome/Edge (v89+).\n- Works offline once loaded (Service Worker + cache).\n- Identical binary protocol and `config_t` struct layout as the desktop GUI — `.cfg` files are interchangeable.\n\n## Project structure\n\n```\nmsrc_gui_web/\n├── index.html              # Full UI (493 lines) — all widgets from the Qt GUI\n├── css/\n│   └── style.css           # Dark theme (387 lines)\n├── js/\n│   ├── config_struct.js    # Binary config_t serialization (308 lines)\n│   ├── serial.js           # Web Serial API communication (168 lines)\n│   ├── ui.js               # Bidirectional config↔UI mapping (534 lines)\n│   ├── circuit.js          # Canvas-based circuit diagram (137 lines)\n│   └── app.js              # Main orchestration (218 lines)\n├── manifest.json           # PWA manifest\n├── sw.js                   # Service Worker (cache-first)\n├── res/                    # 19 PNG images (copied from msrc_gui/res/)\n└── test/\n    └── verify_layout.js    # Struct layout verification test\n```\n\nNo build step, no bundler, no npm — just static files served by any HTTP server.\n\n## How each file was built\n\n### `js/config_struct.js` — Binary struct serialization\n\nThis is the most critical file. It replicates the exact memory layout of `config_t` from `include/shared.h`.\n\n**Approach:**\n1. Every field of `config_t` is listed in declaration order with its C type (`u8`, `u16`, `u32`, `f32`, `bool`, `i16`).\n2. A `buildLayout()` function walks the fields applying **natural alignment rules** (matching GCC ARM / Clang defaults): each field is aligned to its own size boundary (1-byte for `u8`/`bool`, 2-byte for `u16`/`i16`, 4-byte for `u32`/`f32`).\n3. Byte offsets are computed automatically — no hardcoded offsets.\n4. `configEncode(obj)` → `Uint8Array` and `configDecode(Uint8Array)` → `obj` use `DataView` with **little-endian** byte order (matching ARM Cortex-M0+).\n5. All enum types (`rx_protocol_t`, `esc_protocol_t`, etc.) are mirrored as JS constants with the same integer values.\n\n**Verification:**\nThe computed layout was verified against the actual C++ compiled struct:\n\n```sh\n# Host: compiled include/shared.h with clang++ -std=c++11 (typed uint8_t enums)\n# and printed offsetof() for 29 key fields.\n\n# Docker: ran test/verify_layout.js\ndocker run --rm -v $(pwd):/app -w /app node:20-alpine node test/verify_layout.js\n\n# Result:\n# CONFIG_SIZE=224 expected=224 OK\n# 29 passed, 0 failed          (offset checks)\n# Roundtrip: 99 passed, 0 failed (encode→decode for all 99 fields)\n```\n\nThe 29 checked offsets span the entire struct — from `version` at offset 0 through `spare20` at offset 220 — including all alignment-sensitive transitions (e.g. `bool` → `uint32_t` at `enable_gps` → `gps_baudrate`, `uint8_t` → `float` at `ina3221_filter` → `alpha_rpm`). The roundtrip test encodes the default config, decodes it, and compares every field.\n\n### `js/serial.js` — Web Serial API\n\nImplements the same USB CDC binary protocol as the Qt GUI (`board/project/usb.c`):\n\n| Bytes sent | Direction | Meaning |\n|---|---|---|\n| `0x30 0x30` + 224 bytes | App → Board | Write config to flash |\n| `0x30 0x31` | App → Board | Request current config |\n| `0x30 0x32` + 224 bytes | Board → App | Config response |\n| `0x30 0x33` | App → Board | Enable debug text stream |\n| `0x30 0x34` | App → Board | Disable debug |\n| `0x30 0x35` | App → Board | Force write default config |\n\nConnection parameters: **115200 baud, 8N1** (matching `QSerialPort` in mainwindow.cpp).\n\nThe reader accumulates bytes and searches for the `[0x30, 0x32]` header pattern before extracting exactly `CONFIG_SIZE` bytes. In debug mode, raw bytes are decoded as UTF-8 text and forwarded to the debug panel.\n\n### `js/ui.js` — Config ↔ UI mapping\n\nReimplements the two core functions from `mainwindow.cpp`:\n\n- **`setUiFromConfig(cfg)`** — maps every `config_t` field to the corresponding DOM element. Ported from `MainWindow::setUiFromConfig()` (lines ~452–680 of mainwindow.cpp).\n- **`getConfigFromUi()`** — reads all DOM widgets back into a config object. Ported from `MainWindow::getConfigFromUi()` (lines ~682–901).\n\nAll conversion formulas are preserved:\n\n| Conversion | Formula |\n|---|---|\n| Averaging alpha ↔ UI elements | `alpha = 2 / (n + 1)`, `n = round(2/alpha - 1)` |\n| Hall sensor sensitivity ↔ multiplier | `multiplier = 1000 / sensitivity_mV_A` |\n| Shunt resistor | multiplier passed through directly |\n| Airspeed VCC | config stores centivolts (`V × 100`), UI shows volts |\n| HW V4/V5 ESC voltage/current multipliers | config stores raw float, UI shows `× 100000` |\n| ESC combo → `esc_protocol_t` | `enum_value = combo_index + 1` (ESC_NONE = 0 = unchecked) |\n| Barometer combo → `i2c_module_t` | `enum_value = combo_index + 1` (I2C_NONE = 0 = unchecked) |\n| BMP280 filter | `config_value = combo_index + 1` |\n| GPIO bitmask | bits 0–5 → GPIO 17–22 |\n\n**Protocol-dependent visibility** (`updateVisibility()`) replicates `MainWindow::on_cbReceiver_currentTextChanged()`:\n\n| Feature | Visible for protocols |\n|---|---|\n| Refresh rates | SmartPort, Frsky D, FPort, FBUS |\n| Fuel meter | SmartPort, JetiEx, JetiEx Sensor, XBUS, HOTT, FPort, FBUS |\n| Fuel pressure | SRXL, SRXL2, JetiEx, JetiEx Sensor, XBUS, HOTT |\n| GPIO switches | SmartPort, FPort, FBUS |\n| Airspeed | Hidden for Sanwa, GHST |\n| GPS, Current, Vario | Hidden for Sanwa |\n| LiPo (INA3221) | CRSF, SmartPort, FPort, FBUS, HOTT, SRXL, SRXL2, JetiEx, JetiEx Sensor |\n| Averaging subsets | Sanwa: RPM+Volt+Temp only. GHST: Volt+Curr+Vario only |\n| Serial Monitor | hides all sensor sections, shows baudrate/parity/stop bits/GPIO |\n\n### `js/circuit.js` — Circuit diagram\n\nReplicates `MainWindow::generateCircuit()` on an HTML5 Canvas:\n\n1. Preloads all 18 PNG overlays from `res/`.\n2. Draws the base board image (`rp2040_zero.png`) always.\n3. Composites sensor overlays based on current UI state:\n   - Each enabled sensor (voltage, current, NTC, airspeed, GPS) draws its overlay.\n   - ESC overlays: `esc_rp2040_zero.png` for serial ESCs, `pwm_rp2040_zero.png` for PWM, `castle_rp2040_zero.png` for Castle Link, `smart_esc.png` for Smart ESC.\n   - I2C bus: `vario_rp2040_zero.png` shared by vario, gyro, and lipo (same physical bus).\n   - Receiver: `receiver_frsky_d_rp2040_zero.png` for Frsky D / CRSF / JetiEx Sensor (single-wire), `receiver_xbus_rp2040_zero.png` for XBUS (I2C), `receiver_hitec_rp2040_zero.png` for Hitec, `receiver_serial_rp2040_zero.png` for all others (UART).\n   - XBUS + clock stretch → additional `clock_stretch_xbus_rp2040_zero.png`.\n4. Zoom in/out buttons scale the canvas (0.5× – 3×).\n\nThe 19 PNG images in `res/` are copied verbatim from `msrc_gui/res/`.\n\n### `js/app.js` — Application entry point\n\nOrchestrates everything:\n\n- **Connect/Disconnect** via Web Serial API (port selection dialog is browser-native).\n- On connect: disables debug, waits 2 seconds (matching Qt GUI timing), then requests config.\n- **Update button**: calls `getConfigFromUi()` → `configEncode()` → `serial.writeConfig()`.\n- **Tab switching** between Configuration, Circuit, Debug.\n- **Debug panel**: toggles debug mode, displays streaming text, auto-scroll toggle.\n- **File Open/Save**: reads/writes raw `.cfg` binary files (just the `config_t` bytes — same format as Qt GUI's save/load).\n- **Default Config**: sends `0x35` command after confirmation dialog.\n- **PWA Service Worker** registration.\n\n### `index.html` — UI structure\n\nEvery widget from the Qt GUI (`mainwindow.ui` / `mainwindow.cpp`) has a corresponding DOM element:\n\n- **Receiver protocol** dropdown with all 19 protocols, each `<option value=\"N\">` matching the `rx_protocol_t` enum value.\n- **Protocol-specific options**: SmartPort sensor ID, XBUS clock stretch + alt packet, IBUS alt coordinates, Jeti GPS speed units, SBUS battery slot, FPort/FBUS inverted.\n- **Serial Monitor**: GPIO, baudrate, stop bits, parity, timeout, inverted, format.\n- **ESC**: 12 models in dropdown (values 1–12 = `esc_protocol_t`). Smart ESC consumption toggle, HW V4/V5 parameters (init delay, auto offset, voltage/current multipliers, auto detect, PWM out), RPM multipliers.\n- **GPS**: protocol (UBLOX/NMEA), baudrate, rate.\n- **Voltage**, **Temperature (NTC)**, **Current** (Hall / Shunt with all sub-options), **Airspeed**, **Vario** (3 barometer types + filter + auto offset), **Fuel Meter**, **Fuel Pressure** (K value dropdown), **GPIO** switches (17–22 + interval), **Gyro** (MPU6050 scales, weighting, filter), **LiPo** (INA3221 filter, cells).\n- **Toggleable fieldsets**: checkbox in legend enables/disables the fieldset content — same UX as `QGroupBox::setCheckable(true)` in Qt.\n\n### `css/style.css` — Dark theme\n\nCSS custom properties for the color scheme (dark background, blue accents). Responsive two-column grid layout. Styled form controls matching the Betaflight Configurator aesthetic.\n\n### `manifest.json` + `sw.js` — PWA offline\n\n- `manifest.json`: standalone display mode, dark theme color, app icon.\n- `sw.js`: on install, caches all 28 assets (HTML, CSS, 5 JS files, manifest, 19 PNGs). Cache-first strategy — the app works fully offline after first load.\n\n## How to run\n\nServe the `msrc_gui_web/` folder over HTTP. Any static file server works:\n\n```sh\ncd msrc_gui_web\npython3 -m http.server 8080\n# Open http://localhost:8080 in Chrome or Edge\n```\n\nOr with Docker:\n```sh\ndocker run --rm -v $(pwd)/msrc_gui_web:/usr/share/nginx/html:ro -p 8080:80 nginx:alpine\n```\n\nThen click **Connect**, select the RP2040 USB serial port, and the config will be loaded automatically.\n\n## Browser requirements\n\n| Platform | Browser | Transport | Status |\n|----------|---------|-----------|--------|\n| Windows / macOS / Linux / ChromeOS | Chrome 89+ / Edge 89+ | Web Serial API | Full support |\n| Android | Chrome 61+ | WebUSB API (fallback) | Full support |\n| iOS / Safari / Firefox | — | — | Not supported (banner shown) |\n\nThe app auto-detects the best available transport: **Web Serial API** on desktop, **WebUSB API** on Android. Both use the same binary protocol — no firmware changes required.\n\n### WebUSB (Android) notes\n\nOn Android, the [Web Serial API is not available](https://caniuse.com/web-serial), so the app falls back to the **WebUSB API** to communicate with the RP2040 at the raw USB level (CDC ACM).\n\n- The RP2040 is accessed via its default Pico SDK USB VID/PID (`0x2E8A` / `0x000A`).\n- Line coding (115200/8N1) is configured via USB CDC control transfers (`SET_LINE_CODING`, `SET_CONTROL_LINE_STATE`).\n- Data is exchanged over bulk IN/OUT endpoints on the CDC data interface.\n- Android may require the user to grant USB permission when first connecting.\n\n## Compatibility with msrc_gui\n\n- `.cfg` files saved by the desktop GUI can be opened in the web app, and vice versa.\n- The binary protocol is byte-identical — the web app talks to the same firmware with no changes required.\n- The `config_t` struct layout (224 bytes, `CONFIG_VERSION = 2`) was verified to match the C++ compiled output by testing 29 struct field offsets and a full 99-field encode/decode roundtrip.\n"
  },
  {
    "path": "msrc_gui_web/css/style.css",
    "content": ":root {\n    --bg-primary: #1a1a2e;\n    --bg-secondary: #16213e;\n    --bg-card: #0f3460;\n    --bg-input: #1a1a3e;\n    --text-primary: #e0e0e0;\n    --text-secondary: #a0a0b0;\n    --accent: #e94560;\n    --accent-hover: #ff6b81;\n    --success: #2ecc71;\n    --success-hover: #27ae60;\n    --border: #2a2a4a;\n    --danger: #e74c3c;\n    --warning: #f39c12;\n    --header-bg: #0a0a1a;\n    --shadow: rgba(0,0,0,0.3);\n}\n\n* { margin: 0; padding: 0; box-sizing: border-box; }\n\nbody {\n    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, sans-serif;\n    background: var(--bg-primary);\n    color: var(--text-primary);\n    font-size: 14px;\n    display: flex;\n    flex-direction: column;\n    min-height: 100vh;\n}\n\n/* Header */\nheader {\n    background: var(--header-bg);\n    padding: 8px 16px;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    border-bottom: 1px solid var(--border);\n    position: sticky;\n    top: 0;\n    z-index: 100;\n}\n\n.header-left {\n    display: flex;\n    align-items: center;\n    gap: 10px;\n}\n\n.header-left .logo {\n    width: 32px;\n    height: 32px;\n}\n\n.header-left h1 {\n    font-size: 18px;\n    font-weight: 600;\n}\n\n.version {\n    font-size: 12px;\n    color: var(--text-secondary);\n}\n\n.header-right {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n}\n\n/* Buttons */\n.btn {\n    padding: 6px 14px;\n    border: 1px solid var(--border);\n    border-radius: 4px;\n    background: var(--bg-secondary);\n    color: var(--text-primary);\n    cursor: pointer;\n    font-size: 13px;\n    transition: all 0.15s;\n}\n\n.btn:hover:not(:disabled) {\n    background: var(--bg-card);\n    border-color: var(--accent);\n}\n\n.btn:disabled {\n    opacity: 0.5;\n    cursor: not-allowed;\n}\n\n.btn-primary {\n    background: var(--accent);\n    border-color: var(--accent);\n    color: white;\n}\n\n.btn-primary:hover:not(:disabled) {\n    background: var(--accent-hover);\n}\n\n.btn-success {\n    background: var(--success);\n    border-color: var(--success);\n    color: white;\n}\n\n.btn-success:hover:not(:disabled) {\n    background: var(--success-hover);\n}\n\n.btn-danger {\n    border-color: var(--danger);\n    color: var(--danger);\n}\n\n.btn-danger:hover:not(:disabled) {\n    background: var(--danger);\n    color: white;\n}\n\n.btn-sm {\n    padding: 4px 10px;\n    font-size: 12px;\n}\n\n/* Tabs */\n.tabs {\n    display: flex;\n    align-items: center;\n    background: var(--header-bg);\n    border-bottom: 2px solid var(--border);\n    padding: 0 16px;\n}\n\n.tab-warning {\n    margin-left: auto;\n    font-size: 11px;\n    color: #ffcc00;\n    opacity: 0.8;\n    white-space: nowrap;\n}\n\n.tab {\n    padding: 10px 20px;\n    background: none;\n    border: none;\n    color: var(--text-secondary);\n    cursor: pointer;\n    font-size: 14px;\n    border-bottom: 2px solid transparent;\n    margin-bottom: -2px;\n    transition: all 0.15s;\n}\n\n.tab:hover {\n    color: var(--text-primary);\n}\n\n.tab.active {\n    color: var(--accent);\n    border-bottom-color: var(--accent);\n}\n\n/* Main */\nmain {\n    flex: 1;\n    padding: 16px;\n    overflow-y: auto;\n}\n\n.tab-content {\n    display: none;\n}\n\n.tab-content.active {\n    display: block;\n}\n\n/* Config layout */\n.config-layout {\n    display: grid;\n    grid-template-columns: 1fr 1fr;\n    gap: 16px;\n    max-width: 1200px;\n    margin: 0 auto;\n}\n\n@media (max-width: 900px) {\n    .config-layout {\n        grid-template-columns: 1fr;\n    }\n}\n\n.config-column {\n    display: flex;\n    flex-direction: column;\n    gap: 12px;\n}\n\n/* Fieldsets */\nfieldset {\n    border: 1px solid var(--border);\n    border-radius: 6px;\n    padding: 12px;\n    background: var(--bg-secondary);\n}\n\nfieldset legend {\n    padding: 0 6px;\n    font-weight: 600;\n    font-size: 13px;\n    color: var(--accent);\n}\n\nfieldset legend label {\n    display: flex;\n    align-items: center;\n    gap: 6px;\n    cursor: pointer;\n}\n\n.toggleable > .fieldset-content {\n    display: flex;\n    flex-direction: column;\n    gap: 8px;\n}\n\n/* Form elements */\n.form-group {\n    display: flex;\n    flex-direction: column;\n    gap: 3px;\n}\n\n.form-group > label {\n    font-size: 12px;\n    color: var(--text-secondary);\n}\n\n.form-row {\n    display: flex;\n    gap: 12px;\n    flex-wrap: wrap;\n}\n\n.form-row .form-group {\n    flex: 1;\n    min-width: 80px;\n}\n\nselect, input[type=\"number\"], input[type=\"text\"] {\n    padding: 5px 8px;\n    border: 1px solid var(--border);\n    border-radius: 4px;\n    background: var(--bg-input);\n    color: var(--text-primary);\n    font-size: 13px;\n    width: 100%;\n}\n\nselect:focus, input:focus {\n    outline: none;\n    border-color: var(--accent);\n}\n\ninput[type=\"checkbox\"] {\n    accent-color: var(--accent);\n    width: 16px;\n    height: 16px;\n    vertical-align: middle;\n    margin-right: 4px;\n}\n\nlabel:has(input[type=\"checkbox\"]) {\n    display: flex;\n    align-items: center;\n    gap: 4px;\n    cursor: pointer;\n    font-size: 13px;\n    padding: 2px 0;\n}\n\n.gpio-row {\n    gap: 8px;\n}\n\n.protocol-opt {\n    margin-top: 4px;\n    padding-top: 4px;\n    border-top: 1px solid var(--border);\n}\n\n/* circuit */\n.circuit-toolbar {\n    display: flex;\n    gap: 8px;\n    margin-bottom: 12px;\n}\n\n.circuit-container {\n    overflow: auto;\n    background: var(--bg-secondary);\n    border: 1px solid var(--border);\n    border-radius: 6px;\n    text-align: center;\n    padding: 16px;\n}\n\n#circuitCanvas {\n    max-width: 100%;\n    image-rendering: auto;\n}\n\n/* Debug */\n.debug-toolbar {\n    display: flex;\n    gap: 8px;\n    margin-bottom: 8px;\n}\n\n.debug-output {\n    background: #0d0d0d;\n    border: 1px solid var(--border);\n    border-radius: 6px;\n    padding: 12px;\n    font-family: 'Courier New', monospace;\n    font-size: 12px;\n    color: #0f0;\n    min-height: 400px;\n    max-height: 70vh;\n    overflow-y: auto;\n    white-space: pre-wrap;\n    word-break: break-all;\n}\n\n/* Footer */\nfooter {\n    background: var(--header-bg);\n    padding: 6px 16px;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    border-top: 1px solid var(--border);\n    font-size: 12px;\n}\n\n.footer-left {\n    display: flex;\n    gap: 8px;\n}\n\n.footer-right {\n    color: var(--text-secondary);\n}\n\n/* Banner */\n.banner {\n    position: fixed;\n    top: 50%;\n    left: 50%;\n    transform: translate(-50%, -50%);\n    background: var(--danger);\n    padding: 24px 32px;\n    border-radius: 8px;\n    font-size: 16px;\n    z-index: 1000;\n    box-shadow: 0 4px 20px var(--shadow);\n    text-align: center;\n}\n\n/* Disabled fieldset styling */\nfieldset.disabled .fieldset-content {\n    opacity: 0.4;\n    pointer-events: none;\n}\n\n/* Scrollbar */\n::-webkit-scrollbar {\n    width: 8px;\n    height: 8px;\n}\n\n::-webkit-scrollbar-track {\n    background: var(--bg-primary);\n}\n\n::-webkit-scrollbar-thumb {\n    background: var(--border);\n    border-radius: 4px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n    background: var(--accent);\n}\n"
  },
  {
    "path": "msrc_gui_web/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>MSRC Link Web</title>\n    <link rel=\"manifest\" href=\"manifest.json\">\n    <link rel=\"icon\" href=\"res/msrc.png\" type=\"image/png\">\n    <meta name=\"theme-color\" content=\"#1a1a2e\">\n    <link rel=\"stylesheet\" href=\"css/style.css\">\n</head>\n<body>\n    <header>\n        <div class=\"header-left\">\n            <img src=\"res/msrc.png\" alt=\"MSRC\" class=\"logo\">\n            <h1>MSRC Link</h1>\n            <span id=\"appVersion\" class=\"version\"></span>\n        </div>\n        <div class=\"header-right\">\n            <button id=\"btConnect\" class=\"btn btn-primary\">Connect</button>\n            <button id=\"btUpdate\" class=\"btn btn-success\" disabled>Update</button>\n        </div>\n    </header>\n\n    <nav class=\"tabs\">\n        <button class=\"tab active\" data-tab=\"config\">Configuration</button>\n        <button class=\"tab\" data-tab=\"circuit\">Circuit</button>\n        <button class=\"tab\" data-tab=\"debug\">Debug Log</button>\n        <span class=\"tab-warning\">⚠️ This web app is AI-generated, don't use it for anything other than testing!</span>\n    </nav>\n\n    <main>\n        <!-- Configuration Tab -->\n        <section id=\"tab-config\" class=\"tab-content active\">\n            <div class=\"config-layout\">\n                <!-- Left column: Receiver -->\n                <div class=\"config-column\">\n                    <fieldset id=\"fsReceiver\">\n                        <legend>Receiver Protocol</legend>\n                        <div class=\"form-group\">\n                            <label for=\"cbReceiver\">Protocol</label>\n                            <select id=\"cbReceiver\">\n                                <option value=\"0\">Frsky Smartport</option>\n                                <option value=\"1\">Frsky D</option>\n                                <option value=\"15\">Frsky FPort</option>\n                                <option value=\"16\">Frsky FBUS</option>\n                                <option value=\"2\">Spektrum XBUS</option>\n                                <option value=\"3\">Spektrum SRXL</option>\n                                <option value=\"9\">Spektrum SRXL2</option>\n                                <option value=\"4\">Flysky IBUS</option>\n                                <option value=\"5\">Futaba SBUS2</option>\n                                <option value=\"7\">Jeti Ex Bus</option>\n                                <option value=\"18\">Jeti Ex Sensor</option>\n                                <option value=\"6\">Multiplex Sensor Bus</option>\n                                <option value=\"11\">ELRS/CRSF</option>\n                                <option value=\"13\">Sanwa</option>\n                                <option value=\"12\">HOTT</option>\n                                <option value=\"8\">Hitec</option>\n                                <option value=\"14\">JR Propo</option>\n                                <option value=\"17\">GHST</option>\n                                <option value=\"10\">Serial Monitor</option>\n                            </select>\n                        </div>\n\n                        <!-- Protocol-specific options -->\n                        <div id=\"optSmartportSensorId\" class=\"form-group protocol-opt\" style=\"display:none\">\n                            <label for=\"sbSensorId\">Sensor Id</label>\n                            <input type=\"number\" id=\"sbSensorId\" min=\"1\" max=\"28\" value=\"15\">\n                        </div>\n                        <div id=\"optXbusClockStretch\" class=\"protocol-opt\" style=\"display:none\">\n                            <label><input type=\"checkbox\" id=\"cbClockStretch\"> Clock Stretch</label>\n                            <label><input type=\"checkbox\" id=\"cbAlternativePacket\"> Alt. Volt/Temp Packet</label>\n                        </div>\n                        <div id=\"optIbus\" class=\"protocol-opt\" style=\"display:none\">\n                            <label><input type=\"checkbox\" id=\"cbAlternativeCoordinates\"> Alt. GPS Coordinates</label>\n                        </div>\n                        <div id=\"optJeti\" class=\"protocol-opt\" style=\"display:none\">\n                            <div class=\"form-group\">\n                                <label for=\"cbSpeedUnitsGps\">GPS Speed Units</label>\n                                <select id=\"cbSpeedUnitsGps\">\n                                    <option value=\"1\">km/h</option>\n                                    <option value=\"0\">kts</option>\n                                </select>\n                            </div>\n                        </div>\n                        <div id=\"optSbus\" class=\"protocol-opt\" style=\"display:none\">\n                            <label><input type=\"checkbox\" id=\"ckSbusBattery\" checked> Battery Slot</label>\n                            <label><input type=\"checkbox\" id=\"ckSbusExtVolt\"> Ext. Voltage Slot</label>\n                        </div>\n                        <div id=\"optFportInverted\" class=\"protocol-opt\" style=\"display:none\">\n                            <label><input type=\"checkbox\" id=\"cbFPortInverted\"> Inverted</label>\n                        </div>\n                        <div id=\"optFbusInverted\" class=\"protocol-opt\" style=\"display:none\">\n                            <label><input type=\"checkbox\" id=\"cbFbusInverted\"> Inverted</label>\n                        </div>\n                    </fieldset>\n\n                    <!-- Serial Monitor options -->\n                    <fieldset id=\"fsSerialMonitor\" style=\"display:none\">\n                        <legend>Serial Monitor</legend>\n                        <div class=\"form-group\">\n                            <label for=\"cbSerialMonitorGpio\">GPIO</label>\n                            <select id=\"cbSerialMonitorGpio\">\n                                <option>1</option><option>5</option><option>6</option>\n                            </select>\n                        </div>\n                        <div class=\"form-group\">\n                            <label for=\"cbBaudrate\">Baudrate</label>\n                            <select id=\"cbBaudrate\">\n                                <option>115200</option><option>57600</option><option>38400</option>\n                                <option>19200</option><option>9600</option><option>4800</option>\n                            </select>\n                        </div>\n                        <div class=\"form-group\">\n                            <label for=\"cbStopbits\">Stop bits</label>\n                            <select id=\"cbStopbits\"><option>1</option><option>2</option></select>\n                        </div>\n                        <div class=\"form-group\">\n                            <label for=\"cbParity\">Parity</label>\n                            <select id=\"cbParity\"><option value=\"0\">None</option><option value=\"1\">Odd</option><option value=\"2\">Even</option></select>\n                        </div>\n                        <div class=\"form-group\">\n                            <label for=\"sbTimeout\">Timeout (ms)</label>\n                            <input type=\"number\" id=\"sbTimeout\" min=\"1\" max=\"100\" value=\"1\">\n                        </div>\n                        <label><input type=\"checkbox\" id=\"cbInverted\"> Inverted</label>\n                        <div class=\"form-group\">\n                            <label for=\"cbSerialFormat\">Format</label>\n                            <select id=\"cbSerialFormat\"><option value=\"0\">Hex</option><option value=\"1\">String</option></select>\n                        </div>\n                    </fieldset>\n\n                    <!-- Refresh Rates (Frsky only) -->\n                    <fieldset id=\"gbRate\" style=\"display:none\">\n                        <legend>Refresh Rate (ms)</legend>\n                        <div class=\"form-row\">\n                            <div class=\"form-group\"><label>RPM</label><input type=\"number\" id=\"sbRpmRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                            <div class=\"form-group\"><label>Voltage</label><input type=\"number\" id=\"sbVoltageRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                            <div class=\"form-group\"><label>Current</label><input type=\"number\" id=\"sbCurrentRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                            <div class=\"form-group\"><label>Temp</label><input type=\"number\" id=\"sbTemperatureRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                        </div>\n                        <div class=\"form-row\">\n                            <div class=\"form-group\"><label>GPS</label><input type=\"number\" id=\"sbGpsRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                            <div class=\"form-group\"><label>Consumption</label><input type=\"number\" id=\"sbConsumptionRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                            <div class=\"form-group\"><label>Vario</label><input type=\"number\" id=\"sbVarioRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                            <div class=\"form-group\"><label>Airspeed</label><input type=\"number\" id=\"sbAirspeedRate\" min=\"1\" max=\"2000\" value=\"1000\"></div>\n                        </div>\n                    </fieldset>\n\n                    <!-- Averaging -->\n                    <fieldset id=\"gbAverage\">\n                        <legend>Averaging Elements</legend>\n                        <div class=\"form-row\">\n                            <div class=\"form-group\" id=\"grpRpmAvg\"><label>RPM</label><input type=\"number\" id=\"sbRpmAvg\" min=\"1\" max=\"16\" value=\"1\"></div>\n                            <div class=\"form-group\" id=\"grpVoltageAvg\"><label>Voltage</label><input type=\"number\" id=\"sbVoltageAvg\" min=\"1\" max=\"16\" value=\"1\"></div>\n                            <div class=\"form-group\" id=\"grpCurrentAvg\"><label>Current</label><input type=\"number\" id=\"sbCurrentAvg\" min=\"1\" max=\"16\" value=\"1\"></div>\n                        </div>\n                        <div class=\"form-row\">\n                            <div class=\"form-group\" id=\"grpTemperatureAvg\"><label>Temp</label><input type=\"number\" id=\"sbTemperatureAvg\" min=\"1\" max=\"16\" value=\"1\"></div>\n                            <div class=\"form-group\" id=\"grpVarioAvg\"><label>Vario</label><input type=\"number\" id=\"sbVarioAvg\" min=\"1\" max=\"16\" value=\"1\"></div>\n                            <div class=\"form-group\" id=\"grpAirspeedAvg\"><label>Airspeed</label><input type=\"number\" id=\"sbAirspeedAvg\" min=\"1\" max=\"16\" value=\"1\"></div>\n                        </div>\n                    </fieldset>\n                </div>\n\n                <!-- Right column: Sensors -->\n                <div class=\"config-column\">\n                    <!-- ESC -->\n                    <fieldset id=\"gbEsc\" class=\"toggleable\">\n                        <legend><label><input type=\"checkbox\" id=\"chkEsc\"> ESC</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"cbEsc\">Model</label>\n                                <select id=\"cbEsc\">\n                                    <option value=\"1\">Hobbywing V3</option>\n                                    <option value=\"2\">Hobbywing V4/Flyfun (not VBAR firmware)</option>\n                                    <option value=\"3\">PWM</option>\n                                    <option value=\"4\">Castle Link</option>\n                                    <option value=\"5\">Kontronic</option>\n                                    <option value=\"6\">Kiss</option>\n                                    <option value=\"7\">APD HV</option>\n                                    <option value=\"8\">HobbyWing V5</option>\n                                    <option value=\"9\">Smart ESC/BAT</option>\n                                    <option value=\"10\">OMP M4</option>\n                                    <option value=\"11\">ZTW</option>\n                                    <option value=\"12\">OpenYGE</option>\n                                </select>\n                            </div>\n                            <div id=\"optSmartEsc\" style=\"display:none\">\n                                <label><input type=\"checkbox\" id=\"cbCalculateConsumption\"> Calculate Consumption</label>\n                            </div>\n                            <div id=\"optHw4AutoDetect\" style=\"display:none\">\n                                <label><input type=\"checkbox\" id=\"cbHw4AutoDetect\"> Auto Detect</label>\n                                <label><input type=\"checkbox\" id=\"cbPwmOut\"> PWM Out</label>\n                            </div>\n                            <fieldset id=\"gbEscParameters\" style=\"display:none\">\n                                <legend>HW V4/V5 Parameters</legend>\n                                <label><input type=\"checkbox\" id=\"cbInitDelay\"> Init Delay</label>\n                                <label><input type=\"checkbox\" id=\"cbEscAutoOffset\" checked> Auto Offset</label>\n                                <div class=\"form-group\" id=\"grpEscOffset\" style=\"display:none\">\n                                    <label for=\"sbEscOffset\">Offset</label>\n                                    <input type=\"number\" id=\"sbEscOffset\" step=\"0.01\" value=\"0\">\n                                </div>\n                                <div class=\"form-group\">\n                                    <label for=\"sbVoltageMultiplier\">Voltage Mult. (x100000)</label>\n                                    <input type=\"number\" id=\"sbVoltageMultiplier\" step=\"1\" value=\"880\">\n                                </div>\n                                <div class=\"form-group\">\n                                    <label for=\"sbCurrentMultiplier\">Current Mult. (x100000)</label>\n                                    <input type=\"number\" id=\"sbCurrentMultiplier\" step=\"1\" value=\"30000\">\n                                </div>\n                            </fieldset>\n                            <fieldset id=\"gbRpmMultipliers\">\n                                <legend>RPM Multipliers</legend>\n                                <div class=\"form-row\">\n                                    <div class=\"form-group\"><label>Pair of Poles</label><input type=\"number\" id=\"sbPairOfPoles\" min=\"1\" max=\"20\" value=\"1\"></div>\n                                    <div class=\"form-group\"><label>Main Gear</label><input type=\"number\" id=\"sbMainTeeth\" min=\"1\" max=\"1000\" value=\"1\"></div>\n                                    <div class=\"form-group\"><label>Pinion Gear</label><input type=\"number\" id=\"sbPinionTeeth\" min=\"1\" max=\"1000\" value=\"1\"></div>\n                                </div>\n                            </fieldset>\n                        </div>\n                    </fieldset>\n\n                    <!-- GPS -->\n                    <fieldset id=\"gbGps\" class=\"toggleable\">\n                        <legend><label><input type=\"checkbox\" id=\"chkGps\"> GPS</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"cbGpsProtocol\">Protocol</label>\n                                <select id=\"cbGpsProtocol\">\n                                    <option value=\"0\">UBLOX</option>\n                                    <option value=\"1\">NMEA</option>\n                                </select>\n                            </div>\n                            <div class=\"form-group\">\n                                <label for=\"cbGpsBaudrate\">Baudrate</label>\n                                <select id=\"cbGpsBaudrate\">\n                                    <option>115200</option><option>57600</option><option>38400</option><option>9600</option>\n                                </select>\n                            </div>\n                            <div class=\"form-group\" id=\"grpGpsRate\">\n                                <label for=\"cbGpsRate\">Rate (Hz)</label>\n                                <select id=\"cbGpsRate\">\n                                    <option>1</option><option>5</option><option>10</option><option>20</option>\n                                </select>\n                            </div>\n                        </div>\n                    </fieldset>\n\n                    <!-- Voltage -->\n                    <fieldset id=\"gbVoltage1\" class=\"toggleable\">\n                        <legend><label><input type=\"checkbox\" id=\"chkVoltage\"> Voltage</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"sbVoltage1Mult\">Multiplier</label>\n                                <input type=\"number\" id=\"sbVoltage1Mult\" step=\"0.1\" value=\"7.8\">\n                            </div>\n                            <div id=\"optSbusBatterySlot\" style=\"display:none\">\n                                <label><input type=\"checkbox\" id=\"ckSbusBatteryVolt\" checked> Battery Slot</label>\n                                <label><input type=\"checkbox\" id=\"ckSbusExtVoltVolt\"> Ext. Voltage Slot</label>\n                            </div>\n                        </div>\n                    </fieldset>\n\n                    <!-- Temperature NTC -->\n                    <div id=\"grpTemperature\">\n                        <label><input type=\"checkbox\" id=\"cbTemperature1\"> Temperature (NTC)</label>\n                    </div>\n\n                    <!-- Current -->\n                    <fieldset id=\"gbCurrent\" class=\"toggleable\">\n                        <legend><label><input type=\"checkbox\" id=\"chkCurrent\"> Current</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"cbCurrentSensorType\">Sensor Type</label>\n                                <select id=\"cbCurrentSensorType\">\n                                    <option value=\"0\">Hall effect</option>\n                                    <option value=\"1\">Shunt resistor</option>\n                                </select>\n                            </div>\n                            <div id=\"optHallEffect\">\n                                <label><input type=\"checkbox\" id=\"cbCurrentAutoOffset\" checked> Auto Offset</label>\n                                <div class=\"form-group\" id=\"grpQuiescentVoltage\" style=\"display:none\">\n                                    <label for=\"sbQuiescentVoltage\">Zero current output voltage, V<sub>IOUT</sub> (V)</label>\n                                    <input type=\"number\" id=\"sbQuiescentVoltage\" step=\"0.01\" value=\"0\">\n                                </div>\n                                <div class=\"form-group\">\n                                    <label for=\"sbCurrentSens\">Sensitivity (mV/A)</label>\n                                    <input type=\"number\" id=\"sbCurrentSens\" step=\"0.1\" value=\"1\">\n                                </div>\n                            </div>\n                            <div id=\"optShuntResistor\" style=\"display:none\">\n                                <div class=\"form-group\">\n                                    <label for=\"sbAnalogCurrentMultiplier\">Multiplier</label>\n                                    <input type=\"number\" id=\"sbAnalogCurrentMultiplier\" step=\"0.01\" value=\"1\">\n                                </div>\n                            </div>\n                            <div class=\"form-group\">\n                                <label for=\"sbAnalogRate\">Analog Rate (Hz)</label>\n                                <input type=\"number\" id=\"sbAnalogRate\" min=\"1\" max=\"100\" value=\"10\">\n                            </div>\n                        </div>\n                    </fieldset>\n\n                    <!-- Airspeed -->\n                    <fieldset id=\"gbAirspeed\" class=\"toggleable\">\n                        <legend><label><input type=\"checkbox\" id=\"chkAirspeed\"> Airspeed</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"sbAirspeedVcc\">VCC (V)</label>\n                                <input type=\"number\" id=\"sbAirspeedVcc\" step=\"0.01\" value=\"5.0\">\n                            </div>\n                            <div class=\"form-group\">\n                                <label for=\"sbAirspeedOffset\">Offset (mV)</label>\n                                <input type=\"number\" id=\"sbAirspeedOffset\" value=\"0\">\n                            </div>\n                        </div>\n                    </fieldset>\n\n                    <!-- Vario -->\n                    <fieldset id=\"gbAltitude\" class=\"toggleable\">\n                        <legend><label><input type=\"checkbox\" id=\"chkAltitude\"> Vario / Altitude</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"cbBarometerType\">Barometer</label>\n                                <select id=\"cbBarometerType\">\n                                    <option value=\"1\">BMP280</option>\n                                    <option value=\"2\">MS5611</option>\n                                    <option value=\"3\">BMP180</option>\n                                </select>\n                            </div>\n                            <div class=\"form-group\" id=\"grpAltitudeFilter\">\n                                <label for=\"cbAltitudeFilter\">Filter</label>\n                                <select id=\"cbAltitudeFilter\">\n                                    <option value=\"1\">Low</option>\n                                    <option value=\"2\">Medium</option>\n                                    <option value=\"3\">High</option>\n                                </select>\n                            </div>\n                            <label><input type=\"checkbox\" id=\"cbVarioAutoOffset\"> Auto Offset</label>\n                        </div>\n                    </fieldset>\n\n                    <!-- Fuel Meter -->\n                    <fieldset id=\"gbFuelmeter\" class=\"toggleable\" style=\"display:none\">\n                        <legend><label><input type=\"checkbox\" id=\"chkFuelmeter\"> Fuel Meter</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"sbMlPulse\">ml per pulse</label>\n                                <input type=\"number\" id=\"sbMlPulse\" step=\"0.001\" value=\"0.01\">\n                            </div>\n                        </div>\n                    </fieldset>\n\n                    <!-- Fuel Pressure -->\n                    <fieldset id=\"gbFuelPressure\" class=\"toggleable\" style=\"display:none\">\n                        <legend><label><input type=\"checkbox\" id=\"chkFuelPressure\"> Fuel Pressure</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"cbMaxPressure\">Max Pressure</label>\n                                <select id=\"cbMaxPressure\">\n                                    <option value=\"8192\">&lt; 1 kPa (K = 8192)</option>\n                                    <option value=\"4096\">&lt; 2 kPa (K = 4096)</option>\n                                    <option value=\"2048\">&lt; 4 kPa (K = 2048)</option>\n                                    <option value=\"1024\">&lt; 8 kPa (K = 1024)</option>\n                                    <option value=\"512\">&lt; 16 kPa (K = 512)</option>\n                                    <option value=\"256\">&lt; 32 kPa (K = 256)</option>\n                                    <option value=\"128\">&lt; 65 kPa (K = 128)</option>\n                                    <option value=\"64\">&lt; 130 kPa (K = 64)</option>\n                                    <option value=\"32\">&lt; 260 kPa (K = 32)</option>\n                                    <option value=\"16\">&lt; 500 kPa (K = 16)</option>\n                                    <option value=\"8\">&lt; 1000 kPa (K = 8)</option>\n                                    <option value=\"4\">&gt; 1000 kPa (K = 4)</option>\n                                </select>\n                            </div>\n                        </div>\n                    </fieldset>\n\n                    <!-- GPIO -->\n                    <fieldset id=\"gbGpio\" style=\"display:none\">\n                        <legend>GPIO Switches</legend>\n                        <div class=\"form-row gpio-row\">\n                            <label><input type=\"checkbox\" id=\"cbGpio17\"> 17</label>\n                            <label><input type=\"checkbox\" id=\"cbGpio18\"> 18</label>\n                            <label><input type=\"checkbox\" id=\"cbGpio19\"> 19</label>\n                            <label><input type=\"checkbox\" id=\"cbGpio20\"> 20</label>\n                            <label><input type=\"checkbox\" id=\"cbGpio21\"> 21</label>\n                            <label><input type=\"checkbox\" id=\"cbGpio22\"> 22</label>\n                        </div>\n                        <div class=\"form-group\">\n                            <label for=\"sbGpioInterval\">Interval (ms)</label>\n                            <input type=\"number\" id=\"sbGpioInterval\" min=\"100\" max=\"10000\" value=\"1000\">\n                        </div>\n                    </fieldset>\n\n                    <!-- Gyro -->\n                    <fieldset id=\"gbGyro\" class=\"toggleable\">\n                        <legend><label><input type=\"checkbox\" id=\"chkGyro\"> Gyro (MPU6050)</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"cbGyroAccSens\">Accel. Scale (g)</label>\n                                <select id=\"cbGyroAccSens\">\n                                    <option value=\"0\">2</option><option value=\"1\">4</option>\n                                    <option value=\"2\">8</option><option value=\"3\">16</option>\n                                </select>\n                            </div>\n                            <div class=\"form-group\">\n                                <label for=\"cbGyroSens\">Gyro Scale (dps)</label>\n                                <select id=\"cbGyroSens\">\n                                    <option value=\"0\">250</option><option value=\"1\">500</option>\n                                    <option value=\"2\">1000</option><option value=\"3\">2000</option>\n                                </select>\n                            </div>\n                            <div class=\"form-group\">\n                                <label for=\"sbGyroWeight\">Weighting (%)</label>\n                                <input type=\"number\" id=\"sbGyroWeight\" min=\"0\" max=\"100\" value=\"96\">\n                            </div>\n                            <div class=\"form-group\">\n                                <label for=\"sbGyroFilter\">Filter</label>\n                                <input type=\"number\" id=\"sbGyroFilter\" min=\"0\" max=\"6\" value=\"0\">\n                            </div>\n                        </div>\n                    </fieldset>\n\n                    <!-- LiPo (INA3221) -->\n                    <fieldset id=\"gbLipo\" class=\"toggleable\" style=\"display:none\">\n                        <legend><label><input type=\"checkbox\" id=\"chkLipo\"> LiPo (INA3221)</label></legend>\n                        <div class=\"fieldset-content\">\n                            <div class=\"form-group\">\n                                <label for=\"cbIna3221Filter\">Filter</label>\n                                <select id=\"cbIna3221Filter\">\n                                    <option value=\"0\">1</option><option value=\"1\">4</option>\n                                    <option value=\"2\">16</option><option value=\"3\">64</option>\n                                    <option value=\"4\">128</option><option value=\"5\">256</option>\n                                    <option value=\"6\">512</option><option value=\"7\">1024</option>\n                                </select>\n                            </div>\n                            <div class=\"form-group\">\n                                <label for=\"sbLipoCells\">Cells</label>\n                                <input type=\"number\" id=\"sbLipoCells\" min=\"1\" max=\"12\" value=\"3\">\n                            </div>\n                        </div>\n                    </fieldset>\n                </div>\n            </div>\n        </section>\n\n        <!-- Circuit Tab -->\n        <section id=\"tab-circuit\" class=\"tab-content\">\n            <div class=\"circuit-toolbar\">\n                <button id=\"btZoomIn\" class=\"btn\">Zoom +</button>\n                <button id=\"btZoomOut\" class=\"btn\">Zoom −</button>\n            </div>\n            <div class=\"circuit-container\" id=\"circuitContainer\">\n                <canvas id=\"circuitCanvas\" width=\"800\" height=\"600\"></canvas>\n            </div>\n        </section>\n\n        <!-- Debug Tab -->\n        <section id=\"tab-debug\" class=\"tab-content\">\n            <div class=\"debug-toolbar\">\n                <button id=\"btDebug\" class=\"btn\" disabled>Enable Log</button>\n                <button id=\"btClearDebug\" class=\"btn\">Clear</button>\n                <button id=\"btScroll\" class=\"btn\">No scroll</button>\n            </div>\n            <pre id=\"ptDebug\" class=\"debug-output\"></pre>\n        </section>\n    </main>\n\n    <footer>\n        <div class=\"footer-left\">\n            <button id=\"btOpenConfig\" class=\"btn btn-sm\">Open .cfg</button>\n            <button id=\"btSaveConfig\" class=\"btn btn-sm\">Save .cfg</button>\n            <button id=\"btDefaultConfig\" class=\"btn btn-sm btn-danger\" disabled>Default Config</button>\n        </div>\n        <div class=\"footer-right\">\n            <span id=\"statusBar\">Not connected</span>\n        </div>\n    </footer>\n\n    <div id=\"unsupportedBanner\" class=\"banner\" style=\"display:none\">\n        Your browser does not support Web Serial or WebUSB. Use Chrome or Edge (version 89+) on desktop, or Chrome on Android.\n    </div>\n\n    <script src=\"js/version.js\"></script>\n    <script src=\"js/config_struct.js\"></script>\n    <script src=\"js/serial.js\"></script>\n    <script src=\"js/circuit.js\"></script>\n    <script src=\"js/ui.js\"></script>\n    <script src=\"js/app.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "msrc_gui_web/js/app.js",
    "content": "/**\n * app.js — Main application entry point.\n *          Orchestrates serial communication, UI mapping, circuit diagram, and PWA.\n */\n\nconst serial = createConnection();\nlet currentConfig = null;\nlet debugAutoScroll = true;\n\ndocument.addEventListener('DOMContentLoaded', async () => {\n    /* Check for any supported transport */\n    if (!serial) {\n        $('unsupportedBanner').style.display = 'block';\n        $('btConnect').disabled = true;\n        return;\n    }\n\n    /* Show version and transport */\n    $('appVersion').textContent = 'v' + APP_VERSION;\n    const transport = serial instanceof SerialConnection ? 'Web Serial' : 'WebUSB';\n    console.log('MSRC Link v' + APP_VERSION + ' — ' + transport);\n\n    /* Preload circuit images */\n    await preloadCircuitImages();\n\n    /* Bind UI events */\n    bindUIEvents();\n    initCircuit();\n\n    /* Set initial visibility */\n    updateVisibility();\n    updateEscOptions();\n    updateCurrentSensorUI();\n    updateToggleables();\n    generateCircuit();\n\n    /* ── Connection ────────────────────────────────── */\n\n    $('btConnect').addEventListener('click', async () => {\n        if (serial.connected) {\n            await serial.disconnect();\n            setDisconnected();\n            return;\n        }\n        try {\n            await serial.requestPort();\n            await serial.connect();\n            setConnected();\n\n            /* Disable debug first, wait, then request config — same as Qt GUI */\n            await serial.disableDebug();\n            setStatus('Requesting config...');\n            setTimeout(async () => {\n                try { await serial.requestConfig(); }\n                catch (e) { setStatus('Error requesting config: ' + e.message); }\n            }, 2000);\n        } catch (e) {\n            setStatus('Connection failed: ' + e.message);\n        }\n    });\n\n    serial.onConfigReceived = (bytes) => {\n        try {\n            currentConfig = configDecode(bytes);\n            if (currentConfig.version > CONFIG_VERSION) {\n                setStatus('Config version mismatch! Board: ' + currentConfig.version + ', App: ' + CONFIG_VERSION);\n            } else if (currentConfig.version < CONFIG_VERSION) {\n                setStatus('Older config version, forcing current version.');\n                currentConfig.version = CONFIG_VERSION;\n            }\n            setUiFromConfig(currentConfig);\n            setStatus('Config loaded (v' + currentConfig.version + ', ' + bytes.length + ' bytes)');\n        } catch (e) {\n            setStatus('Error decoding config: ' + e.message);\n        }\n    };\n\n    serial.onDebugData = (text) => {\n        const el = $('ptDebug');\n        el.textContent += text;\n        if (debugAutoScroll) el.scrollTop = el.scrollHeight;\n    };\n\n    serial.onDisconnect = () => setDisconnected();\n\n    /* ── Update (write config) ─────────────────────── */\n\n    $('btUpdate').addEventListener('click', async () => {\n        if (!serial.connected) return;\n        try {\n            const cfg = getConfigFromUi();\n            const bytes = configEncode(cfg);\n            await serial.writeConfig(bytes);\n            setStatus('Config written. Reset RP2040 to apply.');\n        } catch (e) {\n            setStatus('Error writing config: ' + e.message);\n        }\n    });\n\n    /* ── Default Config ────────────────────────────── */\n\n    $('btDefaultConfig').addEventListener('click', async () => {\n        if (!serial.connected) return;\n        if (!confirm('Write default config to flash? This will overwrite current settings.')) return;\n        try {\n            await serial.writeDefaultConfig();\n            setStatus('Default config written. Reset RP2040.');\n        } catch (e) {\n            setStatus('Error: ' + e.message);\n        }\n    });\n\n    /* ── Tabs ──────────────────────────────────────── */\n\n    document.querySelectorAll('.tab').forEach(btn => {\n        btn.addEventListener('click', () => {\n            document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));\n            document.querySelectorAll('.tab-content').forEach(s => s.classList.remove('active'));\n            btn.classList.add('active');\n            $('tab-' + btn.dataset.tab).classList.add('active');\n            if (btn.dataset.tab === 'circuit') generateCircuit();\n        });\n    });\n\n    /* ── Debug ─────────────────────────────────────── */\n\n    $('btDebug').addEventListener('click', async () => {\n        if (!serial.connected) return;\n        if (serial.debugMode) {\n            await serial.disableDebug();\n            $('btDebug').textContent = 'Enable Log';\n            setStatus('Debug disabled');\n        } else {\n            await serial.enableDebug();\n            $('btDebug').textContent = 'Disable Log';\n            setStatus('Debug enabled');\n        }\n    });\n\n    $('btClearDebug').addEventListener('click', () => {\n        $('ptDebug').textContent = '';\n    });\n\n    $('btScroll').addEventListener('click', () => {\n        debugAutoScroll = !debugAutoScroll;\n        $('btScroll').textContent = debugAutoScroll ? 'No scroll' : 'Auto scroll';\n    });\n\n    /* ── File Open/Save ────────────────────────────── */\n\n    $('btOpenConfig').addEventListener('click', async () => {\n        const input = document.createElement('input');\n        input.type = 'file';\n        input.accept = '.cfg';\n        input.onchange = async () => {\n            const file = input.files[0];\n            if (!file) return;\n            const buf = await file.arrayBuffer();\n            const bytes = new Uint8Array(buf);\n            if (bytes.length >= CONFIG_SIZE) {\n                try {\n                    currentConfig = configDecode(bytes);\n                    setUiFromConfig(currentConfig);\n                    setStatus('Config loaded from file: ' + file.name);\n                } catch (e) {\n                    setStatus('Error reading file: ' + e.message);\n                }\n            } else {\n                setStatus('File too small (' + bytes.length + ' bytes, need ' + CONFIG_SIZE + ')');\n            }\n        };\n        input.click();\n    });\n\n    $('btSaveConfig').addEventListener('click', () => {\n        const cfg = getConfigFromUi();\n        const bytes = configEncode(cfg);\n        const blob = new Blob([bytes], { type: 'application/octet-stream' });\n        const a = document.createElement('a');\n        a.href = URL.createObjectURL(blob);\n        a.download = 'msrc_config.cfg';\n        a.click();\n        URL.revokeObjectURL(a.href);\n        setStatus('Config saved to msrc_config.cfg');\n    });\n\n    /* ── Register Service Worker ───────────────────── */\n    if ('serviceWorker' in navigator) {\n        navigator.serviceWorker.register('sw.js').catch(e =>\n            console.warn('SW registration failed:', e)\n        );\n    }\n});\n\n/* ── UI state helpers ────────────────────────────────── */\n\nfunction setConnected() {\n    $('btConnect').textContent = 'Disconnect';\n    $('btConnect').classList.add('btn-danger');\n    $('btConnect').classList.remove('btn-primary');\n    $('btUpdate').disabled = false;\n    $('btDefaultConfig').disabled = false;\n    $('btDebug').disabled = false;\n    setStatus('Connected');\n}\n\nfunction setDisconnected() {\n    $('btConnect').textContent = 'Connect';\n    $('btConnect').classList.remove('btn-danger');\n    $('btConnect').classList.add('btn-primary');\n    $('btUpdate').disabled = true;\n    $('btDefaultConfig').disabled = true;\n    $('btDebug').disabled = true;\n    if (serial.debugMode) {\n        serial.debugMode = false;\n        $('btDebug').textContent = 'Enable Log';\n    }\n    setStatus('Disconnected');\n}\n\nfunction setStatus(msg) {\n    $('statusBar').textContent = msg;\n}\n"
  },
  {
    "path": "msrc_gui_web/js/circuit.js",
    "content": "/**\n * circuit.js — Canvas-based circuit diagram that composites PNG overlay images,\n *              matching the Qt generateCircuit() logic from mainwindow.cpp.\n */\n\nconst CIRCUIT_IMAGES = {};\nconst CIRCUIT_IMAGE_LIST = [\n    'rp2040_zero.png', 'current_rp2040_zero.png', 'voltage_rp2040_zero.png',\n    'ntc_rp2040_zero.png', 'airspeed_rp2040_zero.png', 'gps_rp2040_zero.png',\n    'esc_rp2040_zero.png', 'pwm_rp2040_zero.png', 'castle_rp2040_zero.png',\n    'smart_esc.png', 'vario_rp2040_zero.png', 'fuel_pressure.png', 'fuel_meter.png',\n    'receiver_frsky_d_rp2040_zero.png', 'receiver_xbus_rp2040_zero.png',\n    'receiver_hitec_rp2040_zero.png', 'receiver_serial_rp2040_zero.png',\n    'clock_stretch_xbus_rp2040_zero.png'\n];\n\nlet circuitZoom = 1;\nlet imagesLoaded = false;\n\nfunction preloadCircuitImages() {\n    let loaded = 0;\n    return new Promise(resolve => {\n        CIRCUIT_IMAGE_LIST.forEach(name => {\n            const img = new Image();\n            img.onload = img.onerror = () => {\n                loaded++;\n                if (loaded >= CIRCUIT_IMAGE_LIST.length) {\n                    imagesLoaded = true;\n                    resolve();\n                }\n            };\n            img.src = 'res/' + name;\n            CIRCUIT_IMAGES[name] = img;\n        });\n    });\n}\n\nfunction generateCircuit() {\n    const canvas = document.getElementById('circuitCanvas');\n    if (!canvas || !imagesLoaded) return;\n    const ctx = canvas.getContext('2d');\n\n    const base = CIRCUIT_IMAGES['rp2040_zero.png'];\n    if (!base || !base.naturalWidth) return;\n\n    canvas.width = base.naturalWidth * circuitZoom;\n    canvas.height = base.naturalHeight * circuitZoom;\n\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n    ctx.save();\n    ctx.scale(circuitZoom, circuitZoom);\n\n    // Base image always drawn\n    ctx.drawImage(base, 0, 0);\n\n    const proto = parseInt(selectedValue(document.getElementById('cbReceiver')));\n    const isSerial = proto === RX_PROTOCOL.SERIAL_MONITOR;\n\n    if (!isSerial) {\n        // Analog sensors\n        if (document.getElementById('chkCurrent')?.checked)\n            drawOverlay(ctx, 'current_rp2040_zero.png');\n        if (document.getElementById('chkVoltage')?.checked)\n            drawOverlay(ctx, 'voltage_rp2040_zero.png');\n        if (document.getElementById('cbTemperature1')?.checked)\n            drawOverlay(ctx, 'ntc_rp2040_zero.png');\n        if (document.getElementById('chkAirspeed')?.checked)\n            drawOverlay(ctx, 'airspeed_rp2040_zero.png');\n        if (document.getElementById('chkGps')?.checked)\n            drawOverlay(ctx, 'gps_rp2040_zero.png');\n\n        // ESC\n        if (document.getElementById('chkEsc')?.checked) {\n            const escIdx = document.getElementById('cbEsc').selectedIndex;\n            const escProto = escIdx + 1;\n            if (escProto === ESC_PROTOCOL.ESC_PWM) {\n                drawOverlay(ctx, 'pwm_rp2040_zero.png');\n            } else if (escProto === ESC_PROTOCOL.ESC_CASTLE) {\n                drawOverlay(ctx, 'castle_rp2040_zero.png');\n            } else if (escProto === ESC_PROTOCOL.ESC_SMART) {\n                drawOverlay(ctx, 'smart_esc.png');\n            } else {\n                drawOverlay(ctx, 'esc_rp2040_zero.png');\n            }\n        }\n\n        // I2C sensors (vario, gyro, lipo all share same I2C bus image)\n        if (document.getElementById('chkAltitude')?.checked)\n            drawOverlay(ctx, 'vario_rp2040_zero.png');\n        if (document.getElementById('chkGyro')?.checked)\n            drawOverlay(ctx, 'vario_rp2040_zero.png');\n        if (document.getElementById('chkLipo')?.checked)\n            drawOverlay(ctx, 'vario_rp2040_zero.png');\n\n        // Fuel\n        if (document.getElementById('chkFuelPressure')?.checked)\n            drawOverlay(ctx, 'fuel_pressure.png');\n        if (document.getElementById('chkFuelmeter')?.checked)\n            drawOverlay(ctx, 'fuel_meter.png');\n\n        // Receiver overlay\n        if (proto === RX_PROTOCOL.RX_FRSKY_D || proto === RX_PROTOCOL.RX_CRSF || proto === RX_PROTOCOL.RX_JETIEX_SENSOR) {\n            drawOverlay(ctx, 'receiver_frsky_d_rp2040_zero.png');\n        } else if (proto === RX_PROTOCOL.RX_XBUS) {\n            drawOverlay(ctx, 'receiver_xbus_rp2040_zero.png');\n        } else if (proto === RX_PROTOCOL.RX_HITEC) {\n            drawOverlay(ctx, 'receiver_hitec_rp2040_zero.png');\n        } else {\n            drawOverlay(ctx, 'receiver_serial_rp2040_zero.png');\n        }\n\n        // XBUS clock stretch\n        if (proto === RX_PROTOCOL.RX_XBUS && document.getElementById('cbClockStretch')?.checked) {\n            drawOverlay(ctx, 'clock_stretch_xbus_rp2040_zero.png');\n        }\n    }\n\n    ctx.restore();\n}\n\nfunction drawOverlay(ctx, name) {\n    const img = CIRCUIT_IMAGES[name];\n    if (img && img.naturalWidth) {\n        ctx.drawImage(img, 0, 0);\n    }\n}\n\nfunction initCircuit() {\n    document.getElementById('btZoomIn')?.addEventListener('click', () => {\n        circuitZoom = Math.min(circuitZoom + 0.25, 3);\n        generateCircuit();\n    });\n    document.getElementById('btZoomOut')?.addEventListener('click', () => {\n        circuitZoom = Math.max(circuitZoom - 0.25, 0.5);\n        generateCircuit();\n    });\n}\n"
  },
  {
    "path": "msrc_gui_web/js/config_struct.js",
    "content": "/**\n * config_struct.js — Binary serialization of config_t matching the C struct layout in include/shared.h.\n *\n * The firmware sends/receives config_t as raw bytes. We must pack/unpack identical binary\n * layout here. ARM Cortex-M0+ uses little-endian. Struct is packed with natural alignment\n * (no explicit __attribute__((packed)) on config_t itself, so compiler padding applies).\n *\n * We manually lay out every field with its exact byte offset, matching GCC ARM output.\n */\n\nconst CONFIG_VERSION = 2;\n\n/* Enum mappings (same values as shared.h) */\nconst RX_PROTOCOL = {\n    RX_SMARTPORT: 0, RX_FRSKY_D: 1, RX_XBUS: 2, RX_SRXL: 3, RX_IBUS: 4,\n    RX_SBUS: 5, RX_MULTIPLEX: 6, RX_JETIEX: 7, RX_HITEC: 8, RX_SRXL2: 9,\n    SERIAL_MONITOR: 10, RX_CRSF: 11, RX_HOTT: 12, RX_SANWA: 13, RX_JR_PROPO: 14,\n    RX_FPORT: 15, RX_FBUS: 16, RX_GHST: 17, RX_JETIEX_SENSOR: 18\n};\n\nconst ESC_PROTOCOL = {\n    ESC_NONE: 0, ESC_HW3: 1, ESC_HW4: 2, ESC_PWM: 3, ESC_CASTLE: 4,\n    ESC_KONTRONIK: 5, ESC_APD_F: 6, ESC_APD_HV: 7, ESC_HW5: 8, ESC_SMART: 9,\n    ESC_OMP_M4: 10, ESC_ZTW: 11, ESC_OPENYGE: 12\n};\n\n/**\n * Build the field layout descriptor. Each entry is [name, type, byteOffset].\n * Types: 'u8','i8','bool','u16','i16','u32','i32','f32'\n * \n * We compute offsets by walking through the struct exactly as GCC ARM would lay it out\n * with default alignment rules (natural alignment for each type).\n */\nfunction buildLayout() {\n    const fields = [];\n    let offset = 0;\n\n    function align(off, alignment) {\n        return Math.ceil(off / alignment) * alignment;\n    }\n\n    function add(name, type) {\n        let size, al;\n        switch (type) {\n            case 'u8': case 'i8': case 'bool': size = 1; al = 1; break;\n            case 'u16': case 'i16': size = 2; al = 2; break;\n            case 'u32': case 'i32': case 'f32': size = 4; al = 4; break;\n            default: throw new Error('Unknown type: ' + type);\n        }\n        offset = align(offset, al);\n        fields.push({ name, type, offset, size });\n        offset += size;\n    }\n\n    // config_t fields in exact order from shared.h\n    add('version', 'u16');                       // 0x5101\n    add('rx_protocol', 'u8');                    // 0x5102\n    add('esc_protocol', 'u8');                   // 0x5103\n    add('enable_gps', 'bool');                   // 0x5104\n    // padding to align gps_baudrate (u32) to 4-byte boundary\n    add('gps_baudrate', 'u32');                  // 0x5105\n    add('enable_analog_voltage', 'bool');        // 0x5106\n    add('enable_analog_current', 'bool');        // 0x5107\n    add('enable_analog_ntc', 'bool');            // 0x5108\n    add('enable_analog_airspeed', 'bool');       // 0x5109\n    add('i2c_module', 'u8');                     // 0x510A\n    add('ina3221_filter', 'u8');                 // 0x510B\n    add('alpha_rpm', 'f32');                     // 0x510C\n    add('alpha_voltage', 'f32');                 // 0x510D\n    add('alpha_current', 'f32');                 // 0x510E\n    add('alpha_temperature', 'f32');             // 0x510F\n    add('alpha_vario', 'f32');                   // 0x5110\n    add('alpha_airspeed', 'f32');                // 0x5111\n    add('refresh_rate_rpm', 'u16');              // 0x5112\n    add('refresh_rate_voltage', 'u16');          // 0x5113\n    add('refresh_rate_current', 'u16');          // 0x5114\n    add('refresh_rate_temperature', 'u16');      // 0x5115\n    add('refresh_rate_gps', 'u16');              // 0x5116\n    add('refresh_rate_consumption', 'u16');      // 0x5117\n    add('refresh_rate_vario', 'u16');            // 0x5118\n    add('refresh_rate_airspeed', 'u16');         // 0x5119\n    add('refresh_rate_default', 'u16');          // 0x511A\n    add('analog_voltage_multiplier', 'f32');     // 0x511B\n    add('analog_current_type', 'u8');            // 0x511C\n    add('gpio_interval', 'u16');                 // 0x511D\n    add('analog_current_quiescent_voltage', 'f32'); // 0x511E\n    add('analog_current_multiplier', 'f32');     // 0x511F\n    add('analog_current_offset', 'f32');         // 0x5120\n    add('analog_current_autoffset', 'bool');     // 0x5121\n    add('pairOfPoles', 'u8');                    // 0x5122\n    add('mainTeeth', 'u8');                      // 0x5123\n    add('pinionTeeth', 'u8');                    // 0x5124\n    add('rpm_multiplier', 'f32');                // 0x5125\n    add('bmp280_filter', 'u8');                  // 0x5126\n    add('enable_pwm_out', 'bool');               // 0x5127\n    add('smartport_sensor_id', 'u8');            // 0x5128\n    add('smartport_data_id', 'u16');             // 0x5129\n    add('vario_auto_offset', 'bool');            // 0x512A\n    add('xbus_clock_stretch', 'bool');           // 0x512B\n    add('jeti_gps_speed_units_kmh', 'bool');     // 0x512C\n    add('enable_esc_hw4_init_delay', 'bool');    // 0x512D\n    add('esc_hw4_init_delay_duration', 'u16');   // 0x512E\n    add('esc_hw4_current_thresold', 'u8');       // 0x512F\n    add('esc_hw4_current_max', 'u16');           // 0x5130\n    add('esc_hw4_voltage_multiplier', 'f32');    // 0x5131\n    add('esc_hw4_current_multiplier', 'f32');    // 0x5132\n    add('ibus_alternative_coordinates', 'bool'); // 0x5133\n    add('debug', 'u8');                          // 0x5134\n    add('esc_hw4_is_manual_offset', 'bool');     // 0x5135\n    add('analog_rate', 'u8');                    // 0x5136\n    add('xbus_use_alternative_volt_temp', 'bool'); // 0x5137\n    add('gpio_mask', 'u8');                      // 0x5138\n    add('esc_hw4_offset', 'f32');                // 0x5139\n    add('serial_monitor_baudrate', 'u32');       // 0x513A\n    add('serial_monitor_stop_bits', 'u8');       // 0x513B\n    add('serial_monitor_parity', 'u8');          // 0x513C\n    add('serial_monitor_timeout_ms', 'u16');     // 0x513D\n    add('serial_monitor_inverted', 'bool');      // 0x513E\n    add('esc_hw4_auto_detect', 'bool');          // 0x514B\n    add('airspeed_vcc', 'i16');                  // 0x5140\n    add('fuel_flow_ml_per_pulse', 'f32');        // 0x5141\n    add('enable_fuel_flow', 'bool');             // 0x5142\n    add('xgzp68xxd_k', 'u16');                  // 0x5143\n    add('enable_fuel_pressure', 'u8');           // 0x5144\n    add('smart_esc_calc_consumption', 'bool');   // 0x5145\n    add('serial_monitor_gpio', 'u8');            // 0x5146\n    add('gps_rate', 'u8');                       // 0x5147\n    add('serial_monitor_format', 'u8');          // 0x5148\n    add('gps_protocol', 'u8');                   // 0x5149\n    add('sbus_battery_slot', 'bool');            // 0x514A\n    add('fport_inverted', 'bool');\n    add('fbus_inverted', 'bool');\n    add('airspeed_offset', 'i16');               // 0x513F\n    add('mpu6050_acc_scale', 'u8');              // 0x514C\n    add('mpu6050_gyro_scale', 'u8');             // 0x514D\n    add('mpu6050_gyro_weighting', 'u8');         // 0x514E\n    add('enable_gyro', 'bool');                  // 0x514F\n    add('enable_lipo', 'u8');                    // 0x5150\n    add('mpu6050_filter', 'u8');                 // 0x5151\n    add('lipo_cells', 'u8');                     // 0x5152\n    add('spare72', 'u8');\n    add('spare73', 'u16');\n    add('spare8', 'u32');\n    add('spare9', 'u32');\n    add('spare10', 'u32');\n    add('spare11', 'u32');\n    add('spare12', 'u32');\n    add('spare13', 'u32');\n    add('spare14', 'u32');\n    add('spare15', 'u32');\n    add('spare16', 'u32');\n    add('spare17', 'u32');\n    add('spare18', 'u32');\n    add('spare19', 'u32');\n    add('spare20', 'u32');\n\n    return { fields, totalSize: align(offset, 4) }; // struct is padded to 4-byte alignment at end\n}\n\nconst LAYOUT = buildLayout();\nconst CONFIG_SIZE = LAYOUT.totalSize;\n\n/**\n * Decode a config_t from a Uint8Array (little-endian).\n * @param {Uint8Array} bytes\n * @returns {Object} Config object with named fields.\n */\nfunction configDecode(bytes) {\n    if (bytes.length < CONFIG_SIZE) {\n        throw new Error(`Config buffer too small: ${bytes.length} < ${CONFIG_SIZE}`);\n    }\n    const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);\n    const cfg = {};\n    for (const f of LAYOUT.fields) {\n        switch (f.type) {\n            case 'u8':   cfg[f.name] = dv.getUint8(f.offset); break;\n            case 'i8':   cfg[f.name] = dv.getInt8(f.offset); break;\n            case 'bool': cfg[f.name] = dv.getUint8(f.offset) !== 0; break;\n            case 'u16':  cfg[f.name] = dv.getUint16(f.offset, true); break;\n            case 'i16':  cfg[f.name] = dv.getInt16(f.offset, true); break;\n            case 'u32':  cfg[f.name] = dv.getUint32(f.offset, true); break;\n            case 'i32':  cfg[f.name] = dv.getInt32(f.offset, true); break;\n            case 'f32':  cfg[f.name] = dv.getFloat32(f.offset, true); break;\n        }\n    }\n    return cfg;\n}\n\n/**\n * Encode a config object to Uint8Array (little-endian).\n * @param {Object} cfg\n * @returns {Uint8Array}\n */\nfunction configEncode(cfg) {\n    const buf = new ArrayBuffer(CONFIG_SIZE);\n    const dv = new DataView(buf);\n    // Zero-fill already done by ArrayBuffer constructor\n    for (const f of LAYOUT.fields) {\n        const v = cfg[f.name] ?? 0;\n        switch (f.type) {\n            case 'u8':   dv.setUint8(f.offset, v & 0xFF); break;\n            case 'i8':   dv.setInt8(f.offset, v); break;\n            case 'bool': dv.setUint8(f.offset, v ? 1 : 0); break;\n            case 'u16':  dv.setUint16(f.offset, v & 0xFFFF, true); break;\n            case 'i16':  dv.setInt16(f.offset, v, true); break;\n            case 'u32':  dv.setUint32(f.offset, v >>> 0, true); break;\n            case 'i32':  dv.setInt32(f.offset, v, true); break;\n            case 'f32':  dv.setFloat32(f.offset, v, true); break;\n        }\n    }\n    return new Uint8Array(buf);\n}\n\n/**\n * Create a default config object.\n */\nfunction configDefault() {\n    return {\n        version: CONFIG_VERSION,\n        rx_protocol: RX_PROTOCOL.RX_SMARTPORT,\n        esc_protocol: ESC_PROTOCOL.ESC_NONE,\n        enable_gps: false,\n        gps_baudrate: 9600,\n        enable_analog_voltage: false,\n        enable_analog_current: false,\n        enable_analog_ntc: false,\n        enable_analog_airspeed: false,\n        i2c_module: 0,\n        ina3221_filter: 1,\n        alpha_rpm: 2.0 / (1 + 1),\n        alpha_voltage: 2.0 / (1 + 1),\n        alpha_current: 2.0 / (1 + 1),\n        alpha_temperature: 2.0 / (1 + 1),\n        alpha_vario: 2.0 / (1 + 1),\n        alpha_airspeed: 2.0 / (1 + 1),\n        refresh_rate_rpm: 1000,\n        refresh_rate_voltage: 1000,\n        refresh_rate_current: 1000,\n        refresh_rate_temperature: 1000,\n        refresh_rate_gps: 1000,\n        refresh_rate_consumption: 1000,\n        refresh_rate_vario: 1000,\n        refresh_rate_airspeed: 1000,\n        refresh_rate_default: 1000,\n        analog_voltage_multiplier: 7.8,\n        analog_current_type: 0,\n        gpio_interval: 1000,\n        analog_current_quiescent_voltage: 0,\n        analog_current_multiplier: 1,\n        analog_current_offset: 0,\n        analog_current_autoffset: true,\n        pairOfPoles: 1,\n        mainTeeth: 1,\n        pinionTeeth: 1,\n        rpm_multiplier: 1,\n        bmp280_filter: 3,\n        enable_pwm_out: false,\n        smartport_sensor_id: 15,\n        smartport_data_id: 0x5100,\n        vario_auto_offset: false,\n        xbus_clock_stretch: false,\n        jeti_gps_speed_units_kmh: true,\n        enable_esc_hw4_init_delay: false,\n        esc_hw4_init_delay_duration: 10000,\n        esc_hw4_current_thresold: 10,\n        esc_hw4_current_max: 250,\n        esc_hw4_voltage_multiplier: 0.0088,\n        esc_hw4_current_multiplier: 0.3,\n        ibus_alternative_coordinates: false,\n        debug: 0,\n        esc_hw4_is_manual_offset: false,\n        analog_rate: 10,\n        xbus_use_alternative_volt_temp: false,\n        gpio_mask: 0,\n        esc_hw4_offset: 0,\n        serial_monitor_baudrate: 19200,\n        serial_monitor_stop_bits: 1,\n        serial_monitor_parity: 0,\n        serial_monitor_timeout_ms: 1,\n        serial_monitor_inverted: false,\n        esc_hw4_auto_detect: false,\n        airspeed_vcc: 500,\n        fuel_flow_ml_per_pulse: 0.01,\n        enable_fuel_flow: false,\n        xgzp68xxd_k: 64,\n        enable_fuel_pressure: 0,\n        smart_esc_calc_consumption: false,\n        serial_monitor_gpio: 5,\n        gps_rate: 1,\n        serial_monitor_format: 0,\n        gps_protocol: 0,\n        sbus_battery_slot: true,\n        fport_inverted: false,\n        fbus_inverted: false,\n        airspeed_offset: 0,\n        mpu6050_acc_scale: 0,\n        mpu6050_gyro_scale: 0,\n        mpu6050_gyro_weighting: 96,\n        enable_gyro: false,\n        enable_lipo: 0,\n        mpu6050_filter: 0,\n        lipo_cells: 3,\n        spare72: 0, spare73: 0,\n        spare8: 0, spare9: 0, spare10: 0, spare11: 0, spare12: 0,\n        spare13: 0, spare14: 0, spare15: 0, spare16: 0, spare17: 0,\n        spare18: 0, spare19: 0, spare20: 0,\n    };\n}\n"
  },
  {
    "path": "msrc_gui_web/js/serial.js",
    "content": "/**\n * serial.js — Web Serial API communication with MSRC board.\n *\n * Protocol: all messages start with header byte 0x30.\n * Commands:\n *   GUI→Board 0x30 0x30 + config_t bytes  → write config to flash\n *   GUI→Board 0x30 0x31                    → request current config\n *   Board→GUI 0x30 0x32 + config_t bytes   → config response\n *   GUI→Board 0x30 0x33                    → enable debug output\n *   GUI→Board 0x30 0x34                    → disable debug output\n *   GUI→Board 0x30 0x35                    → force write default config\n */\n\nconst HEADER = 0x30;\nconst CMD_WRITE_CONFIG = 0x30;\nconst CMD_REQUEST_CONFIG = 0x31;\nconst CMD_CONFIG_RESPONSE = 0x32;\nconst CMD_DEBUG_ON = 0x33;\nconst CMD_DEBUG_OFF = 0x34;\nconst CMD_DEFAULT_CONFIG = 0x35;\n\nclass SerialConnection {\n    constructor() {\n        this.port = null;\n        this.reader = null;\n        this.writer = null;\n        this.readableStreamClosed = null;\n        this.writableStreamClosed = null;\n        this.connected = false;\n        this.debugMode = false;\n        this._rxBuffer = new Uint8Array(0);\n        this._onConfigReceived = null;\n        this._onDebugData = null;\n        this._onDisconnect = null;\n        this._reading = false;\n    }\n\n    static isSupported() {\n        return 'serial' in navigator;\n    }\n\n    async requestPort() {\n        this.port = await navigator.serial.requestPort();\n        return this.port;\n    }\n\n    async connect() {\n        if (!this.port) throw new Error('No port selected');\n        await this.port.open({ baudRate: 115200, dataBits: 8, stopBits: 1, parity: 'none' });\n        this.connected = true;\n        this._startReading();\n    }\n\n    async disconnect() {\n        this._reading = false;\n        if (this.reader) {\n            try { await this.reader.cancel(); } catch (e) { /* ignore */ }\n            this.reader = null;\n        }\n        if (this.port) {\n            try { await this.port.close(); } catch (e) { /* ignore */ }\n        }\n        this.connected = false;\n        this._rxBuffer = new Uint8Array(0);\n    }\n\n    async _startReading() {\n        this._reading = true;\n        while (this._reading && this.port?.readable) {\n            this.reader = this.port.readable.getReader();\n            try {\n                while (this._reading) {\n                    const { value, done } = await this.reader.read();\n                    if (done) break;\n                    if (value) this._processBytes(value);\n                }\n            } catch (e) {\n                if (this._reading) {\n                    console.error('Serial read error:', e);\n                }\n            } finally {\n                try { this.reader.releaseLock(); } catch (e) { /* ignore */ }\n                this.reader = null;\n            }\n        }\n        if (this.connected) {\n            this.connected = false;\n            if (this._onDisconnect) this._onDisconnect();\n        }\n    }\n\n    _processBytes(chunk) {\n        if (this.debugMode) {\n            if (this._onDebugData) {\n                this._onDebugData(new TextDecoder().decode(chunk));\n            }\n            return;\n        }\n\n        // Accumulate bytes for config response\n        const merged = new Uint8Array(this._rxBuffer.length + chunk.length);\n        merged.set(this._rxBuffer);\n        merged.set(chunk, this._rxBuffer.length);\n        this._rxBuffer = merged;\n\n        // Config response = 0x30 0x32 + CONFIG_SIZE bytes\n        const expectedLen = CONFIG_SIZE + 2;\n        if (this._rxBuffer.length >= expectedLen) {\n            // Search for header\n            for (let i = 0; i <= this._rxBuffer.length - expectedLen; i++) {\n                if (this._rxBuffer[i] === HEADER && this._rxBuffer[i + 1] === CMD_CONFIG_RESPONSE) {\n                    const configBytes = this._rxBuffer.slice(i + 2, i + 2 + CONFIG_SIZE);\n                    this._rxBuffer = this._rxBuffer.slice(i + 2 + CONFIG_SIZE);\n                    if (this._onConfigReceived) {\n                        this._onConfigReceived(configBytes);\n                    }\n                    return;\n                }\n            }\n            // If buffer is way too large without finding pattern, trim\n            if (this._rxBuffer.length > expectedLen * 3) {\n                this._rxBuffer = this._rxBuffer.slice(-expectedLen);\n            }\n        }\n    }\n\n    async _write(bytes) {\n        if (!this.port?.writable) throw new Error('Port not writable');\n        const writer = this.port.writable.getWriter();\n        try {\n            await writer.write(new Uint8Array(bytes));\n        } finally {\n            writer.releaseLock();\n        }\n    }\n\n    async requestConfig() {\n        this._rxBuffer = new Uint8Array(0);\n        await this._write([HEADER, CMD_REQUEST_CONFIG]);\n    }\n\n    async writeConfig(configBytes) {\n        const packet = new Uint8Array(2 + configBytes.length);\n        packet[0] = HEADER;\n        packet[1] = CMD_WRITE_CONFIG;\n        packet.set(configBytes, 2);\n        await this._write(packet);\n    }\n\n    async enableDebug() {\n        this.debugMode = true;\n        this._rxBuffer = new Uint8Array(0);\n        await this._write([HEADER, CMD_DEBUG_ON]);\n    }\n\n    async disableDebug() {\n        this.debugMode = false;\n        await this._write([HEADER, CMD_DEBUG_OFF]);\n    }\n\n    async writeDefaultConfig() {\n        await this._write([HEADER, CMD_DEFAULT_CONFIG]);\n    }\n\n    set onConfigReceived(fn) { this._onConfigReceived = fn; }\n    set onDebugData(fn) { this._onDebugData = fn; }\n    set onDisconnect(fn) { this._onDisconnect = fn; }\n}\n\n/**\n * WebUSB fallback for platforms without Web Serial (e.g. Android).\n * Talks to the RP2040 USB CDC ACM device at the raw USB level.\n * Same public interface as SerialConnection.\n */\n\n// USB CDC Class requests\nconst CDC_SET_LINE_CODING = 0x20;\nconst CDC_SET_CONTROL_LINE_STATE = 0x22;\n\n// RP2040 default Pico SDK USB VID/PID\nconst PICO_VID = 0x2E8A;\nconst PICO_PID_CDC = 0x000A;\n\nclass WebUSBConnection {\n    constructor() {\n        this.device = null;\n        this.interfaceNumber = -1;\n        this.endpointIn = -1;\n        this.endpointOut = -1;\n        this.connected = false;\n        this.debugMode = false;\n        this._rxBuffer = new Uint8Array(0);\n        this._onConfigReceived = null;\n        this._onDebugData = null;\n        this._onDisconnect = null;\n        this._reading = false;\n    }\n\n    static isSupported() {\n        return 'usb' in navigator;\n    }\n\n    async requestPort() {\n        this.device = await navigator.usb.requestDevice({\n            filters: [{ vendorId: PICO_VID }]\n        });\n        return this.device;\n    }\n\n    async connect() {\n        if (!this.device) throw new Error('No device selected');\n        await this.device.open();\n\n        // Select configuration 1 if not already\n        if (this.device.configuration === null) {\n            await this.device.selectConfiguration(1);\n        }\n\n        // Find the CDC data interface (class 0x0A = CDC Data)\n        this._findEndpoints();\n\n        await this.device.claimInterface(this.interfaceNumber);\n\n        // SET_LINE_CODING: 115200 baud, 1 stop bit, no parity, 8 data bits\n        const lineCoding = new ArrayBuffer(7);\n        const dv = new DataView(lineCoding);\n        dv.setUint32(0, 115200, true); // dwDTERate\n        dv.setUint8(4, 0);             // bCharFormat: 1 stop bit\n        dv.setUint8(5, 0);             // bParityType: none\n        dv.setUint8(6, 8);             // bDataBits\n\n        // Control interface is typically interfaceNumber - 1\n        const controlInterface = this.interfaceNumber > 0 ? this.interfaceNumber - 1 : 0;\n        await this.device.controlTransferOut({\n            requestType: 'class',\n            recipient: 'interface',\n            request: CDC_SET_LINE_CODING,\n            value: 0,\n            index: controlInterface\n        }, new Uint8Array(lineCoding));\n\n        // SET_CONTROL_LINE_STATE: activate DTR\n        await this.device.controlTransferOut({\n            requestType: 'class',\n            recipient: 'interface',\n            request: CDC_SET_CONTROL_LINE_STATE,\n            value: 0x01, // DTR = 1\n            index: controlInterface\n        });\n\n        this.connected = true;\n        this._startReading();\n    }\n\n    _findEndpoints() {\n        for (const iface of this.device.configuration.interfaces) {\n            for (const alt of iface.alternates) {\n                // CDC Data class = 0x0A\n                if (alt.interfaceClass === 0x0A) {\n                    this.interfaceNumber = iface.interfaceNumber;\n                    for (const ep of alt.endpoints) {\n                        if (ep.direction === 'in') this.endpointIn = ep.endpointNumber;\n                        if (ep.direction === 'out') this.endpointOut = ep.endpointNumber;\n                    }\n                    return;\n                }\n            }\n        }\n        // Fallback: try any interface with bulk endpoints\n        for (const iface of this.device.configuration.interfaces) {\n            for (const alt of iface.alternates) {\n                let hasIn = false, hasOut = false;\n                for (const ep of alt.endpoints) {\n                    if (ep.type === 'bulk' && ep.direction === 'in') hasIn = true;\n                    if (ep.type === 'bulk' && ep.direction === 'out') hasOut = true;\n                }\n                if (hasIn && hasOut) {\n                    this.interfaceNumber = iface.interfaceNumber;\n                    for (const ep of alt.endpoints) {\n                        if (ep.type === 'bulk' && ep.direction === 'in') this.endpointIn = ep.endpointNumber;\n                        if (ep.type === 'bulk' && ep.direction === 'out') this.endpointOut = ep.endpointNumber;\n                    }\n                    return;\n                }\n            }\n        }\n        throw new Error('No CDC data interface found on device');\n    }\n\n    async disconnect() {\n        this._reading = false;\n        if (this.device) {\n            try {\n                await this.device.releaseInterface(this.interfaceNumber);\n                await this.device.close();\n            } catch (e) { /* ignore */ }\n        }\n        this.connected = false;\n        this._rxBuffer = new Uint8Array(0);\n    }\n\n    async _startReading() {\n        this._reading = true;\n        while (this._reading && this.device?.opened) {\n            try {\n                const result = await this.device.transferIn(this.endpointIn, 64);\n                if (result.status === 'ok' && result.data && result.data.byteLength > 0) {\n                    this._processBytes(new Uint8Array(result.data.buffer));\n                }\n                if (result.status === 'stall') {\n                    await this.device.clearHalt('in', this.endpointIn);\n                }\n            } catch (e) {\n                if (this._reading) {\n                    console.error('WebUSB read error:', e);\n                    break;\n                }\n            }\n        }\n        if (this.connected) {\n            this.connected = false;\n            if (this._onDisconnect) this._onDisconnect();\n        }\n    }\n\n    _processBytes(chunk) {\n        if (this.debugMode) {\n            if (this._onDebugData) {\n                this._onDebugData(new TextDecoder().decode(chunk));\n            }\n            return;\n        }\n        const merged = new Uint8Array(this._rxBuffer.length + chunk.length);\n        merged.set(this._rxBuffer);\n        merged.set(chunk, this._rxBuffer.length);\n        this._rxBuffer = merged;\n\n        const expectedLen = CONFIG_SIZE + 2;\n        if (this._rxBuffer.length >= expectedLen) {\n            for (let i = 0; i <= this._rxBuffer.length - expectedLen; i++) {\n                if (this._rxBuffer[i] === HEADER && this._rxBuffer[i + 1] === CMD_CONFIG_RESPONSE) {\n                    const configBytes = this._rxBuffer.slice(i + 2, i + 2 + CONFIG_SIZE);\n                    this._rxBuffer = this._rxBuffer.slice(i + 2 + CONFIG_SIZE);\n                    if (this._onConfigReceived) this._onConfigReceived(configBytes);\n                    return;\n                }\n            }\n            if (this._rxBuffer.length > expectedLen * 3) {\n                this._rxBuffer = this._rxBuffer.slice(-expectedLen);\n            }\n        }\n    }\n\n    async _write(bytes) {\n        if (!this.device?.opened) throw new Error('Device not open');\n        await this.device.transferOut(this.endpointOut, new Uint8Array(bytes));\n    }\n\n    async requestConfig() {\n        this._rxBuffer = new Uint8Array(0);\n        await this._write([HEADER, CMD_REQUEST_CONFIG]);\n    }\n\n    async writeConfig(configBytes) {\n        const packet = new Uint8Array(2 + configBytes.length);\n        packet[0] = HEADER;\n        packet[1] = CMD_WRITE_CONFIG;\n        packet.set(configBytes, 2);\n        await this._write(packet);\n    }\n\n    async enableDebug() {\n        this.debugMode = true;\n        this._rxBuffer = new Uint8Array(0);\n        await this._write([HEADER, CMD_DEBUG_ON]);\n    }\n\n    async disableDebug() {\n        this.debugMode = false;\n        await this._write([HEADER, CMD_DEBUG_OFF]);\n    }\n\n    async writeDefaultConfig() {\n        await this._write([HEADER, CMD_DEFAULT_CONFIG]);\n    }\n\n    set onConfigReceived(fn) { this._onConfigReceived = fn; }\n    set onDebugData(fn) { this._onDebugData = fn; }\n    set onDisconnect(fn) { this._onDisconnect = fn; }\n}\n\n/**\n * Factory: returns the best available connection for this platform.\n * - Desktop Chrome/Edge → SerialConnection (Web Serial API)\n * - Android Chrome → WebUSBConnection (WebUSB API)\n */\nfunction createConnection() {\n    if (SerialConnection.isSupported()) return new SerialConnection();\n    if (WebUSBConnection.isSupported()) return new WebUSBConnection();\n    return null;\n}\n"
  },
  {
    "path": "msrc_gui_web/js/ui.js",
    "content": "/**\n * ui.js — Bidirectional mapping between config_t and DOM widgets.\n *         Also handles protocol-dependent widget visibility.\n */\n\n/* ── Helpers ─────────────────────────────────────────── */\n\nfunction $(id) { return document.getElementById(id); }\n\nfunction alphaToElements(alpha) {\n    if (alpha <= 0) return 1;\n    return Math.round(2 / alpha - 1) || 1;\n}\nfunction elementsToAlpha(n) {\n    return 2.0 / (n + 1);\n}\n\nfunction selectByValue(sel, val) {\n    const s = typeof val === 'number' ? String(val) : val;\n    for (let i = 0; i < sel.options.length; i++) {\n        if (sel.options[i].value === s || sel.options[i].text === s) {\n            sel.selectedIndex = i;\n            return;\n        }\n    }\n}\n\nfunction selectedValue(sel) { return sel.options[sel.selectedIndex]?.value ?? ''; }\nfunction selectedText(sel) { return sel.options[sel.selectedIndex]?.text ?? ''; }\n\nfunction show(el) { if (el) el.style.display = ''; }\nfunction hide(el) { if (el) el.style.display = 'none'; }\nfunction showIf(el, cond) { if (el) el.style.display = cond ? '' : 'none'; }\n\n/* ── Config → UI ─────────────────────────────────────── */\n\nfunction setUiFromConfig(cfg) {\n    /* Receiver protocol (value is stored as userData in the option) */\n    selectByValue($('cbReceiver'), cfg.rx_protocol);\n\n    /* Serial Monitor */\n    selectByValue($('cbSerialMonitorGpio'), cfg.serial_monitor_gpio);\n    selectByValue($('cbBaudrate'), cfg.serial_monitor_baudrate);\n    $('cbParity').selectedIndex = cfg.serial_monitor_parity > 2 ? 0 : cfg.serial_monitor_parity;\n    $('cbStopbits').selectedIndex = Math.max(0, cfg.serial_monitor_stop_bits - 1);\n    $('sbTimeout').value = Math.min(cfg.serial_monitor_timeout_ms, 100);\n    $('cbInverted').checked = cfg.serial_monitor_inverted;\n    selectByValue($('cbSerialFormat'), cfg.serial_monitor_format);\n\n    /* ESC – combo index = esc_protocol - 1 */\n    if (cfg.esc_protocol === ESC_PROTOCOL.ESC_NONE) {\n        $('chkEsc').checked = false;\n    } else {\n        $('chkEsc').checked = true;\n        $('cbEsc').selectedIndex = cfg.esc_protocol - 1;\n    }\n\n    /* GPS */\n    $('chkGps').checked = cfg.enable_gps;\n    selectByValue($('cbGpsBaudrate'), cfg.gps_baudrate);\n    selectByValue($('cbGpsRate'), cfg.gps_rate);\n    $('cbGpsProtocol').selectedIndex = cfg.gps_protocol;\n\n    /* Analog rate */\n    $('sbAnalogRate').value = cfg.analog_rate;\n\n    /* Voltage */\n    $('chkVoltage').checked = cfg.enable_analog_voltage;\n    $('sbVoltage1Mult').value = cfg.analog_voltage_multiplier;\n    $('ckSbusBattery').checked = cfg.sbus_battery_slot;\n    $('ckSbusExtVolt').checked = !cfg.sbus_battery_slot;\n\n    /* Temperature */\n    $('cbTemperature1').checked = cfg.enable_analog_ntc;\n\n    /* Current – Hall vs Shunt */\n    $('chkCurrent').checked = cfg.enable_analog_current;\n    $('cbCurrentSensorType').selectedIndex = cfg.analog_current_type;\n    $('cbCurrentAutoOffset').checked = cfg.analog_current_autoffset;\n    $('sbQuiescentVoltage').value = cfg.analog_current_quiescent_voltage;\n    if (cfg.analog_current_type === 0) { /* Hall */\n        $('sbCurrentSens').value = cfg.analog_current_multiplier > 0 ? (1000 / cfg.analog_current_multiplier) : 1;\n    } else { /* Shunt */\n        $('sbAnalogCurrentMultiplier').value = cfg.analog_current_multiplier;\n    }\n\n    /* Airspeed */\n    $('chkAirspeed').checked = cfg.enable_analog_airspeed;\n    $('sbAirspeedVcc').value = cfg.airspeed_vcc / 100;\n    $('sbAirspeedOffset').value = cfg.airspeed_offset;\n\n    /* Vario / Altitude */\n    if (cfg.i2c_module === 0) { /* I2C_NONE */\n        $('chkAltitude').checked = false;\n    } else {\n        $('chkAltitude').checked = true;\n        $('cbBarometerType').selectedIndex = cfg.i2c_module - 1;\n    }\n    if (cfg.bmp280_filter >= 1 && cfg.bmp280_filter <= 3) {\n        $('cbAltitudeFilter').selectedIndex = cfg.bmp280_filter - 1;\n    }\n    $('cbVarioAutoOffset').checked = cfg.vario_auto_offset;\n\n    /* Refresh rates */\n    $('sbRpmRate').value = cfg.refresh_rate_rpm;\n    $('sbVoltageRate').value = cfg.refresh_rate_voltage;\n    $('sbCurrentRate').value = cfg.refresh_rate_current;\n    $('sbTemperatureRate').value = cfg.refresh_rate_temperature;\n    $('sbGpsRate').value = cfg.refresh_rate_gps;\n    $('sbConsumptionRate').value = cfg.refresh_rate_consumption;\n    $('sbVarioRate').value = cfg.refresh_rate_vario;\n    $('sbAirspeedRate').value = cfg.refresh_rate_airspeed;\n\n    /* Averaging – alpha → elements */\n    $('sbRpmAvg').value = alphaToElements(cfg.alpha_rpm);\n    $('sbVoltageAvg').value = alphaToElements(cfg.alpha_voltage);\n    $('sbCurrentAvg').value = alphaToElements(cfg.alpha_current);\n    $('sbTemperatureAvg').value = alphaToElements(cfg.alpha_temperature);\n    $('sbVarioAvg').value = alphaToElements(cfg.alpha_vario);\n    $('sbAirspeedAvg').value = alphaToElements(cfg.alpha_airspeed);\n\n    /* Analog voltage multiplier */\n    $('sbVoltage1Mult').value = cfg.analog_voltage_multiplier;\n\n    /* RPM multipliers */\n    $('sbPairOfPoles').value = cfg.pairOfPoles;\n    $('sbMainTeeth').value = cfg.mainTeeth;\n    $('sbPinionTeeth').value = cfg.pinionTeeth;\n\n    /* PWM out */\n    $('cbPwmOut').checked = cfg.enable_pwm_out;\n\n    /* SmartPort */\n    $('sbSensorId').value = cfg.smartport_sensor_id;\n\n    /* XBUS */\n    $('cbClockStretch').checked = cfg.xbus_clock_stretch;\n    $('cbAlternativePacket').checked = cfg.xbus_use_alternative_volt_temp;\n\n    /* IBUS */\n    $('cbAlternativeCoordinates').checked = cfg.ibus_alternative_coordinates;\n\n    /* Jeti Ex */\n    selectByValue($('cbSpeedUnitsGps'), cfg.jeti_gps_speed_units_kmh ? '1' : '0');\n\n    /* SBUS */\n    $('ckSbusBattery').checked = cfg.sbus_battery_slot;\n    $('ckSbusExtVolt').checked = !cfg.sbus_battery_slot;\n\n    /* FPort / FBUS */\n    $('cbFPortInverted').checked = cfg.fport_inverted;\n    $('cbFbusInverted').checked = cfg.fbus_inverted;\n\n    /* HW V4/V5 ESC parameters */\n    $('cbInitDelay').checked = cfg.enable_esc_hw4_init_delay;\n    $('cbEscAutoOffset').checked = !cfg.esc_hw4_is_manual_offset;\n    $('sbEscOffset').value = cfg.esc_hw4_offset;\n    $('sbVoltageMultiplier').value = Math.round(cfg.esc_hw4_voltage_multiplier * 100000);\n    $('sbCurrentMultiplier').value = Math.round(cfg.esc_hw4_current_multiplier * 100000);\n    $('cbHw4AutoDetect').checked = cfg.esc_hw4_auto_detect;\n\n    /* Smart ESC */\n    $('cbCalculateConsumption').checked = cfg.smart_esc_calc_consumption;\n\n    /* Fuel flow */\n    $('chkFuelmeter').checked = cfg.enable_fuel_flow;\n    $('sbMlPulse').value = cfg.fuel_flow_ml_per_pulse;\n\n    /* Fuel pressure */\n    $('chkFuelPressure').checked = cfg.enable_fuel_pressure;\n    selectByValue($('cbMaxPressure'), cfg.xgzp68xxd_k);\n\n    /* GPIOs bitmask */\n    $('cbGpio17').checked = !!(cfg.gpio_mask & 0x01);\n    $('cbGpio18').checked = !!(cfg.gpio_mask & 0x02);\n    $('cbGpio19').checked = !!(cfg.gpio_mask & 0x04);\n    $('cbGpio20').checked = !!(cfg.gpio_mask & 0x08);\n    $('cbGpio21').checked = !!(cfg.gpio_mask & 0x10);\n    $('cbGpio22').checked = !!(cfg.gpio_mask & 0x20);\n    $('sbGpioInterval').value = cfg.gpio_interval;\n\n    /* Gyro */\n    $('chkGyro').checked = cfg.enable_gyro;\n    $('cbGyroAccSens').selectedIndex = cfg.mpu6050_acc_scale;\n    $('cbGyroSens').selectedIndex = cfg.mpu6050_gyro_scale;\n    $('sbGyroWeight').value = cfg.mpu6050_gyro_weighting;\n    $('sbGyroFilter').value = cfg.mpu6050_filter;\n\n    /* LiPo INA3221 */\n    selectByValue($('cbIna3221Filter'), cfg.ina3221_filter);\n    $('sbLipoCells').value = cfg.lipo_cells;\n    $('chkLipo').checked = cfg.enable_lipo;\n\n    /* Trigger visibility update */\n    updateVisibility();\n    updateEscOptions();\n    updateCurrentSensorUI();\n    updateToggleables();\n}\n\n/* ── UI → Config ─────────────────────────────────────── */\n\nfunction getConfigFromUi() {\n    const cfg = configDefault();\n\n    cfg.version = CONFIG_VERSION;\n    cfg.rx_protocol = parseInt(selectedValue($('cbReceiver')));\n\n    /* Serial Monitor */\n    cfg.serial_monitor_baudrate = parseInt(selectedText($('cbBaudrate')));\n    cfg.serial_monitor_gpio = parseInt(selectedText($('cbSerialMonitorGpio')));\n    cfg.serial_monitor_stop_bits = parseInt(selectedText($('cbStopbits')));\n    cfg.serial_monitor_parity = parseInt(selectedValue($('cbParity')));\n    cfg.serial_monitor_timeout_ms = parseInt($('sbTimeout').value);\n    cfg.serial_monitor_inverted = $('cbInverted').checked;\n    cfg.serial_monitor_format = parseInt(selectedValue($('cbSerialFormat')));\n\n    /* ESC */\n    if ($('chkEsc').checked)\n        cfg.esc_protocol = $('cbEsc').selectedIndex + 1;\n    else\n        cfg.esc_protocol = ESC_PROTOCOL.ESC_NONE;\n\n    /* GPS */\n    cfg.enable_gps = $('chkGps').checked;\n    cfg.gps_baudrate = parseInt(selectedText($('cbGpsBaudrate')));\n    cfg.gps_rate = parseInt(selectedText($('cbGpsRate')));\n    cfg.gps_protocol = $('cbGpsProtocol').selectedIndex;\n\n    /* Voltage */\n    cfg.enable_analog_voltage = $('chkVoltage').checked;\n    cfg.analog_voltage_multiplier = parseFloat($('sbVoltage1Mult').value);\n    cfg.sbus_battery_slot = $('ckSbusBattery').checked;\n\n    /* Current */\n    cfg.enable_analog_current = $('chkCurrent').checked;\n    cfg.analog_current_type = parseInt(selectedValue($('cbCurrentSensorType')));\n    cfg.analog_current_quiescent_voltage = parseFloat($('sbQuiescentVoltage').value);\n\n    if (cfg.analog_current_type === 0) { /* Hall */\n        if ($('cbCurrentAutoOffset').checked) {\n            cfg.analog_current_autoffset = true;\n            cfg.analog_current_offset = 0;\n        } else {\n            cfg.analog_current_autoffset = false;\n            cfg.analog_current_offset = parseFloat($('sbQuiescentVoltage').value);\n        }\n        const sens = parseFloat($('sbCurrentSens').value);\n        cfg.analog_current_multiplier = sens > 0 ? 1000 / sens : 1;\n    } else { /* Shunt */\n        cfg.analog_current_autoffset = false;\n        cfg.analog_current_offset = 0;\n        cfg.analog_current_multiplier = parseFloat($('sbAnalogCurrentMultiplier').value);\n    }\n\n    /* Temperature */\n    cfg.enable_analog_ntc = $('cbTemperature1').checked;\n\n    /* Airspeed */\n    cfg.enable_analog_airspeed = $('chkAirspeed').checked;\n    cfg.airspeed_vcc = Math.round(parseFloat($('sbAirspeedVcc').value) * 100);\n    cfg.airspeed_offset = parseInt($('sbAirspeedOffset').value);\n\n    /* Vario */\n    if ($('chkAltitude').checked)\n        cfg.i2c_module = $('cbBarometerType').selectedIndex + 1;\n    else\n        cfg.i2c_module = 0;\n    cfg.bmp280_filter = $('cbAltitudeFilter').selectedIndex + 1;\n    cfg.vario_auto_offset = $('cbVarioAutoOffset').checked;\n\n    /* Refresh rates */\n    cfg.refresh_rate_rpm = parseInt($('sbRpmRate').value);\n    cfg.refresh_rate_voltage = parseInt($('sbVoltageRate').value);\n    cfg.refresh_rate_current = parseInt($('sbCurrentRate').value);\n    cfg.refresh_rate_temperature = parseInt($('sbTemperatureRate').value);\n    cfg.refresh_rate_gps = parseInt($('sbGpsRate').value);\n    cfg.refresh_rate_consumption = parseInt($('sbConsumptionRate').value);\n    cfg.refresh_rate_vario = parseInt($('sbVarioRate').value);\n    cfg.refresh_rate_airspeed = parseInt($('sbAirspeedRate').value);\n\n    /* Averaging – elements → alpha */\n    cfg.alpha_rpm = elementsToAlpha(parseInt($('sbRpmAvg').value));\n    cfg.alpha_voltage = elementsToAlpha(parseInt($('sbVoltageAvg').value));\n    cfg.alpha_current = elementsToAlpha(parseInt($('sbCurrentAvg').value));\n    cfg.alpha_temperature = elementsToAlpha(parseInt($('sbTemperatureAvg').value));\n    cfg.alpha_vario = elementsToAlpha(parseInt($('sbVarioAvg').value));\n    cfg.alpha_airspeed = elementsToAlpha(parseInt($('sbAirspeedAvg').value));\n\n    /* Analog rate */\n    cfg.analog_rate = parseInt($('sbAnalogRate').value);\n\n    /* RPM multipliers */\n    cfg.pairOfPoles = parseInt($('sbPairOfPoles').value);\n    cfg.mainTeeth = parseInt($('sbMainTeeth').value);\n    cfg.pinionTeeth = parseInt($('sbPinionTeeth').value);\n\n    /* PWM out */\n    cfg.enable_pwm_out = $('cbPwmOut').checked;\n\n    /* SmartPort */\n    cfg.smartport_sensor_id = parseInt($('sbSensorId').value);\n\n    /* XBUS */\n    cfg.xbus_clock_stretch = $('cbClockStretch').checked;\n    cfg.xbus_use_alternative_volt_temp = $('cbAlternativePacket').checked;\n\n    /* IBUS */\n    cfg.ibus_alternative_coordinates = $('cbAlternativeCoordinates').checked;\n\n    /* Jeti Ex */\n    cfg.jeti_gps_speed_units_kmh = selectedValue($('cbSpeedUnitsGps')) === '1';\n\n    /* FPort / FBUS */\n    cfg.fport_inverted = $('cbFPortInverted').checked;\n    cfg.fbus_inverted = $('cbFbusInverted').checked;\n\n    /* HW V4/V5 ESC parameters */\n    cfg.enable_esc_hw4_init_delay = $('cbInitDelay').checked;\n    cfg.esc_hw4_is_manual_offset = !$('cbEscAutoOffset').checked;\n    cfg.esc_hw4_offset = parseFloat($('sbEscOffset').value);\n    cfg.esc_hw4_voltage_multiplier = parseInt($('sbVoltageMultiplier').value) / 100000;\n    cfg.esc_hw4_current_multiplier = parseInt($('sbCurrentMultiplier').value) / 100000;\n    cfg.esc_hw4_auto_detect = $('cbHw4AutoDetect').checked;\n\n    /* Smart ESC */\n    cfg.smart_esc_calc_consumption = $('cbCalculateConsumption').checked;\n\n    /* Fuel flow */\n    cfg.enable_fuel_flow = $('chkFuelmeter').checked;\n    cfg.fuel_flow_ml_per_pulse = parseFloat($('sbMlPulse').value);\n\n    /* Fuel pressure */\n    cfg.enable_fuel_pressure = $('chkFuelPressure').checked ? 1 : 0;\n    cfg.xgzp68xxd_k = parseInt(selectedValue($('cbMaxPressure')));\n\n    /* GPIOs bitmask */\n    cfg.gpio_mask = 0;\n    if ($('cbGpio17').checked) cfg.gpio_mask |= 0x01;\n    if ($('cbGpio18').checked) cfg.gpio_mask |= 0x02;\n    if ($('cbGpio19').checked) cfg.gpio_mask |= 0x04;\n    if ($('cbGpio20').checked) cfg.gpio_mask |= 0x08;\n    if ($('cbGpio21').checked) cfg.gpio_mask |= 0x10;\n    if ($('cbGpio22').checked) cfg.gpio_mask |= 0x20;\n    cfg.gpio_interval = parseInt($('sbGpioInterval').value);\n\n    /* Gyro */\n    cfg.enable_gyro = $('chkGyro').checked;\n    cfg.mpu6050_acc_scale = parseInt(selectedValue($('cbGyroAccSens')));\n    cfg.mpu6050_gyro_scale = parseInt(selectedValue($('cbGyroSens')));\n    cfg.mpu6050_gyro_weighting = parseInt($('sbGyroWeight').value);\n    cfg.mpu6050_filter = parseInt($('sbGyroFilter').value);\n\n    /* LiPo INA3221 */\n    cfg.enable_lipo = $('chkLipo').checked ? 1 : 0;\n    cfg.ina3221_filter = parseInt(selectedValue($('cbIna3221Filter')));\n    cfg.lipo_cells = parseInt($('sbLipoCells').value);\n\n    /* Debug always off from GUI */\n    cfg.debug = 0;\n\n    return cfg;\n}\n\n/* ── Protocol-dependent visibility ───────────────────── */\n\nconst P = RX_PROTOCOL; // shorthand\n\nfunction updateVisibility() {\n    const proto = parseInt(selectedValue($('cbReceiver')));\n    const txt = selectedText($('cbReceiver'));\n    const isSerial = proto === P.SERIAL_MONITOR;\n\n    /* Serial Monitor fieldset */\n    showIf($('fsSerialMonitor'), isSerial);\n\n    /* Hide all sensors and averaging when Serial Monitor */\n    const sensorArea = document.querySelector('.config-column:nth-child(2)');\n    showIf(sensorArea, !isSerial);\n    showIf($('gbAverage'), !isSerial);\n\n    /* Refresh rates: SmartPort, Frsky D, FPort, FBUS */\n    const hasRates = [P.RX_SMARTPORT, P.RX_FRSKY_D, P.RX_FPORT, P.RX_FBUS].includes(proto);\n    showIf($('gbRate'), hasRates);\n\n    /* SmartPort sensor ID: SmartPort, FBUS */\n    showIf($('optSmartportSensorId'), proto === P.RX_SMARTPORT || proto === P.RX_FBUS);\n\n    /* Protocol-specific options */\n    showIf($('optXbusClockStretch'), proto === P.RX_XBUS);\n    showIf($('optIbus'), proto === P.RX_IBUS);\n    showIf($('optJeti'), proto === P.RX_JETIEX || proto === P.RX_JETIEX_SENSOR);\n    showIf($('optSbus'), proto === P.RX_SBUS);\n    showIf($('optFportInverted'), proto === P.RX_FPORT);\n    showIf($('optFbusInverted'), proto === P.RX_FBUS);\n\n    /* Fuel meter: SmartPort, JetiEx, JetiEx Sensor, XBUS, HOTT, FPort, FBUS */\n    const hasFuel = [P.RX_SMARTPORT, P.RX_JETIEX, P.RX_JETIEX_SENSOR, P.RX_XBUS, P.RX_HOTT, P.RX_FPORT, P.RX_FBUS].includes(proto);\n    showIf($('gbFuelmeter'), hasFuel && !isSerial);\n\n    /* Fuel pressure: SRXL, SRXL2, JetiEx, JetiEx Sensor, XBUS, HOTT */\n    const hasFuelPressure = [P.RX_SRXL, P.RX_SRXL2, P.RX_JETIEX, P.RX_JETIEX_SENSOR, P.RX_XBUS, P.RX_HOTT].includes(proto);\n    showIf($('gbFuelPressure'), hasFuelPressure && !isSerial);\n\n    /* GPIO: SmartPort, FPort, FBUS */\n    const hasGpio = [P.RX_SMARTPORT, P.RX_FPORT, P.RX_FBUS].includes(proto);\n    showIf($('gbGpio'), hasGpio && !isSerial);\n\n    /* Airspeed: hidden for Sanwa, GHST */\n    showIf($('gbAirspeed'), ![P.RX_SANWA, P.RX_GHST].includes(proto) && !isSerial);\n\n    /* Temperature: hidden for GHST */\n    showIf($('grpTemperature'), proto !== P.RX_GHST && !isSerial);\n\n    /* GPS, Current, Vario: hidden for Sanwa */\n    const sanwaHidden = proto === P.RX_SANWA;\n    showIf($('gbGps'), !sanwaHidden && !isSerial);\n    showIf($('gbCurrent'), !sanwaHidden && !isSerial);\n    showIf($('gbAltitude'), !sanwaHidden && !isSerial);\n\n    /* LiPo: CRSF, SmartPort, FPort, FBUS, HOTT, SRXL, SRXL2, JetiEx, JetiEx Sensor */\n    const hasLipo = [P.RX_CRSF, P.RX_SMARTPORT, P.RX_FPORT, P.RX_FBUS, P.RX_HOTT,\n                     P.RX_SRXL, P.RX_SRXL2, P.RX_JETIEX, P.RX_JETIEX_SENSOR].includes(proto);\n    showIf($('gbLipo'), hasLipo && !isSerial);\n\n    /* Averaging visibility per protocol */\n    const allAvg = !isSerial;\n    showIf($('grpRpmAvg'), allAvg && !([P.RX_GHST].includes(proto)));\n    showIf($('grpVoltageAvg'), allAvg);\n    showIf($('grpCurrentAvg'), allAvg && !([P.RX_SANWA].includes(proto)));\n    showIf($('grpTemperatureAvg'), allAvg && !([P.RX_GHST].includes(proto)));\n    showIf($('grpVarioAvg'), allAvg && !([P.RX_SANWA].includes(proto)));\n    showIf($('grpAirspeedAvg'), allAvg && !([P.RX_SANWA, P.RX_GHST].includes(proto)));\n\n    /* Sanwa: only RPM, Voltage, Temperature in avg */\n    if (proto === P.RX_SANWA) {\n        show($('grpRpmAvg'));\n        show($('grpVoltageAvg'));\n        show($('grpTemperatureAvg'));\n        hide($('grpCurrentAvg'));\n        hide($('grpVarioAvg'));\n        hide($('grpAirspeedAvg'));\n    }\n    /* GHST: only Voltage, Current, Vario in avg */\n    if (proto === P.RX_GHST) {\n        hide($('grpRpmAvg'));\n        show($('grpVoltageAvg'));\n        show($('grpCurrentAvg'));\n        hide($('grpTemperatureAvg'));\n        show($('grpVarioAvg'));\n        hide($('grpAirspeedAvg'));\n    }\n\n    /* Regenerate circuit diagram */\n    if (typeof generateCircuit === 'function') generateCircuit();\n}\n\n/* ── ESC-specific options ────────────────────────────── */\n\nfunction updateEscOptions() {\n    const idx = $('cbEsc').selectedIndex;\n    const escProto = $('chkEsc').checked ? idx + 1 : 0;\n\n    /* Smart ESC option */\n    showIf($('optSmartEsc'), escProto === ESC_PROTOCOL.ESC_SMART);\n\n    /* HW4/HW5 parameter block */\n    const isHw4or5 = escProto === ESC_PROTOCOL.ESC_HW4 || escProto === ESC_PROTOCOL.ESC_HW5;\n    showIf($('gbEscParameters'), isHw4or5);\n\n    /* HW4 Auto Detect + PWM Out */\n    showIf($('optHw4AutoDetect'), isHw4or5);\n\n    /* ESC Offset: shown when manual (autoOffset unchecked) */\n    showIf($('grpEscOffset'), isHw4or5 && !$('cbEscAutoOffset').checked);\n\n    /* RPM Multipliers: always visible when ESC enabled */\n    showIf($('gbRpmMultipliers'), $('chkEsc').checked);\n\n    /* GPS Rate: only visible when GPS protocol is UBLOX */\n    showIf($('grpGpsRate'), $('cbGpsProtocol').selectedIndex === 0);\n\n    /* Baro filter: only for BMP280 (index 0) */\n    showIf($('grpAltitudeFilter'), $('cbBarometerType').selectedIndex === 0);\n\n    if (typeof generateCircuit === 'function') generateCircuit();\n}\n\n/* ── Current sensor type UI toggle ───────────────────── */\n\nfunction updateCurrentSensorUI() {\n    const isHall = $('cbCurrentSensorType').selectedIndex === 0;\n    showIf($('optHallEffect'), isHall);\n    showIf($('optShuntResistor'), !isHall);\n\n    /* Quiescent Voltage: only if Hall and not Auto Offset */\n    showIf($('grpQuiescentVoltage'), isHall && !$('cbCurrentAutoOffset').checked);\n}\n\n/* ── Toggleable fieldsets (checkbox in legend) ───────── */\n\nfunction updateToggleables() {\n    document.querySelectorAll('.toggleable').forEach(fs => {\n        const chk = fs.querySelector('legend input[type=checkbox]');\n        const content = fs.querySelector('.fieldset-content');\n        if (chk && content) {\n            content.style.display = chk.checked ? '' : 'none';\n        }\n    });\n}\n\n/* ── Bind all event listeners for reactive UI ────────── */\n\nfunction bindUIEvents() {\n    $('cbReceiver').addEventListener('change', updateVisibility);\n    $('cbEsc').addEventListener('change', updateEscOptions);\n    $('chkEsc').addEventListener('change', () => { updateEscOptions(); updateToggleables(); });\n    $('cbEscAutoOffset').addEventListener('change', updateEscOptions);\n    $('cbCurrentSensorType').addEventListener('change', updateCurrentSensorUI);\n    $('cbCurrentAutoOffset').addEventListener('change', updateCurrentSensorUI);\n    $('cbBarometerType').addEventListener('change', updateEscOptions);\n    $('cbGpsProtocol').addEventListener('change', updateEscOptions);\n\n    /* Toggleable fieldsets */\n    document.querySelectorAll('.toggleable legend input[type=checkbox]').forEach(chk => {\n        chk.addEventListener('change', () => { updateToggleables(); if (typeof generateCircuit === 'function') generateCircuit(); });\n    });\n\n    /* Temperature checkbox triggers circuit redraw */\n    $('cbTemperature1').addEventListener('change', () => { if (typeof generateCircuit === 'function') generateCircuit(); });\n\n    /* Clock stretch triggers circuit redraw */\n    $('cbClockStretch').addEventListener('change', () => { if (typeof generateCircuit === 'function') generateCircuit(); });\n}\n"
  },
  {
    "path": "msrc_gui_web/js/version.js",
    "content": "const APP_VERSION = '1.0.1';\n"
  },
  {
    "path": "msrc_gui_web/manifest.json",
    "content": "{\n    \"name\": \"MSRC Link Web\",\n    \"short_name\": \"MSRC Link\",\n    \"description\": \"Web-based configuration tool for MSRC (Multi Sensor for RC) telemetry system\",\n    \"start_url\": \".\",\n    \"display\": \"standalone\",\n    \"background_color\": \"#1a1a2e\",\n    \"theme_color\": \"#1a1a2e\",\n    \"icons\": [\n        {\n            \"src\": \"res/msrc-192.png\",\n            \"sizes\": \"192x192\",\n            \"type\": \"image/png\",\n            \"purpose\": \"any\"\n        },\n        {\n            \"src\": \"res/msrc-512.png\",\n            \"sizes\": \"512x512\",\n            \"type\": \"image/png\",\n            \"purpose\": \"any\"\n        }\n    ]\n}\n"
  },
  {
    "path": "msrc_gui_web/sw.js",
    "content": "/**\n * sw.js — Service Worker for offline PWA support.\n * Caches all app assets on install, serves from cache first.\n */\n\nimportScripts('./js/version.js');\nconst CACHE_NAME = 'msrc-link-' + APP_VERSION;\n\nconst ASSETS = [\n    './',\n    './index.html',\n    './css/style.css',\n    './js/version.js',\n    './js/config_struct.js',\n    './js/serial.js',\n    './js/circuit.js',\n    './js/ui.js',\n    './js/app.js',\n    './manifest.json',\n    './res/msrc.png',\n    './res/msrc-192.png',\n    './res/msrc-512.png',\n    './res/rp2040_zero.png',\n    './res/current_rp2040_zero.png',\n    './res/voltage_rp2040_zero.png',\n    './res/ntc_rp2040_zero.png',\n    './res/airspeed_rp2040_zero.png',\n    './res/gps_rp2040_zero.png',\n    './res/esc_rp2040_zero.png',\n    './res/pwm_rp2040_zero.png',\n    './res/castle_rp2040_zero.png',\n    './res/smart_esc.png',\n    './res/vario_rp2040_zero.png',\n    './res/fuel_pressure.png',\n    './res/fuel_meter.png',\n    './res/receiver_frsky_d_rp2040_zero.png',\n    './res/receiver_xbus_rp2040_zero.png',\n    './res/receiver_hitec_rp2040_zero.png',\n    './res/receiver_serial_rp2040_zero.png',\n    './res/clock_stretch_xbus_rp2040_zero.png'\n];\n\nself.addEventListener('install', event => {\n    event.waitUntil(\n        caches.open(CACHE_NAME).then(cache => cache.addAll(ASSETS))\n    );\n    self.skipWaiting();\n});\n\nself.addEventListener('activate', event => {\n    event.waitUntil(\n        caches.keys().then(keys =>\n            Promise.all(keys.filter(k => k !== CACHE_NAME).map(k => caches.delete(k)))\n        )\n    );\n    self.clients.claim();\n});\n\nself.addEventListener('fetch', event => {\n    event.respondWith(\n        caches.match(event.request).then(cached => cached || fetch(event.request))\n    );\n});\n"
  },
  {
    "path": "msrc_gui_web/test/layout_test.html",
    "content": "<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Config Layout Test</title></head>\n<body><pre id=\"out\"></pre>\n<script src=\"../js/config_struct.js\"></script>\n<script>\nconst out = document.getElementById('out');\nconst checks = {\n    'version': 0, 'rx_protocol': 2, 'esc_protocol': 3, 'enable_gps': 4,\n    'gps_baudrate': 8, 'enable_analog_voltage': 12, 'alpha_rpm': 20,\n    'refresh_rate_rpm': 44, 'analog_voltage_multiplier': 64,\n    'analog_current_type': 68, 'gpio_interval': 70,\n    'analog_current_quiescent_voltage': 72, 'rpm_multiplier': 88,\n    'smartport_data_id': 96, 'esc_hw4_init_delay_duration': 102,\n    'esc_hw4_voltage_multiplier': 108, 'esc_hw4_offset': 124,\n    'serial_monitor_baudrate': 128, 'serial_monitor_timeout_ms': 134,\n    'airspeed_vcc': 138, 'fuel_flow_ml_per_pulse': 140,\n    'enable_fuel_flow': 144, 'airspeed_offset': 158,\n    'enable_gyro': 163, 'lipo_cells': 166, 'spare72': 167,\n    'spare73': 168, 'spare8': 172, 'spare20': 220\n};\nlet pass = 0, fail = 0;\nconst fieldMap = {};\nLAYOUT.fields.forEach(f => fieldMap[f.name] = f.offset);\nfor (const [name, expected] of Object.entries(checks)) {\n    const actual = fieldMap[name];\n    if (actual === expected) { pass++; }\n    else { fail++; out.textContent += `FAIL: ${name} expected ${expected} got ${actual}\\n`; }\n}\nout.textContent += `\\nTotal size: JS=${CONFIG_SIZE} expected=224 ${CONFIG_SIZE===224?'OK':'FAIL'}\\n`;\nout.textContent += `${pass} passed, ${fail} failed\\n`;\n</script></body></html>\n"
  },
  {
    "path": "msrc_gui_web/test/verify_layout.js",
    "content": "// Test: verifies JS config_t layout matches C++ offsets.\n// Run: docker run --rm -v $(pwd):/app -w /app node:20-alpine node test/verify_layout.js\nconst fs = require('fs');\n\nconst src = fs.readFileSync('js/config_struct.js', 'utf8');\nconst fn = new Function(src + '\\nreturn { CONFIG_VERSION, RX_PROTOCOL, ESC_PROTOCOL, LAYOUT, CONFIG_SIZE, configDecode, configEncode, configDefault };');\nconst { LAYOUT, CONFIG_SIZE, configDecode, configEncode, configDefault } = fn();\n\nconst checks = {\n    'version': 0, 'rx_protocol': 2, 'esc_protocol': 3, 'enable_gps': 4,\n    'gps_baudrate': 8, 'enable_analog_voltage': 12, 'alpha_rpm': 20,\n    'refresh_rate_rpm': 44, 'analog_voltage_multiplier': 64,\n    'analog_current_type': 68, 'gpio_interval': 70,\n    'analog_current_quiescent_voltage': 72, 'rpm_multiplier': 88,\n    'smartport_data_id': 96, 'esc_hw4_init_delay_duration': 102,\n    'esc_hw4_voltage_multiplier': 108, 'esc_hw4_offset': 124,\n    'serial_monitor_baudrate': 128, 'serial_monitor_timeout_ms': 134,\n    'airspeed_vcc': 138, 'fuel_flow_ml_per_pulse': 140,\n    'enable_fuel_flow': 144, 'airspeed_offset': 158,\n    'enable_gyro': 163, 'lipo_cells': 166, 'spare72': 167,\n    'spare73': 168, 'spare8': 172, 'spare20': 220\n};\n\nconst fm = {};\nLAYOUT.fields.forEach(function(f) { fm[f.name] = f.offset; });\n\nvar pass = 0, fail = 0;\nObject.keys(checks).forEach(function(name) {\n    var expected = checks[name];\n    var actual = fm[name];\n    if (actual === expected) pass++;\n    else { fail++; console.log('FAIL: ' + name + ' expected=' + expected + ' got=' + actual); }\n});\nconsole.log('CONFIG_SIZE=' + CONFIG_SIZE + ' expected=224 ' + (CONFIG_SIZE === 224 ? 'OK' : 'FAIL'));\nconsole.log(pass + ' passed, ' + fail + ' failed');\n\n// Roundtrip test\nvar cfg = configDefault();\nvar encoded = configEncode(cfg);\nvar decoded = configDecode(encoded);\nvar rt_pass = 0, rt_fail = 0;\nLAYOUT.fields.forEach(function(f) {\n    var orig = cfg[f.name] !== undefined ? cfg[f.name] : 0;\n    var got = decoded[f.name];\n    var origN = typeof orig === 'boolean' ? (orig ? 1 : 0) : orig;\n    var gotN = typeof got === 'boolean' ? (got ? 1 : 0) : got;\n    if (f.type === 'f32') {\n        if (Math.abs(origN - gotN) < 0.0001) rt_pass++;\n        else { rt_fail++; console.log('RT FAIL: ' + f.name + ' orig=' + origN + ' got=' + gotN); }\n    } else {\n        if (origN === gotN) rt_pass++;\n        else { rt_fail++; console.log('RT FAIL: ' + f.name + ' orig=' + origN + ' got=' + gotN); }\n    }\n});\nconsole.log('Roundtrip: ' + rt_pass + ' passed, ' + rt_fail + ' failed');\n"
  }
]