diff --git a/.github/actions/setup-build-environment/action.yml b/.github/actions/setup-build-environment/action.yml index 02aaf424..cba2d2e5 100644 --- a/.github/actions/setup-build-environment/action.yml +++ b/.github/actions/setup-build-environment/action.yml @@ -25,5 +25,14 @@ runs: - name: Extract Version from Git Tag shell: bash run: | - GIT_TAG_NAME="${GITHUB_REF#refs/tags/}" - echo "GIT_TAG_VERSION=${GIT_TAG_NAME##*-}" >> $GITHUB_ENV + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + # triggered by a tag push (e.g: refs/tags/companion-v1.2.3) + GIT_TAG_NAME="${GITHUB_REF#refs/tags/}" + VERSION_STRING="${GIT_TAG_NAME##*-}" + else + # triggered by a workflow dispatch (e.g: refs/heads/main) + # strip "refs/heads/" prefix and replace any remaining "/" with "-" to protect file paths + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + VERSION_STRING=$(echo "$BRANCH_NAME" | tr '/' '-') + fi + echo "GIT_TAG_VERSION=${VERSION_STRING}" >> $GITHUB_ENV diff --git a/.github/workflows/build-companion-firmwares.yml b/.github/workflows/build-companion-firmwares.yml index 771fa6d5..8fd796f7 100644 --- a/.github/workflows/build-companion-firmwares.yml +++ b/.github/workflows/build-companion-firmwares.yml @@ -10,33 +10,8 @@ on: - 'companion-*' jobs: - - build: - runs-on: ubuntu-latest - steps: - - - name: Clone Repo - uses: actions/checkout@v6 - - - name: Setup Build Environment - uses: ./.github/actions/setup-build-environment - - - name: Build Firmwares - env: - FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }} - run: /usr/bin/env bash build.sh build-companion-firmwares - - - name: Upload Workflow Artifacts - uses: actions/upload-artifact@v7 - with: - name: companion-firmwares - path: out - - - name: Create Release - uses: softprops/action-gh-release@v3 - if: startsWith(github.ref, 'refs/tags/') - with: - name: Companion Firmware ${{ env.GIT_TAG_VERSION }} - body: "" - draft: true - files: out/* \ No newline at end of file + build-companion-firmwares: + uses: ./.github/workflows/firmware-builder.yml + with: + firmware_type: 'companion' + release_title_prefix: 'Companion Firmware' diff --git a/.github/workflows/build-repeater-firmwares.yml b/.github/workflows/build-repeater-firmwares.yml index 3185d4b2..46b9076c 100644 --- a/.github/workflows/build-repeater-firmwares.yml +++ b/.github/workflows/build-repeater-firmwares.yml @@ -10,33 +10,8 @@ on: - 'repeater-*' jobs: - - build: - runs-on: ubuntu-latest - steps: - - - name: Clone Repo - uses: actions/checkout@v6 - - - name: Setup Build Environment - uses: ./.github/actions/setup-build-environment - - - name: Build Firmwares - env: - FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }} - run: /usr/bin/env bash build.sh build-repeater-firmwares - - - name: Upload Workflow Artifacts - uses: actions/upload-artifact@v7 - with: - name: repeater-firmwares - path: out - - - name: Create Release - uses: softprops/action-gh-release@v3 - if: startsWith(github.ref, 'refs/tags/') - with: - name: Repeater Firmware ${{ env.GIT_TAG_VERSION }} - body: "" - draft: true - files: out/* \ No newline at end of file + build-repeater-firmwares: + uses: ./.github/workflows/firmware-builder.yml + with: + firmware_type: 'repeater' + release_title_prefix: 'Repeater Firmware' diff --git a/.github/workflows/build-room-server-firmwares.yml b/.github/workflows/build-room-server-firmwares.yml index 127095a8..c8bd19f7 100644 --- a/.github/workflows/build-room-server-firmwares.yml +++ b/.github/workflows/build-room-server-firmwares.yml @@ -10,33 +10,8 @@ on: - 'room-server-*' jobs: - - build: - runs-on: ubuntu-latest - steps: - - - name: Clone Repo - uses: actions/checkout@v6 - - - name: Setup Build Environment - uses: ./.github/actions/setup-build-environment - - - name: Build Firmwares - env: - FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }} - run: /usr/bin/env bash build.sh build-room-server-firmwares - - - name: Upload Workflow Artifacts - uses: actions/upload-artifact@v7 - with: - name: room-server-firmwares - path: out - - - name: Create Release - uses: softprops/action-gh-release@v3 - if: startsWith(github.ref, 'refs/tags/') - with: - name: Room Server Firmware ${{ env.GIT_TAG_VERSION }} - body: "" - draft: true - files: out/* \ No newline at end of file + build-room-server-firmwares: + uses: ./.github/workflows/firmware-builder.yml + with: + firmware_type: 'room-server' + release_title_prefix: 'Room Server Firmware' diff --git a/.github/workflows/firmware-builder.yml b/.github/workflows/firmware-builder.yml new file mode 100644 index 00000000..adc9e424 --- /dev/null +++ b/.github/workflows/firmware-builder.yml @@ -0,0 +1,90 @@ +name: Firmware Builder + +on: + workflow_call: + inputs: + firmware_type: + required: true + type: string + release_title_prefix: + required: true + type: string + +jobs: + + generate-build-matrix: + runs-on: ubuntu-latest + outputs: + targets: ${{ steps.get-build-targets.outputs.targets }} + steps: + + - name: Clone Repo + uses: actions/checkout@v6 + + - name: Setup Build Environment + uses: ./.github/actions/setup-build-environment + + - name: Get Build Targets + id: get-build-targets + run: | + # get list of firmwares to build + TARGET_LIST=$(/usr/bin/env bash build.sh get-${{ inputs.firmware_type }}-firmwares-to-build) + + # convert targets separated by new line into a json array string + JSON_ARRAY=$(echo "$TARGET_LIST" | jq -R -s -c 'split("\n") | map(select(length > 0))') + + # use json array as targets result + echo "targets=$JSON_ARRAY" >> $GITHUB_OUTPUT + + build: + needs: generate-build-matrix + runs-on: ubuntu-latest + continue-on-error: true # don't fail entire build if one board fails to build + strategy: + matrix: + target: ${{ fromJson(needs.generate-build-matrix.outputs.targets) }} + fail-fast: false # don't cancel other builds if one board fails to build + steps: + + - name: Clone Repo + uses: actions/checkout@v6 + + - name: Setup Build Environment + uses: ./.github/actions/setup-build-environment + + - name: Build Firmware + env: + FIRMWARE_VERSION: ${{ env.GIT_TAG_VERSION }} + run: /usr/bin/env bash build.sh build-firmware ${{ matrix.target }} + + - name: Upload Workflow Artifacts + uses: actions/upload-artifact@v7 + with: + name: "${{ matrix.target }}" + path: out + + create-release: + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') # only create release for tagged builds + steps: + + - name: Clone Repo + uses: actions/checkout@v6 + + - name: Setup Build Environment + uses: ./.github/actions/setup-build-environment + + - name: Download All Artifacts + uses: actions/download-artifact@v8 + with: + merge-multiple: true + path: out + + - name: Create Release + uses: softprops/action-gh-release@v3 + with: + name: "${{ inputs.release_title_prefix }} ${{ env.GIT_TAG_VERSION }}" + body: "" + draft: true + files: out/* diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index aba04832..826c4cb3 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -25,7 +25,7 @@ jobs: python3 -B scripts/check_arduinojson_pin.py - name: Run Unit Tests - run: pio test -e native -vv + run: pio test -e native -e native_kiss_modem -vv - name: Upload Test Results # Upload test results even if the test step failed. diff --git a/.github/workflows/stale-bot.yml b/.github/workflows/stale-bot.yml new file mode 100644 index 00000000..ec166587 --- /dev/null +++ b/.github/workflows/stale-bot.yml @@ -0,0 +1,32 @@ +name: 'Run Stale Bot' +on: + schedule: + - cron: '30 1 * * *' # daily at 1:30am + workflow_dispatch: {} + +permissions: + actions: write + issues: write + pull-requests: write + +jobs: + close-issues: + # only run on main repo, not forks + if: github.repository == 'meshcore-dev/MeshCore' + runs-on: ubuntu-latest + steps: + - name: Close Stale Issues + uses: actions/stale@v10 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + # auto close issues + days-before-issue-stale: 60 + days-before-issue-close: 7 + exempt-issue-labels: "keep-open" + stale-issue-label: "stale" + stale-issue-message: "This issue is stale because it has been open for 60 days with no activity. Remove the stale label or add a comment if this issue is still relevant, otherwise this issue will automatically close in 7 days." + close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale." + # don't auto close prs + days-before-pr-stale: -1 + days-before-pr-close: -1 + \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..a4b2207d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,57 @@ +# Security Policy + +## Supported Versions + +Security fixes are applied to the latest release only. We do not backport +fixes to older versions. + +| Version | Supported | +|---------|-----------| +| 1.15+ | ✅ | +| <1.15 | ❌ | + +## Reporting a Vulnerability + +**Please do not report security vulnerabilities through public GitHub issues.** + +Use GitHub's private vulnerability reporting instead: +1. Go to the **Security** tab of this repository +2. Click **Report a vulnerability** +3. Fill in the details and submit + +### What to include + +A useful report tells us: +- Which component or file is affected +- What an attacker can do (impact) and under what conditions +- A minimal reproduction case or proof-of-concept if you have one +- Whether you believe it is remotely exploitable + +You do not need a working exploit to report. An incomplete report is better +than no report. + +## What to expect + +This is a volunteer-maintained open-source project. We will do our best to +respond in a reasonable timeframe, but cannot commit to specific deadlines. + +We ask that you give us a fair opportunity to investigate and address the +issue before any public disclosure. If you have not heard back after +**90 days**, feel free to follow up or proceed with disclosure at your +discretion. + +## Scope + +In scope: +- Remote code execution, memory corruption, or denial-of-service via crafted + radio packets +- Authentication or encryption bypasses +- Vulnerabilities in the packet routing or path handling logic + +Out of scope: +- Physical access attacks (e.g., JTAG, UART extraction of keys) +- Regulatory compliance (duty cycle, frequency restrictions) +- Jamming or other physical-layer radio interference +- Issues in third-party libraries (RadioLib, Crypto, etc.) — report those + upstream +- "Best practice" suggestions without a demonstrated attack path diff --git a/boards/heltec_v4.json b/boards/heltec_v4.json index 36cdfc04..0d3a52cd 100644 --- a/boards/heltec_v4.json +++ b/boards/heltec_v4.json @@ -9,7 +9,7 @@ "extra_flags": [ "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", - "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_MODE=1", "-DARDUINO_RUNNING_CORE=1", "-DARDUINO_EVENT_RUNNING_CORE=1" ], @@ -40,4 +40,4 @@ }, "url": "https://heltec.org/", "vendor": "heltec" -} \ No newline at end of file +} diff --git a/build.sh b/build.sh index 5bf13055..13939a5d 100755 --- a/build.sh +++ b/build.sh @@ -3,6 +3,9 @@ PIO_CONFIG_JSON=$(pio project config --json-output) #!/usr/bin/env bash +# exit when any command fails +set -e + global_usage() { cat - <` + +**Parameters:** +- `value`: Maximum flood hop count (0-64) for an advert packet + +**Default:** `8` --- diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 9a996224..3f4dbf9c 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -41,7 +41,7 @@ Maximum unescaped frame size: 512 bytes. | Command | Value | Data | Description | |-------------|--------|--------------------|-------------------------------------------------------------| -| Data | `0x00` | Raw packet | Queue packet for transmission | +| Data | `0x00` | Raw packet | Queue packet for transmission (one pending at a time) | | TXDELAY | `0x01` | Delay (1 byte) | Transmitter keyup delay in 10ms units (default: 50 = 500ms) | | Persistence | `0x02` | P (1 byte) | CSMA persistence parameter 0-255 (default: 63) | | SlotTime | `0x03` | Interval (1 byte) | CSMA slot interval in 10ms units (default: 10 = 100ms) | @@ -58,6 +58,12 @@ Maximum unescaped frame size: 512 bytes. Data frames carry raw packet data only, with no metadata prepended. The Data command payload is limited to 255 bytes to match the MeshCore maximum transmission unit (MAX_TRANS_UNIT); frames larger than 255 bytes are silently dropped. The KISS specification recommends at least 1024 bytes for general-purpose TNCs; this modem is intended for MeshCore packets only, whose protocol MTU is 255 bytes. +Only one packet may be pending for radio transmission at a time. If the host sends a second Data frame before the first has completed, the modem responds with Error (0xF1) and TxBusy (0x07). + +### Host Output Backpressure + +Outbound frames are encoded into a 2-slot queue and flushed when serial output space is available; `loop()` never blocks on writes. Radio TX state advances independently of host read speed. TxDone is retained until it can be queued. If the outbound queue is full, the modem responds with Error (0xF1) and TxBusy (0x07). Hosts should read serial promptly to avoid delayed responses. + ### CSMA Behavior The TNC implements p-persistent CSMA for half-duplex operation: @@ -156,15 +162,15 @@ Response codes use the high-bit convention: `response = command | 0x80`. Generic | MacFailed | `0x04` | MAC verification failed | | UnknownCmd | `0x05` | Unknown sub-command | | EncryptFailed | `0x06` | Encryption failed | -| TxBusy | `0x07` | Transmit busy | +| TxBusy | `0x07` | Radio TX busy, or host output queue full | ### Unsolicited Events The TNC sends these SetHardware frames without a preceding request: -**TxDone (0xF8)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. +**TxDone (0xF8)**: Sent after radio transmission completes. Contains a single byte: 0x01 for success, 0x00 for failure. Delivery to the host may be delayed under serial backpressure but is not dropped. -**RxMeta (0xF9)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Enabled by default; can be toggled with SetSignalReport. Standard KISS clients ignore this frame. +**RxMeta (0xF9)**: Sent after each standard data frame (type 0x00) with SNR (1 byte, signed, value x4) and RSSI (1 byte, signed, dBm). Queued with the data frame; omitted if the data frame cannot be queued. Enabled by default; toggle with SetSignalReport. Standard KISS clients ignore this frame. ## Data Formats diff --git a/docs/payloads.md b/docs/payloads.md index 94768889..b4a98b38 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -12,6 +12,7 @@ Inside each [MeshCore Packet](./packet_format.md) is a payload, identified by th * Group text message (unverified). * Group datagram (unverified). * Multi-part packet +* Control data packet * Custom packet (raw bytes, custom encryption). This document defines the structure of each of these payload types. @@ -22,7 +23,7 @@ NOTE: all 16 and 32-bit integer fields are Little Endian. * Node hash: the first byte of the node's public key -# Node advertisement +## Node advertisement This kind of payload notifies receivers that a node exists, and gives information about the node | Field | Size (bytes) | Description | @@ -56,7 +57,7 @@ Appdata Flags | `0x40` | has feature 2 | Reserved for future use. | | `0x80` | has name | appdata contains a node name | -# Acknowledgement +## Acknowledgement An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement can be sent in the "extra" payload (see [Returned Path](#returned-path)) instead of as a separate acknowledgement packet. CLI commands do not cause acknowledgement responses, neither discrete nor extra. @@ -65,7 +66,7 @@ An acknowledgement that a message was received. Note that for returned path mess | checksum | 4 | CRC checksum of message timestamp, text, and sender pubkey | -# Returned path, request, response, and plain text message +## Returned path, request, response, and plain text message Returned path, request, response, and plain text messages are all formatted in the same way. See the subsection for more details about the ciphertext's associated plaintext representation. @@ -76,7 +77,7 @@ Returned path, request, response, and plain text messages are all formatted in t | cipher MAC | 2 | MAC for encrypted data in next field | | ciphertext | rest of payload | encrypted message, see subsections below for details | -## Returned path +### Returned path Returned path messages provide a description of the route a packet took from the original author. Receivers will send returned path messages to the author of the original message. @@ -87,7 +88,7 @@ Returned path messages provide a description of the route a packet took from the | extra type | 1 | extra, bundled payload type, eg., acknowledgement or response. Same values as in [Packet Format](./packet_format.md) | | extra | rest of data | extra, bundled payload content, follows same format as main content defined by this document | -## Request +### Request | Field | Size (bytes) | Description | |--------------|-----------------|------------------------------------------| @@ -101,7 +102,7 @@ For the common chat/server helpers in `BaseChatMesh`, the current request type v | `0x01` | get stats | get stats of repeater or room server | | `0x02` | keepalive | keep-alive request used for maintained connections | -### Get stats +#### Get stats Gets information about the node, possibly including the following: @@ -124,32 +125,32 @@ Gets information about the node, possibly including the following: * Number posted (?) * Number of post pushes (?) -### Get telemetry data +#### Get telemetry data Not defined in `BaseChatMesh`. Sensor- and application-specific request payloads may be implemented by higher-level firmware. -### Get Telemetry +#### Get Telemetry Not defined in `BaseChatMesh`. -### Get Min/Max/Ave (Sensor nodes) +#### Get Min/Max/Ave (Sensor nodes) Not defined in `BaseChatMesh`. -### Get Access List +#### Get Access List Not defined in `BaseChatMesh`. -### Get Neighbors +#### Get Neighbors Not defined in `BaseChatMesh`. -### Get Owner Info +#### Get Owner Info Not defined in `BaseChatMesh`. -## Response +### Response | Field | Size (bytes) | Description | |---------|-----------------|-----------------------------------| @@ -157,7 +158,7 @@ Not defined in `BaseChatMesh`. Response contents are opaque application data. There is no single generic response envelope beyond the encrypted payload wrapper shown above. -## Plain text message +### Plain text message | Field | Size (bytes) | Description | |--------------------|-----------------|-----------------------------------------------------------------------------------| @@ -165,7 +166,7 @@ Response contents are opaque application data. There is no single generic respon | txt_type + attempt | 1 | upper six bits are txt_type (see below), lower two bits are attempt number (0..3) | | message | rest of payload | the message content, see next table | -Flags +txt_type | Value | Description | Message content | |--------|---------------------------|--------------------------------------------------------------------------| @@ -173,7 +174,7 @@ Flags | `0x01` | CLI command | the command text of the message | | `0x02` | signed plain text message | first four bytes is sender pubkey prefix, followed by plain text message | -# Anonymous request +## Anonymous request | Field | Size (bytes) | Description | |------------------|-----------------|-------------------------------------------| @@ -182,15 +183,22 @@ Flags | cipher MAC | 2 | MAC for encrypted data in next field | | ciphertext | rest of payload | encrypted message, see below for details | -Plaintext message +### Room server login | Field | Size (bytes) | Description | |----------------|-----------------|-------------------------------------------------------------------------------| -| timestamp | 4 | send time (unix timestamp) | -| sync timestamp | 4 | NOTE: room server only! - sender's "sync messages SINCE x" timestamp | -| password | rest of message | password for repeater/room | +| timestamp | 4 | sender time (unix timestamp) | +| sync timestamp | 4 | sender's "sync messages SINCE x" timestamp | +| password | rest of message | password for room | -## Repeater - Regions request +### Repeater/Sensor login + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| password | rest of message | password for repeater/sensor | + +### Repeater - Regions request | Field | Size (bytes) | Description | |----------------|--------------|------------------------------| @@ -199,7 +207,7 @@ Plaintext message | reply path len | 1 | path len for reply | | reply path | (variable) | reply path | -## Repeater - Owner info request +### Repeater - Owner info request | Field | Size (bytes) | Description | |----------------|--------------|------------------------------| @@ -208,7 +216,7 @@ Plaintext message | reply path len | 1 | path len for reply | | reply path | (variable) | reply path | -## Repeater - Clock and status request +### Repeater - Clock and status request | Field | Size (bytes) | Description | |----------------|--------------|------------------------------| @@ -218,7 +226,7 @@ Plaintext message | reply path | (variable) | reply path | -# Group text message +## Group text message | Field | Size (bytes) | Description | |--------------|-----------------|----------------------------------------------| @@ -228,7 +236,7 @@ Plaintext message The plaintext contained in the ciphertext matches the format described in [plain text message](#plain-text-message). Specifically, it consists of a four byte timestamp, a flags byte, and the message. The flags byte will generally be `0x00` because it is a "plain text message". The message will be of the form `: ` (eg., `user123: I'm on my way`). -# Group datagram +## Group datagram | Field | Size (bytes) | Description | |--------------|-----------------|----------------------------------------------| @@ -245,8 +253,32 @@ The data contained in the ciphertext uses the format below: | data | rest of payload | (depends on data type) | -TODO: describe what datagram looks like +## Control data -# Custom packet +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | upper 4 bits is sub_type | +| data | rest of payload | typically unencrypted data | + +### DISCOVER_REQ (sub_type) + +| Field | Size (bytes) | Description | +|--------------|-----------------|----------------------------------------------| +| flags | 1 | 0x8 (upper 4 bits), prefix_only (lowest bit) | +| type_filter | 1 | bit for each ADV_TYPE_* | +| tag | 4 | randomly generate by sender | +| since | 4 | (optional) epoch timestamp (0 by default) | + +### DISCOVER_RESP (sub_type) + +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | 0x9 (upper 4 bits), node_type (lower 4) | +| snr | 1 | signed, SNR*4 | +| tag | 4 | reflected back from DISCOVER_REQ | +| pubkey | 8 or 32 | node's ID (or prefix) | + + +## Custom packet Custom packets have no defined format. diff --git a/docs/qr_codes.md b/docs/qr_codes.md index 364efa8a..3516a76a 100644 --- a/docs/qr_codes.md +++ b/docs/qr_codes.md @@ -12,8 +12,10 @@ meshcore://channel/add?name=Public&secret=8b3387e9c5cdea6ac9e5edbaa115cd72 **Parameters**: -- `name`: Channel name (URL-encoded if needed) +- `name`: Channel name (URL-encoded) - `secret`: 16-byte secret represented as 32 hex characters +- `region_scope`: Region Scope (optional, URL-encoded if provided) + - Supported by MeshCore App v1.47.0+ ## Add Contact diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 0eee45ae..b25b1442 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #ifdef PIN_BUZZER @@ -25,10 +25,10 @@ enum class UIEventType { class AbstractUITask { protected: mesh::MainBoard* _board; - BaseSerialInterface* _serial; + MultiSerialInterface* _interfaceManager; bool _connected; - AbstractUITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial) { + AbstractUITask(mesh::MainBoard* board, MultiSerialInterface* interfaceManager) : _board(board), _interfaceManager(interfaceManager) { _connected = false; } @@ -36,9 +36,9 @@ public: void setHasConnection(bool connected) { _connected = connected; } bool hasConnection() const { return _connected; } uint16_t getBattMilliVolts() const { return _board->getBattMilliVolts(); } - bool isSerialEnabled() const { return _serial->isEnabled(); } - void enableSerial() { _serial->enable(); } - void disableSerial() { _serial->disable(); } + bool isBluetoothEnabled() const { return _interfaceManager->isBluetoothEnabled(); } + void enableBluetooth() { _interfaceManager->enableBluetooth(); } + void disableBluetooth() { _interfaceManager->disableBluetooth(); } virtual void msgRead(int msgcount) = 0; virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0; virtual void notify(UIEventType t = UIEventType::none) = 0; diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 0f3a0f9c..06c56a7a 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -189,17 +189,22 @@ bool DataStore::saveMainIdentity(const mesh::LocalIdentity &identity) { return identity_store.save("_main", identity); } -void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon) { - if (_fs->exists("/new_prefs")) { - loadPrefsInt("/new_prefs", prefs, node_lat, node_lon); // new filename - } else if (_fs->exists("/node_prefs")) { - loadPrefsInt("/node_prefs", prefs, node_lat, node_lon); - savePrefs(prefs, node_lat, node_lon); // save to new filename - _fs->remove("/node_prefs"); // remove old +void DataStore::loadPrefs(NodePrefs& prefs) { + if (_fs->exists("/prefs.json")) { + File file = openRead(_fs, "/prefs.json"); + if (file) { + prefs.loadSerial(file); // new Serial prefs + file.close(); + } + } else if (_fs->exists("/new_prefs")) { + loadPrefsInt("/new_prefs", prefs); + if (savePrefs(prefs) ) { // save to new format + //_fs->remove("/new_prefs"); // remove old + } } } -void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& node_lat, double& node_lon) { +void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs) { File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; @@ -207,12 +212,12 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 file.read(pad, 4); // 36 - file.read((uint8_t *)&node_lat, sizeof(node_lat)); // 40 - file.read((uint8_t *)&node_lon, sizeof(node_lon)); // 48 + file.read((uint8_t *)&_prefs.node_lat, sizeof(_prefs.node_lat)); // 40 + file.read((uint8_t *)&_prefs.node_lon, sizeof(_prefs.node_lon)); // 48 file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.read((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62 + file.read((uint8_t *)&_prefs._client_repeat, sizeof(_prefs._client_repeat)); // 62 file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 @@ -234,48 +239,21 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90 file.read((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121 + // migrate old fields + _prefs.setRepeatEn(_prefs._client_repeat != 0); + file.close(); } } -void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_lon) { - File file = openWrite(_fs, "/new_prefs"); +bool DataStore::savePrefs(NodePrefs& _prefs) { + File file = openWrite(_fs, "/prefs.json"); if (file) { - uint8_t pad[8]; - memset(pad, 0, sizeof(pad)); - - file.write((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 - file.write((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 - file.write(pad, 4); // 36 - file.write((uint8_t *)&node_lat, sizeof(node_lat)); // 40 - file.write((uint8_t *)&node_lon, sizeof(node_lon)); // 48 - file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 - file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 - file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.write((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62 - file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 - file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 - file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 - file.write((uint8_t *)&_prefs.telemetry_mode_base, sizeof(_prefs.telemetry_mode_base)); // 69 - file.write((uint8_t *)&_prefs.telemetry_mode_loc, sizeof(_prefs.telemetry_mode_loc)); // 70 - file.write((uint8_t *)&_prefs.telemetry_mode_env, sizeof(_prefs.telemetry_mode_env)); // 71 - file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 - file.write((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 - file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 - file.write((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78 - file.write(pad, 1); // 79 - file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 - file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 - file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 - file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 - file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 - file.write((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88 - file.write((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89 - file.write((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90 - file.write((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121 - + bool success = _prefs.saveSerial(file); file.close(); + return success; } + return false; } void DataStore::loadContacts(DataStoreHost* host) { diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index af5ee7af..e0a145ca 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -19,7 +19,7 @@ class DataStore { mesh::RTCClock* _clock; IdentityStore identity_store; - void loadPrefsInt(const char *filename, NodePrefs& prefs, double& node_lat, double& node_lon); + void loadPrefsInt(const char *filename, NodePrefs& prefs); #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) void checkAdvBlobFile(); #endif @@ -33,8 +33,8 @@ public: FILESYSTEM* getSecondaryFS() const { return _fsExtra; } bool loadMainIdentity(mesh::LocalIdentity &identity); bool saveMainIdentity(const mesh::LocalIdentity &identity); - void loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon); - void savePrefs(const NodePrefs& prefs, double node_lat, double node_lon); + void loadPrefs(NodePrefs& prefs); + bool savePrefs(NodePrefs& prefs); void loadContacts(DataStoreHost* host); void saveContacts(DataStoreHost* host, bool (*filter)(const ContactInfo& c) = NULL); void loadChannels(DataStoreHost* host); diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 46202406..46f70d78 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -265,9 +265,6 @@ bool MyMesh::getCADEnabled() const { int MyMesh::getInterferenceThreshold() const { return 0; // disabled for now, until currentRSSI() problem is resolved } -bool MyMesh::getCADEnabled() const { - return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default) -} int MyMesh::calcRxDelay(float score, uint32_t air_time) const { if (_prefs.rx_delay_base <= 0.0f) return 0; @@ -487,7 +484,7 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) { } bool MyMesh::allowPacketForward(const mesh::Packet* packet) { - return _prefs.client_repeat != 0; + return _prefs.isRepeatEn(); } void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis) { @@ -880,7 +877,6 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe send_unscoped = false; // defaults - memset(&_prefs, 0, sizeof(_prefs)); _prefs.airtime_factor = 1.0; // one half strcpy(_prefs.node_name, "NONAME"); _prefs.freq = LORA_FREQ; @@ -891,6 +887,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.gps_enabled = 0; // GPS disabled by default _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed + _prefs.setRepeatEn(false); #if defined(USE_SX1262) || defined(USE_SX1268) #ifdef SX126X_RX_BOOSTED_GAIN _prefs.rx_boosted_gain = SX126X_RX_BOOSTED_GAIN; @@ -935,7 +932,9 @@ void MyMesh::begin(bool has_display) { #endif // load persisted prefs - _store->loadPrefs(_prefs, sensors.node_lat, sensors.node_lon); + _store->loadPrefs(_prefs); + sensors.node_lat = _prefs.node_lat; + sensors.node_lon = _prefs.node_lon; // sanitise bad pref values _prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f); @@ -1037,7 +1036,7 @@ void MyMesh::handleCmdFrame(size_t len) { i += 40; StrHelper::strzcpy((char *)&out_frame[i], FIRMWARE_VERSION, 20); i += 20; - out_frame[i++] = _prefs.client_repeat; // v9+ + out_frame[i++] = _prefs.isRepeatEn() ? 1 : 0; // v9+ out_frame[i++] = _prefs.path_hash_mode; // v10+ _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_APP_START && @@ -1399,7 +1398,7 @@ void MyMesh::handleCmdFrame(size_t len) { _prefs.cr = cr; _prefs.freq = (float)freq / 1000.0; _prefs.bw = (float)bw / 1000.0; - _prefs.client_repeat = repeat; + _prefs.setRepeatEn(repeat != 0); savePrefs(); radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index f4190f30..d95b073f 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -165,7 +165,11 @@ protected: } public: - void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } + void savePrefs() { + _prefs.node_lat = sensors.node_lat; + _prefs.node_lon = sensors.node_lon; + _store->savePrefs(_prefs); + } #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 48c381ce..39a5386a 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -1,5 +1,6 @@ #pragma once #include // For uint8_t, uint32_t +#include #define TELEM_MODE_DENY 0 #define TELEM_MODE_ALLOW_FLAGS 1 // use contact.flags @@ -8,30 +9,128 @@ #define ADVERT_LOC_NONE 0 #define ADVERT_LOC_SHARE 1 -struct NodePrefs { // persisted to file - float airtime_factor; +class NodePrefs : public ConfigSerializer { // persisted to file +public: + float airtime_factor = 0; char node_name[32]; - float freq; - uint8_t sf; - uint8_t cr; - uint8_t multi_acks; - uint8_t manual_add_contacts; - float bw; - int8_t tx_power_dbm; - uint8_t telemetry_mode_base; - uint8_t telemetry_mode_loc; - uint8_t telemetry_mode_env; - float rx_delay_base; - uint32_t ble_pin; - uint8_t advert_loc_policy; - uint8_t buzzer_quiet; - uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) - uint32_t gps_interval; // GPS read interval in seconds - uint8_t autoadd_config; // bitmask for auto-add contacts config - uint8_t rx_boosted_gain; // SX126x RX boosted gain mode (0=power saving, 1=boosted) - uint8_t client_repeat; - uint8_t path_hash_mode; // which path mode to use when sending - uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) + double node_lat = 0, node_lon = 0; + float freq = 0; + uint8_t sf = 0; + uint8_t cr = 0; + uint8_t multi_acks = 0; + uint8_t manual_add_contacts = 0; + float bw = 0; + int8_t tx_power_dbm = 0; + uint8_t telemetry_mode_base = 0; + uint8_t telemetry_mode_loc = 0; + uint8_t telemetry_mode_env = 0; + float rx_delay_base = 0; + uint32_t ble_pin = 0; + uint8_t advert_loc_policy = 0; + uint8_t buzzer_quiet = 0; + uint8_t gps_enabled = 0; // GPS enabled flag (0=disabled, 1=enabled) + uint32_t gps_interval = 0; // GPS read interval in seconds + uint8_t autoadd_config = 0; // bitmask for auto-add contacts config + uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode (0=power saving, 1=boosted) + uint8_t _client_repeat = 0; // DEPRECATED -> use repeat.disable_fwd + uint8_t path_hash_mode = 0; // which path mode to use when sending + uint8_t autoadd_max_hops = 0; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) char default_scope_name[31]; uint8_t default_scope_key[16]; + +private: + class RadioPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now) + NodePrefs* _parent; + protected: + void structure() override { + def("freq", _parent->freq); + def("bw", _parent->bw); + def("sf", _parent->sf); + def("cr", _parent->cr); + //def("cad", _parent->cad_enabled); + //def("int_thr", _parent->interference_threshold); + def("rxgain", _parent->rx_boosted_gain); + def("fem_rxgain", _parent->rx_boosted_gain); + def("tx", _parent->tx_power_dbm); + def("af", _parent->airtime_factor); + def("rxdelay", _parent->rx_delay_base); + //def("f_txdelay", _parent->tx_delay_factor); currently hard-coded + //def("d_txdelay", _parent->direct_tx_delay_factor); currently hard-coded + //def("agc_int", _parent->agc_reset_interval); + def("hash_mode", _parent->path_hash_mode); + def("multi_ack", _parent->multi_acks); + } + public: + RadioPrefs(NodePrefs* parent) : _parent(parent) { } + }; + RadioPrefs radio; + + class GPSPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now) + NodePrefs* _parent; + protected: + void structure() override { + def("en", _parent->gps_enabled); // boolean + def("int", _parent->gps_interval); // interval in seconds + def("adv_loc", _parent->advert_loc_policy); + } + public: + GPSPrefs(NodePrefs* parent) : _parent(parent) { } + }; + GPSPrefs gps; + + class RepeatPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now) + public: + uint8_t disable_fwd = 1; + protected: + void structure() override { + def("disable", disable_fwd); + //def("f_max", flood_max); + //def("f_max_uns", flood_max_unscoped); + //def("f_max_adv", flood_max_advert); + //def("loop", loop_detect); + } + }; + RepeatPrefs repeat; + + class CompanionPrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("auto_max", _parent->autoadd_max_hops); // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) + def("defs_nm", _parent->default_scope_name, sizeof(_parent->default_scope_name)); + def("defs_key", (void *) _parent->default_scope_key, sizeof(_parent->default_scope_key)); + def("pin", _parent->ble_pin); + def("buzz_q", _parent->buzzer_quiet); + def("auto_add", _parent->autoadd_config); // bitmask for auto-add contacts config + def("man_add", _parent->manual_add_contacts); + def("tel_base", _parent->telemetry_mode_base); + def("tel_loc", _parent->telemetry_mode_loc); + def("tel_env", _parent->telemetry_mode_env); + } + public: + CompanionPrefs(NodePrefs* parent) : _parent(parent) { } + }; + CompanionPrefs companion; + +protected: + void structure() override { + def("name", node_name, sizeof(node_name)); + //def("adv_int", advert_interval); + //def("f_adv_int", flood_advert_interval); + def("lat", node_lat); + def("lon", node_lon); + def("radio", radio); + def("gps", gps); + def("repeat", repeat); + def("comp", companion); + } +public: + NodePrefs() : radio(this), gps(this), companion(this) { + node_name[0] = 0; + default_scope_name[0] = 0; + memset(default_scope_key, 0, sizeof(default_scope_key)); + } + // new accessor methods + bool isRepeatEn() const { return repeat.disable_fwd == 0; } + void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; } }; \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index d39aeef9..89f0e6cb 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -12,6 +12,59 @@ static uint32_t _atoi(const char* sp) { return n; } +// interface manager +#include +MultiSerialInterface interface_manager; + +// include bluetooth interface +#if defined(BLE_PIN_CODE) + #ifdef ESP32 + // include esp32 bluetooth interface + #include + SerialBLEInterface bluetooth_interface; + #elif defined(NRF52_PLATFORM) + // include nrf52 bluetooth interface + #include + SerialBLEInterface bluetooth_interface; + #else + #error "SerialBLEInterface is not defined for this platform" + #endif +#endif + +// include wifi interface +#ifdef WIFI_SSID + #ifndef TCP_PORT + #define TCP_PORT 5000 + #endif + #ifdef ESP32 + // include esp32 wifi interface + #include + SerialWifiInterface wifi_interface; + #else + #error "SerialWifiInterface is not defined for this platform" + #endif +#endif + +// include usb interface +#if defined(ENABLE_USB_INTERFACE) + #include + ArduinoSerialInterface usb_serial_interface; +#endif + +// include ethernet interface +#if defined(ETHERNET_ENABLED) + #include + ETHERNET_CLASS ethernet_interface; +#endif + +// include hardware serial interface +#if defined(SERIAL_RX) + #include + ArduinoSerialInterface hardware_serial_interface; + HardwareSerial companion_serial(1); +#endif + +// platform file system #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include #if defined(QSPIFLASH) @@ -34,64 +87,10 @@ static uint32_t _atoi(const char* sp) { DataStore store(SPIFFS, rtc_clock); #endif -#ifdef ESP32 - #ifdef WIFI_SSID - #include - SerialWifiInterface serial_interface; - #ifndef TCP_PORT - #define TCP_PORT 5000 - #endif - #elif defined(BLE_PIN_CODE) - #include - SerialBLEInterface serial_interface; - #elif defined(SERIAL_RX) - #include - ArduinoSerialInterface serial_interface; - HardwareSerial companion_serial(1); - #else - #include - ArduinoSerialInterface serial_interface; - #endif -#elif defined(RP2040_PLATFORM) - //#ifdef WIFI_SSID - // #include - // SerialWifiInterface serial_interface; - // #ifndef TCP_PORT - // #define TCP_PORT 5000 - // #endif - // #elif defined(BLE_PIN_CODE) - // #include - // SerialBLEInterface serial_interface; - #if defined(SERIAL_RX) - #include - ArduinoSerialInterface serial_interface; - HardwareSerial companion_serial(1); - #else - #include - ArduinoSerialInterface serial_interface; - #endif -#elif defined(NRF52_PLATFORM) - #ifdef BLE_PIN_CODE - #include - SerialBLEInterface serial_interface; - #elif defined(ETHERNET_ENABLED) - #include - SerialEthernetInterface serial_interface; - #else - #include - ArduinoSerialInterface serial_interface; - #endif -#elif defined(STM32_PLATFORM) - #include - ArduinoSerialInterface serial_interface; -#else - #error "need to define a serial interface" -#endif - /* GLOBAL OBJECTS */ #ifdef DISPLAY_CLASS #include "UITask.h" - UITask ui_task(&board, &serial_interface); + UITask ui_task(&board, &interface_manager); #endif StdRNG fast_rng; @@ -161,26 +160,6 @@ void setup() { false #endif ); - -#ifdef BLE_PIN_CODE - serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); - the_mesh.startInterface(serial_interface); -#elif defined(ETHERNET_ENABLED) - Serial.print("Waiting for serial to connect...\n"); - unsigned long timeout = millis(); - while (!Serial) { - if ((millis() - timeout) < 5000) { delay(100); } else { break; } - } - Serial.println("Initializing Ethernet adapter..."); - if (serial_interface.begin()) { - the_mesh.startInterface(serial_interface); - } else { - Serial.println("ETH: Init failed, continuing without Ethernet (mesh only)"); - } -#else - serial_interface.begin(Serial); - the_mesh.startInterface(serial_interface); -#endif #elif defined(RP2040_PLATFORM) LittleFS.begin(); store.begin(); @@ -191,22 +170,6 @@ void setup() { false #endif ); - - //#ifdef WIFI_SSID - // WiFi.begin(WIFI_SSID, WIFI_PWD); - // serial_interface.begin(TCP_PORT); - // #elif defined(BLE_PIN_CODE) - // char dev_name[32+16]; - // sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); - // serial_interface.begin(dev_name, the_mesh.getBLEPin()); - #if defined(SERIAL_RX) - companion_serial.setPins(SERIAL_RX, SERIAL_TX); - companion_serial.begin(115200); - serial_interface.begin(companion_serial); - #else - serial_interface.begin(Serial); - #endif - the_mesh.startInterface(serial_interface); #elif defined(ESP32) SPIFFS.begin(true); store.begin(); @@ -217,7 +180,17 @@ void setup() { false #endif ); +#else + #error "need to define filesystem" +#endif +// add bluetooth interface +#if defined(BLE_PIN_CODE) + bluetooth_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); + interface_manager.addInterface(InterfaceType::Bluetooth, &bluetooth_interface); +#endif + +// add wifi interface #ifdef WIFI_SSID board.setInhibitSleep(true); // prevent sleep when WiFi is active WiFi.setAutoReconnect(true); @@ -233,21 +206,31 @@ void setup() { }); WiFi.begin(WIFI_SSID, WIFI_PWD); - serial_interface.begin(TCP_PORT); -#elif defined(BLE_PIN_CODE) - serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); -#elif defined(SERIAL_RX) - companion_serial.setPins(SERIAL_RX, SERIAL_TX); - companion_serial.begin(115200); - serial_interface.begin(companion_serial); -#else - serial_interface.begin(Serial); -#endif - the_mesh.startInterface(serial_interface); -#else - #error "need to define filesystem" + wifi_interface.begin(TCP_PORT); + interface_manager.addInterface(InterfaceType::WiFi, &wifi_interface); #endif +// add usb interface +#if defined(ENABLE_USB_INTERFACE) + usb_serial_interface.begin(Serial); + interface_manager.addInterface(InterfaceType::USB, &usb_serial_interface); +#endif + +// add ethernet interface +#if defined(ETHERNET_ENABLED) + ethernet_interface.begin(); + interface_manager.addInterface(InterfaceType::Ethernet, ðernet_interface); +#endif + +// add hardware serial interface +#if defined(SERIAL_RX) + companion_serial.setPins(SERIAL_RX, SERIAL_TX); + companion_serial.begin(115200); + hardware_serial_interface.begin(companion_serial); + interface_manager.addInterface(InterfaceType::HardwareSerial, &hardware_serial_interface); +#endif + + the_mesh.startInterface(interface_manager); sensors.begin(); #if ENV_INCLUDE_GPS == 1 @@ -263,6 +246,7 @@ void setup() { void loop() { the_mesh.loop(); + interface_manager.loop(); sensors.loop(); #ifdef DISPLAY_CLASS ui_task.loop(); @@ -272,10 +256,6 @@ void loop() { external_watchdog.loop(); #endif -#ifdef ETHERNET_ENABLED - serial_interface.loop(); -#endif - if (!the_mesh.hasPendingWork()) { #if defined(NRF52_PLATFORM) board.sleep(0); // nrf ignores seconds param, sleeps whenever possible diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index a26dc19a..051f3b31 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -53,23 +53,24 @@ public: int render(DisplayDriver& display) override { // meshcore logo - display.setColor(DisplayDriver::BLUE); + display.setColor(UIColor::corp_blue); int logoWidth = 128; display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); // meshcore website const char* website = "https://meshcore.io"; - display.setColor(DisplayDriver::LIGHT); + display.setColor(UIColor::primary_txt); display.setTextSize(1); uint16_t websiteWidth = display.getTextWidth(website); display.setCursor((display.width() - websiteWidth) / 2, 22); display.print(website); // version info - display.setColor(DisplayDriver::LIGHT); + display.setColor(UIColor::primary_txt); display.setTextSize(1); display.drawTextCentered(display.width()/2, 35, _version_info); + display.setColor(UIColor::secondary_txt); display.setTextSize(1); display.drawTextCentered(display.width()/2, 48, FIRMWARE_BUILD_DATE); @@ -128,7 +129,7 @@ class HomeScreen : public UIScreen { int iconHeight = 10; int iconX = display.width() - iconWidth - 5; // Position the icon near the top-right corner int iconY = 0; - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::title_txt); // battery outline display.drawRect(iconX, iconY, iconWidth, iconHeight); @@ -143,7 +144,7 @@ class HomeScreen : public UIScreen { // show muted icon if buzzer is muted #ifdef PIN_BUZZER if (_task->isBuzzerQuiet()) { - display.setColor(DisplayDriver::RED); + display.setColor(UIColor::warning_txt); display.drawXbm(iconX - 9, iconY + 1, muted_icon, 8, 8); } #endif @@ -188,34 +189,41 @@ public: } int render(DisplayDriver& display) override { + display.setColor(UIColor::title_bkg); + display.fillRect(0, 0, display.width(), 12); char tmp[80]; // node name display.setTextSize(1); - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::title_txt); char filtered_name[sizeof(_node_prefs->node_name)]; display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name)); - display.setCursor(0, 0); + display.setCursor(0, 2); display.print(filtered_name); // battery voltage renderBatteryIndicator(display, _task->getBattMilliVolts()); // curr page indicator + if (UIColor::title_bkg == UIColor::window_bkg) { + display.setColor(UIColor::title_txt); + } else { + display.setColor(UIColor::title_bkg); + } int y = 14; int x = display.width() / 2 - 5 * (HomePage::Count-1); for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) { if (i == _page) { - display.fillRect(x-1, y-1, 3, 3); + display.fillRect(x-1, y-1, 4, 4); } else { - display.fillRect(x, y, 1, 1); + display.fillRect(x, y, 2, 2); } } if (_page == HomePage::FIRST) { - display.setColor(DisplayDriver::YELLOW); + display.setColor(UIColor::primary_txt); display.setTextSize(2); sprintf(tmp, "MSG: %d", _task->getMsgCount()); - display.drawTextCentered(display.width() / 2, 20, tmp); + display.drawTextCentered(display.width() / 2, 22, tmp); #ifdef WIFI_SSID IPAddress ip = WiFi.localIP(); @@ -224,19 +232,19 @@ public: display.drawTextCentered(display.width() / 2, 54, tmp); #endif if (_task->hasConnection()) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::warning_txt); display.setTextSize(1); display.drawTextCentered(display.width() / 2, 43, "< Connected >"); } else if (the_mesh.getBLEPin() != 0) { // BT pin - display.setColor(DisplayDriver::RED); + display.setColor(UIColor::warning_txt); display.setTextSize(2); sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); display.drawTextCentered(display.width() / 2, 43, tmp); } } else if (_page == HomePage::RECENT) { the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::primary_txt); int y = 20; for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { auto a = &recent[i]; @@ -260,7 +268,7 @@ public: display.print(tmp); } } else if (_page == HomePage::RADIO) { - display.setColor(DisplayDriver::YELLOW); + display.setColor(UIColor::primary_txt); display.setTextSize(1); // freq / sf display.setCursor(0, 20); @@ -279,15 +287,17 @@ public: sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor()); display.print(tmp); } else if (_page == HomePage::BLUETOOTH) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::corp_blue); display.drawXbm((display.width() - 32) / 2, 18, - _task->isSerialEnabled() ? bluetooth_on : bluetooth_off, + _task->isBluetoothEnabled() ? bluetooth_on : bluetooth_off, 32, 32); + display.setColor(UIColor::secondary_txt); display.setTextSize(1); display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL); } else if (_page == HomePage::ADVERT) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::corp_blue); display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); + display.setColor(UIColor::secondary_txt); display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL); #if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { @@ -305,24 +315,33 @@ public: #else strcpy(buf, gps_state ? "gps on" : "gps off"); #endif + display.setColor(UIColor::primary_txt); display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { y = y + 12; + display.setColor(UIColor::secondary_txt); display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { + display.setColor(UIColor::primary_txt); strcpy(buf, nmea->isValid()?"fix":"no fix"); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; + display.setColor(UIColor::secondary_txt); display.drawTextLeftAlign(0, y, "sat"); + display.setColor(UIColor::primary_txt); sprintf(buf, "%d", nmea->satellitesCount()); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; + display.setColor(UIColor::secondary_txt); display.drawTextLeftAlign(0, y, "pos"); + display.setColor(UIColor::primary_txt); sprintf(buf, "%.4f %.4f", nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; + display.setColor(UIColor::secondary_txt); display.drawTextLeftAlign(0, y, "alt"); + display.setColor(UIColor::primary_txt); sprintf(buf, "%.2f", nmea->getAltitude()/1000.); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; @@ -390,7 +409,9 @@ public: strcpy(name, "unk"); sprintf(buf, ""); } display.setCursor(0, y); + display.setColor(UIColor::secondary_txt); display.print(name); + display.setColor(UIColor::primary_txt); display.setCursor( display.width()-display.getTextWidth(buf)-1, y ); @@ -401,11 +422,13 @@ public: else sensors_scroll_offset = 0; #endif } else if (_page == HomePage::SHUTDOWN) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::corp_blue); display.setTextSize(1); if (_shutdown_init) { + display.setColor(UIColor::warning_txt); display.drawTextCentered(display.width() / 2, 34, "hibernating..."); } else { + display.setColor(UIColor::secondary_txt); display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL); } @@ -426,10 +449,10 @@ public: return true; } if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) { - if (_task->isSerialEnabled()) { // toggle Bluetooth on/off - _task->disableSerial(); + if (_task->isBluetoothEnabled()) { // toggle Bluetooth on/off + _task->disableBluetooth(); } else { - _task->enableSerial(); + _task->enableBluetooth(); } return true; } @@ -498,7 +521,7 @@ public: char tmp[16]; display.setCursor(0, 0); display.setTextSize(1); - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::corp_blue); sprintf(tmp, "Unread: %d", num_unread); display.print(tmp); @@ -518,13 +541,13 @@ public: display.drawRect(0, 11, display.width(), 1); // horiz line display.setCursor(0, 14); - display.setColor(DisplayDriver::YELLOW); + display.setColor(UIColor::secondary_txt); char filtered_origin[sizeof(p->origin)]; display.translateUTF8ToBlocks(filtered_origin, p->origin, sizeof(filtered_origin)); display.print(filtered_origin); display.setCursor(0, 25); - display.setColor(DisplayDriver::LIGHT); + display.setColor(UIColor::primary_txt); char filtered_msg[sizeof(p->msg)]; display.translateUTF8ToBlocks(filtered_msg, p->msg, sizeof(filtered_msg)); display.printWordWrap(filtered_msg, display.width()); @@ -806,9 +829,9 @@ void UITask::loop() { _display->setTextSize(1); int y = _display->height() / 3; int p = _display->height() / 32; - _display->setColor(DisplayDriver::DARK); + _display->setColor(UIColor::popup_bkg); _display->fillRect(p, y, _display->width() - p*2, y); - _display->setColor(DisplayDriver::LIGHT); // draw box border + _display->setColor(UIColor::popup_txt); // draw box border _display->drawRect(p, y, _display->width() - p*2, y); _display->drawTextCentered(_display->width() / 2, y + p*3, _alert); _next_refresh = _alert_expiry; // will need refresh when alert is dismissed @@ -845,7 +868,7 @@ void UITask::loop() { if (_display != NULL) { _display->startFrame(); _display->setTextSize(2); - _display->setColor(DisplayDriver::RED); + _display->setColor(UIColor::warning_txt); _display->drawTextCentered(_display->width() / 2, 20, "Low Battery."); _display->drawTextCentered(_display->width() / 2, 40, "Shutting Down!"); _display->endFrame(); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index a77ad6e7..52d3ffa1 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -65,7 +65,7 @@ class UITask : public AbstractUITask { public: - UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { + UITask(mesh::MainBoard* board, MultiSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { next_batt_chck = _next_refresh = 0; ui_started_at = 0; curr = NULL; diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index b48f6412..09fc8e77 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -167,7 +167,7 @@ void UITask::renderBatteryIndicator(uint16_t batteryMilliVolts) { int iconHeight = 12; int iconX = _display->width() - iconWidth - 5; // Position the icon near the top-right corner int iconY = 0; - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::primary_txt); // battery outline _display->drawRect(iconX, iconY, iconWidth, iconHeight); @@ -188,7 +188,7 @@ void UITask::renderCurrScreen() { _display->setTextSize(1.4); uint16_t textWidth = _display->getTextWidth(_alert); _display->setCursor((_display->width() - textWidth) / 2, 22); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::warning_txt); _display->print(_alert); _alert[0] = 0; _need_refresh = true; @@ -197,30 +197,29 @@ void UITask::renderCurrScreen() { // render message preview _display->setCursor(0, 0); _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::primary_txt); _display->print(_node_prefs->node_name); _display->setCursor(0, 12); - _display->setColor(DisplayDriver::YELLOW); + _display->setColor(UIColor::secondary_txt); _display->print(_origin); _display->setCursor(0, 24); - _display->setColor(DisplayDriver::LIGHT); _display->print(_msg); _display->setCursor(_display->width() - 28, 9); _display->setTextSize(2); - _display->setColor(DisplayDriver::ORANGE); + _display->setColor(UIColor::primary_txt); sprintf(tmp, "%d", _msgcount); _display->print(tmp); - _display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114 + _display->setColor(UIColor::secondary_txt); // last color will be kept on T114 } else if ((millis() - ui_started_at) < BOOT_SCREEN_MILLIS) { // boot screen // meshcore logo - _display->setColor(DisplayDriver::BLUE); + _display->setColor(UIColor::corp_blue); int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); // version info - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setTextSize(1); uint16_t textWidth = _display->getTextWidth(_version_info); _display->setCursor((_display->width() - textWidth) / 2, 22); @@ -229,7 +228,7 @@ void UITask::renderCurrScreen() { // node name _display->setCursor(0, 0); _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::primary_txt); _display->print(_node_prefs->node_name); // battery voltage @@ -237,7 +236,7 @@ void UITask::renderCurrScreen() { // freq / sf _display->setCursor(0, 20); - _display->setColor(DisplayDriver::YELLOW); + _display->setColor(UIColor::secondary_txt); sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); _display->print(tmp); @@ -248,14 +247,14 @@ void UITask::renderCurrScreen() { // BT pin if (!_connected && the_mesh.getBLEPin() != 0) { - _display->setColor(DisplayDriver::RED); + _display->setColor(UIColor::warning_txt); _display->setTextSize(2); _display->setCursor(0, 43); sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); _display->print(tmp); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::primary_txt); } else { - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); } } _need_refresh = false; diff --git a/examples/companion_radio/ui-orig/UITask.h b/examples/companion_radio/ui-orig/UITask.h index 60cd0d04..961c07a0 100644 --- a/examples/companion_radio/ui-orig/UITask.h +++ b/examples/companion_radio/ui-orig/UITask.h @@ -54,7 +54,7 @@ class UITask : public AbstractUITask { public: - UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { + UITask(mesh::MainBoard* board, MultiSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { _next_refresh = 0; ui_started_at = 0; } diff --git a/examples/companion_radio/ui-tiny/ScrollingStatusBar.h b/examples/companion_radio/ui-tiny/ScrollingStatusBar.h index f5943c9b..9967489f 100644 --- a/examples/companion_radio/ui-tiny/ScrollingStatusBar.h +++ b/examples/companion_radio/ui-tiny/ScrollingStatusBar.h @@ -104,7 +104,7 @@ public: if (_status[0] == 0) return; display.setTextSize(1); - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::primary_txt); // if (_needs_redraw) { // _text_width = display.getTextWidth(_status); diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index 452c02d4..b6bdbcf4 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -56,24 +56,24 @@ public: int render(DisplayDriver& display) override { if (millis() < version_after) { // meshcore logo - display.setColor(DisplayDriver::BLUE); + display.setColor(UIColor::corp_blue); int logoWidth = 72; display.drawXbm(0, 0, meshcore_logo, 72, 36); } else { // meshcore website const char* website = "meshcore.io"; - display.setColor(DisplayDriver::LIGHT); + display.setColor(UIColor::primary_txt); display.setTextSize(1); uint16_t websiteWidth = display.getTextWidth(website); display.setCursor((display.width() - websiteWidth) / 2, 9); display.print(website); // version info - display.setColor(DisplayDriver::LIGHT); display.setTextSize(1); display.drawTextCentered(display.width()/2, 18, _version_info); + display.setColor(UIColor::secondary_txt); display.setTextSize(1); display.drawTextCentered(display.width()/2, 27, FIRMWARE_BUILD_DATE); } @@ -163,7 +163,7 @@ public: // display.print(filtered_name); - display.setColor(DisplayDriver::YELLOW); + display.setColor(UIColor::primary_txt); display.setTextSize(2); sprintf(tmp, "MSG: %d", _task->getMsgCount()); display.setCursor(0, 10); @@ -180,19 +180,19 @@ public: display.drawTextCentered(display.width() / 2, 54, tmp); #endif if (_task->hasConnection()) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::warning_txt); display.setTextSize(1); display.drawTextCentered(display.width() / 2, display.height()-8, "< Connected >"); } else if (the_mesh.getBLEPin() != 0) { // BT pin - display.setColor(DisplayDriver::RED); + display.setColor(UIColor::warning_txt); display.setTextSize(2); sprintf(tmp, "Pin:%d", the_mesh.getBLEPin()); display.drawTextCentered(display.width() / 2, display.height()-8, tmp); } } else if (_page == HomePage::RECENT) { the_mesh.getRecentlyHeard(recent, UI_RECENT_LIST_SIZE); - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::primary_txt); int y = 8; for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { auto a = &recent[i]; @@ -216,7 +216,7 @@ public: display.print(tmp); } } else if (_page == HomePage::RADIO) { - display.setColor(DisplayDriver::YELLOW); + display.setColor(UIColor::primary_txt); display.setTextSize(1); // frequency and spreading factor display.setCursor(0, 8); @@ -238,14 +238,14 @@ public: display.drawTextRightAlign(display.width(), 26, tmp); } else if (_page == HomePage::BLUETOOTH) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::corp_blue); display.drawXbm((display.width() - 32) / 2, 8, - _task->isSerialEnabled() ? bluetooth_on : bluetooth_off, + _task->isBluetoothEnabled() ? bluetooth_on : bluetooth_off, 32, 32); display.setTextSize(1); // display.drawTextCentered(display.width() / 2, 40 - 11, "toggle: " PRESS_LABEL); } else if (_page == HomePage::ADVERT) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::corp_blue); display.drawXbm((display.width() - 32) / 2, 8, advert_icon, 32, 32); // display.drawTextCentered(display.width() / 2, 40 - 11, "advert: " PRESS_LABEL); #if ENV_INCLUDE_GPS == 1 @@ -264,9 +264,11 @@ public: #else strcpy(buf, gps_state ? "gps on" : "gps off"); #endif + display.setColor(UIColor::primary_txt); display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { // y = y + 8; + display.setColor(UIColor::warning_txt); display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { if (!gps_state || !nmea->isValid()) { @@ -274,6 +276,7 @@ public: } else { sprintf(buf, "%d sat", nmea->satellitesCount()); } + display.setColor(UIColor::primary_txt); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 8; sprintf(buf, "lat %.4f", @@ -349,8 +352,10 @@ public: r.skipData(type); strcpy(name, "unk"); sprintf(buf, ""); } + display.setColor(UIColor::secondary_txt); display.setCursor(0, y); display.print(name); + display.setColor(UIColor::primary_txt); display.setCursor( display.width()-display.getTextWidth(buf)-1, y ); @@ -361,7 +366,7 @@ public: else sensors_scroll_offset = 0; #endif } else if (_page == HomePage::SHUTDOWN) { - display.setColor(DisplayDriver::GREEN); + display.setColor(UIColor::secondary_txt); display.setTextSize(1); if (_shutdown_init) { display.drawTextCentered(display.width() / 2, 20, "hibernating..."); @@ -386,10 +391,10 @@ public: return true; } if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) { - if (_task->isSerialEnabled()) { // toggle Bluetooth on/off - _task->disableSerial(); + if (_task->isBluetoothEnabled()) { // toggle Bluetooth on/off + _task->disableBluetooth(); } else { - _task->enableSerial(); + _task->enableBluetooth(); } return true; } @@ -672,7 +677,7 @@ void UITask::loop() { _cached_batt_mv, isBuzzerQuiet(), getGPSState(), - isSerialEnabled()); + isBluetoothEnabled()); bool status_dirty = _statusBar.needsRedraw(); bool content_dirty = (millis() >= _next_refresh && curr); @@ -692,9 +697,9 @@ void UITask::loop() { _display->setTextSize(1); int y = _display->height() / 3; int p = _display->height() / 32; - _display->setColor(DisplayDriver::DARK); + _display->setColor(UIColor::popup_bkg); _display->fillRect(p, y, _display->width() - p*2, y); - _display->setColor(DisplayDriver::LIGHT); // draw box border + _display->setColor(UIColor::popup_txt); // draw box border _display->drawRect(p, y, _display->width() - p*2, y); _display->drawTextCentered(_display->width() / 2, y + p*3, _alert); _next_refresh = _alert_expiry; // will need refresh when alert is dismissed diff --git a/examples/companion_radio/ui-tiny/UITask.h b/examples/companion_radio/ui-tiny/UITask.h index 344e48b9..dc689478 100644 --- a/examples/companion_radio/ui-tiny/UITask.h +++ b/examples/companion_radio/ui-tiny/UITask.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include @@ -71,7 +71,7 @@ class UITask : public AbstractUITask { public: - UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { + UITask(mesh::MainBoard* board, MultiSerialInterface* serial) : AbstractUITask(board, serial), _display(NULL), _sensors(NULL) { next_batt_chck = _next_refresh = 0; _cached_batt_mv = 0; ui_started_at = 0; diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index eeab1501..7dcfc8d7 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -22,6 +22,7 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _getStatsCallback = nullptr; _config = {0, 0, 0, 0, 0}; _signal_report_enabled = true; + resetOutputQueue(); } void KissModem::begin() { @@ -30,37 +31,168 @@ void KissModem::begin() { _rx_active = false; _has_pending_tx = false; _tx_state = TX_IDLE; + resetOutputQueue(); } -void KissModem::writeByte(uint8_t b) { - if (b == KISS_FEND) { - _serial.write(KISS_FESC); - _serial.write(KISS_TFEND); - } else if (b == KISS_FESC) { - _serial.write(KISS_FESC); - _serial.write(KISS_TFESC); - } else { - _serial.write(b); +void KissModem::resetOutputQueue() { + _tx_frame_head = 0; + _tx_frame_tail = 0; + _tx_frame_count = 0; + _tx_busy_error_pending = false; + _tx_done_pending = false; + _tx_done_result = 0; +} + +void KissModem::popTxFrame() { + _tx_frame_head = (uint8_t)((_tx_frame_head + 1) % KISS_TX_FRAME_QUEUE_DEPTH); + _tx_frame_count--; +} + +uint16_t KissModem::appendEscapedByte(uint8_t* dest, uint16_t idx, uint16_t max_len, uint8_t b) { + if (b == KISS_FEND || b == KISS_FESC) { + if (idx + 2 > max_len) { + return 0; + } + dest[idx++] = KISS_FESC; + dest[idx++] = (b == KISS_FEND) ? KISS_TFEND : KISS_TFESC; + return idx; } + if (idx + 1 > max_len) { + return 0; + } + dest[idx++] = b; + return idx; } -void KissModem::writeFrame(uint8_t type, const uint8_t* data, uint16_t len) { - _serial.write(KISS_FEND); - writeByte(type); +uint16_t KissModem::encodeFrame(uint8_t type, const uint8_t* data, uint16_t len, uint8_t* dest, uint16_t max_len) { + if (max_len < KISS_FRAME_BOUNDARY_BYTES) { + return 0; + } + + uint16_t idx = 0; + dest[idx++] = KISS_FEND; + + idx = appendEscapedByte(dest, idx, max_len, type); + if (idx == 0) { + return 0; + } + for (uint16_t i = 0; i < len; i++) { - writeByte(data[i]); + idx = appendEscapedByte(dest, idx, max_len, data[i]); + if (idx == 0) { + return 0; + } } - _serial.write(KISS_FEND); + + if (idx + 1 > max_len) { + return 0; + } + dest[idx++] = KISS_FEND; + return idx; } -void KissModem::writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len) { - _serial.write(KISS_FEND); - writeByte(KISS_CMD_SETHARDWARE); - writeByte(sub_cmd); - for (uint16_t i = 0; i < len; i++) { - writeByte(data[i]); +bool KissModem::tryFlushFrames() { + while (_tx_frame_count > 0) { + const uint8_t idx = _tx_frame_head; + const uint16_t frame_len = _tx_frame_len[idx]; + uint16_t written_len = _tx_frame_written[idx]; + + if (written_len >= frame_len) { + popTxFrame(); + continue; + } + + const int available = _serial.availableForWrite(); + if (available <= 0) { + return false; + } + + const uint16_t remaining = frame_len - written_len; + const uint16_t chunk_len = (available < (int)remaining) ? (uint16_t)available : remaining; + if (chunk_len == 0) { + return false; + } + + size_t chunk_written = _serial.write(_tx_frame_buf[idx] + written_len, chunk_len); + if (chunk_written == 0) { + return false; + } + + written_len += (uint16_t)chunk_written; + _tx_frame_written[idx] = written_len; + + if (written_len < frame_len) { + return false; + } + + popTxFrame(); } - _serial.write(KISS_FEND); + return true; +} + +bool KissModem::queueFrame(uint8_t type, const uint8_t* data, uint16_t len, bool mark_busy_error) { + if (_tx_frame_count >= KISS_TX_FRAME_QUEUE_DEPTH && !tryFlushFrames()) { + if (mark_busy_error) { + _tx_busy_error_pending = true; + } + return false; + } + const uint8_t idx = _tx_frame_tail; + uint16_t frame_len = encodeFrame(type, data, len, _tx_frame_buf[idx], sizeof(_tx_frame_buf[idx])); + if (frame_len == 0) { + return false; + } + + _tx_frame_len[idx] = frame_len; + _tx_frame_written[idx] = 0; + _tx_frame_tail = (uint8_t)((_tx_frame_tail + 1) % KISS_TX_FRAME_QUEUE_DEPTH); + _tx_frame_count++; + tryFlushFrames(); + return true; +} + +bool KissModem::queuePendingBusyError() { + if (!_tx_busy_error_pending) { + return true; + } + const uint8_t err = HW_ERR_TX_BUSY; + if (!queueHardwareFrame(HW_RESP_ERROR, &err, 1, false)) { + return false; + } + _tx_busy_error_pending = false; + return true; +} + +bool KissModem::queueHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len, bool mark_busy_error) { + if (len > KISS_MAX_FRAME_SIZE) { + return false; + } + _tx_hw_payload[0] = sub_cmd; + if (len > 0) { + memcpy(_tx_hw_payload + 1, data, len); + } + return queueFrame(KISS_CMD_SETHARDWARE, _tx_hw_payload, len + 1, mark_busy_error); +} + +bool KissModem::queuePendingTxDone() { + if (!_tx_done_pending) { + return true; + } + if (!queueHardwareFrame(HW_RESP_TX_DONE, &_tx_done_result, 1, false)) { + return false; + } + _tx_done_pending = false; + return true; +} + +void KissModem::setTxDonePending(uint8_t result) { + _tx_done_result = result; + _tx_done_pending = true; + _tx_state = TX_DONE_PENDING; +} + +bool KissModem::writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len) { + return queueHardwareFrame(sub_cmd, data, len, true); } void KissModem::writeHardwareError(uint8_t error_code) { @@ -68,6 +200,8 @@ void KissModem::writeHardwareError(uint8_t error_code) { } void KissModem::loop() { + tryFlushFrames(); + while (_serial.available()) { uint8_t b = _serial.read(); @@ -106,6 +240,8 @@ void KissModem::loop() { } processTx(); + tryFlushFrames(); + queuePendingBusyError(); } void KissModem::processFrame() { @@ -295,10 +431,7 @@ void KissModem::processTx() { _tx_timer = millis(); _tx_state = TX_SENDING; } else { - uint8_t result = 0x00; - writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); - _has_pending_tx = false; - _tx_state = TX_IDLE; + setTxDonePending(0x00); } } break; @@ -306,14 +439,15 @@ void KissModem::processTx() { case TX_SENDING: if (_radio.isSendComplete()) { _radio.onSendFinished(); - uint8_t result = 0x01; - writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); - _has_pending_tx = false; - _tx_state = TX_IDLE; + setTxDonePending(0x01); } else if (millis() - _tx_timer >= _radio.getEstAirtimeFor(_pending_tx_len) * KISS_TX_TIMEOUT_FACTOR) { _radio.onSendFinished(); - uint8_t result = 0x00; - writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); + setTxDonePending(0x00); + } + break; + + case TX_DONE_PENDING: + if (queuePendingTxDone()) { _has_pending_tx = false; _tx_state = TX_IDLE; } @@ -322,8 +456,7 @@ void KissModem::processTx() { } void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { - writeFrame(KISS_CMD_DATA, packet, len); - if (_signal_report_enabled) { + if (queueFrame(KISS_CMD_DATA, packet, len) && _signal_report_enabled) { uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi }; writeHardwareFrame(HW_RESP_RX_META, meta, 2); } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index bbe99d6d..a23e459b 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -13,6 +13,14 @@ #define KISS_MAX_FRAME_SIZE 512 #define KISS_MAX_PACKET_SIZE 255 +#define KISS_FRAME_BOUNDARY_BYTES 2 +#define KISS_TYPE_BYTES 1 +#define KISS_HW_SUBCMD_BYTES 1 +#define KISS_MAX_ESCAPABLE_BYTES (KISS_MAX_FRAME_SIZE + KISS_TYPE_BYTES + KISS_HW_SUBCMD_BYTES) +#define KISS_MAX_ESCAPED_PAYLOAD_SIZE (2 * KISS_MAX_ESCAPABLE_BYTES) +#define KISS_MAX_ENCODED_FRAME_SIZE (KISS_FRAME_BOUNDARY_BYTES + KISS_MAX_ESCAPED_PAYLOAD_SIZE) +#define KISS_TX_FRAME_QUEUE_DEPTH 2 +#define KISS_HW_MAX_PAYLOAD_SIZE (KISS_MAX_FRAME_SIZE + KISS_HW_SUBCMD_BYTES) #define KISS_CMD_DATA 0x00 #define KISS_CMD_TXDELAY 0x01 @@ -94,7 +102,8 @@ enum TxState { TX_WAIT_CLEAR, TX_SLOT_WAIT, TX_DELAY, - TX_SENDING + TX_SENDING, + TX_DONE_PENDING }; class KissModem { @@ -130,10 +139,28 @@ class KissModem { RadioConfig _config; bool _signal_report_enabled; + uint8_t _tx_frame_buf[KISS_TX_FRAME_QUEUE_DEPTH][KISS_MAX_ENCODED_FRAME_SIZE]; + uint16_t _tx_frame_len[KISS_TX_FRAME_QUEUE_DEPTH]; + uint16_t _tx_frame_written[KISS_TX_FRAME_QUEUE_DEPTH]; + uint8_t _tx_frame_head; + uint8_t _tx_frame_tail; + uint8_t _tx_frame_count; + bool _tx_busy_error_pending; + bool _tx_done_pending; + uint8_t _tx_done_result; + uint8_t _tx_hw_payload[KISS_HW_MAX_PAYLOAD_SIZE]; - void writeByte(uint8_t b); - void writeFrame(uint8_t type, const uint8_t* data, uint16_t len); - void writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len); + static uint16_t appendEscapedByte(uint8_t* dest, uint16_t idx, uint16_t max_len, uint8_t b); + static uint16_t encodeFrame(uint8_t type, const uint8_t* data, uint16_t len, uint8_t* dest, uint16_t max_len); + void resetOutputQueue(); + void popTxFrame(); + bool tryFlushFrames(); + bool queueFrame(uint8_t type, const uint8_t* data, uint16_t len, bool mark_busy_error = true); + bool queuePendingBusyError(); + bool queueHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len, bool mark_busy_error); + bool queuePendingTxDone(); + void setTxDonePending(uint8_t result); + bool writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len); void writeHardwareError(uint8_t error_code); void processFrame(); void handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len); @@ -182,4 +209,5 @@ public: bool isTxBusy() const { return _tx_state != TX_IDLE; } /** True only when radio is actually transmitting; use to skip recvRaw in main loop. */ bool isActuallyTransmitting() const { return _tx_state == TX_SENDING; } + bool isHostOutputBackedUp() const { return _tx_frame_count > 0 || _tx_busy_error_pending || _tx_done_pending; } }; diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 7fbcaed1..5836a694 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -20,6 +20,8 @@ #define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 #define AGC_RESET_INTERVAL_MS 30000 +#define USB_TX_TIMEOUT_MS 50 +#define USB_TX_BUFFER_SIZE 1024 StdRNG rng; mesh::LocalIdentity identity; @@ -111,6 +113,10 @@ void setup() { uint32_t start = millis(); while (!Serial && millis() - start < 3000) delay(10); delay(100); +#if defined(ESP32) && ARDUINO_USB_MODE + Serial.setTxTimeoutMs(USB_TX_TIMEOUT_MS); + Serial.setTxBufferSize(USB_TX_BUFFER_SIZE); +#endif modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); #endif @@ -126,7 +132,7 @@ void setup() { void loop() { modem->loop(); - if (!modem->isActuallyTransmitting()) { + if (!modem->isActuallyTransmitting() && !modem->isHostOutputBackedUp()) { if (!modem->isTxBusy()) { if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { radio_driver.resetAGC(); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 79d68d64..7f781961 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1007,7 +1007,6 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc #endif // defaults - memset(&_prefs, 0, sizeof(_prefs)); _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index d96afdf9..84b88a6d 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -12,6 +12,7 @@ #include #elif defined(ESP32) #include + using File = fs::File; #endif #ifdef WITH_RS232_BRIDGE diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index 28e2e5fc..2cd75ee4 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -62,18 +62,17 @@ void UITask::renderCurrScreen() { char tmp[80]; if (millis() < _started_at + BOOT_SCREEN_MILLIS) { // boot screen // meshcore logo - _display->setColor(DisplayDriver::BLUE); + _display->setColor(UIColor::corp_blue); int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); // meshcore website const char* website = "https://meshcore.io"; - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setTextSize(1); _display->drawTextCentered(_display->width() / 2, 22, website); // version info - _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); _display->drawTextCentered(_display->width() / 2, 35, _version_info); @@ -82,13 +81,13 @@ void UITask::renderCurrScreen() { _display->drawTextCentered(_display->width() / 2, 48, node_type); } else if (_powering_off_at > 0) { // meshcore logo - _display->setColor(DisplayDriver::BLUE); + _display->setColor(UIColor::corp_blue); int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); // meshcore website const char* website = "https://meshcore.io"; - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setTextSize(1); _display->drawTextCentered(_display->width()/ 2, 22, website); @@ -103,10 +102,10 @@ void UITask::renderCurrScreen() { // save confirmed on-device: show ground truth even if the browser // lost its connection before the confirmation reached it _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::corp_blue); _display->setCursor(0, 14); _display->print("Config saved!"); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setCursor(0, 30); _display->print("Rebooting..."); return; @@ -115,21 +114,21 @@ void UITask::renderCurrScreen() { if (WebConfigServer::getSetupInfo(wc_ssid, sizeof(wc_ssid), wc_ip, sizeof(wc_ip))) { // setup portal active: show join instructions instead of the home screen _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::corp_blue); _display->setCursor(0, 0); _display->print("Observer WiFi Setup"); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setCursor(0, 14); _display->print("Join WiFi:"); - _display->setColor(DisplayDriver::YELLOW); + _display->setColor(UIColor::warning_txt); _display->setCursor(6, 24); _display->print(wc_ssid); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setCursor(0, 40); _display->print("Then browse to:"); - _display->setColor(DisplayDriver::YELLOW); + _display->setColor(UIColor::warning_txt); _display->setCursor(6, 50); _display->print(wc_ip); return; @@ -138,12 +137,11 @@ void UITask::renderCurrScreen() { // node name _display->setCursor(0, 0); _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::primary_txt); _display->print(_node_prefs->node_name); // freq / sf _display->setCursor(0, 20); - _display->setColor(DisplayDriver::YELLOW); sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); _display->print(tmp); @@ -157,7 +155,7 @@ void UITask::renderCurrScreen() { if (WiFi.status() == WL_CONNECTED) { IPAddress ip = WiFi.localIP(); _display->setCursor(0, 40); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); _display->print(tmp); } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index a921fcf8..4ebc04e4 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -47,18 +47,35 @@ struct ServerStats { }; void MyMesh::addPost(ClientInfo *client, const char *postData) { - // TODO: suggested postData format: /<descrption> - posts[next_post_idx].author = client->id; // add to cyclic queue - StrHelper::strncpy(posts[next_post_idx].text, postData, MAX_POST_TEXT_LEN); + storePost(client->id, postData); +} - posts[next_post_idx].post_timestamp = getRTCClock()->getCurrentTimeUnique(); +void MyMesh::addSystemPost(const char *postData) { + if (!postData || postData[0] == 0) return; + + MESH_DEBUG_PRINTLN("room.post: addSystemPost: %s", postData); + + storePost(self_id, postData); +} + +void MyMesh::storePost(const mesh::Identity &author, const char *postData) { + int idx = next_post_idx; + // TODO: suggested postData format: <title>/<descrption> + posts[idx].author = author; // add to cyclic queue + StrHelper::strncpy(posts[idx].text, postData, MAX_POST_TEXT_LEN); + + posts[idx].post_timestamp = getRTCClock()->getCurrentTimeUnique(); + MESH_DEBUG_PRINTLN("room.post: storePost idx=%d text=%s", idx, posts[idx].text); + MESH_DEBUG_PRINTLN("room.post: timestamp=%u", posts[idx].post_timestamp); next_post_idx = (next_post_idx + 1) % MAX_UNSYNCED_POSTS; next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); _num_posted++; // stats + MESH_DEBUG_PRINTLN("room.post: next_post_idx=%d num_posted=%d push scheduled", next_post_idx, _num_posted); } void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { + MESH_DEBUG_PRINTLN("room.post: pushPostToClient text=%s", post.text); int len = 0; memcpy(&reply_data[len], &post.post_timestamp, 4); len += 4; // this is a PAST timestamp... but should be accepted by client @@ -830,7 +847,6 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc recv_pkt_region = NULL; // defaults - memset(&_prefs, 0, sizeof(_prefs)); _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // off by default, was 10.0 _prefs.tx_delay_factor = 0.5f; // was 0.25f; @@ -861,6 +877,14 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + +#if defined(USE_SX1262) || defined(USE_SX1268) +#ifdef SX126X_RX_BOOSTED_GAIN + _prefs.rx_boosted_gain = SX126X_RX_BOOSTED_GAIN; +#else + _prefs.rx_boosted_gain = 1; // enabled by default; +#endif +#endif _prefs.radio_fem_rxgain = 1; // Observer defaults (alert.*, etc.) moved to applyMQTTDefaults() — they live @@ -936,6 +960,7 @@ void MyMesh::begin(FILESYSTEM *fs) { radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setTxPower(_prefs.tx_power_dbm); + radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain); // LoRa FEM LNA (FEM boards only) updateAdvertTimer(); @@ -1108,6 +1133,10 @@ void MyMesh::setTxPower(int8_t power_dbm) { radio_driver.setTxPower(power_dbm); } +bool MyMesh::setRxBoostedGain(bool enable) { + return radio_driver.setRxBoostedGainMode(enable); +} + void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); @@ -1447,6 +1476,15 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else if (memcmp(command, "discover.scopes", 15) == 0) { strcpy(reply, "Err - not supported (requires PSRAM)"); #endif + } else if (strncmp(command, "room.post", 9) == 0) { + char* msg = command + 9; + while (*msg == ' ') msg++; + if (*msg == 0) { + snprintf(reply, MAX_POST_TEXT_LEN, "ERR empty message"); + } else { + addSystemPost(msg); + snprintf(reply, MAX_POST_TEXT_LEN, "OK"); + } } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index ec0cd15a..302055b8 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -211,6 +211,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks #endif void addPost(ClientInfo* client, const char* postData); + void storePost(const mesh::Identity& author, const char* postData); void pushPostToClient(ClientInfo* client, PostInfo& post); uint8_t getUnsyncedCount(ClientInfo* client); bool processAck(const uint8_t *data); @@ -273,6 +274,7 @@ public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); void begin(FILESYSTEM* fs); + void addSystemPost(const char* postData); const char* getFirmwareVer() override { return FIRMWARE_VERSION; } const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } @@ -309,6 +311,7 @@ public: void dumpLogFile() override; void setTxPower(int8_t power_dbm) override; + bool setRxBoostedGain(bool enable) override; void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index 7d9ecebb..97562297 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -54,20 +54,19 @@ void UITask::renderCurrScreen() { char tmp[80]; if (millis() < BOOT_SCREEN_MILLIS) { // boot screen // meshcore logo - _display->setColor(DisplayDriver::BLUE); + _display->setColor(UIColor::corp_blue); int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); // meshcore website const char* website = "https://meshcore.io"; - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setTextSize(1); uint16_t websiteWidth = _display->getTextWidth(website); _display->setCursor((_display->width() - websiteWidth) / 2, 22); _display->print(website); // version info - _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); uint16_t versionWidth = _display->getTextWidth(_version_info); _display->setCursor((_display->width() - versionWidth) / 2, 35); @@ -84,10 +83,10 @@ void UITask::renderCurrScreen() { // save confirmed on-device: show ground truth even if the browser // lost its connection before the confirmation reached it _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::corp_blue); _display->setCursor(0, 14); _display->print("Config saved!"); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setCursor(0, 30); _display->print("Rebooting..."); return; @@ -96,21 +95,21 @@ void UITask::renderCurrScreen() { if (WebConfigServer::getSetupInfo(wc_ssid, sizeof(wc_ssid), wc_ip, sizeof(wc_ip))) { // setup portal active: show join instructions instead of the home screen _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::corp_blue); _display->setCursor(0, 0); _display->print("Observer WiFi Setup"); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setCursor(0, 14); _display->print("Join WiFi:"); - _display->setColor(DisplayDriver::YELLOW); + _display->setColor(UIColor::warning_txt); _display->setCursor(6, 24); _display->print(wc_ssid); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setCursor(0, 40); _display->print("Then browse to:"); - _display->setColor(DisplayDriver::YELLOW); + _display->setColor(UIColor::warning_txt); _display->setCursor(6, 50); _display->print(wc_ip); return; @@ -119,12 +118,11 @@ void UITask::renderCurrScreen() { // node name _display->setCursor(0, 0); _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::primary_txt); _display->print(_node_prefs->node_name); // freq / sf _display->setCursor(0, 20); - _display->setColor(DisplayDriver::YELLOW); sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); _display->print(tmp); @@ -138,7 +136,7 @@ void UITask::renderCurrScreen() { if (WiFi.status() == WL_CONNECTED) { IPAddress ip = WiFi.localIP(); _display->setCursor(0, 40); - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); _display->print(tmp); } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index ffa363bb..097cfc35 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -327,9 +327,6 @@ uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) { int SensorMesh::getInterferenceThreshold() const { return _prefs.interference_threshold; } -bool SensorMesh::getCADEnabled() const { - return _prefs.cad_enabled; -} int SensorMesh::getAGCResetInterval() const { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } @@ -714,7 +711,6 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise set_radio_at = revert_radio_at = 0; // defaults - memset(&_prefs, 0, sizeof(_prefs)); _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f diff --git a/examples/simple_sensor/UITask.cpp b/examples/simple_sensor/UITask.cpp index 68a80607..75a0b465 100644 --- a/examples/simple_sensor/UITask.cpp +++ b/examples/simple_sensor/UITask.cpp @@ -49,20 +49,19 @@ void UITask::renderCurrScreen() { char tmp[80]; if (millis() < BOOT_SCREEN_MILLIS) { // boot screen // meshcore logo - _display->setColor(DisplayDriver::BLUE); + _display->setColor(UIColor::corp_blue); int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); // meshcore website const char* website = "https://meshcore.io"; - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(UIColor::primary_txt); _display->setTextSize(1); uint16_t websiteWidth = _display->getTextWidth(website); _display->setCursor((_display->width() - websiteWidth) / 2, 22); _display->print(website); // version info - _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); uint16_t versionWidth = _display->getTextWidth(_version_info); _display->setCursor((_display->width() - versionWidth) / 2, 35); @@ -77,12 +76,11 @@ void UITask::renderCurrScreen() { // node name _display->setCursor(0, 0); _display->setTextSize(1); - _display->setColor(DisplayDriver::GREEN); + _display->setColor(UIColor::primary_txt); _display->print(_node_prefs->node_name); // freq / sf _display->setCursor(0, 20); - _display->setColor(DisplayDriver::YELLOW); sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf); _display->print(tmp); diff --git a/platformio.ini b/platformio.ini index 73b0ff45..79c02a86 100644 --- a/platformio.ini +++ b/platformio.ini @@ -109,7 +109,7 @@ lib_deps = extends = arduino_base upload_protocol = picotool board_build.core = earlephilhower -platform = https://github.com/maxgerhardt/platform-raspberrypi.git#4e22a0d ; framework-arduinopico @ 1.50600.0+sha.6a1d13e9 +platform = https://github.com/maxgerhardt/platform-raspberrypi.git ; framework-arduinopico @ 1.50600.0+sha.6a1d13e9 build_flags = ${arduino_base.build_flags} -D RP2040_PLATFORM @@ -174,11 +174,29 @@ build_flags = -std=c++17 -I src -I test/mocks test_build_src = yes +test_ignore = test_kiss_modem build_src_filter = -<*> +<../src/Utils.cpp> +<../src/helpers/MQTTPayloadBuilder.cpp> +<../src/Packet.cpp> + +<../src/helpers/ConfigSerializer.cpp> +lib_deps = + google/googletest @ 1.17.0 + bblanchon/ArduinoJson @ 7.4.3 + +[env:native_kiss_modem] +platform = native +test_framework = googletest +build_flags = -std=c++17 + -I test/mocks + -I src + -I examples/kiss_modem +test_build_src = yes +test_filter = test_kiss_modem +build_src_filter = + -<*> + +<../examples/kiss_modem/KissModem.cpp> lib_deps = google/googletest @ 1.17.0 bblanchon/ArduinoJson @ 7.4.3 diff --git a/src/helpers/AdvertDataHelpers.cpp b/src/helpers/AdvertDataHelpers.cpp index 0e05620e..998733ae 100644 --- a/src/helpers/AdvertDataHelpers.cpp +++ b/src/helpers/AdvertDataHelpers.cpp @@ -1,4 +1,5 @@ #include <helpers/AdvertDataHelpers.h> +#include <helpers/UTF8Helpers.h> uint8_t AdvertDataBuilder::encodeTo(uint8_t app_data[]) { app_data[0] = _type; @@ -16,11 +17,12 @@ app_data[0] |= ADV_FEAT2_MASK; memcpy(&app_data[i], &_extra2, 2); i += 2; } - if (_name && *_name != 0) { - app_data[0] |= ADV_NAME_MASK; - const char* sp = _name; - while (*sp && i < MAX_ADVERT_DATA_SIZE) { - app_data[i++] = *sp++; + if (_name && *_name != 0) { + size_t name_len = mesh::validUtf8PrefixLength(_name, MAX_ADVERT_DATA_SIZE - i); + if (name_len > 0) { + app_data[0] |= ADV_NAME_MASK; + memcpy(&app_data[i], _name, name_len); + i += name_len; } } return i; @@ -84,4 +86,4 @@ void AdvertTimeHelper::formatRelativeTimeDiff(char dest[], int32_t seconds_from_ } } } -} \ No newline at end of file +} diff --git a/src/helpers/BaseSerialInterface.h b/src/helpers/BaseSerialInterface.h index e9a3f2ab..23933fcb 100644 --- a/src/helpers/BaseSerialInterface.h +++ b/src/helpers/BaseSerialInterface.h @@ -14,6 +14,7 @@ public: virtual bool isEnabled() const = 0; virtual bool isConnected() const = 0; + virtual void loop() {}; virtual bool isWriteBusy() const = 0; virtual size_t writeFrame(const uint8_t src[], size_t len) = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 7e786f23..cecec064 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -52,40 +52,40 @@ static const size_t LEGACY_MQTT_GAP_6SLOT = 306 + 6 * 186; // 1422 static const size_t LEGACY_MQTT_GAP_3SLOT = 306 + 3 * 186; // 864 static const size_t LEGACY_OBS_TAIL_MAX = 124; // rx_boosted(1) + flood(2) + snmp(25) + watchdog(1) + alert block(95) -// Bytes savePrefs() writes after owner_info (offsets 290-294): rx_boosted_gain, -// flood_max_unscoped, flood_max_advert, radio_fem_rxgain, cad_enabled. loadPrefsInt() -// treats any larger remainder as a legacy MQTT-gap file — keep this in sync with the -// trailing writes in savePrefs() whenever an upstream merge appends /com_prefs fields. +// Bytes the last binary layout wrote after owner_info (offsets 290-294): +// rx_boosted_gain, flood_max_unscoped, flood_max_advert, radio_fem_rxgain, +// cad_enabled. loadPrefsInt() treats any larger remainder as a legacy MQTT-gap +// file. Prefs are now written as JSON, so this describes read-side history only. static const size_t COM_PREFS_TAIL_BYTES = 5; void CommonCLI::loadPrefs(FILESYSTEM* fs) { bool is_fresh_install = false; bool is_upgrade = false; -#ifdef WITH_MQTT_BRIDGE - bool node_prefs_needs_migration = false; + // Set when prefs came from one of the legacy binary files; they are republished + // as /prefs.json below. The legacy file is never removed, so it stays available + // as a fallback if the JSON save does not commit this boot. + bool loaded_from_legacy = false; + + if (fs->exists("/prefs.json")) { +#if defined(RP2040_PLATFORM) + File file = fs->open("/prefs.json", "r"); +#else + File file = fs->open("/prefs.json"); #endif - - if (fs->exists("/com_prefs")) { - loadPrefsInt(fs, "/com_prefs"); // new filename + if (file) { + _prefs->loadSerial(file); + file.close(); + } + } else if (fs->exists("/com_prefs")) { + // Legacy binary layout. This is a file-format migration only: settings keep + // their stored values, so it must not trigger the bridge.source upgrade below. + loadPrefsInt(fs, "/com_prefs"); + loaded_from_legacy = true; } else if (fs->exists("/node_prefs")) { loadPrefsInt(fs, "/node_prefs"); - is_upgrade = true; // Migrating from old filename -#ifdef WITH_MQTT_BRIDGE - // Wait for loadMQTTPrefs() to persist any observer tail captured from this - // old file before replacing or removing its only on-flash copy. - node_prefs_needs_migration = true; -#else - if (saveCommonPrefsImageAtomically(fs)) { - fs->remove("/node_prefs"); // remove old only after the rename commits - } else { - MESH_DEBUG_PRINTLN("Prefs: preserving legacy /node_prefs until /com_prefs migration commits"); - } - // This boot has either completed the filename handoff or deliberately - // preserved /node_prefs for a retry. Do not follow it with the ordinary - // non-atomic legacy compaction path below. - _com_prefs_needs_upgrade = false; -#endif + is_upgrade = true; // pre-/com_prefs filename + loaded_from_legacy = true; } else { // File doesn't exist - set default bridge settings for fresh installs is_fresh_install = true; @@ -96,7 +96,7 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) { // Readers (MQTTBridge, AlertReporter, observer CLI) use _mqtt_prefs directly — // these fields no longer exist in NodePrefs, so there is nothing to sync. MQTTPrefsAtomicStore::LegacyUpgradeGate legacy_upgrade( - _com_prefs_needs_upgrade || node_prefs_needs_migration); + _com_prefs_needs_upgrade || loaded_from_legacy); loadMQTTPrefs(fs, &legacy_upgrade); if (_mqtt_prefs_hold) legacy_upgrade.holdMqttSource(); @@ -108,10 +108,9 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) { } else { MESH_DEBUG_PRINTLN("MQTT Bridge: Migrating bridge.source from tx to rx (MQTT bridge default)"); _prefs->bridge_pkt_src = 1; // Set to RX (logRx) - if (node_prefs_needs_migration) { - // The atomic /node_prefs -> /com_prefs handoff below persists this - // in-memory change. Do not publish /com_prefs before that transaction. - MESH_DEBUG_PRINTLN("MQTT Bridge: bridge.source will be saved with node prefs migration"); + if (loaded_from_legacy) { + // The /prefs.json migration below persists this in-memory change. + MESH_DEBUG_PRINTLN("MQTT Bridge: bridge.source will be saved with the prefs migration"); } else { savePrefs(fs); // Save the updated preference } @@ -122,46 +121,29 @@ void CommonCLI::loadPrefs(FILESYSTEM* fs) { // set by setMQTTPrefsDefaults(). No explicit migration needed. #endif + // Republish legacy binary prefs as /prefs.json. Old-format files also carried a + // trailing observer block, which loadPrefsInt() recovered into _legacy_tail; wait + // for loadMQTTPrefs() to commit that to /mqtt_prefs first. The legacy file is left + // on flash either way, so a deferred or failed save just retries on the next boot. #ifdef WITH_MQTT_BRIDGE - if (node_prefs_needs_migration) { + if (loaded_from_legacy || _com_prefs_needs_upgrade) { if (legacy_upgrade.mayRewriteComPrefs()) { - // The MQTT image (and any tail from /node_prefs) is committed, so it is - // now safe to publish the replacement name. Keep /node_prefs until the - // complete /com_prefs image is closed and atomically renamed into place. - if (saveCommonPrefsImageAtomically(fs)) { - fs->remove("/node_prefs"); - legacy_upgrade.recordComPrefsRewrite(); - _com_prefs_needs_upgrade = false; - } else { - MESH_DEBUG_PRINTLN("MQTT: preserving legacy /node_prefs until /com_prefs migration commits"); - } - } else { - MESH_DEBUG_PRINTLN("MQTT: preserving legacy /node_prefs until /mqtt_prefs migration commits"); - } - } else if (_com_prefs_needs_upgrade) { - // Old-format /com_prefs (legacy MQTT gap + trailing observer block) was detected: - // rewrite the prefs files in the current layout, one time. This persists the - // recovered rx_boosted_gain/flood_max_* values and (on MQTT builds) the observer - // settings that loadMQTTPrefs carried over into /mqtt_prefs. - if (legacy_upgrade.mayRewriteComPrefs()) { - // loadMQTTPrefs has already committed the full MQTT payload (including - // any recovered observer tail), so compact only /com_prefs now. - savePrefs(fs, false); + savePrefs(fs, false); // loadMQTTPrefs already committed the MQTT payload legacy_upgrade.recordComPrefsRewrite(); _com_prefs_needs_upgrade = false; } else { - MESH_DEBUG_PRINTLN("MQTT: preserving legacy /com_prefs until /mqtt_prefs migration commits"); + MESH_DEBUG_PRINTLN("Prefs: deferring /prefs.json migration until /mqtt_prefs commits"); } } #else - if (_com_prefs_needs_upgrade) { + if (loaded_from_legacy || _com_prefs_needs_upgrade) { savePrefs(fs); _com_prefs_needs_upgrade = false; } #endif } -void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { +void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { // Legacy prefs loader #if defined(RP2040_PLATFORM) File file = fs->open(filename, "r"); #else @@ -374,98 +356,27 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { } } -// Keep the byte layout in one place so ordinary saves and the atomic legacy -// rename path write exactly the same /com_prefs image. The Writer interface is -// deliberately just write(bytes, size), which lets the transaction helper test -// every short-write boundary without an Arduino filesystem. -template <typename Writer> -static bool writeCommonPrefsImage(Writer& writer, const NodePrefs* prefs) { - uint8_t pad[8]; - memset(pad, 0, sizeof(pad)); - -#define WRITE_COMMON_PREFS(value) \ - do { \ - if (writer.write((const uint8_t *)(value), sizeof(*(value))) != sizeof(*(value))) return false; \ - } while (0) -#define WRITE_COMMON_PREFS_BYTES(value, size) \ - do { \ - if (writer.write((const uint8_t *)(value), (size)) != (size)) return false; \ - } while (0) - - WRITE_COMMON_PREFS(&prefs->airtime_factor); // 0 - WRITE_COMMON_PREFS(&prefs->node_name); // 4 - WRITE_COMMON_PREFS_BYTES(pad, 4); // 36 - WRITE_COMMON_PREFS(&prefs->node_lat); // 40 - WRITE_COMMON_PREFS(&prefs->node_lon); // 48 - WRITE_COMMON_PREFS_BYTES(prefs->password, sizeof(prefs->password)); // 56 - WRITE_COMMON_PREFS(&prefs->freq); // 72 - WRITE_COMMON_PREFS(&prefs->tx_power_dbm); // 76 - WRITE_COMMON_PREFS(&prefs->disable_fwd); // 77 - WRITE_COMMON_PREFS(&prefs->advert_interval); // 78 - WRITE_COMMON_PREFS_BYTES(pad, 1); // 79 - WRITE_COMMON_PREFS(&prefs->rx_delay_base); // 80 - WRITE_COMMON_PREFS(&prefs->tx_delay_factor); // 84 - WRITE_COMMON_PREFS_BYTES(prefs->guest_password, sizeof(prefs->guest_password)); // 88 - WRITE_COMMON_PREFS(&prefs->direct_tx_delay_factor); // 104 - WRITE_COMMON_PREFS_BYTES(pad, 4); // 108 - WRITE_COMMON_PREFS(&prefs->sf); // 112 - WRITE_COMMON_PREFS(&prefs->cr); // 113 - WRITE_COMMON_PREFS(&prefs->allow_read_only); // 114 - WRITE_COMMON_PREFS(&prefs->multi_acks); // 115 - WRITE_COMMON_PREFS(&prefs->bw); // 116 - WRITE_COMMON_PREFS(&prefs->agc_reset_interval); // 120 - WRITE_COMMON_PREFS(&prefs->path_hash_mode); // 121 - WRITE_COMMON_PREFS(&prefs->loop_detect); // 122 - WRITE_COMMON_PREFS_BYTES(pad, 1); // 123 - WRITE_COMMON_PREFS(&prefs->flood_max); // 124 - WRITE_COMMON_PREFS(&prefs->flood_advert_interval); // 125 - WRITE_COMMON_PREFS(&prefs->interference_threshold); // 126 - WRITE_COMMON_PREFS(&prefs->bridge_enabled); // 127 - WRITE_COMMON_PREFS(&prefs->bridge_delay); // 128 - WRITE_COMMON_PREFS(&prefs->bridge_pkt_src); // 130 - WRITE_COMMON_PREFS(&prefs->bridge_baud); // 131 - WRITE_COMMON_PREFS(&prefs->bridge_channel); // 135 - WRITE_COMMON_PREFS_BYTES(prefs->bridge_secret, sizeof(prefs->bridge_secret)); // 136 - WRITE_COMMON_PREFS(&prefs->powersaving_enabled); // 152 - WRITE_COMMON_PREFS_BYTES(pad, 3); // 153 - WRITE_COMMON_PREFS(&prefs->gps_enabled); // 156 - WRITE_COMMON_PREFS(&prefs->gps_interval); // 157 - WRITE_COMMON_PREFS(&prefs->advert_loc_policy); // 161 - WRITE_COMMON_PREFS(&prefs->discovery_mod_timestamp); // 162 - WRITE_COMMON_PREFS(&prefs->adc_multiplier); // 166 - WRITE_COMMON_PREFS_BYTES(prefs->owner_info, sizeof(prefs->owner_info)); // 170 - // MQTT/observer settings are stored in /mqtt_prefs, not here. No zero-gap is - // written anymore — /com_prefs holds only the (non-observer) fields below. - // These trailing writes are COM_PREFS_TAIL_BYTES; keep the two in sync. - WRITE_COMMON_PREFS(&prefs->rx_boosted_gain); // 290 - WRITE_COMMON_PREFS(&prefs->flood_max_unscoped); // 291 - WRITE_COMMON_PREFS(&prefs->flood_max_advert); // 292 - WRITE_COMMON_PREFS(&prefs->radio_fem_rxgain); // 293 - WRITE_COMMON_PREFS(&prefs->cad_enabled); // 294 - -#undef WRITE_COMMON_PREFS_BYTES -#undef WRITE_COMMON_PREFS - return true; -} - -void CommonCLI::savePrefs(FILESYSTEM* fs, bool save_mqtt) { +bool CommonCLI::savePrefs(FILESYSTEM* fs, bool save_mqtt) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - fs->remove("/com_prefs"); - File file = fs->open("/com_prefs", FILE_O_WRITE); + fs->remove("/prefs.json"); + File file = fs->open("/prefs.json", FILE_O_WRITE); #elif defined(RP2040_PLATFORM) - File file = fs->open("/com_prefs", "w"); + File file = fs->open("/prefs.json", "w"); #else - File file = fs->open("/com_prefs", "w", true); + File file = fs->open("/prefs.json", "w", true); #endif + bool success = false; if (file) { - writeCommonPrefsImage(file, _prefs); + success = _prefs->saveSerial(file); file.close(); } #ifdef WITH_MQTT_BRIDGE // Observer config (MQTT/WiFi/timezone/SNMP/alert) is persisted separately. The // observer CLI writes _mqtt_prefs directly, so no NodePrefs->MQTTPrefs sync runs. + // Runs regardless of the NodePrefs result so a failed JSON write cannot strand it. if (save_mqtt) saveMQTTPrefs(fs); #endif + return success; } #ifdef WITH_MQTT_BRIDGE @@ -644,106 +555,6 @@ private: #endif // WITH_MQTT_BRIDGE -// The old /node_prefs name is only removed after this transaction has published -// a complete /com_prefs image. At this migration point /com_prefs is absent, so -// rename never needs a platform-specific replace-existing implementation. -class CommonPrefsFileStore { -public: - explicit CommonPrefsFileStore(FILESYSTEM* fs) : _fs(fs) {} - - bool begin() { - _finished = false; - _open = false; - _bytes_written = 0; - // The old-name migration only starts when /com_prefs is absent. Refuse to - // overwrite a destination that appeared unexpectedly before this handoff. - if (_fs->exists("/com_prefs")) return false; - // Clear only stale, unpublished output. Guarded on exists() for the same - // reason as the /mqtt_prefs store above: remove() on a missing file logs a - // spurious VFS error on ESP32. - if (_fs->exists("/com_prefs.tmp")) { - _fs->remove("/com_prefs.tmp"); - if (_fs->exists("/com_prefs.tmp")) return false; // could not clear it - } -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _file = _fs->open("/com_prefs.tmp", FILE_O_WRITE); -#elif defined(RP2040_PLATFORM) - _file = _fs->open("/com_prefs.tmp", "w"); -#else - _file = _fs->open("/com_prefs.tmp", "w", true); -#endif - _open = _file; - return _open; - } - - size_t write(const uint8_t* bytes, size_t size) { - if (!_open) return 0; - const size_t written = _file.write(bytes, size); - _bytes_written += written; - return written; - } - - bool finish() { - if (!_open) return false; - _file.close(); - _open = false; -#if defined(RP2040_PLATFORM) - File verify = _fs->open("/com_prefs.tmp", "r"); -#else - File verify = _fs->open("/com_prefs.tmp"); -#endif - if (!verify) return false; - const bool complete = verify.size() == _bytes_written; - verify.close(); - if (!complete) return false; - _finished = true; - return true; - } - - bool commit() { - return _finished && _fs->rename("/com_prefs.tmp", "/com_prefs"); - } - - void abort() { - if (_open) _file.close(); - _open = false; - _finished = false; - if (_fs->exists("/com_prefs.tmp")) _fs->remove("/com_prefs.tmp"); - } - -private: - FILESYSTEM* _fs; - File _file; - bool _open = false; - bool _finished = false; - size_t _bytes_written = 0; -}; - -static const char* commonPrefsSaveResultName(MQTTPrefsAtomicStore::ImageResult result) { - switch (result) { - case MQTTPrefsAtomicStore::ImageResult::BeginFailed: return "begin"; - case MQTTPrefsAtomicStore::ImageResult::WriteFailed: return "write"; - case MQTTPrefsAtomicStore::ImageResult::FinishFailed: return "close"; - case MQTTPrefsAtomicStore::ImageResult::CommitFailed: return "rename"; - case MQTTPrefsAtomicStore::ImageResult::Committed: return "committed"; - } - return "unknown"; -} - -bool CommonCLI::saveCommonPrefsImageAtomically(FILESYSTEM* fs) { - CommonPrefsFileStore store(fs); - const MQTTPrefsAtomicStore::ImageResult result = MQTTPrefsAtomicStore::writeImage( - store, [this](CommonPrefsFileStore& target) { - return writeCommonPrefsImage(target, _prefs); - }); - if (!MQTTPrefsAtomicStore::imageCommitted(result)) { - MESH_DEBUG_PRINTLN("Prefs: atomic /com_prefs migration save failed at %s; /node_prefs preserved", - commonPrefsSaveResultName(result)); - return false; - } - return true; -} - #ifdef WITH_MQTT_BRIDGE static const char* mqttPrefsSaveResultName(MQTTPrefsAtomicStore::Result result) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index d3eea766..63d20c04 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -6,6 +6,7 @@ #include <helpers/ClientACL.h> #include <helpers/MQTTPresets.h> // For MAX_MQTT_SLOTS (used in NodePrefs struct layout) #include <helpers/RegionMap.h> +#include <helpers/ConfigSerializer.h> #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) || defined(WITH_MQTT_BRIDGE) #define WITH_BRIDGE @@ -20,62 +21,180 @@ #define LOOP_DETECT_MODERATE 2 #define LOOP_DETECT_STRICT 3 -struct NodePrefs { // persisted to file - float airtime_factor; +class NodePrefs : public ConfigSerializer { +public: + // in-memory backing data + float airtime_factor = 0; char node_name[32]; - double node_lat, node_lon; + double node_lat = 0, node_lon = 0; char password[16]; - float freq; - int8_t tx_power_dbm; - uint8_t disable_fwd; - uint8_t advert_interval; // minutes / 2 - uint8_t rx_boosted_gain; // power settings (persisted at /com_prefs offset 290; - // offset 79 is a pad — see writeCommonPrefsImage) - uint8_t flood_advert_interval; // hours - float rx_delay_base; - float tx_delay_factor; + float freq = 0; + int8_t tx_power_dbm = 0; + uint8_t disable_fwd = 0; + uint8_t advert_interval = 0; // minutes / 2 + uint8_t flood_advert_interval = 0; // hours + float rx_delay_base = 0; + float tx_delay_factor = 0; char guest_password[16]; - float direct_tx_delay_factor; - uint32_t guard; - uint8_t sf; - uint8_t cr; - uint8_t allow_read_only; - uint8_t multi_acks; - float bw; - uint8_t flood_max; - uint8_t flood_max_unscoped; - uint8_t flood_max_advert; - uint8_t interference_threshold; - uint8_t agc_reset_interval; // secs / 4 - uint8_t path_hash_mode; // which path mode to use when sending + float direct_tx_delay_factor = 0; + uint32_t guard = 0; + uint8_t sf = 0; + uint8_t cr = 0; + uint8_t allow_read_only = 0; + uint8_t multi_acks = 0; + float bw = 0; + uint8_t flood_max = 0; + uint8_t flood_max_unscoped = 0; + uint8_t flood_max_advert = 0; + uint8_t interference_threshold = 0; + uint8_t agc_reset_interval = 0; // secs / 4 // Bridge settings - uint8_t bridge_enabled; // boolean - uint16_t bridge_delay; // milliseconds (default 500 ms) - uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logRx) - uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) - uint8_t bridge_channel; // 1-14 (ESP-NOW only) + uint8_t bridge_enabled = 0; // boolean + uint16_t bridge_delay = 0; // milliseconds (default 500 ms) + uint8_t bridge_pkt_src = 0; // 0 = logTx, 1 = logRx (fresh installs default to logRx) + uint32_t bridge_baud = 0; // 9600, 19200, 38400, 57600, 115200 (default 115200) + uint8_t bridge_channel = 0; // 1-14 (ESP-NOW only) char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) // Power setting - uint8_t powersaving_enabled; // boolean + uint8_t powersaving_enabled = 0; // boolean // Gps settings - uint8_t gps_enabled; - uint32_t gps_interval; // in seconds - uint8_t advert_loc_policy; - uint32_t discovery_mod_timestamp; - float adc_multiplier; + uint8_t gps_enabled = 0; + uint32_t gps_interval = 0; // in seconds + uint8_t advert_loc_policy = 0; + uint32_t discovery_mod_timestamp = 0; + float adc_multiplier = 0; char owner_info[120]; + uint8_t rx_boosted_gain = 0; // power settings + uint8_t radio_fem_rxgain = 0; // LoRa FEM RX-gain (LNA); hardware driving is wired per-board + uint8_t path_hash_mode = 0; // which path mode to use when sending + uint8_t loop_detect = 0; + uint8_t cad_enabled = 0; // hardware Channel Activity Detection before TX (boolean) - uint8_t loop_detect; + // NOTE: observer settings (MQTT/WiFi/timezone/SNMP/alert) are not in NodePrefs. + // They live in MQTTPrefs, persisted separately to /mqtt_prefs, so this struct + // stays aligned with upstream. See struct MQTTPrefs below. - // Restored from upstream (dropped by the 22eb9b87 revert). Persisted at the same - // /com_prefs offsets upstream uses (293, 294) so the file stays upstream-aligned. - uint8_t radio_fem_rxgain; // LoRa FEM RX-gain (LNA); default on. Hardware driving is - // wired per-board in the FEM-restore change; persisted here. - uint8_t cad_enabled; // hardware Channel Activity Detection before TX; default off +private: + class RadioPrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("freq", _parent->freq); + def("bw", _parent->bw); + def("sf", _parent->sf); + def("cr", _parent->cr); + def("cad", _parent->cad_enabled); + def("int_thr", _parent->interference_threshold); + def("rxgain", _parent->rx_boosted_gain); + def("fem_rxgain", _parent->radio_fem_rxgain); + def("tx", _parent->tx_power_dbm); + def("af", _parent->airtime_factor); + def("rxdelay", _parent->rx_delay_base); + def("f_txdelay", _parent->tx_delay_factor); + def("d_txdelay", _parent->direct_tx_delay_factor); + def("agc_int", _parent->agc_reset_interval); + def("hash_mode", _parent->path_hash_mode); + def("multi_ack", _parent->multi_acks); + } + public: + RadioPrefs(NodePrefs* parent) : _parent(parent) { } + }; + RadioPrefs radio; - // NOTE: observer settings (MQTT/WiFi/timezone/SNMP/alert) were moved out of - // NodePrefs into MQTTPrefs (persisted to /mqtt_prefs) so this struct stays - // aligned with upstream. See struct MQTTPrefs below. + class BridgePrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("en", _parent->bridge_enabled); // boolean + def("delay", _parent->bridge_delay); // milliseconds (default 500 ms) + def("src", _parent->bridge_pkt_src); // 0 = logTx, 1 = logRx + def("baud", _parent->bridge_baud); // 9600, 19200, 38400, 57600, 115200 (default 115200) + def("ch", _parent->bridge_channel); // 1-14 (ESP-NOW only) + def("secret", _parent->bridge_secret, sizeof(_parent->bridge_secret)); // for XOR encryption of bridge packets (ESP-NOW only) + } + public: + BridgePrefs(NodePrefs* parent) : _parent(parent) { } + }; + BridgePrefs bridge; + + class GPSPrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("en", _parent->gps_enabled); // boolean + def("int", _parent->gps_interval); // interval in seconds + def("adv_loc", _parent->advert_loc_policy); + } + public: + GPSPrefs(NodePrefs* parent) : _parent(parent) { } + }; + GPSPrefs gps; + + class PowerPrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("adc_mult", _parent->adc_multiplier); + def("pwr_sav_en", _parent->powersaving_enabled); + } + public: + PowerPrefs(NodePrefs* parent) : _parent(parent) { } + }; + PowerPrefs power; + + class RepeatPrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("disable", _parent->disable_fwd); + def("f_max", _parent->flood_max); + def("f_max_uns", _parent->flood_max_unscoped); + def("f_max_adv", _parent->flood_max_advert); + def("loop", _parent->loop_detect); + } + public: + RepeatPrefs(NodePrefs* parent) : _parent(parent) { } + }; + RepeatPrefs repeat; + + class RoomPrefs : public ConfigSerializer { + NodePrefs* _parent; + protected: + void structure() override { + def("rd_only", _parent->allow_read_only); + } + public: + RoomPrefs(NodePrefs* parent) : _parent(parent) { } + }; + RoomPrefs room; + +protected: + void structure() override { + def("name", node_name, sizeof(node_name)); + def("pass", password, sizeof(password)); + def("guest", guest_password, sizeof(guest_password)); + def("owner", owner_info, sizeof(owner_info)); + def("adv_int", advert_interval); + def("f_adv_int", flood_advert_interval); + def("lat", node_lat); + def("lon", node_lon); + def("disc_mod", discovery_mod_timestamp); // gates 'since'-filtered DISCOVER replies + def("radio", radio); + def("bridge", bridge); + def("gps", gps); + def("repeat", repeat); + def("room", room); + def("power", power); + } + +public: + NodePrefs() : ConfigSerializer(), bridge(this), gps(this), radio(this), power(this), repeat(this), room(this) { + node_name[0] = 0; + password[0] = 0; + guest_password[0] = 0; + bridge_secret[0] = 0; + owner_info[0] = 0; + } }; #ifdef WITH_MQTT_BRIDGE @@ -236,12 +355,11 @@ class CommonCLI { // run on defaults and saveMQTTPrefs() must not overwrite the source file. bool _mqtt_prefs_hold = false; #endif - bool _com_prefs_needs_upgrade = false; // old-format /com_prefs detected; rewrite once after load + bool _com_prefs_needs_upgrade = false; // old-format legacy prefs detected; rewrite once after load mesh::RTCClock* getRTCClock() { return _rtc; } void savePrefs(); void loadPrefsInt(FILESYSTEM* _fs, const char* filename); - bool saveCommonPrefsImageAtomically(FILESYSTEM* fs); #ifdef WITH_MQTT_BRIDGE void loadMQTTPrefs(FILESYSTEM* fs, MQTTPrefsAtomicStore::LegacyUpgradeGate* legacy_upgrade); bool saveMQTTPrefs(FILESYSTEM* fs); @@ -266,7 +384,7 @@ public: : _board(&board), _rtc(&rtc), _sensors(&sensors), _region_map(®ion_map), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { } void loadPrefs(FILESYSTEM* _fs); - void savePrefs(FILESYSTEM* _fs, bool save_mqtt = true); + bool savePrefs(FILESYSTEM* _fs, bool save_mqtt = true); void handleCommand(uint32_t sender_timestamp, char* command, char* reply); mesh::MainBoard* getBoard() { return _board; } uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data); diff --git a/src/helpers/ConfigSerializer.cpp b/src/helpers/ConfigSerializer.cpp new file mode 100644 index 00000000..adff147f --- /dev/null +++ b/src/helpers/ConfigSerializer.cpp @@ -0,0 +1,331 @@ +#include "ConfigSerializer.h" + +bool ConfigSerializer::saveSerial(Stream& s) { + Context context(&s, OP::WRITE); + _context = &context; // set the context for structure() call + s.print("{"); // root object + _first = true; + structure(); + if (s.print("}") != 1) context.success = false; // failure detect + _context = NULL; + return context.success; +} + +#define TOK_ERROR -1 +#define TOK_EOF 0 +#define TOK_KEY 1 +#define TOK_VALUE 2 +#define TOK_START_OBJ 3 +#define TOK_END_OBJ 4 +#define TOK_WHITESPACE 5 + +static bool is_whitespace(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} +static bool is_key_char(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'; +} +static bool is_value_char(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || c == '-' || c == '.'; +} + +#define EXPECT_OPEN_BRACE 0 +#define EXPECT_KEY 1 +#define EXPECT_VAL_OR_OBJ 2 +#define EXPECT_STRING_VAL 3 +#define EXPECT_STRING_ESCAPE 4 +#define EXPECT_COMMA_OR_CLOSE 5 +#define EXPECT_COMMA_OR_KEY 6 +#define EXPECT_COMMA_OR_KEY_OR_CLOSE 7 + +int ConfigSerializer::Context::readNext() { + char c; + if (pending) { + c = pending; + pending = 0; + } else { + if (_f->available() == 0) return TOK_EOF; + + int n = _f->read(); + if (n < 0) return TOK_EOF; + c = (char)n; + } + + switch (rd_mode) { + case EXPECT_OPEN_BRACE: + if (c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; } + if (is_whitespace(c)) return TOK_WHITESPACE; + return TOK_ERROR; + + case EXPECT_COMMA_OR_KEY_OR_CLOSE: + if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; } + case EXPECT_COMMA_OR_KEY: + if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; } + case EXPECT_KEY: + if (rd_len > 0 && c == ':') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_VAL_OR_OBJ; return TOK_KEY; } + if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE; + if (rd_len < CONFIG_MAX_KEYLEN-1 && is_key_char(c)) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; } + return TOK_ERROR; + + case EXPECT_VAL_OR_OBJ: + if (rd_len == 0 && is_whitespace(c)) return TOK_WHITESPACE; + if (rd_len == 0 && c == '"') { rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; } + if (rd_len == 0 && c == '{') { rd_mode = EXPECT_KEY; return TOK_START_OBJ; } + if (is_value_char(c) && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; } + if (rd_len > 0 && (c == ',' || c == '}' || is_whitespace(c))) { pending = c; rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; } + return TOK_ERROR; + + case EXPECT_STRING_ESCAPE: + if ((c == 'n') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = '\n'; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; } + if ((c == 'r') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = '\r'; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; } + if ((c == '"' || c == '\\' || c == '/') && rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; rd_mode = EXPECT_STRING_VAL; return TOK_WHITESPACE; } + return TOK_ERROR; // unsupport escape + + case EXPECT_STRING_VAL: + if (c == '"') { rd_buf[rd_len] = 0; rd_len = 0; rd_mode = EXPECT_COMMA_OR_CLOSE; return TOK_VALUE; } + if (c == '\\') { rd_mode = EXPECT_STRING_ESCAPE; return TOK_WHITESPACE; } + if (rd_len < CONFIG_MAX_TOKEN_LEN-1) { rd_buf[rd_len++] = c; return TOK_WHITESPACE; } + return TOK_ERROR; + + case EXPECT_COMMA_OR_CLOSE: + if (c == ',') { rd_mode = EXPECT_KEY; return TOK_WHITESPACE; } + if (c == '}') { rd_mode = EXPECT_COMMA_OR_KEY_OR_CLOSE; return TOK_END_OBJ; } + if (is_whitespace(c)) return TOK_WHITESPACE; + return TOK_ERROR; + } + return TOK_ERROR; // unknown mode +} + +bool ConfigSerializer::loadSerial(Stream& s) { + Context context(&s, OP::READ); + _context = &context; // set the context for structure() call + uint8_t sp = 0; // object nesting stack pointer + int next_tok; + + // parse the Json file + while ((next_tok = context.readNext()) > TOK_EOF) { + if (next_tok == TOK_KEY) { + context.setKey(sp, context.getToken()); + } else if (next_tok == TOK_VALUE) { + _depth = 1; // re-run the structure() hierarchy again (looking for specific key, at specific depth) + structure(); + } else if (next_tok == TOK_START_OBJ) { + if (sp < CONFIG_MAX_DEPTH - 1) { + sp++; + } else { + //Serial.printf("Error: max nesting reached"); // TODO: debug logging + context.success = false; + break; + } + } else if (next_tok == TOK_END_OBJ) { + if (sp > 0) { + sp--; + } else { + //Serial.printf("Error: too many closing '}'"); // TODO: debug logging + context.success = false; + break; + } + } + } + if (sp != 0 || next_tok == TOK_ERROR) { + context.success = false; // unmatched { }, or other parse error + } + _context = NULL; + return context.success; +} + +void ConfigSerializer::writeComma() { + if (_first) { + _first = false; + } else { + _context->file()->print(","); // comma separated properties + } +} + +#include <Utils.h> + +void ConfigSerializer::def(const char* key, void* value, size_t len) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":\""); + mesh::Utils::printHex(*_context->file(), (uint8_t*) value, len); + _context->file()->print("\""); + } else { + if (_context->keyMatch(_depth, key)) { + memset(value, 0, len); + mesh::Utils::fromHex((uint8_t *)value, len, _context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, char* value, size_t max_len) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":\""); + char c; + while ((c = *value++) != 0) { // TODO: handle UTF-8 encoding + if (c == '"') { + _context->file()->print("\\\""); + } else if (c == '\\') { + _context->file()->print("\\\\"); + } else if (c == '\n') { + _context->file()->print("\\n"); + } else if (c == '\r') { + _context->file()->print("\\r"); + } else { + _context->file()->print(c); + } + } + _context->file()->print("\""); + } else { + if (_context->keyMatch(_depth, key)) { + strncpy(value, _context->getToken(), max_len - 1); + value[max_len - 1] = 0; + } + } +} + +void ConfigSerializer::def(const char* key, int32_t& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + _context->file()->print(value); + } else { + if (_context->keyMatch(_depth, key)) { + value = atol(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, uint32_t& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + _context->file()->print(value); + } else { + if (_context->keyMatch(_depth, key)) { + value = atol(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, int16_t& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + _context->file()->print((int32_t) value, 10); + } else { + if (_context->keyMatch(_depth, key)) { + value = atol(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, uint16_t& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + _context->file()->print((uint32_t) value, 10); + } else { + if (_context->keyMatch(_depth, key)) { + value = atoi(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, uint8_t& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + _context->file()->print((uint32_t) value, 10); + } else { + if (_context->keyMatch(_depth, key)) { + value = atoi(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, int8_t& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + _context->file()->print((int32_t) value, 10); + } else { + if (_context->keyMatch(_depth, key)) { + value = atoi(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, bool& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + _context->file()->print(value ? "true" : "false"); + } else { + if (_context->keyMatch(_depth, key)) { + value = strcmp(_context->getToken(), "true") == 0 || atoi(_context->getToken()) != 0; // 'true' or a non-zero number + } + } +} + +void ConfigSerializer::def(const char* key, double& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + if (value == 0.0) { + _context->file()->print("0"); // shorter encoding + } else { + _context->file()->print(value, 6); // REVISIT: how many dec places? + } + } else { + if (_context->keyMatch(_depth, key)) { + value = atof(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, float& value) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":"); + if (value == 0.0f) { + _context->file()->print("0"); // shorter encoding + } else { + _context->file()->print(value, 4); // REVISIT: how many dec places? + } + } else { + if (_context->keyMatch(_depth, key)) { + value = (float) atof(_context->getToken()); + } + } +} + +void ConfigSerializer::def(const char* key, ConfigSerializer& sub_obj) { + if (_context->op() == OP::WRITE) { + writeComma(); + _context->file()->print(key); + _context->file()->print(":{"); + sub_obj._context = _context; // inherit the Context + sub_obj._first = true; + sub_obj.structure(); // recurse into sub object + if (_context->file()->print("}") != 1) _context->success = false; // failure detect + } else { + if (_context->keyMatch(_depth, key)) { + sub_obj._context = _context; // inherit the Context + sub_obj._depth = _depth + 1; + sub_obj.structure(); // recurse into sub object + } + } +} diff --git a/src/helpers/ConfigSerializer.h b/src/helpers/ConfigSerializer.h new file mode 100644 index 00000000..7e6d6f2a --- /dev/null +++ b/src/helpers/ConfigSerializer.h @@ -0,0 +1,68 @@ +#pragma once + +#include <Arduino.h> + +#ifndef CONFIG_MAX_DEPTH + #define CONFIG_MAX_DEPTH 8 +#endif + +#ifndef CONFIG_MAX_KEYLEN + #define CONFIG_MAX_KEYLEN 16 +#endif + +#ifndef CONFIG_MAX_TOKEN_LEN + #define CONFIG_MAX_TOKEN_LEN 128 +#endif + +class ConfigSerializer { + bool _first; + int8_t _depth; + + enum OP { READ, WRITE }; + + class Context { + Stream* _f; + OP _op; + uint8_t rd_len; + uint8_t rd_mode; + char pending; + char rd_buf[CONFIG_MAX_TOKEN_LEN]; + char _keys[CONFIG_MAX_DEPTH][CONFIG_MAX_KEYLEN]; + + public: + bool success = true; + Context(Stream* f, OP op) : _f(f), _op(op) { rd_buf[rd_len = 0] = 0; rd_mode = 0; pending = 0; } + OP op() const { return _op; } + Stream* file() const { return _f; } + int readNext(); + const char* getToken() const { return rd_buf; } + bool keyMatch(int8_t depth, const char* key) { return strcmp(key, _keys[depth]) == 0; } + void setKey(uint8_t depth, const char* key) { strcpy(_keys[depth], key); } + }; + + Context* _context = NULL; + + void writeComma(); + +protected: + ConfigSerializer() { } + + void def(const char* key, char* value, size_t max_len); // max_len inclusive of null + void def(const char* key, void* value, size_t len); // binary blob (encoded in hex) + void def(const char* key, int32_t& value); + void def(const char* key, int16_t& value); + void def(const char* key, int8_t& value); + void def(const char* key, uint32_t& value); + void def(const char* key, uint16_t& value); + void def(const char* key, uint8_t& value); + void def(const char* key, float& value); + void def(const char* key, double& value); + void def(const char* key, bool& value); + void def(const char* key, ConfigSerializer& sub_obj); + + virtual void structure() = 0; + +public: + bool loadSerial(Stream& s); + bool saveSerial(Stream& s); +}; diff --git a/src/helpers/MultiSerialInterface.h b/src/helpers/MultiSerialInterface.h new file mode 100644 index 00000000..f7742b24 --- /dev/null +++ b/src/helpers/MultiSerialInterface.h @@ -0,0 +1,200 @@ +#pragma once + +#include "BaseSerialInterface.h" + +#ifndef MAX_INTERFACES + // ble, usb, wifi, ethernet + #define MAX_INTERFACES 4 +#endif + +enum class InterfaceType : uint8_t { + NONE, + Bluetooth, + USB, + WiFi, + Ethernet, + HardwareSerial +}; + +class MultiSerialInterface : public BaseSerialInterface { +private: + + struct RegisteredInterface { + InterfaceType type = InterfaceType::NONE; + BaseSerialInterface* instance = nullptr; + }; + + bool _enabled = false; + RegisteredInterface _interfaces[MAX_INTERFACES] = {}; + +public: + bool addInterface(InterfaceType type, BaseSerialInterface* iface) { + // make sure an interface was provided + if(iface == nullptr){ + return false; + } + + // put it in the first free slot + for(int i = 0; i < MAX_INTERFACES; i++){ + if(_interfaces[i].instance == nullptr){ + _interfaces[i].instance = iface; + _interfaces[i].type = type; + return true; + } + } + + // no free slots available + return false; + } + + bool removeInterface(BaseSerialInterface* iface) { + // make sure an interface was provided + if(iface == nullptr){ + return false; + } + + // find and remove interface + for(int i = 0; i < MAX_INTERFACES; i++){ + if(_interfaces[i].instance == iface){ + _interfaces[i] = {}; + return true; + } + } + + // interface not found + return false; + } + + void enableBluetooth() { + for(auto iface : _interfaces){ + if(iface.instance && iface.type == InterfaceType::Bluetooth){ + iface.instance->enable(); + } + } + } + + void disableBluetooth() { + for(auto iface : _interfaces){ + if(iface.instance && iface.type == InterfaceType::Bluetooth){ + iface.instance->disable(); + } + } + } + + bool isBluetoothEnabled() { + for(auto iface : _interfaces){ + if(iface.instance && iface.type == InterfaceType::Bluetooth){ + return iface.instance->isEnabled(); + } + } + return false; + } + + // enable all interfaces + void enable() override { + _enabled = true; + for(auto iface : _interfaces){ + if(iface.instance){ + iface.instance->enable(); + } + } + } + + // disable all interfaces + void disable() override { + _enabled = false; + for(auto iface : _interfaces){ + if(iface.instance){ + iface.instance->disable(); + } + } + } + + bool isEnabled() const override { + return _enabled; + } + + bool isConnected() const override { + // not connected when disabled + if(!_enabled){ + return false; + } + + // check if any interface is connected + for(auto iface : _interfaces){ + if(iface.instance && iface.instance->isConnected()) { + return true; + } + } + + // nothing connected + return false; + } + + // loop all interfaces + void loop() override { + for(auto iface : _interfaces){ + if(iface.instance){ + iface.instance->loop(); + } + } + } + + bool isWriteBusy() const override { + // not busy when disabled + if(!_enabled){ + return false; + } + + // check if any interface is busy + for(auto iface : _interfaces){ + if(iface.instance && iface.instance->isEnabled() && iface.instance->isWriteBusy()){ + return true; + } + } + + // nothing busy + return false; + } + + size_t writeFrame(const uint8_t src[], size_t len) override { + // don't write when disabled or nothing provided + if(!_enabled || len == 0){ + return 0; + } + + // write frame to all enabled interfaces + bool allSuccessful = true; + for(auto iface : _interfaces){ + if(iface.instance && iface.instance->isEnabled()){ + if(iface.instance->writeFrame(src, len) != len){ + allSuccessful = false; + } + } + } + + // report success if all writes completed successfully + return allSuccessful ? len : 0; + } + + size_t checkRecvFrame(uint8_t dest[]) override { + // don't read when disabled + if(!_enabled){ + return 0; + } + + // try to read a frame from any enabled interface + for(auto iface : _interfaces){ + if(iface.instance && iface.instance->isEnabled()){ + size_t frameSize = iface.instance->checkRecvFrame(dest); + if(frameSize > 0){ + return frameSize; + } + } + } + + // no frame received + return 0; + } + +}; diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index ea3d7a45..d37a2279 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -314,14 +314,38 @@ float NRF52Board::getMCUTemperature() { void NRF52Board::shutdownPeripherals() { // Power off the display if any #ifdef DISPLAY_CLASS - display.turnOff(); + if (display.isOn()) { + display.turnOff(); + } #endif - + // Prep LoRa radio for power down + #ifdef P_LORA_RESET + digitalWrite(P_LORA_RESET, HIGH); // preload OUT latch so pinMode can't glitch NRESET low + pinMode(P_LORA_RESET, OUTPUT); + digitalWrite(P_LORA_RESET, LOW); // deliberate hardware reset (datasheet: >=100us) + delayMicroseconds(200); + digitalWrite(P_LORA_RESET, HIGH); + #endif + #if defined(P_LORA_SCLK) && defined(P_LORA_MISO) && defined(P_LORA_MOSI) + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); // SPI may not be started on some shutdown paths, need it to shut down radio + #endif + #ifdef P_LORA_BUSY + pinMode(P_LORA_BUSY, INPUT); + uint32_t started_at = millis(); + while (digitalRead(P_LORA_BUSY) && millis() - started_at < 10) {} //wait for radio to be ready + #endif + #ifdef P_LORA_NSS + pinMode(P_LORA_NSS, OUTPUT); + digitalWrite(P_LORA_NSS, HIGH); + #endif // Power off LoRa radio_driver.powerOff(); // Keep LoRa inactive during deepsleep - digitalWrite(P_LORA_NSS, HIGH); + #ifdef P_LORA_NSS + digitalWrite(P_LORA_NSS, HIGH); + #endif // Power off GPS if any if(sensors.getLocationProvider() != NULL) { @@ -372,7 +396,8 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[], bool force_ap) { Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - Bluefruit.begin(1, 0); + if (!Bluefruit.begin(1, 0)) return false; + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 Bluefruit.setTxPower(4); // Set the BLE device name diff --git a/src/helpers/UTF8Helpers.h b/src/helpers/UTF8Helpers.h new file mode 100644 index 00000000..e06cf4a6 --- /dev/null +++ b/src/helpers/UTF8Helpers.h @@ -0,0 +1,56 @@ +#pragma once + +#include <stddef.h> +#include <stdint.h> + +namespace mesh { + +inline bool isUtf8Continuation(uint8_t byte) { + return (byte & 0xC0) == 0x80; +} + +inline size_t validUtf8PrefixLength(const char* text, size_t max_bytes) { + if (text == nullptr) return 0; + + size_t offset = 0; + while (text[offset] != '\0') { + const uint8_t first = static_cast<uint8_t>(text[offset]); + size_t sequence_length = 0; + + if (first <= 0x7F) { + sequence_length = 1; + } else if (first >= 0xC2 && first <= 0xDF) { + sequence_length = 2; + } else if (first >= 0xE0 && first <= 0xEF) { + sequence_length = 3; + } else if (first >= 0xF0 && first <= 0xF4) { + sequence_length = 4; + } else { + break; + } + + if (offset + sequence_length > max_bytes) break; + + bool complete = true; + for (size_t i = 1; i < sequence_length; i++) { + if (text[offset + i] == '\0' || !isUtf8Continuation(static_cast<uint8_t>(text[offset + i]))) { + complete = false; + break; + } + } + if (!complete) break; + + if (sequence_length == 3) { + const uint8_t second = static_cast<uint8_t>(text[offset + 1]); + if ((first == 0xE0 && second < 0xA0) || (first == 0xED && second > 0x9F)) break; + } else if (sequence_length == 4) { + const uint8_t second = static_cast<uint8_t>(text[offset + 1]); + if ((first == 0xF0 && second < 0x90) || (first == 0xF4 && second > 0x8F)) break; + } + + offset += sequence_length; + } + return offset; +} + +} // namespace mesh diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index dcfa0e1e..6371cf33 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -103,10 +103,9 @@ void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param void SerialBLEInterface::onDisconnect(BLEServer* pServer) { BLE_DEBUG_PRINTLN("onDisconnect()"); + deviceConnected = false; if (_isEnabled) { adv_restart_time = millis() + ADVERT_RESTART_DELAY; - - // loop() will detect this on next loop, and set deviceConnected to false } } @@ -118,17 +117,24 @@ void SerialBLEInterface::onWrite(BLECharacteristic* pCharacteristic, esp_ble_gat if (len > MAX_FRAME_SIZE) { BLE_DEBUG_PRINTLN("ERROR: onWrite(), frame too big, len=%d", len); - } else if (recv_queue_len >= FRAME_QUEUE_SIZE) { - BLE_DEBUG_PRINTLN("ERROR: onWrite(), recv_queue is full!"); } else { - recv_queue[recv_queue_len].len = len; - memcpy(recv_queue[recv_queue_len].buf, rxValue, len); - recv_queue_len++; + Frame frame = {}; + frame.len = len; + memcpy(frame.buf, rxValue, len); + + if (xQueueSend(recv_queue, &frame, 0) != pdTRUE) { + BLE_DEBUG_PRINTLN("ERROR: onWrite(), recv_queue is full!"); + } } } // ---------- public methods +void SerialBLEInterface::clearBuffers() { + xQueueReset(recv_queue); + send_queue_len = 0; +} + void SerialBLEInterface::enable() { if (_isEnabled) return; @@ -202,21 +208,13 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { } } - if (recv_queue_len > 0) { // check recv queue - size_t len = recv_queue[0].len; // take from top of queue - memcpy(dest, recv_queue[0].buf, len); - - BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]); - - recv_queue_len--; - for (int i = 0; i < recv_queue_len; i++) { // delete top item from queue - recv_queue[i] = recv_queue[i + 1]; - } - return len; + Frame frame; + if (xQueueReceive(recv_queue, &frame, 0) == pdTRUE) { + memcpy(dest, frame.buf, frame.len); + BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", (uint32_t) frame.len, (uint32_t) dest[0]); + return frame.len; } - if (pServer->getConnectedCount() == 0) deviceConnected = false; - if (deviceConnected != oldDeviceConnected) { if (!deviceConnected) { // disconnecting clearBuffers(); diff --git a/src/helpers/esp32/SerialBLEInterface.h b/src/helpers/esp32/SerialBLEInterface.h index 965e90fd..9fb6adba 100644 --- a/src/helpers/esp32/SerialBLEInterface.h +++ b/src/helpers/esp32/SerialBLEInterface.h @@ -5,6 +5,8 @@ #include <BLEServer.h> #include <BLEUtils.h> #include <BLE2902.h> +#include <freertos/FreeRTOS.h> +#include <freertos/queue.h> class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLEServerCallbacks, BLECharacteristicCallbacks { BLEServer *pServer; @@ -24,12 +26,13 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE }; #define FRAME_QUEUE_SIZE 4 - int recv_queue_len; - Frame recv_queue[FRAME_QUEUE_SIZE]; + StaticQueue_t recv_queue_state; + uint8_t recv_queue_storage[FRAME_QUEUE_SIZE * sizeof(Frame)]; + QueueHandle_t recv_queue; int send_queue_len; Frame send_queue[FRAME_QUEUE_SIZE]; - void clearBuffers() { recv_queue_len = 0; send_queue_len = 0; } + void clearBuffers(); protected: // BLESecurityCallbacks methods @@ -58,7 +61,10 @@ public: _isEnabled = false; _last_write = 0; last_conn_id = 0; - send_queue_len = recv_queue_len = 0; + recv_queue = xQueueCreateStatic( + FRAME_QUEUE_SIZE, sizeof(Frame), recv_queue_storage, &recv_queue_state + ); + send_queue_len = 0; } /** diff --git a/src/helpers/esp32/TBeamBoard.cpp b/src/helpers/esp32/TBeamBoard.cpp index 5f708d71..d9f6e022 100644 --- a/src/helpers/esp32/TBeamBoard.cpp +++ b/src/helpers/esp32/TBeamBoard.cpp @@ -16,6 +16,16 @@ void TBeamBoard::begin() { ESP32Board::begin(); +#ifdef TBEAM_SUPREME_SX1262 + // On the T-Beam S3 Supreme the PMU + RTC sit on Wire1 (GPIO 42/41, brought + // up by XPowersLib), while the SH1106 OLED and the BME280/QMC6310 sensors + // sit on the primary bus, Wire (GPIO PIN_BOARD_SDA/PIN_BOARD_SCL). Nothing + // else initialises Wire on this board, so the display driver would + // otherwise talk on the wrong (default) pins and display.begin() fails, + // leaving the screen blank. Bring the OLED bus up here. + Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL); +#endif + power_init(); //Configure user button diff --git a/src/helpers/ethernet/EthernetInterface.h b/src/helpers/ethernet/EthernetInterface.h new file mode 100644 index 00000000..4e1f9176 --- /dev/null +++ b/src/helpers/ethernet/EthernetInterface.h @@ -0,0 +1,11 @@ +#pragma once + +#if defined(ETHERNET_ENABLED) + #if defined(ETHERNET_USE_CH390) + #include "helpers/ethernet/ch390/CH390EthernetInterface.h" + #elif defined(ETHERNET_USE_RAK13800) + #include "helpers/ethernet/RAK13800/RAK13800EthernetInterface.h" + #else + #error "ETHERNET_ENABLED is defined, but no specific driver flag (e.g. ETHERNET_USE_CH390) was provided!" + #endif +#endif diff --git a/src/helpers/ethernet/RAK13800/RAK13800EthernetInterface.cpp b/src/helpers/ethernet/RAK13800/RAK13800EthernetInterface.cpp new file mode 100644 index 00000000..027581e3 --- /dev/null +++ b/src/helpers/ethernet/RAK13800/RAK13800EthernetInterface.cpp @@ -0,0 +1,109 @@ +#include "RAK13800EthernetInterface.h" +#include "../../nrf52/EthernetMac.h" +#include <SPI.h> +#include <EthernetUdp.h> + +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI); + +#define PIN_ETHERNET_POWER_EN WB_IO2 // output, high to enable +#define PIN_ETHERNET_RESET 21 +#define PIN_ETHERNET_SS 26 + +bool RAK13800EthernetInterface::begin() { + + // WB_IO2 (power enable) is already driven HIGH by early constructor + // in RAK4631Board.cpp to support POE boot. + // Skip hardware reset — the W5100S comes out of power-on reset cleanly, + // and toggling reset kills the PHY link which breaks POE power. +#ifdef PIN_ETHERNET_RESET + pinMode(PIN_ETHERNET_RESET, OUTPUT); + digitalWrite(PIN_ETHERNET_RESET, HIGH); +#endif + + // generate mac address + uint8_t mac[6]; + generateEthernetMac(mac); + ETHERNET_DEBUG_PRINTLN( + "Ethernet MAC: %02X:%02X:%02X:%02X:%02X:%02X", + mac[0], + mac[1], + mac[2], + mac[3], + mac[4], + mac[5]); + ETHERNET_SPI_PORT.begin(); + Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS); + + // Use static IP if build flags are defined, otherwise DHCP + #if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET) && defined(ETHERNET_STATIC_DNS) + IPAddress ip(ETHERNET_STATIC_IP); + IPAddress gateway(ETHERNET_STATIC_GATEWAY); + IPAddress subnet(ETHERNET_STATIC_SUBNET); + IPAddress dns(ETHERNET_STATIC_DNS); + Ethernet.begin(mac, ip, dns, gateway, subnet); + #else + if (Ethernet.begin(mac) == 0) { + ETHERNET_DEBUG_PRINTLN("Failed to initialize RAK13800 hardware."); + if (Ethernet.hardwareStatus() == EthernetNoHardware) { + ETHERNET_DEBUG_PRINTLN("Ethernet hardware not found."); + } else if (Ethernet.linkStatus() == LinkOFF) { + ETHERNET_DEBUG_PRINTLN("Ethernet cable not connected."); + } else { + ETHERNET_DEBUG_PRINTLN("DHCP failed for unknown reason."); + } + return false; + } + #endif + + ETHERNET_DEBUG_PRINTLN("Ethernet begin complete"); + ETHERNET_DEBUG_PRINT_IP("IP Address", Ethernet.localIP()); + ETHERNET_DEBUG_PRINT_IP("Subnet Mask", Ethernet.subnetMask()); + ETHERNET_DEBUG_PRINT_IP("Gateway", Ethernet.gatewayIP()); + ETHERNET_DEBUG_PRINT_IP("DNS", Ethernet.dnsServerIP()); + + server.begin(); + ETHERNET_DEBUG_PRINTLN("listening on TCP port: %d", ETHERNET_TCP_PORT); + + return true; +} + +int RAK13800EthernetInterface::available() { + return client.available(); +} + +int RAK13800EthernetInterface::read() { + return client.read(); +} + +size_t RAK13800EthernetInterface::write(const uint8_t *buf, size_t size) { + return client.write(buf, size); +} + +bool RAK13800EthernetInterface::isConnected() const { + return _isConnected; +} + +void RAK13800EthernetInterface::loop() { + + Ethernet.maintain(); + + auto newClient = server.accept(); + if (newClient) { + IPAddress remoteIp = newClient.remoteIP(); + uint16_t remotePort = newClient.remotePort(); + ETHERNET_DEBUG_PRINTLN("New client accepted %u.%u.%u.%u:%u", remoteIp[0], remoteIp[1], remoteIp[2], remoteIp[3], remotePort); + if (client) { + ETHERNET_DEBUG_PRINTLN("Closing previous client"); + client.stop(); + } + client = newClient; + onClientConnected(); + } + + _isConnected = client.connected(); + +} diff --git a/src/helpers/ethernet/RAK13800/RAK13800EthernetInterface.h b/src/helpers/ethernet/RAK13800/RAK13800EthernetInterface.h new file mode 100644 index 00000000..34bb7587 --- /dev/null +++ b/src/helpers/ethernet/RAK13800/RAK13800EthernetInterface.h @@ -0,0 +1,27 @@ +#pragma once + +#include "../SerialEthernetInterface.h" +#include <SPI.h> +#include <RAK13800_W5100S.h> + +class RAK13800EthernetInterface : public SerialEthernetInterface { + + bool _isConnected; + EthernetServer server; + EthernetClient client; + + public: + RAK13800EthernetInterface() : server(EthernetServer(ETHERNET_TCP_PORT)) { + _isConnected = false; + } + + bool begin(); + void loop() override; + + // BaseSerialInterface methods + bool isConnected() const override; + + int available() override; + int read() override; + size_t write(const uint8_t *buf, size_t size) override; +}; diff --git a/src/helpers/ethernet/SerialEthernetInterface.cpp b/src/helpers/ethernet/SerialEthernetInterface.cpp new file mode 100644 index 00000000..22bcabc1 --- /dev/null +++ b/src/helpers/ethernet/SerialEthernetInterface.cpp @@ -0,0 +1,140 @@ +#include "SerialEthernetInterface.h" + +#define RECV_STATE_IDLE 0 +#define RECV_STATE_HDR_FOUND 1 +#define RECV_STATE_LEN1_FOUND 2 +#define RECV_STATE_LEN2_FOUND 3 + +bool SerialEthernetInterface::begin() { + return true; +} + +void SerialEthernetInterface::enable() { + if (_isEnabled) return; + _isEnabled = true; + clearBuffers(); +} + +void SerialEthernetInterface::disable() { + _isEnabled = false; +} + +size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) { + if (len > MAX_FRAME_SIZE) { + ETHERNET_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d\n", len); + return 0; + } + + if (isConnected() && len > 0) { + if (send_queue_len >= FRAME_QUEUE_SIZE) { + ETHERNET_DEBUG_PRINTLN("writeFrame(), send_queue is full!"); + return 0; + } + + send_queue[send_queue_len].len = len; // add to send queue + memcpy(send_queue[send_queue_len].buf, src, len); + send_queue_len++; + + return len; + } + return 0; +} + +bool SerialEthernetInterface::isWriteBusy() const { + return false; +} + +void SerialEthernetInterface::onClientConnected() { + _state = RECV_STATE_IDLE; + _frame_len = 0; + _rx_len = 0; +} + +size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) { + + if (isConnected()) { + if (send_queue_len > 0) { // first, check send queue + + _last_write = millis(); + int len = send_queue[0].len; + +#if ETHERNET_RAW_LINE + ETHERNET_DEBUG_PRINTLN("TX line len=%d", len); + client.write(send_queue[0].buf, len); + client.write("\r\n", 2); +#else + uint8_t pkt[3+len]; // use same header as serial interface so client can delimit frames + pkt[0] = '>'; + pkt[1] = (len & 0xFF); // LSB + pkt[2] = (len >> 8); // MSB + memcpy(&pkt[3], send_queue[0].buf, send_queue[0].len); + ETHERNET_DEBUG_PRINTLN("Sending frame len=%d", len); + #if ETHERNET_DEBUG_LOGGING && ARDUINO + ETHERNET_DEBUG_PRINTLN("TX frame len=%d", len); + #endif + write(pkt, 3 + len); +#endif + send_queue_len--; + for (int i = 0; i < send_queue_len; i++) { // delete top item from queue + send_queue[i] = send_queue[i + 1]; + } + } else { + while (available()) { + int c = read(); + if (c < 0) break; + +#if ETHERNET_RAW_LINE + if (c == '\r' || c == '\n') { + if (_rx_len == 0) { + continue; + } + uint16_t out_len = _rx_len; + if (out_len > MAX_FRAME_SIZE) out_len = MAX_FRAME_SIZE; + memcpy(dest, _rx_buf, out_len); + _rx_len = 0; + return out_len; + } + if (_rx_len < MAX_FRAME_SIZE) { + _rx_buf[_rx_len] = (uint8_t)c; + _rx_len++; + } +#else + switch (_state) { + case RECV_STATE_IDLE: + if (c == '<') { + _state = RECV_STATE_HDR_FOUND; + } + break; + case RECV_STATE_HDR_FOUND: + _frame_len = (uint8_t)c; + _state = RECV_STATE_LEN1_FOUND; + break; + case RECV_STATE_LEN1_FOUND: + _frame_len |= ((uint16_t)c) << 8; + _rx_len = 0; + _state = _frame_len > 0 ? RECV_STATE_LEN2_FOUND : RECV_STATE_IDLE; + break; + default: + if (_rx_len < MAX_FRAME_SIZE) { + _rx_buf[_rx_len] = (uint8_t)c; + } + _rx_len++; + if (_rx_len >= _frame_len) { + if (_frame_len > MAX_FRAME_SIZE) { + _frame_len = MAX_FRAME_SIZE; + } + #if ETHERNET_DEBUG_LOGGING && ARDUINO + ETHERNET_DEBUG_PRINTLN("RX frame len=%d", _frame_len); + #endif + memcpy(dest, _rx_buf, _frame_len); + _state = RECV_STATE_IDLE; + return _frame_len; + } + } +#endif + } + } + } + + return 0; +} diff --git a/src/helpers/nrf52/SerialEthernetInterface.h b/src/helpers/ethernet/SerialEthernetInterface.h similarity index 79% rename from src/helpers/nrf52/SerialEthernetInterface.h rename to src/helpers/ethernet/SerialEthernetInterface.h index 95ce8a52..46789f71 100644 --- a/src/helpers/nrf52/SerialEthernetInterface.h +++ b/src/helpers/ethernet/SerialEthernetInterface.h @@ -1,8 +1,6 @@ #pragma once -#include "helpers/BaseSerialInterface.h" -#include <SPI.h> -#include <RAK13800_W5100S.h> +#include "../BaseSerialInterface.h" #ifndef ETHERNET_TCP_PORT #define ETHERNET_TCP_PORT 5000 @@ -10,7 +8,6 @@ // define ETHERNET_RAW_LINE=1 to use raw line-based CLI instead of framed packets class SerialEthernetInterface : public BaseSerialInterface { - bool deviceConnected; bool _isEnabled; unsigned long _last_write; uint8_t _state; @@ -18,9 +15,6 @@ class SerialEthernetInterface : public BaseSerialInterface { uint16_t _rx_len; uint8_t _rx_buf[MAX_FRAME_SIZE]; - EthernetServer server; - EthernetClient client; - struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; @@ -40,8 +34,7 @@ class SerialEthernetInterface : public BaseSerialInterface { protected: public: - SerialEthernetInterface() : server(EthernetServer(ETHERNET_TCP_PORT)) { - deviceConnected = false; + SerialEthernetInterface() { _isEnabled = false; _last_write = 0; send_queue_len = 0; @@ -51,6 +44,8 @@ class SerialEthernetInterface : public BaseSerialInterface { } bool begin(); + void onClientConnected(); + // BaseSerialInterface methods void enable() override; void disable() override; @@ -62,7 +57,9 @@ class SerialEthernetInterface : public BaseSerialInterface { size_t writeFrame(const uint8_t src[], size_t len) override; size_t checkRecvFrame(uint8_t dest[]) override; - void loop(); + virtual int available() = 0; + virtual int read() = 0; + virtual size_t write(const uint8_t *buf, size_t size) = 0; }; @@ -70,7 +67,7 @@ class SerialEthernetInterface : public BaseSerialInterface { #include <Arduino.h> #define ETHERNET_DEBUG_PRINT(F, ...) Serial.printf("ETH: " F, ##__VA_ARGS__) #define ETHERNET_DEBUG_PRINTLN(F, ...) Serial.printf("ETH: " F "\n", ##__VA_ARGS__) - #define ETHERNET_DEBUG_PRINT_IP(name, ip) Serial.printf(name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3]) + #define ETHERNET_DEBUG_PRINT_IP(name, ip) Serial.printf("ETH: " name ": %u.%u.%u.%u" "\n", ip[0], ip[1], ip[2], ip[3]) #else #define ETHERNET_DEBUG_PRINT(...) {} #define ETHERNET_DEBUG_PRINTLN(...) {} diff --git a/src/helpers/ethernet/ch390/CH390EthernetInterface.cpp b/src/helpers/ethernet/ch390/CH390EthernetInterface.cpp new file mode 100644 index 00000000..7f696245 --- /dev/null +++ b/src/helpers/ethernet/ch390/CH390EthernetInterface.cpp @@ -0,0 +1,94 @@ +#include "CH390EthernetInterface.h" + +void onWiFiEvent(WiFiEvent_t event) { + switch(event){ + case ARDUINO_EVENT_ETH_START: + ETHERNET_DEBUG_PRINTLN("Ethernet Started"); + break; + case ARDUINO_EVENT_ETH_CONNECTED: + ETHERNET_DEBUG_PRINTLN("Ethernet Connected"); + break; + case ARDUINO_EVENT_ETH_DISCONNECTED: + ETHERNET_DEBUG_PRINTLN("Ethernet Disconnected"); + break; + case ARDUINO_EVENT_ETH_GOT_IP: + ETHERNET_DEBUG_PRINTLN("Ethernet Got IP"); + ETHERNET_DEBUG_PRINT_IP("IP Address", CH390.localIP()); + ETHERNET_DEBUG_PRINT_IP("Subnet Mask", CH390.subnetMask()); + ETHERNET_DEBUG_PRINT_IP("Gateway", CH390.gatewayIP()); + ETHERNET_DEBUG_PRINT_IP("DNS", CH390.dnsIP()); + ETHERNET_DEBUG_PRINTLN("MAC Address: %s", CH390.macAddress().c_str()); + break; + default: + break; + } +} + +bool CH390EthernetInterface::begin() { + + // listen to ethernet events + WiFi.onEvent(onWiFiEvent); + + // Init CH390 + ch390_config_t config = CH390_DEFAULT_CONFIG(); + config.spi_miso_gpio = ETH_MISO_PIN; + config.spi_mosi_gpio = ETH_MOSI_PIN; + config.spi_sck_gpio = ETH_SCLK_PIN; + config.spi_cs_gpio = ETH_CS_PIN; + config.int_gpio = ETH_INT_PIN; + if (!CH390.begin(config)) { + ETHERNET_DEBUG_PRINTLN("Failed to initialize CH390 hardware."); + return false; + } + + // Setup Static IP if build flags are present + #if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET) + IPAddress ip(ETHERNET_STATIC_IP); + IPAddress gw(ETHERNET_STATIC_GATEWAY); + IPAddress sn(ETHERNET_STATIC_SUBNET); + CH390.config(ip, gw, sn); + #endif + + // Start Server + server.begin(ETHERNET_TCP_PORT); + ETHERNET_DEBUG_PRINTLN("listening on TCP port: %d", ETHERNET_TCP_PORT); + + return true; +} + +int CH390EthernetInterface::available() { + return client.available(); +} + +int CH390EthernetInterface::read() { + return client.read(); +} + +size_t CH390EthernetInterface::write(const uint8_t *buf, size_t size) { + return client.write(buf, size); +} + +bool CH390EthernetInterface::isConnected() const { + return _isConnected; +} + +void CH390EthernetInterface::loop() { + + if (server.hasClient()) { + auto newClient = server.available(); + if (newClient) { + IPAddress remoteIp = newClient.remoteIP(); + uint16_t remotePort = newClient.remotePort(); + ETHERNET_DEBUG_PRINTLN("New client accepted %u.%u.%u.%u:%u", remoteIp[0], remoteIp[1], remoteIp[2], remoteIp[3], remotePort); + if (client) { + ETHERNET_DEBUG_PRINTLN("Closing previous client"); + client.stop(); + } + client = newClient; + onClientConnected(); + } + } + + _isConnected = client.connected(); + +} diff --git a/src/helpers/ethernet/ch390/CH390EthernetInterface.h b/src/helpers/ethernet/ch390/CH390EthernetInterface.h new file mode 100644 index 00000000..09c3fc23 --- /dev/null +++ b/src/helpers/ethernet/ch390/CH390EthernetInterface.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../SerialEthernetInterface.h" +#include <SPI.h> +#include <WiFi.h> +#include <WiFiServer.h> +#include <WiFiClient.h> +#include <ESP32_CH390.h> + +class CH390EthernetInterface : public SerialEthernetInterface { + + bool _isConnected; + WiFiServer server; + WiFiClient client; + + public: + CH390EthernetInterface(){ + _isConnected = false; + } + + bool begin(); + void loop() override; + + // BaseSerialInterface methods + bool isConnected() const override; + + int available() override; + int read() override; + size_t write(const uint8_t *buf, size_t size) override; +}; diff --git a/src/helpers/nrf52/SerialEthernetInterface.cpp b/src/helpers/nrf52/SerialEthernetInterface.cpp deleted file mode 100644 index 70891023..00000000 --- a/src/helpers/nrf52/SerialEthernetInterface.cpp +++ /dev/null @@ -1,264 +0,0 @@ -#include "SerialEthernetInterface.h" -#include "EthernetMac.h" -#include <SPI.h> -#include <EthernetUdp.h> - -#define PIN_SPI1_MISO (29) // (0 + 29) -#define PIN_SPI1_MOSI (30) // (0 + 30) -#define PIN_SPI1_SCK (3) // (0 + 3) - -SPIClass ETHERNET_SPI_PORT(NRF_SPIM1, PIN_SPI1_MISO, PIN_SPI1_SCK, PIN_SPI1_MOSI); - -#define PIN_ETHERNET_POWER_EN WB_IO2 // output, high to enable -#define PIN_ETHERNET_RESET 21 -#define PIN_ETHERNET_SS 26 - -#define RECV_STATE_IDLE 0 -#define RECV_STATE_HDR_FOUND 1 -#define RECV_STATE_LEN1_FOUND 2 -#define RECV_STATE_LEN2_FOUND 3 - -bool SerialEthernetInterface::begin() { - - ETHERNET_DEBUG_PRINTLN("Ethernet initializing"); - - // WB_IO2 (power enable) is already driven HIGH by early constructor - // in RAK4631Board.cpp to support POE boot. - // Skip hardware reset — the W5100S comes out of power-on reset cleanly, - // and toggling reset kills the PHY link which breaks POE power. -#ifdef PIN_ETHERNET_RESET - pinMode(PIN_ETHERNET_RESET, OUTPUT); - digitalWrite(PIN_ETHERNET_RESET, HIGH); -#endif - - uint8_t mac[6]; - generateEthernetMac(mac); - ETHERNET_DEBUG_PRINTLN( - "Ethernet MAC: %02X:%02X:%02X:%02X:%02X:%02X", - mac[0], - mac[1], - mac[2], - mac[3], - mac[4], - mac[5]); - ETHERNET_DEBUG_PRINTLN("Init"); - ETHERNET_SPI_PORT.begin(); - Ethernet.init(ETHERNET_SPI_PORT, PIN_ETHERNET_SS); - - // Use static IP if build flags are defined, otherwise DHCP - #if defined(ETHERNET_STATIC_IP) && defined(ETHERNET_STATIC_GATEWAY) && defined(ETHERNET_STATIC_SUBNET) && defined(ETHERNET_STATIC_DNS) - IPAddress ip(ETHERNET_STATIC_IP); - IPAddress gateway(ETHERNET_STATIC_GATEWAY); - IPAddress subnet(ETHERNET_STATIC_SUBNET); - IPAddress dns(ETHERNET_STATIC_DNS); - Ethernet.begin(mac, ip, dns, gateway, subnet); - #else - ETHERNET_DEBUG_PRINTLN("Begin"); - if (Ethernet.begin(mac) == 0) { - ETHERNET_DEBUG_PRINTLN("Begin failed."); - - // DHCP failed -- let's figure out why - if (Ethernet.hardwareStatus() == EthernetNoHardware) // Check for Ethernet hardware present. - { - ETHERNET_DEBUG_PRINTLN("Ethernet hardware not found."); - return false; - } - if (Ethernet.linkStatus() == LinkOFF) // No physical connection - { - ETHERNET_DEBUG_PRINTLN("Ethernet cable not connected."); - return false; - } - ETHERNET_DEBUG_PRINTLN("Ethernet: DHCP failed for unknown reason."); - return false; - } - #endif - ETHERNET_DEBUG_PRINTLN("Ethernet begin complete"); - ETHERNET_DEBUG_PRINT_IP("IP", Ethernet.localIP()); - ETHERNET_DEBUG_PRINT_IP("Subnet", Ethernet.subnetMask()); - ETHERNET_DEBUG_PRINT_IP("Gateway", Ethernet.gatewayIP()); - - server.begin(); // start listening for clients - ETHERNET_DEBUG_PRINTLN("Ethernet: listening on TCP port: %d", ETHERNET_TCP_PORT); - - return true; -} - -void SerialEthernetInterface::enable() { - if (_isEnabled) return; - - _isEnabled = true; - clearBuffers(); -} - -void SerialEthernetInterface::disable() { - _isEnabled = false; -} - -size_t SerialEthernetInterface::writeFrame(const uint8_t src[], size_t len) { - if (len > MAX_FRAME_SIZE) { - ETHERNET_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d\n", len); - return 0; - } - - if (deviceConnected && len > 0) { - if (send_queue_len >= FRAME_QUEUE_SIZE) { - ETHERNET_DEBUG_PRINTLN("writeFrame(), send_queue is full!"); - return 0; - } - - send_queue[send_queue_len].len = len; // add to send queue - memcpy(send_queue[send_queue_len].buf, src, len); - send_queue_len++; - - return len; - } - return 0; -} - -bool SerialEthernetInterface::isWriteBusy() const { - return false; -} - -size_t SerialEthernetInterface::checkRecvFrame(uint8_t dest[]) { - // Use accept() (not available()) so we only see newly-accepted sockets. - // available() also returns existing connected sockets that have data, - // which would cause us to treat each inbound packet as a "new client" - // and stop() the underlying socket — disconnecting the companion. - auto newClient = server.accept(); - if (newClient) { - IPAddress new_ip = newClient.remoteIP(); - uint16_t new_port = newClient.remotePort(); - ETHERNET_DEBUG_PRINTLN( - "New client accepted %u.%u.%u.%u:%u", - new_ip[0], - new_ip[1], - new_ip[2], - new_ip[3], - new_port); - - deviceConnected = false; - if (client) { - ETHERNET_DEBUG_PRINTLN("Closing previous client"); - client.stop(); - } - _state = RECV_STATE_IDLE; - _frame_len = 0; - _rx_len = 0; - client = newClient; - ETHERNET_DEBUG_PRINTLN("Switched to new client"); - } - - if (client.connected()) { - if (!deviceConnected) { - ETHERNET_DEBUG_PRINTLN( - "Got connection %u.%u.%u.%u:%u", - client.remoteIP()[0], - client.remoteIP()[1], - client.remoteIP()[2], - client.remoteIP()[3], - client.remotePort()); - deviceConnected = true; - } - } else { - if (deviceConnected) { - deviceConnected = false; - ETHERNET_DEBUG_PRINTLN("Disconnected"); - } - } - - if (deviceConnected) { - if (send_queue_len > 0) { // first, check send queue - - _last_write = millis(); - int len = send_queue[0].len; - -#if ETHERNET_RAW_LINE - ETHERNET_DEBUG_PRINTLN("TX line len=%d", len); - client.write(send_queue[0].buf, len); - client.write("\r\n", 2); -#else - uint8_t pkt[3+len]; // use same header as serial interface so client can delimit frames - pkt[0] = '>'; - pkt[1] = (len & 0xFF); // LSB - pkt[2] = (len >> 8); // MSB - memcpy(&pkt[3], send_queue[0].buf, send_queue[0].len); - ETHERNET_DEBUG_PRINTLN("Sending frame len=%d", len); - #if ETHERNET_DEBUG_LOGGING && ARDUINO - ETHERNET_DEBUG_PRINTLN("TX frame len=%d", len); - #endif - client.write(pkt, 3 + len); -#endif - send_queue_len--; - for (int i = 0; i < send_queue_len; i++) { // delete top item from queue - send_queue[i] = send_queue[i + 1]; - } - } else { - while (client.available()) { - int c = client.read(); - if (c < 0) break; - -#if ETHERNET_RAW_LINE - if (c == '\r' || c == '\n') { - if (_rx_len == 0) { - continue; - } - uint16_t out_len = _rx_len; - if (out_len > MAX_FRAME_SIZE) { - out_len = MAX_FRAME_SIZE; - } - memcpy(dest, _rx_buf, out_len); - _rx_len = 0; - return out_len; - } - if (_rx_len < MAX_FRAME_SIZE) { - _rx_buf[_rx_len] = (uint8_t)c; - _rx_len++; - } -#else - switch (_state) { - case RECV_STATE_IDLE: - if (c == '<') { - _state = RECV_STATE_HDR_FOUND; - } - break; - case RECV_STATE_HDR_FOUND: - _frame_len = (uint8_t)c; - _state = RECV_STATE_LEN1_FOUND; - break; - case RECV_STATE_LEN1_FOUND: - _frame_len |= ((uint16_t)c) << 8; - _rx_len = 0; - _state = _frame_len > 0 ? RECV_STATE_LEN2_FOUND : RECV_STATE_IDLE; - break; - default: - if (_rx_len < MAX_FRAME_SIZE) { - _rx_buf[_rx_len] = (uint8_t)c; - } - _rx_len++; - if (_rx_len >= _frame_len) { - if (_frame_len > MAX_FRAME_SIZE) { - _frame_len = MAX_FRAME_SIZE; - } - #if ETHERNET_DEBUG_LOGGING && ARDUINO - ETHERNET_DEBUG_PRINTLN("RX frame len=%d", _frame_len); - #endif - memcpy(dest, _rx_buf, _frame_len); - _state = RECV_STATE_IDLE; - return _frame_len; - } - } -#endif - } - } - } - - return 0; -} - -bool SerialEthernetInterface::isConnected() const { - return deviceConnected; -} - -void SerialEthernetInterface::loop() { - Ethernet.maintain(); -} diff --git a/src/helpers/radiolib/CustomLLCC68.h b/src/helpers/radiolib/CustomLLCC68.h index 686b09ec..1dcd916f 100644 --- a/src/helpers/radiolib/CustomLLCC68.h +++ b/src/helpers/radiolib/CustomLLCC68.h @@ -2,10 +2,12 @@ #include <RadioLib.h> -#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received -#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 - class CustomLLCC68 : public LLCC68 { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; + public: CustomLLCC68(Module *mod) : LLCC68(mod) { } @@ -78,10 +80,61 @@ class CustomLLCC68 : public LLCC68 { return true; // success } + int16_t startReceive() override { + // include the PREAMBLE_DETECTED irq bit in reported flags + return LLCC68::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + bool isReceiving() { - uint16_t irq = getIrqFlags(); - bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); - return detected; + uint32_t irq = getIrqFlags(); + bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2 + bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4 + bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5 + uint32_t now = millis(); + if (hdrErr) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + // something cleared the header flag, reset our state. + _activityAt = 0; _headerSeen = false; + return false; + } + + if (header) { + if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) _activityAt = now; + if (now - _activityAt > _preambleMillis) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + + return false; + } + return true; + } + _activityAt = 0; _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); + } + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); } bool getRxBoostedGainMode() { diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index 851fd644..ae0fe0a2 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -14,6 +14,10 @@ public: ((CustomLLCC68 *)_radio)->setBandwidth(bw); ((CustomLLCC68 *)_radio)->setCodingRate(cr); updatePreamble(sf); + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomLLCC68 *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomLLCC68 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); + } bool isReceivingPacket() override { diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 4061c6b1..c481cf5e 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -4,6 +4,10 @@ #include "MeshCore.h" class CustomLR1110 : public LR1110 { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; bool _rx_boosted = false; public: @@ -31,10 +35,60 @@ class CustomLR1110 : public LR1110 { bool getRxBoostedGainMode() const { return _rx_boosted; } + int16_t startReceive() override { + // include the PREAMBLE_DETECTED irq bit in reported flags + return LR1110::startReceive(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + bool isReceiving() { - uint16_t irq = getIrqStatus(); - bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); - return detected; + uint32_t irq = getIrqStatus(); + bool preamble = irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED; // bit 4 + bool header = irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID; // bit 5 + bool hdrErr = irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR; // bit 6 + uint32_t now = millis(); + if (hdrErr) { + clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + // something cleared the header flag, reset our state. + _activityAt = 0; _headerSeen = false; + return false; + } + if (header) { + if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID | RADIOLIB_LR11X0_IRQ_HEADER_ERR); + _activityAt = 0; _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) _activityAt = now; + if (now - _activityAt > _preambleMillis) { + clearIrqState(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + + return false; + } + return true; + } + _activityAt = 0; _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); + } + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); } uint8_t getSpreadingFactor() const { return spreadingFactor; } diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index c6b1acb4..44230c61 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -14,6 +14,9 @@ public: ((CustomLR1110 *)_radio)->setBandwidth(bw); ((CustomLR1110 *)_radio)->setCodingRate(cr); updatePreamble(sf); + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomLR1110 *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomLR1110 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); } void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); } diff --git a/src/helpers/radiolib/CustomSTM32WLx.h b/src/helpers/radiolib/CustomSTM32WLx.h index cbdd5c8c..e312a5ee 100644 --- a/src/helpers/radiolib/CustomSTM32WLx.h +++ b/src/helpers/radiolib/CustomSTM32WLx.h @@ -2,16 +2,70 @@ #include <RadioLib.h> -#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received -#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 - class CustomSTM32WLx : public STM32WLx { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; + public: CustomSTM32WLx(STM32WLx_Module *mod) : STM32WLx(mod) { } - bool isReceiving() { - uint16_t irq = getIrqFlags(); - bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); - return detected; + int16_t startReceive() override { + // include the PREAMBLE_DETECTED irq bit in reported flags + return STM32WLx::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); } + + bool isReceiving() { + uint32_t irq = getIrqFlags(); + bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2 + bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4 + bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5 + uint32_t now = millis(); + if (hdrErr) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + // something cleared the header flag, reset our state. + _activityAt = 0; _headerSeen = false; + return false; + } + + if (header) { + if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) _activityAt = now; + if (now - _activityAt > _preambleMillis) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + + return false; + } + return true; + } + _activityAt = 0; _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); + } + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); + } + }; \ No newline at end of file diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index 97bf6820..a792a877 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -15,6 +15,9 @@ public: ((CustomSTM32WLx *)_radio)->setBandwidth(bw); ((CustomSTM32WLx *)_radio)->setCodingRate(cr); updatePreamble(sf); + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomSTM32WLx *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomSTM32WLx *)_radio)->setMaxPayloadMillis(pm.payloadMillis); } bool isReceivingPacket() override { diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index ca62fc26..b4ee6c97 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -3,10 +3,12 @@ #include <RadioLib.h> #include "MeshCore.h" -#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received -#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 - class CustomSX1262 : public SX1262 { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; + public: CustomSX1262(Module *mod) : SX1262(mod) { } @@ -98,10 +100,61 @@ class CustomSX1262 : public SX1262 { return true; // success } + int16_t startReceive() override { + // include the PREAMBLE_DETECTED irq bit in reported flags + return SX1262::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + } + bool isReceiving() { - uint16_t irq = getIrqFlags(); - bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); - return detected; + uint32_t irq = getIrqFlags(); + bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2 + bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4 + bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5 + uint32_t now = millis(); + if (hdrErr) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + // something cleared the header flag, reset our state. + _activityAt = 0; _headerSeen = false; + return false; + } + + if (header) { + if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) _activityAt = now; + if (now - _activityAt > _preambleMillis) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + + return false; + } + return true; + } + _activityAt = 0; _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); + } + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); } bool getRxBoostedGainMode() { diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 1d103f57..bfea50ec 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -18,6 +18,9 @@ public: ((CustomSX1262 *)_radio)->setBandwidth(bw); ((CustomSX1262 *)_radio)->setCodingRate(cr); updatePreamble(sf); + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomSX1262 *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomSX1262 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); } bool isReceivingPacket() override { diff --git a/src/helpers/radiolib/CustomSX1268.h b/src/helpers/radiolib/CustomSX1268.h index 0c6f828b..f915332d 100644 --- a/src/helpers/radiolib/CustomSX1268.h +++ b/src/helpers/radiolib/CustomSX1268.h @@ -2,10 +2,12 @@ #include <RadioLib.h> -#define SX126X_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received -#define SX126X_IRQ_PREAMBLE_DETECTED 0x04 - class CustomSX1268 : public SX1268 { + uint32_t _preambleMillis = 66; + uint32_t _maxPayloadMillis = 3934; + uint32_t _activityAt = 0; + bool _headerSeen = false; + public: CustomSX1268(Module *mod) : SX1268(mod) { } @@ -78,12 +80,64 @@ class CustomSX1268 : public SX1268 { return true; // success } - bool isReceiving() { - uint16_t irq = getIrqFlags(); - bool detected = (irq & SX126X_IRQ_HEADER_VALID) || (irq & SX126X_IRQ_PREAMBLE_DETECTED); - return detected; + int16_t startReceive() override { + // include the PREAMBLE_DETECTED irq bit in reported flags + return SX1268::startReceive(RADIOLIB_SX126X_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); } + bool isReceiving() { + uint32_t irq = getIrqFlags(); + bool preamble = irq & RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED; // bit 2 + bool header = irq & RADIOLIB_SX126X_IRQ_HEADER_VALID; // bit 4 + bool hdrErr = irq & RADIOLIB_SX126X_IRQ_HEADER_ERR; // bit 5 + uint32_t now = millis(); + if (hdrErr) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; + _headerSeen = false; + return false; + } + if (!header && _headerSeen) { + // something cleared the header flag, reset our state. + _activityAt = 0; _headerSeen = false; + return false; + } + + if (header) { + if (!_headerSeen) { _headerSeen = true; _activityAt = now; }; + if (now - _activityAt > _maxPayloadMillis) { + MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis); + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED | RADIOLIB_SX126X_IRQ_HEADER_VALID | RADIOLIB_SX126X_IRQ_HEADER_ERR | RADIOLIB_SX126X_IRQ_SYNC_WORD_VALID); + _activityAt = 0; _headerSeen = false; + return false; + } + return true; + } + if (preamble) { + if (_activityAt == 0) _activityAt = now; + if (now - _activityAt > _preambleMillis) { + clearIrqFlags(RADIOLIB_SX126X_IRQ_PREAMBLE_DETECTED); + _activityAt = 0; + MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis); + + return false; + } + return true; + } + _activityAt = 0; _headerSeen = false; + return false; + } + + void setPreambleMillis(uint32_t preambleMillis) { + _preambleMillis = preambleMillis; + MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis); + } + void setMaxPayloadMillis(uint32_t payloadMillis) { + _maxPayloadMillis = payloadMillis; + MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis); + } + + bool getRxBoostedGainMode() { uint8_t rxGain = 0; readRegister(RADIOLIB_SX126X_REG_RX_GAIN, &rxGain, 1); diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index bce56b99..104ba08b 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -18,6 +18,9 @@ public: ((CustomSX1268 *)_radio)->setBandwidth(bw); ((CustomSX1268 *)_radio)->setCodingRate(cr); updatePreamble(sf); + PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf)); + ((CustomSX1268 *)_radio)->setPreambleMillis(pm.preambleMillis); + ((CustomSX1268 *)_radio)->setMaxPayloadMillis(pm.payloadMillis); } bool isReceivingPacket() override { diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index c87bd7f3..f5a6da9f 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -234,3 +234,21 @@ float RadioLibWrapper::packetScoreInt(float snr, int sf, int packet_len) { return max(0.0, min(1.0, success_rate_based_on_snr * collision_penalty)); } + +PacketMillis RadioLibWrapper::calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols) { + // based on RadioLib's calculateTimeOnAir() + uint32_t tsym_us = ((uint32_t)10000 << sf) / (bw * 10); + uint32_t sfCoeff1_x4 = (sf == 5 || sf == 6) ? 25 : 17; // 6.25 : 4.25, semtech magic numbers to account for sync word + sfd + + // preamble + syncword + sfd + header + uint32_t preamble_us = (((preambleSymbols + 8) * 4 + sfCoeff1_x4) * tsym_us) / 4; + + // airtime for max packet at current radio settings + uint32_t total_us = _radio->getTimeOnAir(MAX_TRANS_UNIT); + // airtime for payload only (no preamble, header or SOF) + uint32_t payload_us = total_us > preamble_us ? total_us - preamble_us : 4000 - preamble_us; // fallback to 4 secs at worst case + // rescale payload_us for max possible CR + if (cr >= 5 && cr < 8) { payload_us = (payload_us * 8) / cr; } + + return PacketMillis {(preamble_us + 999) / 1000, (payload_us + 999) / 1000}; +} \ No newline at end of file diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 9a5b0cb4..947f9322 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -3,6 +3,11 @@ #include <Mesh.h> #include <RadioLib.h> +struct PacketMillis { + uint32_t preambleMillis; // preamble-detect -> header-valid deadline + uint32_t payloadMillis; // header-valid -> rx-done deadline +}; + class RadioLibWrapper : public mesh::Radio { protected: PhysicalLayer* _radio; @@ -54,6 +59,7 @@ public: virtual uint8_t getSpreadingFactor() const { return LORA_SF; } static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; } void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); } + PacketMillis calcMaxPacketMillis(uint8_t sf, float bw, uint8_t cr, uint8_t preambleSymbols); virtual int16_t performChannelScan(); int getNoiseFloor() const override { return _noise_floor; } diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index fe1bf926..c9aee3b6 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -13,8 +13,12 @@ #endif #endif -#ifndef PIN_GPS_EN_ACTIVE - #define PIN_GPS_EN_ACTIVE HIGH +#ifndef GPS_EN_ACTIVE + #ifdef PIN_GPS_EN_ACTIVE + #define GPS_EN_ACTIVE PIN_GPS_EN_ACTIVE + #else + #define GPS_EN_ACTIVE HIGH + #endif #endif #ifndef GPS_RESET @@ -25,11 +29,11 @@ #endif #endif -#ifndef GPS_RESET_FORCE +#ifndef GPS_RESET_ACTIVE #ifdef PIN_GPS_RESET_ACTIVE - #define GPS_RESET_FORCE PIN_GPS_RESET_ACTIVE + #define GPS_RESET_ACTIVE PIN_GPS_RESET_ACTIVE #else - #define GPS_RESET_FORCE LOW + #define GPS_RESET_ACTIVE LOW #endif #endif @@ -50,11 +54,11 @@ public : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) { if (_pin_reset != -1) { pinMode(_pin_reset, OUTPUT); - digitalWrite(_pin_reset, GPS_RESET_FORCE); + digitalWrite(_pin_reset, GPS_RESET_ACTIVE); } if (_pin_en != -1) { pinMode(_pin_en, OUTPUT); - digitalWrite(_pin_en, LOW); + digitalWrite(_pin_en, !GPS_EN_ACTIVE); } } @@ -72,27 +76,27 @@ public : void begin() override { if (_peripher_power) _peripher_power->claim(); if (_pin_en != -1) { - digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE); + digitalWrite(_pin_en, GPS_EN_ACTIVE); } if (_pin_reset != -1) { - digitalWrite(_pin_reset, !GPS_RESET_FORCE); + digitalWrite(_pin_reset, !GPS_RESET_ACTIVE); } } void reset() override { if (_pin_reset != -1) { - digitalWrite(_pin_reset, GPS_RESET_FORCE); + digitalWrite(_pin_reset, GPS_RESET_ACTIVE); delay(10); - digitalWrite(_pin_reset, !GPS_RESET_FORCE); + digitalWrite(_pin_reset, !GPS_RESET_ACTIVE); } } void stop() override { if (_pin_en != -1) { - digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE); + digitalWrite(_pin_en, !GPS_EN_ACTIVE); } if (_pin_reset != -1) { - digitalWrite(_pin_reset, GPS_RESET_FORCE); + digitalWrite(_pin_reset, GPS_RESET_ACTIVE); } if (_peripher_power) _peripher_power->release(); } @@ -101,7 +105,7 @@ public : // directly read the enable pin if present as gps can be // activated/deactivated outside of here ... if (_pin_en != -1) { - return digitalRead(_pin_en) == PIN_GPS_EN_ACTIVE; + return digitalRead(_pin_en) == GPS_EN_ACTIVE; } else { return true; // no enable so must be active } diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index dcc5fe03..b76a1b6c 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -3,12 +3,20 @@ #include <stdint.h> #include <string.h> +using ColorVal = uint16_t; + +class UIColor { +public: + // color definitions (by element _type_) + static ColorVal window_bkg, title_bkg, title_txt, primary_txt, secondary_txt, warning_txt, popup_bkg, popup_txt, corp_blue; +}; + class DisplayDriver { int _w, _h; protected: DisplayDriver(int w, int h) { _w = w; _h = h; } public: - enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light + //enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light int width() const { return _w; } int height() const { return _h; } @@ -18,9 +26,9 @@ public: virtual void turnOn() = 0; virtual void turnOff() = 0; virtual void clear() = 0; - virtual void startFrame(Color bkg = DARK) = 0; + virtual void startFrame(ColorVal bkg = UIColor::window_bkg) = 0; virtual void setTextSize(int sz) = 0; - virtual void setColor(Color c) = 0; + virtual void setColor(ColorVal c) = 0; virtual void setCursor(int x, int y) = 0; virtual void print(const char* str) = 0; virtual void printWordWrap(const char* str, int max_width) { print(str); } // fallback to basic print() if no override diff --git a/src/helpers/ui/E213Display.cpp b/src/helpers/ui/E213Display.cpp index 814693a0..daf26989 100644 --- a/src/helpers/ui/E213Display.cpp +++ b/src/helpers/ui/E213Display.cpp @@ -2,6 +2,17 @@ #include "../../MeshCore.h" +// Color scheme +ColorVal UIColor::window_bkg = WHITE; +ColorVal UIColor::title_bkg = WHITE; +ColorVal UIColor::title_txt = BLACK; +ColorVal UIColor::primary_txt = BLACK; +ColorVal UIColor::secondary_txt = BLACK; +ColorVal UIColor::warning_txt = BLACK; +ColorVal UIColor::popup_bkg = WHITE; +ColorVal UIColor::popup_txt = BLACK; +ColorVal UIColor::corp_blue = BLACK; + BaseDisplay* E213Display::detectEInk() { // Test 1: Logic of BUSY pin @@ -108,16 +119,18 @@ void E213Display::clear() { display->clear(); } -void E213Display::startFrame(Color bkg) { +void E213Display::startFrame(ColorVal bkg) { display_crc.reset(); // Fill screen with white first to ensure clean background display->fillRect(0, 0, width(), height(), WHITE); - if (bkg == LIGHT) { + if (bkg == 0) { // Fill with black if light background requested (inverted for e-ink) display->fillRect(0, 0, width(), height(), BLACK); } + _color = UIColor::primary_txt; + display->setTextColor(_color); } void E213Display::setTextSize(int sz) { @@ -126,9 +139,10 @@ void E213Display::setTextSize(int sz) { display->setTextSize(sz); } -void E213Display::setColor(Color c) { - display_crc.update<Color>(c); - // implemented in individual display methods +void E213Display::setColor(ColorVal c) { + _color = c; + display_crc.update<ColorVal>(c); + display->setTextColor(_color); } void E213Display::setCursor(int x, int y) { @@ -147,7 +161,7 @@ void E213Display::fillRect(int x, int y, int w, int h) { display_crc.update<int>(y); display_crc.update<int>(w); display_crc.update<int>(h); - display->fillRect(x, y, w, h, BLACK); + display->fillRect(x, y, w, h, _color); } void E213Display::drawRect(int x, int y, int w, int h) { @@ -155,7 +169,7 @@ void E213Display::drawRect(int x, int y, int w, int h) { display_crc.update<int>(y); display_crc.update<int>(w); display_crc.update<int>(h); - display->drawRect(x, y, w, h, BLACK); + display->drawRect(x, y, w, h, _color); } void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { @@ -179,7 +193,7 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { // If the bit is set, draw the pixel if (bitSet) { - display->drawPixel(x + bx, y + by, BLACK); + display->drawPixel(x + bx, y + by, _color); } } } diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index add8f11b..32567a79 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -16,6 +16,7 @@ class E213Display : public DisplayDriver { RefCountedDigitalPin* _periph_power; CRC32 display_crc; uint32_t last_display_crc_value = 0; + uint16_t _color; public: E213Display(RefCountedDigitalPin* periph_power = NULL) : DisplayDriver(250, 122), _periph_power(periph_power) {} @@ -30,9 +31,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char *str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/E290Display.cpp b/src/helpers/ui/E290Display.cpp index ef4df05e..34bbcdbc 100644 --- a/src/helpers/ui/E290Display.cpp +++ b/src/helpers/ui/E290Display.cpp @@ -2,6 +2,17 @@ #include "../../MeshCore.h" +// Color scheme +ColorVal UIColor::window_bkg = WHITE; +ColorVal UIColor::title_bkg = WHITE; +ColorVal UIColor::title_txt = BLACK; +ColorVal UIColor::primary_txt = BLACK; +ColorVal UIColor::secondary_txt = BLACK; +ColorVal UIColor::warning_txt = BLACK; +ColorVal UIColor::popup_bkg = WHITE; +ColorVal UIColor::popup_txt = BLACK; +ColorVal UIColor::corp_blue = BLACK; + bool E290Display::begin() { if (_init) return true; @@ -62,15 +73,17 @@ void E290Display::clear() { display.clear(); } -void E290Display::startFrame(Color bkg) { +void E290Display::startFrame(ColorVal bkg) { display_crc.reset(); // Fill screen with white first to ensure clean background display.fillRect(0, 0, width(), height(), WHITE); - if (bkg == LIGHT) { + if (bkg == 0) { // Fill with black if light background requested (inverted for e-ink) display.fillRect(0, 0, width(), height(), BLACK); } + _color = UIColor::primary_txt; + display.setTextColor(_color); } void E290Display::setTextSize(int sz) { @@ -79,9 +92,10 @@ void E290Display::setTextSize(int sz) { display.setTextSize(sz); } -void E290Display::setColor(Color c) { - display_crc.update<Color>(c); - // implemented in individual display methods +void E290Display::setColor(ColorVal c) { + _color = c; + display_crc.update<ColorVal>(c); + display.setTextColor(_color); } void E290Display::setCursor(int x, int y) { @@ -100,7 +114,7 @@ void E290Display::fillRect(int x, int y, int w, int h) { display_crc.update<int>(y); display_crc.update<int>(w); display_crc.update<int>(h); - display.fillRect(x, y, w, h, BLACK); + display.fillRect(x, y, w, h, _color); } void E290Display::drawRect(int x, int y, int w, int h) { @@ -108,7 +122,7 @@ void E290Display::drawRect(int x, int y, int w, int h) { display_crc.update<int>(y); display_crc.update<int>(w); display_crc.update<int>(h); - display.drawRect(x, y, w, h, BLACK); + display.drawRect(x, y, w, h, _color); } void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { @@ -132,7 +146,7 @@ void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { // If the bit is set, draw the pixel if (bitSet) { - display.drawPixel(x + bx, y + by, BLACK); + display.drawPixel(x + bx, y + by, _color); } } } diff --git a/src/helpers/ui/E290Display.h b/src/helpers/ui/E290Display.h index 88bf34ff..bf6296bb 100644 --- a/src/helpers/ui/E290Display.h +++ b/src/helpers/ui/E290Display.h @@ -16,6 +16,7 @@ class E290Display : public DisplayDriver { RefCountedDigitalPin* _periph_power; CRC32 display_crc; uint32_t last_display_crc_value = 0; + uint16_t _color; public: E290Display(RefCountedDigitalPin* periph_power = NULL) : DisplayDriver(296, 128), _periph_power(periph_power) {} @@ -26,9 +27,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char *str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index ad47754b..13dafa34 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -14,6 +14,18 @@ SPIClass SPI1 = SPIClass(FSPI); #endif +// Color scheme +ColorVal UIColor::window_bkg = GxEPD_WHITE; +ColorVal UIColor::title_bkg = GxEPD_WHITE; +ColorVal UIColor::title_txt = GxEPD_BLACK; +ColorVal UIColor::primary_txt = GxEPD_BLACK; +ColorVal UIColor::secondary_txt = GxEPD_BLACK; +ColorVal UIColor::warning_txt = GxEPD_BLACK; +ColorVal UIColor::popup_bkg = GxEPD_WHITE; +ColorVal UIColor::popup_txt = GxEPD_BLACK; +ColorVal UIColor::corp_blue = GxEPD_BLACK; + + bool GxEPDDisplay::begin() { display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); #ifdef ESP32 @@ -61,9 +73,9 @@ void GxEPDDisplay::clear() { display_crc.reset(); } -void GxEPDDisplay::startFrame(Color bkg) { - display.fillScreen(GxEPD_WHITE); - display.setTextColor(_curr_color = GxEPD_BLACK); +void GxEPDDisplay::startFrame(ColorVal bkg) { + display.fillScreen(bkg); + display.setTextColor(_curr_color = UIColor::primary_txt); display_crc.reset(); } @@ -85,14 +97,9 @@ void GxEPDDisplay::setTextSize(int sz) { } } -void GxEPDDisplay::setColor(Color c) { - display_crc.update<Color> (c); - // colours need to be inverted for epaper displays - if (c == DARK) { - display.setTextColor(_curr_color = GxEPD_WHITE); - } else { - display.setTextColor(_curr_color = GxEPD_BLACK); - } +void GxEPDDisplay::setColor(ColorVal c) { + display_crc.update<ColorVal> (c); + display.setTextColor(_curr_color = c); } void GxEPDDisplay::setCursor(int x, int y) { diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index 219b6076..c653eac4 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -51,9 +51,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/LGFXDisplay.cpp b/src/helpers/ui/LGFXDisplay.cpp index a53cbc62..7bdd52a2 100644 --- a/src/helpers/ui/LGFXDisplay.cpp +++ b/src/helpers/ui/LGFXDisplay.cpp @@ -1,5 +1,16 @@ #include "LGFXDisplay.h" +// Color scheme +ColorVal UIColor::window_bkg = 0xFFFF; +ColorVal UIColor::title_bkg = 0x001F; +ColorVal UIColor::title_txt = 0xFFFF; +ColorVal UIColor::primary_txt = 0x0000; +ColorVal UIColor::secondary_txt = (18 << 11) | (36 << 5) | 18; // mid-gray +ColorVal UIColor::warning_txt = 0xFD20; +ColorVal UIColor::popup_bkg = 0x07FF; // CYAN +ColorVal UIColor::popup_txt = 0x0000; +ColorVal UIColor::corp_blue = 0x001A; + bool LGFXDisplay::begin() { turnOn(); display->init(); @@ -35,45 +46,20 @@ void LGFXDisplay::clear() { buffer.clearDisplay(); } -void LGFXDisplay::startFrame(Color bkg) { +void LGFXDisplay::startFrame(ColorVal bkg) { // display->startWrite(); // display->getScanLine(); - buffer.clearDisplay(); - buffer.setTextColor(TFT_WHITE); + _color = bkg; + buffer.fillScreen(_color); + buffer.setTextColor(_color = UIColor::primary_txt); } void LGFXDisplay::setTextSize(int sz) { buffer.setTextSize(sz); } -void LGFXDisplay::setColor(Color c) { - // _color = (c != 0) ? ILI9342_WHITE : ILI9342_BLACK; - switch (c) { - case DARK: - _color = TFT_BLACK; - break; - case LIGHT: - _color = TFT_WHITE; - break; - case RED: - _color = TFT_RED; - break; - case GREEN: - _color = TFT_GREEN; - break; - case BLUE: - _color = TFT_BLUE; - break; - case YELLOW: - _color = TFT_YELLOW; - break; - case ORANGE: - _color = TFT_ORANGE; - break; - default: - _color = TFT_WHITE; - } - buffer.setTextColor(_color); +void LGFXDisplay::setColor(ColorVal c) { + buffer.setTextColor(_color = c); } void LGFXDisplay::setCursor(int x, int y) { diff --git a/src/helpers/ui/LGFXDisplay.h b/src/helpers/ui/LGFXDisplay.h index ad7212ec..a2d660b2 100644 --- a/src/helpers/ui/LGFXDisplay.h +++ b/src/helpers/ui/LGFXDisplay.h @@ -25,9 +25,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/NV3001BDisplay.cpp b/src/helpers/ui/NV3001BDisplay.cpp index 03825cc0..1ff7765c 100644 --- a/src/helpers/ui/NV3001BDisplay.cpp +++ b/src/helpers/ui/NV3001BDisplay.cpp @@ -96,18 +96,16 @@ #define NV3001B_TEXT_SIZE2_SCALE_Y 3 #endif -static uint16_t mapColor(DisplayDriver::Color c) { - switch (c) { - case DisplayDriver::DARK: return 0x0000; - case DisplayDriver::LIGHT: return 0xffff; - case DisplayDriver::RED: return 0xf800; - case DisplayDriver::GREEN: return 0x07e0; - case DisplayDriver::BLUE: return 0x001f; - case DisplayDriver::YELLOW: return 0xffe0; - case DisplayDriver::ORANGE: return 0xfd20; - default: return 0xffff; - } -} +// Color scheme +ColorVal UIColor::window_bkg = 0xFFFF; +ColorVal UIColor::title_bkg = 0x001F; +ColorVal UIColor::title_txt = 0xFFFF; +ColorVal UIColor::primary_txt = 0x0000; +ColorVal UIColor::secondary_txt = (18 << 11) | (36 << 5) | 18; // mid-gray +ColorVal UIColor::warning_txt = 0xFD20; +ColorVal UIColor::popup_bkg = 0x07FF; // CYAN +ColorVal UIColor::popup_txt = 0x0000; +ColorVal UIColor::corp_blue = 0x001A; static int scaleX(int x) { return (int)(x * DISPLAY_SCALE_X); @@ -465,15 +463,15 @@ void NV3001BDisplay::turnOff() { void NV3001BDisplay::clear() { uint16_t saved = color; - color = 0x0000; + color = UIColor::window_bkg; fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT); color = saved; } -void NV3001BDisplay::startFrame(Color bkg) { - color = mapColor(bkg); +void NV3001BDisplay::startFrame(ColorVal bkg) { + color = bkg; fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT); - color = 0xffff; + color = UIColor::primary_txt; text_size = 1; cursor_x = 0; cursor_y = 0; @@ -483,8 +481,8 @@ void NV3001BDisplay::setTextSize(int sz) { text_size = sz < 1 ? 1 : sz; } -void NV3001BDisplay::setColor(Color c) { - color = mapColor(c); +void NV3001BDisplay::setColor(ColorVal c) { + color = c; } void NV3001BDisplay::setCursor(int x, int y) { diff --git a/src/helpers/ui/NV3001BDisplay.h b/src/helpers/ui/NV3001BDisplay.h index 98cdaae8..76505b5d 100644 --- a/src/helpers/ui/NV3001BDisplay.h +++ b/src/helpers/ui/NV3001BDisplay.h @@ -55,9 +55,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/NullDisplayDriver.cpp b/src/helpers/ui/NullDisplayDriver.cpp new file mode 100644 index 00000000..1500f5c1 --- /dev/null +++ b/src/helpers/ui/NullDisplayDriver.cpp @@ -0,0 +1,11 @@ +#include "NullDisplayDriver.h" + +ColorVal UIColor::window_bkg = 0; +ColorVal UIColor::title_bkg = 0; +ColorVal UIColor::title_txt = 0; +ColorVal UIColor::primary_txt = 0; +ColorVal UIColor::secondary_txt = 0; +ColorVal UIColor::warning_txt = 0; +ColorVal UIColor::popup_bkg = 0; +ColorVal UIColor::popup_txt = 0; +ColorVal UIColor::corp_blue = 0; diff --git a/src/helpers/ui/NullDisplayDriver.h b/src/helpers/ui/NullDisplayDriver.h index 2a9670bd..ff214c88 100644 --- a/src/helpers/ui/NullDisplayDriver.h +++ b/src/helpers/ui/NullDisplayDriver.h @@ -11,9 +11,9 @@ public: void turnOn() override { } void turnOff() override { } void clear() override { } - void startFrame(Color bkg = DARK) override { } + void startFrame(ColorVal bkg = UIColor::window_bkg) override { } void setTextSize(int sz) override { } - void setColor(Color c) override { } + void setColor(ColorVal c) override { } void setCursor(int x, int y) override { } void print(const char* str) override { } void fillRect(int x, int y, int w, int h) override { } diff --git a/src/helpers/ui/SH1106Display.cpp b/src/helpers/ui/SH1106Display.cpp index f383bb00..c3840c02 100644 --- a/src/helpers/ui/SH1106Display.cpp +++ b/src/helpers/ui/SH1106Display.cpp @@ -9,9 +9,23 @@ bool SH1106Display::i2c_probe(TwoWire &wire, uint8_t addr) return (error == 0); } +// Color scheme +ColorVal UIColor::window_bkg = SH110X_BLACK; +ColorVal UIColor::title_bkg = SH110X_BLACK; +ColorVal UIColor::title_txt = SH110X_WHITE; +ColorVal UIColor::primary_txt = SH110X_WHITE; +ColorVal UIColor::secondary_txt = SH110X_WHITE; +ColorVal UIColor::warning_txt = SH110X_WHITE; +ColorVal UIColor::popup_bkg = SH110X_BLACK; +ColorVal UIColor::popup_txt = SH110X_WHITE; +ColorVal UIColor::corp_blue = SH110X_WHITE; + bool SH1106Display::begin() { - return display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS); + // Wire must already be initialised by board.begin() before this is called. + // Boards with non-standard SH1106 addresses should define DISPLAY_ADDRESS + // in their variant/platformio configuration. + return i2c_probe(Wire, DISPLAY_ADDRESS) && display.begin(DISPLAY_ADDRESS, true); } void SH1106Display::turnOn() @@ -32,7 +46,7 @@ void SH1106Display::clear() display.display(); } -void SH1106Display::startFrame(Color bkg) +void SH1106Display::startFrame(ColorVal bkg) { display.clearDisplay(); // TODO: apply 'bkg' _color = SH110X_WHITE; @@ -46,9 +60,9 @@ void SH1106Display::setTextSize(int sz) display.setTextSize(sz); } -void SH1106Display::setColor(Color c) +void SH1106Display::setColor(ColorVal c) { - _color = (c != 0) ? SH110X_WHITE : SH110X_BLACK; + _color = c; display.setTextColor(_color); } @@ -74,7 +88,7 @@ void SH1106Display::drawRect(int x, int y, int w, int h) void SH1106Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { - display.drawBitmap(x, y, bits, w, h, SH110X_WHITE); + display.drawBitmap(x, y, bits, w, h, _color); } uint16_t SH1106Display::getTextWidth(const char *str) diff --git a/src/helpers/ui/SH1106Display.h b/src/helpers/ui/SH1106Display.h index b52a6adf..4e269d5e 100644 --- a/src/helpers/ui/SH1106Display.h +++ b/src/helpers/ui/SH1106Display.h @@ -30,9 +30,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char *str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 464b2642..ab2d77fb 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -6,6 +6,17 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { return (error == 0); } +// Color scheme +ColorVal UIColor::window_bkg = SSD1306_BLACK; +ColorVal UIColor::title_bkg = SSD1306_BLACK; +ColorVal UIColor::title_txt = SSD1306_WHITE; +ColorVal UIColor::primary_txt = SSD1306_WHITE; +ColorVal UIColor::secondary_txt = SSD1306_WHITE; +ColorVal UIColor::warning_txt = SSD1306_WHITE; +ColorVal UIColor::popup_bkg = SSD1306_BLACK; +ColorVal UIColor::popup_txt = SSD1306_WHITE; +ColorVal UIColor::corp_blue = SSD1306_WHITE; + bool SSD1306Display::begin() { if (!_isOn) { if (_peripher_power) _peripher_power->claim(); @@ -44,7 +55,7 @@ void SSD1306Display::clear() { display.display(); } -void SSD1306Display::startFrame(Color bkg) { +void SSD1306Display::startFrame(ColorVal bkg) { display.clearDisplay(); // TODO: apply 'bkg' _color = SSD1306_WHITE; display.setTextColor(_color); @@ -56,8 +67,8 @@ void SSD1306Display::setTextSize(int sz) { display.setTextSize(sz); } -void SSD1306Display::setColor(Color c) { - _color = (c != 0) ? SSD1306_WHITE : SSD1306_BLACK; +void SSD1306Display::setColor(ColorVal c) { + _color = c; display.setTextColor(_color); } @@ -78,7 +89,7 @@ void SSD1306Display::drawRect(int x, int y, int w, int h) { } void SSD1306Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x, y, bits, w, h, SSD1306_WHITE); + display.drawBitmap(x, y, bits, w, h, _color); } uint16_t SSD1306Display::getTextWidth(const char* str) { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index d843da85..5dabbc58 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -35,9 +35,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index 62b27a16..413ea7ec 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -421,6 +421,17 @@ bool ST7735Display::i2c_probe(TwoWire& wire, uint8_t addr) { #define PIN_TFT_LEDA_CTL_ACTIVE HIGH #endif +// Color scheme +ColorVal UIColor::window_bkg = ST77XX_WHITE; +ColorVal UIColor::title_bkg = ST77XX_BLUE; +ColorVal UIColor::title_txt = ST77XX_WHITE; +ColorVal UIColor::primary_txt = ST77XX_BLACK; +ColorVal UIColor::secondary_txt = (18 << 11) | (36 << 5) | 18; // mid-gray +ColorVal UIColor::warning_txt = ST77XX_ORANGE; +ColorVal UIColor::popup_bkg = ST77XX_CYAN; +ColorVal UIColor::popup_txt = ST77XX_BLACK; +ColorVal UIColor::corp_blue = 0x001A; + bool ST7735Display::begin() { if (!sprite) { // alloc offscreen canvas @@ -531,9 +542,9 @@ void ST7735Display::clear() { sprite->fillScreen(ST77XX_BLACK); } -void ST7735Display::startFrame(Color bkg) { - sprite->fillScreen(ST77XX_BLACK); - sprite->setTextColor(curr_color = ST77XX_WHITE); +void ST7735Display::startFrame(ColorVal bkg) { + sprite->fillScreen(bkg); + sprite->setTextColor(curr_color = UIColor::primary_txt); sprite->setFreeFont(); sprite->setTextSize(1); // This one affects size of Please wait... message //sprite->cp437(true); // Use full 256 char 'Code Page 437' font @@ -543,33 +554,8 @@ void ST7735Display::setTextSize(int sz) { sprite->setTextSize(sz); } -void ST7735Display::setColor(Color c) { - switch (c) { - case DisplayDriver::DARK : - curr_color = ST77XX_BLACK; - break; - case DisplayDriver::LIGHT : - curr_color = ST77XX_WHITE; - break; - case DisplayDriver::RED : - curr_color = ST77XX_RED; - break; - case DisplayDriver::GREEN : - curr_color = ST77XX_GREEN; - break; - case DisplayDriver::BLUE : - curr_color = ST77XX_BLUE; - break; - case DisplayDriver::YELLOW : - curr_color = ST77XX_YELLOW; - break; - case DisplayDriver::ORANGE : - curr_color = ST77XX_ORANGE; - break; - default: - curr_color = ST77XX_WHITE; - break; - } +void ST7735Display::setColor(ColorVal c) { + curr_color = c; sprite->setTextColor(curr_color); } diff --git a/src/helpers/ui/ST7735Display.h b/src/helpers/ui/ST7735Display.h index 68c83db2..6e289b21 100644 --- a/src/helpers/ui/ST7735Display.h +++ b/src/helpers/ui/ST7735Display.h @@ -33,9 +33,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 98d69395..7d039a13 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -26,6 +26,17 @@ #define SCALE_Y DISPLAY_SCALE_Y #endif +// Color scheme +ColorVal UIColor::window_bkg = OLEDDISPLAY_COLOR::BLACK; +ColorVal UIColor::title_bkg = OLEDDISPLAY_COLOR::BLACK; +ColorVal UIColor::title_txt = OLEDDISPLAY_COLOR::WHITE; +ColorVal UIColor::primary_txt = OLEDDISPLAY_COLOR::WHITE; +ColorVal UIColor::secondary_txt = OLEDDISPLAY_COLOR::WHITE; +ColorVal UIColor::warning_txt = OLEDDISPLAY_COLOR::WHITE; +ColorVal UIColor::popup_bkg = OLEDDISPLAY_COLOR::BLACK; +ColorVal UIColor::popup_txt = OLEDDISPLAY_COLOR::WHITE; +ColorVal UIColor::corp_blue = OLEDDISPLAY_COLOR::WHITE; + bool ST7789Display::begin() { if(!_isOn) { pinMode(PIN_TFT_VDD_CTL, OUTPUT); @@ -90,10 +101,9 @@ void ST7789Display::clear() { display.clear(); } -void ST7789Display::startFrame(Color bkg) { - display.clear(); - _color = ST77XX_WHITE; - display.setRGB(_color); +void ST7789Display::startFrame(ColorVal bkg) { + display.clear(); // TODO: use bkg + setColor(UIColor::primary_txt); display.setFont(ArialMT_Plain_16); } @@ -110,38 +120,10 @@ void ST7789Display::setTextSize(int sz) { } } -void ST7789Display::setColor(Color c) { - switch (c) { - case DisplayDriver::DARK : - _color = ST77XX_BLACK; - display.setColor(OLEDDISPLAY_COLOR::BLACK); - break; -#if 0 - case DisplayDriver::LIGHT : - _color = ST77XX_WHITE; - break; - case DisplayDriver::RED : - _color = ST77XX_RED; - break; - case DisplayDriver::GREEN : - _color = ST77XX_GREEN; - break; - case DisplayDriver::BLUE : - _color = ST77XX_BLUE; - break; - case DisplayDriver::YELLOW : - _color = ST77XX_YELLOW; - break; - case DisplayDriver::ORANGE : - _color = ST77XX_ORANGE; - break; -#endif - default: - _color = ST77XX_WHITE; - display.setColor(OLEDDISPLAY_COLOR::WHITE); - break; - } - display.setRGB(_color); +void ST7789Display::setColor(ColorVal c) { + _color = c; + display.setColor((OLEDDISPLAY_COLOR)_color); + display.setRGB(_color == OLEDDISPLAY_COLOR::WHITE ? ST77XX_WHITE : ST77XX_BLACK); } void ST7789Display::setCursor(int x, int y) { diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index 9822a67d..580ad463 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -27,9 +27,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; void printWordWrap(const char* str, int max_width) override; diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 7a02668b..a9f30dd5 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -23,6 +23,17 @@ bool ST7789LCDDisplay::i2c_probe(TwoWire& wire, uint8_t addr) { return true; } +// Color scheme +ColorVal UIColor::window_bkg = ST77XX_WHITE; +ColorVal UIColor::title_bkg = ST77XX_BLUE; +ColorVal UIColor::title_txt = ST77XX_WHITE; +ColorVal UIColor::primary_txt = ST77XX_BLACK; +ColorVal UIColor::secondary_txt = (18 << 11) | (36 << 5) | 18; // mid-gray +ColorVal UIColor::warning_txt = ST77XX_ORANGE; +ColorVal UIColor::popup_bkg = ST77XX_CYAN; +ColorVal UIColor::popup_txt = ST77XX_BLACK; +ColorVal UIColor::corp_blue = 0x001A; + bool ST7789LCDDisplay::begin() { if (!_isOn) { if (_peripher_power) _peripher_power->claim(); @@ -78,9 +89,9 @@ void ST7789LCDDisplay::clear() { display.fillScreen(ST77XX_BLACK); } -void ST7789LCDDisplay::startFrame(Color bkg) { - display.fillScreen(ST77XX_BLACK); - display.setTextColor(ST77XX_WHITE); +void ST7789LCDDisplay::startFrame(ColorVal bkg) { + display.fillScreen(bkg); + display.setTextColor(_color = UIColor::primary_txt); display.setTextSize(1 * DISPLAY_SCALE_X); // This one affects size of Please wait... message display.cp437(true); // Use full 256 char 'Code Page 437' font } @@ -89,34 +100,8 @@ void ST7789LCDDisplay::setTextSize(int sz) { display.setTextSize(sz * DISPLAY_SCALE_X); } -void ST7789LCDDisplay::setColor(Color c) { - switch (c) { - case DisplayDriver::DARK : - _color = ST77XX_BLACK; - break; - case DisplayDriver::LIGHT : - _color = ST77XX_WHITE; - break; - case DisplayDriver::RED : - _color = ST77XX_RED; - break; - case DisplayDriver::GREEN : - _color = ST77XX_GREEN; - break; - case DisplayDriver::BLUE : - _color = ST77XX_BLUE; - break; - case DisplayDriver::YELLOW : - _color = ST77XX_YELLOW; - break; - case DisplayDriver::ORANGE : - _color = ST77XX_ORANGE; - break; - default: - _color = ST77XX_WHITE; - break; - } - display.setTextColor(_color); +void ST7789LCDDisplay::setColor(ColorVal c) { + display.setTextColor(_color = c); } void ST7789LCDDisplay::setCursor(int x, int y) { diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index 03a6d3f1..b5127d35 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -47,9 +47,9 @@ public: void turnOn() override; void turnOff() override; void clear() override; - void startFrame(Color bkg = DARK) override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; void setTextSize(int sz) override; - void setColor(Color c) override; + void setColor(ColorVal c) override; void setCursor(int x, int y) override; void print(const char* str) override; void fillRect(int x, int y, int w, int h) override; diff --git a/src/helpers/ui/U8g2Display.cpp b/src/helpers/ui/U8g2Display.cpp new file mode 100644 index 00000000..e8d7fe2f --- /dev/null +++ b/src/helpers/ui/U8g2Display.cpp @@ -0,0 +1,12 @@ +#include "U8g2Display.h" + +// Color scheme +ColorVal UIColor::window_bkg = 0; +ColorVal UIColor::title_bkg = 0; +ColorVal UIColor::title_txt = 1; +ColorVal UIColor::primary_txt = 1; +ColorVal UIColor::secondary_txt = 1; +ColorVal UIColor::warning_txt = 1; +ColorVal UIColor::popup_bkg = 0; +ColorVal UIColor::popup_txt = 1; +ColorVal UIColor::corp_blue = 1; diff --git a/src/helpers/ui/U8g2Display.h b/src/helpers/ui/U8g2Display.h index 73c58936..a87ee19b 100644 --- a/src/helpers/ui/U8g2Display.h +++ b/src/helpers/ui/U8g2Display.h @@ -72,10 +72,10 @@ public: _u8g2.sendBuffer(); } - void startFrame(Color bkg = DARK) override { - _u8g2.clearBuffer(); - _drawColor = 1; - _u8g2.setDrawColor(1); + void startFrame(ColorVal bkg = UIColor::window_bkg) override { + _u8g2.clearBuffer(); // TODO: apply 'bkg' color + _drawColor = UIColor::primary_txt; + _u8g2.setDrawColor(_drawColor); applyFont(1); } @@ -83,8 +83,8 @@ public: applyFont(sz); } - void setColor(Color c) override { - _drawColor = (c != DARK) ? 1 : 0; + void setColor(ColorVal c) override { + _drawColor = c; _u8g2.setDrawColor(_drawColor); } @@ -94,22 +94,18 @@ public: } void print(const char* str) override { - _u8g2.setDrawColor(_drawColor); _u8g2.drawStr(_cursorX, _cursorY, str); } void fillRect(int x, int y, int w, int h) override { - _u8g2.setDrawColor(_drawColor); _u8g2.drawBox(x, y, w, h); } void drawRect(int x, int y, int w, int h) override { - _u8g2.setDrawColor(_drawColor); _u8g2.drawFrame(x, y, w, h); } void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override { - _u8g2.setDrawColor(1); _u8g2.drawXBM(x, y, w, h, bits); } diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h new file mode 100644 index 00000000..77499fe4 --- /dev/null +++ b/test/mocks/Arduino.h @@ -0,0 +1,17 @@ +#pragma once + +#include <cstdint> +#include <cmath> +#include "Stream.h" + +inline uint32_t g_mock_millis = 0; + +using std::isnan; + +inline uint32_t millis() { + return g_mock_millis; +} + +inline void delay(uint32_t ms) { + g_mock_millis += ms; +} diff --git a/test/mocks/CayenneLPP.h b/test/mocks/CayenneLPP.h new file mode 100644 index 00000000..9d51c0fc --- /dev/null +++ b/test/mocks/CayenneLPP.h @@ -0,0 +1,11 @@ +#pragma once + +#include <cstddef> +#include <cstdint> + +class CayenneLPP { +public: + explicit CayenneLPP(size_t) {} + const uint8_t* getBuffer() const { return nullptr; } + uint16_t getSize() const { return 0; } +}; diff --git a/test/mocks/Identity.h b/test/mocks/Identity.h new file mode 100644 index 00000000..5c3d25e9 --- /dev/null +++ b/test/mocks/Identity.h @@ -0,0 +1,39 @@ +#pragma once + +#include <cstdint> +#include <cstring> +#include "Utils.h" + +namespace mesh { + +class Identity { +public: + uint8_t pub_key[PUB_KEY_SIZE]; + + Identity() { + std::memset(pub_key, 0, sizeof(pub_key)); + } + + explicit Identity(const uint8_t* src) { + std::memcpy(pub_key, src, PUB_KEY_SIZE); + } + + bool verify(const uint8_t*, const uint8_t*, int) const { + return true; + } +}; + +class LocalIdentity : public Identity { +public: + LocalIdentity() : Identity() {} + + void sign(uint8_t* sig, const uint8_t*, int) const { + std::memset(sig, 0x5A, SIGNATURE_SIZE); + } + + void calcSharedSecret(uint8_t* secret, const uint8_t*) const { + std::memset(secret, 0x11, PUB_KEY_SIZE); + } +}; + +} diff --git a/test/mocks/Mesh.h b/test/mocks/Mesh.h new file mode 100644 index 00000000..b6c263c1 --- /dev/null +++ b/test/mocks/Mesh.h @@ -0,0 +1,27 @@ +#pragma once + +#include <cstdint> + +namespace mesh { + +class Radio { +public: + virtual ~Radio() = default; + virtual bool isReceiving() { return false; } + virtual uint32_t getEstAirtimeFor(uint16_t) { return 10; } + virtual bool startSendRaw(const uint8_t*, uint16_t) { return true; } + virtual bool isSendComplete() { return true; } + virtual void onSendFinished() {} + virtual int16_t getNoiseFloor() { return -120; } +}; + +class MainBoard { +public: + virtual ~MainBoard() = default; + virtual uint16_t getBattMilliVolts() { return 4200; } + virtual float getMCUTemperature() { return 25.0f; } + virtual const char* getManufacturerName() { return "mock-board"; } + virtual void reboot() {} +}; + +} diff --git a/test/mocks/Stream.h b/test/mocks/Stream.h index 195a3029..56675607 100644 --- a/test/mocks/Stream.h +++ b/test/mocks/Stream.h @@ -1,10 +1,73 @@ #pragma once +#include <stddef.h> +#include <stdint.h> +#include <string.h> + // Mock Stream class for native testing // Provides minimal interface needed by Utils.h -class Stream { +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +class Print +{ public: - virtual void print(char c) {} - virtual void print(const char* str) {} + virtual size_t write(uint8_t b) { return 1; } + size_t write(const char *str) + { + if(str == NULL) { + return 0; + } + return write((const uint8_t *) str, strlen(str)); + } + virtual size_t write(const uint8_t *buffer, size_t size) { + size_t t = 0; + for (int i = 0; i < size; i++) { t += write(buffer[i]); } + return t; + } + size_t write(const char *buffer, size_t size) + { + return write((const uint8_t *) buffer, size); + } + + virtual size_t print(unsigned char b, int r = DEC) { return 0; } + virtual size_t print(int v, int r = DEC) { return 0; } + virtual size_t print(unsigned int v, int r = DEC) { return 0; } + virtual size_t print(long v, int r = DEC) { return 0; } + virtual size_t print(unsigned long v, int r = DEC) { return 0; } + virtual size_t print(long long v, int r = DEC) { return 0; } + virtual size_t print(unsigned long long v, int r = DEC) { return 0; } + virtual size_t print(double v, int p = 2) { return 0; } + + size_t print(char c) { return write(c); } + size_t print(const char* str) { return write(str); } + + //size_t println(void) { return 0; } + + virtual void flush() { /* Empty implementation for backward compatibility */ } }; + +class Stream: public Print +{ +public: + virtual ~Stream() = default; + virtual int available() { return 0; } + virtual int availableForWrite() { return 0; } + virtual int read() { return -1; } + virtual int peek() { return 0; } + + virtual size_t readBytes(char *buffer, size_t length) { + size_t i = 0; + while (i < length && available()) { + buffer[i++] = read(); + } + return i; + } + virtual size_t readBytes(uint8_t *buffer, size_t length) + { + return readBytes((char *) buffer, length); + } +}; \ No newline at end of file diff --git a/test/mocks/Utils.h b/test/mocks/Utils.h new file mode 100644 index 00000000..9bb6b060 --- /dev/null +++ b/test/mocks/Utils.h @@ -0,0 +1,44 @@ +#pragma once + +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <algorithm> + +#define PUB_KEY_SIZE 32 +#define PRV_KEY_SIZE 64 +#define SIGNATURE_SIZE 64 +#define CIPHER_MAC_SIZE 16 + +namespace mesh { + +class RNG { +public: + virtual ~RNG() = default; + virtual void random(uint8_t* dest, size_t sz) = 0; +}; + +class Utils { +public: + static void sha256(uint8_t* hash, size_t hash_len, const uint8_t*, int) { + std::memset(hash, 0, hash_len); + } + + static int encryptThenMAC(const uint8_t*, uint8_t* dest, const uint8_t* src, int src_len) { + int out_len = src_len + CIPHER_MAC_SIZE; + std::memset(dest, 0xAA, CIPHER_MAC_SIZE); + std::memcpy(dest + CIPHER_MAC_SIZE, src, src_len); + return out_len; + } + + static int MACThenDecrypt(const uint8_t*, uint8_t* dest, const uint8_t* src, int src_len) { + if (src_len < CIPHER_MAC_SIZE) { + return 0; + } + int out_len = src_len - CIPHER_MAC_SIZE; + std::memcpy(dest, src + CIPHER_MAC_SIZE, out_len); + return out_len; + } +}; + +} diff --git a/test/mocks/helpers/SensorManager.h b/test/mocks/helpers/SensorManager.h new file mode 100644 index 00000000..d1a41cb5 --- /dev/null +++ b/test/mocks/helpers/SensorManager.h @@ -0,0 +1,10 @@ +#pragma once + +#include <cstdint> +#include "CayenneLPP.h" + +class SensorManager { +public: + virtual ~SensorManager() = default; + virtual bool querySensors(uint8_t, CayenneLPP&) { return false; } +}; diff --git a/test/test_config_serializer/test_config_serializer.cpp b/test/test_config_serializer/test_config_serializer.cpp new file mode 100644 index 00000000..7a13f487 --- /dev/null +++ b/test/test_config_serializer/test_config_serializer.cpp @@ -0,0 +1,180 @@ +#include <gtest/gtest.h> +#include "helpers/ConfigSerializer.h" + +#define TEST_INT_S "56" +#define TEST_INT 56 +#define TEST_FLOAT_S "-6.123" +#define TEST_FLOAT -6.1230f +#define TEST_DOUBLE_S "12.123456" +#define TEST_DOUBLE 12.123456 + +class MockInputStream : public Stream { + const char* _text; + int pos, len; +public: + MockInputStream(const char* text) : _text(text) { pos = 0; len = strlen(text); } + int available() override { return len - pos; } + int read() override { if (pos < len) { return _text[pos++]; } return -1; } + int peek() override { if (pos < len) { return _text[pos]; } return -1; } +}; + +class MockPrintStream : public Stream { + int len = 0; + uint8_t _buf[1024]; +public: + size_t write(uint8_t b) override { + if (len < sizeof(_buf)) { + _buf[len++] = b; + return 1; + } + return 0; + } + + size_t print(unsigned char b, int r) override { if (b == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(unsigned int v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(unsigned long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(unsigned long long v, int r) override { if (v == TEST_INT) return Print::print(TEST_INT_S); return 0; } + size_t print(double v, int p = 2) override { + if (p == 6) return Print::print(TEST_DOUBLE_S); + if (p == 4) return Print::print(TEST_FLOAT_S); + return 0; + } + + int getLength() const { return len; } + const uint8_t* getBytes() const { return _buf; } +}; + +class TestStruct : public ConfigSerializer { + protected: + void structure() override { + def("age", age); + def("flags", flags); + def("name", name, sizeof(name)); + } + public: + int32_t age; + char name[16]; + uint8_t flags; +}; + +// ── saveSerial: basic ─────────────────────────────────────────────────────── + +TEST(ConfigSerializer, SaveSerial_Basic) { + MockPrintStream s; + TestStruct data; + + data.age = TEST_INT; + data.flags = TEST_INT; + strcpy(data.name, "Scott"); + + bool success = data.saveSerial(s); + EXPECT_TRUE(success); + + auto l = s.getLength(); + const char* expect = "{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"Scott\"}"; + EXPECT_EQ(strlen(expect), l); + + bool match = memcmp(s.getBytes(), expect, l) == 0; + EXPECT_TRUE(match); +} + + +TEST(ConfigSerializer, SaveSerial_EscChars) { + MockPrintStream s; + TestStruct data; + + data.age = TEST_INT; + data.flags = TEST_INT; + strcpy(data.name, "\"Scott\"\n"); + + bool success = data.saveSerial(s); + EXPECT_TRUE(success); + + auto l = s.getLength(); + const char* expect = "{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"\\\"Scott\\\"\\n\"}"; + EXPECT_EQ(strlen(expect), l); + + bool match = memcmp(s.getBytes(), expect, l) == 0; + EXPECT_TRUE(match); +} + +// ── loadSerial: basic ─────────────────────────────────────────────────────── + +TEST(ConfigSerializer, LoadSerial_Basic) { + MockInputStream s("{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"Scott\"}"); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_TRUE(success); + + EXPECT_EQ(TEST_INT, data.age); + EXPECT_EQ(TEST_INT, data.flags); + bool match = strcmp("Scott", data.name) == 0; + EXPECT_TRUE(match); +} + +TEST(ConfigSerializer, LoadSerial_HandleWhitespace) { + MockInputStream s(" { age: " TEST_INT_S " , flags: " TEST_INT_S " , name: \"Scott\" } "); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_TRUE(success); + + EXPECT_EQ(TEST_INT, data.age); + EXPECT_EQ(TEST_INT, data.flags); + bool match = strcmp("Scott", data.name) == 0; + EXPECT_TRUE(match); +} + +TEST(ConfigSerializer, LoadSerial_EscChars) { + MockInputStream s("{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"\\\"Scott\\\"\\n\"}"); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_TRUE(success); + + bool match = strcmp("\"Scott\"\n", data.name) == 0; + EXPECT_TRUE(match); +} + +TEST(ConfigSerializer, LoadSerial_UnmatchedBraces) { + MockInputStream s("{age:" TEST_INT_S ",flags:" TEST_INT_S ",name:\"Scott\""); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_FALSE(success); +} + +TEST(ConfigSerializer, LoadSerial_MissingCommas) { + MockInputStream s("{age:" TEST_INT_S " flags:" TEST_INT_S " name:\"Scott\"}"); + TestStruct data; + + bool success = data.loadSerial(s); + EXPECT_FALSE(success); +} + +TEST(ConfigSerializer, LoadSerial_IgnoreUnknowns) { + MockInputStream s("{age:" TEST_INT_S ",xxx:" TEST_INT_S ",name:\"Scott\"}"); + TestStruct data; + data.flags = 1; + + // should ignore the 'xxx' property + bool success = data.loadSerial(s); + EXPECT_TRUE(success); + + EXPECT_EQ(TEST_INT, data.age); + EXPECT_EQ(1, data.flags); // flags should be unmodified + bool match = strcmp("Scott", data.name) == 0; + EXPECT_TRUE(match); +} + + +// ── main ─────────────────────────────────────────────────────── + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/test_kiss_modem/test_tx_backpressure.cpp b/test/test_kiss_modem/test_tx_backpressure.cpp new file mode 100644 index 00000000..39a5e3b7 --- /dev/null +++ b/test/test_kiss_modem/test_tx_backpressure.cpp @@ -0,0 +1,294 @@ +#include <gtest/gtest.h> + +#include <atomic> +#include <cstddef> +#include <condition_variable> +#include <future> +#include <mutex> +#include <queue> +#include <vector> + +#include "KissModem.h" + +static constexpr int TEST_TX_AVAILABLE_BYTES = 4096; +static constexpr size_t TEST_DEFAULT_MAX_WRITE_CHUNK = SIZE_MAX; +static constexpr size_t TEST_PARTIAL_WRITE_CHUNK = 2; +static constexpr int TEST_PARTIAL_WRITE_FLUSH_LOOPS = 3; +static constexpr uint8_t TEST_SNR = 8; +static constexpr uint8_t TEST_RSSI = 200; + +class BlockingStream : public Stream { +public: + void pushRx(const std::vector<uint8_t>& bytes) { + std::lock_guard<std::mutex> lock(_mutex); + for (uint8_t b : bytes) { + _rx.push(b); + } + } + + void setBlockWrites(bool blocked) { + { + std::lock_guard<std::mutex> lock(_mutex); + _block_writes = blocked; + } + _cv.notify_all(); + } + + bool isWriteBlocked() const { + return _entered_block.load(); + } + + size_t writesCount() const { + std::lock_guard<std::mutex> lock(_mutex); + return _writes.size(); + } + + std::vector<uint8_t> writesSnapshot() const { + std::lock_guard<std::mutex> lock(_mutex); + return _writes; + } + + int availableForWrite() override { + std::lock_guard<std::mutex> lock(_mutex); + return _block_writes ? 0 : TEST_TX_AVAILABLE_BYTES; + } + + void setMaxWriteChunk(size_t chunk) { + std::lock_guard<std::mutex> lock(_mutex); + _max_write_chunk = chunk; + } + + size_t write(const uint8_t* buffer, size_t size) override { + std::unique_lock<std::mutex> lock(_mutex); + while (_block_writes) { + _entered_block.store(true); + _cv.wait(lock); + } + const size_t chunk = (size < _max_write_chunk) ? size : _max_write_chunk; + for (size_t i = 0; i < chunk; i++) { + _writes.push_back(buffer[i]); + } + return chunk; + } + + size_t write(uint8_t b) override { + return write(&b, 1); + } + + int available() override { + std::lock_guard<std::mutex> lock(_mutex); + return static_cast<int>(_rx.size()); + } + + int read() override { + std::lock_guard<std::mutex> lock(_mutex); + if (_rx.empty()) { + return -1; + } + int b = _rx.front(); + _rx.pop(); + return b; + } + +private: + mutable std::mutex _mutex; + std::condition_variable _cv; + std::queue<uint8_t> _rx; + std::vector<uint8_t> _writes; + bool _block_writes = false; + std::atomic<bool> _entered_block = false; + size_t _max_write_chunk = TEST_DEFAULT_MAX_WRITE_CHUNK; +}; + +class FakeRNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + for (size_t i = 0; i < sz; i++) { + dest[i] = 0; + } + } +}; + +class FakeRadio : public mesh::Radio { +public: + bool isReceiving() override { return false; } + uint32_t getEstAirtimeFor(uint16_t) override { return 10; } + bool startSendRaw(const uint8_t*, uint16_t) override { + _start_send_count++; + return _start_send_result; + } + bool isSendComplete() override { return _send_complete; } + void onSendFinished() override { _send_finished_count++; } + int16_t getNoiseFloor() override { return -120; } + + void setStartSendResult(bool result) { _start_send_result = result; } + void setSendComplete(bool complete) { _send_complete = complete; } + int startSendCount() const { return _start_send_count; } + int sendFinishedCount() const { return _send_finished_count; } + +private: + bool _start_send_result = true; + bool _send_complete = true; + int _start_send_count = 0; + int _send_finished_count = 0; +}; + +class FakeBoard : public mesh::MainBoard { +public: + uint16_t getBattMilliVolts() override { return 4200; } + float getMCUTemperature() override { return 24.0f; } + const char* getManufacturerName() override { return "test-board"; } + void reboot() override {} +}; + +class FakeSensors : public SensorManager { +public: + bool querySensors(uint8_t, CayenneLPP&) override { return false; } +}; + +class KissModemFixture : public ::testing::Test { +protected: + BlockingStream serial; + mesh::LocalIdentity identity; + FakeRNG rng; + FakeRadio radio; + FakeBoard board; + FakeSensors sensors; + KissModem modem; + + KissModemFixture() + : modem(serial, identity, rng, radio, board, sensors) { + modem.begin(); + } + + static std::vector<uint8_t> dataFrame(const std::vector<uint8_t>& packet) { + std::vector<uint8_t> frame = {KISS_FEND, KISS_CMD_DATA}; + frame.insert(frame.end(), packet.begin(), packet.end()); + frame.push_back(KISS_FEND); + return frame; + } + + void advanceToTxSending() { + modem.loop(); + modem.loop(); + delay((uint32_t)KISS_DEFAULT_TXDELAY * 10); + modem.loop(); + } +}; + +TEST_F(KissModemFixture, PingResponseShouldNotStallLoopUnderTxBackpressure) { + serial.setBlockWrites(true); + serial.pushRx({KISS_FEND, KISS_CMD_SETHARDWARE, HW_CMD_PING, KISS_FEND}); + + auto future = std::async(std::launch::async, [this]() { + modem.loop(); + }); + + auto status = future.wait_for(std::chrono::milliseconds(100)); + EXPECT_EQ(status, std::future_status::ready) << "KissModem::loop blocked in serial write under TX backpressure"; + EXPECT_FALSE(serial.isWriteBlocked()) << "KissModem entered blocking write path"; + + serial.setBlockWrites(false); + future.wait(); + modem.loop(); + EXPECT_GT(serial.writesCount(), 0U) << "KissModem did not flush queued response after backpressure cleared"; +} + +TEST_F(KissModemFixture, PingResponseKeepsStandardKissFraming) { + serial.pushRx({KISS_FEND, KISS_CMD_SETHARDWARE, HW_CMD_PING, KISS_FEND}); + modem.loop(); + + const std::vector<uint8_t> expected = {KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP(HW_CMD_PING), KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +TEST_F(KissModemFixture, PingResponseKeepsFramingWithPartialBulkWrites) { + serial.setMaxWriteChunk(TEST_PARTIAL_WRITE_CHUNK); + serial.pushRx({KISS_FEND, KISS_CMD_SETHARDWARE, HW_CMD_PING, KISS_FEND}); + for (int i = 0; i < TEST_PARTIAL_WRITE_FLUSH_LOOPS; i++) { + modem.loop(); + } + + const std::vector<uint8_t> expected = {KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP(HW_CMD_PING), KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +TEST_F(KissModemFixture, PacketAndMetaAreQueuedTogetherUnderBackpressure) { + static constexpr uint8_t TEST_PACKET[] = {0x01, 0x02, 0x03}; + + serial.setBlockWrites(true); + modem.onPacketReceived((int8_t)TEST_SNR, (int8_t)TEST_RSSI, TEST_PACKET, sizeof(TEST_PACKET)); + serial.setBlockWrites(false); + modem.loop(); + modem.loop(); + + const std::vector<uint8_t> expected = { + KISS_FEND, KISS_CMD_DATA, TEST_PACKET[0], TEST_PACKET[1], TEST_PACKET[2], KISS_FEND, + KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP_RX_META, TEST_SNR, TEST_RSSI, KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +TEST_F(KissModemFixture, RadioTxCompletionAdvancesWhileHostOutputIsBackedUp) { + serial.pushRx(dataFrame({0x42})); + advanceToTxSending(); + ASSERT_EQ(radio.startSendCount(), 1); + + serial.setBlockWrites(true); + modem.loop(); + EXPECT_EQ(radio.sendFinishedCount(), 1); + EXPECT_TRUE(modem.isTxBusy()); + + serial.setBlockWrites(false); + modem.loop(); + + const std::vector<uint8_t> expected = {KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP_TX_DONE, 0x01, KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); + EXPECT_FALSE(modem.isTxBusy()); +} + +TEST_F(KissModemFixture, QueueFullReportsBusyWithoutDroppingQueuedFrames) { + static constexpr uint8_t TEST_PACKET_ONE[] = {0x11, 0x12}; + static constexpr uint8_t TEST_PACKET_TWO[] = {0x21, 0x22}; + + serial.setBlockWrites(true); + modem.onPacketReceived((int8_t)TEST_SNR, (int8_t)TEST_RSSI, TEST_PACKET_ONE, sizeof(TEST_PACKET_ONE)); + modem.onPacketReceived((int8_t)TEST_SNR, (int8_t)TEST_RSSI, TEST_PACKET_TWO, sizeof(TEST_PACKET_TWO)); + serial.setBlockWrites(false); + modem.loop(); + + const std::vector<uint8_t> expected = { + KISS_FEND, KISS_CMD_DATA, TEST_PACKET_ONE[0], TEST_PACKET_ONE[1], KISS_FEND, + KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP_RX_META, TEST_SNR, TEST_RSSI, KISS_FEND, + KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP_ERROR, HW_ERR_TX_BUSY, KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +TEST_F(KissModemFixture, QueuedEncoderEscapesKissSpecialBytes) { + static constexpr uint8_t TEST_PACKET[] = {KISS_FEND, KISS_FESC, 0x01}; + + modem.onPacketReceived((int8_t)TEST_SNR, (int8_t)TEST_RSSI, TEST_PACKET, sizeof(TEST_PACKET)); + + const std::vector<uint8_t> expected = { + KISS_FEND, KISS_CMD_DATA, KISS_FESC, KISS_TFEND, KISS_FESC, KISS_TFESC, 0x01, KISS_FEND, + KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP_RX_META, TEST_SNR, TEST_RSSI, KISS_FEND}; + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +TEST_F(KissModemFixture, MaxPacketWorstCaseEscapingFitsQueuedFrame) { + std::vector<uint8_t> packet(KISS_MAX_PACKET_SIZE, KISS_FEND); + std::vector<uint8_t> expected = {KISS_FEND, KISS_CMD_DATA}; + for (size_t i = 0; i < packet.size(); i++) { + expected.push_back(KISS_FESC); + expected.push_back(KISS_TFEND); + } + expected.push_back(KISS_FEND); + expected.insert(expected.end(), {KISS_FEND, KISS_CMD_SETHARDWARE, HW_RESP_RX_META, TEST_SNR, TEST_RSSI, KISS_FEND}); + + modem.onPacketReceived((int8_t)TEST_SNR, (int8_t)TEST_RSSI, packet.data(), packet.size()); + EXPECT_EQ(serial.writesSnapshot(), expected); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/test_utf8_helpers/test_utf8_helpers.cpp b/test/test_utf8_helpers/test_utf8_helpers.cpp new file mode 100644 index 00000000..ee953522 --- /dev/null +++ b/test/test_utf8_helpers/test_utf8_helpers.cpp @@ -0,0 +1,38 @@ +#include <gtest/gtest.h> + +#include <helpers/UTF8Helpers.h> + +TEST(UTF8Helpers, KeepsCompleteNameWithinLimit) { + const char* name = "Example RPT 🔋🇵🇱"; + + EXPECT_EQ(24u, mesh::validUtf8PrefixLength(name, 24)); +} + +TEST(UTF8Helpers, StopsBeforeCodePointCrossingLimit) { + const char* name = "Example RPT 🔋🇵🇱"; + + EXPECT_EQ(20u, mesh::validUtf8PrefixLength(name, 23)); +} + +TEST(UTF8Helpers, RejectsMalformedAndTruncatedSequences) { + const char overlong[] = {'A', static_cast<char>(0xC0), static_cast<char>(0xAF), 0}; + const char surrogate[] = {'A', static_cast<char>(0xED), static_cast<char>(0xA0), static_cast<char>(0x80), 0}; + const char out_of_range[] = {'A', static_cast<char>(0xF4), static_cast<char>(0x90), static_cast<char>(0x80), static_cast<char>(0x80), 0}; + const char truncated[] = {'A', static_cast<char>(0xF0), static_cast<char>(0x9F), 0}; + + EXPECT_EQ(1u, mesh::validUtf8PrefixLength(overlong, sizeof(overlong))); + EXPECT_EQ(1u, mesh::validUtf8PrefixLength(surrogate, sizeof(surrogate))); + EXPECT_EQ(1u, mesh::validUtf8PrefixLength(out_of_range, sizeof(out_of_range))); + EXPECT_EQ(1u, mesh::validUtf8PrefixLength(truncated, sizeof(truncated))); +} + +TEST(UTF8Helpers, RejectsUnexpectedContinuationByte) { + const char invalid[] = {'A', static_cast<char>(0x80), 'B', 0}; + + EXPECT_EQ(1u, mesh::validUtf8PrefixLength(invalid, sizeof(invalid))); +} + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/variants/ebyte_eora_s3/platformio.ini b/variants/ebyte_eora_s3/platformio.ini index 15fe761b..1ab3d3fb 100644 --- a/variants/ebyte_eora_s3/platformio.ini +++ b/variants/ebyte_eora_s3/platformio.ini @@ -102,6 +102,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} diff --git a/variants/gat562_30s_mesh_kit/platformio.ini b/variants/gat562_30s_mesh_kit/platformio.ini index 2baac256..89276a49 100644 --- a/variants/gat562_30s_mesh_kit/platformio.ini +++ b/variants/gat562_30s_mesh_kit/platformio.ini @@ -75,6 +75,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter} diff --git a/variants/gat562_mesh_tracker_pro/platformio.ini b/variants/gat562_mesh_tracker_pro/platformio.ini index af153b8f..142cfe4b 100644 --- a/variants/gat562_mesh_tracker_pro/platformio.ini +++ b/variants/gat562_mesh_tracker_pro/platformio.ini @@ -71,6 +71,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${GAT562_Mesh_Tracker_Pro.build_src_filter} diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 0179d965..401a30d2 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -101,6 +101,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 123edd91..02193819 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -73,6 +73,7 @@ build_flags = -D DISPLAY_CLASS=E213Display -D AUTO_OFF_MILLIS=0 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${Heltec_E213_base.build_src_filter} +<helpers/ui/E213Display.cpp> +<helpers/esp32/*.cpp> diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index fb5cd515..17f8ba21 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -87,6 +87,7 @@ build_flags = ${Heltec_mesh_solar.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_rc32/platformio.ini b/variants/heltec_rc32/platformio.ini index d535fb77..354004f0 100644 --- a/variants/heltec_rc32/platformio.ini +++ b/variants/heltec_rc32/platformio.ini @@ -131,9 +131,11 @@ build_flags = -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE build_src_filter = ${Heltec_RC32.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<helpers/ui/buzzer.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -157,6 +159,7 @@ build_flags = build_src_filter = ${Heltec_RC32.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<helpers/ui/buzzer.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -180,6 +183,7 @@ build_flags = build_src_filter = ${Heltec_RC32.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<helpers/ui/buzzer.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -272,6 +276,7 @@ build_flags = -D UI_HAS_ROTARY_INPUT -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE build_src_filter = ${Heltec_RC32_with_display.build_src_filter} +<helpers/ui/buzzer.cpp> +<helpers/esp32/*.cpp> diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index 0a062f00..02e88328 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -152,6 +152,7 @@ build_flags = -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_t1/platformio.ini b/variants/heltec_t1/platformio.ini index d5c3caf1..28ed67ed 100644 --- a/variants/heltec_t1/platformio.ini +++ b/variants/heltec_t1/platformio.ini @@ -23,7 +23,7 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + bodmer/TFT_eSPI @ ^2.4.31 debug_tool = jlink upload_protocol = nrfutil @@ -90,6 +90,7 @@ build_flags = -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t1.build_src_filter} diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 135babb1..48fb4b0f 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -109,6 +109,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + +<helpers/ui/NullDisplayDriver.cpp> +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -126,8 +127,10 @@ build_flags = -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE build_src_filter = ${Heltec_t114.build_src_filter} +<helpers/nrf52/*.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = @@ -236,6 +239,7 @@ build_flags = -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 9b0db55e..9e59d9a7 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -74,6 +74,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${Heltec_T190_base.build_src_filter} +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> diff --git a/variants/heltec_tower_v2/platformio.ini b/variants/heltec_tower_v2/platformio.ini index f029b7d4..207e0a50 100644 --- a/variants/heltec_tower_v2/platformio.ini +++ b/variants/heltec_tower_v2/platformio.ini @@ -69,6 +69,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tower_v2.build_src_filter} + +<helpers/ui/NullDisplayDriver.cpp> +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -86,11 +87,13 @@ build_flags = -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tower_v2.build_src_filter} + +<helpers/ui/NullDisplayDriver.cpp> +<helpers/nrf52/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 07d2e987..75d1cadf 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -59,6 +59,7 @@ build_flags = -D DISPLAY_CLASS=ST7735Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D BLE_PIN_CODE=123456 ; HWT will use display for pin ; -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index b5e12774..63aab297 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -337,6 +337,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7735Display + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_v2.build_src_filter} diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index ba4f8694..78561a14 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -137,6 +137,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index c0bc220a..1c6a7260 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -252,6 +252,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -437,6 +438,7 @@ build_flags = ${Heltec_lora32_v3.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ccdcda66..7b0e6b8e 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -428,6 +428,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_oled.build_src_filter} @@ -592,6 +593,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7789LCDDisplay + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${heltec_v4_tft.build_src_filter} @@ -673,5 +675,10 @@ lib_deps = [env:heltec_v4_kiss_modem] extends = Heltec_lora32_v4 +build_unflags = + -DARDUINO_USB_MODE=0 +build_flags = + ${Heltec_lora32_v4.build_flags} + -DARDUINO_USB_MODE=1 build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<../examples/kiss_modem/> diff --git a/variants/heltec_v4_r8/platformio.ini b/variants/heltec_v4_r8/platformio.ini index 4057d6f1..f523ebf9 100644 --- a/variants/heltec_v4_r8/platformio.ini +++ b/variants/heltec_v4_r8/platformio.ini @@ -138,6 +138,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display + -D ENABLE_USB_INTERFACE build_src_filter = ${heltec_v4_r8_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> @@ -262,6 +263,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7789LCDDisplay + -D ENABLE_USB_INTERFACE build_src_filter = ${heltec_v4_r8_tft.build_src_filter} +<helpers/ui/ST7789LCDDisplay.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 48723d16..55ed62b3 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -73,6 +73,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E213Display -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} +<helpers/ui/E213Display.cpp> +<helpers/esp32/*.cpp> diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini index 51b602e4..1c6f17c2 100644 --- a/variants/ikoka_handheld_nrf/platformio.ini +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -74,6 +74,7 @@ extends = ikoka_handheld_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 + -D ENABLE_USB_INTERFACE build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb] @@ -82,6 +83,7 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 -D DISPLAY_ROTATION=2 + -D ENABLE_USB_INTERFACE build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_repeater] diff --git a/variants/ikoka_nano_nrf/platformio.ini b/variants/ikoka_nano_nrf/platformio.ini index e72f83ce..87c6240e 100644 --- a/variants/ikoka_nano_nrf/platformio.ini +++ b/variants/ikoka_nano_nrf/platformio.ini @@ -31,6 +31,8 @@ debug_tool = jlink upload_protocol = nrfutil lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} +build_src_filter = ${nrf52_base.build_src_filter} + +<helpers/ui/NullDisplayDriver.cpp> [ikoka_nano_nrf_e22_22dbm] extends = ikoka_nano_nrf @@ -42,7 +44,6 @@ build_flags = build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> - +<helpers/ui/NullDisplayDriver.cpp> +<../variants/ikoka_nano_nrf> [ikoka_nano_nrf_e22_30dbm] @@ -56,7 +57,6 @@ build_flags = build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> - +<helpers/ui/NullDisplayDriver.cpp> +<../variants/ikoka_nano_nrf> [ikoka_nano_nrf_e22_33dbm] @@ -70,7 +70,6 @@ build_flags = build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> - +<helpers/ui/NullDisplayDriver.cpp> +<../variants/ikoka_nano_nrf> [ikoka_nano_nrf_companion_radio_ble] @@ -106,6 +105,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -I examples/companion_radio/ui-new -D QSPIFLASH=1 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ikoka_nano_nrf.build_src_filter} diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 06e39e84..c8184fc4 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -111,6 +111,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -I examples/companion_radio/ui-new -D QSPIFLASH=1 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ikoka_stick_nrf.build_src_filter} diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini index 27cf809e..11dc214b 100644 --- a/variants/keepteen_lt1/platformio.ini +++ b/variants/keepteen_lt1/platformio.ini @@ -66,6 +66,7 @@ build_flags = ${KeepteenLT1.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${KeepteenLT1.build_src_filter} diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 577be024..b0d72e35 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -225,6 +225,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index e579e91c..c6497dcb 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -138,6 +138,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 53078e1f..db71d038 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -114,6 +114,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D PERSISTANT_GPS=1 -D ENV_SKIP_GPS_DETECT=1 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 46bf131f..4e28c3eb 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -45,7 +45,7 @@ build_flags = ${LilyGo_TBeam_SX1276.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=160 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=128 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 9ca87c0b..384d689d 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -19,6 +19,7 @@ build_flags = -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D DISPLAY_CLASS=SH1106Display + -D DISPLAY_ADDRESS=0x3D -D LORA_TX_POWER=22 -D P_LORA_TX_LED=6 -D PIN_BOARD_SDA=17 diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini index 745d8ff5..00571db9 100644 --- a/variants/lilygo_tdeck/platformio.ini +++ b/variants/lilygo_tdeck/platformio.ini @@ -71,6 +71,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${LilyGo_TDeck.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index 5df77f95..81da2e48 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -120,6 +120,7 @@ build_flags = -D UI_SENSORS_PAGE=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 -D QSPIFLASH=1 + -D ENABLE_USB_INTERFACE build_src_filter = ${LilyGo_T-Echo.build_src_filter} +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> diff --git a/variants/lilygo_techo_card/platformio.ini b/variants/lilygo_techo_card/platformio.ini index 07bcebfe..4ebf43da 100644 --- a/variants/lilygo_techo_card/platformio.ini +++ b/variants/lilygo_techo_card/platformio.ini @@ -23,7 +23,7 @@ build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<TechoCardBoard.cpp> +<helpers/sensors/EnvironmentSensorManager.cpp> - +<helpers/ui/U8g2Display.h> + +<helpers/ui/U8g2Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../variants/lilygo_techo_card> lib_deps = @@ -110,6 +110,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D ENABLE_USB_INTERFACE build_src_filter = ${LilyGo_T-Echo_Card.build_src_filter} +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-tiny/*.cpp> diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index a9b3d124..9ec73d59 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -174,6 +174,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D UI_RECENT_LIST_SIZE=9 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${nrf52_base.build_src_filter} diff --git a/variants/lilygo_teth_elite/platformio.ini b/variants/lilygo_teth_elite/platformio.ini index 97728f8b..ee1b9879 100644 --- a/variants/lilygo_teth_elite/platformio.ini +++ b/variants/lilygo_teth_elite/platformio.ini @@ -72,6 +72,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index c411e8fa..9bb86dd9 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -89,6 +89,7 @@ build_flags = -I examples/companion_radio/ui-new -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index 94083eb4..190add04 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -97,6 +97,7 @@ build_flags = ${M5Stack_Unit_C6L.build_flags} -D OFFLINE_QUEUE_SIZE=256 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ARDUINO_USB_MODE=1 + -D ENABLE_USB_INTERFACE build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} +<helpers/esp32/*.cpp> -<helpers/esp32/ESPNOWRadio.cpp> diff --git a/variants/mesh_pocket/platformio.ini b/variants/mesh_pocket/platformio.ini index 0d2a74ad..a6f823db 100644 --- a/variants/mesh_pocket/platformio.ini +++ b/variants/mesh_pocket/platformio.ini @@ -96,6 +96,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D AUTO_OFF_MILLIS=0 + -D ENABLE_USB_INTERFACE ; -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index f85be238..4af0f7f7 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -186,6 +186,7 @@ build_flags = -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=128 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -209,8 +210,8 @@ build_flags = -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=128 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 lib_deps = ${Meshadventurer.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -266,6 +267,7 @@ build_flags = -D MAX_CONTACTS=160 -D OFFLINE_QUEUE_SIZE=128 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -289,8 +291,8 @@ build_flags = -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=128 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 lib_deps = ${Meshadventurer.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/meshtiny/platformio.ini b/variants/meshtiny/platformio.ini index c5439c88..c0a3c5f9 100644 --- a/variants/meshtiny/platformio.ini +++ b/variants/meshtiny/platformio.ini @@ -36,6 +36,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Meshtiny.build_src_filter} diff --git a/variants/minewsemi_me25ls01/NullDisplayDriver.h b/variants/minewsemi_me25ls01/NullDisplayDriver.h deleted file mode 100644 index 38bf93f1..00000000 --- a/variants/minewsemi_me25ls01/NullDisplayDriver.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include <helpers/ui/DisplayDriver.h> - -class NullDisplayDriver : public DisplayDriver { -public: - NullDisplayDriver() : DisplayDriver(128, 64) { } - bool begin() { return false; } // not present - - bool isOn() override { return false; } - void turnOn() override { } - void turnOff() override { } - void clear() override { } - void startFrame(Color bkg = DARK) override { } - void setTextSize(int sz) override { } - void setColor(Color c) override { } - void setCursor(int x, int y) override { } - void print(const char* str) override { } - void fillRect(int x, int y, int w, int h) override { } - void drawRect(int x, int y, int w, int h) override { } - void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override { } - uint16_t getTextWidth(const char* str) override { return 0; } - void endFrame() { } -}; diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index 39d4252d..d115a1f1 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -46,8 +46,8 @@ build_flags = ${me25ls01.build_flags} -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE @@ -55,6 +55,7 @@ build_flags = ${me25ls01.build_flags} ;-D PIN_BUZZER=25 ;-D PIN_BUZZER_EN=37 build_src_filter = ${me25ls01.build_src_filter} + +<helpers/ui/NullDisplayDriver.cpp> +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> @@ -66,8 +67,8 @@ build_flags = ${me25ls01.build_flags} -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE @@ -79,6 +80,7 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_repeater> + +<helpers/ui/NullDisplayDriver.cpp> [env:Minewsemi_me25ls01_room_server] extends = me25ls01 @@ -101,6 +103,7 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_room_server> + +<helpers/ui/NullDisplayDriver.cpp> [env:Minewsemi_me25ls01_terminal_chat] extends = me25ls01 @@ -109,8 +112,8 @@ build_flags = ${me25ls01.build_flags} -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE @@ -123,6 +126,7 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_secure_chat/main.cpp> + +<helpers/ui/NullDisplayDriver.cpp> [env:Minewsemi_me25ls01_companion_radio_usb] extends = me25ls01 @@ -134,14 +138,16 @@ build_flags = ${me25ls01.build_flags} -D MAX_GROUP_CHANNELS=40 ;-D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 -D RX_BOOSTED_GAIN=true -D RF_SWITCH_TABLE -D DISPLAY_CLASS=NullDisplayDriver + -D ENABLE_USB_INTERFACE build_src_filter = ${me25ls01.build_src_filter} +<helpers/nrf52/*.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> diff --git a/variants/minewsemi_me25ls01/target.h b/variants/minewsemi_me25ls01/target.h index f8d42863..978e616b 100644 --- a/variants/minewsemi_me25ls01/target.h +++ b/variants/minewsemi_me25ls01/target.h @@ -10,7 +10,7 @@ #include <helpers/sensors/LocationProvider.h> #include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS - #include "NullDisplayDriver.h" + #include <helpers/ui/NullDisplayDriver.h> #endif #ifdef DISPLAY_CLASS diff --git a/variants/muziworks_r1_neo/platformio.ini b/variants/muziworks_r1_neo/platformio.ini index 3dbecf1e..52dc3e38 100644 --- a/variants/muziworks_r1_neo/platformio.ini +++ b/variants/muziworks_r1_neo/platformio.ini @@ -66,6 +66,7 @@ build_flags = -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${R1Neo.build_src_filter} @@ -127,7 +128,7 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_DEBUG=1 build_src_filter = ${R1Neo.build_src_filter} +<../examples/simple_sensor> diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 3cdc29ff..b817b3e9 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -99,6 +99,7 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SH1106Display -D PIN_BUZZER=4 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Nano_G2_Ultra.build_src_filter} diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini index 6a2f3dec..112181df 100644 --- a/variants/nibble_screen_connect/platformio.ini +++ b/variants/nibble_screen_connect/platformio.ini @@ -107,6 +107,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE build_src_filter = ${nibble_screen_connect_base.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/nibble_zero_connect/platformio.ini b/variants/nibble_zero_connect/platformio.ini index 83495cd8..1161743e 100644 --- a/variants/nibble_zero_connect/platformio.ini +++ b/variants/nibble_zero_connect/platformio.ini @@ -104,6 +104,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE build_src_filter = ${nibble_zero_connect_base.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 5415e158..90cb475f 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -119,6 +119,7 @@ build_flags = ${Promicro.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Promicro.build_src_filter} @@ -143,7 +144,7 @@ build_flags = ${Promicro.build_flags} -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_DEBUG=1 build_src_filter = ${Promicro.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/SSD1306Display.cpp> diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini index ab820cf6..c8526317 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rak11310/platformio.ini @@ -85,6 +85,7 @@ extends = rak11310 build_flags = ${rak11310.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak11310.build_src_filter} diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index 6f6df909..200f5d20 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -223,6 +223,7 @@ build_flags = -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 20a8a548..48e71922 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -67,6 +67,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak3401.build_src_filter} diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini index f9667860..c23019c8 100644 --- a/variants/rak3x72/platformio.ini +++ b/variants/rak3x72/platformio.ini @@ -37,6 +37,7 @@ build_flags = ${rak3x72.build_flags} ; -D FORMAT_FS=true -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE build_src_filter = ${rak3x72.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${rak3x72.lib_deps} diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 31b507b4..0dfbf797 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -165,6 +165,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} @@ -190,13 +191,16 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D ETHERNET_ENABLED=1 + -D ETHERNET_USE_RAK13800 + -D ETHERNET_CLASS=RAK13800EthernetInterface ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; -D ETHERNET_DEBUG_LOGGING=1 build_src_filter = ${rak4631.build_src_filter} +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> - +<helpers/nrf52/SerialEthernetInterface.cpp> + +<helpers/ethernet/*.cpp> + +<helpers/ethernet/RAK13800/> lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -254,7 +258,7 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_sensor> diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index e9cddb74..d6140187 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -71,6 +71,7 @@ build_flags = -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak_wismesh_tag.build_src_filter} diff --git a/variants/rpi_picow/platformio.ini b/variants/rpi_picow/platformio.ini index 66c7b9f8..0fe8c436 100644 --- a/variants/rpi_picow/platformio.ini +++ b/variants/rpi_picow/platformio.ini @@ -58,6 +58,7 @@ extends = rpi_picow build_flags = ${rpi_picow.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rpi_picow.build_src_filter} diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 8f3d89a0..15c21a63 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -92,6 +92,7 @@ build_flags = ${SenseCap_Solar.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 28b60543..f8544355 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -114,7 +114,7 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 -D SX126X_RX_BOOSTED_GAIN=1 ; https://wiki.uniteng.com/en/meshtastic/station-g2#impact-of-lora-node-dense-areashigh-noise-environments-on-rf-performance ; -D MESH_DEBUG=1 @@ -159,7 +159,7 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 -D SX126X_RX_BOOSTED_GAIN=1 -D WITH_ESPNOW_BRIDGE=1 ; -D BRIDGE_DEBUG=1 @@ -237,6 +237,7 @@ build_flags = -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} @@ -290,6 +291,11 @@ lib_deps = [env:Station_G2_kiss_modem] extends = Station_G2 +build_unflags = + -DARDUINO_USB_MODE=0 +build_flags = + ${Station_G2.build_flags} + -DARDUINO_USB_MODE=1 build_src_filter = ${Station_G2.build_src_filter} +<../examples/kiss_modem/> diff --git a/variants/station_g3_esp32/platformio.ini b/variants/station_g3_esp32/platformio.ini index b29388ef..e4a66a18 100644 --- a/variants/station_g3_esp32/platformio.ini +++ b/variants/station_g3_esp32/platformio.ini @@ -71,7 +71,7 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G3_ESP32.build_src_filter} +<../examples/simple_repeater> @@ -103,6 +103,7 @@ build_flags = -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Station_G3_ESP32.build_src_filter} diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 43a3d93f..8456dc91 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -82,8 +82,10 @@ build_flags = ${t1000-e.build_flags} -D DISPLAY_CLASS=NullDisplayDriver -D PIN_BUZZER=25 -D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E + -D ENABLE_USB_INTERFACE build_src_filter = ${t1000-e.build_src_filter} +<helpers/ui/buzzer.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${t1000-e.lib_deps} @@ -112,6 +114,7 @@ build_flags = ${t1000-e.build_flags} build_src_filter = ${t1000-e.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/buzzer.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${t1000-e.lib_deps} diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 617f9240..89c48403 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -117,6 +117,7 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 -D PIN_BUZZER=6 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D ENABLE_USB_INTERFACE build_src_filter = ${ThinkNode_M1.build_src_filter} +<helpers/ui/GxEPDDisplay.cpp> +<helpers/ui/buzzer.cpp> diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini index aae83324..583f913c 100644 --- a/variants/thinknode_m2/platformio.ini +++ b/variants/thinknode_m2/platformio.ini @@ -30,7 +30,7 @@ build_flags = ${esp32_base.build_flags} -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D SX126X_RX_BOOSTED_GAIN=1 - -D MESH_DEBUG=1 +; -D MESH_DEBUG=1 build_src_filter = ${esp32_base.build_src_filter} +<helpers/ui/SH1106Display.cpp> +<helpers/ui/MomentaryButton.cpp> @@ -159,6 +159,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${ThinkNode_M2.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/thinknode_m3/ThinkNodeM3Board.h b/variants/thinknode_m3/ThinkNodeM3Board.h index 396d80d1..9e8f4989 100644 --- a/variants/thinknode_m3/ThinkNodeM3Board.h +++ b/variants/thinknode_m3/ThinkNodeM3Board.h @@ -18,12 +18,12 @@ public: void begin(); uint16_t getBattMilliVolts() override; -#if defined(P_LORA_TX_LED) +#ifdef P_LORA_TX_LED void onBeforeTransmit() override { - digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + digitalWrite(P_LORA_TX_LED, LED_STATE_ON); // turn TX LED on } void onAfterTransmit() override { - digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + digitalWrite(P_LORA_TX_LED, !LED_STATE_ON); // turn TX LED off } #endif @@ -44,9 +44,9 @@ public: void powerOff() override { // turn off all leds, sd_power_system_off will not do this for us - #ifdef P_LORA_TX_LED - digitalWrite(P_LORA_TX_LED, LOW); - #endif + digitalWrite(PIN_LED_BLUE, !LED_STATE_ON); + digitalWrite(PIN_LED_GREEN, !LED_STATE_ON); + digitalWrite(PIN_LED_RED, !LED_STATE_ON); // power off board NRF52Board::powerOff(); diff --git a/variants/thinknode_m3/platformio.ini b/variants/thinknode_m3/platformio.ini index 0a3d4eda..ce1e7e59 100644 --- a/variants/thinknode_m3/platformio.ini +++ b/variants/thinknode_m3/platformio.ini @@ -24,10 +24,9 @@ build_flags = ${nrf52_base.build_flags} -D P_LORA_MOSI=46 -D P_LORA_RESET=42 -D P_LORA_TX_LED=PIN_LED_BLUE - -D P_LORA_TX_LED_ON=LOW -D LR11X0_DIO_AS_RF_SWITCH=true -D LR11X0_DIO3_TCXO_VOLTAGE=3.3 - -D MESH_DEBUG=1 +; -D MESH_DEBUG=1 -D ENV_INCLUDE_GPS=1 build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> @@ -84,8 +83,10 @@ build_flags = ${ThinkNode_M3.build_flags} -D DISPLAY_CLASS=NullDisplayDriver -D PIN_BUZZER=23 -D PIN_BUZZER_EN=36 + -D ENABLE_USB_INTERFACE build_src_filter = ${ThinkNode_M3.build_src_filter} +<helpers/ui/buzzer.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${ThinkNode_M3.lib_deps} @@ -105,7 +106,6 @@ build_flags = ${ThinkNode_M3.build_flags} -D BLE_TX_POWER=0 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 - -D GPS_NMEA_DEBUG -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=NullDisplayDriver -D PIN_BUZZER=23 @@ -113,6 +113,7 @@ build_flags = ${ThinkNode_M3.build_flags} build_src_filter = ${ThinkNode_M3.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/buzzer.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${ThinkNode_M3.lib_deps} diff --git a/variants/thinknode_m3/variant.cpp b/variants/thinknode_m3/variant.cpp index dad0f3f5..b47b8354 100644 --- a/variants/thinknode_m3/variant.cpp +++ b/variants/thinknode_m3/variant.cpp @@ -80,16 +80,18 @@ void initVariant() digitalWrite(LED_POWER, HIGH); pinMode(PIN_LED_BLUE, OUTPUT); + digitalWrite(PIN_LED_BLUE, !LED_STATE_ON); pinMode(PIN_LED_GREEN, OUTPUT); + digitalWrite(PIN_LED_GREEN, !LED_STATE_ON); pinMode(PIN_LED_RED, OUTPUT); + digitalWrite(PIN_LED_RED, !LED_STATE_ON); pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(PIN_GPS_POWER, OUTPUT); pinMode(PIN_GPS_EN, OUTPUT); - pinMode(PIN_GPS_RESET, OUTPUT); // Power on gps but in standby - digitalWrite(PIN_GPS_EN, LOW); - digitalWrite(PIN_GPS_POWER, HIGH); + digitalWrite(PIN_GPS_EN, !GPS_EN_ACTIVE); + digitalWrite(PIN_GPS_POWER, GPS_POWER_ACTIVE); } diff --git a/variants/thinknode_m3/variant.h b/variants/thinknode_m3/variant.h index 02ed78a8..78dfab85 100644 --- a/variants/thinknode_m3/variant.h +++ b/variants/thinknode_m3/variant.h @@ -32,7 +32,7 @@ #define EXT_CHRG_DETECT (32) // P1.3 #define EXT_PWR_DETECT (31) // P0.5 -#define PIN_VBAT_READ (5) +#define PIN_VBAT_READ (5) #define AREF_VOLTAGE (2.4f) #define ADC_MULTIPLIER (2.0) //(1.75f) // 2.0 gives more coherent value, 4.2V when charged, needs tweaking @@ -92,18 +92,19 @@ // GPS #define HAS_GPS 1 -#define PIN_GPS_RX (22) +#define PIN_GPS_RX (22) #define PIN_GPS_TX (20) #define PIN_GPS_POWER (14) #define PIN_GPS_EN (21) // STANDBY #define PIN_GPS_RESET (25) // REINIT -#define GPS_RESET_ACTIVE LOW +#define GPS_POWER_ACTIVE HIGH #define GPS_EN_ACTIVE HIGH +#define GPS_RESET (-1) #define GPS_BAUDRATE 9600 //////////////////////////////////////////////////////////////////////////////// // Buzzer #define BUZZER_EN (37) // P1.5 -#define BUZZER_PIN (25) // P0.25 \ No newline at end of file +#define BUZZER_PIN (25) // P0.25 diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 8572d1eb..5e85b649 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -37,7 +37,7 @@ build_flags = ${esp32_base.build_flags} -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D SX126X_RX_BOOSTED_GAIN=1 - -D MESH_DEBUG=1 +; -D MESH_DEBUG=1 -D ENV_INCLUDE_GPS=1 -D PERSISTANT_GPS=1 -D ENV_SKIP_GPS_DETECT=1 @@ -173,6 +173,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${ThinkNode_M5.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini index 187a8819..3606d6a8 100644 --- a/variants/thinknode_m6/platformio.ini +++ b/variants/thinknode_m6/platformio.ini @@ -110,6 +110,7 @@ build_flags = -D QSPIFLASH=1 -D OFFLINE_QUEUE_SIZE=256 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D ENABLE_USB_INTERFACE build_src_filter = ${ThinkNode_M6.build_src_filter} +<helpers/ui/buzzer.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/thinknode_m7/platformio.ini b/variants/thinknode_m7/platformio.ini index af5d8ea0..7d9892e5 100644 --- a/variants/thinknode_m7/platformio.ini +++ b/variants/thinknode_m7/platformio.ini @@ -1,6 +1,8 @@ [ThinkNode_M7] extends = esp32_base board = thinknode_m7 +board_upload.flash_size = 8MB +board_build.partitions = default_8MB.csv build_flags = ${esp32_base.build_flags} -I src/helpers/esp32 -I variants/thinknode_m7 @@ -34,6 +36,23 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 +[ThinkNode_M7_ethernet] +build_flags = + -D ETHERNET_ENABLED + -D ETHERNET_USE_CH390 + -D ETHERNET_CLASS=CH390EthernetInterface + -D ETH_MISO_PIN=14 + -D ETH_MOSI_PIN=48 + -D ETH_SCLK_PIN=47 + -D ETH_CS_PIN=21 + -D ETH_INT_PIN=45 + -D ETHERNET_DEBUG_LOGGING=1 +build_src_filter = + +<helpers/ethernet/*.cpp> + +<helpers/ethernet/ch390/> +lib_deps = + https://github.com/liamcottle/ESP32-CH390.git#47b401f1de546118b03c18b5689dedec45871f2d + [env:ThinkNode_M7_repeater] extends = ThinkNode_M7 build_src_filter = ${ThinkNode_M7.build_src_filter} @@ -72,6 +91,7 @@ lib_deps = extends = ThinkNode_M7 build_flags = ${ThinkNode_M7.build_flags} + ${ThinkNode_M7_ethernet.build_flags} -I examples/companion_radio/ui-orig -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 @@ -81,12 +101,15 @@ build_flags = ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 build_src_filter = ${ThinkNode_M7.build_src_filter} + ${ThinkNode_M7_ethernet.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${ThinkNode_M7.lib_deps} + ${ThinkNode_M7_ethernet.lib_deps} densaugeo/base64 @ ~1.4.0 [env:ThinkNode_M7_companion_radio_usb] @@ -97,6 +120,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${ThinkNode_M7.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> @@ -118,16 +142,37 @@ build_flags = -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' -D OFFLINE_QUEUE_SIZE=256 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 build_src_filter = ${ThinkNode_M7.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${ThinkNode_M7.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:ThinkNode_M7_companion_radio_ethernet] +extends = ThinkNode_M7 +build_flags = + ${ThinkNode_M7.build_flags} + ${ThinkNode_M7_ethernet.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=NullDisplayDriver + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M7.build_src_filter} + ${ThinkNode_M7_ethernet.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${ThinkNode_M7.lib_deps} + ${ThinkNode_M7_ethernet.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:ThinkNode_M7_kiss_modem] extends = ThinkNode_M7 build_src_filter = ${ThinkNode_M7.build_src_filter} diff --git a/variants/thinknode_m9/platformio.ini b/variants/thinknode_m9/platformio.ini index a7171726..09b1391d 100755 --- a/variants/thinknode_m9/platformio.ini +++ b/variants/thinknode_m9/platformio.ini @@ -120,6 +120,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE build_src_filter = ${ThinkNode_M9.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/buzzer.cpp> diff --git a/variants/tiny_relay/platformio.ini b/variants/tiny_relay/platformio.ini index 82cb251f..13c2ec1c 100644 --- a/variants/tiny_relay/platformio.ini +++ b/variants/tiny_relay/platformio.ini @@ -44,6 +44,7 @@ build_flags = ${Tiny_Relay.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D MAX_LORA_TX_POWER=22 + -D ENABLE_USB_INTERFACE build_src_filter = ${Tiny_Relay.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${Tiny_Relay.lib_deps} diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 7dfe1401..db1ec603 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -84,6 +84,7 @@ extends = waveshare_rp2040_lora build_flags = ${waveshare_rp2040_lora.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} diff --git a/variants/wio-e5-dev/platformio.ini b/variants/wio-e5-dev/platformio.ini index 22bdc3c8..82b3781e 100644 --- a/variants/wio-e5-dev/platformio.ini +++ b/variants/wio-e5-dev/platformio.ini @@ -46,6 +46,7 @@ build_flags = ${lora_e5.build_flags} -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE build_src_filter = ${lora_e5.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${lora_e5.lib_deps} diff --git a/variants/wio-e5-mini/NullDisplayDriver.h b/variants/wio-e5-mini/NullDisplayDriver.h deleted file mode 100644 index 2a9670bd..00000000 --- a/variants/wio-e5-mini/NullDisplayDriver.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include <helpers/ui/DisplayDriver.h> - -class NullDisplayDriver : public DisplayDriver { -public: - NullDisplayDriver() : DisplayDriver(128, 64) { } - bool begin() { return false; } // not present - - bool isOn() override { return false; } - void turnOn() override { } - void turnOff() override { } - void clear() override { } - void startFrame(Color bkg = DARK) override { } - void setTextSize(int sz) override { } - void setColor(Color c) override { } - void setCursor(int x, int y) override { } - void print(const char* str) override { } - void fillRect(int x, int y, int w, int h) override { } - void drawRect(int x, int y, int w, int h) override { } - void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override { } - uint16_t getTextWidth(const char* str) override { return 0; } - void endFrame() { } -}; diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 82f01331..021c7db2 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -44,7 +44,9 @@ build_flags = ${lora_e5_mini.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=NullDisplayDriver + -D ENABLE_USB_INTERFACE build_src_filter = ${lora_e5_mini.build_src_filter} + +<helpers/ui/NullDisplayDriver.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${lora_e5_mini.lib_deps} diff --git a/variants/wio-e5-mini/target.h b/variants/wio-e5-mini/target.h index 4807e0f7..f4d6a088 100644 --- a/variants/wio-e5-mini/target.h +++ b/variants/wio-e5-mini/target.h @@ -8,7 +8,7 @@ #include <helpers/ArduinoHelpers.h> #include <helpers/SensorManager.h> #ifdef DISPLAY_CLASS - #include "NullDisplayDriver.h" + #include <helpers/ui/NullDisplayDriver.h> #endif #include <BME280I2C.h> diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 7bb175bb..fc958ea2 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -68,6 +68,7 @@ build_flags = ${WioTrackerL1.build_flags} -D OFFLINE_QUEUE_SIZE=256 -D PIN_BUZZER=12 -D QSPIFLASH=1 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index c0e8458d..587c5c27 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -100,6 +100,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index b2638a44..e71e7f65 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -47,7 +47,7 @@ public: #ifdef PIN_USER_BTN // configure button press to wake up when in powered off state - nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #endif NRF52Board::powerOff(); diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index a0854336..1076bd8d 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -53,7 +53,7 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_DEBUG=1 -D QSPIFLASH=1 build_src_filter = ${Xiao_nrf52.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> @@ -74,6 +74,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D QSPIFLASH=1 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index ab6fe279..def1dbbe 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -4,6 +4,7 @@ #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); #endif XiaoNrf52Board board; diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index bb3d2a81..20093904 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -11,7 +11,9 @@ #ifdef DISPLAY_CLASS #include <helpers/ui/NullDisplayDriver.h> + #include <helpers/ui/MomentaryButton.h> extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif extern XiaoNrf52Board board; diff --git a/variants/xiao_rp2040/platformio.ini b/variants/xiao_rp2040/platformio.ini index ca00e38b..cbc50a51 100644 --- a/variants/xiao_rp2040/platformio.ini +++ b/variants/xiao_rp2040/platformio.ini @@ -38,8 +38,8 @@ build_flags = ${Xiao_rp2040.build_flags} -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 build_src_filter = ${Xiao_rp2040.build_src_filter} +<../examples/simple_repeater> @@ -61,6 +61,7 @@ extends = Xiao_rp2040 build_flags = ${Xiao_rp2040.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=8 + -D ENABLE_USB_INTERFACE ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Xiao_rp2040.build_src_filter} diff --git a/variants/xiao_s3/platformio.ini b/variants/xiao_s3/platformio.ini index 22464e7d..1632d112 100644 --- a/variants/xiao_s3/platformio.ini +++ b/variants/xiao_s3/platformio.ini @@ -123,6 +123,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 09fab2fb..afef16be 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -208,6 +208,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D OFFLINE_QUEUE_SIZE=256 + -D ENABLE_USB_INTERFACE ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_S3_WIO.build_src_filter}