From d521dcfd8a2d3cf55a159952b4baf92c3734c54f Mon Sep 17 00:00:00 2001
From: mr_small_hui <small_yaicev@benis.com>
Date: Fri, 1 Nov 2024 02:34:54 +0100
Subject: [PATCH] Original meshtastic repo code

---
 .devcontainer/Dockerfile                      |  25 ++
 .devcontainer/devcontainer.json               |  25 ++
 .devcontainer/setup.sh                        |   3 +
 .github/ISSUE_TEMPLATE/Bug Report.yml         |  99 +++++
 .github/ISSUE_TEMPLATE/New Board.yml          |  46 ++
 .github/ISSUE_TEMPLATE/feature.yml            |  31 ++
 .github/actions/build-variant/action.yml      |  94 ++++
 .github/actions/setup-base/action.yml         |  49 +++
 .github/dependabot.yml                        |  26 ++
 .github/pull_request_template.md              |  15 +
 .github/workflows/build_esp32.yml             |  34 ++
 .github/workflows/build_esp32_c3.yml          |  35 ++
 .github/workflows/build_esp32_c6.yml          |  36 ++
 .github/workflows/build_esp32_s3.yml          |  34 ++
 .github/workflows/build_native.yml            |  85 ++++
 .github/workflows/build_nrf52.yml             |  28 ++
 .github/workflows/build_raspbian.yml          |  52 +++
 .github/workflows/build_raspbian_armv7l.yml   |  52 +++
 .github/workflows/build_rpi2040.yml           |  26 ++
 .github/workflows/build_stm32.yml             |  27 ++
 .github/workflows/generate-userprefs.yml      |  35 ++
 .github/workflows/main_matrix.yml             | 401 ++++++++++++++++++
 .github/workflows/nightly.yml                 |  19 +
 .github/workflows/package_amd64.yml           |  81 ++++
 .github/workflows/package_raspbian.yml        |  81 ++++
 .github/workflows/package_raspbian_armv7l.yml |  81 ++++
 .github/workflows/sec_sast_flawfinder.yml     |  41 ++
 .github/workflows/sec_sast_semgrep_cron.yml   |  43 ++
 .github/workflows/sec_sast_semgrep_pull.yml   |  25 ++
 .github/workflows/stale_bot.yml               |  21 +
 .github/workflows/tests.yml                   | 106 +++++
 .github/workflows/trunk-check.yml             |  22 +
 .github/workflows/update_protobufs.yml        |  34 ++
 .trunk/.gitignore                             |   9 +
 .trunk/configs/.bandit                        |   2 +
 .trunk/configs/.clang-format                  |   6 +
 .trunk/configs/.flake8                        |   3 +
 .trunk/configs/.hadolint.yaml                 |   4 +
 .trunk/configs/.isort.cfg                     |   2 +
 .trunk/configs/.markdownlint.yaml             |  10 +
 .trunk/configs/.shellcheckrc                  |  10 +
 .trunk/configs/.yamllint.yaml                 |  10 +
 .trunk/configs/ruff.toml                      |   5 +
 .trunk/configs/svgo.config.js                 |  14 +
 .trunk/trunk.yaml                             |  49 +++
 .vscode/settings.json                         |  11 +
 .vscode/tasks.json                            |  15 +
 README.md                                     |  18 +
 arch/esp32/esp32.ini                          |   2 +-
 platformio.ini                                |   6 +-
 src/gps/GPS.cpp                               |  17 +-
 src/gps/GPS.h                                 |   3 +-
 src/mesh/NodeDB.cpp                           |  32 +-
 src/mesh/RadioInterface.cpp                   |   2 +-
 src/mesh/Router.cpp                           |  19 +-
 src/modules/ExternalNotificationModule.cpp    | 110 ++---
 src/modules/ExternalNotificationModule.h      |   3 +-
 src/modules/NodeInfoModule.cpp                |   6 +
 src/modules/SerialModule.cpp                  |   2 +-
 src/platform/esp32/main-esp32.cpp             |   4 +
 variants/portduino/variant.h                  |   9 +-
 61 files changed, 2079 insertions(+), 116 deletions(-)
 create mode 100644 .devcontainer/Dockerfile
 create mode 100644 .devcontainer/devcontainer.json
 create mode 100644 .devcontainer/setup.sh
 create mode 100644 .github/ISSUE_TEMPLATE/Bug Report.yml
 create mode 100644 .github/ISSUE_TEMPLATE/New Board.yml
 create mode 100644 .github/ISSUE_TEMPLATE/feature.yml
 create mode 100644 .github/actions/build-variant/action.yml
 create mode 100644 .github/actions/setup-base/action.yml
 create mode 100644 .github/dependabot.yml
 create mode 100644 .github/pull_request_template.md
 create mode 100644 .github/workflows/build_esp32.yml
 create mode 100644 .github/workflows/build_esp32_c3.yml
 create mode 100644 .github/workflows/build_esp32_c6.yml
 create mode 100644 .github/workflows/build_esp32_s3.yml
 create mode 100644 .github/workflows/build_native.yml
 create mode 100644 .github/workflows/build_nrf52.yml
 create mode 100644 .github/workflows/build_raspbian.yml
 create mode 100644 .github/workflows/build_raspbian_armv7l.yml
 create mode 100644 .github/workflows/build_rpi2040.yml
 create mode 100644 .github/workflows/build_stm32.yml
 create mode 100644 .github/workflows/generate-userprefs.yml
 create mode 100644 .github/workflows/main_matrix.yml
 create mode 100644 .github/workflows/nightly.yml
 create mode 100644 .github/workflows/package_amd64.yml
 create mode 100644 .github/workflows/package_raspbian.yml
 create mode 100644 .github/workflows/package_raspbian_armv7l.yml
 create mode 100644 .github/workflows/sec_sast_flawfinder.yml
 create mode 100644 .github/workflows/sec_sast_semgrep_cron.yml
 create mode 100644 .github/workflows/sec_sast_semgrep_pull.yml
 create mode 100644 .github/workflows/stale_bot.yml
 create mode 100644 .github/workflows/tests.yml
 create mode 100644 .github/workflows/trunk-check.yml
 create mode 100644 .github/workflows/update_protobufs.yml
 create mode 100644 .trunk/.gitignore
 create mode 100644 .trunk/configs/.bandit
 create mode 100644 .trunk/configs/.clang-format
 create mode 100644 .trunk/configs/.flake8
 create mode 100644 .trunk/configs/.hadolint.yaml
 create mode 100644 .trunk/configs/.isort.cfg
 create mode 100644 .trunk/configs/.markdownlint.yaml
 create mode 100644 .trunk/configs/.shellcheckrc
 create mode 100644 .trunk/configs/.yamllint.yaml
 create mode 100644 .trunk/configs/ruff.toml
 create mode 100644 .trunk/configs/svgo.config.js
 create mode 100644 .trunk/trunk.yaml
 create mode 100644 .vscode/settings.json
 create mode 100644 .vscode/tasks.json
 create mode 100644 README.md

diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000..c215644
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,25 @@
+FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12
+
+# [Optional] Uncomment this section to install additional packages.
+RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
+    && apt-get -y install --no-install-recommends \
+    ca-certificates \
+    g++ \
+    git \
+    libbluetooth-dev \
+    libgpiod-dev \
+    liborcania-dev \
+    libssl-dev \
+    libulfius-dev \
+    libyaml-cpp-dev \
+    pipx \
+    pkg-config \
+    python3 \
+    python3-pip \
+    python3-venv \
+    python3-wheel \
+    wget \
+    zip \
+    && apt-get clean && rm -rf /var/lib/apt/lists/*
+
+RUN pipx install platformio==6.1.15
\ No newline at end of file
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..d83d052
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,25 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/cpp
+{
+  "name": "Meshtastic Firmware Dev",
+  "build": {
+    "dockerfile": "Dockerfile"
+  },
+  "features": {
+    "ghcr.io/devcontainers/features/python:1": {
+      "installTools": true,
+      "version": "latest"
+    }
+  },
+  "customizations": {
+    "vscode": {
+      "extensions": ["ms-vscode.cpptools", "platformio.platformio-ide"]
+    }
+  },
+
+  // Use 'forwardPorts' to make a list of ports inside the container available locally.
+  "forwardPorts": [4403],
+
+  // Run commands to prepare the container for use
+  "postCreateCommand": ".devcontainer/setup.sh"
+}
diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh
new file mode 100644
index 0000000..0b2665f
--- /dev/null
+++ b/.devcontainer/setup.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env sh
+
+git submodule update --init
diff --git a/.github/ISSUE_TEMPLATE/Bug Report.yml b/.github/ISSUE_TEMPLATE/Bug Report.yml
new file mode 100644
index 0000000..f2d2f65
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/Bug Report.yml	
@@ -0,0 +1,99 @@
+name: Bug Report
+description: File a bug report
+title: "[Bug]: "
+labels: ["bug", "triage"]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for taking the time to fill out this bug report!
+
+  - type: dropdown
+    id: category
+    attributes:
+      label: Category
+      description: How would you catagorize this issue?
+      multiple: true
+      options:
+        - Hardware Compatibility
+        - BLE
+        - Serial
+        - WiFi
+        - Other
+    validations:
+      required: true
+
+  - type: dropdown
+    id: hardware
+    attributes:
+      label: Hardware
+      description: What hardware are you encountering this issue on?
+      multiple: true
+      options:
+        - Not Applicable
+        - T-Beam
+        - T-Beam S3
+        - T-Beam 0.7
+        - T-Lora v1
+        - T-Lora v1.3
+        - T-Lora v2 1.6
+        - T-Deck
+        - T-Echo
+        - T-Watch
+        - Rak4631
+        - Rak11200
+        - Rak11310
+        - Heltec v1
+        - Heltec v2
+        - Heltec v2.1
+        - Heltec V3
+        - Heltec Wireless Paper
+        - Heltec Wireless Tracker
+        - Heltec Mesh Node T114
+        - Heltec Vision Master E213
+        - Heltec Vision Master E290
+        - Heltec Vision Master T190
+        - Nano G1
+        - Nano G1 Explorer
+        - Nano G2 Ultra
+        - Raspberry Pi Pico (W)
+        - Relay v1
+        - Relay v2
+        - Seeed Wio Tracker 1110
+        - Seeed Card Tracker T1000-E
+        - Station G1
+        - Station G2
+        - unPhone
+        - CanaryOne
+        - Chatter
+        - Linux Native
+        - DIY
+        - Other
+    validations:
+      required: true
+
+  - type: input
+    id: version
+    attributes:
+      label: Firmware Version
+      description: This can be found on the device's screen or via one of the apps.
+      placeholder: x.x.x.yyyyyyy
+    validations:
+      required: true
+
+  - type: textarea
+    id: body
+    attributes:
+      label: Description
+      description: Please provide details on what steps you performed for this to happen.
+    validations:
+      required: true
+
+  - type: textarea
+    id: logs
+    attributes:
+      label: Relevant log output
+      description: If you have any log output to help in diagnosing your bug, please provide it here.
+      render: Shell
+    validations:
+      required: false
diff --git a/.github/ISSUE_TEMPLATE/New Board.yml b/.github/ISSUE_TEMPLATE/New Board.yml
new file mode 100644
index 0000000..c71ed4b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/New Board.yml	
@@ -0,0 +1,46 @@
+name: New Board
+description: Request us to support new hardware
+title: "[Board]: "
+labels: ["enhancement", "triage"]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for requesting a new board, this will not gurantee that we will support it, but will be on our radar.
+
+  - type: dropdown
+    id: soc
+    attributes:
+      label: SOC
+      description: What SOC does your board have?
+      multiple: true
+      options:
+        - NRF52
+        - ESP32
+        - Other
+    validations:
+      required: true
+
+  - type: input
+    id: lora
+    attributes:
+      label: Lora IC
+      description: What LoRa IC does the board have?
+    validations:
+      required: true
+
+  - type: input
+    id: link
+    attributes:
+      label: Product Link
+      description: Where can we find this product?
+    validations:
+      required: true
+
+  - type: textarea
+    id: body
+    attributes:
+      label: Description
+      description: Please provide any further details you think we may need.
+    validations:
+      required: true
diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml
new file mode 100644
index 0000000..b50ccac
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature.yml
@@ -0,0 +1,31 @@
+name: Feature Request
+description: Request a new feature
+title: "[Feature Request]: "
+labels: ["enhancement"]
+body:
+  - type: markdown
+    attributes:
+      value: |
+        Thanks for your request this will not gurantee that we will implement it, but it will be reviewed.
+  - type: dropdown
+    id: soc
+    attributes:
+      label: Platform
+      description: What device platform will support your feature?
+      multiple: true
+      options:
+        - NRF52
+        - ESP32
+        - RP2040
+        - Linux Native
+        - Cross-Platform
+        - other
+    validations:
+      required: true
+  - type: textarea
+    id: body
+    attributes:
+      label: Description
+      description: Please provide details about your enhancement.
+    validations:
+      required: true
diff --git a/.github/actions/build-variant/action.yml b/.github/actions/build-variant/action.yml
new file mode 100644
index 0000000..80d2a56
--- /dev/null
+++ b/.github/actions/build-variant/action.yml
@@ -0,0 +1,94 @@
+name: Setup Build Variant Composite Action
+description: Variant build actions for Meshtastic Platform IO steps
+
+inputs:
+  board:
+    description: The board to build for
+    required: true
+  github_token:
+    description: GitHub token
+    required: true
+  build-script-path:
+    description: Path to the build script
+    required: true
+  remove-debug-flags:
+    description: A space separated list of files to remove debug flags from
+    required: false
+    default: ""
+  ota-firmware-source:
+    description: The OTA firmware file to pull
+    required: false
+    default: ""
+  ota-firmware-target:
+    description: The target path to store the OTA firmware file
+    required: false
+    default: ""
+  artifact-paths:
+    description: A newline separated list of paths to store as artifacts
+    required: false
+    default: ""
+  include-web-ui:
+    description: Include the web UI in the build
+    required: false
+    default: "false"
+  arch:
+    description: Processor arch name
+    required: true
+    default: "esp32"
+
+runs:
+  using: composite
+  steps:
+    - name: Build base
+      id: base
+      uses: ./.github/actions/setup-base
+
+    - name: Pull web ui
+      if: inputs.include-web-ui == 'true'
+      uses: dsaltares/fetch-gh-release-asset@master
+      with:
+        repo: meshtastic/web
+        file: build.tar
+        target: build.tar
+        token: ${{ inputs.github_token }}
+
+    - name: Unpack web ui
+      if: inputs.include-web-ui == 'true'
+      shell: bash
+      run: |
+        tar -xf build.tar -C data/static
+        rm build.tar
+
+    - name: Remove debug flags for release
+      shell: bash
+      if: inputs.remove-debug-flags != ''
+      run: |
+        for INI_FILE in ${{ inputs.remove-debug-flags }}; do
+          sed -i '/DDEBUG_HEAP/d' ${INI_FILE}
+        done
+
+    - name: Build ${{ inputs.board }}
+      shell: bash
+      run: ${{ inputs.build-script-path }} ${{ inputs.board }}
+
+    - name: Pull OTA Firmware
+      if: inputs.ota-firmware-source != '' &&  inputs.ota-firmware-target != ''
+      uses: dsaltares/fetch-gh-release-asset@master
+      with:
+        repo: meshtastic/firmware-ota
+        file: ${{ inputs.ota-firmware-source }}
+        target: ${{ inputs.ota-firmware-target }}
+        token: ${{ inputs.github_token }}
+
+    - name: Get release version string
+      shell: bash
+      run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+      id: version
+
+    - name: Store binaries as an artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: firmware-${{ inputs.arch }}-${{ inputs.board }}-${{ steps.version.outputs.version }}.zip
+        overwrite: true
+        path: |
+          ${{ inputs.artifact-paths }}
diff --git a/.github/actions/setup-base/action.yml b/.github/actions/setup-base/action.yml
new file mode 100644
index 0000000..c0f6c4e
--- /dev/null
+++ b/.github/actions/setup-base/action.yml
@@ -0,0 +1,49 @@
+name: "Setup Build Base Composite Action"
+description: "Base build actions for Meshtastic Platform IO steps"
+
+runs:
+  using: "composite"
+  steps:
+    - name: Checkout code
+      uses: actions/checkout@v4
+      with:
+        submodules: "recursive"
+        ref: ${{github.event.pull_request.head.ref}}
+        repository: ${{github.event.pull_request.head.repo.full_name}}
+
+    - name: Uncomment build epoch
+      shell: bash
+      run: |
+        sed -i 's/#-DBUILD_EPOCH=$UNIX_TIME/-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
+
+    - name: Install dependencies
+      shell: bash
+      run: |
+        sudo apt-get -y update --fix-missing
+        sudo apt-get install -y cppcheck libbluetooth-dev libgpiod-dev libyaml-cpp-dev
+
+    - name: Setup Python
+      uses: actions/setup-python@v5
+      with:
+        python-version: 3.x
+
+    # - name: Cache python libs
+    #   uses: actions/cache@v4
+    #   id: cache-pip # needed in if test
+    #   with:
+    #     path: ~/.cache/pip
+    #     key: ${{ runner.os }}-pip
+
+    - name: Upgrade python tools
+      shell: bash
+      run: |
+        python -m pip install --upgrade pip
+        pip install -U --no-build-isolation --no-cache-dir "setuptools<72"
+        pip install -U platformio adafruit-nrfutil --no-build-isolation
+        pip install -U poetry --no-build-isolation
+        pip install -U meshtastic --pre --no-build-isolation
+
+    - name: Upgrade platformio
+      shell: bash
+      run: |
+        pio upgrade
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..616c16c
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,26 @@
+version: 2
+updates:
+  - package-ecosystem: docker
+    directory: devcontainer
+    schedule:
+      interval: daily
+      time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+      timezone: US/Pacific
+  - package-ecosystem: docker
+    directory: /
+    schedule:
+      interval: daily
+      time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+      timezone: US/Pacific
+  - package-ecosystem: gitsubmodule
+    directory: /
+    schedule:
+      interval: daily
+      time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+      timezone: US/Pacific
+  - package-ecosystem: github-actions
+    directory: /.github/workflows
+    schedule:
+      interval: daily
+      time: "05:00" # trunk-ignore(yamllint/quoted-strings): required by dependabot syntax check
+      timezone: US/Pacific
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..6ccb4a1
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,15 @@
+### ❌ (Please delete all these tips and replace them with your text) ❌
+
+## Thank you for sending in a pull request, here's some tips to get started!
+
+- Before starting on some new big chunk of code, it it is optional but highly recommended to open an issue first
+  to say "Hey, I think this idea X should be implemented and I'm starting work on it. My general plan is Y, any feedback
+  is appreciated." This will allow other devs to potentially save you time by not accidentially duplicating work etc...
+- Please do not check in files that don't have real changes
+- Please do not reformat lines that you didn't have to change the code on
+- We recommend using the [Visual Studio Code](https://platformio.org/install/ide?install=vscode) editor along with the ['Trunk Check' extension](https://marketplace.visualstudio.com/items?itemName=trunk.io) (In beta for windows, WSL2 for the linux version),
+  because it automatically follows our indentation rules and its auto reformatting will not cause spurious changes to lines.
+- If your PR fixes a bug, mention "fixes #bugnum" somewhere in your pull request description.
+- If your other co-developers have comments on your PR please tweak as needed.
+- Please also enable "Allow edits by maintainers".
+- If your PR gets accepted you can request a "Contributor" role in the Meshtastic Discord
diff --git a/.github/workflows/build_esp32.yml b/.github/workflows/build_esp32.yml
new file mode 100644
index 0000000..7d069e3
--- /dev/null
+++ b/.github/workflows/build_esp32.yml
@@ -0,0 +1,34 @@
+name: Build ESP32
+
+on:
+  workflow_call:
+    inputs:
+      board:
+        required: true
+        type: string
+
+jobs:
+  build-esp32:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Build ESP32
+        id: build
+        uses: ./.github/actions/build-variant
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          board: ${{ inputs.board }}
+          remove-debug-flags: >-
+            ./arch/esp32/esp32.ini
+            ./arch/esp32/esp32s2.ini
+            ./arch/esp32/esp32s3.ini
+            ./arch/esp32/esp32c3.ini
+          build-script-path: bin/build-esp32.sh
+          ota-firmware-source: firmware.bin
+          ota-firmware-target: release/bleota.bin
+          artifact-paths: |
+            release/*.bin
+            release/*.elf
+          include-web-ui: true
+          arch: esp32
diff --git a/.github/workflows/build_esp32_c3.yml b/.github/workflows/build_esp32_c3.yml
new file mode 100644
index 0000000..5234dbe
--- /dev/null
+++ b/.github/workflows/build_esp32_c3.yml
@@ -0,0 +1,35 @@
+name: Build ESP32-C3
+
+on:
+  workflow_call:
+    inputs:
+      board:
+        required: true
+        type: string
+
+permissions: read-all
+
+jobs:
+  build-esp32-c3:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Build ESP32-C3
+        id: build
+        uses: ./.github/actions/build-variant
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          board: ${{ inputs.board }}
+          remove-debug-flags: >-
+            ./arch/esp32/esp32.ini
+            ./arch/esp32/esp32s2.ini
+            ./arch/esp32/esp32s3.ini
+            ./arch/esp32/esp32c3.ini
+          build-script-path: bin/build-esp32.sh
+          ota-firmware-source: firmware-c3.bin
+          ota-firmware-target: release/bleota-c3.bin
+          artifact-paths: |
+            release/*.bin
+            release/*.elf
+          arch: esp32c3
diff --git a/.github/workflows/build_esp32_c6.yml b/.github/workflows/build_esp32_c6.yml
new file mode 100644
index 0000000..66f2764
--- /dev/null
+++ b/.github/workflows/build_esp32_c6.yml
@@ -0,0 +1,36 @@
+name: Build ESP32-C6
+
+on:
+  workflow_call:
+    inputs:
+      board:
+        required: true
+        type: string
+
+permissions: read-all
+
+jobs:
+  build-esp32-c6:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Build ESP32-C6
+        id: build
+        uses: ./.github/actions/build-variant
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          board: ${{ inputs.board }}
+          remove-debug-flags: >-
+            ./arch/esp32/esp32.ini
+            ./arch/esp32/esp32s2.ini
+            ./arch/esp32/esp32s3.ini
+            ./arch/esp32/esp32c3.ini
+            ./arch/esp32/esp32c6.ini
+          build-script-path: bin/build-esp32.sh
+          ota-firmware-source: firmware-c3.bin
+          ota-firmware-target: release/bleota-c3.bin
+          artifact-paths: |
+            release/*.bin
+            release/*.elf
+          arch: esp32c6
diff --git a/.github/workflows/build_esp32_s3.yml b/.github/workflows/build_esp32_s3.yml
new file mode 100644
index 0000000..554b37c
--- /dev/null
+++ b/.github/workflows/build_esp32_s3.yml
@@ -0,0 +1,34 @@
+name: Build ESP32-S3
+
+on:
+  workflow_call:
+    inputs:
+      board:
+        required: true
+        type: string
+
+jobs:
+  build-esp32-s3:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Build ESP32-S3
+        id: build
+        uses: ./.github/actions/build-variant
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          board: ${{ inputs.board }}
+          remove-debug-flags: >-
+            ./arch/esp32/esp32.ini
+            ./arch/esp32/esp32s2.ini
+            ./arch/esp32/esp32s3.ini
+            ./arch/esp32/esp32c3.ini
+          build-script-path: bin/build-esp32.sh
+          ota-firmware-source: firmware-s3.bin
+          ota-firmware-target: release/bleota-s3.bin
+          artifact-paths: |
+            release/*.bin
+            release/*.elf
+          include-web-ui: true
+          arch: esp32s3
diff --git a/.github/workflows/build_native.yml b/.github/workflows/build_native.yml
new file mode 100644
index 0000000..1fb44a7
--- /dev/null
+++ b/.github/workflows/build_native.yml
@@ -0,0 +1,85 @@
+name: Build Native
+
+on: workflow_call
+
+permissions:
+  contents: write
+  packages: write
+
+jobs:
+  build-native:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Install libs needed for native build
+        shell: bash
+        run: |
+          sudo apt-get update --fix-missing
+          sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - name: Upgrade python tools
+        shell: bash
+        run: |
+          python -m pip install --upgrade pip
+          pip install -U platformio adafruit-nrfutil
+          pip install -U meshtastic --pre
+
+      - name: Upgrade platformio
+        shell: bash
+        run: |
+          pio upgrade
+
+      - name: Build Native
+        run: bin/build-native.sh
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Store binaries as an artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-native-${{ steps.version.outputs.version }}.zip
+          overwrite: true
+          path: |
+            release/meshtasticd_linux_x86_64
+            bin/config-dist.yaml
+
+      - name: Docker login
+        if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+        uses: docker/login-action@v3
+        continue-on-error: true # FIXME: Failing docker login auth
+        with:
+          username: meshtastic
+          password: ${{ secrets.DOCKER_FIRMWARE_TOKEN }}
+
+      - name: Docker setup
+        if: ${{ github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+        continue-on-error: true # FIXME: Failing docker login auth
+        uses: docker/setup-buildx-action@v3
+
+      - name: Docker build and push tagged versions
+        if: ${{ github.event_name == 'workflow_dispatch' }}
+        continue-on-error: true # FIXME: Failing docker login auth
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          file: ./Dockerfile
+          push: true
+          tags: meshtastic/device-simulator:${{ steps.version.outputs.version }}
+
+      - name: Docker build and push
+        if: ${{ github.ref == 'refs/heads/master' && github.event_name != 'pull_request_target' && github.event_name != 'pull_request' }}
+        continue-on-error: true # FIXME: Failing docker login auth
+        uses: docker/build-push-action@v6
+        with:
+          context: .
+          file: ./Dockerfile
+          push: true
+          tags: meshtastic/device-simulator:latest
diff --git a/.github/workflows/build_nrf52.yml b/.github/workflows/build_nrf52.yml
new file mode 100644
index 0000000..ce26838
--- /dev/null
+++ b/.github/workflows/build_nrf52.yml
@@ -0,0 +1,28 @@
+name: Build NRF52
+
+on:
+  workflow_call:
+    inputs:
+      board:
+        required: true
+        type: string
+
+jobs:
+  build-nrf52:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Build NRF52
+        id: build
+        uses: ./.github/actions/build-variant
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          board: ${{ inputs.board }}
+          build-script-path: bin/build-nrf52.sh
+          artifact-paths: |
+            release/*.hex
+            release/*.uf2
+            release/*.elf
+            release/*.zip
+          arch: nrf52840
diff --git a/.github/workflows/build_raspbian.yml b/.github/workflows/build_raspbian.yml
new file mode 100644
index 0000000..1fd8fad
--- /dev/null
+++ b/.github/workflows/build_raspbian.yml
@@ -0,0 +1,52 @@
+name: Build Raspbian
+
+on: workflow_call
+
+permissions:
+  contents: write
+  packages: write
+
+jobs:
+  build-raspbian:
+    runs-on: [self-hosted, linux, ARM64]
+    steps:
+      - name: Install libbluetooth
+        shell: bash
+        run: |
+          apt-get update -y --fix-missing
+          apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - name: Upgrade python tools
+        shell: bash
+        run: |
+          python -m pip install --upgrade pip
+          pip install -U platformio adafruit-nrfutil
+          pip install -U meshtastic --pre
+
+      - name: Upgrade platformio
+        shell: bash
+        run: |
+          pio upgrade
+
+      - name: Build Raspbian
+        run: bin/build-native.sh
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Store binaries as an artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
+          overwrite: true
+          path: |
+            release/meshtasticd_linux_aarch64
+            bin/config-dist.yaml
diff --git a/.github/workflows/build_raspbian_armv7l.yml b/.github/workflows/build_raspbian_armv7l.yml
new file mode 100644
index 0000000..39b297d
--- /dev/null
+++ b/.github/workflows/build_raspbian_armv7l.yml
@@ -0,0 +1,52 @@
+name: Build Raspbian Arm
+
+on: workflow_call
+
+permissions:
+  contents: write
+  packages: write
+
+jobs:
+  build-raspbian-armv7l:
+    runs-on: [self-hosted, linux, ARM]
+    steps:
+      - name: Install libbluetooth
+        shell: bash
+        run: |
+          apt-get update -y --fix-missing
+          apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - name: Upgrade python tools
+        shell: bash
+        run: |
+          python -m pip install --upgrade pip
+          pip install -U platformio adafruit-nrfutil
+          pip install -U meshtastic --pre
+
+      - name: Upgrade platformio
+        shell: bash
+        run: |
+          pio upgrade
+
+      - name: Build Raspbian
+        run: bin/build-native.sh
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Store binaries as an artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip
+          overwrite: true
+          path: |
+            release/meshtasticd_linux_armv7l
+            bin/config-dist.yaml
diff --git a/.github/workflows/build_rpi2040.yml b/.github/workflows/build_rpi2040.yml
new file mode 100644
index 0000000..492a1f0
--- /dev/null
+++ b/.github/workflows/build_rpi2040.yml
@@ -0,0 +1,26 @@
+name: Build RPI2040
+
+on:
+  workflow_call:
+    inputs:
+      board:
+        required: true
+        type: string
+
+jobs:
+  build-rpi2040:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Build Raspberry Pi 2040
+        id: build
+        uses: ./.github/actions/build-variant
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          board: ${{ inputs.board }}
+          build-script-path: bin/build-rpi2040.sh
+          artifact-paths: |
+            release/*.uf2
+            release/*.elf
+          arch: rp2040
diff --git a/.github/workflows/build_stm32.yml b/.github/workflows/build_stm32.yml
new file mode 100644
index 0000000..b463bab
--- /dev/null
+++ b/.github/workflows/build_stm32.yml
@@ -0,0 +1,27 @@
+name: Build STM32
+
+on:
+  workflow_call:
+    inputs:
+      board:
+        required: true
+        type: string
+
+jobs:
+  build-stm32:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: Build STM32WL
+        id: build
+        uses: ./.github/actions/build-variant
+        with:
+          github_token: ${{ secrets.GITHUB_TOKEN }}
+          board: ${{ inputs.board }}
+          build-script-path: bin/build-stm32.sh
+          artifact-paths: |
+            release/*.hex
+            release/*.bin
+            release/*.elf
+          arch: stm32
diff --git a/.github/workflows/generate-userprefs.yml b/.github/workflows/generate-userprefs.yml
new file mode 100644
index 0000000..10dd1ff
--- /dev/null
+++ b/.github/workflows/generate-userprefs.yml
@@ -0,0 +1,35 @@
+name: Generate UsersPrefs JSON manifest
+
+on:
+  push:
+    paths:
+      - userPrefs.h
+    branches:
+      - master
+
+jobs:
+  generate-userprefs:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      - name: Install Clang
+        run: sudo apt-get install -y clang
+
+      - name: Install trunk
+        run: curl https://get.trunk.io -fsSL | bash
+
+      - name: Generate userPrefs.jsom
+        run: python3 ./bin/build-userprefs-json.py
+
+      - name: Trunk format json
+        run: trunk format userPrefs.json
+
+      - name: Commit userPrefs.json
+        run: |
+          git config --global user.email "actions@github.com"
+          git config --global user.name "GitHub Actions"
+          git add userPrefs.json
+          git commit -m "Update userPrefs.json"
+          git push
diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml
new file mode 100644
index 0000000..0853df1
--- /dev/null
+++ b/.github/workflows/main_matrix.yml
@@ -0,0 +1,401 @@
+name: CI
+concurrency:
+  group: ci-${{ github.head_ref || github.run_id }}
+  cancel-in-progress: true
+on:
+  # # Triggers the workflow on push but only for the master branch
+  push:
+    branches: [master, develop]
+    paths-ignore:
+      - "**.md"
+      - version.properties
+
+  # Note: This is different from "pull_request". Need to specify ref when doing checkouts.
+  pull_request_target:
+    branches: [master, develop]
+    paths-ignore:
+      - "**.md"
+      #- "**.yml"
+
+  workflow_dispatch:
+
+jobs:
+  setup:
+    strategy:
+      fail-fast: false
+      matrix:
+        arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32, check]
+    runs-on: ubuntu-latest
+    steps:
+      - id: checkout
+        uses: actions/checkout@v4
+        name: Checkout base
+      - id: jsonStep
+        run: |
+          if [[ "${{ github.head_ref }}" == "" ]]; then
+            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}})
+          else  
+            TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} quick)
+          fi
+          echo "Name: ${{ github.ref_name }} Base: ${{ github.base_ref }} Head: ${{ github.head_ref }} Ref: ${{ github.ref }} Targets: $TARGETS"
+          echo "${{matrix.arch}}=$(jq -cn --argjson environments "$TARGETS" '{board: $environments}')" >> $GITHUB_OUTPUT
+    outputs:
+      esp32: ${{ steps.jsonStep.outputs.esp32 }}
+      esp32s3: ${{ steps.jsonStep.outputs.esp32s3 }}
+      esp32c3: ${{ steps.jsonStep.outputs.esp32c3 }}
+      esp32c6: ${{ steps.jsonStep.outputs.esp32c6 }}
+      nrf52840: ${{ steps.jsonStep.outputs.nrf52840 }}
+      rp2040: ${{ steps.jsonStep.outputs.rp2040 }}
+      stm32: ${{ steps.jsonStep.outputs.stm32 }}
+      check: ${{ steps.jsonStep.outputs.check }}
+
+  check:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.check) }}
+
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name != 'workflow_dispatch' }}
+    steps:
+      - uses: actions/checkout@v4
+      - name: Build base
+        id: base
+        uses: ./.github/actions/setup-base
+      - name: Check ${{ matrix.board }}
+        run: bin/check-all.sh ${{ matrix.board }}
+
+  build-esp32:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32) }}
+    uses: ./.github/workflows/build_esp32.yml
+    with:
+      board: ${{ matrix.board }}
+
+  build-esp32-s3:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32s3) }}
+    uses: ./.github/workflows/build_esp32_s3.yml
+    with:
+      board: ${{ matrix.board }}
+
+  build-esp32-c3:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32c3) }}
+    uses: ./.github/workflows/build_esp32_c3.yml
+    with:
+      board: ${{ matrix.board }}
+
+  build-esp32-c6:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.esp32c6) }}
+    uses: ./.github/workflows/build_esp32_c6.yml
+    with:
+      board: ${{ matrix.board }}
+
+  build-nrf52:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.nrf52840) }}
+    uses: ./.github/workflows/build_nrf52.yml
+    with:
+      board: ${{ matrix.board }}
+
+  build-rpi2040:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.rp2040) }}
+    uses: ./.github/workflows/build_rpi2040.yml
+    with:
+      board: ${{ matrix.board }}
+
+  build-stm32:
+    needs: setup
+    strategy:
+      fail-fast: false
+      matrix: ${{ fromJson(needs.setup.outputs.stm32) }}
+    uses: ./.github/workflows/build_stm32.yml
+    with:
+      board: ${{ matrix.board }}
+
+  package-raspbian:
+    uses: ./.github/workflows/package_raspbian.yml
+
+  package-raspbian-armv7l:
+    uses: ./.github/workflows/package_raspbian_armv7l.yml
+
+  package-native:
+    uses: ./.github/workflows/package_amd64.yml
+
+  after-checks:
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name != 'workflow_dispatch' }}
+    needs: [check]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+  gather-artifacts:
+    permissions:
+      contents: write
+      pull-requests: write
+    strategy:
+      fail-fast: false
+      matrix:
+        arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32]
+    runs-on: ubuntu-latest
+    needs:
+      [
+        build-esp32,
+        build-esp32-s3,
+        build-esp32-c3,
+        build-esp32-c6,
+        build-nrf52,
+        build-rpi2040,
+        build-stm32,
+      ]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - uses: actions/download-artifact@v4
+        with:
+          path: ./
+          pattern: firmware-${{matrix.arch}}-*
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Move files up
+        run: mv -b -t ./ ./bin/device-*.sh ./bin/device-*.bat
+
+      - name: Repackage in single firmware zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
+          overwrite: true
+          path: |
+            ./firmware-*.bin
+            ./firmware-*.uf2
+            ./firmware-*.hex
+            ./firmware-*-ota.zip
+            ./device-*.sh
+            ./device-*.bat
+            ./littlefs-*.bin
+            ./bleota*bin
+            ./Meshtastic_nRF52_factory_erase*.uf2
+          retention-days: 30
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
+          merge-multiple: true
+          path: ./output
+
+      # For diagnostics
+      - name: Show artifacts
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output
+
+      - name: Repackage in single elfs zip
+        uses: actions/upload-artifact@v4
+        with:
+          name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+          overwrite: true
+          path: ./*.elf
+          retention-days: 30
+
+      - uses: scruplelesswizard/comment-artifact@main
+        if: ${{ github.event_name == 'pull_request' }}
+        with:
+          name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
+          description: "Download firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip. This artifact will be available for 90 days from creation"
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+
+  release-artifacts:
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    outputs:
+      upload_url: ${{ steps.create_release.outputs.upload_url }}
+    needs: [
+        gather-artifacts,
+        package-raspbian,
+        package-raspbian-armv7l,
+        package-native,
+      ]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Create release
+        uses: actions/create-release@v1
+        id: create_release
+        with:
+          draft: true
+          prerelease: true
+          release_name: Meshtastic Firmware ${{ steps.version.outputs.version }} Alpha
+          tag_name: v${{ steps.version.outputs.version }}
+          body: |
+            Autogenerated by github action, developer should edit as required before publishing...
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+
+      - name: Download deb files
+        uses: actions/download-artifact@v4
+        with:
+          pattern: meshtasticd_${{ steps.version.outputs.version }}_*.deb
+          merge-multiple: true
+          path: ./output
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add raspbian aarch64 .deb
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
+          asset_name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
+          asset_content_type: application/vnd.debian.binary-package
+
+      - name: Add raspbian armv7l .deb
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
+          asset_name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
+          asset_content_type: application/vnd.debian.binary-package
+
+      - name: Add raspbian amd64 .deb
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+        with:
+          upload_url: ${{ steps.create_release.outputs.upload_url }}
+          asset_path: ./output/meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
+          asset_name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
+          asset_content_type: application/vnd.debian.binary-package
+
+      - name: Bump version.properties
+        run: >-
+          bin/bump_version.py
+
+      - name: Create version.properties pull request
+        uses: peter-evans/create-pull-request@v7
+        with:
+          title: Bump version.properties
+          add-paths: |
+            version.properties
+
+  release-firmware:
+    strategy:
+      fail-fast: false
+      matrix:
+        arch: [esp32, esp32s3, esp32c3, esp32c6, nrf52840, rp2040, stm32]
+    runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'workflow_dispatch' }}
+    needs: [release-artifacts]
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: 3.x
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - uses: actions/download-artifact@v4
+        with:
+          pattern: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}
+          merge-multiple: true
+          path: ./output
+
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Device scripts permissions
+        run: |
+          chmod +x ./output/device-install.sh
+          chmod +x ./output/device-update.sh
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./output
+
+      - uses: actions/download-artifact@v4
+        with:
+          name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+          merge-multiple: true
+          path: ./elfs
+
+      - name: Zip firmware
+        run: zip -j -9 -r ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip ./elfs
+
+      # For diagnostics
+      - name: Display structure of downloaded files
+        run: ls -lR
+
+      - name: Add bins to release
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+        with:
+          upload_url: ${{needs.release-artifacts.outputs.upload_url}}
+          asset_path: ./firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+          asset_name: firmware-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+          asset_content_type: application/zip
+
+      - name: Add debug elfs to release
+        uses: actions/upload-release-asset@v1
+        env:
+          GITHUB_TOKEN: ${{ github.token }}
+        with:
+          upload_url: ${{needs.release-artifacts.outputs.upload_url}}
+          asset_path: ./debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+          asset_name: debug-elfs-${{matrix.arch}}-${{ steps.version.outputs.version }}.zip
+          asset_content_type: application/zip
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
new file mode 100644
index 0000000..e249823
--- /dev/null
+++ b/.github/workflows/nightly.yml
@@ -0,0 +1,19 @@
+name: Nightly
+on:
+  schedule:
+    - cron: 0 8 * * 1-5
+  workflow_dispatch: {}
+
+jobs:
+  trunk_check:
+    name: Trunk Check Upload
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Trunk Check
+        uses: trunk-io/trunk-action@782e83f803ca6e369f035d64c6ba2768174ba61b
+        with:
+          trunk-token: ${{ secrets.TRUNK_TOKEN }}
diff --git a/.github/workflows/package_amd64.yml b/.github/workflows/package_amd64.yml
new file mode 100644
index 0000000..a544224
--- /dev/null
+++ b/.github/workflows/package_amd64.yml
@@ -0,0 +1,81 @@
+name: Package Native
+
+on:
+  workflow_call:
+  workflow_dispatch:
+
+permissions:
+  contents: write
+  packages: write
+
+jobs:
+  build-native:
+    uses: ./.github/workflows/build_native.yml
+
+  package-native:
+    runs-on: ubuntu-22.04
+    needs: build-native
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - name: Pull web ui
+        uses: dsaltares/fetch-gh-release-asset@master
+        with:
+          repo: meshtastic/web
+          file: build.tar
+          target: build.tar
+          token: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Download artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: firmware-native-${{ steps.version.outputs.version }}.zip
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: build .debpkg
+        run: |
+          mkdir -p .debpkg/DEBIAN
+          mkdir -p .debpkg/usr/share/doc/meshtasticd/web
+          mkdir -p .debpkg/usr/sbin
+          mkdir -p .debpkg/etc/meshtasticd
+          mkdir -p .debpkg/etc/meshtasticd/config.d
+          mkdir -p .debpkg/etc/meshtasticd/available.d
+          mkdir -p .debpkg/usr/lib/systemd/system/
+          tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+          gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
+          cp release/meshtasticd_linux_x86_64 .debpkg/usr/sbin/meshtasticd
+          cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
+          cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/
+          chmod +x .debpkg/usr/sbin/meshtasticd
+          cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
+          echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
+          chmod +x .debpkg/DEBIAN/conffiles
+
+      - uses: jiro4989/build-deb-action@v3
+        with:
+          package: meshtasticd
+          package_root: .debpkg
+          maintainer: Jonathan Bennett
+          version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
+          arch: amd64
+          depends: libyaml-cpp0.7, openssl, libulfius2.7
+          desc: Native Linux Meshtastic binary.
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: meshtasticd_${{ steps.version.outputs.version }}_amd64.deb
+          overwrite: true
+          path: |
+            ./*.deb
diff --git a/.github/workflows/package_raspbian.yml b/.github/workflows/package_raspbian.yml
new file mode 100644
index 0000000..89efba1
--- /dev/null
+++ b/.github/workflows/package_raspbian.yml
@@ -0,0 +1,81 @@
+name: Package Raspbian
+
+on:
+  workflow_call:
+  workflow_dispatch:
+
+permissions:
+  contents: write
+  packages: write
+
+jobs:
+  build-raspbian:
+    uses: ./.github/workflows/build_raspbian.yml
+
+  package-raspbian:
+    runs-on: ubuntu-22.04
+    needs: build-raspbian
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - name: Pull web ui
+        uses: dsaltares/fetch-gh-release-asset@master
+        with:
+          repo: meshtastic/web
+          file: build.tar
+          target: build.tar
+          token: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Download artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: firmware-raspbian-${{ steps.version.outputs.version }}.zip
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: build .debpkg
+        run: |
+          mkdir -p .debpkg/DEBIAN
+          mkdir -p .debpkg/usr/share/doc/meshtasticd/web
+          mkdir -p .debpkg/usr/sbin
+          mkdir -p .debpkg/etc/meshtasticd
+          mkdir -p .debpkg/etc/meshtasticd/config.d
+          mkdir -p .debpkg/etc/meshtasticd/available.d
+          mkdir -p .debpkg/usr/lib/systemd/system/
+          tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+          gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
+          cp release/meshtasticd_linux_aarch64 .debpkg/usr/sbin/meshtasticd
+          cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
+          cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/
+          chmod +x .debpkg/usr/sbin/meshtasticd
+          cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
+          echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
+          chmod +x .debpkg/DEBIAN/conffiles
+
+      - uses: jiro4989/build-deb-action@v3
+        with:
+          package: meshtasticd
+          package_root: .debpkg
+          maintainer: Jonathan Bennett
+          version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
+          arch: arm64
+          depends: libyaml-cpp0.7, openssl, libulfius2.7
+          desc: Native Linux Meshtastic binary.
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: meshtasticd_${{ steps.version.outputs.version }}_arm64.deb
+          overwrite: true
+          path: |
+            ./*.deb
diff --git a/.github/workflows/package_raspbian_armv7l.yml b/.github/workflows/package_raspbian_armv7l.yml
new file mode 100644
index 0000000..5cbc270
--- /dev/null
+++ b/.github/workflows/package_raspbian_armv7l.yml
@@ -0,0 +1,81 @@
+name: Package Raspbian
+
+on:
+  workflow_call:
+  workflow_dispatch:
+
+permissions:
+  contents: write
+  packages: write
+
+jobs:
+  build-raspbian_armv7l:
+    uses: ./.github/workflows/build_raspbian_armv7l.yml
+
+  package-raspbian_armv7l:
+    runs-on: ubuntu-22.04
+    needs: build-raspbian_armv7l
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+          ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
+
+      - name: Pull web ui
+        uses: dsaltares/fetch-gh-release-asset@master
+        with:
+          repo: meshtastic/web
+          file: build.tar
+          target: build.tar
+          token: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Get release version string
+        run: echo "version=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+        id: version
+
+      - name: Download artifacts
+        uses: actions/download-artifact@v4
+        with:
+          name: firmware-raspbian-armv7l-${{ steps.version.outputs.version }}.zip
+          merge-multiple: true
+
+      - name: Display structure of downloaded files
+        run: ls -R
+
+      - name: build .debpkg
+        run: |
+          mkdir -p .debpkg/DEBIAN
+          mkdir -p .debpkg/usr/share/doc/meshtasticd/web
+          mkdir -p .debpkg/usr/sbin
+          mkdir -p .debpkg/etc/meshtasticd
+          mkdir -p .debpkg/etc/meshtasticd/config.d
+          mkdir -p .debpkg/etc/meshtasticd/available.d
+          mkdir -p .debpkg/usr/lib/systemd/system/
+          tar -xf build.tar -C .debpkg/usr/share/doc/meshtasticd/web
+          gunzip .debpkg/usr/share/doc/meshtasticd/web/*.gz
+          cp release/meshtasticd_linux_armv7l .debpkg/usr/sbin/meshtasticd
+          cp bin/config-dist.yaml .debpkg/etc/meshtasticd/config.yaml
+          cp bin/config.d/* .debpkg/etc/meshtasticd/available.d/
+          chmod +x .debpkg/usr/sbin/meshtasticd
+          cp bin/meshtasticd.service .debpkg/usr/lib/systemd/system/meshtasticd.service
+          echo "/etc/meshtasticd/config.yaml" > .debpkg/DEBIAN/conffiles
+          chmod +x .debpkg/DEBIAN/conffiles
+
+      - uses: jiro4989/build-deb-action@v3
+        with:
+          package: meshtasticd
+          package_root: .debpkg
+          maintainer: Jonathan Bennett
+          version: ${{ steps.version.outputs.version }} # refs/tags/v*.*.*
+          arch: armhf
+          depends: libyaml-cpp0.7, openssl, libulfius2.7
+          desc: Native Linux Meshtastic binary.
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: meshtasticd_${{ steps.version.outputs.version }}_armhf.deb
+          overwrite: true
+          path: |
+            ./*.deb
diff --git a/.github/workflows/sec_sast_flawfinder.yml b/.github/workflows/sec_sast_flawfinder.yml
new file mode 100644
index 0000000..99cc721
--- /dev/null
+++ b/.github/workflows/sec_sast_flawfinder.yml
@@ -0,0 +1,41 @@
+---
+name: Flawfinder Scan
+
+on:
+  push:
+    branches: [master, develop]
+    paths-ignore:
+      - "**.md"
+      - "version.properties"
+
+jobs:
+  flawfinder:
+    runs-on: ubuntu-latest
+    name: Flawfinder
+
+    steps:
+      # step 1
+      - name: clone application source code
+        uses: actions/checkout@v4
+
+      # step 2
+      - name: flawfinder_scan
+        uses: david-a-wheeler/flawfinder@2.0.19
+        with:
+          arguments: "--sarif ./"
+          output: "flawfinder_report.sarif"
+
+      # step 3
+      - name: save report as pipeline artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: flawfinder_report.sarif
+          overwrite: true
+          path: flawfinder_report.sarif
+
+      # step 4
+      - name: publish code scanning alerts
+        uses: github/codeql-action/upload-sarif@v3
+        with:
+          sarif_file: flawfinder_report.sarif
+          category: flawfinder
diff --git a/.github/workflows/sec_sast_semgrep_cron.yml b/.github/workflows/sec_sast_semgrep_cron.yml
new file mode 100644
index 0000000..54bbbe6
--- /dev/null
+++ b/.github/workflows/sec_sast_semgrep_cron.yml
@@ -0,0 +1,43 @@
+---
+name: Semgrep Full Scan
+
+on:
+  workflow_dispatch:
+    branches:
+      - master
+  schedule:
+    - cron: "0 1 * * 6"
+
+jobs:
+  semgrep-full:
+    runs-on: ubuntu-latest
+    container:
+      image: semgrep/semgrep
+
+    steps:
+      # step 1
+      - name: clone application source code
+        uses: actions/checkout@v4
+
+      # step 2
+      - name: full scan
+        run: |
+          semgrep \
+            --sarif --output report.sarif \
+            --metrics=off \
+            --config="p/default"
+
+      # step 3
+      - name: save report as pipeline artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: report.sarif
+          overwrite: true
+          path: report.sarif
+
+      # step 4
+      - name: publish code scanning alerts
+        uses: github/codeql-action/upload-sarif@v3
+        with:
+          sarif_file: report.sarif
+          category: semgrep
diff --git a/.github/workflows/sec_sast_semgrep_pull.yml b/.github/workflows/sec_sast_semgrep_pull.yml
new file mode 100644
index 0000000..9013f1c
--- /dev/null
+++ b/.github/workflows/sec_sast_semgrep_pull.yml
@@ -0,0 +1,25 @@
+---
+name: Semgrep Differential Scan
+on: pull_request
+
+jobs:
+  semgrep-diff:
+    runs-on: ubuntu-22.04
+    container:
+      image: semgrep/semgrep
+
+    steps:
+      # step 1
+      - name: clone application source code
+        uses: actions/checkout@v4
+        with:
+          fetch-depth: 0
+
+      # step 2
+      - name: differential scan
+        run: |
+          semgrep scan \
+            --error \
+            --metrics=off \
+            --baseline-commit ${{ github.event.pull_request.base.sha }} \
+            --config="p/default"
diff --git a/.github/workflows/stale_bot.yml b/.github/workflows/stale_bot.yml
new file mode 100644
index 0000000..0fd2cd5
--- /dev/null
+++ b/.github/workflows/stale_bot.yml
@@ -0,0 +1,21 @@
+name: process stale Issues and PR's
+on:
+  schedule:
+    - cron: 0 6 * * *
+  workflow_dispatch: {}
+
+permissions:
+  issues: write
+  pull-requests: write
+
+jobs:
+  stale_issues:
+    name: Close Stale Issues
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Stale PR+Issues
+        uses: actions/stale@v9.0.0
+        with:
+          exempt-issue-labels: pinned,3.0
+          exempt-pr-labels: pinned,3.0
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..241598f
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,106 @@
+name: End to end tests
+
+on:
+  schedule:
+    - cron: "0 0 * * *" # Run every day at midnight
+  workflow_dispatch: {}
+
+jobs:
+  test-simulator:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Install libbluetooth
+        shell: bash
+        run: |
+          sudo apt-get update --fix-missing
+          sudo apt-get install -y libbluetooth-dev libgpiod-dev libyaml-cpp-dev openssl libssl-dev libulfius-dev liborcania-dev
+
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: recursive
+
+      - name: Upgrade python tools
+        shell: bash
+        run: |
+          python -m pip install --upgrade pip
+          pip install -U platformio adafruit-nrfutil
+          pip install -U meshtastic --pre
+
+      - name: Upgrade platformio
+        shell: bash
+        run: |
+          pio upgrade
+
+      - name: Build Native
+        run: bin/build-native.sh
+
+      # We now run integration test before other build steps (to quickly see runtime failures)
+      - name: Build for native
+        run: platformio run -e native
+
+      - name: Integration test
+        run: |
+          .pio/build/native/program & sleep 10 # 5 seconds was not enough
+          echo "Simulator started, launching python test..."
+          python3 -c 'from meshtastic.test import testSimulator; testSimulator()'
+
+      - name: PlatformIO Tests
+        run: platformio test -e native --junit-output-path testreport.xml
+
+      - name: Test Report
+        uses: dorny/test-reporter@v1.9.1
+        if: success() || failure() # run this step even if previous step failed
+        with:
+          name: PlatformIO Tests
+          path: testreport.xml
+          reporter: java-junit
+
+  hardware-tests:
+    runs-on: test-runner
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+
+      # - uses: actions/setup-python@v5
+      #   with:
+      #     python-version: '3.10'
+
+      # pipx install "setuptools<72"
+      - name: Upgrade python tools
+        shell: bash
+        run: |
+          pipx install adafruit-nrfutil
+          pipx install poetry
+          pipx install meshtastic --pip-args=--pre
+
+      - name: Install PlatformIO from script
+        shell: bash
+        run: |
+          curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py
+          python3 get-platformio.py
+
+      - name: Upgrade platformio
+        shell: bash
+        run: |
+          export PATH=$PATH:$HOME/.local/bin
+          pio upgrade
+
+      - name: Setup Node
+        uses: actions/setup-node@v4
+        with:
+          node-version: 18
+
+      - name: Setup pnpm
+        uses: pnpm/action-setup@v4
+        with:
+          version: latest
+
+      - name: Install dependencies, setup devices and run
+        shell: bash
+        run: |
+          git submodule update --init --recursive
+          cd meshtestic/
+          pnpm install
+          pnpm run setup
+          pnpm run test
diff --git a/.github/workflows/trunk-check.yml b/.github/workflows/trunk-check.yml
new file mode 100644
index 0000000..6ed905b
--- /dev/null
+++ b/.github/workflows/trunk-check.yml
@@ -0,0 +1,22 @@
+name: Pull Request
+on: [pull_request]
+concurrency:
+  group: ${{ github.head_ref || github.run_id }}
+  cancel-in-progress: true
+
+permissions: read-all
+
+jobs:
+  trunk_check:
+    name: Trunk Check Runner
+    runs-on: ubuntu-latest
+    permissions:
+      checks: write # For trunk to post annotations
+      contents: read # For repo checkout
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - name: Trunk Check
+        uses: trunk-io/trunk-action@v1
diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml
new file mode 100644
index 0000000..f1c92b8
--- /dev/null
+++ b/.github/workflows/update_protobufs.yml
@@ -0,0 +1,34 @@
+name: Update protobufs and regenerate classes
+on: workflow_dispatch
+
+jobs:
+  update-protobufs:
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v4
+        with:
+          submodules: true
+
+      - name: Update submodule
+        run: |
+          git submodule update --remote protobufs
+
+      - name: Download nanopb
+        run: |
+          wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9-linux-x86.tar.gz
+          tar xvzf nanopb-0.4.9-linux-x86.tar.gz
+          mv nanopb-0.4.9-linux-x86 nanopb-0.4.9
+
+      - name: Re-generate protocol buffers
+        run: |
+          ./bin/regen-protos.sh
+
+      - name: Create pull request
+        uses: peter-evans/create-pull-request@v7
+        with:
+          title: Update protobufs and classes
+          add-paths: |
+            protobufs
+            src/mesh
diff --git a/.trunk/.gitignore b/.trunk/.gitignore
new file mode 100644
index 0000000..15966d0
--- /dev/null
+++ b/.trunk/.gitignore
@@ -0,0 +1,9 @@
+*out
+*logs
+*actions
+*notifications
+*tools
+plugins
+user_trunk.yaml
+user.yaml
+tmp
diff --git a/.trunk/configs/.bandit b/.trunk/configs/.bandit
new file mode 100644
index 0000000..d286ded
--- /dev/null
+++ b/.trunk/configs/.bandit
@@ -0,0 +1,2 @@
+[bandit]
+skips = B101
\ No newline at end of file
diff --git a/.trunk/configs/.clang-format b/.trunk/configs/.clang-format
new file mode 100644
index 0000000..a0e6387
--- /dev/null
+++ b/.trunk/configs/.clang-format
@@ -0,0 +1,6 @@
+Language: Cpp
+IndentWidth: 4
+ColumnLimit: 130
+PointerAlignment: Right
+BreakBeforeBraces: Linux
+AllowShortFunctionsOnASingleLine: Inline
diff --git a/.trunk/configs/.flake8 b/.trunk/configs/.flake8
new file mode 100644
index 0000000..5ba6e2f
--- /dev/null
+++ b/.trunk/configs/.flake8
@@ -0,0 +1,3 @@
+# Autoformatter friendly flake8 config (all formatting rules disabled)
+[flake8]
+extend-ignore = D1, D2, E1, E2, E3, E501, W1, W2, W3, W5
diff --git a/.trunk/configs/.hadolint.yaml b/.trunk/configs/.hadolint.yaml
new file mode 100644
index 0000000..98bf0cd
--- /dev/null
+++ b/.trunk/configs/.hadolint.yaml
@@ -0,0 +1,4 @@
+# Following source doesn't work in most setups
+ignored:
+  - SC1090
+  - SC1091
diff --git a/.trunk/configs/.isort.cfg b/.trunk/configs/.isort.cfg
new file mode 100644
index 0000000..b9fb3f3
--- /dev/null
+++ b/.trunk/configs/.isort.cfg
@@ -0,0 +1,2 @@
+[settings]
+profile=black
diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml
new file mode 100644
index 0000000..fb94039
--- /dev/null
+++ b/.trunk/configs/.markdownlint.yaml
@@ -0,0 +1,10 @@
+# Autoformatter friendly markdownlint config (all formatting rules disabled)
+default: true
+blank_lines: false
+bullet: false
+html: false
+indentation: false
+line_length: false
+spaces: false
+url: false
+whitespace: false
diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc
new file mode 100644
index 0000000..b2e8a14
--- /dev/null
+++ b/.trunk/configs/.shellcheckrc
@@ -0,0 +1,10 @@
+enable=all
+source-path=SCRIPTDIR
+disable=SC2154
+disable=SC2248
+disable=SC2250
+
+# If you're having issues with shellcheck following source, disable the errors via:
+# disable=SC1090
+# disable=SC1091
+# 
\ No newline at end of file
diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml
new file mode 100644
index 0000000..7908461
--- /dev/null
+++ b/.trunk/configs/.yamllint.yaml
@@ -0,0 +1,10 @@
+rules:
+  quoted-strings:
+    required: only-when-needed
+    extra-allowed: ["{|}"]
+  empty-values:
+    forbid-in-block-mappings: false
+    forbid-in-flow-mappings: true
+  key-duplicates: {}
+  octal-values:
+    forbid-implicit-octal: true
diff --git a/.trunk/configs/ruff.toml b/.trunk/configs/ruff.toml
new file mode 100644
index 0000000..346b1d9
--- /dev/null
+++ b/.trunk/configs/ruff.toml
@@ -0,0 +1,5 @@
+# Generic, formatter-friendly config.
+select = ["B", "D3", "D4", "E", "F"]
+
+# Never enforce `E501` (line length violations). This should be handled by formatters.
+ignore = ["E501"]
diff --git a/.trunk/configs/svgo.config.js b/.trunk/configs/svgo.config.js
new file mode 100644
index 0000000..b257d13
--- /dev/null
+++ b/.trunk/configs/svgo.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+  plugins: [
+    {
+      name: "preset-default",
+      params: {
+        overrides: {
+          removeViewBox: false, // https://github.com/svg/svgo/issues/1128
+          sortAttrs: true,
+          removeOffCanvasPaths: true,
+        },
+      },
+    },
+  ],
+};
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
new file mode 100644
index 0000000..2fa237d
--- /dev/null
+++ b/.trunk/trunk.yaml
@@ -0,0 +1,49 @@
+version: 0.1
+cli:
+  version: 1.22.7
+plugins:
+  sources:
+    - id: trunk
+      ref: v1.6.4
+      uri: https://github.com/trunk-io/plugins
+lint:
+  enabled:
+    - trufflehog@3.82.13
+    - yamllint@1.35.1
+    - bandit@1.7.10
+    - checkov@3.2.269
+    - terrascan@1.19.9
+    - trivy@0.56.2
+    #- trufflehog@3.63.2-rc0
+    - taplo@0.9.3
+    - ruff@0.7.1
+    - isort@5.13.2
+    - markdownlint@0.42.0
+    - oxipng@9.1.2
+    - svgo@3.3.2
+    - actionlint@1.7.3
+    - flake8@7.1.1
+    - hadolint@2.12.0
+    - shfmt@3.6.0
+    - shellcheck@0.10.0
+    - black@24.10.0
+    - git-diff-check
+    - gitleaks@8.21.1
+    - clang-format@16.0.3
+    - prettier@3.3.3
+  ignore:
+    - linters: [ALL]
+      paths:
+        - bin/**
+runtimes:
+  enabled:
+    - python@3.10.8
+    - go@1.21.0
+    - node@18.12.1
+actions:
+  disabled:
+    - trunk-announce
+  enabled:
+    - trunk-fmt-pre-commit
+    - trunk-check-pre-push
+    - trunk-upgrade-available
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..bf9b821
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,11 @@
+{
+  "editor.formatOnSave": true,
+  "editor.defaultFormatter": "trunk.io",
+  "trunk.enableWindows": true,
+  "files.insertFinalNewline": false,
+  "files.trimFinalNewlines": false,
+  "cmake.configureOnOpen": false,
+  "[cpp]": {
+    "editor.defaultFormatter": "trunk.io"
+  }
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..cdb8f51
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,15 @@
+{
+  "version": "2.0.0",
+  "tasks": [
+    {
+      "type": "PlatformIO",
+      "task": "Build",
+      "problemMatcher": ["$platformio"],
+      "group": {
+        "kind": "build",
+        "isDefault": true
+      },
+      "label": "PlatformIO: Build"
+    }
+  ]
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ca8a924
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# Meshtastic Firmware
+
+![GitHub release downloads](https://img.shields.io/github/downloads/meshtastic/firmware/total)
+[![CI](https://img.shields.io/github/actions/workflow/status/meshtastic/firmware/main_matrix.yml?branch=master&label=actions&logo=github&color=yellow)](https://github.com/meshtastic/firmware/actions/workflows/ci.yml)
+[![CLA assistant](https://cla-assistant.io/readme/badge/meshtastic/firmware)](https://cla-assistant.io/meshtastic/firmware)
+[![Fiscal Contributors](https://opencollective.com/meshtastic/tiers/badge.svg?label=Fiscal%20Contributors&color=deeppink)](https://opencollective.com/meshtastic/)
+[![Vercel](https://img.shields.io/static/v1?label=Powered%20by&message=Vercel&style=flat&logo=vercel&color=000000)](https://vercel.com?utm_source=meshtastic&utm_campaign=oss)
+
+## Overview
+
+This repository contains the device firmware for the Meshtastic project.
+
+- **[Building Instructions](https://meshtastic.org/docs/development/firmware/build)**
+- **[Flashing Instructions](https://meshtastic.org/docs/getting-started/flashing-firmware/)**
+
+## Stats
+
+![Alt](https://repobeats.axiom.co/api/embed/a92f097d9197ae853e780ec53d7d126e545629ab.svg "Repobeats analytics image")
diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini
index 36d8b95..382975e 100644
--- a/arch/esp32/esp32.ini
+++ b/arch/esp32/esp32.ini
@@ -31,7 +31,7 @@ build_flags =
   -DCONFIG_BT_NIMBLE_ENABLED
   -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2
   -DCONFIG_BT_NIMBLE_MAX_CCCDS=20
-  -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=5120
+  -DCONFIG_BT_NIMBLE_HOST_TASK_STACK_SIZE=8192
   -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING
   -DSERIAL_BUFFER_SIZE=4096
   -DLIBPAX_ARDUINO
diff --git a/platformio.ini b/platformio.ini
index bbb4287..2e3ee56 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -2,7 +2,7 @@
 ; https://docs.platformio.org/page/projectconf.html
 
 [platformio]
-default_envs = native
+default_envs = tbeam
 ;default_envs = pico
 ;default_envs = tbeam-s3-core
 ;default_envs = tbeam0.7
@@ -96,7 +96,7 @@ lib_deps =
   https://github.com/meshtastic/ArduinoThread.git#1ae8778c85d0a2a729f989e0b1e7d7c4dc84eef0
   nanopb/Nanopb@^0.4.9
   erriez/ErriezCRC32@^1.0.1
-
+  
 ; Used for the code analysis in PIO Home / Inspect
 check_tool = cppcheck
 check_skip_packages = yes
@@ -161,6 +161,6 @@ lib_deps =
   https://github.com/KodinLanewave/INA3221@^1.0.1
   lewisxhe/SensorLib@0.2.0
   mprograms/QMC5883LCompass@^1.2.0
-
+  
   https://github.com/meshtastic/DFRobot_LarkWeatherStation#4de3a9cadef0f6a5220a8a906cf9775b02b0040d
   https://github.com/gjelsoe/STK8xxx-Accelerometer.git#v0.1.1
diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp
index 2dda776..1e9e212 100644
--- a/src/gps/GPS.cpp
+++ b/src/gps/GPS.cpp
@@ -421,7 +421,7 @@ bool GPS::setup()
         if (tx_gpio && gnssModel == GNSS_MODEL_UNKNOWN) {
 
             // if GPS_BAUDRATE is specified in variant (i.e. not 9600), skip to the specified rate.
-            if (speedSelect == 0 && GPS_BAUDRATE != serialSpeeds[speedSelect]) {
+            if (speedSelect == 0 && probeTries == 2 && GPS_BAUDRATE != serialSpeeds[speedSelect]) {
                 speedSelect = std::find(serialSpeeds, std::end(serialSpeeds), GPS_BAUDRATE) - serialSpeeds;
             }
 
@@ -431,7 +431,7 @@ bool GPS::setup()
                 if (++speedSelect == sizeof(serialSpeeds) / sizeof(int)) {
                     speedSelect = 0;
                     if (--probeTries == 0) {
-                        LOG_WARN("Giving up on GPS probe and setting to 9600.");
+                        LOG_WARN("Giving up on GPS probe and setting to %d", GPS_BAUDRATE);
                         return true;
                     }
                 }
@@ -476,6 +476,18 @@ bool GPS::setup()
             // Switch to Fitness Mode, for running and walking purpose with low speed (<5 m/s)
             _serial_gps->write("$PMTK886,1*29\r\n");
             delay(250);
+        } else if (gnssModel == GNSS_MODEL_MTK_PA1616S) {
+            // PA1616S is used in some GPS breakout boards from Adafruit
+            // PA1616S does not have GLONASS capability. PA1616D does, but is not implemented here.
+            _serial_gps->write("$PMTK353,1,0,0,0,0*2A\r\n");
+            // Above command will reset the GPS and takes longer before it will accept new commands
+            delay(1000);
+            // Only ask for RMC and GGA (GNRMC and GNGGA)
+            _serial_gps->write("$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28\r\n");
+            delay(250);
+            // Enable SBAS / WAAS
+            _serial_gps->write("$PMTK301,2*2E\r\n");
+            delay(250);
         } else if (gnssModel == GNSS_MODEL_ATGM336H) {
             // Set the intial configuration of the device - these _should_ work for most AT6558 devices
             msglen = makeCASPacket(0x06, 0x07, sizeof(_message_CAS_CFG_NAVX_CONF), _message_CAS_CFG_NAVX_CONF);
@@ -1144,6 +1156,7 @@ GnssModel_t GPS::probe(int serialSpeed)
     delay(20);
 
     PROBE_SIMPLE("L76B", "$PMTK605*31", "Quectel-L76B", GNSS_MODEL_MTK_L76B, 500);
+    PROBE_SIMPLE("PA1616S", "$PMTK605*31", "1616S", GNSS_MODEL_MTK_PA1616S, 500);
 
     uint8_t cfg_rate[] = {0xB5, 0x62, 0x06, 0x08, 0x00, 0x00, 0x00, 0x00};
     UBXChecksum(cfg_rate, sizeof(cfg_rate));
diff --git a/src/gps/GPS.h b/src/gps/GPS.h
index 8b1982c..240d087 100644
--- a/src/gps/GPS.h
+++ b/src/gps/GPS.h
@@ -34,6 +34,7 @@ typedef enum {
     GNSS_MODEL_UC6580,
     GNSS_MODEL_UNKNOWN,
     GNSS_MODEL_MTK_L76B,
+    GNSS_MODEL_MTK_PA1616S,
     GNSS_MODEL_AG3335,
     GNSS_MODEL_AG3352
 } GnssModel_t;
@@ -75,7 +76,7 @@ class GPS : private concurrency::OSThread
     uint8_t fixType = 0;      // fix type from GPGSA
 #endif
   private:
-    const int serialSpeeds[6] = {9600, 115200, 38400, 4800, 57600, 9600};
+    const int serialSpeeds[6] = {9600, 115200, 38400, 4800, 57600, GPS_BAUDRATE};
     uint32_t lastWakeStartMsec = 0, lastSleepStartMsec = 0, lastFixStartMsec = 0;
     uint32_t rx_gpio = 0;
     uint32_t tx_gpio = 0;
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 69cd631..6b29919 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -174,21 +174,23 @@ NodeDB::NodeDB()
     }
 
 #if !(MESHTASTIC_EXCLUDE_PKI_KEYGEN || MESHTASTIC_EXCLUDE_PKI)
-    bool keygenSuccess = false;
-    if (config.security.private_key.size == 32) {
-        if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
+    if (!owner.is_licensed) {
+        bool keygenSuccess = false;
+        if (config.security.private_key.size == 32) {
+            if (crypto->regeneratePublicKey(config.security.public_key.bytes, config.security.private_key.bytes)) {
+                keygenSuccess = true;
+            }
+        } else {
+            LOG_INFO("Generating new PKI keys");
+            crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
             keygenSuccess = true;
         }
-    } else {
-        LOG_INFO("Generating new PKI keys");
-        crypto->generateKeyPair(config.security.public_key.bytes, config.security.private_key.bytes);
-        keygenSuccess = true;
-    }
-    if (keygenSuccess) {
-        config.security.public_key.size = 32;
-        config.security.private_key.size = 32;
-        owner.public_key.size = 32;
-        memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
+        if (keygenSuccess) {
+            config.security.public_key.size = 32;
+            config.security.private_key.size = 32;
+            owner.public_key.size = 32;
+            memcpy(owner.public_key.bytes, config.security.public_key.bytes, 32);
+        }
     }
 #elif !(MESHTASTIC_EXCLUDE_PKI)
     // Calculate Curve25519 public and private keys
@@ -557,8 +559,10 @@ void NodeDB::installRoleDefaults(meshtastic_Config_DeviceConfig_Role role)
     if (role == meshtastic_Config_DeviceConfig_Role_ROUTER) {
         initConfigIntervals();
         initModuleConfigIntervals();
+        config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
     } else if (role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
         config.display.screen_on_secs = 1;
+        config.device.rebroadcast_mode = meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
     } else if (role == meshtastic_Config_DeviceConfig_Role_SENSOR) {
         moduleConfig.telemetry.environment_measurement_enabled = true;
         moduleConfig.telemetry.environment_update_interval = 300;
@@ -1274,4 +1278,4 @@ void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, co
     LOG_ERROR("A critical failure occurred, portduino is exiting...");
     exit(2);
 #endif
-}
+}
\ No newline at end of file
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index f51c9bc..e8f6d1c 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -271,7 +271,7 @@ uint32_t RadioInterface::getTxDelayMsecWeighted(float snr)
     if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
         config.device.role == meshtastic_Config_DeviceConfig_Role_REPEATER) {
         delay = random(0, 2 * CWsize) * slotTimeMsec;
-        LOG_DEBUG("rx_snr found in packet. As a router, setting tx delay:%d", delay);
+        LOG_DEBUG("rx_snr found in packet. Router: setting tx delay:%d", delay);
     } else {
         // offset the maximum delay for routers: (2 * CWmax * slotTimeMsec)
         delay = (2 * CWmax * slotTimeMsec) + random(0, pow(2, CWsize)) * slotTimeMsec;
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index 84ae7bb..d82268c 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -591,19 +591,20 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
             skipHandle = true;
         }
 
+        bool shouldIgnoreNonstandardPorts =
+            config.device.rebroadcast_mode == meshtastic_Config_DeviceConfig_RebroadcastMode_CORE_PORTNUMS_ONLY;
 #if USERPREFS_EVENT_MODE
-        if (p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
-            (p->decoded.portnum == meshtastic_PortNum_ATAK_FORWARDER || p->decoded.portnum == meshtastic_PortNum_ATAK_PLUGIN ||
-             p->decoded.portnum == meshtastic_PortNum_PAXCOUNTER_APP || p->decoded.portnum == meshtastic_PortNum_IP_TUNNEL_APP ||
-             p->decoded.portnum == meshtastic_PortNum_AUDIO_APP || p->decoded.portnum == meshtastic_PortNum_PRIVATE_APP ||
-             p->decoded.portnum == meshtastic_PortNum_DETECTION_SENSOR_APP ||
-             p->decoded.portnum == meshtastic_PortNum_RANGE_TEST_APP ||
-             p->decoded.portnum == meshtastic_PortNum_REMOTE_HARDWARE_APP)) {
-            LOG_DEBUG("Ignoring packet on blacklisted portnum during event");
+        shouldIgnoreNonstandardPorts = true;
+#endif
+        if (shouldIgnoreNonstandardPorts && p->which_payload_variant == meshtastic_MeshPacket_decoded_tag &&
+            IS_ONE_OF(p->decoded.portnum, meshtastic_PortNum_ATAK_FORWARDER, meshtastic_PortNum_ATAK_PLUGIN,
+                      meshtastic_PortNum_PAXCOUNTER_APP, meshtastic_PortNum_IP_TUNNEL_APP, meshtastic_PortNum_AUDIO_APP,
+                      meshtastic_PortNum_PRIVATE_APP, meshtastic_PortNum_DETECTION_SENSOR_APP, meshtastic_PortNum_RANGE_TEST_APP,
+                      meshtastic_PortNum_REMOTE_HARDWARE_APP)) {
+            LOG_DEBUG("Ignoring packet on blacklisted portnum for CORE_PORTNUMS_ONLY");
             cancelSending(p->from, p->id);
             skipHandle = true;
         }
-#endif
     } else {
         printPacket("packet decoding failed or skipped (no PSK?)", p);
     }
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 87387f0..41ad0ea 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -93,7 +93,7 @@ int32_t ExternalNotificationModule::runOnce()
             nagCycleCutoff = UINT32_MAX;
             LOG_INFO("Turning off external notification: ");
             for (int i = 0; i < 3; i++) {
-                setExternalOff(i);
+                setExternalState(i, false);
                 externalTurnedOn[i] = 0;
                 LOG_INFO("%d ", i);
             }
@@ -114,17 +114,17 @@ int32_t ExternalNotificationModule::runOnce()
             if (externalTurnedOn[0] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
                                                                                     : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
                 millis()) {
-                getExternal(0) ? setExternalOff(0) : setExternalOn(0);
+                setExternalState(0, !getExternal(0));
             }
             if (externalTurnedOn[1] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
                                                                                     : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
                 millis()) {
-                getExternal(1) ? setExternalOff(1) : setExternalOn(1);
+                setExternalState(0, !getExternal(1));
             }
             if (externalTurnedOn[2] + (moduleConfig.external_notification.output_ms ? moduleConfig.external_notification.output_ms
                                                                                     : EXT_NOTIFICATION_MODULE_OUTPUT_MS) <
                 millis()) {
-                getExternal(2) ? setExternalOff(2) : setExternalOn(2);
+                setExternalState(0, !getExternal(2));
             }
 #if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
             red = (colorState & 4) ? brightnessValues[brightnessIndex] : 0;          // Red enabled on colorState = 4,5,6,7
@@ -203,86 +203,42 @@ bool ExternalNotificationModule::wantPacket(const meshtastic_MeshPacket *p)
 }
 
 /**
- * Sets the external notification on for the specified index.
+ * Sets the external notification for the specified index.
  *
- * @param index The index of the external notification to turn on.
+ * @param index The index of the external notification to change state.
+ * @param on Whether we are turning things on (true) or off (false).
  */
-void ExternalNotificationModule::setExternalOn(uint8_t index)
+void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
 {
-    externalCurrentState[index] = 1;
+    externalCurrentState[index] = on;
     externalTurnedOn[index] = millis();
 
     switch (index) {
     case 1:
 #ifdef UNPHONE
-        unphone.vibe(true); // the unPhone's vibration motor is on a i2c GPIO expander
+        unphone.vibe(on); // the unPhone's vibration motor is on a i2c GPIO expander
 #endif
         if (moduleConfig.external_notification.output_vibra)
-            digitalWrite(moduleConfig.external_notification.output_vibra, true);
+            digitalWrite(moduleConfig.external_notification.output_vibra, on);
         break;
     case 2:
         if (moduleConfig.external_notification.output_buzzer)
-            digitalWrite(moduleConfig.external_notification.output_buzzer, true);
+            digitalWrite(moduleConfig.external_notification.output_buzzer, on);
         break;
     default:
         if (output > 0)
-            digitalWrite(output, (moduleConfig.external_notification.active ? true : false));
-        break;
-    }
-
-#ifdef HAS_NCP5623
-    if (rgb_found.type == ScanI2C::NCP5623) {
-        rgb.setColor(red, green, blue);
-    }
-#endif
-#ifdef RGBLED_CA
-    analogWrite(RGBLED_RED, 255 - red); // CA type needs reverse logic
-    analogWrite(RGBLED_GREEN, 255 - green);
-    analogWrite(RGBLED_BLUE, 255 - blue);
-#elif defined(RGBLED_RED)
-    analogWrite(RGBLED_RED, red);
-    analogWrite(RGBLED_GREEN, green);
-    analogWrite(RGBLED_BLUE, blue);
-#endif
-#ifdef HAS_NEOPIXEL
-    pixels.fill(pixels.Color(red, green, blue), 0, NEOPIXEL_COUNT);
-    pixels.show();
-#endif
-#ifdef UNPHONE
-    unphone.rgb(red, green, blue);
-#endif
-#ifdef T_WATCH_S3
-    drv.go();
-#endif
-}
-
-void ExternalNotificationModule::setExternalOff(uint8_t index)
-{
-    externalCurrentState[index] = 0;
-    externalTurnedOn[index] = millis();
-
-    switch (index) {
-    case 1:
-#ifdef UNPHONE
-        unphone.vibe(false); // the unPhone's vibration motor is on a i2c GPIO expander
-#endif
-        if (moduleConfig.external_notification.output_vibra)
-            digitalWrite(moduleConfig.external_notification.output_vibra, false);
-        break;
-    case 2:
-        if (moduleConfig.external_notification.output_buzzer)
-            digitalWrite(moduleConfig.external_notification.output_buzzer, false);
-        break;
-    default:
-        if (output > 0)
-            digitalWrite(output, (moduleConfig.external_notification.active ? false : true));
+            digitalWrite(output, (moduleConfig.external_notification.active ? on : !on));
         break;
     }
 
 #if defined(HAS_NCP5623) || defined(RGBLED_RED) || defined(HAS_NEOPIXEL) || defined(UNPHONE)
-    red = 0;
-    green = 0;
-    blue = 0;
+    if (!on) {
+        red = 0;
+        green = 0;
+        blue = 0;
+    }
+#endif
+
 #ifdef HAS_NCP5623
     if (rgb_found.type == ScanI2C::NCP5623) {
         rgb.setColor(red, green, blue);
@@ -304,10 +260,12 @@ void ExternalNotificationModule::setExternalOff(uint8_t index)
 #ifdef UNPHONE
     unphone.rgb(red, green, blue);
 #endif
-#endif
-
 #ifdef T_WATCH_S3
-    drv.stop();
+    if (on) {
+        drv.go();
+    } else {
+        drv.stop();
+    }
 #endif
 }
 
@@ -379,19 +337,19 @@ ExternalNotificationModule::ExternalNotificationModule()
             LOG_INFO("Using Pin %i in digital mode", output);
             pinMode(output, OUTPUT);
         }
-        setExternalOff(0);
+        setExternalState(0, false);
         externalTurnedOn[0] = 0;
         if (moduleConfig.external_notification.output_vibra) {
             LOG_INFO("Using Pin %i for vibra motor", moduleConfig.external_notification.output_vibra);
             pinMode(moduleConfig.external_notification.output_vibra, OUTPUT);
-            setExternalOff(1);
+            setExternalState(1, false);
             externalTurnedOn[1] = 0;
         }
         if (moduleConfig.external_notification.output_buzzer) {
             if (!moduleConfig.external_notification.use_pwm) {
                 LOG_INFO("Using Pin %i for buzzer", moduleConfig.external_notification.output_buzzer);
                 pinMode(moduleConfig.external_notification.output_buzzer, OUTPUT);
-                setExternalOff(2);
+                setExternalState(2, false);
                 externalTurnedOn[2] = 0;
             } else {
                 config.device.buzzer_gpio = config.device.buzzer_gpio ? config.device.buzzer_gpio : PIN_BUZZER;
@@ -449,7 +407,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell");
                     isNagging = true;
-                    setExternalOn(0);
+                    setExternalState(0, true);
                     if (moduleConfig.external_notification.nag_timeout) {
                         nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
                     } else {
@@ -462,7 +420,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 if (containsBell) {
                     LOG_INFO("externalNotificationModule - Notification Bell (Vibra)");
                     isNagging = true;
-                    setExternalOn(1);
+                    setExternalState(1, true);
                     if (moduleConfig.external_notification.nag_timeout) {
                         nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
                     } else {
@@ -476,7 +434,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                     LOG_INFO("externalNotificationModule - Notification Bell (Buzzer)");
                     isNagging = true;
                     if (!moduleConfig.external_notification.use_pwm) {
-                        setExternalOn(2);
+                        setExternalState(2, true);
                     } else {
 #ifdef HAS_I2S
                         audioThread->beginRttl(rtttlConfig.ringtone, strlen_P(rtttlConfig.ringtone));
@@ -495,7 +453,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
             if (moduleConfig.external_notification.alert_message) {
                 LOG_INFO("externalNotificationModule - Notification Module");
                 isNagging = true;
-                setExternalOn(0);
+                setExternalState(0, true);
                 if (moduleConfig.external_notification.nag_timeout) {
                     nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
                 } else {
@@ -506,7 +464,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
             if (moduleConfig.external_notification.alert_message_vibra) {
                 LOG_INFO("externalNotificationModule - Notification Module (Vibra)");
                 isNagging = true;
-                setExternalOn(1);
+                setExternalState(1, true);
                 if (moduleConfig.external_notification.nag_timeout) {
                     nagCycleCutoff = millis() + moduleConfig.external_notification.nag_timeout * 1000;
                 } else {
@@ -518,7 +476,7 @@ ProcessMessage ExternalNotificationModule::handleReceived(const meshtastic_MeshP
                 LOG_INFO("externalNotificationModule - Notification Module (Buzzer)");
                 isNagging = true;
                 if (!moduleConfig.external_notification.use_pwm && !moduleConfig.external_notification.use_i2s_as_buzzer) {
-                    setExternalOn(2);
+                    setExternalState(2, true);
                 } else {
 #ifdef HAS_I2S
                     if (moduleConfig.external_notification.use_i2s_as_buzzer) {
diff --git a/src/modules/ExternalNotificationModule.h b/src/modules/ExternalNotificationModule.h
index 20d02d6..841ca6d 100644
--- a/src/modules/ExternalNotificationModule.h
+++ b/src/modules/ExternalNotificationModule.h
@@ -34,8 +34,7 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
 
     uint32_t nagCycleCutoff = 1;
 
-    void setExternalOn(uint8_t index = 0);
-    void setExternalOff(uint8_t index = 0);
+    void setExternalState(uint8_t index = 0, bool on = false);
     bool getExternal(uint8_t index = 0);
 
     void setMute(bool mute) { isMuted = mute; }
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index 8437118..855cf44 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -81,6 +81,12 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
         ignoreRequest = false; // Don't ignore requests anymore
         meshtastic_User &u = owner;
 
+        // Strip the public key if the user is licensed
+        if (u.is_licensed && u.public_key.size > 0) {
+            u.public_key.bytes[0] = 0;
+            u.public_key.size = 0;
+        }
+
         LOG_INFO("sending owner %s/%s/%s", u.id, u.long_name, u.short_name);
         lastSentToMesh = millis();
         return allocDataProtobuf(u);
diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp
index dc9c8aa..1c45a1d 100644
--- a/src/modules/SerialModule.cpp
+++ b/src/modules/SerialModule.cpp
@@ -315,7 +315,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
         // LOG_DEBUG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s",
         //          nodeDB->getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
 
-        if (!isFromUs(&mp)) {
+        if (isFromUs(&mp)) {
 
             /*
              * If moduleConfig.serial.echo is true, then echo the packets that are sent out
diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp
index 7b3493f..b1185e2 100644
--- a/src/platform/esp32/main-esp32.cpp
+++ b/src/platform/esp32/main-esp32.cpp
@@ -51,7 +51,11 @@ void updateBatteryLevel(uint8_t level) {}
 
 void getMacAddr(uint8_t *dmac)
 {
+#if defined(CONFIG_IDF_TARGET_ESP32C6) && defined(CONFIG_SOC_IEEE802154_SUPPORTED)
+    assert(esp_base_mac_addr_get(dmac) == ESP_OK);
+#else
     assert(esp_efuse_mac_get_default(dmac) == ESP_OK);
+#endif
 }
 
 #ifdef HAS_32768HZ
diff --git a/variants/portduino/variant.h b/variants/portduino/variant.h
index 5c8f2e1..b7b39d6 100644
--- a/variants/portduino/variant.h
+++ b/variants/portduino/variant.h
@@ -1,8 +1,5 @@
-#define HAS_SCREEN 0
-#define HAS_RADIO 1
+#define HAS_SCREEN 1
 #define CANNED_MESSAGE_MODULE_ENABLE 1
-#define HAS_GPS 0
+#define HAS_GPS 1
 #define MAX_RX_TOPHONE settingsMap[maxtophone]
-#define MAX_NUM_NODES settingsMap[maxnodes]
-#define RADIOLIB_GODMODE 1
-#define ARCH_PORTDUINO 1
\ No newline at end of file
+#define MAX_NUM_NODES settingsMap[maxnodes]
\ No newline at end of file