Compare commits

...
Author SHA1 Message Date
Jonathan Bennett 11a14bdda5 latest test changes 2026-05-31 17:02:08 -04:00
Jonathan Bennett f25e3e893d talking stick 2026-05-29 22:18:32 +01:00
Jonathan Bennett 134ec6dc54 Add real Lock handling on Portduino 2026-05-29 22:17:10 +01:00
Jonathan BennettandGitHub a04b7c4a87 Merge branch 'develop' into vibe-coded-dmshell 2026-05-29 13:49:20 -05:00
60303968bb Add Heltec mesh node t1 (#10416)
* add heltec-mesh-node-t1

* fixed low power

* Update the sensor enumeration values.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Fix memory leak in ICM42607PSensor

* fix  ST7735_MISO  error

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-29 12:47:09 -05:00
Catalin PatuleaandGitHub d3c7f05baa Simplify tracking of BLE connection handle & improve thread safety. (#10390)
- Redefine isConnected in terms of nimbleBluetoothConnHandle. isConnected was not safe because BLEServer::getConnectedCount is not thread-safe (https://github.com/espressif/arduino-esp32/issues/12538) while isConnected is called from various threads. Now we can avoid checking bleServer every time before calling isConnected.
- getRssi: don't try to "populate nimbleBluetoothConnHandle", that requires calling BLEServer::getPeerDevices which is not thread-safe (same issue as above).
2026-05-29 06:48:43 -05:00
Ben Meadors 1971e5ab13 Remove now unused payload variant settings in allocAtakPli function 2026-05-29 06:15:11 -05:00
Ben Meadors e2fda6598c Update protobufs 2026-05-28 21:01:30 -05:00
982440d21d Noise floor (#9347)
* add noise floor

* Sliding window noise floor

* Add getCurrentRSSI() to SimRadio for noise floor support

* Remove sendLocalStatsToPhone call from runOnce

* Change noise floor to int32_t type

* Use int32_t for RSSI sample storage in noise floor

* Remove float cast from noise floor assignment

* Fix Copilot review issues: fix noise floor logic, types, and null pointer

- Use robust busyTx/busyRx checks instead of simple isReceiving check
- Initialize noiseFloorSamples to NOISE_FLOOR_MIN instead of 0
- Move noise_floor assignment inside null check to prevent potential crash
- Change getNoiseFloor() and getAverageNoiseFloor() to return int32_t
- Fix RSSI validation to check for positive values (rssi > 0)
- Fix format specifier from %.1f to %d for int32_t
- Update comments to accurately reflect the sampling logic

* Fix RSSI condition to include zero value

* Change noise floor initialization to zero

* Disable noise floor for LR11x0 chips: getRSSI(bool) unsupported

* Remove updateNoiseFloor call from onNotify to avoid radio queue overflow

Per PR review feedback, calling updateNoiseFloor() in onNotify() for every
ISR event (ISR_TX, ISR_RX, TRANSMIT_DELAY_COMPLETED) can cause the LoRa
radio queue to get full. The noise floor sampling still happens in
startReceive() and after transmitting.

* fix lr11x0 current rssi

* Address noise floor review comments

* Address Copilot SimRadio noise floor comments

* Fix RadioLibInterface formatting

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-05-28 19:39:41 -05:00
Ben Meadors f9fea562aa Add TAKTALK message and room data structures to support voice/text chat 2026-05-27 09:05:38 -05:00
Ben Meadors c366296ab4 Update LoRaConfig region codes and add new amateur radio bands 2026-05-26 18:56:16 -05:00
Ben MeadorsGitHubCopilotcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
a076d97eb6 Implement ATAK Plugin V2 and drop unishox2 compression support (#10105)
* Implement ATAK Plugin V2 and drop unishox2 compression support

* Fix course calculation and improve error handling in allocAtakPli

* Update src/modules/AtakPluginModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix buffer overflow in allocAtakPli by ensuring null termination for callsigns

* Add missing include for concurrency::OSThread in AtakPluginModule.h

* Update src/modules/AtakPluginModule.h

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Refactor allocAtakPli to use configurable team and role; improve position source mapping

* Update src/modules/PositionModule.cpp

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Remove OSThread inheritance from AtakPluginModule - pure passthrough needs no periodic scheduling

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/bdc82eb6-77c4-4711-839c-04bcbb1aa9cd

Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>

* Apply clang-format (trunk fmt)

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-26 15:16:47 -05:00
Clive BlackledgeandGitHub 32dcd90abf Preserve forwarded position payload precision (#10554) 2026-05-26 06:33:11 -05:00
5b7a5b2c22 feat: add Raspberry Pi Pico 2 + W5500 + E22-900M30S variant (#10135)
* feat: add Raspberry Pi Pico 2 + W5500 + E22-900M30S variant

Adds community variant for Raspberry Pi Pico 2 (RP2350, 4 MB flash)
with external WIZnet W5500 Ethernet module and EBYTE E22-900M30S LoRa
module (SX1262, 30 dBm PA, 868/915 MHz).

Key details:
- LoRa on SPI1: GP10/11/12/13 (SCK/MOSI/MISO/CS), RST=GP15,
  DIO1=GP14, BUSY=GP2, RXEN=GP3 (held HIGH via SX126X_ANT_SW)
- W5500 on SPI0: GP16/17/18/19/20 (MISO/CS/SCK/MOSI/RST)
- SX126X_DIO2_AS_RF_SWITCH: DIO2→TXEN bridge on module handles PA
- SX126X_DIO3_TCXO_VOLTAGE 1.8: TCXO support via EBYTE_E22 flags
- DHCP timeout reduced to 10 s to avoid blocking LoRa startup
- GPS on UART1/Serial2: GP8 TX, GP9 RX
- Reuses WIZNET_5500_EVB_PICO2 code paths for Ethernet init

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* pico2_w5500_e22: rename define and address review feedback

Rename WIZNET_5500_EVB_PICO2 to PICO2_W5500_E22 so the variant-specific
define matches the variant directory name and isn't confused with an
on-board EVB SKU.

Review fixes from PR #10135:
- Gate the 10 s Ethernet DHCP timeout behind PICO2_W5500_E22 so other
  Ethernet builds keep the default 60 s behavior; apply the same timeout
  to reconnectETH() for consistency.
- Drop the unused -D EBYTE_E22 flag; EBYTE_E22_900M30S already selects
  TX_GAIN_LORA / SX126X_MAX_POWER in src/configuration.h.
- Rewrite "on-board W5500" comments to describe the external module.
- Correct README TX_GAIN_LORA value (7, not 10) and drop the EBYTE_E22
  row.

* fix(pico2_w5500_e22): drop DEBUG_RP2040_PORT=Serial

The arduino-pico framework hooks _write() when DEBUG_RP2040_PORT=Serial
is set and dumps raw debug bytes onto USB CDC, corrupting any binary
protobuf stream sent through StreamAPI (e.g. `meshtastic --port COMx`).

The variant excludes BT and WiFi, so the primary client transport is
Ethernet TCP via ethServerAPI — unaffected — but users who configure
the node over USB serial would see protobuf decode failures from
debug-byte interleaving. Removing the flag restores clean USB CDC.

Debug output can still be enabled per-build by adding -D DEBUG_RP2040_PORT=Serial1
to redirect to UART0 instead of USB CDC.

* style(pico2_w5500_e22): apply trunk fmt — fixes Trunk Check

- variant.h: clang-format 16.0.3 (drop manual #define alignment)
- README.md: prettier + add `text` language to fenced code blocks (markdownlint MD040)
- wiring.svg: svgo optimization

Resolves the Trunk Check Runner failure on this PR (3 unformatted
files + 8 markdownlint issues). No functional changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(pico2_w5500_e22): address review — move to rp2350/diy, generic guards

Maintainer feedback from NomDeTom on PR #10135:

- Move the variant from variants/rp2350/ to variants/rp2350/diy/ to
  distinguish DIY from prebuilt boards (matches the variants/*/diy/
  pattern; still discovered via the existing variants/*/diy/*/platformio.ini
  glob in the root platformio.ini).
- Replace the board-name macro PICO2_W5500_E22 in shared code with a
  generic capability macro USE_ARDUINO_ETHERNET, defined from variant.h.
  DebugConfiguration.h / ethServerAPI.h / ethClient.cpp no longer
  reference a board name.
- Drop the architecture.h hook entirely: variant.h now defines PRIVATE_HW,
  which the existing `#elif defined(PRIVATE_HW)` branch already handles.

No functional change. Build verified: pico2_w5500_e22 SUCCESS
(RAM 19.2%, Flash 28.1%).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(pico2_w5500_e22): drop unoptimized wiring.svg to fix trunk fmt check

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-25 14:05:46 -05:00
Alexander BalyaandGitHub 5bce26d9b7 Fix SHT2x detection for INA219 addresses (#10482) 2026-05-24 21:03:24 -05:00
ManuelandGitHub ef734b73c7 fix: mbed TLS crash in Arduino 3.x (pioarduino) (#10535)
* fix mbed TLS crash

* adapt SSL_MAX_CONTENT_LEN size to framework-libs

* found two more
2026-05-22 20:14:42 -05:00
Ben Meadors c7748a1602 Fix update neighbor_info before checking update_interval in handleSetModuleConfig 2026-05-22 14:53:52 -05:00
Jaime RoldanandGitHub 91f930d5c0 fix(telemetry): stop emitting -0.001V sentinel when battery unavailable (#7958) (#10217)
* fix(telemetry): stop emitting -0.001V sentinel when battery unavailable (#7958)

* address review: use int32_t for batteryMv to avoid uint16_t signed wrap
2026-05-22 06:58:21 -05:00
κρμγandGitHub f2c5cb0a05 fix: first set pinMode, then write to pin (#10520) 2026-05-21 13:30:27 -05:00
e10e13226d nrf54l15: fix SHT4x libdep -- arduino-sht, not Adafruit_SHT4X (#10515)
SHTXXSensor (the SHT4x driver) includes <SHTSensor.h>, gated by
__has_include(<SHTSensor.h>). That header ships in Sensirion/arduino-sht.
Adafruit_SHT4X ships Adafruit_SHT4X.h and has no consumer anywhere in
src/, so the SHT40 driver was silently excluded from the build -- the
nRF54L15 variant could not read an SHT4x sensor as committed in #10193.

Replace the dead Adafruit_SHT4X libdep with arduino-sht v1.2.6.
Validated on nRF54L15-DK: SHT40-AD1B @0x44, 24.3h soak, 0 reboots,
temperature/humidity telemetry stable end-to-end.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-21 11:00:10 -05:00
5e69bc6c3f Enable Narrow and Lite regions for EU (#10120)
* Enable Lite and Narrow regions and introduce getEffectiveDutyCycle for Lite profiles

* Add TrafficType enum and extend getConfiguredOrDefaultMsScaled to manage based on regionProfile settings

* Refactor telemetry modules to include TrafficType in getConfiguredOrDefaultMsScaled calls

* Update submodule protobufs to latest commit

* Add support for new region presets and modem presets in menu options

* Add new LoRa region codes and modem presets for EU bands

* boof

* Add modem presets for LITE and NARROW configurations

* Update subproject commit reference in protobufs

* Update protobufs

* Refactor modem preset definitions to use macro for consistency and clarity

* Refactor modem preset cases to use PRESET macro for consistency

* fix: update LoRa region code for EU 868 narrowband configuration

Co-authored-by: Copilot <copilot@github.com>

* Fix test suite failure

Co-authored-by: Copilot <copilot@github.com>

* Add override slot override - for when one override isn't enough.

Co-authored-by: Copilot <copilot@github.com>

* address copilot comments

---------

Co-authored-by: Copilot <copilot@github.com>
2026-05-21 10:20:09 -05:00
f3cb2bff78 Refactor keyboard cell height logic for consistency (#10501)
Adjust keyboard cell height calculation for better layout consistency across different screen sizes.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-21 06:12:34 -05:00
AustinandGitHub 894c5556cf Actions: Fix tagging upon release. (#10521)
Current release tags are actually based upon the latest state of `develop` currently...
Specify target_commitish to always use the commit that triggered the build
2026-05-21 06:09:57 -05:00
Ben MeadorsandGitHub 2f92eb8499 Refactor position precision handling to honor explicit channel settings and prevent location leaks (#10513) 2026-05-20 10:18:46 -05:00
Ben Meadors 8d08077412 Add Ethernet configuration to platformio.ini for ThinkNode variants 2026-05-20 07:12:43 -05:00
Ben Meadors 00ec69201d Develop protos should be on develop 2026-05-19 06:57:05 -05:00
Ben Meadors 4304480ca3 Merge remote-tracking branch 'origin/master' into develop 2026-05-19 05:37:01 -05:00
82aefd1af1 Upgrade trunk (#10503)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-19 05:23:01 -05:00
0f9eb86830 Enabled SX_LNA_EN by default (#10469)
* Enabled SX_LNA_EN by default
* Update I2C configuration for IO direction and pull settings

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-05-19 04:56:01 -05:00
Thomas Göttgens e2aa44ec54 T-Echo-Card support (#10267)
# Conflicts:
#	src/graphics/draw/UIRenderer.cpp
2026-05-19 09:53:45 +02:00
Thomas GöttgensandGitHub 1747e2d8e5 T-Echo-Card support (#10267) 2026-05-19 09:31:04 +02:00
622aa046f1 Enabled SX_LNA_EN by default (#10469)
* Enabled SX_LNA_EN by default
* Update I2C configuration for IO direction and pull settings

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
2026-05-19 08:31:42 +02:00
Thomas GöttgensandGitHub 0832330327 Fix antenna switch initialization logic 2026-05-19 08:00:02 +02:00
Thomas GöttgensandGitHub 98e0604edf Fix antenna switch initialization logic once more 2026-05-19 07:58:24 +02:00
23ead5f2f1 Update protobufs (#10500)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2026-05-19 07:47:06 +02:00
af3739fd63 Update protobufs (#10499)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2026-05-19 07:22:15 +02:00
0148a89ddb Upgrade trunk (#10493)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-18 20:49:28 -05:00
Thomas Göttgens 6199faacf1 cherry pick backport fix for cardputer 2026-05-18 23:24:32 +02:00
Thomas GöttgensandGitHub 3261c04afb Fix Antenna Switch on Cardputer (#10491) 2026-05-18 11:18:40 +02:00
AustinGitHubmverch67ManuelCatalin Patuleacopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>vidplace7CopilotthebenternBen Meadors
a541957480 ESP32: Migrate to Arduino 3.x (pioarduino) (#9122)
* Migrate esp32 families to pioarduino platform

* ESP32c6 align text.handler_execute same as C3

* Use pioarduino `develop`

The latest fixes and the latest bugs!

* preliminary esp32p4.ini

* pioarduino: Update LovyanGFX

Includes Manuel's recent commit

* pioarduino 3.3.6

* pioarduino 3.3.6 *release*

chasing the release

* pioarduino: Fix OG ESP32 duplicate libs

* pioarduino: T-Beam 1W CDC mode

* pioarduino: disable network provisioning (wifiprov)

* pioarduino: use legacy esptoolpy naming (forward-compatible)

* Update lovyangfx from `develop` commit to 1.2.19

* fix esp32p4.ini

* check for esp32 w/ wifi

* esp32-p4 specific adaptations

* Switch to meshtastic/esp32_https_server fork (idf5 branch)

* don't ignore esp_lcd

* config for MUI

* fix/workaround SDMMC

* revert a6f6175, update to 3.3.8

* enable esp_hosted for esp32-p4 (experimental)

* Pioarduino 55.03.38-1

* NimBLE-Arduino -> Arduino "BLE" (3.3.x) migration (#10164)

* NimBLE-Arduino -> Arduino "BLE" (3.3.x) migration

* More NimBLE

* Fix Device Name in ATT Read Request (0x2A00).

Device Name is exposed in two places:

- Advertisement data: this is set properly in startAdvertising.
- GATT attribute Device Name (0x2A00). This one is handled internally in NimBLE
  and comes from ble_svc_gap_device_name_set. This is set initially, but then
  BLEDevice::createServer calls ble_svc_gap_init which resets the device name.
  This causes the device to apparently "change name after pairing":

< ACL Data TX:... flags 0x00 dlen 7  #113 [hci0] 14.241149
      ATT: Read Request (0x0a) len 2
        Handle: 0x0003 Type: Device Name (0x2a00)
> ACL Data RX: Handle 2048 flags 0x02 dlen 11             #115 [hci0] 14.269050
      ATT: Read Response (0x0b) len 6
        Value[6]: 6e696d626c65   # "nimble"

Workaround this by setting the device name once again after
BLEDevice::createServer.

* Temporarily lower CORE_DEBUG_LEVEL to INFO to avoid triggering an apparent ESP-IDF Bluetooth bug when re-connecting to Pixel 8 Android devices.

Initial pairing works, but after ESP32 is rebooted, phone fails to reconnect. Meshtastic app shows it as disconnecting immediately. LightBlue shows a more detailed error "Peripheral Connection - Warning: onConnectionStatusChange: status 61" (0x3D - MIC Failure).

Bug report to Espresssif: https://github.com/espressif/esp-idf/issues/18126#issuecomment-4286197744

* Temporarily disable ble_gap_set_data_len, causes crash with Pixel 8 Android reconnect.

Crash looks like this:
  [ 11966][E][BLEAdvertising.cpp:341] setScanResponseData(): ble_gap_adv_rsp_set_data: 22
  [ 11975][E][BLEAdvertising.cpp:1554] start(): Host reset, wait for sync.
  ERROR | ??:??:?? 11 BLE failed to start advertising
  Guru Meditation Error: Core  0 panic'ed (LoadProhibited). Exception was unhandled.

  Core  0 register dump:
  PC      : 0x420e6190  PS      : 0x00060730  A0      : 0x820e158b  A1      : 0x3fce50c0
  A2      : 0x00000000  A3      : 0x3fcb8600  A4      : 0x3fcb85cc  A5      : 0x00000000
  A6      : 0x00000000  A7      : 0x00000c03  A8      : 0x00000000  A9      : 0x3fce50b0
  A10     : 0x0000000e  A11     : 0x00000000  A12     : 0x00000010  A13     : 0x3fce50e0
  A14     : 0x00000c03  A15     : 0x00000001  SAR     : 0x0000001e  EXCCAUSE: 0x0000001c
  EXCVADDR: 0x00000000  LBEG    : 0x400570e8  LEND    : 0x400570f3  LCOUNT  : 0x00000000

  Backtrace: 0x420e618d:0x3fce50c0 0x420e1588:0x3fce5110 0x420dfe87:0x3fce5200 0x420dfefb:0x3fce5220 0x420dff3f:0x3fce5240 0x4219602b:0x3fce5260 0x4037b0e5:0x3fce5280 0x4201edf3:0x3fce52a0

Connection seems fast enough even without this. We'll investigate the
reason for the crash and re-enable once it's safe.

---------

Co-authored-by: Catalin Patulea <cronos586@gmail.com>

* Add extension from pioarduino nag
"Jason2866.esp-decoder"

* Cleanup after merge

* ESP32: Disable classic bluetooth

* Cleanup: Fix ADC channels on new variants

* InkHUD: Fix type casting for message size in saveToFlash method

inkhud compiles again!

* update p4 esp_hosted for BT

* I thought I fixed this

* fix linker error using response file (p4 only)

* fix infinite loop

* Fix Power.cpp check warning

Local variable 'config' shadows outer variable [shadowVariable]

* Build ESP32 original with NimBLE ('custom_sdkconfig' approach). (#10235)

* Re-enable littlefs json manifest

This works locally again :)
Not sure what changed

* Re-add tool-mklittlefs

* sensecap indicator fixes after upgrade arduino-esp & lovyanGFX libs

* hackaday fix

* robot tbeam cache error fix

Co-authored-by: Copilot <copilot@github.com>

* trunk fmt

* ignore trunk

* BLEDevice::deinit() added

Co-authored-by: Copilot <copilot@github.com>

* platformio-custom: Modify mtjson target dependency to prevent fake-success. (#10291)

Co-authored-by: Copilot <copilot@github.com>

* Fix ESP32-C6 linker errors.

Align .text.handler_execute section to 4 bytes and update watchdog timer core mask configuration

Co-authored-by: Copilot <copilot@github.com>

* tlora-c6: Disable Screen

MESHTASTIC_EXCLUDE_SCREEN=1 on tlora-c6.

It doesn't have a screen, and this gets it compiling again (saving flash).

* Use mverch's iram_memset hack for all OG-ESP32

* Refactor watchdog timer initialization and handling

* use adc_channel_t in variant.h

* Fix variant headers

* More idiomatic default ethernet that doesn't break the build

* Elecrows: Delete problematic variant.cpp

Not needed after USE_ETHERNET_DEFAULT

---------

Co-authored-by: mverch67 <manuel.verch@gmx.de>
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
Co-authored-by: Catalin Patulea <cronos586@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-17 18:53:32 -05:00
fc5556b8e6 Clamp direct position packets to channel precision (fixes #8640) (#10383)
* Fix position precision for direct sends

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Clarify zero position precision logging

* Use const channel reference for position precision

* Use C linkage for position precision test entrypoints

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-16 14:08:46 -05:00
502c5af524 Upgrade trunk (#10481)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-15 20:03:50 -05:00
renovate[bot]GitHubrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
05707079bd Update libch341-spi-userspace digest to 2e5ff75 (#10485)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-15 19:30:04 -05:00
Ben MeadorsandGitHub 1c05633fcd Add more support for small fonts in screen resolution determination (#10480) 2026-05-15 05:59:15 -05:00
fce419b335 Upgrade trunk (#10476)
Co-authored-by: vidplace7 <1779290+vidplace7@users.noreply.github.com>
2026-05-14 06:43:06 -05:00
Jonathan Bennett 8ccb2c918f dmshell reliability tweaks 2026-04-30 21:28:28 -05:00
Jonathan BennettandGitHub 6b49b7228a Merge branch 'develop' into vibe-coded-dmshell 2026-04-30 10:56:15 -05:00
Jonathan BennettandGitHub c194666885 Merge branch 'develop' into vibe-coded-dmshell 2026-04-28 22:03:03 -05:00
Jonathan BennettandGitHub d7cb5d7885 Merge branch 'develop' into vibe-coded-dmshell 2026-04-17 12:17:55 -05:00
Jonathan BennettandGitHub ffd144da83 Merge branch 'develop' into vibe-coded-dmshell 2026-04-16 22:48:26 -05:00
Jonathan BennettandGitHub 1e8c9e7071 Merge branch 'develop' into vibe-coded-dmshell 2026-04-16 21:29:43 -05:00
3c83e01d0e Update protobufs (#10188)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2026-04-16 21:28:53 -05:00
Jonathan Bennett dc3947117e Make new protobuf value consistent 2026-04-14 18:47:53 -05:00
Jonathan Bennett 87d0850f95 Refactor and Simplify 2026-04-14 17:16:36 -05:00
Jonathan Bennett 5831952636 simplify pt 1 2026-04-14 12:57:29 -05:00
Jonathan Bennett a6d61413c3 Add PortduinoSetOptions to overwrite the realhardware bool 2026-04-13 22:20:38 -05:00
Jonathan Bennett e393a5c410 Make consoleInit() Reentrant, and initialize it earlier on native 2026-04-13 20:56:44 -05:00
Jonathan Bennett 8f2ecbdb4d No Child Left Behind 2026-04-13 20:32:38 -05:00
Jonathan Bennett 6c28d11cee Minor cleanups 2026-04-13 20:23:31 -05:00
Jonathan Bennett 69f1b502cc Harden against possible memory overflows 2026-04-13 20:05:50 -05:00
Jonathan Bennett 3a498fbbe4 Make the DMShell tests compile 2026-04-13 19:47:33 -05:00
Jonathan Bennett 8f7dea0580 Use RemoteShell in protobufs 2026-04-13 19:25:09 -05:00
Jonathan Bennett 322f0262a8 Trunk 2026-04-13 12:11:31 -05:00
Jonathan BennettandGitHub 00762393cf Merge branch 'develop' into vibe-coded-dmshell 2026-04-13 12:07:56 -05:00
Jonathan BennettandGitHub 866c89f801 Merge branch 'develop' into vibe-coded-dmshell 2026-04-12 22:41:58 -05:00
Jonathan Bennett 8c248927c8 Remove some dead code and LLM overcomplication 2026-04-10 16:01:35 -05:00
Ben MeadorsandGitHub 188d895eb4 Merge branch 'develop' into vibe-coded-dmshell 2026-04-10 07:21:42 -05:00
Jonathan BennettandGitHub 6f476f3475 Merge branch 'develop' into vibe-coded-dmshell 2026-04-09 21:46:10 -05:00
Jonathan Bennett 50e1fe88e8 dmshell client serial support and tweaks 2026-04-09 18:31:29 -05:00
Jonathan Bennett f9bedd8adc DMShell heartbeat 2026-04-08 23:19:02 -05:00
Jonathan Bennett 5a619c9031 Attempt at better responsiveness 2026-04-08 21:15:23 -05:00
Jonathan Bennett 8d3f9222ff Don't firehose missing packets 2026-04-08 17:52:47 -05:00
Jonathan Bennett f5335f22ea troubleshoot dropped packets 2026-04-08 16:42:46 -05:00
Jonathan Bennett 4a3f449555 Try to re-request missed sequences 2026-04-08 15:16:33 -05:00
Jonathan Bennett 608713470b Interactive mode 2026-04-08 14:55:37 -05:00
Jonathan Bennett f475be19c6 Dumb fixes 2026-04-08 14:27:15 -05:00
Jonathan Bennett 27cc76d5ed Very WIP dmshell 2026-04-08 13:18:21 -05:00
231 changed files with 6196 additions and 3012 deletions
+1
View File
@@ -333,6 +333,7 @@ jobs:
prerelease: true
name: Meshtastic Firmware ${{ needs.version.outputs.long }} Alpha
tag_name: v${{ needs.version.outputs.long }}
target_commitish: ${{ github.sha }}
body: ${{ steps.release_notes.outputs.notes }}
- name: Download source deb
+4 -4
View File
@@ -4,11 +4,11 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.9.0
ref: v1.10.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.2.528
- checkov@3.2.529
- renovate@43.150.0
- prettier@3.8.3
- trufflehog@3.95.3
@@ -16,7 +16,7 @@ lint:
- bandit@1.9.4
- trivy@0.70.0
- taplo@0.10.0
- ruff@0.15.12
- ruff@0.15.13
- isort@8.0.1
- markdownlint@0.48.0
- oxipng@10.1.1
@@ -26,7 +26,7 @@ lint:
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@26.3.1
- black@26.5.1
- git-diff-check
- gitleaks@8.30.1
- clang-format@16.0.3
+1 -1
View File
@@ -38,4 +38,4 @@ cp bin/device-install.* $OUTDIR/
cp bin/device-update.* $OUTDIR/
echo "Copying manifest"
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json || true
cp $BUILDDIR/$basename.mt.json $OUTDIR/$basename.mt.json
File diff suppressed because it is too large Load Diff
+8 -4
View File
@@ -293,9 +293,12 @@ if ("HAS_TFT", 1) in env.get("CPPDEFINES", []):
board_arch = infer_architecture(env.BoardConfig())
should_skip_manifest = board_arch is None
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
mtjson_deps = [] if should_skip_manifest else ["buildprog"]
if not should_skip_manifest and platform.name == "espressif32":
# Most platforms can generate the manifest as part of the default 'buildprog' target.
# Typically this passes success/failure properly.
mtjson_deps = ["buildprog"]
if platform.name == "espressif32":
# On ESP32, we need to explicitly depend upon the binary to prevent fake-success upon failure.
mtjson_deps = ["$BUILD_DIR/${PROGNAME}.bin"]
# Build littlefs image as part of mtjson target
# Equivalent to `pio run -t buildfs`
target_lfs = env.DataToBin(
@@ -309,7 +312,8 @@ if should_skip_manifest:
env.AddCustomTarget(
name="mtjson",
dependencies=mtjson_deps,
# For host/native envs, avoid depending on 'buildprog' (some targets don't define it)
dependencies=[],
actions=[skip_manifest],
title="Meshtastic Manifest (skipped)",
description="mtjson generation is skipped for native environments",
+1 -1
View File
@@ -7,7 +7,7 @@
"extra_flags": [
"-D CDEBYTE_EORA_S3",
"-D ARDUINO_USB_CDC_ON_BOOT=1",
"-D ARDUINO_USB_MODE=0",
"-D ARDUINO_USB_MODE=1",
"-D ARDUINO_RUNNING_CORE=1",
"-D ARDUINO_EVENT_RUNNING_CORE=1",
"-D BOARD_HAS_PSRAM"
+1 -1
View File
@@ -6,7 +6,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
+1 -1
View File
@@ -8,7 +8,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"
],
+54
View File
@@ -0,0 +1,54 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [
["0x239A", "0x4405"],
["0x239A", "0x0029"],
["0x239A", "0x002A"],
["0x2886", "0x1667"]
],
"usb_product": "HT-n5262",
"mcu": "nrf52840",
"variant": "heltec_mesh_node_t1",
"variants_dir": "variants",
"bsp": {
"name": "adafruit"
},
"softdevice": {
"sd_flags": "-DS140",
"sd_name": "s140",
"sd_version": "6.1.1",
"sd_fwid": "0x00B6"
},
"bootloader": {
"settings_addr": "0xFF000"
}
},
"connectivity": ["bluetooth"],
"debug": {
"jlink_device": "nRF52840_xxAA",
"onboard_tools": ["jlink"],
"svd_path": "nrf52840.svd",
"openocd_target": "nrf52840-mdk-rs"
},
"frameworks": ["arduino"],
"name": "Heltec Mesh Node T1",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"speed": 115200,
"protocol": "nrfutil",
"protocols": ["jlink", "nrfjprog", "nrfutil", "stlink"],
"use_1200bps_touch": true,
"require_upload_port": true,
"wait_for_upload_port": true
},
"url": "https://heltec.org",
"vendor": "Heltec"
}
+1 -1
View File
@@ -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"
],
+1 -1
View File
@@ -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"
],
+1 -1
View File
@@ -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"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"extra_flags": [
"-DHELTEC_WIRELESS_TRACKER",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -7,7 +7,7 @@
"core": "esp32",
"extra_flags": [
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,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=0"
],
+1 -1
View File
@@ -9,7 +9,7 @@
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_1W",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -8,7 +8,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"
],
+1 -1
View File
@@ -8,7 +8,7 @@
"-DBOARD_HAS_PSRAM",
"-DLILYGO_TBEAM_S3_CORE",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+1 -1
View File
@@ -7,7 +7,7 @@
"extra_flags": [
"-DLILYGO_T3S3_V1",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1",
"-DBOARD_HAS_PSRAM"
+1 -1
View File
@@ -10,7 +10,7 @@
"-DBOARD_HAS_PSRAM",
"-DUNPHONE_SPIN=9",
"-DARDUINO_USB_CDC_ON_BOOT=1",
"-DARDUINO_USB_MODE=0",
"-DARDUINO_USB_MODE=1",
"-DARDUINO_RUNNING_CORE=1",
"-DARDUINO_EVENT_RUNNING_CORE=1"
],
+7
View File
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x640000,
app1, app, ota_1, 0x650000,0x640000,
spiffs, data, spiffs, 0xc90000,0x360000,
coredump, data, coredump,0xFF0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x640000
5 app1 app ota_1 0x650000 0x640000
6 spiffs data spiffs 0xc90000 0x360000
7 coredump data coredump 0xFF0000 0x10000
+7
View File
@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x330000,
app1, app, ota_1, 0x340000,0x330000,
spiffs, data, spiffs, 0x670000,0x180000,
coredump, data, coredump,0x7F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x330000
5 app1 app ota_1 0x340000 0x330000
6 spiffs data spiffs 0x670000 0x180000
7 coredump data coredump 0x7F0000 0x10000
+3 -14
View File
@@ -70,17 +70,6 @@ def esp32_create_combined_bin(source, target, env):
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)
esp32_kind = env.GetProjectOption("custom_esp32_kind")
if esp32_kind == "esp32":
# Free up some IRAM by removing auxiliary SPI flash chip drivers.
# Wrapped stub symbols are defined in src/platform/esp32/iram-quirk.c.
env.Append(
LINKFLAGS=[
"-Wl,--wrap=esp_flash_chip_gd",
"-Wl,--wrap=esp_flash_chip_issi",
"-Wl,--wrap=esp_flash_chip_winbond",
]
)
else:
# For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
# Enable Newlib Nano formatting to save space
# ...but allow printf float support (compromise)
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# trunk-ignore-all(ruff/F821)
# trunk-ignore-all(flake8/F821): For SConstruct imports
# force linker response file instead of command line arguments
Import("env")
def wrap_with_tempfile(command_key):
command = env.get(command_key)
if not command or not isinstance(command, str):
return
if "TEMPFILE(" in command:
return
env.Replace(**{command_key: "${TEMPFILE('%s')}" % command})
# Force SCons to spill long commands into response files on this target.
env.Replace(MAXLINELENGTH=8192)
for key in ("LINKCOM", "CXXLINKCOM", "SHLINKCOM", "SHCXXLINKCOM"):
wrap_with_tempfile(key)
+5 -1
View File
@@ -2,7 +2,7 @@
; https://docs.platformio.org/page/projectconf.html
[platformio]
default_envs = tbeam
default_envs = heltec-v3
extra_configs =
variants/*/*.ini
@@ -186,12 +186,16 @@ lib_deps =
https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/archive/refs/tags/v1.1.2.zip
# renovate: datasource=github-tags depName=SparkFun 9DoF IMU Breakout ICM 20948 packageName=sparkfun/SparkFun_ICM-20948_ArduinoLibrary
https://github.com/sparkfun/SparkFun_ICM-20948_ArduinoLibrary/archive/refs/tags/v1.3.2.zip
# renovate: datasource=github-tags depName=TDK InvenSense ICM42670P packageName=tdk-invn-oss/motion.arduino.ICM42670P
https://github.com/tdk-invn-oss/motion.arduino.ICM42670P/archive/refs/tags/1.0.8.zip
# renovate: datasource=github-tags depName=Adafruit LTR390 Library packageName=adafruit/Adafruit_LTR390
https://github.com/adafruit/Adafruit_LTR390/archive/refs/tags/1.1.2.zip
# renovate: datasource=github-tags depName=Adafruit PCT2075 packageName=adafruit/Adafruit_PCT2075
https://github.com/adafruit/Adafruit_PCT2075/archive/refs/tags/1.0.6.zip
# renovate: datasource=github-tags depName=DFRobot_BMM150 packageName=dfrobot/DFRobot_BMM150
https://github.com/DFRobot/DFRobot_BMM150/archive/refs/tags/V1.0.0.zip
# renovate: datasource=github-tags depName=SparkFun MMC5983MA Magnetometer packageName=sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library
https://github.com/sparkfun/SparkFun_MMC5983MA_Magnetometer_Arduino_Library/archive/refs/tags/v1.1.4.zip
# renovate: datasource=github-tags depName=Adafruit_TSL2561 packageName=adafruit/Adafruit_TSL2561
https://github.com/adafruit/Adafruit_TSL2561/archive/refs/tags/1.1.3.zip
# renovate: datasource=github-tags depName=BH1750_WE packageName=wollewald/BH1750_WE
+5 -4
View File
@@ -152,15 +152,16 @@ extern "C" void logLegacy(const char *level, const char *fmt, ...);
// Default Bluetooth PIN
#define defaultBLEPin 123456
#if HAS_ETHERNET && defined(USE_CH390D)
#if HAS_ETHERNET && defined(USE_ARDUINO_ETHERNET)
#include <Ethernet.h> // arduino-libraries/Ethernet — supports W5500 auto-detect
#elif HAS_ETHERNET && defined(USE_CH390D)
#include <ESP32_CH390.h>
#elif HAS_ETHERNET && !defined(USE_WS5500)
#include <RAK13800_W5100S.h>
#endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
#if HAS_WIFI
+22 -9
View File
@@ -1,4 +1,5 @@
#include "DisplayFormatters.h"
#include "MeshRadio.h"
const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaConfig_ModemPreset preset, bool useShortName,
bool usePreset)
@@ -11,33 +12,45 @@ const char *DisplayFormatters::getModemPresetDisplayName(meshtastic_Config_LoRaC
}
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
case PRESET(SHORT_TURBO):
return useShortName ? "ShortT" : "ShortTurbo";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case PRESET(SHORT_SLOW):
return useShortName ? "ShortS" : "ShortSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case PRESET(SHORT_FAST):
return useShortName ? "ShortF" : "ShortFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case PRESET(MEDIUM_SLOW):
return useShortName ? "MedS" : "MediumSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
case PRESET(MEDIUM_FAST):
return useShortName ? "MedF" : "MediumFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case PRESET(LONG_SLOW):
return useShortName ? "LongS" : "LongSlow";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
case PRESET(LONG_FAST):
return useShortName ? "LongF" : "LongFast";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
case PRESET(LONG_TURBO):
return useShortName ? "LongT" : "LongTurbo";
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case PRESET(LONG_MODERATE):
return useShortName ? "LongM" : "LongMod";
break;
case PRESET(LITE_FAST):
return useShortName ? "LiteF" : "LiteFast";
break;
case PRESET(LITE_SLOW):
return useShortName ? "LiteS" : "LiteSlow";
break;
case PRESET(NARROW_FAST):
return useShortName ? "NarF" : "NarrowFast";
break;
case PRESET(NARROW_SLOW):
return useShortName ? "NarS" : "NarrowSlow";
break;
default:
return useShortName ? "Custom" : "Invalid";
break;
+1 -1
View File
@@ -277,7 +277,7 @@ void fsInit()
*/
void setupSDCard()
{
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI)
#if defined(HAS_SDCARD) && !defined(SDCARD_USE_SOFT_SPI) && !defined(HAS_SD_MMC)
concurrency::LockGuard g(spiLock);
SDHandler.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
if (!SD.begin(SDCARD_CS, SDHandler, SD_SPI_FREQUENCY)) {
+126 -83
View File
@@ -24,6 +24,13 @@
#include "meshUtils.h"
#include "power/PowerHAL.h"
#include "sleep.h"
#ifdef ARCH_ESP32
// #include <driver/adc.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_err.h>
#endif
#if defined(ARCH_PORTDUINO)
#include "api/WiFiServerAPI.h"
@@ -63,9 +70,8 @@
#include <WiFi.h>
#endif
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
#endif
@@ -77,21 +83,86 @@
#if defined(BATTERY_PIN) && defined(ARCH_ESP32)
#ifndef BAT_MEASURE_ADC_UNIT // ADC1 is default
static const adc1_channel_t adc_channel = ADC_CHANNEL;
static const adc_channel_t adc_channel = ADC_CHANNEL;
static const adc_unit_t unit = ADC_UNIT_1;
#else // ADC2
static const adc2_channel_t adc_channel = ADC_CHANNEL;
#else // ADC2
static const adc_channel_t adc_channel = ADC_CHANNEL;
static const adc_unit_t unit = ADC_UNIT_2;
RTC_NOINIT_ATTR uint64_t RTC_reg_b;
#endif // BAT_MEASURE_ADC_UNIT
esp_adc_cal_characteristics_t *adc_characs = (esp_adc_cal_characteristics_t *)calloc(1, sizeof(esp_adc_cal_characteristics_t));
static adc_oneshot_unit_handle_t adc_handle = nullptr;
static adc_cali_handle_t adc_cali_handle = nullptr;
static bool adc_calibrated = false;
#ifndef ADC_ATTENUATION
static const adc_atten_t atten = ADC_ATTEN_DB_12;
#else
static const adc_atten_t atten = ADC_ATTENUATION;
#endif
#ifdef ADC_BITWIDTH
static const adc_bitwidth_t adc_width = ADC_BITWIDTH;
#else
static const adc_bitwidth_t adc_width = ADC_BITWIDTH_DEFAULT;
#endif
static int adcBitWidthToBits(adc_bitwidth_t width)
{
switch (width) {
case ADC_BITWIDTH_9:
return 9;
case ADC_BITWIDTH_10:
return 10;
case ADC_BITWIDTH_11:
return 11;
case ADC_BITWIDTH_12:
return 12;
#ifdef ADC_BITWIDTH_13
case ADC_BITWIDTH_13:
return 13;
#endif
default:
return 12;
}
}
static bool initAdcCalibration()
{
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = adc_width,
};
esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle);
if (ret == ESP_OK) {
LOG_INFO("ADC calibration: curve fitting enabled");
return true;
}
if (ret != ESP_ERR_NOT_SUPPORTED) {
LOG_WARN("ADC calibration: curve fitting failed: %s", esp_err_to_name(ret));
}
#endif
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
adc_cali_line_fitting_config_t cali_config = {
.unit_id = unit,
.atten = atten,
.bitwidth = adc_width,
.default_vref = DEFAULT_VREF,
};
esp_err_t ret = adc_cali_create_scheme_line_fitting(&cali_config, &adc_cali_handle);
if (ret == ESP_OK) {
LOG_INFO("ADC calibration: line fitting enabled");
return true;
}
if (ret != ESP_ERR_NOT_SUPPORTED) {
LOG_WARN("ADC calibration: line fitting failed: %s", esp_err_to_name(ret));
}
#endif
LOG_INFO("ADC calibration not supported; using approximate scaling");
return false;
}
#endif // BATTERY_PIN && ARCH_ESP32
#ifdef EXT_PWR_DETECT
@@ -367,8 +438,20 @@ class AnalogBatteryLevel : public HasBatteryLevel
scaled *= operativeAdcMultiplier;
#elif defined(ARCH_ESP32) // ADC block for espressif platforms
raw = espAdcRead();
scaled = esp_adc_cal_raw_to_voltage(raw, adc_characs);
scaled *= operativeAdcMultiplier;
int voltage_mv = 0;
if (adc_calibrated && adc_cali_handle) {
if (adc_cali_raw_to_voltage(adc_cali_handle, raw, &voltage_mv) != ESP_OK) {
LOG_WARN("ADC calibration read failed; using raw value");
voltage_mv = 0;
}
}
if (voltage_mv == 0) {
// Fallback approximate conversion without calibration
const int bits = adcBitWidthToBits(adc_width);
const float max_code = powf(2.0f, bits) - 1.0f;
voltage_mv = (int)((raw / max_code) * DEFAULT_VREF);
}
scaled = voltage_mv * operativeAdcMultiplier;
#else // block for all other platforms
#ifdef ARCH_NRF52
concurrency::LockGuard saadcGuard(concurrency::nrf52SaadcLock);
@@ -410,51 +493,22 @@ class AnalogBatteryLevel : public HasBatteryLevel
uint32_t raw = 0;
uint8_t raw_c = 0; // raw reading counter
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
if (!adc_handle) {
LOG_ERROR("ADC oneshot handle not initialized");
return 0;
}
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
int val_ = adc1_get_raw(adc_channel);
if (val_ >= 0) { // save only valid readings
raw += val_;
int val = 0;
esp_err_t err = adc_oneshot_read(adc_handle, adc_channel, &val);
if (err == ESP_OK) {
raw += val;
raw_c++;
}
// delayMicroseconds(100);
}
#else // ADC2
#ifdef CONFIG_IDF_TARGET_ESP32S3 // ESP32S3
// ADC2 wifi bug workaround not required, breaks compile
// On ESP32S3, ADC2 can take turns with Wifi (?)
int32_t adc_buf;
esp_err_t read_result;
// Multiple samples
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
adc_buf = 0;
read_result = -1;
read_result = adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
if (read_result == ESP_OK) {
raw += adc_buf;
raw_c++; // Count valid samples
} else {
LOG_DEBUG("An attempt to sample ADC2 failed");
LOG_DEBUG("ADC read failed: %s", esp_err_to_name(err));
}
}
#else // Other ESP32
int32_t adc_buf = 0;
for (int i = 0; i < BATTERY_SENSE_SAMPLES; i++) {
// ADC2 wifi bug workaround, see
// https://github.com/espressif/arduino-esp32/issues/102
WRITE_PERI_REG(SENS_SAR_READ_CTRL2_REG, RTC_reg_b);
SET_PERI_REG_MASK(SENS_SAR_READ_CTRL2_REG, SENS_SAR2_DATA_INV);
adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf);
raw += adc_buf;
raw_c++;
}
#endif // BAT_MEASURE_ADC_UNIT
#endif // End BAT_MEASURE_ADC_UNIT
return (raw / (raw_c < 1 ? 1 : raw_c));
}
#endif
@@ -666,42 +720,31 @@ bool Power::analogInit()
#ifdef ARCH_STM32WL
analogReadResolution(BATTERY_SENSE_RESOLUTION_BITS);
#elif defined(ARCH_ESP32) // ESP32 needs special analog stuff
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = unit,
};
#ifndef ADC_WIDTH // max resolution by default
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
#else
static const adc_bits_width_t width = ADC_WIDTH;
#endif
#ifndef BAT_MEASURE_ADC_UNIT // ADC1
adc1_config_width(width);
adc1_config_channel_atten(adc_channel, atten);
#else // ADC2
adc2_config_channel_atten(adc_channel, atten);
#ifndef CONFIG_IDF_TARGET_ESP32S3
// ADC2 wifi bug workaround
// Not required with ESP32S3, breaks compile
RTC_reg_b = READ_PERI_REG(SENS_SAR_READ_CTRL2_REG);
#endif
#endif
// calibrate ADC
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, width, DEFAULT_VREF, adc_characs);
// show ADC characterization base
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
LOG_INFO("ADC config based on Two Point values stored in eFuse");
} else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
LOG_INFO("ADC config based on reference voltage stored in eFuse");
if (!adc_handle) {
esp_err_t err = adc_oneshot_new_unit(&init_config, &adc_handle);
if (err != ESP_OK) {
LOG_ERROR("ADC oneshot init failed: %s", esp_err_to_name(err));
return false;
}
}
#ifdef CONFIG_IDF_TARGET_ESP32S3
// ESP32S3
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP_FIT) {
LOG_INFO("ADC config based on Two Point values and fitting curve "
"coefficients stored in eFuse");
adc_oneshot_chan_cfg_t chan_cfg = {
.atten = atten,
.bitwidth = adc_width,
};
esp_err_t err = adc_oneshot_config_channel(adc_handle, adc_channel, &chan_cfg);
if (err != ESP_OK) {
LOG_ERROR("ADC channel config failed: %s", esp_err_to_name(err));
return false;
}
#endif
else {
LOG_INFO("ADC config based on default reference voltage");
}
#endif // ARCH_ESP32
adc_calibrated = initAdcCalibration();
#endif // ARCH_ESP32
// NRF52 ADC init moved to powerHAL_init in nrf52 platform
+4 -3
View File
@@ -133,11 +133,12 @@ bool AirTime::isTxAllowedChannelUtil(bool polite)
bool AirTime::isTxAllowedAirUtil()
{
if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) {
if (utilizationTXPercent() < myRegion->dutyCycle * polite_duty_cycle_percent / 100) {
float effectiveDutyCycle = getEffectiveDutyCycle();
if (!config.lora.override_duty_cycle && effectiveDutyCycle < 100) {
if (utilizationTXPercent() < effectiveDutyCycle * polite_duty_cycle_percent / 100) {
return true;
} else {
LOG_WARN("TX air util. >%f%%. Skip send", myRegion->dutyCycle * polite_duty_cycle_percent / 100);
LOG_WARN("TX air util. >%f%%. Skip send", effectiveDutyCycle * polite_duty_cycle_percent / 100);
return false;
}
}
+25 -4
View File
@@ -1,6 +1,7 @@
#include "Lock.h"
#include "configuration.h"
#include <cassert>
#include <logging.h>
namespace concurrency
{
@@ -33,13 +34,33 @@ void Lock::unlock()
}
}
#else
Lock::Lock() {}
Lock::Lock()
{
pthread_mutex_init(&mutex, NULL);
}
Lock::~Lock() {}
void Lock::lock()
{
if (locked) {
LOG_INFO("Attempt to lock an already locked Lock!");
}
pthread_mutex_lock(&mutex);
locked = true;
void Lock::lock() {}
if (console)
LOG_WARN("Lock");
}
void Lock::unlock() {}
void Lock::unlock()
{
pthread_mutex_unlock(&mutex);
locked = false;
}
Lock::~Lock()
{
pthread_mutex_destroy(&mutex);
}
#endif
} // namespace concurrency
+3
View File
@@ -30,6 +30,9 @@ class Lock
private:
#ifdef HAS_FREE_RTOS
SemaphoreHandle_t handle;
#else
pthread_mutex_t mutex;
bool locked = false;
#endif
};
+7
View File
@@ -243,6 +243,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define QMI8658_ADDR 0x6B
#define QMC5883L_ADDR 0x0D
#define HMC5883L_ADDR 0x1E
#define MMC5983MA_ADDR 0x30
#define SHTC3_ADDR 0x70
#define LPS22HB_ADDR 0x5C
#define LPS22HB_ADDR_ALT 0x5D
@@ -292,6 +293,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define DA217_ADDR 0x26
#define BMI270_ADDR 0x68
#define BMI270_ADDR_ALT 0x69
#define ICM42607P_ADDR 0x68
#define ICM42607P_ADDR_ALT 0x69
// -----------------------------------------------------------------------------
// LED
@@ -561,5 +564,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define HAS_SCREEN 0
#endif
#ifndef USE_ETHERNET_DEFAULT
#define USE_ETHERNET_DEFAULT 0
#endif
#include "DebugConfiguration.h"
#include "RF95Configuration.h"
+9 -2
View File
@@ -37,8 +37,15 @@ ScanI2C::FoundDevice ScanI2C::firstKeyboard() const
ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const
{
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX, ICM20948, QMA6100P, BMM150, BMI270};
return firstOfOrNONE(10, types);
ScanI2C::DeviceType types[] = {MPU6050, LIS3DH, BMA423, LSM6DS3, BMX160, STK8BAXX,
ICM20948, QMA6100P, BMM150, BMI270, ICM42607P};
return firstOfOrNONE(11, types);
}
ScanI2C::FoundDevice ScanI2C::firstMagnetometer() const
{
ScanI2C::DeviceType types[] = {MMC5983MA};
return firstOfOrNONE(1, types);
}
ScanI2C::FoundDevice ScanI2C::firstAQI() const
+4
View File
@@ -41,6 +41,7 @@ class ScanI2C
QMI8658,
QMC5883L,
HMC5883L,
MMC5983MA,
PMSA003I,
QMA6100P,
MPU6050,
@@ -65,6 +66,7 @@ class ScanI2C
FT6336U,
STK8BAXX,
ICM20948,
ICM42607P,
SCD4X,
MAX30102,
TPS65233,
@@ -149,6 +151,8 @@ class ScanI2C
FoundDevice firstAccelerometer() const;
FoundDevice firstMagnetometer() const;
FoundDevice firstAQI() const;
FoundDevice firstRGBLED() const;
+47 -15
View File
@@ -179,8 +179,22 @@ String readSEN5xProductName(TwoWire *i2cBus, uint8_t address)
#endif
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
static uint8_t crcSHT2X(const uint8_t *data, uint8_t len)
{
uint8_t crc = 0;
for (uint8_t i = 0; i < len; i++) {
crc ^= data[i];
for (uint8_t bit = 0; bit < 8; bit++) {
crc = (crc & 0x80) ? (crc << 1) ^ 0x31 : crc << 1;
}
}
return crc;
}
bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address)
{
uint8_t serialA[8] = {0};
uint8_t serialB[6] = {0};
i2cBus->beginTransmission(address);
i2cBus->write(0xFA);
@@ -189,12 +203,13 @@ bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address)
if (i2cBus->endTransmission() != 0)
return false;
if (i2cBus->requestFrom(address, (uint8_t)8) != 8)
if (i2cBus->requestFrom(address, (uint8_t)sizeof(serialA)) != sizeof(serialA))
return false;
// Just flush the data
while (i2cBus->available() < 8) {
i2cBus->read();
for (uint8_t i = 0; i < sizeof(serialA); i++) {
if (!i2cBus->available())
return false;
serialA[i] = i2cBus->read();
}
i2cBus->beginTransmission(address);
@@ -204,16 +219,18 @@ bool detectSHT21SerialNumber(TwoWire *i2cBus, uint8_t address)
if (i2cBus->endTransmission() != 0)
return false;
if (i2cBus->requestFrom(address, (uint8_t)6) != 6)
if (i2cBus->requestFrom(address, (uint8_t)sizeof(serialB)) != sizeof(serialB))
return false;
// Just flush the data
while (i2cBus->available() < 6) {
i2cBus->read();
for (uint8_t i = 0; i < sizeof(serialB); i++) {
if (!i2cBus->available())
return false;
serialB[i] = i2cBus->read();
}
// Assume we detect the SHT21 if something came back from the request
return true;
return crcSHT2X(&serialA[0], 1) == serialA[1] && crcSHT2X(&serialA[2], 1) == serialA[3] &&
crcSHT2X(&serialA[4], 1) == serialA[5] && crcSHT2X(&serialA[6], 1) == serialA[7] &&
crcSHT2X(&serialB[0], 2) == serialB[2] && crcSHT2X(&serialB[3], 2) == serialB[5];
}
#endif
@@ -343,9 +360,6 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
SCAN_SIMPLE_CASE(ST7567_ADDRESS, SCREEN_ST7567, "ST7567", (uint8_t)addr.address);
#ifdef HAS_NCP5623
SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address);
#endif
#ifdef HAS_LP5562
SCAN_SIMPLE_CASE(LP5562_ADDR, LP5562, "LP5562", (uint8_t)addr.address);
#endif
case XPOWERS_AXP192_AXP2101_ADDRESS:
// Do we have the axp2101/192 or the TCA8418
@@ -583,6 +597,18 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
#else
SCAN_SIMPLE_CASE(PMSA003I_ADDR, PMSA003I, "PMSA003I", (uint8_t)addr.address)
#endif
case MMC5983MA_ADDR:
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x2F), 1);
if (registerValue == 0x30) {
type = MMC5983MA;
logFoundDevice("MMC5983MA", (uint8_t)addr.address);
#ifdef HAS_LP5562
} else {
type = LP5562;
logFoundDevice("LP5562", (uint8_t)addr.address);
#endif
}
break;
case BMA423_ADDR: // this can also be LIS3DH_ADDR_ALT
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x0F), 2);
if (registerValue == 0x3300 || registerValue == 0x3333) { // RAK4631 WisBlock has LIS3DH register at 0x3333
@@ -737,8 +763,8 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
}
break;
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, and SEN5X_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR
case ICM20948_ADDR: // same as BMX160_ADDR, BMI270_ADDR_ALT, ICM42607P_ADDR_ALT, and SEN5X_ADDR
case ICM20948_ADDR_ALT: // same as MPU6050_ADDR, BMI270_ADDR, and ICM42607P_ADDR
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x00), 1);
#ifdef HAS_ICM20948
type = ICM20948;
@@ -758,6 +784,12 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize)
logFoundDevice("BMX160", (uint8_t)addr.address);
break;
} else {
registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0x75), 1); // WHO_AM_I
if (registerValue == 0x60) {
type = ICM42607P;
logFoundDevice("ICM-42607-P", (uint8_t)addr.address);
break;
}
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_AIR_QUALITY_SENSOR
String prod = "";
prod = readSEN5xProductName(i2cBus, addr.address);
+8 -3
View File
@@ -425,6 +425,11 @@ Screen::Screen(ScanI2C::DeviceAddress address, meshtastic_Config_DisplayConfig_O
#elif defined(USE_SSD1306)
dispdev = new SSD1306Wire(address.address, -1, -1, geometry,
(address.port == ScanI2C::I2CPort::WIRE1) ? HW_I2C::I2C_TWO : HW_I2C::I2C_ONE);
#if defined(OLED_Y_OFFSET_PAGES)
// Panels whose active window does not start at GDDRAM row 0 (e.g. 72x40
// modules on pages 3..7) need a fixed vertical page shift on every write.
static_cast<SSD1306Wire *>(dispdev)->setYOffset(OLED_Y_OFFSET_PAGES);
#endif
#elif defined(USE_SPISSD1306)
dispdev = new SSD1306Spi(SSD1306_RESET, SSD1306_RS, SSD1306_NSS, GEOMETRY_64_48);
if (!dispdev->init()) {
@@ -910,7 +915,7 @@ int32_t Screen::runOnce()
#ifndef DISABLE_WELCOME_UNSET
if (!NotificationRenderer::isOverlayBannerShowing() && config.lora.region == meshtastic_Config_LoRaConfig_RegionCode_UNSET) {
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
menuHandler::LoraRegionPicker();
#else
menuHandler::OnboardMessage();
@@ -1147,7 +1152,7 @@ void Screen::setFrames(FrameFocus focus)
#if defined(DISPLAY_CLOCK_FRAME)
if (!hiddenFrames.clock) {
fsi.positions.clock = numframes;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
normalFrames[numframes++] = graphics::ClockRenderer::drawAnalogClockFrame;
#else
normalFrames[numframes++] = uiconfig.is_clockface_analog ? graphics::ClockRenderer::drawAnalogClockFrame
@@ -1606,7 +1611,7 @@ void Screen::showFrame(FrameDirection direction)
void Screen::setFastFramerate()
{
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
dispdev->clear();
#if GRAPHICS_TFT_COLORING_ENABLED
prepareFrameColorRegions();
+1 -1
View File
@@ -106,7 +106,7 @@
#define FONT_SMALL FONT_MEDIUM_LOCAL // Height: 19
#define FONT_MEDIUM FONT_LARGE_LOCAL // Height: 28
#define FONT_LARGE FONT_LARGE_LOCAL // Height: 28
#elif defined(M5STACK_UNITC6L)
#elif defined(OLED_TINY)
#define FONT_SMALL FONT_SMALL_LOCAL // Height: 13
#define FONT_MEDIUM FONT_SMALL_LOCAL // Height: 13
#define FONT_LARGE FONT_SMALL_LOCAL // Height: 13
+7 -1
View File
@@ -31,6 +31,12 @@ ScreenResolution determineScreenResolution(int16_t screenheight, int16_t screenw
return ScreenResolution::UltraLow;
}
#ifdef DISPLAY_FORCE_SMALL_FONTS
if (screenwidth <= 160 && screenheight <= 80) {
return ScreenResolution::Low;
}
#endif
// Standard OLED screens
if (screenwidth > 128 && screenheight <= 64) {
return ScreenResolution::Low;
@@ -240,7 +246,7 @@ void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *ti
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === Battery Icons ===
if (usbPowered && !isCharging) { // This is a basic check to determine USB Powered is flagged but not charging
batteryX += 1;
+12 -8
View File
@@ -1458,7 +1458,8 @@ void TFTDisplay::sendCommand(uint8_t com)
digitalWrite(portduino_config.displayBacklight.pin, TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
tft->displayOn();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
tft->wakeup();
tft->powerSaveOff();
#endif
@@ -1469,7 +1470,7 @@ void TFTDisplay::sendCommand(uint8_t com)
#ifdef UNPHONE
unphone.backlight(true); // using unPhone library
#endif
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(ST7789_CS) && \
!defined(HACKADAY_COMMUNICATOR) // T-Deck gets brightness set in Screen.cpp in the handleSetOn function
tft->setBrightness(172);
@@ -1485,7 +1486,8 @@ void TFTDisplay::sendCommand(uint8_t com)
digitalWrite(portduino_config.displayBacklight.pin, !TFT_BACKLIGHT_ON);
#elif defined(HACKADAY_COMMUNICATOR)
tft->displayOff();
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096)
#elif !defined(RAK14014) && !defined(M5STACK) && !defined(UNPHONE) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
tft->sleep();
tft->powerSaveOn();
#endif
@@ -1496,7 +1498,7 @@ void TFTDisplay::sendCommand(uint8_t com)
#ifdef UNPHONE
unphone.backlight(false); // using unPhone library
#endif
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR)
tft->setBrightness(0);
#endif
@@ -1511,7 +1513,7 @@ void TFTDisplay::sendCommand(uint8_t com)
void TFTDisplay::setDisplayBrightness(uint8_t _brightness)
{
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
// todo
#elif !defined(HACKADAY_COMMUNICATOR)
tft->setBrightness(_brightness);
@@ -1531,7 +1533,8 @@ bool TFTDisplay::hasTouch(void)
{
#ifdef RAK14014
return true;
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
return tft->touch() != nullptr;
#else
return false;
@@ -1550,7 +1553,8 @@ bool TFTDisplay::getTouch(int16_t *x, int16_t *y)
} else {
return false;
}
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096)
#elif !defined(M5STACK) && !defined(HACKADAY_COMMUNICATOR) && !defined(HELTEC_MESH_NODE_T096) && \
!defined(HELTEC_MESH_NODE_T1)
return tft->getTouch(x, y);
#else
return false;
@@ -1567,7 +1571,7 @@ bool TFTDisplay::connect()
{
concurrency::LockGuard g(spiLock);
LOG_INFO("Do TFT init");
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096)
#if defined(RAK14014) || defined(HELTEC_MESH_NODE_T096) || defined(HELTEC_MESH_NODE_T1)
tft = new TFT_eSPI;
#elif defined(HACKADAY_COMMUNICATOR)
bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, 38 /* SCK */, 21 /* MOSI */, GFX_NOT_DEFINED /* MISO */, HSPI /* spi_num */);
+6 -8
View File
@@ -142,8 +142,9 @@ void VirtualKeyboard::draw(OLEDDisplay *display, int16_t offsetX, int16_t offset
if (keyboardStartY < 0)
keyboardStartY = 0;
} else {
// Default (non-wide, non-64px) behavior: use key height heuristic and place at bottom
cellH = KEY_HEIGHT;
// Default (non-wide, non-64px) e.g. SH1107 128x128:
// cellH = FONT_HEIGHT_SMALL - 2 so rows are tighter while still hosting the font
cellH = std::max((int)KEY_HEIGHT, FONT_HEIGHT_SMALL - 2);
int keyboardHeight = KEYBOARD_ROWS * cellH;
keyboardStartY = screenH - keyboardHeight;
if (keyboardStartY < 0)
@@ -446,11 +447,8 @@ void VirtualKeyboard::drawKey(OLEDDisplay *display, const VirtualKey &key, bool
if (textX < x)
textX = x; // guard
} else {
if (display->getHeight() <= 64 && (key.character >= '0' && key.character <= '9')) {
textX = x + (width - textWidth + 1) / 2;
} else {
textX = x + (width - textWidth) / 2;
}
// Use ceil rounding for all screens (consistent with 128x64 behavior for numbers)
textX = x + (width - textWidth + 1) / 2;
}
int contentTop = y;
int contentH = height;
@@ -746,4 +744,4 @@ bool VirtualKeyboard::isTimedOut() const
}
} // namespace graphics
#endif
#endif
+2 -2
View File
@@ -460,7 +460,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x,
nameX = (SCREEN_WIDTH - textWidth) / 2;
display->drawString(nameX, getTextPositions(display)[line++], frequencyslot);
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === Fifth Row: Channel Utilization ===
const char *chUtil = "ChUtil:";
char chUtilPercentage[10];
@@ -592,7 +592,7 @@ void drawSystemScreen(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x
// Label
display->setTextAlignment(TEXT_ALIGN_LEFT);
display->drawString(labelX, getTextPositions(display)[line], label);
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// Bar
int barY = getTextPositions(display)[line] + (FONT_HEIGHT_SMALL - barHeight) / 2;
display->setColor(WHITE);
+67 -36
View File
@@ -2,6 +2,7 @@
#if HAS_SCREEN
#include "ClockRenderer.h"
#include "Default.h"
#include "DisplayFormatters.h"
#include "GPS.h"
#include "MenuHandler.h"
#include "MeshRadio.h"
@@ -180,6 +181,8 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
{"US", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_US},
{"EU_433", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_433},
{"EU_868", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_868},
{"EU_866", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_866},
{"EU_868_NARROW", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_EU_N_868},
{"CN", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_CN},
{"JP", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_JP},
{"ANZ", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_ANZ},
@@ -203,6 +206,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
{"KZ_863", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_KZ_863},
{"NP_865", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_NP_865},
{"BR_902", OptionsAction::Select, meshtastic_Config_LoRaConfig_RegionCode_BR_902},
};
constexpr size_t regionCount = sizeof(regionOptions) / sizeof(regionOptions[0]);
@@ -244,7 +248,7 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
#endif
config.lora.tx_enabled = true;
initRegion();
if (myRegion->dutyCycle < 100) {
if (getEffectiveDutyCycle() < 100) {
config.lora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}
@@ -378,42 +382,64 @@ void menuHandler::FrequencySlotPicker()
screen->showOverlayBanner(bannerOptions);
}
// Maximum presets any region can have + 1 for Back
static constexpr int MAX_PRESET_OPTIONS = 16;
static BannerOverlayOptions buildRegionPresetBanner()
{
// Static storage reused each call — safe because the banner is shown immediately after.
static const char *optionsArray[MAX_PRESET_OPTIONS];
static int optionsEnumArray[MAX_PRESET_OPTIONS];
static char presetLabelBuf[MAX_PRESET_OPTIONS][12]; // scratch space for name copies
int count = 0;
optionsArray[count] = "Back";
optionsEnumArray[count++] = -1;
if (myRegion && myRegion->profile) {
const meshtastic_Config_LoRaConfig_ModemPreset *presets = myRegion->getAvailablePresets();
size_t numPresets = myRegion->getNumPresets();
for (size_t i = 0; i < numPresets && count < MAX_PRESET_OPTIONS; ++i) {
const char *name = DisplayFormatters::getModemPresetDisplayName(presets[i], false, true);
strncpy(presetLabelBuf[count], name, sizeof(presetLabelBuf[count]) - 1);
presetLabelBuf[count][sizeof(presetLabelBuf[count]) - 1] = '\0';
optionsArray[count] = presetLabelBuf[count];
optionsEnumArray[count++] = static_cast<int>(presets[i]);
}
}
int initialSelection = 0;
for (int i = 1; i < count; ++i) {
if (optionsEnumArray[i] == static_cast<int>(config.lora.modem_preset)) {
initialSelection = i;
break;
}
}
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Radio Preset";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = static_cast<uint8_t>(count);
bannerOptions.InitialSelected = initialSelection;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == -1) {
menuHandler::menuQueue = menuHandler::LoraMenu;
screen->runNow();
return;
}
config.lora.use_preset = true;
config.lora.modem_preset = static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(selected);
config.lora.channel_num = 0; // Reset to default channel for the preset
config.lora.override_frequency = 0; // Clear any custom frequency
service->reloadConfig(SEGMENT_CONFIG);
};
return bannerOptions;
}
void menuHandler::radioPresetPicker()
{
static const RadioPresetOption presetOptions[] = {
{"Back", OptionsAction::Back},
{"LongTurbo", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO},
{"LongModerate", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE},
{"LongFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST},
{"MediumSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW},
{"MediumFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST},
{"ShortSlow", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW},
{"ShortFast", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST},
{"ShortTurbo", OptionsAction::Select, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO},
};
constexpr size_t presetCount = sizeof(presetOptions) / sizeof(presetOptions[0]);
static std::array<const char *, presetCount> presetLabels{};
auto bannerOptions =
createStaticBannerOptions("Radio Preset", presetOptions, presetLabels, [](const RadioPresetOption &option, int) -> void {
if (option.action == OptionsAction::Back) {
menuHandler::menuQueue = menuHandler::LoraMenu;
screen->runNow();
return;
}
if (!option.hasValue) {
return;
}
config.lora.modem_preset = option.value;
config.lora.channel_num = 0; // Reset to default channel for the preset
config.lora.override_frequency = 0; // Clear any custom frequency
service->reloadConfig(SEGMENT_CONFIG);
});
screen->showOverlayBanner(bannerOptions);
screen->showOverlayBanner(buildRegionPresetBanner());
}
void menuHandler::twelveHourPicker()
@@ -555,7 +581,7 @@ void menuHandler::TZPicker()
void menuHandler::clockMenu()
{
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
static const char *optionsArray[] = {"Back", "Time Format", "Timezone"};
#else
static const char *optionsArray[] = {"Back", "Clock Face", "Time Format", "Timezone"};
@@ -1307,6 +1333,11 @@ void menuHandler::positionBaseMenu()
if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
if (magnetometerThread) {
magnetometerThread->calibrate(30);
}
#endif
break;
case PositionAction::GPSSmartPosition:
+4 -4
View File
@@ -26,7 +26,7 @@ extern bool haveGlyphs(const char *str);
// Global screen instance
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
static uint32_t lastSwitchTime = 0;
#endif
namespace graphics
@@ -788,7 +788,7 @@ void drawDynamicListScreen_Nodes(OLEDDisplay *display, OLEDDisplayUiState *state
unsigned long now = millis();
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
@@ -824,7 +824,7 @@ void drawDynamicListScreen_Location(OLEDDisplay *display, OLEDDisplayUiState *st
unsigned long now = millis();
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->clear();
if (now - lastSwitchTime >= 3000) {
display->display();
@@ -894,7 +894,7 @@ void drawNodeListWithCompasses(OLEDDisplay *display, OLEDDisplayUiState *state,
double lat = DegD(ourSelfPos.latitude_i);
double lon = DegD(ourSelfPos.longitude_i);
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->clear();
uint32_t now = millis();
if (now - lastSwitchTime >= 2000) {
+1 -1
View File
@@ -728,7 +728,7 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
}
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
boxHeight += (currentResolution == ScreenResolution::High) ? 2 : 1;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
if (visibleTotalLines == 1) {
boxTop += 25;
}
+15 -14
View File
@@ -2,6 +2,7 @@
#if HAS_SCREEN
#include "CompassRenderer.h"
#include "GPSStatus.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
@@ -25,7 +26,7 @@
// External variables
extern graphics::Screen *screen;
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
static uint32_t lastSwitchTime = 0;
#endif
namespace graphics
@@ -753,7 +754,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
if (!node || node->num == nodeDB->getNodeNum() || !nodeInfoLiteIsFavorite(node))
return;
display->clear();
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
uint32_t now = millis();
if (now - lastSwitchTime >= 10000) // 10000 ms = 10 秒
{
@@ -816,16 +817,16 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
// Helper to get SNR limit based on modem preset
auto getSnrLimit = [](meshtastic_Config_LoRaConfig_ModemPreset preset) -> float {
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
case PRESET(LONG_SLOW):
case PRESET(LONG_MODERATE):
case PRESET(LONG_FAST):
return -6.0f;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
case PRESET(MEDIUM_SLOW):
case PRESET(MEDIUM_FAST):
return -5.5f;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
case PRESET(SHORT_SLOW):
case PRESET(SHORT_FAST):
case PRESET(SHORT_TURBO):
return -4.5f;
default:
return -6.0f;
@@ -959,7 +960,7 @@ void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *stat
if (seenStr[0]) {
display->drawString(x, getTextPositions(display)[line++], seenStr);
}
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === 4. Uptime (only show if metric is present) ===
char uptimeStr[32] = "";
meshtastic_DeviceMetrics nodeMetrics;
@@ -1172,7 +1173,7 @@ void UIRenderer::drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *sta
}
#endif
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
line += 1;
// === Node Identity ===
@@ -1456,7 +1457,7 @@ void UIRenderer::drawIconScreen(const char *upperMsg, OLEDDisplay *display, OLED
// needs to be drawn relative to x and y
// draw centered icon left to right and centered above the one line of app text
#if defined(M5STACK_UNITC6L)
#if defined(OLED_TINY)
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
if (gBootSplashBoldPass) {
display->drawXbm(x + (SCREEN_WIDTH - 50) / 2 + 1, y + (SCREEN_HEIGHT - 28) / 2, icon_width, icon_height, icon_bits);
@@ -1666,7 +1667,7 @@ void UIRenderer::drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayU
}
display->drawString(x, textPos[line++], altitudeLine);
}
#if !defined(M5STACK_UNITC6L)
#if !defined(OLED_TINY)
// === Draw Compass ===
if (validHeading || statusLine1) {
// --- Compass Rendering: landscape (wide) screens use original side-aligned logic ---
+1 -1
View File
@@ -318,7 +318,7 @@ const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f};
#define connection_icon_height 5
const uint8_t connection_icon[] = {0x36, 0x41, 0x5D, 0x41, 0x36};
#ifdef M5STACK_UNITC6L
#ifdef OLED_TINY
#include "img/icon_small.xbm"
#else
#include "img/icon.xbm"
@@ -64,6 +64,8 @@ enum MenuAction {
SET_REGION_KZ_863,
SET_REGION_NP_865,
SET_REGION_BR_902,
SET_REGION_EU_866,
SET_REGION_NARROW_868,
// Device Roles
SET_ROLE_CLIENT,
SET_ROLE_CLIENT_MUTE,
@@ -78,6 +80,11 @@ enum MenuAction {
SET_PRESET_SHORT_SLOW,
SET_PRESET_SHORT_FAST,
SET_PRESET_SHORT_TURBO,
SET_PRESET_LITE_SLOW,
SET_PRESET_LITE_FAST,
SET_PRESET_NARROW_SLOW,
SET_PRESET_NARROW_FAST,
SET_PRESET_FROM_REGION, // Dynamic: preset chosen from region-available list
// Timezones
SET_TZ_US_HAWAII,
SET_TZ_US_ALASKA,
@@ -4,6 +4,7 @@
#include "DisplayFormatters.h"
#include "GPS.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "RTC.h"
#include "Router.h"
@@ -257,6 +258,11 @@ int32_t InkHUD::MenuApplet::runOnce()
return OSThread::disable();
}
// Storage for the dynamically-built region preset list — populated in showPage(NODE_CONFIG_PRESET)
static constexpr uint8_t MAX_REGION_PRESETS = 16;
static meshtastic_Config_LoRaConfig_ModemPreset regionPresets[MAX_REGION_PRESETS];
static uint8_t regionPresetCount = 0;
static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region)
{
if (config.lora.region == region)
@@ -276,7 +282,7 @@ static void applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode region)
initRegion();
if (myRegion && myRegion->dutyCycle < 100) {
if (myRegion && getEffectiveDutyCycle() < 100) {
config.lora.ignore_mqtt = true;
}
@@ -770,6 +776,14 @@ void InkHUD::MenuApplet::execute(MenuItem item)
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_BR_902);
break;
case SET_REGION_EU_866:
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_866);
break;
case SET_REGION_NARROW_868:
applyLoRaRegion(meshtastic_Config_LoRaConfig_RegionCode_EU_N_868);
break;
// Roles
case SET_ROLE_CLIENT:
applyDeviceRole(meshtastic_Config_DeviceConfig_Role_CLIENT);
@@ -789,37 +803,46 @@ void InkHUD::MenuApplet::execute(MenuItem item)
// Presets
case SET_PRESET_LONG_SLOW:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW);
applyLoRaPreset(PRESET(LONG_SLOW));
break;
case SET_PRESET_LONG_MODERATE:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE);
applyLoRaPreset(PRESET(LONG_MODERATE));
break;
case SET_PRESET_LONG_FAST:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST);
applyLoRaPreset(PRESET(LONG_FAST));
break;
case SET_PRESET_MEDIUM_SLOW:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW);
applyLoRaPreset(PRESET(MEDIUM_SLOW));
break;
case SET_PRESET_MEDIUM_FAST:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST);
applyLoRaPreset(PRESET(MEDIUM_FAST));
break;
case SET_PRESET_SHORT_SLOW:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW);
applyLoRaPreset(PRESET(SHORT_SLOW));
break;
case SET_PRESET_SHORT_FAST:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST);
applyLoRaPreset(PRESET(SHORT_FAST));
break;
case SET_PRESET_SHORT_TURBO:
applyLoRaPreset(meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO);
applyLoRaPreset(PRESET(SHORT_TURBO));
break;
case SET_PRESET_FROM_REGION: {
// cursor - 1 because index 0 is "Back"
const uint8_t index = cursor - 1;
if (index < regionPresetCount) {
applyLoRaPreset(regionPresets[index]);
}
break;
}
// Timezones
case SET_TZ_US_HAWAII:
applyTimezone("HST10");
@@ -1421,6 +1444,8 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
items.push_back(MenuItem("US", MenuAction::SET_REGION_US, MenuPage::EXIT));
items.push_back(MenuItem("EU 868", MenuAction::SET_REGION_EU_868, MenuPage::EXIT));
items.push_back(MenuItem("EU 433", MenuAction::SET_REGION_EU_433, MenuPage::EXIT));
items.push_back(MenuItem("EU 866", MenuAction::SET_REGION_EU_866, MenuPage::EXIT));
items.push_back(MenuItem("EU 868 Narrow", MenuAction::SET_REGION_NARROW_868, MenuPage::EXIT));
items.push_back(MenuItem("CN", MenuAction::SET_REGION_CN, MenuPage::EXIT));
items.push_back(MenuItem("JP", MenuAction::SET_REGION_JP, MenuPage::EXIT));
items.push_back(MenuItem("ANZ", MenuAction::SET_REGION_ANZ, MenuPage::EXIT));
@@ -1450,13 +1475,17 @@ void InkHUD::MenuApplet::showPage(MenuPage page)
case NODE_CONFIG_PRESET: {
previousPage = MenuPage::NODE_CONFIG_LORA;
items.push_back(MenuItem("Back", previousPage));
items.push_back(MenuItem("Long Moderate", MenuAction::SET_PRESET_LONG_MODERATE, MenuPage::EXIT));
items.push_back(MenuItem("Long Fast", MenuAction::SET_PRESET_LONG_FAST, MenuPage::EXIT));
items.push_back(MenuItem("Medium Slow", MenuAction::SET_PRESET_MEDIUM_SLOW, MenuPage::EXIT));
items.push_back(MenuItem("Medium Fast", MenuAction::SET_PRESET_MEDIUM_FAST, MenuPage::EXIT));
items.push_back(MenuItem("Short Slow", MenuAction::SET_PRESET_SHORT_SLOW, MenuPage::EXIT));
items.push_back(MenuItem("Short Fast", MenuAction::SET_PRESET_SHORT_FAST, MenuPage::EXIT));
items.push_back(MenuItem("Short Turbo", MenuAction::SET_PRESET_SHORT_TURBO, MenuPage::EXIT));
regionPresetCount = 0;
if (myRegion && myRegion->profile) {
const meshtastic_Config_LoRaConfig_ModemPreset *presets = myRegion->getAvailablePresets();
size_t numPresets = myRegion->getNumPresets();
for (size_t i = 0; i < numPresets && regionPresetCount < MAX_REGION_PRESETS; ++i) {
regionPresets[regionPresetCount++] = presets[i];
const char *name = DisplayFormatters::getModemPresetDisplayName(presets[i], false, true);
nodeConfigLabels.emplace_back(name);
items.push_back(MenuItem(nodeConfigLabels.back().c_str(), MenuAction::SET_PRESET_FROM_REGION, MenuPage::EXIT));
}
}
items.push_back(MenuItem("Exit", MenuPage::EXIT));
break;
}
+5 -4
View File
@@ -53,10 +53,11 @@ void InkHUD::MessageStore::saveToFlash()
f.write(reinterpret_cast<const uint8_t *>(&m.timestamp), sizeof(m.timestamp)); // Write timestamp. 4 bytes
f.write(reinterpret_cast<const uint8_t *>(&m.sender), sizeof(m.sender)); // Write sender NodeId. 4 Bytes
f.write(reinterpret_cast<const uint8_t *>(&m.channelIndex), sizeof(m.channelIndex)); // Write channel index. 1 Byte
f.write(reinterpret_cast<const uint8_t *>(m.text.c_str()), min(MAX_MESSAGE_SIZE, m.text.size())); // Write message text
f.write('\0'); // Append null term
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast<uint32_t>(i), min(MAX_MESSAGE_SIZE, m.text.size()),
m.text.c_str());
f.write(reinterpret_cast<const uint8_t *>(m.text.c_str()),
min((size_t)MAX_MESSAGE_SIZE, m.text.size())); // Write message text
f.write('\0'); // Append null term
LOG_DEBUG("Wrote message %u, length %u, text \"%s\"", static_cast<uint32_t>(i),
min((size_t)MAX_MESSAGE_SIZE, m.text.size()), m.text.c_str());
}
// Release firmware's SPI lock, because SafeFile::close needs it
+2
View File
@@ -13,6 +13,8 @@
#ifdef INPUTBROKER_EXPRESSLRSFIVEWAY_TYPE
// REVISIT esp_adc_cal.h
// "legacy adc calibration driver is deprecated, please migrate to use esp_adc/adc_cali.h and esp_adc/adc_cali_scheme.h"
#include <esp_adc_cal.h>
#include <soc/adc_channel.h>
+2 -2
View File
@@ -68,7 +68,7 @@ TLoraPagerKeyboard::TLoraPagerKeyboard()
: TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(UINT8_MAX),
next_key(UINT8_MAX), last_tap(0L), char_idx(0), tap_interval(0)
{
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
ledcAttach(KB_BL_PIN, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
#else
ledcSetup(LEDC_BACKLIGHT_CHANNEL, LEDC_BACKLIGHT_FREQ, LEDC_BACKLIGHT_BIT_WIDTH);
@@ -108,7 +108,7 @@ void TLoraPagerKeyboard::setBacklight(bool on)
uint32_t _brightness = 0;
if (on)
_brightness = brightness;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#if ESP_ARDUINO_VERSION >= ESP_ARDUINO_VERSION_VAL(3, 0, 0)
ledcWrite(KB_BL_PIN, _brightness);
#else
ledcWrite(LEDC_BACKLIGHT_CHANNEL, _brightness);
+28
View File
@@ -136,6 +136,10 @@ void printPartitionTable()
#include "motion/AccelerometerThread.h"
AccelerometerThread *accelerometerThread = nullptr;
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
#include "motion/MagnetometerThread.h"
MagnetometerThread *magnetometerThread = nullptr;
#endif
#ifdef HAS_I2S
#include "AudioThread.h"
@@ -206,6 +210,8 @@ bool osk_found = false;
ScanI2C::DeviceAddress rtc_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the Accelerometer (if found)
ScanI2C::DeviceAddress accelerometer_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the Magnetometer (if found)
ScanI2C::DeviceAddress magnetometer_found = ScanI2C::ADDRESS_NONE;
// The I2C address of the RGB LED (if found)
ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE, ScanI2C::ADDRESS_NONE);
/// The I2C address of our Air Quality Indicator (if found)
@@ -431,6 +437,11 @@ void setup()
digitalWrite(VEXT_ENABLE, VEXT_ON_VALUE); // turn on the display power
#endif
#if defined(PIN_SENSOR_EN)
pinMode(PIN_SENSOR_EN, OUTPUT);
digitalWrite(PIN_SENSOR_EN, PIN_SENSOR_EN_ACTIVE); // turn on sensor power
#endif
#if defined(BIAS_T_ENABLE)
pinMode(BIAS_T_ENABLE, OUTPUT);
digitalWrite(BIAS_T_ENABLE, BIAS_T_VALUE); // turn on 5V for GPS Antenna
@@ -671,6 +682,11 @@ void setup()
accelerometer_found = acc_info.type != ScanI2C::DeviceType::NONE ? acc_info.address : accelerometer_found;
LOG_DEBUG("acc_info = %i", acc_info.type);
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_MAGNETOMETER
auto mag_info = i2cScanner->firstMagnetometer();
magnetometer_found = mag_info.type != ScanI2C::DeviceType::NONE ? mag_info.address : magnetometer_found;
LOG_DEBUG("mag_info = %i", mag_info.type);
#endif
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA260, meshtastic_TelemetrySensorType_INA260);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::INA226, meshtastic_TelemetrySensorType_INA226);
@@ -683,6 +699,8 @@ void setup()
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMI8658, meshtastic_TelemetrySensorType_QMI8658);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::QMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::HMC5883L, meshtastic_TelemetrySensorType_QMC5883L);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MMC5983MA, meshtastic_TelemetrySensorType_MMC5983MA);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM42607P, meshtastic_TelemetrySensorType_ICM42607P);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MLX90614, meshtastic_TelemetrySensorType_MLX90614);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::ICM20948, meshtastic_TelemetrySensorType_ICM20948);
scannerToSensorsMap(i2cScanner, ScanI2C::DeviceType::MAX30102, meshtastic_TelemetrySensorType_MAX30102);
@@ -753,6 +771,11 @@ void setup()
}
}
#endif
#ifdef OLED_GEOMETRY_OVERRIDE
// Per-variant geometry (e.g. 72x40 micro-OLEDs). Takes precedence over the
// default GEOMETRY_128_64 set at the top of setup().
screen_geometry = OLED_GEOMETRY_OVERRIDE;
#endif
#endif
#if !MESHTASTIC_EXCLUDE_I2C
@@ -761,6 +784,11 @@ void setup()
accelerometerThread = new AccelerometerThread(acc_info.type);
}
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_MAGNETOMETER
if (mag_info.type != ScanI2C::DeviceType::NONE) {
magnetometerThread = new MagnetometerThread(mag_info.type);
}
#endif
#if defined(HAS_NEOPIXEL) || defined(UNPHONE) || defined(RGBLED_RED)
ambientLightingThread = new AmbientLightingThread(ScanI2C::DeviceType::NONE);
+5
View File
@@ -39,6 +39,7 @@ extern bool kb_found;
extern bool osk_found;
extern ScanI2C::DeviceAddress rtc_found;
extern ScanI2C::DeviceAddress accelerometer_found;
extern ScanI2C::DeviceAddress magnetometer_found;
extern ScanI2C::FoundDevice rgb_found;
extern ScanI2C::DeviceAddress aqi_found;
@@ -73,6 +74,10 @@ extern graphics::Screen *screen;
#include "motion/AccelerometerThread.h"
extern AccelerometerThread *accelerometerThread;
#endif
#if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C && !MESHTASTIC_EXCLUDE_MAGNETOMETER
#include "motion/MagnetometerThread.h"
extern MagnetometerThread *magnetometerThread;
#endif
extern bool isVibrating;
+20
View File
@@ -60,6 +60,26 @@ uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t d
return base * coef;
}
uint32_t Default::getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes,
TrafficType type)
{
uint32_t baseMs = getConfiguredOrDefaultMsScaled(configured, defaultValue, numOnlineNodes);
if (!myRegion || !myRegion->profile)
return baseMs;
int8_t throttle =
(type == TrafficType::POSITION) ? myRegion->profile->positionThrottle : myRegion->profile->telemetryThrottle;
// throttle <= 0 means unset; 1 is the neutral multiplier — skip the multiply for performance
if (throttle <= 1)
return baseMs;
constexpr uint32_t MAX_MS = static_cast<uint32_t>(INT32_MAX);
uint64_t result = static_cast<uint64_t>(baseMs) * static_cast<uint64_t>(throttle);
return result >= static_cast<uint64_t>(MAX_MS) ? MAX_MS : static_cast<uint32_t>(result);
}
uint32_t Default::getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue)
{
// If zero, intervals should be coalesced later by getConfiguredOrDefault... methods
+4
View File
@@ -31,6 +31,8 @@
#define min_neighbor_info_broadcast_secs 4 * 60 * 60
#define default_map_publish_interval_secs 60 * 60
enum class TrafficType { POSITION, TELEMETRY };
// Traffic management defaults
#define default_traffic_mgmt_position_precision_bits 24 // ~10m grid cells
#define default_traffic_mgmt_position_min_interval_secs (ONE_DAY / 2) // 12 hours between identical positions
@@ -64,6 +66,8 @@ class Default
// Note: numOnlineNodes uses uint32_t to match the public API and allow flexibility,
// even though internal node counts use uint16_t (max 65535 nodes)
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes);
static uint32_t getConfiguredOrDefaultMsScaled(uint32_t configured, uint32_t defaultValue, uint32_t numOnlineNodes,
TrafficType type);
static uint8_t getConfiguredOrDefaultHopLimit(uint8_t configured);
static uint32_t getConfiguredOrMinimumValue(uint32_t configured, uint32_t minValue);
+6
View File
@@ -368,4 +368,10 @@ template <typename T> bool LR11x0Interface<T>::sleep()
return true;
}
template <typename T> int16_t LR11x0Interface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI(false, true);
return (int16_t)round(rssi);
}
#endif
+2
View File
@@ -37,6 +37,8 @@ template <class T> class LR11x0Interface : public RadioLibInterface
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
+6 -22
View File
@@ -136,17 +136,6 @@ template <typename T> bool LR20x0Interface<T>::init()
if (res == RADIOLIB_ERR_CHIP_NOT_FOUND || res == RADIOLIB_ERR_SPI_CMD_FAILED)
return false;
// Some basic info about the module's explicit firmware version - no other info available
// Currently requires radiolib godmode
#if RADIOLIB_GODMODE
uint8_t fwMajor = 0;
uint8_t fwMinor = 0;
int versionRes = lora.getVersion(&fwMajor, &fwMinor);
if (versionRes == RADIOLIB_ERR_NONE)
LOG_DEBUG("LR20x0 FW %d.%d", fwMajor, fwMinor);
#endif
LOG_INFO("Frequency set to %f", getFreq());
LOG_INFO("Bandwidth set to %f", bw);
LOG_INFO("Power output set to %d", power);
@@ -154,17 +143,6 @@ template <typename T> bool LR20x0Interface<T>::init()
if (res == RADIOLIB_ERR_NONE)
res = lora.setCRC(2);
// Standard DCDC ramp timing from RadioLib workarounds (register 0x00F20024)
// Currently requires radiolib godmode
#if RADIOLIB_GODMODE
if (res == RADIOLIB_ERR_NONE) {
uint8_t rampTimes[4] = {15, 15, 15, 15}; // Standard case for all conditions
res = lora.setRegMode(RADIOLIB_LR2021_REG_MODE_SIMO_NORMAL, rampTimes);
if (res != RADIOLIB_ERR_NONE)
LOG_WARN("LR2021 setRegMode failed: %d", res);
}
#endif
#ifdef LR2021_DIO_AS_RF_SWITCH
bool dioAsRfSwitch = true;
#elif defined(ARCH_PORTDUINO)
@@ -396,4 +374,10 @@ template <typename T> bool LR20x0Interface<T>::sleep()
return true;
}
template <typename T> int16_t LR20x0Interface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI(false, true);
return (int16_t)round(rssi);
}
#endif
+2
View File
@@ -37,6 +37,8 @@ template <class T> class LR20x0Interface : public RadioLibInterface
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
+49 -13
View File
@@ -13,9 +13,16 @@
static const meshtastic_Config_LoRaConfig_ModemPreset MODEM_PRESET_END =
static_cast<meshtastic_Config_LoRaConfig_ModemPreset>(0xFF);
#define PRESET(name) meshtastic_Config_LoRaConfig_ModemPreset_##name
// Override slot magic numbers for RegionProfile.overrideSlot
#define OVERRIDE_SLOT_DEFAULT_CHANNEL_HASH 0 // Use hash of primary channel name
#define OVERRIDE_SLOT_PRESET_HASH -1 // Use hash of preset name instead
// Positive values (1-32767) are explicit slot numbers
// Region profile: bundles the preset list with regulatory parameters shared across regions
struct RegionProfile {
const meshtastic_Config_LoRaConfig_ModemPreset *presets; // sentinel-terminated; first entry is the default
const meshtastic_Config_LoRaConfig_ModemPreset *presets; // sentinel-terminated
float spacing; // gaps between radio channels
float padding; // padding at each side of the "operating channel"
bool audioPermitted;
@@ -23,14 +30,22 @@ struct RegionProfile {
int8_t textThrottle; // throttle for text - future expansion
int8_t positionThrottle; // throttle for location data - future expansion
int8_t telemetryThrottle; // throttle for telemetry - future expansion
uint8_t overrideSlot; // a per-region override slot for if we need to fix it in place
int16_t overrideSlot; // a per-region override slot for if we need to fix it in place
// Magic values: 0 = use channel name hash, -1 = use preset name hash, >0 = explicit slot
};
/**
* Get the effective duty cycle for the current region based on device role.
* For EU_866, returns 10% for fixed devices (ROUTER, ROUTER_LATE) and 2.5% for mobile devices.
* For other regions, returns the standard duty cycle.
*/
extern float getEffectiveDutyCycle();
extern const RegionProfile PROFILE_STD;
extern const RegionProfile PROFILE_EU868;
extern const RegionProfile PROFILE_UNDEF;
// extern const RegionProfile PROFILE_LITE;
// extern const RegionProfile PROFILE_NARROW;
extern const RegionProfile PROFILE_LITE;
extern const RegionProfile PROFILE_NARROW;
// extern const RegionProfile PROFILE_HAM;
// Map from old region names to new region enums
@@ -43,10 +58,11 @@ struct RegionInfo {
bool freqSwitching;
bool wideLora;
const RegionProfile *profile;
meshtastic_Config_LoRaConfig_ModemPreset defaultPreset;
const char *name; // EU433 etc
// Preset accessors (delegate through profile)
meshtastic_Config_LoRaConfig_ModemPreset getDefaultPreset() const { return profile->presets[0]; }
meshtastic_Config_LoRaConfig_ModemPreset getDefaultPreset() const { return defaultPreset; }
const meshtastic_Config_LoRaConfig_ModemPreset *getAvailablePresets() const { return profile->presets; }
size_t getNumPresets() const
{
@@ -143,46 +159,66 @@ static inline void modemPresetToParams(meshtastic_Config_LoRaConfig_ModemPreset
uint8_t &cr)
{
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
case PRESET(SHORT_TURBO):
bwKHz = wideLora ? 1625.0f : 500.0f;
cr = 5;
sf = 7;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case PRESET(SHORT_FAST):
bwKHz = wideLora ? 812.5f : 250.0f;
cr = 5;
sf = 7;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case PRESET(SHORT_SLOW):
bwKHz = wideLora ? 812.5f : 250.0f;
cr = 5;
sf = 8;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
case PRESET(MEDIUM_FAST):
bwKHz = wideLora ? 812.5f : 250.0f;
cr = 5;
sf = 9;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case PRESET(MEDIUM_SLOW):
bwKHz = wideLora ? 812.5f : 250.0f;
cr = 5;
sf = 10;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO:
case PRESET(LONG_TURBO):
bwKHz = wideLora ? 1625.0f : 500.0f;
cr = 8;
sf = 11;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case PRESET(LONG_MODERATE):
bwKHz = wideLora ? 406.25f : 125.0f;
cr = 8;
sf = 11;
break;
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case PRESET(LONG_SLOW):
bwKHz = wideLora ? 406.25f : 125.0f;
cr = 8;
sf = 12;
break;
case PRESET(LITE_FAST):
bwKHz = 125;
cr = 5;
sf = 9;
break;
case PRESET(LITE_SLOW):
bwKHz = 125;
cr = 5;
sf = 10;
break;
case PRESET(NARROW_FAST):
bwKHz = 62.5f;
cr = 6;
sf = 7;
break;
case PRESET(NARROW_SLOW):
bwKHz = 62.5f;
cr = 6;
sf = 8;
break;
default: // LONG_FAST (or illegal)
bwKHz = wideLora ? 812.5f : 250.0f;
cr = 5;
+7 -2
View File
@@ -985,6 +985,10 @@ void NodeDB::installDefaultConfig(bool preserveKey = false)
config.network.wifi_enabled = USERPREFS_NETWORK_WIFI_ENABLED;
#endif
#if USE_ETHERNET_DEFAULT
config.network.eth_enabled = true;
#endif
#ifdef USERPREFS_NETWORK_WIFI_SSID
strncpy(config.network.wifi_ssid, USERPREFS_NETWORK_WIFI_SSID, sizeof(config.network.wifi_ssid));
#endif
@@ -1067,7 +1071,8 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.has_store_forward = true;
moduleConfig.has_telemetry = true;
moduleConfig.has_external_notification = true;
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION)
#if defined(PIN_BUZZER) || defined(PIN_VIBRATION) || defined(LED_NOTIFICATION) || defined(PCA_LED_NOTIFICATION) || \
defined(NEOPIXEL_STATUS_NOTIFICATION_PIN)
moduleConfig.external_notification.enabled = true;
#endif
#if defined(PIN_BUZZER)
@@ -1088,7 +1093,7 @@ void NodeDB::installDefaultModuleConfig()
#endif
#if defined(PIN_VIBRATION)
moduleConfig.external_notification.nag_timeout = 2;
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION)
#elif defined(PIN_BUZZER) || defined(LED_NOTIFICATION) || defined(NEOPIXEL_STATUS_NOTIFICATION_PIN)
moduleConfig.external_notification.nag_timeout = default_ringtone_nag_secs;
#endif
+1 -1
View File
@@ -132,7 +132,7 @@ inline bool shouldDropPacketForPreHop(const meshtastic_MeshPacket &p)
if (isFromUs(&p)) {
return false; // local-originated packets should never be dropped by pre-hop drop policy
}
return classifyHopStart(p) != HopStartStatus::VALID;
return classifyHopStart(p) == HopStartStatus::INVALID;
#endif
}
+9 -7
View File
@@ -4,17 +4,19 @@
#include <Arduino.h>
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
uint32_t getPositionPrecisionForChannel(const meshtastic_Channel &channel)
{
const meshtastic_Channel &channel = channels.getByIndex(channelIndex);
if (channel.settings.has_module_settings) {
return channel.settings.module_settings.position_precision;
} else if (channel.role == meshtastic_Channel_Role_PRIMARY) {
return 32;
} else {
return 0;
}
// No module settings: fail closed. A PRIMARY channel used to default to 32
// here, leaking an exact position on a sharing-disabled channel. See #10509.
return 0;
}
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex)
{
return getPositionPrecisionForChannel(channels.getByIndex(channelIndex));
}
static int32_t truncateCoordinate(int32_t coordinate, uint32_t precision)
+2
View File
@@ -1,8 +1,10 @@
#pragma once
#include "meshtastic/channel.pb.h"
#include "meshtastic/mesh.pb.h"
#include <stdint.h>
uint32_t getPositionPrecisionForChannel(const meshtastic_Channel &channel);
uint32_t getPositionPrecisionForChannel(uint8_t channelIndex);
void applyPositionPrecision(meshtastic_Position &position, uint32_t precision);
bool applyPositionPrecision(meshtastic_MeshPacket &packet, uint32_t precision);
+6
View File
@@ -341,4 +341,10 @@ bool RF95Interface::sleep()
return true;
}
int16_t RF95Interface::getCurrentRSSI()
{
float rssi = lora->getRSSI(false);
return (int16_t)round(rssi);
}
#endif
+2
View File
@@ -37,6 +37,8 @@ class RF95Interface : public RadioLibInterface
*/
virtual void disableInterrupt() override;
int16_t getCurrentRSSI() override;
/**
* Enable a particular ISR callback glue function
*/
+102 -58
View File
@@ -35,31 +35,32 @@
#endif
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_STD[] = {
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW,
meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_TURBO, MODEM_PRESET_END};
PRESET(LONG_FAST), PRESET(LONG_SLOW), PRESET(MEDIUM_SLOW), PRESET(MEDIUM_FAST), PRESET(SHORT_SLOW),
PRESET(SHORT_FAST), PRESET(LONG_MODERATE), PRESET(SHORT_TURBO), PRESET(LONG_TURBO), MODEM_PRESET_END};
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_EU_868[] = {
meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST, meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW,
meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW, meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST,
meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE, MODEM_PRESET_END};
PRESET(LONG_FAST), PRESET(LONG_SLOW), PRESET(MEDIUM_SLOW), PRESET(MEDIUM_FAST),
PRESET(SHORT_SLOW), PRESET(SHORT_FAST), PRESET(LONG_MODERATE), MODEM_PRESET_END};
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_UNDEF[] = {meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST,
MODEM_PRESET_END};
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_UNDEF[] = {PRESET(LONG_FAST), MODEM_PRESET_END};
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_LITE[] = {PRESET(LITE_FAST), PRESET(LITE_SLOW), MODEM_PRESET_END};
static const meshtastic_Config_LoRaConfig_ModemPreset PRESETS_NARROW[] = {PRESET(NARROW_FAST), PRESET(NARROW_SLOW),
MODEM_PRESET_END};
// Region profiles: bundle preset list + regulatory parameters shared across regions
// presets, spacing, padding, audio, licensed, text throttle, position throttle, telemetry throttle, override slot
const RegionProfile PROFILE_STD = {PRESETS_STD, 0, 0, true, false, 0, 0, 0, 0};
const RegionProfile PROFILE_EU868 = {PRESETS_EU_868, 0, 0, false, false, 0, 0, 0, 0};
const RegionProfile PROFILE_UNDEF = {PRESETS_UNDEF, 0, 0, true, false, 0, 0, 0, 0};
const RegionProfile PROFILE_STD = {PRESETS_STD, 0, 0, true, false, 0, 1, 1, 0};
const RegionProfile PROFILE_EU868 = {PRESETS_EU_868, 0, 0, false, false, 0, 1, 1, 0};
const RegionProfile PROFILE_UNDEF = {PRESETS_UNDEF, 0, 0, true, false, 0, 1, 1, 0};
const RegionProfile PROFILE_LITE = {PRESETS_LITE, 0.4, 0.0375f, false, false, 0, 10, 10, 0};
const RegionProfile PROFILE_NARROW = {PRESETS_NARROW, 0, 0.0104f, true, false, 0, 1, 1, 1};
#define RDEF(name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, wide_lora, profile_ptr) \
#define RDEF(name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, wide_lora, profile_ptr, default_preset) \
{ \
meshtastic_Config_LoRaConfig_RegionCode_##name, freq_start, freq_end, duty_cycle, power_limit, frequency_switching, \
wide_lora, &profile_ptr, #name \
wide_lora, &profile_ptr, default_preset, #name \
}
const RegionInfo regions[] = {
@@ -67,7 +68,7 @@ const RegionInfo regions[] = {
https://link.springer.com/content/pdf/bbm%3A978-1-4842-4357-2%2F1.pdf
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
*/
RDEF(US, 902.0f, 928.0f, 100, 30, false, false, PROFILE_STD),
RDEF(US, 902.0f, 928.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
EN300220 ETSI V3.2.1 [Table B.1, Item H, p. 21]
@@ -75,7 +76,7 @@ const RegionInfo regions[] = {
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.02.01_60/en_30022002v030201p.pdf
FIXME: https://github.com/meshtastic/firmware/issues/3371
*/
RDEF(EU_433, 433.0f, 434.0f, 10, 10, false, false, PROFILE_STD),
RDEF(EU_433, 433.0f, 434.0f, 10, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://www.thethingsnetwork.org/docs/lorawan/duty-cycle/
https://www.thethingsnetwork.org/docs/lorawan/regional-parameters/
@@ -90,33 +91,33 @@ const RegionInfo regions[] = {
AFA) to avoid a duty cycle. (Please refer to line P page 22 of this document.)
https://www.etsi.org/deliver/etsi_en/300200_300299/30022002/03.01.01_60/en_30022002v030101p.pdf
*/
RDEF(EU_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_EU868),
RDEF(EU_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_EU868, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
*/
RDEF(CN, 470.0f, 510.0f, 100, 19, false, false, PROFILE_STD),
RDEF(CN, 470.0f, 510.0f, 100, 19, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://www.arib.or.jp/english/html/overview/doc/5-STD-T108v1_5-E1.pdf
https://qiita.com/ammo0613/items/d952154f1195b64dc29f
*/
RDEF(JP, 920.5f, 923.5f, 100, 13, false, false, PROFILE_STD),
RDEF(JP, 920.5f, 923.5f, 100, 13, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://www.iot.org.au/wp/wp-content/uploads/2016/12/IoTSpectrumFactSheet.pdf
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
Also used in Brazil.
*/
RDEF(ANZ, 915.0f, 928.0f, 100, 30, false, false, PROFILE_STD),
RDEF(ANZ, 915.0f, 928.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
433.05 - 434.79 MHz, 25mW EIRP max, No duty cycle restrictions
AU Low Interference Potential https://www.acma.gov.au/licences/low-interference-potential-devices-lipd-class-licence
NZ General User Radio Licence for Short Range Devices https://gazette.govt.nz/notice/id/2022-go3100
*/
RDEF(ANZ_433, 433.05f, 434.79f, 100, 14, false, false, PROFILE_STD),
RDEF(ANZ_433, 433.05f, 434.79f, 100, 14, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://digital.gov.ru/uploaded/files/prilozhenie-12-k-reshenyu-gkrch-18-46-03-1.pdf
@@ -124,13 +125,13 @@ const RegionInfo regions[] = {
Note:
- We do LBT, so 100% is allowed.
*/
RDEF(RU, 868.7f, 869.2f, 100, 20, false, false, PROFILE_STD),
RDEF(RU, 868.7f, 869.2f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://www.law.go.kr/LSW/admRulLsInfoP.do?admRulId=53943&efYd=0
https://resources.lora-alliance.org/technical-specifications/rp002-1-0-4-regional-parameters
*/
RDEF(KR, 920.0f, 923.0f, 100, 23, false, false, PROFILE_STD),
RDEF(KR, 920.0f, 923.0f, 100, 23, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Taiwan, 920-925Mhz, limited to 0.5W indoor or coastal, 1.0W outdoor.
@@ -138,40 +139,40 @@ const RegionInfo regions[] = {
https://www.ncc.gov.tw/english/files/23070/102_5190_230703_1_doc_C.PDF
https://gazette.nat.gov.tw/egFront/e_detail.do?metaid=147283
*/
RDEF(TW, 920.0f, 925.0f, 100, 27, false, false, PROFILE_STD),
RDEF(TW, 920.0f, 925.0f, 100, 27, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
*/
RDEF(IN, 865.0f, 867.0f, 100, 30, false, false, PROFILE_STD),
RDEF(IN, 865.0f, 867.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://rrf.rsm.govt.nz/smart-web/smart/page/-smart/domain/licence/LicenceSummary.wdk?id=219752
https://iotalliance.org.nz/wp-content/uploads/sites/4/2019/05/IoT-Spectrum-in-NZ-Briefing-Paper.pdf
*/
RDEF(NZ_865, 864.0f, 868.0f, 100, 36, false, false, PROFILE_STD),
RDEF(NZ_865, 864.0f, 868.0f, 100, 36, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
https://lora-alliance.org/wp-content/uploads/2020/11/lorawan_regional_parameters_v1.0.3reva_0.pdf
https://standard.nbtc.go.th/getattachment/Standards/%E0%B8%A1%E0%B8%B2%E0%B8%95%E0%B8%A3%E0%B8%90%E0%B8%B2%E0%B8%99%E0%B8%97%E0%B8%B2%E0%B8%87%E0%B9%80%E0%B8%97%E0%B8%84%E0%B8%99%E0%B8%B4%E0%B8%84%E0%B8%82%E0%B8%AD%E0%B8%87%E0%B9%80%E0%B8%84%E0%B8%A3%E0%B8%B7%E0%B9%88%E0%B8%AD%E0%B8%87%E0%B9%82%E0%B8%97%E0%B8%A3%E0%B8%84%E0%B8%A1%E0%B8%99%E0%B8%B2%E0%B8%84%E0%B8%A1/1033-2565.pdf.aspx?lang=th-TH
Thailand 920925 MHz set max TX power to 27 dBm and enforce 10% duty cycle, aligned with NBTC regulations.
*/
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD),
RDEF(TH, 920.0f, 925.0f, 10, 27, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
433,05-434,7 Mhz 10 mW
868,0-868,6 Mhz 25 mW
https://nkrzi.gov.ua/images/upload/256/5810/PDF_UUZ_19_01_2016.pdf
*/
RDEF(UA_433, 433.0f, 434.7f, 10, 10, false, false, PROFILE_STD),
RDEF(UA_868, 868.0f, 868.6f, 1, 14, false, false, PROFILE_STD),
RDEF(UA_433, 433.0f, 434.7f, 10, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(UA_868, 868.0f, 868.6f, 1, 14, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Malaysia
433 - 435 MHz at 100mW, no restrictions.
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
*/
RDEF(MY_433, 433.0f, 435.0f, 100, 20, false, false, PROFILE_STD),
RDEF(MY_433, 433.0f, 435.0f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Malaysia
@@ -180,14 +181,14 @@ const RegionInfo regions[] = {
Frequency hopping is used for 919 - 923 MHz.
https://www.mcmc.gov.my/skmmgovmy/media/General/pdf/Short-Range-Devices-Specification.pdf
*/
RDEF(MY_919, 919.0f, 924.0f, 100, 27, true, false, PROFILE_STD),
RDEF(MY_919, 919.0f, 924.0f, 100, 27, true, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Singapore
SG_923 Band 30d: 917 - 925 MHz at 100mW, no restrictions.
https://www.imda.gov.sg/-/media/imda/files/regulation-licensing-and-consultations/ict-standards/telecommunication-standards/radio-comms/imdatssrd.pdf
*/
RDEF(SG_923, 917.0f, 925.0f, 100, 20, false, false, PROFILE_STD),
RDEF(SG_923, 917.0f, 925.0f, 100, 20, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Philippines
@@ -197,9 +198,9 @@ const RegionInfo regions[] = {
https://github.com/meshtastic/firmware/issues/4948#issuecomment-2394926135
*/
RDEF(PH_433, 433.0f, 434.7f, 100, 10, false, false, PROFILE_STD),
RDEF(PH_868, 868.0f, 869.4f, 100, 14, false, false, PROFILE_STD),
RDEF(PH_915, 915.0f, 918.0f, 100, 24, false, false, PROFILE_STD),
RDEF(PH_433, 433.0f, 434.7f, 100, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(PH_868, 868.0f, 869.4f, 100, 14, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(PH_915, 915.0f, 918.0f, 100, 24, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Kazakhstan
@@ -207,32 +208,46 @@ const RegionInfo regions[] = {
863 - 868 MHz <25 mW EIRP, 500kHz channels allowed, must not be used at airfields
https://github.com/meshtastic/firmware/issues/7204
*/
RDEF(KZ_433, 433.075f, 434.775f, 100, 10, false, false, PROFILE_STD),
RDEF(KZ_863, 863.0f, 868.0f, 100, 30, false, false, PROFILE_STD),
RDEF(KZ_433, 433.075f, 434.775f, 100, 10, false, false, PROFILE_STD, PRESET(LONG_FAST)),
RDEF(KZ_863, 863.0f, 868.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Nepal
865MHz to 868MHz frequency band for IoT (Internet of Things), M2M (Machine-to-Machine), and smart metering use,
specifically in non-cellular mode. https://www.nta.gov.np/uploads/contents/Radio-Frequency-Policy-2080-English.pdf
*/
RDEF(NP_865, 865.0f, 868.0f, 100, 30, false, false, PROFILE_STD),
RDEF(NP_865, 865.0f, 868.0f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
Brazil
902 - 907.5 MHz , 1W power limit, no duty cycle restrictions
https://github.com/meshtastic/firmware/issues/3741
*/
RDEF(BR_902, 902.0f, 907.5f, 100, 30, false, false, PROFILE_STD),
RDEF(BR_902, 902.0f, 907.5f, 100, 30, false, false, PROFILE_STD, PRESET(LONG_FAST)),
/*
2.4 GHZ WLAN Band equivalent. Only for SX128x chips.
*/
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 10, false, true, PROFILE_STD),
RDEF(LORA_24, 2400.0f, 2483.5f, 100, 10, false, true, PROFILE_STD, PRESET(LONG_FAST)),
/*
EU 866MHz band (Band no. 46b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD)
Gives 4 channels at 865.7/866.3/866.9/867.5 MHz, 400 kHz gap plus 37.5 kHz padding between channels, 27 dBm,
duty cycle 2.5% (mobile) or 10% (fixed) https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:02006D0771(01)-20250123
*/
RDEF(EU_866, 865.6f, 867.6f, 2.5, 27, false, false, PROFILE_LITE, PRESET(LITE_FAST)),
/*
EU 868MHz band: 3 channels at 869.410/869.4625/869.577 MHz
Channel centres at 869.442/869.525/869.608 MHz,
10.4 kHz padding on channels, 27 dBm, duty cycle 10%
*/
RDEF(EU_N_868, 869.4f, 869.65f, 10, 27, false, false, PROFILE_NARROW, PRESET(NARROW_SLOW)),
/*
This needs to be last. Same as US.
*/
RDEF(UNSET, 902.0f, 928.0f, 100, 30, false, false, PROFILE_UNDEF)
RDEF(UNSET, 902.0f, 928.0f, 100, 30, false, false, PROFILE_UNDEF, PRESET(LONG_FAST)),
};
@@ -546,6 +561,23 @@ const RegionInfo *getRegion(meshtastic_Config_LoRaConfig_RegionCode code)
return r;
}
/**
* Get duty cycle for current region. EU_866: 10% for routers, 2.5% for mobile.
*/
float getEffectiveDutyCycle()
{
if (myRegion->code == meshtastic_Config_LoRaConfig_RegionCode_EU_866) {
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER ||
config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE) {
return 10.0f;
} else {
return 2.5f;
}
}
// For all other regions, return the standard duty cycle
return myRegion->dutyCycle;
}
uint32_t RadioInterface::getPacketTime(const meshtastic_MeshPacket *p, bool received)
{
uint32_t pl = 0;
@@ -897,12 +929,15 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo
if (loraConfig.override_frequency == 0) {
// Check if we use the default frequency slot
// overrideSlot: 0 = channel hash, -1 = preset hash, >0 = explicit slot
uses_default_frequency_slot =
(loraConfig.channel_num == 0) || // user choice unset, no frequency override, so use default
(newRegion->profile->overrideSlot != 0 &&
loraConfig.channel_num == newRegion->profile->overrideSlot) || // user setting matches override
((newRegion->profile->overrideSlot == 0) &&
((uint32_t)(loraConfig.channel_num - 1) == presetNameHashSlot)); // user setting matches preset hash, no override
(newRegion->profile->overrideSlot > 0 &&
loraConfig.channel_num == newRegion->profile->overrideSlot) || // user setting matches explicit override slot
((newRegion->profile->overrideSlot == OVERRIDE_SLOT_DEFAULT_CHANNEL_HASH) &&
((uint32_t)(loraConfig.channel_num - 1) == channelNameHashSlot)) || // user setting matches channel name hash
((newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) &&
((uint32_t)(loraConfig.channel_num - 1) == presetNameHashSlot)); // user setting matches preset name hash
// check if user setting different to preset name
uses_custom_channel_name = (strcmp(channelName, presetNameDisplay) != 0);
@@ -917,10 +952,14 @@ bool RadioInterface::checkOrClampConfigLora(meshtastic_Config_LoRaConfig &loraCo
if (clamp) {
if (uses_custom_channel_name) { // clamp to channel name hash
loraConfig.channel_num =
channelNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
} else if ((loraConfig.use_preset) && (newRegion->profile->overrideSlot != 0)) { // clamp to preset override slot
channelNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
} else if (newRegion->profile->overrideSlot > 0) { // clamp to explicit override slot
loraConfig.channel_num =
newRegion->profile->overrideSlot; // use the override slot specified by the region profile
newRegion->profile->overrideSlot; // use the explicit override slot specified by the region profile
uses_default_frequency_slot = true;
} else if (newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH && loraConfig.use_preset) {
// clamp to preset name hash
loraConfig.channel_num = presetNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
uses_default_frequency_slot = true;
} else if (loraConfig.use_preset) { // clamp to preset slot
loraConfig.channel_num = presetNameHashSlot + 1; // channel_num is 1-based, but hash slot is 0-based, so add 1
@@ -1018,6 +1057,8 @@ void RadioInterface::applyModemConfig()
// Calculate hash of channel name and preset name to pick a default frequency slot if user has not specified one.
// Note that channel_num is actually (channel_num - 1), i.e. zero-based, since modulus (%) returns values from 0 to
// (numFreqSlots - 1).
const char *channelName = channels.getName(channels.getPrimaryIndex());
uint32_t channelNameHashSlot = hash(channelName) % numFreqSlots;
uint32_t presetNameHashSlot =
hash(DisplayFormatters::getModemPresetDisplayName(loraConfig.modem_preset, false, loraConfig.use_preset)) % numFreqSlots;
@@ -1034,11 +1075,13 @@ void RadioInterface::applyModemConfig()
// (channel_num - 1), i.e. zero-based, since modulus (%) returns values from 0 to (numFreqSlots - 1).
// NB: channel_num is also know as frequency slot but it's too late to fix now.
if (uses_default_frequency_slot) {
// if there's an override slot, use that
if (newRegion->profile->overrideSlot != 0) {
channel_num = newRegion->profile->overrideSlot - 1;
// Handle three override slot cases: explicit slot (>0), preset hash (-1), or channel hash (0)
if (newRegion->profile->overrideSlot > 0) {
channel_num = newRegion->profile->overrideSlot - 1; // explicit override slot (1-based to 0-based)
} else if (newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) {
channel_num = presetNameHashSlot; // use preset name hash
} else {
channel_num = presetNameHashSlot;
channel_num = channelNameHashSlot; // use channel name hash (default case)
}
} else { // use the manually defined one
channel_num = loraConfig.channel_num - 1;
@@ -1051,7 +1094,6 @@ void RadioInterface::applyModemConfig()
saveChannelNum(channel_num);
saveFreq(freq + loraConfig.frequency_offset);
const char *channelName = channels.getName(channels.getPrimaryIndex());
if (newRegion->wideLora) { // clamp if wide freq range
preambleLength = wideLoraPreambleLengthDefault; // 12 is the default for operation above 2GHz
@@ -1068,9 +1110,11 @@ void RadioInterface::applyModemConfig()
channel_num, power);
LOG_INFO("newRegion->freqStart -> newRegion->freqEnd: %f -> %f (%f MHz)", newRegion->freqStart, newRegion->freqEnd,
newRegion->freqEnd - newRegion->freqStart);
LOG_INFO("numFreqSlots: %d x %.3fkHz", numFreqSlots, bw);
if (newRegion->profile->overrideSlot != 0) {
LOG_INFO("Using region override slot: %d", newRegion->profile->overrideSlot);
LOG_INFO("numFreqSlots: %u x %.3fkHz", numFreqSlots, bw);
if (newRegion->profile->overrideSlot > 0) {
LOG_INFO("Using region explicit override slot: %d", newRegion->profile->overrideSlot);
} else if (newRegion->profile->overrideSlot == OVERRIDE_SLOT_PRESET_HASH) {
LOG_INFO("Using region preset name hash for slot selection");
}
LOG_INFO("channel_num: %d", channel_num + 1);
LOG_INFO("frequency: %f", getFreq());
+6
View File
@@ -283,6 +283,12 @@ class RadioInterface
*/
virtual void saveChannelNum(uint32_t savedChannelNum);
/**
* Get current RSSI reading from the radio.
* Returns 0 if not available.
*/
virtual int16_t getCurrentRSSI() { return 0; }
private:
/**
* Convert our modemConfig enum into wf, sf, etc...
+90 -5
View File
@@ -15,6 +15,7 @@
#include "PortduinoGlue.h"
#include "meshUtils.h"
#endif
void LockingArduinoHal::spiBeginTransaction()
{
spiLock->lock();
@@ -28,6 +29,7 @@ void LockingArduinoHal::spiEndTransaction()
spiLock->unlock();
}
#if ARCH_PORTDUINO
void LockingArduinoHal::spiTransfer(uint8_t *out, size_t len, uint8_t *in)
{
@@ -40,6 +42,12 @@ RadioLibInterface::RadioLibInterface(LockingArduinoHal *hal, RADIOLIB_PIN_TYPE c
: NotifiedWorkerThread("RadioIf"), module(hal, cs, irq, rst, busy), iface(_iface)
{
instance = this;
// Initialize unused sample slots to a sane default; sample count controls averaging.
for (uint8_t i = 0; i < NOISE_FLOOR_SAMPLES; i++) {
noiseFloorSamples[i] = NOISE_FLOOR_DEFAULT;
}
#if defined(ARCH_STM32WL) && defined(USE_SX1262)
module.setCb_digitalWrite(stm32wl_emulate_digitalWrite);
module.setCb_digitalRead(stm32wl_emulate_digitalRead);
@@ -246,6 +254,87 @@ bool RadioLibInterface::findInTxQueue(NodeNum from, PacketId id)
return txQueue.find(from, id);
}
void RadioLibInterface::updateNoiseFloor()
{
// Only sample from idle receive mode. TX/RX-critical paths must return to radio work quickly.
if (!isReceiving || sendingPacket != NULL || isActivelyReceiving() || isIRQPending()) {
return;
}
uint32_t now = millis();
if (now - lastNoiseFloorUpdate < NOISE_FLOOR_UPDATE_INTERVAL_MS) {
return;
}
lastNoiseFloorUpdate = now;
int16_t rssi = getCurrentRSSI();
if (rssi == NOISE_FLOOR_INVALID || rssi >= 0 || rssi < NOISE_FLOOR_VALID_MIN) {
LOG_DEBUG("Skipping invalid RSSI reading: %d", rssi);
return;
}
noiseFloorSamples[currentSampleIndex] = (int32_t)rssi;
currentSampleIndex++;
if (currentSampleIndex >= NOISE_FLOOR_SAMPLES) {
currentSampleIndex = 0;
isNoiseFloorBufferFull = true;
}
currentNoiseFloor = getAverageNoiseFloorInternal();
LOG_DEBUG("Noise floor: %d dBm (samples: %d, latest: %d dBm)", currentNoiseFloor, getNoiseFloorSampleCountInternal(), rssi);
}
uint8_t RadioLibInterface::getNoiseFloorSampleCountInternal() const
{
return isNoiseFloorBufferFull ? NOISE_FLOOR_SAMPLES : currentSampleIndex;
}
int32_t RadioLibInterface::getAverageNoiseFloorInternal() const
{
uint8_t sampleCount = getNoiseFloorSampleCountInternal();
if (sampleCount == 0) {
return NOISE_FLOOR_DEFAULT;
}
int32_t sum = 0;
for (uint8_t i = 0; i < sampleCount; i++) {
sum += noiseFloorSamples[i];
}
return sum / sampleCount;
}
int32_t RadioLibInterface::getAverageNoiseFloor()
{
return getAverageNoiseFloorInternal();
}
int32_t RadioLibInterface::getNoiseFloor()
{
return currentNoiseFloor;
}
bool RadioLibInterface::hasNoiseFloorSamples()
{
return getNoiseFloorSampleCountInternal() > 0;
}
uint8_t RadioLibInterface::getNoiseFloorSampleCount()
{
return getNoiseFloorSampleCountInternal();
}
void RadioLibInterface::resetNoiseFloor()
{
currentSampleIndex = 0;
isNoiseFloorBufferFull = false;
currentNoiseFloor = NOISE_FLOOR_DEFAULT;
LOG_INFO("Noise floor reset - rolling window collection will restart");
}
bool RadioLibInterface::randomBytes(uint8_t *buffer, size_t length)
{
if (!buffer || length == 0 || !iface) {
@@ -273,6 +362,7 @@ currently active.
*/
void RadioLibInterface::onNotify(uint32_t notification)
{
switch (notification) {
case ISR_TX:
handleTransmitInterrupt();
@@ -404,11 +494,6 @@ bool RadioLibInterface::removePendingTXPacket(NodeNum from, PacketId id, uint32_
return false;
}
/**
* Remove a packet that is eligible for replacement from the TX queue
*/
// void RadioLibInterface::removePending
void RadioLibInterface::handleTransmitInterrupt()
{
// This can be null if we forced the device to enter standby mode. In that case
+58
View File
@@ -99,6 +99,26 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/// are _trying_ to receive a packet currently (note - we might just be waiting for one)
bool isReceiving = false;
protected:
// Noise floor tracking - rolling window of samples.
static const uint8_t NOISE_FLOOR_SAMPLES = 20;
static const int32_t NOISE_FLOOR_DEFAULT = -120;
static const int32_t NOISE_FLOOR_VALID_MIN = -127;
static const int32_t NOISE_FLOOR_INVALID = -128;
int32_t noiseFloorSamples[NOISE_FLOOR_SAMPLES];
uint8_t currentSampleIndex = 0;
bool isNoiseFloorBufferFull = false;
uint32_t lastNoiseFloorUpdate = 0;
static const uint32_t NOISE_FLOOR_UPDATE_INTERVAL_MS = 5000;
int32_t currentNoiseFloor = NOISE_FLOOR_DEFAULT;
/**
* Pure virtual hook for derived radio interfaces to provide instantaneous RSSI.
* Implementations should return dBm, or an invalid value that updateNoiseFloor()
* can reject.
*/
virtual int16_t getCurrentRSSI() = 0;
public:
/** Our ISR code currently needs this to find our active instance
*/
@@ -111,6 +131,17 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
instance = nullptr;
}
/**
* Get the current calculated noise floor in dBm
* Returns -120 dBm if not yet calibrated
*/
int32_t getNoiseFloor();
/**
* Calculate the average noise floor from collected samples
*/
int32_t getAverageNoiseFloor();
/**
* Glue functions called from ISR land
*/
@@ -179,6 +210,30 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
/** Attempt to find a packet in the TxQueue. Returns true if the packet was found. */
virtual bool findInTxQueue(NodeNum from, PacketId id) override;
uint8_t packetsInTxQueue() { return txQueue.getMaxLen() - txQueue.getFree(); }
/**
* Update the noise floor measurement by sampling RSSI from a slow path.
* This should not be called from radio interrupt or TX/RX critical paths.
*/
void updateNoiseFloor();
/**
* Check if we have collected any noise floor samples
*/
bool hasNoiseFloorSamples();
/**
* Get the number of samples in the rolling window
*/
uint8_t getNoiseFloorSampleCount();
/**
* Reset the noise floor calibration
* Will automatically restart collection
*/
void resetNoiseFloor();
/**
* Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface.
* @return true if len bytes were produced, false otherwise.
@@ -186,6 +241,9 @@ class RadioLibInterface : public RadioInterface, protected concurrency::Notified
bool randomBytes(uint8_t *buffer, size_t length);
private:
uint8_t getNoiseFloorSampleCountInternal() const;
int32_t getAverageNoiseFloorInternal() const;
/** if we have something waiting to send, start a short (random) timer so we can come check for collision before actually
* doing the transmit */
void setTransmitDelay();
+12 -7
View File
@@ -318,10 +318,11 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
} // should have already been handled by sendLocal
// Abort sending if we are violating the duty cycle
if (!config.lora.override_duty_cycle && myRegion->dutyCycle < 100) {
float effectiveDutyCycle = getEffectiveDutyCycle();
if (!config.lora.override_duty_cycle && effectiveDutyCycle < 100) {
float hourlyTxPercent = airTime->utilizationTXPercent();
if (hourlyTxPercent > myRegion->dutyCycle) {
uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle);
if (hourlyTxPercent > effectiveDutyCycle) {
uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, effectiveDutyCycle);
LOG_WARN("Duty cycle limit exceeded. Aborting send for now, you can send again in %d mins", silentMinutes);
@@ -368,10 +369,14 @@ ErrorCode Router::send(meshtastic_MeshPacket *p)
}
fixPriority(p); // Before encryption, fix the priority if it's unset
if (!applyPositionPrecisionForChannel(*p, p->channel)) {
LOG_ERROR("Dropping malformed position packet before send");
packetPool.release(p);
return meshtastic_Routing_Error_BAD_REQUEST;
// Position precision is an originator-only privacy policy. Relays keep
// p->from as the original sender, so do not rewrite their POSITION_APP payload.
if (isFromUs(p)) {
if (!applyPositionPrecisionForChannel(*p, p->channel)) {
LOG_ERROR("Dropping malformed position packet before send");
packetPool.release(p);
return meshtastic_Routing_Error_BAD_REQUEST;
}
}
// If the packet is not yet encrypted, do so now
+8 -2
View File
@@ -52,8 +52,8 @@ template <typename T> bool SX126xInterface<T>::init()
#ifdef SX126X_POWER_EN // Perhaps add RADIOLIB_NC check, and beforehand define as such if it is undefined, but it is not commonly
// used and not part of the 'default' set of pin definitions.
digitalWrite(SX126X_POWER_EN, HIGH);
pinMode(SX126X_POWER_EN, OUTPUT);
digitalWrite(SX126X_POWER_EN, HIGH);
#endif
#if HAS_LORA_FEM
@@ -65,8 +65,8 @@ template <typename T> bool SX126xInterface<T>::init()
#endif
#ifdef RF95_FAN_EN
digitalWrite(RF95_FAN_EN, HIGH);
pinMode(RF95_FAN_EN, OUTPUT);
digitalWrite(RF95_FAN_EN, HIGH);
#endif
#if ARCH_PORTDUINO
@@ -265,6 +265,12 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
return true;
}
template <typename T> int16_t SX126xInterface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI(false);
return (int16_t)round(rssi);
}
template <typename T> void SX126xInterface<T>::disableInterrupt()
{
lora.clearDio1Action();
+2
View File
@@ -41,6 +41,8 @@ template <class T> class SX126xInterface : public RadioLibInterface
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
+6
View File
@@ -325,4 +325,10 @@ template <typename T> bool SX128xInterface<T>::sleep()
return true;
}
template <typename T> int16_t SX128xInterface<T>::getCurrentRSSI()
{
float rssi = lora.getRSSI(false);
return (int16_t)round(rssi);
}
#endif
+2
View File
@@ -35,6 +35,8 @@ template <class T> class SX128xInterface : public RadioLibInterface
*/
T lora;
int16_t getCurrentRSSI() override;
/**
* Glue functions called from ISR land
*/
+2 -3
View File
@@ -3,9 +3,8 @@
#include "ServerAPI.h"
#include <WiFi.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
/**
+4
View File
@@ -2,7 +2,11 @@
#include "ServerAPI.h"
#if !defined(USE_WS5500) && !defined(USE_CH390D)
#if defined(USE_ARDUINO_ETHERNET)
#include <Ethernet.h>
#else
#include <RAK13800_W5100S.h>
#endif
/**
* Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs
File diff suppressed because it is too large Load Diff
-347
View File
@@ -1,347 +0,0 @@
/*
* Copyright (C) 2020 Siara Logics (cc)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* @author Arundale Ramanathan
*
* Port for Particle (particle.io) / Aruino - Jonathan Greenblatt
*
* This file describes each function of the Unishox2 API \n
* For finding out how this API can be used in your program, \n
* please see test_unishox2.c.
*/
#ifndef unishox2
#define unishox2
#define UNISHOX_VERSION "2.0" ///< Unicode spec version
/**
* Macro switch to enable/disable output buffer length parameter in low level api \n
* Disabled by default \n
* When this macro is defined, the all the API functions \n
* except the simple API functions accept an additional parameter olen \n
* that enables the developer to pass the size of the output buffer provided \n
* so that the api function may not write beyond that length. \n
* This can be disabled if the developer knows that the buffer provided is sufficient enough \n
* so no additional parameter is passed and the program is faster since additional check \n
* for output length is not performed at each step \n
* The simple api, i.e. unishox2_(de)compress_simple will always omit the buffer length
*/
#ifndef UNISHOX_API_WITH_OUTPUT_LEN
#define UNISHOX_API_WITH_OUTPUT_LEN 1
#endif
/// Upto 8 bits of initial magic bit sequence can be included. Bit count can be specified with UNISHOX_MAGIC_BIT_LEN
#ifndef UNISHOX_MAGIC_BITS
#define UNISHOX_MAGIC_BITS 0xFF
#endif
/// Desired length of Magic bits defined by UNISHOX_MAGIC_BITS
#ifdef UNISHOX_MAGIC_BIT_LEN
#if UNISHOX_MAGIC_BIT_LEN < 0 || 9 <= UNISHOX_MAGIC_BIT_LEN
#error "UNISHOX_MAGIC_BIT_LEN need between [0, 8)"
#endif
#else
#define UNISHOX_MAGIC_BIT_LEN 1
#endif
// enum {USX_ALPHA = 0, USX_SYM, USX_NUM, USX_DICT, USX_DELTA};
/// Default Horizontal codes. When composition of text is know beforehand, the other hcodes in this section can be used to achieve
/// more compression.
#define USX_HCODES_DFLT \
(const unsigned char[]) \
{ \
0x00, 0x40, 0x80, 0xC0, 0xE0 \
}
/// Length of each default hcode
#define USX_HCODE_LENS_DFLT \
(const unsigned char[]) \
{ \
2, 2, 2, 3, 3 \
}
/// Horizontal codes preset for English Alphabet content only
#define USX_HCODES_ALPHA_ONLY \
(const unsigned char[]) \
{ \
0x00, 0x00, 0x00, 0x00, 0x00 \
}
/// Length of each Alpha only hcode
#define USX_HCODE_LENS_ALPHA_ONLY \
(const unsigned char[]) \
{ \
0, 0, 0, 0, 0 \
}
/// Horizontal codes preset for Alpha Numeric content only
#define USX_HCODES_ALPHA_NUM_ONLY \
(const unsigned char[]) \
{ \
0x00, 0x00, 0x80, 0x00, 0x00 \
}
/// Length of each Alpha numeric hcode
#define USX_HCODE_LENS_ALPHA_NUM_ONLY \
(const unsigned char[]) \
{ \
1, 0, 1, 0, 0 \
}
/// Horizontal codes preset for Alpha Numeric and Symbol content only
#define USX_HCODES_ALPHA_NUM_SYM_ONLY \
(const unsigned char[]) \
{ \
0x00, 0x80, 0xC0, 0x00, 0x00 \
}
/// Length of each Alpha numeric and symbol hcodes
#define USX_HCODE_LENS_ALPHA_NUM_SYM_ONLY \
(const unsigned char[]) \
{ \
1, 2, 2, 0, 0 \
}
/// Horizontal codes preset favouring Alphabet content
#define USX_HCODES_FAVOR_ALPHA \
(const unsigned char[]) \
{ \
0x00, 0x80, 0xA0, 0xC0, 0xE0 \
}
/// Length of each hcode favouring Alpha content
#define USX_HCODE_LENS_FAVOR_ALPHA \
(const unsigned char[]) \
{ \
1, 3, 3, 3, 3 \
}
/// Horizontal codes preset favouring repeating sequences
#define USX_HCODES_FAVOR_DICT \
(const unsigned char[]) \
{ \
0x00, 0x40, 0xC0, 0x80, 0xE0 \
}
/// Length of each hcode favouring repeating sequences
#define USX_HCODE_LENS_FAVOR_DICT \
(const unsigned char[]) \
{ \
2, 2, 3, 2, 3 \
}
/// Horizontal codes preset favouring symbols
#define USX_HCODES_FAVOR_SYM \
(const unsigned char[]) \
{ \
0x80, 0x00, 0xA0, 0xC0, 0xE0 \
}
/// Length of each hcode favouring symbols
#define USX_HCODE_LENS_FAVOR_SYM \
(const unsigned char[]) \
{ \
3, 1, 3, 3, 3 \
}
// #define USX_HCODES_FAVOR_UMLAUT {0x00, 0x40, 0xE0, 0xC0, 0x80}
// #define USX_HCODE_LENS_FAVOR_UMLAUT {2, 2, 3, 3, 2}
/// Horizontal codes preset favouring umlaut letters
#define USX_HCODES_FAVOR_UMLAUT \
(const unsigned char[]) \
{ \
0x80, 0xA0, 0xC0, 0xE0, 0x00 \
}
/// Length of each hcode favouring umlaut letters
#define USX_HCODE_LENS_FAVOR_UMLAUT \
(const unsigned char[]) \
{ \
3, 3, 3, 3, 1 \
}
/// Horizontal codes preset for no repeating sequences
#define USX_HCODES_NO_DICT \
(const unsigned char[]) \
{ \
0x00, 0x40, 0x80, 0x00, 0xC0 \
}
/// Length of each hcode for no repeating sequences
#define USX_HCODE_LENS_NO_DICT \
(const unsigned char[]) \
{ \
2, 2, 2, 0, 2 \
}
/// Horizontal codes preset for no Unicode characters
#define USX_HCODES_NO_UNI \
(const unsigned char[]) \
{ \
0x00, 0x40, 0x80, 0xC0, 0x00 \
}
/// Length of each hcode for no Unicode characters
#define USX_HCODE_LENS_NO_UNI \
(const unsigned char[]) \
{ \
2, 2, 2, 2, 0 \
}
extern const char *USX_FREQ_SEQ_DFLT[];
extern const char *USX_FREQ_SEQ_TXT[];
extern const char *USX_FREQ_SEQ_URL[];
extern const char *USX_FREQ_SEQ_JSON[];
extern const char *USX_FREQ_SEQ_HTML[];
extern const char *USX_FREQ_SEQ_XML[];
extern const char *USX_TEMPLATES[];
/// Default preset parameter set. When composition of text is know beforehand, the other parameter sets in this section can be
/// used to achieve more compression.
#define USX_PSET_DFLT USX_HCODES_DFLT, USX_HCODE_LENS_DFLT, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set for English Alphabet only content
#define USX_PSET_ALPHA_ONLY USX_HCODES_ALPHA_ONLY, USX_HCODE_LENS_ALPHA_ONLY, USX_FREQ_SEQ_TXT, USX_TEMPLATES
/// Preset parameter set for Alpha numeric content
#define USX_PSET_ALPHA_NUM_ONLY USX_HCODES_ALPHA_NUM_ONLY, USX_HCODE_LENS_ALPHA_NUM_ONLY, USX_FREQ_SEQ_TXT, USX_TEMPLATES
/// Preset parameter set for Alpha numeric and symbol content
#define USX_PSET_ALPHA_NUM_SYM_ONLY \
USX_HCODES_ALPHA_NUM_SYM_ONLY, USX_HCODE_LENS_ALPHA_NUM_SYM_ONLY, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set for Alpha numeric symbol content having predominantly text
#define USX_PSET_ALPHA_NUM_SYM_ONLY_TXT \
USX_HCODES_ALPHA_NUM_SYM_ONLY, USX_HCODE_LENS_ALPHA_NUM_SYM_ONLY, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set favouring Alphabet content
#define USX_PSET_FAVOR_ALPHA USX_HCODES_FAVOR_ALPHA, USX_HCODE_LENS_FAVOR_ALPHA, USX_FREQ_SEQ_TXT, USX_TEMPLATES
/// Preset parameter set favouring repeating sequences
#define USX_PSET_FAVOR_DICT USX_HCODES_FAVOR_DICT, USX_HCODE_LENS_FAVOR_DICT, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set favouring symbols
#define USX_PSET_FAVOR_SYM USX_HCODES_FAVOR_SYM, USX_HCODE_LENS_FAVOR_SYM, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set favouring unlaut letters
#define USX_PSET_FAVOR_UMLAUT USX_HCODES_FAVOR_UMLAUT, USX_HCODE_LENS_FAVOR_UMLAUT, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set for when there are no repeating sequences
#define USX_PSET_NO_DICT USX_HCODES_NO_DICT, USX_HCODE_LENS_NO_DICT, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set for when there are no unicode symbols
#define USX_PSET_NO_UNI USX_HCODES_NO_UNI, USX_HCODE_LENS_NO_UNI, USX_FREQ_SEQ_DFLT, USX_TEMPLATES
/// Preset parameter set for when there are no unicode symbols favouring text
#define USX_PSET_NO_UNI_FAVOR_TEXT USX_HCODES_NO_UNI, USX_HCODE_LENS_NO_UNI, USX_FREQ_SEQ_TXT, USX_TEMPLATES
/// Preset parameter set favouring URL content
#define USX_PSET_URL USX_HCODES_DFLT, USX_HCODE_LENS_DFLT, USX_FREQ_SEQ_URL, USX_TEMPLATES
/// Preset parameter set favouring JSON content
#define USX_PSET_JSON USX_HCODES_DFLT, USX_HCODE_LENS_DFLT, USX_FREQ_SEQ_JSON, USX_TEMPLATES
/// Preset parameter set favouring JSON content having no Unicode symbols
#define USX_PSET_JSON_NO_UNI USX_HCODES_NO_UNI, USX_HCODE_LENS_NO_UNI, USX_FREQ_SEQ_JSON, USX_TEMPLATES
/// Preset parameter set favouring XML content
#define USX_PSET_XML USX_HCODES_DFLT, USX_HCODE_LENS_DFLT, USX_FREQ_SEQ_XML, USX_TEMPLATES
/// Preset parameter set favouring HTML content
#define USX_PSET_HTML USX_HCODES_DFLT, USX_HCODE_LENS_DFLT, USX_FREQ_SEQ_HTML, USX_TEMPLATES
/**
* This structure is used when a string array needs to be compressed.
* This is passed as a parameter to the unishox2_decompress_lines() function
*/
struct us_lnk_lst {
char *data;
struct us_lnk_lst *previous;
};
/**
* This macro is for internal use, but builds upon the macro UNISHOX_API_WITH_OUTPUT_LEN
* When the macro UNISHOX_API_WITH_OUTPUT_LEN is defined, the all the API functions
* except the simple API functions accept an additional parameter olen
* that enables the developer to pass the size of the output buffer provided
* so that the api function may not write beyond that length.
* This can be disabled if the developer knows that the buffer provided is sufficient enough
* so no additional parameter is passed and the program is faster since additional check
* for output length is not performed at each step
*/
#if defined(UNISHOX_API_WITH_OUTPUT_LEN) && UNISHOX_API_WITH_OUTPUT_LEN != 0
#define UNISHOX_API_OUT_AND_LEN(out, olen) out, olen
#else
#define UNISHOX_API_OUT_AND_LEN(out, olen) out
#endif
/**
* Simple API for compressing a string
* @param[in] in Input ASCII / UTF-8 string
* @param[in] len length in bytes
* @param[out] out output buffer - should be large enough to hold compressed output
*/
extern int unishox2_compress_simple(const char *in, int len, char *out);
/**
* Simple API for decompressing a string
* @param[in] in Input compressed bytes (output of unishox2_compress functions)
* @param[in] len length of 'in' in bytes
* @param[out] out output buffer for ASCII / UTF-8 string - should be large enough
*/
extern int unishox2_decompress_simple(const char *in, int len, char *out);
/**
* Comprehensive API for compressing a string
*
* Presets are available for the last four parameters so they can be passed as single parameter. \n
* See USX_PSET_* macros. Example call: \n
* unishox2_compress(in, len, out, olen, USX_PSET_ALPHA_ONLY);
*
* @param[in] in Input ASCII / UTF-8 string
* @param[in] len length in bytes
* @param[out] out output buffer - should be large enough to hold compressed output
* @param[in] olen length of 'out' buffer in bytes. Can be omitted if sufficient buffer is provided
* @param[in] usx_hcodes Horizontal codes (array of bytes). See macro section for samples.
* @param[in] usx_hcode_lens Length of each element in usx_hcodes array
* @param[in] usx_freq_seq Frequently occurring sequences. See USX_FREQ_SEQ_* macros for samples
* @param[in] usx_templates Templates of frequently occurring patterns. See USX_TEMPLATES macro.
*/
extern int unishox2_compress(const char *in, int len, UNISHOX_API_OUT_AND_LEN(char *out, int olen),
const unsigned char usx_hcodes[], const unsigned char usx_hcode_lens[], const char *usx_freq_seq[],
const char *usx_templates[]);
/**
* Comprehensive API for de-compressing a string
*
* Presets are available for the last four parameters so they can be passed as single parameter. \n
* See USX_PSET_* macros. Example call: \n
* unishox2_decompress(in, len, out, olen, USX_PSET_ALPHA_ONLY);
*
* @param[in] in Input compressed bytes (output of unishox2_compress functions)
* @param[in] len length of 'in' in bytes
* @param[out] out output buffer - should be large enough to hold de-compressed output
* @param[in] olen length of 'out' buffer in bytes. Can be omitted if sufficient buffer is provided
* @param[in] usx_hcodes Horizontal codes (array of bytes). See macro section for samples.
* @param[in] usx_hcode_lens Length of each element in usx_hcodes array
* @param[in] usx_freq_seq Frequently occurring sequences. See USX_FREQ_SEQ_* macros for samples
* @param[in] usx_templates Templates of frequently occurring patterns. See USX_TEMPLATES macro.
*/
extern int unishox2_decompress(const char *in, int len, UNISHOX_API_OUT_AND_LEN(char *out, int olen),
const unsigned char usx_hcodes[], const unsigned char usx_hcode_lens[], const char *usx_freq_seq[],
const char *usx_templates[]);
/**
* More Comprehensive API for compressing array of strings
*
* See unishox2_compress() function for parameter definitions. \n
* This function takes an additional parameter, i.e. 'prev_lines' - the usx_lnk_lst structure \n
* See -g parameter in test_unishox2.c to find out how this can be used. \n
* This function is used when an array of strings need to be compressed \n
* and stored in a compressed array of bytes for use as a constant in other programs \n
* where each element of the array can be decompressed and used at runtime.
*/
extern int unishox2_compress_lines(const char *in, int len, UNISHOX_API_OUT_AND_LEN(char *out, int olen),
const unsigned char usx_hcodes[], const unsigned char usx_hcode_lens[],
const char *usx_freq_seq[], const char *usx_templates[], struct us_lnk_lst *prev_lines);
/**
* More Comprehensive API for de-compressing array of strings \n
* This function is not be used in conjuction with unishox2_compress_lines()
*
* See unishox2_decompress() function for parameter definitions. \n
* Typically an array is compressed using unishox2_compress_lines() and \n
* a header (.h) file is generated using the resultant compressed array. \n
* This header file can be used in another program with another decompress \n
* routine which takes this compressed array as parameter and index to be \n
* decompressed.
*/
extern int unishox2_decompress_lines(const char *in, int len, UNISHOX_API_OUT_AND_LEN(char *out, int olen),
const unsigned char usx_hcodes[], const unsigned char usx_hcode_lens[],
const char *usx_freq_seq[], const char *usx_templates[], struct us_lnk_lst *prev_lines);
#endif
+30
View File
@@ -6,7 +6,13 @@
#include "main.h"
#include "mesh/api/ethServerAPI.h"
#include "target_specific.h"
#ifdef USE_ARDUINO_ETHERNET
#include <Ethernet.h> // arduino-libraries/Ethernet — supports W5100/W5200/W5500
// Shorter DHCP timeout so LoRa startup isn't blocked when no DHCP server is present.
#define ETH_DHCP_TIMEOUT_MS 10000
#else
#include <RAK13800_W5100S.h>
#endif
#include <SPI.h>
#if HAS_NETWORKING && !defined(USE_WS5500) && !defined(USE_CH390D)
@@ -69,6 +75,13 @@ static int32_t reconnectETH()
delay(100);
#endif
#ifdef USE_ARDUINO_ETHERNET // Re-configure SPI0 for the W5500 module
SPI.setRX(ETH_SPI0_MISO);
SPI.setSCK(ETH_SPI0_SCK);
SPI.setTX(ETH_SPI0_MOSI);
SPI.begin();
Ethernet.init(PIN_ETHERNET_SS);
#else
#ifdef RAK11310
ETH_SPI_PORT.setSCK(PIN_SPI0_SCK);
ETH_SPI_PORT.setTX(PIN_SPI0_MOSI);
@@ -76,10 +89,15 @@ static int32_t reconnectETH()
ETH_SPI_PORT.begin();
#endif
Ethernet.init(ETH_SPI_PORT, PIN_ETHERNET_SS);
#endif
int status = 0;
if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_DHCP) {
#ifdef ETH_DHCP_TIMEOUT_MS
status = Ethernet.begin(expectedMac, ETH_DHCP_TIMEOUT_MS);
#else
status = Ethernet.begin(expectedMac);
#endif
} else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) {
Ethernet.begin(expectedMac, config.network.ipv4_config.ip, config.network.ipv4_config.dns,
config.network.ipv4_config.gateway, config.network.ipv4_config.subnet);
@@ -182,6 +200,13 @@ bool initEthernet()
digitalWrite(PIN_ETHERNET_RESET, HIGH); // Reset Time.
#endif
#ifdef USE_ARDUINO_ETHERNET // Configure SPI0 for the W5500 module
SPI.setRX(ETH_SPI0_MISO);
SPI.setSCK(ETH_SPI0_SCK);
SPI.setTX(ETH_SPI0_MOSI);
SPI.begin();
Ethernet.init(PIN_ETHERNET_SS);
#else
#ifdef RAK11310 // Initialize the SPI port
ETH_SPI_PORT.setSCK(PIN_SPI0_SCK);
ETH_SPI_PORT.setTX(PIN_SPI0_MOSI);
@@ -189,6 +214,7 @@ bool initEthernet()
ETH_SPI_PORT.begin();
#endif
Ethernet.init(ETH_SPI_PORT, PIN_ETHERNET_SS);
#endif
uint8_t mac[6];
@@ -201,7 +227,11 @@ bool initEthernet()
if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_DHCP) {
LOG_INFO("Start Ethernet DHCP");
#ifdef ETH_DHCP_TIMEOUT_MS
status = Ethernet.begin(mac, ETH_DHCP_TIMEOUT_MS);
#else
status = Ethernet.begin(mac);
#endif
} else if (config.network.address_mode == meshtastic_Config_NetworkConfig_AddressMode_STATIC) {
LOG_INFO("Start Ethernet Static");
Ethernet.begin(mac, config.network.ipv4_config.ip, config.network.ipv4_config.dns, config.network.ipv4_config.gateway,
+10 -1
View File
@@ -30,7 +30,7 @@ PB_BIND(meshtastic_AircraftTrack, meshtastic_AircraftTrack, AUTO)
PB_BIND(meshtastic_CotGeoPoint, meshtastic_CotGeoPoint, AUTO)
PB_BIND(meshtastic_DrawnShape, meshtastic_DrawnShape, 2)
PB_BIND(meshtastic_DrawnShape, meshtastic_DrawnShape, AUTO)
PB_BIND(meshtastic_Marker, meshtastic_Marker, AUTO)
@@ -63,6 +63,15 @@ PB_BIND(meshtastic_TAKEnvironment, meshtastic_TAKEnvironment, AUTO)
PB_BIND(meshtastic_SensorFov, meshtastic_SensorFov, AUTO)
PB_BIND(meshtastic_TakTalkMessage, meshtastic_TakTalkMessage, AUTO)
PB_BIND(meshtastic_TakTalkRoomData, meshtastic_TakTalkRoomData, AUTO)
PB_BIND(meshtastic_Marti, meshtastic_Marti, AUTO)
PB_BIND(meshtastic_TAKPacketV2, meshtastic_TAKPacketV2, 2)
+214 -31
View File
@@ -325,7 +325,15 @@ typedef enum _meshtastic_CotType {
meshtastic_CotType_CotType_b_a_o_c = 123,
/* t-s: Task / engage request. Structured payload carried via the new
TaskRequest typed variant. */
meshtastic_CotType_CotType_t_s = 124
meshtastic_CotType_CotType_t_s = 124,
/* m-t-t: TAKTALK voice/text chat message. Payload carried via the
TakTalkMessage typed variant (text, chatroom_id, lang, from_voice). */
meshtastic_CotType_CotType_m_t_t = 125,
/* y-: TAKTALK room/membership broadcast. Payload carried via the
TakTalkRoomData typed variant (sender_callsign, room_id, room_name,
participants). The CoT type literally has a trailing dash and no
second atom not a typo. */
meshtastic_CotType_CotType_y = 126
} meshtastic_CotType;
/* Geopoint and altitude source */
@@ -553,6 +561,18 @@ typedef struct _meshtastic_GeoChat {
/* Receipt kind discriminator. See ReceiptType doc. Default ReceiptType_None
means this is a regular chat message, not a receipt. */
meshtastic_GeoChat_ReceiptType receipt_type;
/* BCP-47-ish language tag or human-readable name (e.g. "en", "English")
that the originator's TAKTALK plugin recorded for the message. */
pb_callback_t lang;
/* TAKTALK chatroom UUID (e.g. "30b2755c-c547-44ef-a0cc-cdbd8a15616f") that
the receiver's TAKTALK plugin uses to thread the message under the
right room. Resolved to a friendly name via TakTalkRoomData broadcasts. */
pb_callback_t room_id;
/* TAKTALK voice profile pointer. Often empty in practice (the empty
marker `<voice_profile_id/>` still signals TAKTALK origination), so
receivers should treat empty-but-present as the equivalent of the
marker rather than a missing field. */
pb_callback_t voice_profile_id;
} meshtastic_GeoChat;
/* ATAK Group
@@ -715,12 +735,7 @@ typedef struct _meshtastic_DrawnShape {
uint32_t fill_argb;
/* Whether labels are rendered on this shape. */
bool labels_on;
/* Vertex list for polyline/polygon/rectangle shapes. Capped at 32 by
the nanopb pool; senders MUST truncate longer inputs and set
`truncated = true`. */
pb_size_t vertices_count;
meshtastic_CotGeoPoint vertices[32];
/* True if the sender truncated `vertices` to fit the pool. */
/* True if the sender truncated the vertex columns to fit the pool. */
bool truncated; /* --- Bullseye-only fields. All ignored unless kind == Kind_Bullseye. --- */
/* Bullseye distance in meters * 10 (e.g. 3285 = 328.5 m). 0 = unset. */
uint32_t bullseye_distance_dm;
@@ -734,6 +749,8 @@ typedef struct _meshtastic_DrawnShape {
uint8_t bullseye_flags;
/* Bullseye reference UID (anchor marker). Empty = anchor is self. */
char bullseye_uid_ref[48];
pb_callback_t vertex_lat_deltas;
pb_callback_t vertex_lon_deltas;
} meshtastic_DrawnShape;
/* Fixed point of interest: spot marker, waypoint, checkpoint, 2525 symbol,
@@ -1068,6 +1085,87 @@ typedef struct _meshtastic_SensorFov {
pb_callback_t model;
} meshtastic_SensorFov;
/* TAKTALK chat message payload (CoT type m-t-t).
TAKTALK is an ATAK plugin for voice + text team messaging. The voice
audio stream goes over UDP/RTP and is NOT carried by the mesh only
the text envelope (this message) is. `from_voice` marks messages sent
via push-to-talk speech-to-text so receivers can render a mic icon
next to the text.
Wire shape inside <event type="m-t-t">/<detail>:
<callsign>...</callsign> - mapped to TAKPacketV2.callsign
<lang>English</lang> - lang
<text>...</text> - text
<chatroom-id>1</chatroom-id> - chatroom_id
<voice/> - presence sets from_voice = true */
typedef struct _meshtastic_TakTalkMessage {
/* The text body of the TAKTALK message (speech-to-text transcript when
from_voice = true, typed message otherwise). */
pb_callback_t text;
/* TAKTALK chatroom identifier. May be a short id like "1" for the
default room or a UUID like "30b2755c-c547-44ef-a0cc-cdbd8a15616f"
for custom rooms (resolved by TakTalkRoomData broadcasts).
Empty = broadcast room. */
pb_callback_t chatroom_id;
/* BCP-47-ish language tag or human-readable name (e.g. "en", "English").
Empty = unspecified. */
pb_callback_t lang;
/* True when the source CoT carried a <voice/> marker, i.e. the message
originated as push-to-talk speech-to-text. Lets receivers show a mic
icon. Proto3 only encodes when true so empty payload cost is 0 bytes. */
bool from_voice;
} meshtastic_TakTalkMessage;
/* TAKTALK room/membership broadcast (CoT type y-).
Announces a TAKTALK chatroom's friendly name and roster so peers can
resolve room UUIDs (used in TakTalkMessage.chatroom_id and
GeoChat.room_id) to a display name and participant list. Not a chat
message itself these events are emitted by TAKTALK when rooms are
created or memberships change. */
typedef struct _meshtastic_TakTalkRoomData {
/* Callsign of the device broadcasting the room state (typically the
room owner / latest writer).
DEPRECATED in v0.3.2: always equals TAKPacketV2.callsign, so the wire
byte was redundant. Builders stop emitting this field in v0.3.2;
parsers still read it for one release so v0.3.1-encoded packets decode
cleanly. To be removed entirely in v0.4.x. */
pb_callback_t sender_callsign;
/* Room UUID, matches TakTalkMessage.chatroom_id / GeoChat.room_id on
messages routed into this room. */
pb_callback_t room_id;
/* Friendly display name for the room (e.g. "test", "Alpha Team"). */
pb_callback_t room_name;
/* Member callsigns. Wire-encoded as repeated strings; the underlying
CoT carries them as a single <chatroom-participants>A,B,C</> element
which parsers split / builders join on ','. */
pb_callback_t participants;
} meshtastic_TakTalkRoomData;
/* ATAK directed-routing recipient list (CoT <marti><dest callsign='X'/>…</marti>).
Present when an event is addressed to specific TAK users rather than the
broadcast group. TAKTALK gates voice TTS on this element matching the
receiver's callsign; directed b-t-f chats use it for the same purpose. A
missing <marti> means "broadcast to all peers", which is the default for
PLI, alerts, drawings, and most situational-awareness events.
Carried as repeated strings (not indexes into a per-packet table) because
the typical event has 1-2 destinations and table overhead would erase the
savings. Receivers that need the original XML element rebuild it from
dest_callsign on emit. */
typedef struct _meshtastic_Marti {
/* Recipient callsigns. Order is preserved end-to-end so receivers can show
primary-vs-cc distinction the same way ATAK does.
If dest_callsign is [TAKPacketV2.callsign] (self-addressed, unusual but
legal e.g. ATAK echoing back to its own room), the builder still emits
the element so loopback shapes round-trip cleanly. */
pb_callback_t dest_callsign;
} meshtastic_Marti;
typedef PB_BYTES_ARRAY_T(220) meshtastic_TAKPacketV2_raw_detail_t;
/* ATAK v2 packet with expanded CoT field support and zstd dictionary compression.
Sent on ATAK_PLUGIN_V2 port. The wire payload is:
@@ -1089,7 +1187,14 @@ typedef struct _meshtastic_TAKPacketV2 {
int32_t latitude_i;
/* Longitude, multiply by 1e-7 to get degrees in floating point */
int32_t longitude_i;
/* Altitude in meters (HAE) */
/* Altitude in meters (HAE). ATAK's "no altitude" sentinel is hae=9999999.0.
NOTE: an earlier v0.4.0 attempt made this `optional` to omit the 9999999
sentinel from the wire, but measurement showed it was net-negative: the
zstd dictionary already compresses the literal 9999999 to ~nothing, while
proto3 `optional` forces a genuine 0 m HAE (common on routes/drawings that
carry hae="0.0" or omit hae parsed as 0) to encode explicitly (+2 bytes),
which REGRESSED the worst-case route fixture. Kept as a plain field. */
int32_t altitude;
/* Speed in cm/s */
uint32_t speed;
@@ -1135,10 +1240,18 @@ typedef struct _meshtastic_TAKPacketV2 {
/* Sensor field-of-view cone (camera, FLIR, laser, etc.). From <sensor>. */
bool has_sensor_fov;
meshtastic_SensorFov sensor_fov;
/* Directed-routing recipient list (CoT <marti><dest callsign='X'/>…</marti>).
Empty / unset = broadcast to all peers (the default for situational-awareness
events). Populated for TAKTALK m-t-t, directed b-t-f DMs, and any other CoT
shape that ATAK addresses to specific recipients. TAKTALK gates voice TTS
playback on this element matching the receiver's callsign, so dropping it
silently breaks voice messaging end-to-end.
See Marti. */
bool has_marti;
meshtastic_Marti marti;
pb_size_t which_payload_variant;
union {
/* Position report (true = PLI, no extra fields beyond the common ones above) */
bool pli;
/* ATAK GeoChat message */
meshtastic_GeoChat chat;
/* Aircraft track data (ADS-B, military air) */
@@ -1163,6 +1276,14 @@ typedef struct _meshtastic_TAKPacketV2 {
meshtastic_EmergencyAlert emergency;
/* Task / engage request. See TaskRequest. */
meshtastic_TaskRequest task;
/* TAKTALK chat message (CoT type m-t-t). See TakTalkMessage.
Voice audio itself rides UDP/RTP outside the mesh; this carries the
text envelope plus a from_voice marker for receiver UX. */
meshtastic_TakTalkMessage taktalk;
/* TAKTALK room/membership broadcast (CoT type y-). See TakTalkRoomData.
Resolves room UUIDs (used in TakTalkMessage.chatroom_id and
GeoChat.room_id) to display name + roster on receivers. */
meshtastic_TakTalkRoomData taktalk_room;
} payload_variant;
} meshtastic_TAKPacketV2;
@@ -1185,8 +1306,8 @@ extern "C" {
#define _meshtastic_CotHow_ARRAYSIZE ((meshtastic_CotHow)(meshtastic_CotHow_CotHow_m_s+1))
#define _meshtastic_CotType_MIN meshtastic_CotType_CotType_Other
#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_t_s
#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_t_s+1))
#define _meshtastic_CotType_MAX meshtastic_CotType_CotType_y
#define _meshtastic_CotType_ARRAYSIZE ((meshtastic_CotType)(meshtastic_CotType_CotType_y+1))
#define _meshtastic_GeoPointSource_MIN meshtastic_GeoPointSource_GeoPointSource_Unspecified
#define _meshtastic_GeoPointSource_MAX meshtastic_GeoPointSource_GeoPointSource_NETWORK
@@ -1282,6 +1403,9 @@ extern "C" {
#define meshtastic_SensorFov_type_ENUMTYPE meshtastic_SensorFov_SensorType
#define meshtastic_TAKPacketV2_cot_type_id_ENUMTYPE meshtastic_CotType
#define meshtastic_TAKPacketV2_how_ENUMTYPE meshtastic_CotHow
#define meshtastic_TAKPacketV2_team_ENUMTYPE meshtastic_Team
@@ -1292,14 +1416,14 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_TAKPacket_init_default {0, false, meshtastic_Contact_init_default, false, meshtastic_Group_init_default, false, meshtastic_Status_init_default, 0, {meshtastic_PLI_init_default}}
#define meshtastic_GeoChat_init_default {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN}
#define meshtastic_GeoChat_init_default {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Group_init_default {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN}
#define meshtastic_Status_init_default {0}
#define meshtastic_Contact_init_default {"", ""}
#define meshtastic_PLI_init_default {0, 0, 0, 0, 0}
#define meshtastic_AircraftTrack_init_default {"", "", "", "", 0, "", 0, 0, ""}
#define meshtastic_CotGeoPoint_init_default {0, 0}
#define meshtastic_DrawnShape_init_default {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default, meshtastic_CotGeoPoint_init_default}, 0, 0, 0, 0, ""}
#define meshtastic_DrawnShape_init_default {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, 0, 0, 0, "", {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Marker_init_default {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""}
#define meshtastic_RangeAndBearing_init_default {false, meshtastic_CotGeoPoint_init_default, "", 0, 0, _meshtastic_Team_MIN, 0, 0}
#define meshtastic_Route_init_default {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default, meshtastic_Route_Link_init_default}, 0}
@@ -1310,16 +1434,19 @@ extern "C" {
#define meshtastic_TaskRequest_init_default {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""}
#define meshtastic_TAKEnvironment_init_default {0, 0, 0}
#define meshtastic_SensorFov_init_default {_meshtastic_SensorFov_SensorType_MIN, 0, false, 0, 0, 0, 0, 0, {{NULL}, NULL}}
#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_default, false, meshtastic_SensorFov_init_default, 0, {0}}
#define meshtastic_TakTalkMessage_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0}
#define meshtastic_TakTalkRoomData_init_default {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Marti_init_default {{{NULL}, NULL}}
#define meshtastic_TAKPacketV2_init_default {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_default, false, meshtastic_SensorFov_init_default, false, meshtastic_Marti_init_default, 0, {meshtastic_GeoChat_init_default}}
#define meshtastic_TAKPacket_init_zero {0, false, meshtastic_Contact_init_zero, false, meshtastic_Group_init_zero, false, meshtastic_Status_init_zero, 0, {meshtastic_PLI_init_zero}}
#define meshtastic_GeoChat_init_zero {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN}
#define meshtastic_GeoChat_init_zero {"", false, "", false, "", "", _meshtastic_GeoChat_ReceiptType_MIN, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Group_init_zero {_meshtastic_MemberRole_MIN, _meshtastic_Team_MIN}
#define meshtastic_Status_init_zero {0}
#define meshtastic_Contact_init_zero {"", ""}
#define meshtastic_PLI_init_zero {0, 0, 0, 0, 0}
#define meshtastic_AircraftTrack_init_zero {"", "", "", "", 0, "", 0, 0, ""}
#define meshtastic_CotGeoPoint_init_zero {0, 0}
#define meshtastic_DrawnShape_init_zero {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, {meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero, meshtastic_CotGeoPoint_init_zero}, 0, 0, 0, 0, ""}
#define meshtastic_DrawnShape_init_zero {_meshtastic_DrawnShape_Kind_MIN, _meshtastic_DrawnShape_StyleMode_MIN, 0, 0, 0, _meshtastic_Team_MIN, 0, 0, _meshtastic_Team_MIN, 0, 0, 0, 0, 0, 0, "", {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Marker_init_zero {_meshtastic_Marker_Kind_MIN, _meshtastic_Team_MIN, 0, 0, "", "", "", ""}
#define meshtastic_RangeAndBearing_init_zero {false, meshtastic_CotGeoPoint_init_zero, "", 0, 0, _meshtastic_Team_MIN, 0, 0}
#define meshtastic_Route_init_zero {_meshtastic_Route_Method_MIN, _meshtastic_Route_Direction_MIN, "", 0, 0, {meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero, meshtastic_Route_Link_init_zero}, 0}
@@ -1330,7 +1457,10 @@ extern "C" {
#define meshtastic_TaskRequest_init_zero {"", "", "", _meshtastic_TaskRequest_Priority_MIN, _meshtastic_TaskRequest_Status_MIN, ""}
#define meshtastic_TAKEnvironment_init_zero {0, 0, 0}
#define meshtastic_SensorFov_init_zero {_meshtastic_SensorFov_SensorType_MIN, 0, false, 0, 0, 0, 0, 0, {{NULL}, NULL}}
#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_zero, false, meshtastic_SensorFov_init_zero, 0, {0}}
#define meshtastic_TakTalkMessage_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, 0}
#define meshtastic_TakTalkRoomData_init_zero {{{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}, {{NULL}, NULL}}
#define meshtastic_Marti_init_zero {{{NULL}, NULL}}
#define meshtastic_TAKPacketV2_init_zero {_meshtastic_CotType_MIN, _meshtastic_CotHow_MIN, "", _meshtastic_Team_MIN, _meshtastic_MemberRole_MIN, 0, 0, 0, 0, 0, 0, _meshtastic_GeoPointSource_MIN, _meshtastic_GeoPointSource_MIN, "", "", 0, "", "", "", "", "", "", "", {{NULL}, NULL}, false, meshtastic_TAKEnvironment_init_zero, false, meshtastic_SensorFov_init_zero, false, meshtastic_Marti_init_zero, 0, {meshtastic_GeoChat_init_zero}}
/* Field tags (for use in manual encoding/decoding) */
#define meshtastic_GeoChat_message_tag 1
@@ -1338,6 +1468,9 @@ extern "C" {
#define meshtastic_GeoChat_to_callsign_tag 3
#define meshtastic_GeoChat_receipt_for_uid_tag 4
#define meshtastic_GeoChat_receipt_type_tag 5
#define meshtastic_GeoChat_lang_tag 6
#define meshtastic_GeoChat_room_id_tag 7
#define meshtastic_GeoChat_voice_profile_id_tag 8
#define meshtastic_Group_role_tag 1
#define meshtastic_Group_team_tag 2
#define meshtastic_Status_battery_tag 1
@@ -1377,12 +1510,13 @@ extern "C" {
#define meshtastic_DrawnShape_fill_color_tag 9
#define meshtastic_DrawnShape_fill_argb_tag 10
#define meshtastic_DrawnShape_labels_on_tag 11
#define meshtastic_DrawnShape_vertices_tag 12
#define meshtastic_DrawnShape_truncated_tag 13
#define meshtastic_DrawnShape_bullseye_distance_dm_tag 14
#define meshtastic_DrawnShape_bullseye_bearing_ref_tag 15
#define meshtastic_DrawnShape_bullseye_flags_tag 16
#define meshtastic_DrawnShape_bullseye_uid_ref_tag 17
#define meshtastic_DrawnShape_vertex_lat_deltas_tag 18
#define meshtastic_DrawnShape_vertex_lon_deltas_tag 19
#define meshtastic_Marker_kind_tag 1
#define meshtastic_Marker_color_tag 2
#define meshtastic_Marker_color_argb_tag 3
@@ -1467,6 +1601,15 @@ extern "C" {
#define meshtastic_SensorFov_elevation_deg_tag 6
#define meshtastic_SensorFov_roll_deg_tag 7
#define meshtastic_SensorFov_model_tag 8
#define meshtastic_TakTalkMessage_text_tag 1
#define meshtastic_TakTalkMessage_chatroom_id_tag 2
#define meshtastic_TakTalkMessage_lang_tag 3
#define meshtastic_TakTalkMessage_from_voice_tag 4
#define meshtastic_TakTalkRoomData_sender_callsign_tag 1
#define meshtastic_TakTalkRoomData_room_id_tag 2
#define meshtastic_TakTalkRoomData_room_name_tag 3
#define meshtastic_TakTalkRoomData_participants_tag 4
#define meshtastic_Marti_dest_callsign_tag 1
#define meshtastic_TAKPacketV2_cot_type_id_tag 1
#define meshtastic_TAKPacketV2_how_tag 2
#define meshtastic_TAKPacketV2_callsign_tag 3
@@ -1493,7 +1636,7 @@ extern "C" {
#define meshtastic_TAKPacketV2_remarks_tag 24
#define meshtastic_TAKPacketV2_environment_tag 25
#define meshtastic_TAKPacketV2_sensor_fov_tag 26
#define meshtastic_TAKPacketV2_pli_tag 30
#define meshtastic_TAKPacketV2_marti_tag 29
#define meshtastic_TAKPacketV2_chat_tag 31
#define meshtastic_TAKPacketV2_aircraft_tag 32
#define meshtastic_TAKPacketV2_raw_detail_tag 33
@@ -1504,6 +1647,8 @@ extern "C" {
#define meshtastic_TAKPacketV2_casevac_tag 38
#define meshtastic_TAKPacketV2_emergency_tag 39
#define meshtastic_TAKPacketV2_task_tag 40
#define meshtastic_TAKPacketV2_taktalk_tag 41
#define meshtastic_TAKPacketV2_taktalk_room_tag 42
/* Struct field encoding specification for nanopb */
#define meshtastic_TAKPacket_FIELDLIST(X, a) \
@@ -1527,8 +1672,11 @@ X(a, STATIC, SINGULAR, STRING, message, 1) \
X(a, STATIC, OPTIONAL, STRING, to, 2) \
X(a, STATIC, OPTIONAL, STRING, to_callsign, 3) \
X(a, STATIC, SINGULAR, STRING, receipt_for_uid, 4) \
X(a, STATIC, SINGULAR, UENUM, receipt_type, 5)
#define meshtastic_GeoChat_CALLBACK NULL
X(a, STATIC, SINGULAR, UENUM, receipt_type, 5) \
X(a, CALLBACK, OPTIONAL, STRING, lang, 6) \
X(a, CALLBACK, OPTIONAL, STRING, room_id, 7) \
X(a, CALLBACK, OPTIONAL, STRING, voice_profile_id, 8)
#define meshtastic_GeoChat_CALLBACK pb_default_field_callback
#define meshtastic_GeoChat_DEFAULT NULL
#define meshtastic_Group_FIELDLIST(X, a) \
@@ -1588,15 +1736,15 @@ X(a, STATIC, SINGULAR, UINT32, stroke_weight_x10, 8) \
X(a, STATIC, SINGULAR, UENUM, fill_color, 9) \
X(a, STATIC, SINGULAR, FIXED32, fill_argb, 10) \
X(a, STATIC, SINGULAR, BOOL, labels_on, 11) \
X(a, STATIC, REPEATED, MESSAGE, vertices, 12) \
X(a, STATIC, SINGULAR, BOOL, truncated, 13) \
X(a, STATIC, SINGULAR, UINT32, bullseye_distance_dm, 14) \
X(a, STATIC, SINGULAR, UINT32, bullseye_bearing_ref, 15) \
X(a, STATIC, SINGULAR, UINT32, bullseye_flags, 16) \
X(a, STATIC, SINGULAR, STRING, bullseye_uid_ref, 17)
#define meshtastic_DrawnShape_CALLBACK NULL
X(a, STATIC, SINGULAR, STRING, bullseye_uid_ref, 17) \
X(a, CALLBACK, REPEATED, SINT32, vertex_lat_deltas, 18) \
X(a, CALLBACK, REPEATED, SINT32, vertex_lon_deltas, 19)
#define meshtastic_DrawnShape_CALLBACK pb_default_field_callback
#define meshtastic_DrawnShape_DEFAULT NULL
#define meshtastic_DrawnShape_vertices_MSGTYPE meshtastic_CotGeoPoint
#define meshtastic_Marker_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, kind, 1) \
@@ -1726,6 +1874,27 @@ X(a, CALLBACK, SINGULAR, STRING, model, 8)
#define meshtastic_SensorFov_CALLBACK pb_default_field_callback
#define meshtastic_SensorFov_DEFAULT NULL
#define meshtastic_TakTalkMessage_FIELDLIST(X, a) \
X(a, CALLBACK, SINGULAR, STRING, text, 1) \
X(a, CALLBACK, SINGULAR, STRING, chatroom_id, 2) \
X(a, CALLBACK, SINGULAR, STRING, lang, 3) \
X(a, STATIC, SINGULAR, BOOL, from_voice, 4)
#define meshtastic_TakTalkMessage_CALLBACK pb_default_field_callback
#define meshtastic_TakTalkMessage_DEFAULT NULL
#define meshtastic_TakTalkRoomData_FIELDLIST(X, a) \
X(a, CALLBACK, SINGULAR, STRING, sender_callsign, 1) \
X(a, CALLBACK, SINGULAR, STRING, room_id, 2) \
X(a, CALLBACK, SINGULAR, STRING, room_name, 3) \
X(a, CALLBACK, REPEATED, STRING, participants, 4)
#define meshtastic_TakTalkRoomData_CALLBACK pb_default_field_callback
#define meshtastic_TakTalkRoomData_DEFAULT NULL
#define meshtastic_Marti_FIELDLIST(X, a) \
X(a, CALLBACK, REPEATED, STRING, dest_callsign, 1)
#define meshtastic_Marti_CALLBACK pb_default_field_callback
#define meshtastic_Marti_DEFAULT NULL
#define meshtastic_TAKPacketV2_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UENUM, cot_type_id, 1) \
X(a, STATIC, SINGULAR, UENUM, how, 2) \
@@ -1753,7 +1922,7 @@ X(a, STATIC, SINGULAR, STRING, cot_type_str, 23) \
X(a, CALLBACK, SINGULAR, STRING, remarks, 24) \
X(a, STATIC, OPTIONAL, MESSAGE, environment, 25) \
X(a, STATIC, OPTIONAL, MESSAGE, sensor_fov, 26) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,pli,payload_variant.pli), 30) \
X(a, STATIC, OPTIONAL, MESSAGE, marti, 29) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,chat,payload_variant.chat), 31) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,aircraft,payload_variant.aircraft), 32) \
X(a, STATIC, ONEOF, BYTES, (payload_variant,raw_detail,payload_variant.raw_detail), 33) \
@@ -1763,11 +1932,14 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,rab,payload_variant.rab), 3
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,route,payload_variant.route), 37) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,casevac,payload_variant.casevac), 38) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,emergency,payload_variant.emergency), 39) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,task,payload_variant.task), 40)
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,task,payload_variant.task), 40) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,taktalk,payload_variant.taktalk), 41) \
X(a, STATIC, ONEOF, MESSAGE, (payload_variant,taktalk_room,payload_variant.taktalk_room), 42)
#define meshtastic_TAKPacketV2_CALLBACK pb_default_field_callback
#define meshtastic_TAKPacketV2_DEFAULT NULL
#define meshtastic_TAKPacketV2_environment_MSGTYPE meshtastic_TAKEnvironment
#define meshtastic_TAKPacketV2_sensor_fov_MSGTYPE meshtastic_SensorFov
#define meshtastic_TAKPacketV2_marti_MSGTYPE meshtastic_Marti
#define meshtastic_TAKPacketV2_payload_variant_chat_MSGTYPE meshtastic_GeoChat
#define meshtastic_TAKPacketV2_payload_variant_aircraft_MSGTYPE meshtastic_AircraftTrack
#define meshtastic_TAKPacketV2_payload_variant_shape_MSGTYPE meshtastic_DrawnShape
@@ -1777,6 +1949,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload_variant,task,payload_variant.task),
#define meshtastic_TAKPacketV2_payload_variant_casevac_MSGTYPE meshtastic_CasevacReport
#define meshtastic_TAKPacketV2_payload_variant_emergency_MSGTYPE meshtastic_EmergencyAlert
#define meshtastic_TAKPacketV2_payload_variant_task_MSGTYPE meshtastic_TaskRequest
#define meshtastic_TAKPacketV2_payload_variant_taktalk_MSGTYPE meshtastic_TakTalkMessage
#define meshtastic_TAKPacketV2_payload_variant_taktalk_room_MSGTYPE meshtastic_TakTalkRoomData
extern const pb_msgdesc_t meshtastic_TAKPacket_msg;
extern const pb_msgdesc_t meshtastic_GeoChat_msg;
@@ -1797,6 +1971,9 @@ extern const pb_msgdesc_t meshtastic_EmergencyAlert_msg;
extern const pb_msgdesc_t meshtastic_TaskRequest_msg;
extern const pb_msgdesc_t meshtastic_TAKEnvironment_msg;
extern const pb_msgdesc_t meshtastic_SensorFov_msg;
extern const pb_msgdesc_t meshtastic_TakTalkMessage_msg;
extern const pb_msgdesc_t meshtastic_TakTalkRoomData_msg;
extern const pb_msgdesc_t meshtastic_Marti_msg;
extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
@@ -1819,20 +1996,27 @@ extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg;
#define meshtastic_TaskRequest_fields &meshtastic_TaskRequest_msg
#define meshtastic_TAKEnvironment_fields &meshtastic_TAKEnvironment_msg
#define meshtastic_SensorFov_fields &meshtastic_SensorFov_msg
#define meshtastic_TakTalkMessage_fields &meshtastic_TakTalkMessage_msg
#define meshtastic_TakTalkRoomData_fields &meshtastic_TakTalkRoomData_msg
#define meshtastic_Marti_fields &meshtastic_Marti_msg
#define meshtastic_TAKPacketV2_fields &meshtastic_TAKPacketV2_msg
/* Maximum encoded size of messages (where known) */
/* meshtastic_TAKPacket_size depends on runtime parameters */
/* meshtastic_GeoChat_size depends on runtime parameters */
/* meshtastic_DrawnShape_size depends on runtime parameters */
/* meshtastic_CasevacReport_size depends on runtime parameters */
/* meshtastic_ZMistEntry_size depends on runtime parameters */
/* meshtastic_SensorFov_size depends on runtime parameters */
/* meshtastic_TakTalkMessage_size depends on runtime parameters */
/* meshtastic_TakTalkRoomData_size depends on runtime parameters */
/* meshtastic_Marti_size depends on runtime parameters */
/* meshtastic_TAKPacketV2_size depends on runtime parameters */
#define MESHTASTIC_MESHTASTIC_ATAK_PB_H_MAX_SIZE meshtastic_Route_size
#define meshtastic_AircraftTrack_size 134
#define meshtastic_Contact_size 242
#define meshtastic_CotGeoPoint_size 12
#define meshtastic_DrawnShape_size 553
#define meshtastic_EmergencyAlert_size 100
#define meshtastic_GeoChat_size 495
#define meshtastic_Group_size 4
#define meshtastic_Marker_size 191
#define meshtastic_PLI_size 31
@@ -1841,7 +2025,6 @@ extern const pb_msgdesc_t meshtastic_TAKPacketV2_msg;
#define meshtastic_Route_size 1379
#define meshtastic_Status_size 3
#define meshtastic_TAKEnvironment_size 18
#define meshtastic_TAKPacket_size 756
#define meshtastic_TaskRequest_size 132
#ifdef __cplusplus
+21 -8
View File
@@ -198,7 +198,9 @@ typedef enum _meshtastic_Config_DisplayConfig_OledType {
/* Can not be auto detected but set by proto. Used for 128x64 screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107 = 3,
/* Can not be auto detected but set by proto. Used for 128x128 screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128 = 4,
/* Can not be auto detected but set by proto. Used for 64x128 rotated screens */
meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED = 5
} meshtastic_Config_DisplayConfig_OledType;
typedef enum _meshtastic_Config_DisplayConfig_DisplayMode {
@@ -288,15 +290,26 @@ typedef enum _meshtastic_Config_LoRaConfig_RegionCode {
meshtastic_Config_LoRaConfig_RegionCode_BR_902 = 26,
/* ITU Region 1 Amateur Radio 2m band (144-146 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU1_2M = 27,
/* ITU Region 2 / 3 Amateur Radio 2m band (144-148 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU23_2M = 28,
/* ITU Region 2 Amateur Radio 2m band (144-148 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU2_2M = 28,
/* EU 866MHz band (Band no. 47b of 2006/771/EC and subsequent amendments) for Non-specific short-range devices (SRD) */
meshtastic_Config_LoRaConfig_RegionCode_EU_866 = 29,
/* EU 874MHz and 917MHz bands (Band no. 1 and 4 of 2022/172/EC and subsequent amendments) for Non-specific short-range devices (SRD) */
meshtastic_Config_LoRaConfig_RegionCode_EU_874 = 30,
meshtastic_Config_LoRaConfig_RegionCode_EU_917 = 31,
/* EU 868MHz band, with narrow presets */
meshtastic_Config_LoRaConfig_RegionCode_EU_N_868 = 32
meshtastic_Config_LoRaConfig_RegionCode_EU_N_868 = 32,
/* ITU Region 3 Amateur Radio 2m band (144-148 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU3_2M = 33,
/* ITU Region 1 Amateur Radio 70cm band (430-440 MHz) */
meshtastic_Config_LoRaConfig_RegionCode_ITU1_70CM = 34,
/* ITU Region 2 Amateur Radio 70cm band (420-450 MHz)
Note: Some countries do not allocate 420-430 MHz or 440-450 MHz.
Check local law! */
meshtastic_Config_LoRaConfig_RegionCode_ITU2_70CM = 35,
/* ITU Region 3 Amateur Radio 70cm band (430-450 MHz)
Note: Some countries do not allocate 440-450 MHz. Check local law! */
meshtastic_Config_LoRaConfig_RegionCode_ITU3_70CM = 36
} meshtastic_Config_LoRaConfig_RegionCode;
/* Standard predefined channel settings
@@ -720,8 +733,8 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_DisplayUnits_ARRAYSIZE ((meshtastic_Config_DisplayConfig_DisplayUnits)(meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL+1))
#define _meshtastic_Config_DisplayConfig_OledType_MIN meshtastic_Config_DisplayConfig_OledType_OLED_AUTO
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_128_128+1))
#define _meshtastic_Config_DisplayConfig_OledType_MAX meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED
#define _meshtastic_Config_DisplayConfig_OledType_ARRAYSIZE ((meshtastic_Config_DisplayConfig_OledType)(meshtastic_Config_DisplayConfig_OledType_OLED_SH1107_ROTATED+1))
#define _meshtastic_Config_DisplayConfig_DisplayMode_MIN meshtastic_Config_DisplayConfig_DisplayMode_DEFAULT
#define _meshtastic_Config_DisplayConfig_DisplayMode_MAX meshtastic_Config_DisplayConfig_DisplayMode_COLOR
@@ -732,8 +745,8 @@ extern "C" {
#define _meshtastic_Config_DisplayConfig_CompassOrientation_ARRAYSIZE ((meshtastic_Config_DisplayConfig_CompassOrientation)(meshtastic_Config_DisplayConfig_CompassOrientation_DEGREES_270_INVERTED+1))
#define _meshtastic_Config_LoRaConfig_RegionCode_MIN meshtastic_Config_LoRaConfig_RegionCode_UNSET
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_EU_N_868
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_EU_N_868+1))
#define _meshtastic_Config_LoRaConfig_RegionCode_MAX meshtastic_Config_LoRaConfig_RegionCode_ITU3_70CM
#define _meshtastic_Config_LoRaConfig_RegionCode_ARRAYSIZE ((meshtastic_Config_LoRaConfig_RegionCode)(meshtastic_Config_LoRaConfig_RegionCode_ITU3_70CM+1))
#define _meshtastic_Config_LoRaConfig_ModemPreset_MIN meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST
#define _meshtastic_Config_LoRaConfig_ModemPreset_MAX meshtastic_Config_LoRaConfig_ModemPreset_NARROW_SLOW
+4
View File
@@ -321,6 +321,10 @@ typedef enum _meshtastic_HardwareModel {
meshtastic_HardwareModel_HELTEC_MESH_NODE_T1 = 133,
/* B&Q Consulting Station G3: TBD */
meshtastic_HardwareModel_STATION_G3 = 134,
/* Lilygo T-Impulse-Plus */
meshtastic_HardwareModel_T_IMPULSE_PLUS = 135,
/* Lilygo T-Echo Card */
meshtastic_HardwareModel_T_ECHO_CARD = 136,
/* ------------------------------------------------------------------------------------------------------------------------------------------
Reserved ID For developing private Ports. These will show up in live traffic sparsely, so we can use a high number. Keep it within 8 bits.
------------------------------------------------------------------------------------------------------------------------------------------ */
@@ -144,7 +144,7 @@ typedef struct _meshtastic_ModuleConfig_MQTTConfig {
(the default official mqtt.meshtastic.org server can handle encrypted packets)
Decrypted packets may be useful for external systems that want to consume meshtastic packets */
bool encryption_enabled;
/* Whether to send / consume json packets on MQTT */
/* Deprecated: JSON packet support on MQTT was removed, and this field is ignored. */
bool json_enabled;
/* If true, we attempt to establish a secure connection using TLS */
bool tls_enabled;
+2 -3
View File
@@ -13,9 +13,8 @@
#include <WebServer.h>
#include <WiFi.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
#ifdef ARCH_ESP32
+2 -3
View File
@@ -12,9 +12,8 @@
#include <AsyncUDP.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
#define UDP_MULTICAST_DEFAUL_PORT 4403 // Default port for UDP multicast is same as TCP api server
+2 -3
View File
@@ -10,9 +10,8 @@
#include "target_specific.h"
#include <WiFi.h>
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
#if HAS_ETHERNET && defined(USE_CH390D)
+2 -3
View File
@@ -9,9 +9,8 @@
#include <WiFi.h>
#endif
#if HAS_ETHERNET && defined(USE_WS5500)
#include <ETHClass2.h>
#define ETH ETH2
#if HAS_ETHERNET && defined(ARCH_ESP32)
#include <ETH.h>
#endif // HAS_ETHERNET
extern bool needReconnect;
+3 -3
View File
@@ -262,7 +262,7 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
break;
}
case meshtastic_AdminMessage_ota_request_tag: {
#if defined(ARCH_ESP32)
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WIFI
LOG_INFO("OTA Requested");
if (r->ota_request.ota_hash.size != 32) {
@@ -824,7 +824,7 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
// Ensure initRegion() uses the newly validated region
config.lora.region = validatedLora.region;
initRegion();
if (myRegion->dutyCycle < 100) {
if (getEffectiveDutyCycle() < 100) {
validatedLora.ignore_mqtt = true; // Ignore MQTT by default if region has a duty cycle limit
}
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
@@ -1013,11 +1013,11 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c)
case meshtastic_ModuleConfig_neighbor_info_tag:
LOG_INFO("Set module config: Neighbor Info");
moduleConfig.has_neighbor_info = true;
moduleConfig.neighbor_info = c.payload_variant.neighbor_info;
if (moduleConfig.neighbor_info.update_interval < min_neighbor_info_broadcast_secs) {
LOG_DEBUG("Tried to set update_interval too low, setting to %d", default_neighbor_info_broadcast_secs);
moduleConfig.neighbor_info.update_interval = default_neighbor_info_broadcast_secs;
}
moduleConfig.neighbor_info = c.payload_variant.neighbor_info;
break;
case meshtastic_ModuleConfig_detection_sensor_tag:
LOG_INFO("Set module config: Detection Sensor");
+5 -201
View File
@@ -1,207 +1,11 @@
#include "AtakPluginModule.h"
#include "Default.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "PowerFSM.h"
#include "configuration.h"
#include "main.h"
#include "mesh/compression/unishox2.h"
#include "meshUtils.h"
#include "meshtastic/atak.pb.h"
AtakPluginModule *atakPluginModule;
AtakPluginModule::AtakPluginModule()
: ProtobufModule("atak", meshtastic_PortNum_ATAK_PLUGIN, &meshtastic_TAKPacket_msg), concurrency::OSThread("AtakPlugin")
AtakPluginModule::AtakPluginModule() : SinglePortModule("atak", meshtastic_PortNum_ATAK_PLUGIN_V2) {}
ProcessMessage AtakPluginModule::handleReceived(const meshtastic_MeshPacket &mp)
{
ourPortNum = meshtastic_PortNum_ATAK_PLUGIN;
(void)mp; // Passthrough — no processing needed, apps handle compression/decompression
return ProcessMessage::CONTINUE;
}
/*
Encompasses the full construction and sending packet to mesh
Will be used for broadcast.
*/
int32_t AtakPluginModule::runOnce()
{
return default_broadcast_interval_secs;
}
bool AtakPluginModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_TAKPacket *r)
{
return false;
}
meshtastic_TAKPacket AtakPluginModule::cloneTAKPacketData(meshtastic_TAKPacket *t)
{
meshtastic_TAKPacket clone = meshtastic_TAKPacket_init_zero;
if (t->has_group) {
clone.has_group = true;
clone.group = t->group;
}
if (t->has_status) {
clone.has_status = true;
clone.status = t->status;
}
if (t->has_contact) {
clone.has_contact = true;
clone.contact = {0};
}
if (t->which_payload_variant == meshtastic_TAKPacket_pli_tag) {
clone.which_payload_variant = meshtastic_TAKPacket_pli_tag;
clone.payload_variant.pli = t->payload_variant.pli;
} else if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) {
clone.which_payload_variant = meshtastic_TAKPacket_chat_tag;
clone.payload_variant.chat = {0};
} else if (t->which_payload_variant == meshtastic_TAKPacket_detail_tag) {
clone.which_payload_variant = meshtastic_TAKPacket_detail_tag;
clone.payload_variant.detail.size = t->payload_variant.detail.size;
memcpy(clone.payload_variant.detail.bytes, t->payload_variant.detail.bytes, t->payload_variant.detail.size);
}
return clone;
}
void AtakPluginModule::alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_TAKPacket *t)
{
// From Phone (EUD)
if (mp.from == 0) {
LOG_DEBUG("Received uncompressed TAK payload from phone: %d bytes", mp.decoded.payload.size);
// Compress for LoRA transport
auto compressed = cloneTAKPacketData(t);
compressed.is_compressed = true;
if (t->has_contact) {
auto length = unishox2_compress_lines(
t->contact.callsign, pb_string_length(t->contact.callsign, sizeof(t->contact.callsign)),
compressed.contact.callsign, sizeof(compressed.contact.callsign) - 1, USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Compress overflow contact.callsign. Revert to uncompressed packet");
return;
}
LOG_DEBUG("Compressed callsign: %d bytes", length);
length = unishox2_compress_lines(
t->contact.device_callsign, pb_string_length(t->contact.device_callsign, sizeof(t->contact.device_callsign)),
compressed.contact.device_callsign, sizeof(compressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Compress overflow contact.device_callsign. Revert to uncompressed packet");
return;
}
LOG_DEBUG("Compressed device_callsign: %d bytes", length);
}
if (t->which_payload_variant == meshtastic_TAKPacket_chat_tag) {
auto length = unishox2_compress_lines(
t->payload_variant.chat.message,
pb_string_length(t->payload_variant.chat.message, sizeof(t->payload_variant.chat.message)),
compressed.payload_variant.chat.message, sizeof(compressed.payload_variant.chat.message) - 1, USX_PSET_DFLT,
NULL);
if (length < 0) {
LOG_WARN("Compress overflow chat.message. Revert to uncompressed packet");
return;
}
LOG_DEBUG("Compressed chat message: %d bytes", length);
if (t->payload_variant.chat.has_to) {
compressed.payload_variant.chat.has_to = true;
length = unishox2_compress_lines(
t->payload_variant.chat.to, pb_string_length(t->payload_variant.chat.to, sizeof(t->payload_variant.chat.to)),
compressed.payload_variant.chat.to, sizeof(compressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Compress overflow chat.to. Revert to uncompressed packet");
return;
}
LOG_DEBUG("Compressed chat to: %d bytes", length);
}
if (t->payload_variant.chat.has_to_callsign) {
compressed.payload_variant.chat.has_to_callsign = true;
length = unishox2_compress_lines(
t->payload_variant.chat.to_callsign,
pb_string_length(t->payload_variant.chat.to_callsign, sizeof(t->payload_variant.chat.to_callsign)),
compressed.payload_variant.chat.to_callsign, sizeof(compressed.payload_variant.chat.to_callsign) - 1,
USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Compress overflow chat.to_callsign. Revert to uncompressed packet");
return;
}
LOG_DEBUG("Compressed chat to_callsign: %d bytes", length);
}
}
mp.decoded.payload.size = pb_encode_to_bytes(mp.decoded.payload.bytes, sizeof(mp.decoded.payload.bytes),
meshtastic_TAKPacket_fields, &compressed);
LOG_DEBUG("Final payload: %d bytes", mp.decoded.payload.size);
} else {
if (!t->is_compressed) {
// Not compressed. Something is wrong
LOG_WARN("Received uncompressed TAKPacket over radio! Skip");
return;
}
// Decompress for Phone (EUD)
auto uncompressed = cloneTAKPacketData(t);
uncompressed.is_compressed = false;
if (t->has_contact) {
auto length = unishox2_decompress_lines(
t->contact.callsign, pb_string_length(t->contact.callsign, sizeof(t->contact.callsign)),
uncompressed.contact.callsign, sizeof(uncompressed.contact.callsign) - 1, USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Decompress overflow contact.callsign. Bailing out");
return;
}
LOG_DEBUG("Decompressed callsign: %d bytes", length);
length = unishox2_decompress_lines(
t->contact.device_callsign, pb_string_length(t->contact.device_callsign, sizeof(t->contact.device_callsign)),
uncompressed.contact.device_callsign, sizeof(uncompressed.contact.device_callsign) - 1, USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Decompress overflow contact.device_callsign. Bailing out");
return;
}
LOG_DEBUG("Decompressed device_callsign: %d bytes", length);
}
if (uncompressed.which_payload_variant == meshtastic_TAKPacket_chat_tag) {
auto length = unishox2_decompress_lines(
t->payload_variant.chat.message,
pb_string_length(t->payload_variant.chat.message, sizeof(t->payload_variant.chat.message)),
uncompressed.payload_variant.chat.message, sizeof(uncompressed.payload_variant.chat.message) - 1, USX_PSET_DFLT,
NULL);
if (length < 0) {
LOG_WARN("Decompress overflow chat.message. Bailing out");
return;
}
LOG_DEBUG("Decompressed chat message: %d bytes", length);
if (t->payload_variant.chat.has_to) {
uncompressed.payload_variant.chat.has_to = true;
length = unishox2_decompress_lines(
t->payload_variant.chat.to, pb_string_length(t->payload_variant.chat.to, sizeof(t->payload_variant.chat.to)),
uncompressed.payload_variant.chat.to, sizeof(uncompressed.payload_variant.chat.to) - 1, USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Decompress overflow chat.to. Bailing out");
return;
}
LOG_DEBUG("Decompressed chat to: %d bytes", length);
}
if (t->payload_variant.chat.has_to_callsign) {
uncompressed.payload_variant.chat.has_to_callsign = true;
length = unishox2_decompress_lines(
t->payload_variant.chat.to_callsign,
pb_string_length(t->payload_variant.chat.to_callsign, sizeof(t->payload_variant.chat.to_callsign)),
uncompressed.payload_variant.chat.to_callsign, sizeof(uncompressed.payload_variant.chat.to_callsign) - 1,
USX_PSET_DFLT, NULL);
if (length < 0) {
LOG_WARN("Decompress overflow chat.to_callsign. Bailing out");
return;
}
LOG_DEBUG("Decompressed chat to_callsign: %d bytes", length);
}
}
auto decompressedCopy = packetPool.allocCopy(mp);
decompressedCopy->decoded.payload.size =
pb_encode_to_bytes(decompressedCopy->decoded.payload.bytes, sizeof(decompressedCopy->decoded.payload),
meshtastic_TAKPacket_fields, &uncompressed);
service->sendToPhone(decompressedCopy);
}
return;
}
+10 -12
View File
@@ -1,11 +1,15 @@
#pragma once
#include "ProtobufModule.h"
#include "meshtastic/atak.pb.h"
#include "SinglePortModule.h"
/**
* Waypoint message handling for meshtastic
* ATAK Plugin V2 module - passthrough for ATAK_PLUGIN_V2 payloads.
* The wire format includes a leading flags byte followed by opaque payload bytes.
* Depending on the flags, the payload may be zstd dictionary-compressed or raw/uncompressed protobuf.
* Compression/decompression and payload interpretation are handled by the apps
* (Android, iOS, ATAK plugin); firmware forwards the bytes unchanged on the
* ATAK_PLUGIN_V2 port.
*/
class AtakPluginModule : public ProtobufModule<meshtastic_TAKPacket>, private concurrency::OSThread
class AtakPluginModule : public SinglePortModule
{
public:
/** Constructor
@@ -14,13 +18,7 @@ class AtakPluginModule : public ProtobufModule<meshtastic_TAKPacket>, private co
AtakPluginModule();
protected:
virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_TAKPacket *t) override;
virtual void alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_TAKPacket *t) override;
/* Does our periodic broadcast */
int32_t runOnce() override;
private:
meshtastic_TAKPacket cloneTAKPacketData(meshtastic_TAKPacket *t);
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
};
extern AtakPluginModule *atakPluginModule;
extern AtakPluginModule *atakPluginModule;
+9 -8
View File
@@ -6,6 +6,7 @@
#include "CannedMessageModule.h"
#include "Channels.h"
#include "FSCommon.h"
#include "MeshRadio.h"
#include "MeshService.h"
#include "MessageStore.h"
#include "NodeDB.h"
@@ -2103,16 +2104,16 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
static float getSnrLimit(meshtastic_Config_LoRaConfig_ModemPreset preset)
{
switch (preset) {
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE:
case meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST:
case PRESET(LONG_SLOW):
case PRESET(LONG_MODERATE):
case PRESET(LONG_FAST):
return -6.0f;
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST:
case PRESET(MEDIUM_SLOW):
case PRESET(MEDIUM_FAST):
return -5.5f;
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST:
case meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO:
case PRESET(SHORT_SLOW):
case PRESET(SHORT_FAST):
case PRESET(SHORT_TURBO):
return -4.5f;
default:
return -6.0f;
+804
View File
@@ -0,0 +1,804 @@
#include "DMShell.h"
#if defined(ARCH_PORTDUINO)
#include "Channels.h"
#include "MeshService.h"
#include "NodeDB.h"
#include "Throttle.h"
#include "configuration.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include "mesh/mesh-pb-constants.h"
#include "pb_decode.h"
#include "pb_encode.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <pty.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
DMShellModule *dmShellModule;
namespace
{
constexpr uint16_t PTY_COLS_DEFAULT = 120;
constexpr uint16_t PTY_ROWS_DEFAULT = 40;
constexpr size_t MAX_MESSAGE_SIZE = 200;
// --- Half-duplex turn-taking ("talking stick") protocol, carried in RemoteShell.flags ---
// On a 2-party LoRa link, Meshtastic's CSMA-CA breaks down when both ends transmit at once
// (synchronized same-slot collisions that CAD can't prevent). These flags let exactly one
// side transmit at a time, eliminating those collisions. The client is the master/idle-owner.
constexpr uint32_t TURN_FLAG_GRANT = 0x01; // I am handing you the turn; you may transmit now
constexpr uint32_t TURN_FLAG_MORE = 0x02; // I yielded under a budget but still have data queued
constexpr uint32_t TURN_FLAG_RTS = 0x04; // I have output but no turn; please grant me one
constexpr size_t TURN_BUDGET_FRAMES = 8; // max output frames per granted turn before yielding (bounds interrupt latency)
constexpr uint32_t RTS_RETRY_MS = 250; // min interval between request-to-send frames
// After being granted the turn we keep it for a short "linger" window, continuing to drain shell
// output as it appears, instead of yielding the instant the PTY drains. This lets a command's
// output (and the next prompt) ride the same turn as the keystroke that triggered it, avoiding a
// full RTS->grant round-trip per command. The turn still ends promptly once the PTY is idle this
// long, or once TURN_BUDGET_FRAMES is hit (so the client can interject, e.g. Ctrl-C).
constexpr uint32_t TURN_LINGER_MS = 120;
} // namespace
DMShellModule::DMShellModule()
: SinglePortModule("DMShellModule", meshtastic_PortNum_REMOTE_SHELL_APP), concurrency::OSThread("DMShell", 100)
{
LOG_WARN("DMShell enabled on Portduino: remote shell access is dangerous and intended for trusted debugging only");
}
ProcessMessage DMShellModule::handleReceived(const meshtastic_MeshPacket &mp)
{
meshtastic_RemoteShell frame = meshtastic_RemoteShell_init_zero;
if (!mp.pki_encrypted) {
LOG_WARN("DMShell: ignoring packet without PKI from 0x%x", mp.from);
return ProcessMessage::STOP;
}
if (!parseFrame(mp, frame)) {
LOG_WARN("DMShell: ignoring malformed frame");
return ProcessMessage::STOP;
}
if (frame.op == meshtastic_RemoteShell_OpCode_ACK) {
if (session.active && frame.session_id == session.sessionId && getFrom(&mp) == session.peer) {
LOG_WARN("DMShell: Received ack from 0x%x 0x%x", getFrom(&mp), session.peer);
applyTurnFlags(frame);
if (frame.last_rx_seq > 0) {
resendFramesFrom(frame.last_rx_seq + 1);
}
// A standalone grant (client re-granting for MORE, replying to our RTS, or a heartbeat
// poll) is our cue to flush any pending shell output during this turn.
serviceTurn();
}
return ProcessMessage::CONTINUE;
}
if (frame.op >= 64) {
LOG_WARN("DMShell: ignoring frame with op code %d, seq %d", frame.op, frame.seq);
return ProcessMessage::CONTINUE;
}
if (!isAuthorizedPacket(mp)) {
LOG_WARN("DMShell: unauthorized sender 0x%x, %u", mp.from, frame.op);
myReply = allocErrorResponse(meshtastic_Routing_Error_NOT_AUTHORIZED, &mp);
return ProcessMessage::STOP;
}
if (frame.op == meshtastic_RemoteShell_OpCode_OPEN) {
LOG_WARN("DMShell: received OPEN from 0x%x sessionId=0x%x", mp.from, frame.session_id);
if (!openSession(mp, frame)) {
sendError("open_failed", getFrom(&mp));
}
return ProcessMessage::STOP;
}
if (!session.active || frame.session_id != session.sessionId || getFrom(&mp) != session.peer) {
if (!session.active) {
LOG_WARN("DMShell: no active session, rejecting op %d from 0x%x", frame.op, mp.from);
} else {
LOG_WARN("DMShell: session ID mismatch (got 0x%x expected 0x%x) or peer mismatch (got 0x%x expected 0x%x), rejecting "
"op %d",
frame.session_id, session.sessionId, mp.from, session.peer, frame.op);
}
sendError("invalid_session", getFrom(&mp));
return ProcessMessage::STOP;
}
// Honor channel-access flags before ordering checks: a GRANT transfers the turn regardless of
// whether this frame's payload is in order.
applyTurnFlags(frame);
if (!shouldProcessIncomingFrame(frame)) {
// We won't process the payload (gap/duplicate), but we may now hold the turn, so flush
// output and/or hand it back rather than stalling the link.
serviceTurn();
return ProcessMessage::STOP;
}
session.lastActivityMs = millis();
switch (frame.op) {
case meshtastic_RemoteShell_OpCode_INPUT:
if (!writeSessionInput(frame)) {
sendError("input_write_failed");
} else if (!session.turnManaged) {
// Legacy peer (no turn-taking): echo immediately as before. In managed mode the
// serviceTurn() call at the end of handleReceived drains the echo and yields the turn.
uint8_t outBuf[MAX_MESSAGE_SIZE];
const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf));
if (bytesRead > 0) {
LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead);
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_OUTPUT,
.session_id = session.sessionId,
.seq = session.nextTxSeq++,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = 0,
};
assert(bytesRead <= sizeof(frame.payload.bytes));
memcpy(frame.payload.bytes, outBuf, bytesRead);
frame.payload.size = bytesRead;
sendFrameToPeer(session.peer, frame, true);
session.lastActivityMs = millis();
}
}
break;
case meshtastic_RemoteShell_OpCode_RESIZE:
if (frame.rows > 0 && frame.cols > 0) {
struct winsize ws = {};
ws.ws_row = frame.rows;
ws.ws_col = frame.cols;
if (session.masterFd >= 0) {
ioctl(session.masterFd, TIOCSWINSZ, &ws);
}
}
break;
case meshtastic_RemoteShell_OpCode_PING: {
uint32_t peerLastRxSeq = frame.ack_seq;
if (frame.last_rx_seq > 0) {
peerLastRxSeq = frame.last_rx_seq;
}
const uint32_t nextMissingForPeer = peerLastRxSeq + 1;
if (nextMissingForPeer > 0 && nextMissingForPeer < session.nextTxSeq) {
resendFramesFrom(nextMissingForPeer);
}
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_PONG,
.session_id = session.sessionId,
.seq = session.nextTxSeq++,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = 0,
.last_tx_seq = session.nextTxSeq > 0 ? session.nextTxSeq - 1 : 0,
.last_rx_seq = session.lastAckedRxSeq,
};
frame.payload.size = 0;
sendFrameToPeer(session.peer, frame, true);
break;
}
case meshtastic_RemoteShell_OpCode_CLOSE:
closeSession("peer_close", true);
break;
default:
sendError("unsupported_op");
break;
}
// If the peer granted us the turn, flush pending shell output and hand the turn back.
serviceTurn();
return ProcessMessage::STOP;
}
int32_t DMShellModule::runOnce()
{
processPendingChildReap();
if (!session.active) {
return 100;
}
reapChildIfExited();
if (!session.active) {
return 100;
}
if (Throttle::isWithinTimespanMs(session.lastActivityMs, SESSION_IDLE_TIMEOUT_MS) == false) {
closeSession("idle_timeout", true);
return 100;
}
if (RadioLibInterface::instance->packetsInTxQueue() > 1) {
return 50;
}
if (session.turnManaged) {
if (session.hasToken) {
// We hold the turn: flush output and hand it back.
serviceTurn();
} else if (ptyHasOutput() && !Throttle::isWithinTimespanMs(session.lastRtsMs, RTS_RETRY_MS)) {
// Unsolicited shell output but no turn: ask the client to grant us one.
sendRts();
session.lastRtsMs = millis();
}
return 50;
}
// Legacy free-send path (peer is not using turn-taking).
uint8_t outBuf[MAX_MESSAGE_SIZE];
while (session.masterFd >= 0) {
const ssize_t bytesRead = read(session.masterFd, outBuf, sizeof(outBuf));
if (bytesRead > 0) {
LOG_WARN("DMShell: read %zd bytes from PTY", bytesRead);
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_OUTPUT,
.session_id = session.sessionId,
.seq = session.nextTxSeq++,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = 0,
};
assert(bytesRead <= sizeof(frame.payload.bytes));
memcpy(frame.payload.bytes, outBuf, bytesRead);
frame.payload.size = bytesRead;
sendFrameToPeer(session.peer, frame, true);
session.lastActivityMs = millis();
// continue;
// do we want to ack every data message, and only send the next on ack?
// would require some retry logic. Maybe re-use the wantAck bit
return 50;
}
if (bytesRead == 0) {
closeSession("pty_eof", true);
break;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
break;
}
LOG_WARN("DMShell: PTY read error errno=%d", errno);
closeSession("pty_read_error", true);
break;
}
return 100;
}
bool DMShellModule::parseFrame(const meshtastic_MeshPacket &mp, meshtastic_RemoteShell &outFrame)
{
if (mp.which_payload_variant != meshtastic_MeshPacket_decoded_tag) {
return false;
}
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_RemoteShell_fields, &outFrame)) {
LOG_INFO("Received a DMShell message");
} else {
LOG_ERROR("Error decoding DMShell message!");
return false;
}
return true;
}
bool DMShellModule::isAuthorizedPacket(const meshtastic_MeshPacket &mp) const
{
if (mp.from == 0) {
return !config.security.is_managed;
}
const meshtastic_Channel *ch = &channels.getByIndex(mp.channel);
if (strcasecmp(ch->settings.name, Channels::adminChannel) == 0) {
return config.security.admin_channel_enabled;
}
if (mp.pki_encrypted) {
for (uint8_t i = 0; i < 3; ++i) {
if (config.security.admin_key[i].size == 32 &&
memcmp(mp.public_key.bytes, config.security.admin_key[i].bytes, 32) == 0) {
return true;
}
}
}
return false;
}
bool DMShellModule::openSession(const meshtastic_MeshPacket &mp, const meshtastic_RemoteShell &frame)
{
if (session.active) {
closeSession("preempted", false);
}
int masterFd = -1;
struct winsize ws = {};
if (frame.rows > 0) {
ws.ws_row = frame.rows;
} else {
ws.ws_row = PTY_ROWS_DEFAULT;
}
if (frame.cols > 0) {
ws.ws_col = frame.cols;
} else {
ws.ws_col = PTY_COLS_DEFAULT;
}
const pid_t childPid = forkpty(&masterFd, nullptr, nullptr, &ws);
if (childPid < 0) {
LOG_ERROR("DMShell: forkpty failed errno=%d", errno);
return false;
}
if (childPid == 0) {
const char *shell = getenv("SHELL");
if (!shell || !*shell) {
shell = "/bin/sh";
}
execl(shell, shell, "-i", static_cast<char *>(nullptr));
_exit(127);
}
const int flags = fcntl(masterFd, F_GETFL, 0);
if (flags >= 0) {
fcntl(masterFd, F_SETFL, flags | O_NONBLOCK);
}
session.active = true;
session.sessionId = (frame.session_id != 0) ? frame.session_id : static_cast<uint32_t>(random(1, 0x7fffffff));
session.peer = getFrom(&mp);
session.channel = mp.channel;
session.masterFd = masterFd;
session.childPid = childPid;
session.nextTxSeq = 1;
session.lastAckedRxSeq = frame.seq;
session.nextExpectedRxSeq = frame.seq + 1;
session.highestSeenRxSeq = frame.seq;
session.lastActivityMs = millis();
session.turnManaged = true;
session.hasToken = false;
session.lastRtsMs = 0;
// Honor any GRANT the client put on OPEN (opts this session into turn-taking).
applyTurnFlags(frame);
meshtastic_RemoteShell newFrame = {
.op = meshtastic_RemoteShell_OpCode_OPEN_OK,
.session_id = session.sessionId,
.seq = session.nextTxSeq++,
.ack_seq = frame.seq,
.cols = ws.ws_col,
.rows = ws.ws_row,
.flags = session.turnManaged ? TURN_FLAG_GRANT : 0u,
};
newFrame.payload.size = 0;
sendFrameToPeer(session.peer, newFrame, true);
if (session.turnManaged) {
// OPEN_OK handed the turn back to the client; it is now the idle-owner.
session.hasToken = false;
}
LOG_INFO("DMShell: opened session=0x%x peer=0x%x pid=%d", session.sessionId, session.peer, session.childPid);
return true;
}
bool DMShellModule::writeSessionInput(const meshtastic_RemoteShell &frame)
{
if (session.masterFd < 0) {
return false;
}
if (frame.payload.size == 0) {
return true;
}
const ssize_t bytesWritten = write(session.masterFd, frame.payload.bytes, frame.payload.size);
return bytesWritten >= 0;
}
void DMShellModule::closeSession(const char *reason, bool notifyPeer)
{
if (!session.active) {
return;
}
if (notifyPeer) {
const size_t reasonLen = strnlen(reason, 256);
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_CLOSED,
.session_id = session.sessionId,
.seq = session.nextTxSeq++,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = 0,
};
assert(reasonLen <= sizeof(frame.payload.bytes));
memcpy(frame.payload.bytes, reason, reasonLen);
frame.payload.size = reasonLen;
sendFrameToPeer(session.peer, frame, true);
}
if (session.masterFd >= 0) {
close(session.masterFd);
session.masterFd = -1;
}
if (session.childPid > 0) {
// Run this to avoid forgetting a child
processPendingChildReap();
if (kill(session.childPid, SIGTERM) < 0 && errno != ESRCH) {
LOG_WARN("DMShell: failed to send SIGTERM to pid=%d errno=%d", session.childPid, errno);
}
pendingChildPid = session.childPid;
session.childPid = -1;
}
LOG_INFO("DMShell: closed session=0x%x reason=%s", session.sessionId, reason);
session = DMShellSession{};
}
void DMShellModule::reapChildIfExited()
{
if (!session.active || session.childPid <= 0) {
return;
}
int status = 0;
const pid_t result = waitpid(session.childPid, &status, WNOHANG);
if (result == session.childPid) {
closeSession("shell_exited", true);
}
}
void DMShellModule::processPendingChildReap()
{
if (pendingChildPid <= 0) {
return;
}
int status = 0;
const pid_t result = waitpid(pendingChildPid, &status, WNOHANG);
if (result == pendingChildPid || (result < 0 && errno == ECHILD)) {
pendingChildPid = -1;
return;
}
if (result < 0) {
LOG_WARN("DMShell: waitpid failed for pid=%d errno=%d", pendingChildPid, errno);
pendingChildPid = -1;
return;
}
if (pendingChildPid > 0) {
if (kill(pendingChildPid, SIGKILL) < 0 && errno != ESRCH) {
LOG_WARN("DMShell: failed to send SIGKILL to pid=%d errno=%d", pendingChildPid, errno);
}
pendingChildPid = -1;
}
}
void DMShellModule::rememberSentFrame(meshtastic_RemoteShell frame)
{
if (frame.seq == 0 || frame.op == meshtastic_RemoteShell_OpCode_ACK) {
return;
}
auto &entry = session.txHistory[session.txHistoryNext];
entry.valid = true;
entry.op = frame.op;
entry.sessionId = frame.session_id;
entry.seq = frame.seq;
entry.ackSeq = frame.ack_seq;
entry.cols = frame.cols;
entry.rows = frame.rows;
entry.flags = frame.flags;
entry.payloadLen = frame.payload.size;
if (frame.payload.size > 0) {
memcpy(entry.payload, frame.payload.bytes, frame.payload.size);
}
session.txHistoryNext = (session.txHistoryNext + 1) % session.txHistory.size();
}
void DMShellModule::resendFramesFrom(uint32_t startSeq)
{
if (startSeq == 0) {
return;
}
DMShellSession::SentFrame *match = nullptr;
for (auto &entry : session.txHistory) {
if (!entry.valid || entry.seq != startSeq) {
continue;
}
match = &entry;
break;
}
if (!match) {
LOG_WARN("DMShell: replay request for seq=%u not found in history", startSeq);
return;
}
LOG_INFO("DMShell: replaying frame seq=%u op=%d", match->seq, match->op);
meshtastic_RemoteShell frame = {
.op = match->op,
.session_id = match->sessionId,
.seq = match->seq,
.ack_seq = match->ackSeq,
.cols = match->cols,
.rows = match->rows,
.flags = match->flags,
};
assert(match->payloadLen <= sizeof(frame.payload.bytes));
memcpy(frame.payload.bytes, match->payload, match->payloadLen);
frame.payload.size = match->payloadLen;
sendFrameToPeer(session.peer, frame, false);
}
void DMShellModule::sendAck(uint32_t replayFromSeq)
{
if (replayFromSeq > 0) {
LOG_WARN("DMShell: requesting replay from seq=%u", replayFromSeq);
}
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_ACK,
.session_id = session.sessionId,
.seq = 0,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = 0,
.last_rx_seq = replayFromSeq - 1,
};
frame.payload.size = 0;
sendFrameToPeer(session.peer, frame, false);
}
bool DMShellModule::shouldProcessIncomingFrame(const meshtastic_RemoteShell &frame)
{
if (frame.seq == 0) {
return true;
}
if (frame.seq < session.nextExpectedRxSeq) {
if (session.highestSeenRxSeq >= session.nextExpectedRxSeq) {
sendAck(session.nextExpectedRxSeq);
} else {
sendAck();
}
return false;
}
if (frame.seq > session.nextExpectedRxSeq) {
if (frame.seq > session.highestSeenRxSeq) {
session.highestSeenRxSeq = frame.seq;
}
sendAck(session.nextExpectedRxSeq);
return false;
}
session.lastAckedRxSeq = frame.seq;
session.nextExpectedRxSeq = frame.seq + 1;
if (frame.seq > session.highestSeenRxSeq) {
session.highestSeenRxSeq = frame.seq;
}
if (session.highestSeenRxSeq >= session.nextExpectedRxSeq) {
sendAck(session.nextExpectedRxSeq);
} else {
session.highestSeenRxSeq = 0;
}
return true;
}
void DMShellModule::sendFrameToPeer(NodeNum peer, meshtastic_RemoteShell frame, bool remember)
{
meshtastic_MeshPacket *packet = allocDataPacket();
if (!packet) {
return;
}
LOG_WARN("DMShell: building packet op=%u session=0x%x seq=%u payloadLen=%zu", frame.op, frame.session_id, frame.seq,
frame.payload.size);
const size_t encoded = pb_encode_to_bytes(packet->decoded.payload.bytes, sizeof(packet->decoded.payload.bytes),
meshtastic_RemoteShell_fields, &frame);
if (encoded == 0) {
return;
}
packet->decoded.payload.size = encoded;
if (remember) {
rememberSentFrame(frame);
}
packet->to = peer;
packet->hop_limit = 0;
packet->hop_start = 0;
packet->channel = 0;
packet->want_ack = false;
packet->pki_encrypted = true;
packet->priority = meshtastic_MeshPacket_Priority_RELIABLE;
service->sendToMesh(packet);
}
void DMShellModule::applyTurnFlags(const meshtastic_RemoteShell &frame)
{
if (frame.flags & (TURN_FLAG_GRANT | TURN_FLAG_MORE | TURN_FLAG_RTS)) {
session.turnManaged = true; // peer speaks turn-taking; enable gating for this session
}
if (frame.flags & TURN_FLAG_GRANT) {
if (!session.hasToken) {
// Fresh turn: start the linger window and reset the per-turn budget.
session.turnDeadlineMs = millis() + TURN_LINGER_MS;
session.turnFramesSent = 0;
}
session.hasToken = true;
}
}
bool DMShellModule::ptyHasOutput()
{
if (session.masterFd < 0) {
return false;
}
struct pollfd pfd = {};
pfd.fd = session.masterFd;
pfd.events = POLLIN;
return poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN);
}
void DMShellModule::sendOutputFrame(const uint8_t *data, size_t len, uint32_t extraFlags)
{
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_OUTPUT,
.session_id = session.sessionId,
.seq = session.nextTxSeq++,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = extraFlags,
};
assert(len <= sizeof(frame.payload.bytes));
memcpy(frame.payload.bytes, data, len);
frame.payload.size = len;
sendFrameToPeer(session.peer, frame, true);
}
void DMShellModule::sendTurnGrant(bool more)
{
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_ACK,
.session_id = session.sessionId,
.seq = 0,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = TURN_FLAG_GRANT | (more ? TURN_FLAG_MORE : 0u),
.last_rx_seq = 0,
};
frame.payload.size = 0;
sendFrameToPeer(session.peer, frame, false);
}
void DMShellModule::sendRts()
{
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_ACK,
.session_id = session.sessionId,
.seq = 0,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = TURN_FLAG_RTS,
.last_rx_seq = 0,
};
frame.payload.size = 0;
sendFrameToPeer(session.peer, frame, false);
}
// Called (every tick) while we hold the turn. Drains available shell output and sends it
// immediately, then decides whether to keep the turn (linger, to catch output that is about to
// appear) or hand it back. The turn is yielded once the per-turn budget is hit (so the client can
// interject, e.g. Ctrl-C) or once the PTY has been idle past the linger window. Output frames go
// out as soon as they are read (no extra delay); the grant is a trailing ACK.
void DMShellModule::serviceTurn()
{
if (!session.active || !session.turnManaged || !session.hasToken) {
return;
}
uint8_t buf[MAX_MESSAGE_SIZE];
bool eof = false;
bool readError = false;
// Drain whatever is available right now, up to the remaining per-turn budget.
while (session.turnFramesSent < TURN_BUDGET_FRAMES && session.masterFd >= 0) {
const ssize_t n = read(session.masterFd, buf, sizeof(buf));
if (n > 0) {
sendOutputFrame(buf, (size_t)n, 0u);
session.turnFramesSent++;
session.lastActivityMs = millis();
session.turnDeadlineMs = millis() + TURN_LINGER_MS; // extend the linger while output flows
} else if (n == 0) {
eof = true;
break;
} else {
if (errno != EAGAIN && errno != EWOULDBLOCK) {
readError = true;
}
break;
}
}
if (eof || readError) {
session.hasToken = false;
sendTurnGrant(false);
if (eof) {
closeSession("pty_eof", true);
} else {
LOG_WARN("DMShell: PTY read error errno=%d", errno);
closeSession("pty_read_error", true);
}
return;
}
const bool morePending = ptyHasOutput();
if (session.turnFramesSent >= TURN_BUDGET_FRAMES) {
// Hit the per-turn budget: yield so the client gets a chance to interject (e.g. Ctrl-C).
session.hasToken = false;
sendTurnGrant(morePending);
return;
}
if (!morePending && (int32_t)(millis() - session.turnDeadlineMs) >= 0) {
// PTY has been idle past the linger window: hand the turn back.
session.hasToken = false;
sendTurnGrant(false);
return;
}
// Otherwise keep holding the turn: there is more to drain next pass, or we are lingering for
// output that may be about to appear. runOnce re-enters serviceTurn on the next tick.
}
void DMShellModule::sendError(const char *message, NodeNum peer)
{
const size_t len = strnlen(message, MAX_MESSAGE_SIZE);
meshtastic_RemoteShell frame = {
.op = meshtastic_RemoteShell_OpCode_ERROR,
.session_id = session.sessionId,
.seq = session.nextTxSeq++,
.ack_seq = session.lastAckedRxSeq,
.cols = 0,
.rows = 0,
.flags = 0,
};
if (message && len > 0) {
assert(len <= sizeof(frame.payload.bytes));
memcpy(frame.payload.bytes, message, len);
frame.payload.size = len;
}
if (peer == 0) {
peer = session.peer;
}
sendFrameToPeer(peer, frame, true);
}
#endif
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include "MeshModule.h"
#include "Router.h"
#include "SinglePortModule.h"
#include "concurrency/OSThread.h"
#include "configuration.h"
#include "mesh/generated/meshtastic/mesh.pb.h"
#include <Arduino.h>
#include <array>
#include <functional>
#if defined(ARCH_PORTDUINO)
struct DMShellSession {
bool active = false;
uint32_t sessionId = 0;
NodeNum peer = 0;
uint8_t channel = 0;
int masterFd = -1;
int childPid = -1;
uint32_t nextTxSeq = 1;
uint32_t lastAckedRxSeq = 0;
uint32_t nextExpectedRxSeq = 1;
uint32_t highestSeenRxSeq = 0;
uint32_t lastActivityMs = 0;
// --- Half-duplex turn-taking ("talking stick") state ---
bool turnManaged = false; // becomes true once the peer uses turn-taking flags; enables gating
bool hasToken = false; // do we currently hold the right to transmit?
uint32_t lastRtsMs = 0; // last time we asked the client for a turn (rate-limit)
uint32_t turnDeadlineMs = 0; // while holding the turn, keep it until this time (extended on output)
uint32_t turnFramesSent = 0; // output frames sent in the current held turn (vs TURN_BUDGET_FRAMES)
struct SentFrame {
bool valid = false;
meshtastic_RemoteShell_OpCode op = meshtastic_RemoteShell_OpCode_ERROR;
uint32_t sessionId = 0;
uint32_t seq = 0;
uint32_t ackSeq = 0;
uint32_t cols = 0;
uint32_t rows = 0;
uint32_t flags = 0;
uint8_t payload[meshtastic_Constants_DATA_PAYLOAD_LEN] = {0};
size_t payloadLen = 0;
};
std::array<SentFrame, 50> txHistory = {};
size_t txHistoryNext = 0;
};
class DMShellModule : private concurrency::OSThread, public SinglePortModule
{
public:
DMShellModule();
protected:
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
virtual int32_t runOnce() override;
private:
static constexpr uint32_t SESSION_IDLE_TIMEOUT_MS = 5 * 60 * 1000;
DMShellSession session;
pid_t pendingChildPid = -1;
bool parseFrame(const meshtastic_MeshPacket &mp, meshtastic_RemoteShell &outFrame);
bool isAuthorizedPacket(const meshtastic_MeshPacket &mp) const;
bool openSession(const meshtastic_MeshPacket &mp, const meshtastic_RemoteShell &frame);
bool shouldProcessIncomingFrame(const meshtastic_RemoteShell &frame);
bool writeSessionInput(const meshtastic_RemoteShell &frame);
void closeSession(const char *reason, bool notifyPeer);
void reapChildIfExited();
void processPendingChildReap();
void rememberSentFrame(meshtastic_RemoteShell frame);
void resendFramesFrom(uint32_t startSeq);
void sendAck(uint32_t replayFromSeq = 0);
void sendFrameToPeer(NodeNum peer, meshtastic_RemoteShell frame, bool remember = true);
void sendError(const char *message, NodeNum peer = 0);
// --- Turn-taking helpers ---
void applyTurnFlags(const meshtastic_RemoteShell &frame);
bool ptyHasOutput();
void serviceTurn();
void sendOutputFrame(const uint8_t *data, size_t len, uint32_t extraFlags);
void sendTurnGrant(bool more);
void sendRts();
};
extern DMShellModule *dmShellModule;
#endif
@@ -206,6 +206,10 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#ifdef PCA_LED_NOTIFICATION
io.digitalWrite(PCA_LED_NOTIFICATION, on);
#endif
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
notificationPixel.setPixelColor(0, on ? NEOPIXEL_STATUS_NOTIFICATION_COLOR : 0);
notificationPixel.show();
#endif
break;
}
@@ -324,6 +328,12 @@ ExternalNotificationModule::ExternalNotificationModule()
LOG_INFO("Use Pin %i in digital mode", output);
pinMode(output, OUTPUT);
}
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
LOG_INFO("Use WS2812 on GPIO %d as notification LED", NEOPIXEL_STATUS_NOTIFICATION_PIN);
notificationPixel.begin();
notificationPixel.clear();
notificationPixel.show();
#endif
setExternalState(0, false);
externalTurnedOn[0] = 0;
if (moduleConfig.external_notification.output_vibra) {
+17
View File
@@ -10,6 +10,19 @@
extern AmbientLightingThread *ambientLightingThread;
#endif
// Drive a single WS2812 as the notification LED (M1/M2-style LED_NOTIFICATION
// but addressable). A variant defines NEOPIXEL_STATUS_NOTIFICATION_PIN to
// enable. Colour defaults to green but can be overridden.
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
#include <Adafruit_NeoPixel.h>
#ifndef NEOPIXEL_STATUS_TYPE
#define NEOPIXEL_STATUS_TYPE (NEO_GRB + NEO_KHZ800)
#endif
#ifndef NEOPIXEL_STATUS_NOTIFICATION_COLOR
#define NEOPIXEL_STATUS_NOTIFICATION_COLOR 0x00FF00 // green
#endif
#endif
#if !defined(ARCH_PORTDUINO) && !defined(ARCH_STM32WL) && !defined(CONFIG_IDF_TARGET_ESP32C6)
#include <NonBlockingRtttl.h>
#else
@@ -38,6 +51,10 @@ class ExternalNotificationModule : public SinglePortModule, private concurrency:
CallbackObserver<ExternalNotificationModule, const InputEvent *>(this, &ExternalNotificationModule::handleInputEvent);
uint32_t output = 0;
#ifdef NEOPIXEL_STATUS_NOTIFICATION_PIN
Adafruit_NeoPixel notificationPixel = Adafruit_NeoPixel(1, NEOPIXEL_STATUS_NOTIFICATION_PIN, NEOPIXEL_STATUS_TYPE);
#endif
public:
ExternalNotificationModule();
+2
View File
@@ -49,6 +49,7 @@
#include "modules/WaypointModule.h"
#endif
#if ARCH_PORTDUINO
#include "modules/DMShell.h"
#include "modules/Telemetry/HostMetrics.h"
#if !MESHTASTIC_EXCLUDE_STOREFORWARD
#include "modules/StoreForwardModule.h"
@@ -195,6 +196,7 @@ void setupModules()
#endif
#if ARCH_PORTDUINO
new HostMetricsModule();
dmShellModule = new DMShellModule();
#endif
#if HAS_TELEMETRY
new DeviceTelemetryModule();

Some files were not shown because too many files have changed in this diff Show More