Compare commits

..
185 Commits
Author SHA1 Message Date
Ben Meadors a1acad6dc4 Add support for Wi-Fi HaLow on Seeed XIAO ESP32-S3 2026-05-16 09:48:17 -05:00
Ben Meadors 2a91d186eb Add max_session_seconds to LockdownAuth for session management 2026-05-15 15:14:29 -05:00
4827498188 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-14 20:51:44 -05:00
Ben MeadorsandGitHub 3a0c08b695 Merge pull request #10474 from meshtastic/2.8
Develop is 2.8 WIP now
2026-05-13 19:36:15 -05:00
Ben Meadors 7bdff8ff70 Bump protos 2026-05-13 14:33:15 -05:00
Ben Meadors 35b0590408 Develop is 2.8 WIP now 2026-05-13 13:50:17 -05:00
Ben MeadorsandGitHub cbddf07bc8 Merge pull request #10472 from meshtastic/remove-arial 2026-05-13 12:01:02 -05:00
Jonathan BennettGitHubBen MeadorsHarukiToredaJason Pcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>thebentern
871194517d Ble banner (#8902)
* Drop unneeded Sizeof() instances

* Use SimpleBanner for BLE pin

* Support for different font sizes on notification banner

* Fix NRF52 BLE cppcheck shadow warning

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/de12b52c-49d5-452a-b3fb-344724649270

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

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-05-13 11:02:19 -05:00
Ben Meadors 5a1d2b9ef4 Refine nRF52 flash optimization comment for FONT_LARGE_LOCAL definition 2026-05-13 10:52:05 -05:00
Ben Meadors 9cd3a86938 Cleanup 2026-05-13 10:43:16 -05:00
Ben Meadors 748668b8e9 Remove ARIAL24 on NRF52 2026-05-13 10:13:53 -05:00
Ben Meadors 778d1ad90f Merge remote-tracking branch 'origin/master' into develop 2026-05-13 09:40:16 -05:00
Ben Meadors 75b7a7df4f Missed one 2026-05-13 08:50:15 -05:00
Ben Meadors 8110887be2 Turns out it's already excluded 2026-05-13 07:57:54 -05:00
Ben Meadors 5f734dabf9 Trunk 2026-05-13 06:44:51 -05:00
Andros FenollosaandGitHub 039ad42758 Fix WiFi TCP/HTTP services not starting without USB serial connected (#10460)
Move WiFi.onEvent(WiFiEvent) registration before createSSLCert() to
prevent a race where the ESP32 auto-reconnects during cert generation
and fires GOT_IP before the handler is attached, causing
onNetworkConnected() to never run and the TCP/HTTP API services to
never initialize when booting without USB serial.

Also call onNetworkConnected() from reconnectWiFi() on all platforms
(not just RP2040) as a safety net; it is already guarded by
APStartupComplete so it only runs once.
2026-05-13 06:43:36 -05:00
Ben Meadors 593909c26b Radiolib excludes 2026-05-13 06:42:49 -05:00
Ben Meadors 29a61dc75c Fix type declaration for ambientLightingThread and correct uint32_t usage in PacketHistory 2026-05-12 21:45:16 -05:00
Ben MeadorsandGitHub eead467ce6 Added NodeDB fixtures and refactored to use std maps for better memory efficiency (#10464)
* Added NodeDB fixtures and refactored to use std maps for better efficiency

* Defer NodeDB save during xmodem transfer to prevent mid-transfer fsFormat
2026-05-12 17:23:29 -05:00
Ben Meadors f3ae02c425 Cleanup comments 2026-05-12 16:32:00 -05:00
Ben Meadors d9cb74e4dd XModemAdapter: ensure file truncation before receiving and add isBusy() method to prevent concurrent writes 2026-05-12 15:38:56 -05:00
7ff6641f97 Fix missing potential null termination in xmodem filename handling (#10308)
* Fix missing potential null termination in xmodem filename handling

The packet size max is 128 bytes, and the filename is 128 bytes, so
potentially there is no NUL at the end. use strlcpy() as that takes
care of null termination even if buffer size is exceeded.

* Protect against theoretical buffer overflows in BLE logging

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-12 06:26:13 -05:00
Ben MeadorsGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
f7548e7c25 Remove gradient sync nonce and simplify replay handling (#10459)
* Remove gradient sync nonce and simplify replay handling

* Fix ONLY_CONFIG replay gating and stale gradient-sync comments

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/cfa93978-e2e0-4dc2-ba5f-b82b5b43cef8

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

* Add transport mechanism to replay packets for client filtering

* Comments

* Update protobuf definitions to include precision_bits in PositionLite

* Propagate position precision_bits and remove verbose NodeInfo sync log

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/41572cbc-408e-499d-b59e-00f330b5789f

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-05-11 21:42:07 -05:00
b960121464 BaseUI: remove legacy single-message runtime path and keep multimessage flow (#10450)
* cleanup

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

* Potential fix for pull request finding

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-11 20:53:41 -05:00
Ben Meadors b074645586 Change node pointer to const in JsonSerialize function 2026-05-11 17:16:32 -05:00
Ben Meadors 811dd427dd Update protobufs 2026-05-11 16:36:35 -05:00
Jonathan Bennett 1c06b702dc Merge remote-tracking branch 'origin/master' into develop 2026-05-11 15:07:22 -05:00
59b4993861 Update protobufs (#10456)
Co-authored-by: jp-bennett <5630967+jp-bennett@users.noreply.github.com>
2026-05-11 15:06:37 -05:00
Jonathan BennettandGitHub da61a0db7d Refactor mutex handling in PhoneAPI.cpp
Replace LockGuard with explicit lock and unlock calls to avoid deadlock
2026-05-11 11:45:14 -05:00
Thomas GöttgensGitHubBen Meadorscopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>caveman99Jonathan Bennett
8e99ffbe7e ThinkNode M7 (#8077)
* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* ThinkNode G3, ETH support WIP

* rename variant and add guard macros

* older G3 operational. M7 next.

* Split out G3 and M7 to different variants. Completely new PCB design. The G3 stays on 'PRIVATE_HW'

* Define button behaviour and use all of the device flash

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-05-11 11:33:13 -05:00
Ben Meadors a23f923e64 Update subproject commit reference in protobufs 2026-05-11 08:09:39 -05:00
Ben Meadors 0522039830 Merge branch 'master' into develop 2026-05-11 08:09:10 -05:00
Ben MeadorsGitHubcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Copilot Autofix powered by AI
94bb21ecc7 2.8: NodeDB shrink, decoupling, and restructuring (#10413)
* 2.8: NodeDB refactor to decouple satellite entries and decrease size

* Regen

* Refactor node mute handling to use dedicated functions for clarity and consistency

* Develop ref

* Fix NodeDB review follow-ups

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e

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

* Address review validation nits

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/6b1d6cf6-ed6b-43b6-95cb-8e141757664e

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

* Trunk

* Potential fix for pull request finding

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

* Extract legacy NodeDatabase migration

* Fix remaining NodeDB review issues

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/c76b9a5a-7244-4fbc-9ef0-98091d8caaea

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

* Fixes

* Trunk

* Fix latest review compile follow-ups

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/5198da01-ec4c-4c16-8a09-68b8e6d5d410

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

* Fix cppcheck style warnings

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/e60287ba-4ece-46e0-83d8-a6d89664c0bb

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

* Change pointer type for mesh node in set_favorite function

* Change pointer types for mesh node references to const in multiple applets

* Add NodeDB layout v25 documentation and migration guidelines

* Remove tests for uninitialized PacketHistory state due to undefined behavior

* Fix code block formatting in copilot instructions

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-09 15:12:10 -05:00
1bcabb893b mesh: bound the user-facing notification sprintf calls (#10437)
Two sites built ClientNotification messages with sprintf into a
fixed-size proto buffer with no length cap. The current format strings
fit comfortably, but a future caller editing either format string
without rechecking the buffer size would get a silent stack/heap
overrun. Switch to snprintf with sizeof so the bound is enforced at
the call site.

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-09 13:32:42 -05:00
BJKandGitHub fefd424901 Fix screen geometry update for SH1107 display (#10444)
Added conditional block to update screen geometry for SH1107 128x128.
2026-05-09 12:53:07 -05:00
f63716c322 Automated version bumps (#10419)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-05-08 19:05:11 -05:00
5e2ca8aed4 LR2021 radio on NRF_Promicro (#10401)
* LR2021 radio on NRF_Promicro

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

* Refactor LR2021 interface includes and conditional compilation for improved clarity

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

* Refactor LR20x0 interface: remove unused includes and update comments for clarity

* Fix LR2021 max power definitions and add radio type detection tests

* remove potato radio type detection tests

* Include placeholder for DCDC - currently requires godmode

* Added godmode features - not enabled by default

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-07 20:24:39 -05:00
4553d1e0b1 Skip MQTT allocation when disabled (#10411)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-07 11:51:55 -05:00
Ben Meadors 784e3748e2 Merge remote-tracking branch 'origin/master' into develop 2026-05-07 11:45:04 -05:00
TomandGitHub b11d29ff31 fix(mesh): update reconfigure methods to return true instead of RADIOLIB_ERR_NONE (#10407) 2026-05-07 05:57:34 -05:00
cdc47a2aea Fix GPS initialization logic for Portduino configuration (#10395)
Co-authored-by: jessm33 <root@example.com>
2026-05-05 14:17:04 -05:00
fcef46f4b0 dependency swap - INA3221Sensor (#10379)
* dependency swap - INA3221Sensor
update INA3221 initialization and measurement methods for compatibility with Rob Tillaart's library

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

* Addresses copilot review

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

* Potential fix for pull request finding

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

* Refine comments on USB detection and INA3221

Updated comments regarding USB detection and INA3221 usage.

* Fix static_assert conditions for INA3221 channel definitions

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

* moved macro defines earlier to allow better use.

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

* Add raw register read methods for bus voltage and shunt current in INA3221Sensor

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

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-05-05 06:52:07 -05:00
Jonathan Bennett bb86cf4e81 Merge remote-tracking branch 'origin/master' into develop 2026-05-04 18:10:59 -05:00
Jonathan BennettandGitHub 6ea0d5ebba Add TFT_BACKLIGHT_ON for cardputer to fix builds (#10387) 2026-05-04 16:19:48 -05:00
JordandGitHub b2bda3b07a Enable MESHTASTIC_PREHOP_DROP by default (#9924) 2026-05-02 16:38:06 -05:00
Jonathan BennettandGitHub b7a9555905 Update RadioLib dependency to a specific commit (#10370)
Exploratory PR to test new radiolib change
2026-05-01 12:50:07 -05:00
1eb860a3fc fix(stm32wl,nrf52,fs): flash hardening, FS platform unification, write-behind LFS cache (FORMAT BREAK) (#10171)
* stm32wl: check HAL_FLASH_Unlock() return in _internal_flash_erase

_internal_flash_prog already checks HAL_FLASH_Unlock() and returns
LFS_ERR_IO on failure. _internal_flash_erase discarded the return
value, proceeding to erase even if the flash was not unlocked.

Apply the same check for consistency and safety.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* stm32wl: fix _internal_flash_prog to abort on first write error

Previously the programming loop continued to the next doubleword after
HAL_FLASH_Program() failed, potentially writing to invalid addresses
and returning a misleading error code only at the end (last iteration).
HAL_FLASH_Lock() was also skipped on the mid-loop early return path.

- Move bounds check before the loop (validate full range at once)
- Break on first HAL error so subsequent doublewords are not written
- Move HAL_FLASH_Lock() after the loop so it always runs

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* stm32wl: clear stale flash SR error flags before erase and program

Stale error flags in FLASH->SR from a previous failed operation can
cause HAL_FLASH_Program() or HAL_FLASHEx_Erase() to return HAL_ERROR
immediately without attempting the operation.

Add __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS) after each
HAL_FLASH_Unlock() in both _internal_flash_prog and
_internal_flash_erase to ensure a clean state before each operation.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* stm32wl: reject flash prog writes not aligned to 8-byte doubleword

The STM32WL HAL minimum write unit is one 64-bit doubleword (8 bytes).
_internal_flash_prog silently truncated any trailing bytes when size % 8
!= 0 because dw_count = size / 8 drops the remainder. Return LFS_ERR_INVAL
early so LittleFS sees the error rather than a silent short write.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(nrf52,fs): use atomic SafeFile rename instead of direct write

NRF52 was bypassing the .tmp/readback/rename path entirely — openFile()
deleted the target file and wrote directly to it, and close() returned
true without verifying the write or renaming anything.

Adafruit_LittleFS::rename() calls lfs_rename() directly (confirmed at
Adafruit_LittleFS.cpp:205). Remove both ARCH_NRF52 guards so NRF52
follows the same write-to-.tmp → readback-hash → rename path used by
all other platforms.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(admin): skip uiconfig.proto save on devices without a screen

handleStoreDeviceUIConfig() was writing /prefs/uiconfig.proto
unconditionally. MenuHandler.cpp is already gated behind #if HAS_SCREEN,
so there is no path that populates UI config on screen-less platforms.
Guard the save with #if HAS_SCREEN to avoid wasting a flash block on
devices that will never use it.

The read path (handleGetDeviceUIConfig) does not touch the filesystem
and needs no change.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fs: enable format-on-retry for all platforms in saveToDisk

The FSCom.format() call on save failure was guarded to ARCH_NRF52 with
a comment that other platforms were not ready (bug #4184). STM32WL was
added to the guard in a prior commit. All platforms now expose format
semantics and the retry logic is identical — remove the guard.

To keep NodeDB.cpp platform-agnostic and fix a CI failure on native-tft
(portduino's fs::FS has no format() method), introduce fsFormat() in
FSCommon as the single call-site for all callers:

  - Embedded (ESP32, NRF52, STM32WL, RP2040): delegates to FSCom.format()
  - Portduino: rmDir("/prefs") + FSBegin() (a no-op on portduino).
    rmDir("/prefs") is already called unconditionally by factoryReset()
    (NodeDB.cpp:504), so both primitives are proven on portduino.

Replace both direct FSCom.format() calls in NodeDB.cpp with fsFormat().

Note: we do not run portduino locally — portduino/native build testers
please verify the format-on-retry path.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* DO NOT MERGE: nrf52(fs): add File() default constructor bound to InternalFS

Adds File() to the Adafruit LittleFS File class (in the Meshtastic
Adafruit_nRF52_Arduino fork), delegating to File(InternalFS). This
matches the default-constructible File API on all other platforms.

The constructor is implemented in Adafruit_LittleFS_File.cpp rather
than inline in the header to avoid a circular include between
Adafruit_LittleFS_File.h and InternalFileSystem.h.

FOLLOW-UP REQUIRED: nrf52.ini points to a commit SHA on the
mesh-malaysia/Adafruit_nRF52_Arduino fork instead of the upstream
meshtastic framework. Once meshtastic/Adafruit_nRF52_Arduino#5 is
merged, revert nrf52.ini to point back to the upstream meshtastic
framework URL.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* stm32wl(fs): add File() default constructor and document LFS tunables

Adds File() to STM32_LittleFS_Namespace::File, delegating to
File(InternalFS). Implemented in the .cpp to avoid a circular include
between STM32_LittleFS_File.h (which cannot include LittleFS.h) and
the InternalFS extern declaration.

This matches the File API on ESP32/RP2040/Portduino and is a
prerequisite for removing the ARCH_STM32WL guard in xmodem.h.

No behavior change — the constructor leaves the file in the same
closed/unattached state as File(InternalFS) would.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fs: remove arch-specific ifdefs from FSCommon, SafeFile, xmodem

Now that NRF52 and STM32WL have File() default constructors and NRF52
has working atomic SafeFile rename, the capability gaps are closed.
Remove all per-arch guards across the shared FS layer:

FSCommon.cpp — renameFile():
  Use FSCom.rename() on all platforms. Adafruit_LittleFS::rename()
  calls lfs_rename() directly (Adafruit_LittleFS.cpp:205). The
  copy+delete fallback on NRF52/RP2040 was never necessary.

FSCommon.cpp — getFiles():
  Replace four ARCH_ESP32 guards with a single filepath pointer at
  the top of the loop (file.path() on ESP32, file.name() elsewhere).
  Fix strcpy(fileInfo.file_name, filepath): bounded to
  sizeof(fileInfo.file_name)-1 with explicit NUL termination to prevent
  overflow of the 228-byte meshtastic_FileInfo::file_name array.

FSCommon.cpp — listDir():
  Same filepath pointer approach. NRF52/STM32WL were in an else-branch
  that only logged but never deleted — now all platforms follow the
  unified del path. 12 guards → 2.
  Fix three strncpy(buffer, ..., sizeof(buffer)) calls that did not
  NUL-terminate when source length >= sizeof(buffer) (255 bytes).
  Add explicit buffer[sizeof(buffer)-1] = '\0' after each.

FSCommon.cpp — rmDir():
  Use listDir(del=true) everywhere. The ARCH_NRF52 rmdir_r() path and
  the ARCH_ESP32|RP2040|PORTDUINO listDir() path collapse to one line.

SafeFile.cpp:
  ARCH_NRF52 bypass removed (handled in preceding commit).

xmodem.h:
  File file; now works on all platforms via default constructors
  added in the two preceding commits.

Remaining #ifdef ARCH_ESP32 in FSCommon.cpp: exactly 4, all for the
file.path() vs file.name() API difference (ESP32 Arduino LittleFS
returns the full path; all others return only the name). That
difference lives in the framework and cannot be closed here.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* stm32wl(fs): add write-behind page cache, reduce virtual block size and FS reservation (FORMAT BREAK)

Adds a write-behind (RMW) page cache to the STM32WL LittleFS driver,
modelled after the NRF52 Adafruit approach (flash_cache.c). This allows
LFS to use 256-byte virtual blocks backed by 2048-byte physical pages:
the erase/prog callbacks accumulate changes in a 2 KB RAM buffer; the
sync callback (and page eviction on page-change) flushes with a single
HAL physical-erase + doubleword-program pass.

LFS tunables changed (FORMAT BREAK — superblock parameters):
  block_size:  2048 B → 256 B  (8 virtual blocks per physical page)
  read_size:   2048 B → 256 B  (= block_size)
  prog_size:   2048 B → 256 B  (= block_size; hardware min is 8 B)
  block_count: 112   → 80     (14 phys pages → 10 phys pages = 20 KiB)

Benefits:
  - Internal fragmentation: max 2047 B/file → max 255 B/file
  - Heap per open LFS file: ~4 KB → 512 B (prog + read buffers)
  - Code flash headroom: 6.7 KB → ~14.1 KB (+7.4 KB)
  - Block budget: 80 virtual blocks, worst-case peak ~20, ~60 free

Updates board_upload.maximum_size in wio-e5/platformio.ini from 233472
(256 KB − 28 KB) to 241664 (256 KB − 20 KB) to match the reduced FS
reservation.

Justification for the format break: the prior STM32WL firmware had
several flash write bugs fixed earlier in this series (missing error
flag clearing, no abort on first write failure, unaligned write
acceptance). These bugs very likely caused silent config corruption on
deployed devices. The format break should be treated as an enhancement:
it provides a clean, reliably-written starting point. Users will need
to reconfigure their device once after this update.

Correctness fixes applied to the cache implementation:
  - alignas(8) on _page_cache: the buffer was uint8_t[] (alignment 1)
    but _flash_cache_flush casts it to const uint64_t* — undefined
    behaviour per C++ standard, potential Cortex-M hardfault. alignas(8)
    guarantees the required alignment for the doubleword cast.
  - HAL_FLASH_Lock() return value: was discarded. Now assigned to
    lock_rc and propagated into rc if prior writes succeeded, so LFS
    sees the error rather than a false success.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* stm32wl(fs): reduce FS reservation from 10 pages to 7 pages (FORMAT BREAK)

Reduces LFS_FLASH_TOTAL_SIZE from 10 × 2 KiB pages (20 KiB) to
7 × 2 KiB pages (14 KiB), freeing 6 KiB for firmware.

board_upload.maximum_size updated accordingly across all STM32WL variants:
  241664 (256 KiB - 20 KiB) → 247808 (256 KiB - 14 KiB)

This is a FORMAT BREAK: existing filesystems must be erased before use.

Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Andrew Yong <me@ndoo.sg>

* fix(fs): return false in renameFile() when FSCom is not defined

Avoids undefined behavior and -Wreturn-type warnings in configurations
that compile FSCommon.cpp without a filesystem backend.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-05-01 08:25:19 -05:00
Jonathan Bennett 989b8620ba Merge remote-tracking branch 'origin/master' into develop 2026-04-30 10:49:26 -05:00
a0951f23c3 fix: MQTT connection on Portduino/Linux native nodes (#10330)
isConnectedToNetwork() always returned false on ARCH_PORTDUINO
because none of HAS_WIFI, HAS_ETHERNET, or USE_WS5500 are defined
for Linux native builds. This caused wantsLink() to always return
false, preventing the MQTT thread from ever connecting at boot.

Fix: return true for ARCH_PORTDUINO since Linux always has network
access available.

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-04-30 06:00:50 -05:00
HarukiToredaandGitHub e19f531059 Update Screen.cpp (#10344) 2026-04-29 21:05:16 -05:00
6c7ffa1054 macOS: enable CH341 LoRa-hardware path (fix serial truncation, document setup) (#10320)
* macOS: enable CH341 LoRa-hardware path — fix serial truncation, document setup

Verified on Apple Silicon with a CH341A USB-SPI bridge (VID 0x1A86,
PID 0x5512) wired to an SX1262 (Meshstick variant) that the existing
`pine64/libch341-spi-userspace` lib_dep works on macOS as-is — Apple's
bundled CH34x driver only matches the CH340 *UART* variant
(PID 0x7523), so the CH341A's interface 0 is left unclaimed and
libusb opens / configures / claims it directly via IOUSBHostInterface.
End-to-end test: meshtasticd boots, libusb claim succeeds, SX1262 init
returns 0, TCP API serves the meshtastic CLI's --info / --sendtext flow.

Two changes:

1. **`PortduinoGlue.cpp:497`**: pass `sizeof(serial)` (= 9) instead of
   the literal `8` to `Ch341Hal::getSerialString()`. The function in
   `USBHal.h:61-68` treats `len` as buffer size and reserves one slot
   for the null terminator (`bytesCopied = (len - 1) < 8 ? (len - 1) : 8`),
   so passing 8 produced a 7-char serial — which then broke the
   `strlen(serial) == 8` check at line 502, skipping the auto-MAC
   derivation from serial + product string. On Linux this was masked
   by the BlueZ HCI MAC fallback in `getMacAddr()` at lines 139-157,
   but on macOS that fallback is `__linux__`-guarded so the serial path
   is mandatory and the truncation left `mac_address` empty, causing
   the daemon to exit with `*** Blank MAC Address not allowed!`.

2. **`variants/native/portduino/platformio.ini`**: expand the
   `[env:native-macos]` comment block with a "Real LoRa hardware on
   macOS" section. Documents:
   - Why no upstream library change is needed (Apple kext targets
     CH340/UART, not CH341A/SPI; libusb's `#ifdef __linux__` skip is
     correct for macOS in this case).
   - How to point `meshtasticd` at an existing platform-agnostic
     `bin/config.d/lora-*.yaml` for CH341 hardware.
   - The auto-MAC-derivation contract (now working with this fix).
   - `ioreg` and `LIBUSB_DEBUG=4` diagnostic recipes for the failure
     mode where a third-party WCH `CH34xVCPDriver` *would* claim
     interface 0 (`kmutil unload -b <bundleID>` workaround).

No upstream library forks, no PR chain, no additional lib_deps —
the existing `pine64/libch341-spi-userspace` + libusb-1.0 stack does
the right thing on macOS already.

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

* Apply suggestion from @Copilot

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

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-28 08:31:08 -05:00
f037ce2165 add heltec-v4-r8 board (#10268)
* add heltec-v4-r8 board

* Fixed default SPI pin and macro definition errors.

* update platformio.ini according device-ui LGFX display definitions

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

* fix commit reference

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: mverch67 <manuel.verch@gmx.de>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Manuel <71137295+mverch67@users.noreply.github.com>
2026-04-27 09:25:19 -05:00
Ben Meadors bc17d004ab Merge branch 'master' into develop 2026-04-27 08:05:22 -05:00
Ben MeadorsGitHubJonathan BennettCopilotcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
06a6c3ee20 Native MacOS hello world (#10309)
* Native MacOS hello world

* Apply suggestion from @Copilot

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

* Update variants/native/portduino/platformio.ini

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

* fix: ensure null-termination in getSerialString() and handle len==0

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/e5647919-2255-48ad-bcaa-7a2c2fdbf212

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

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-26 22:07:07 -05:00
bfadf0c36a fix(Router): localize p_encrypted to prevent recursive-overwrite leak (#10311)
Router::handleReceived stores its allocCopy of the encrypted packet in the
class member p_encrypted. callModules() invokes module replies that re-enter
the router via MeshService::sendToMesh -> Router::sendLocal, which on a
broadcast reply recursively calls handleReceived. The inner call overwrites
the outer's p_encrypted without releasing it; on the way out it nulls the
member, the outer release(p_encrypted) now releases nullptr, and the original
allocation is permanently leaked. ~381 B per recursion.

Promote p_encrypted to a function-local so each invocation owns its own copy
for its full lifetime. The MQTT-publish null check at the call site (added by
PR #9136 as a workaround for this bug) stays in place because allocCopy can
still legitimately return nullptr on packetPool exhaustion.

Copilot's review of PR #8999 (the original introduction) flagged this exact
pattern at merge time:
  "Storing p_encrypted as a class member can cause issues with recursive or
  concurrent calls to handleReceived() since each call would overwrite the
  previous packet pointer."

The historical reason for the member (S&F needing to retain the encrypted
copy across calls) was satisfied differently by PR #9799 (ServerAPI converted
to std::unique_ptr + cleanup on connection close), so the member is no longer
load-bearing.

Reproduces issues #9632 / #10101 / #8729 (heap leak when MeshMonitor
connected; TCP drops on Station G2 / LILYGO ServerAPI dump abort).

Hardware A/B on Station G2 under sustained TCP-API retry storm (open :4403,
request config, disconnect mid-stream, repeat at ~0.6/s) - 9 min run:

  | Build         | heapFree drift | rebootCount delta |
  | this patch    | -1.5 KB (noise)| 0                 |
  | stock 2.7.13  | -73 KB (8.1KB/min) | +1 (OOM crash) |

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-26 21:02:42 -05:00
Jonathan BennettandGitHub 24c4162a75 Standardize PMU IRQ handling and enable power button cancel on tbeam-s3 (#10285)
* Standardize PMU IRQ handling and enable power button as cancel on tbeam s3

* Original T-beam, too
2026-04-26 19:58:23 -05:00
Ben Meadors b148fac340 Update framework version reference for Adafruit nRF52 to latest master branch 2026-04-26 09:49:39 -05:00
8dde4eeee1 BaseUI: Color Support for TFT Nodes (#10233)
* True Colors on TFT (Heltec Mesh Node T114, Heltec Vision Master T190, CardPuter Adv, T-Deck, T-Lora Pager)

* Theme support - New and some Classic Themes!

* Colored Compass

---------

Co-authored-by: Jason P <applewiz@mac.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-26 07:44:56 -05:00
Ben Meadors 4d4e14600c Merge remote-tracking branch 'origin/master' into develop 2026-04-25 15:55:16 -05:00
Ben Meadors 0bd8dee346 Merge remote-tracking branch 'origin/master' into develop 2026-04-25 15:06:28 -05:00
2828dbe4ca t5s3-epaper: Move variant.cpp -> extra_variants/variant.cpp ...again (#10297)
Fixes issues with #includes inherited from `configuration.h` when building for pioarduino.

Aligns t5s3_epaper with other variants like t_deck_pro.

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-25 13:36:31 -04:00
Ben Meadors a8c8fd7002 Remove redundant power interrupt methods for ESP32 2026-04-25 07:11:03 -05:00
Ben MeadorsandCopilot e7c02da24b Merge remote-tracking branch 'origin/master' into develop
Co-authored-by: Copilot <copilot@github.com>
2026-04-25 06:41:38 -05:00
HarukiToredaandGitHub 7421953e8f InkHUD: Add full touch support to T5s3 (#10286)
* InkHUD touch rework

* Applet Switcher

* Update ED047TC1.cpp

* trunk fix

* Custom tip screen for T5s3

* Update TouchScreenImpl1.cpp

* Update ED047TC1.cpp

* Delete variant.cpp
2026-04-25 05:22:24 -05:00
9306e66067 fix(inkhud): scale MapApplet markers with fontSmall line height (#10288)
Marker boxes, the own-node bullseye, and the labeled-marker cross were
all hardcoded in pixels (11px box, r=8 circle, 12px cross). On the
T5S3 with a 12pt fontSmall (~17px line height) the hop-count digit
overflowed its box entirely. Sizes now derive from fontSmall.lineHeight()
so the applet renders correctly on both small (6pt) and large (12pt+)
display variants.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:20:39 -04:00
Jonathan BennettandGitHub 7adfc3f992 Remove incorrect LED_STATE_ON definition for t-beam-s3 (#10280)
Fixes #9912  and #10170
2026-04-24 15:17:33 +10:00
ba9cadc14d Fix INA226 detection for non-TI compatible chip (Silergy) (#10247)
* Fix INA226 detection for non-TI compatible chip (Silergy)

* Removed extra I2C transaction + 20ms delay on every scan of address 0x40 (including real SHT2x sensors).

Changes suggested by Copilot

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

* Apply formatting (trunk fmt)

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-04-24 13:09:37 +10:00
924411de59 T-Watch S3 Power button managment (#9855)
* PMU interrupt pin defined in t-watch s3

* Implement button control on T-Watch S3

Added interrupt handling for the Power/Corona button on T-Watch S3, I use it to control screen state.

* Reducing labels

* Reducing labels

* Updated the comment

* ISR is now IRAM-safe

Updated interrupt management not to cause random crashes.

* Trunk

* Simplify and use INPUT_BROKER_CANCEL

---------

Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-04-23 19:53:59 -05:00
d9195944df PositionModule::sendLostAndFoundText: use stack buffer, eliminate heap alloc (#10251)
* PositionModule::sendLostAndFoundText: use stack buffer, eliminate heap alloc

The lost-and-found message was built with an unnecessary heap allocation:

    char *message = new char[60];
    sprintf(message, "..."...);
    ...
    delete[] message;

Two problems:

1. **Buffer too small.** The format string expands with two %f (IEEE 754
   doubles), which `sprintf` prints with full precision — easily 15+
   digits each plus separators — so the actual rendered string can run
   40-50 characters before even considering the emoji (4 UTF-8 bytes)
   and the embedded BEL. A pathological lat/lon can overflow 60 bytes
   and corrupt heap metadata. Unbounded `sprintf` with no size check.

2. **Heap churn in a GPS callback.** This function is called from the
   position-update path which is already heap-sensitive. An infrequent
   60-byte transient alloc isn't catastrophic, but stack is trivially
   available here and removes the failure mode entirely.

Fix: replace with a 128-byte stack buffer and `snprintf` bounded by
`sizeof(message)`. Drop the matching `delete[]` since there's nothing
to delete.

Behavior is identical on the happy path; the overflow case now
truncates safely instead of scribbling over heap.

* Potential fix for pull request finding

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

* PositionModule.cpp: add trailing newline for clang-format

* Address Copilot review: cleaner snprintf size handling

Review feedback from @Copilot on PR #10251: the ternary-plus-static-cast
form mixed signed/unsigned types (int written vs. pb_size_t payload.size
vs. size_t sizeof(message)) and was harder to read than necessary.

Cleaner form:
  const size_t msg_len = std::min(static_cast<size_t>(written), sizeof(message) - 1);
  p->decoded.payload.size = msg_len;

Same behaviour (clamp to buffer-minus-NUL) with one explicit cast and
a size_t variable that names the meaning. Handles the encoding-error
path (written < 0) separately so no bad values leak into payload.size.

* Trunk

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-04-23 18:20:07 -05:00
83a98c81f6 Hash table index for O(1) packet history lookups (#9499)
* Use hash table for O(1) lookup of recently seen packets

* Eliminate a packet lookup during deduplication

* Infinite loop checks for find and remove

* Consolidate conditional compilation

* Exclude hash table from minimal build

* Additional comment on hash table capacity

* Unit tests for packet history changes

* Update incorrect comment about size clamp

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

* Const

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-23 18:04:34 -05:00
Jonathan BennettandGitHub 837637b70c Only enable wakeup via EXT_CHRG_DETECT if we shut down due to low power (#10263) 2026-04-23 15:37:35 -05:00
Valentin V. BartenevandGitHub 7b3f58875a Fix example comment in airtime.h (#10275)
Looks like a copy'n'paste typo from the previous line.
It definitely meant to be RX_ALL_LOG according to comment.
2026-04-23 14:44:39 -05:00
Andrew YongandGitHub b2d980fc25 feat(Power): support EXT_PWR_DETECT_MODE & EXT_PWR_DETECT_VALUE, simplify EXT_PWR_DETECT (#10140)
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-04-23 14:32:17 -05:00
2ed7bba5e7 fix(Power): refactor EXT_CHRG_DETECT to compile-time macros (#10191)
Mirror the EXT_PWR_DETECT pattern: replace runtime static variables
(ext_chrg_detect_mode, ext_chrg_detect_value) with compile-time macros.
Auto-infer EXT_CHRG_DETECT_VALUE from EXT_CHRG_DETECT_MODE when the mode
is INPUT_PULLUP (→ LOW) or INPUT_PULLDOWN (→ HIGH); default to HIGH.

This fixes inverted polarity on variants that define EXT_CHRG_DETECT_MODE
INPUT_PULLUP without an explicit EXT_CHRG_DETECT_VALUE (e.g. russell):
previously the runtime default of HIGH caused isCharging() to return the
opposite of the correct value. With auto-inference the correct LOW active
level is now derived at compile time.

Remove the now-redundant EXT_CHRG_DETECT_VALUE HIGH from ELECROW-ThinkNode-M4
variant.h since HIGH is the inferred default.


Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>

Signed-off-by: Andrew Yong <noreply@example.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-04-23 13:24:05 -05:00
Catalin PatuleaandGitHub 22d50fe437 NimbleBluetooth misc cleanups (#10264)
* Delete unused clearNVS() (last used in commit 761804b1).

* virtual methods: add 'override' to ensure we get the signature right.

This is a safety net for pioarduino/NimBLE work where there's multiple
similar variants of the same method (eg. onConnect) and it's easy to get
the wrong one and accidentally miss a callback.
2026-04-23 09:18:41 -05:00
Ben Meadors cde5a08bc5 Merge remote-tracking branch 'origin/master' into develop 2026-04-23 06:48:43 -05:00
55bf8c25fc PhoneAPI: add missing tak_tag case + skip reserved gap in module-config iteration (#10256)
* PhoneAPI: add missing tak_tag case + skip reserved gap in module-config iteration

The STATE_SEND_MODULECONFIG state machine iterates config_state through
ModuleConfigType enum values (1..MAX+1 = 16) and switches on
meshtastic_ModuleConfig_*_tag values. Two of the resulting tag values
land in the default / LOG_ERROR path:

1. `tak_tag` (16) — the meshtastic_ModuleConfig_TAKConfig struct exists
   in the protobuf and has a `.tak` member in payload_variant, but no
   PhoneAPI case ever sends it to the phone. As a result, Android
   clients can't read the persisted TAK (Team Awareness Kit) module
   config at all. Added case that sends moduleConfig.tak, matching the
   pattern used for all other module-config tags (paxcounter,
   traffic_management, etc.). NodeDB already persists the struct via
   the moduleConfig protobuf save; this just wires the read path to
   the phone.

2. Tag 14 — reserved gap in the oneof numbering. No payload_variant
   member exists at tag 14. Without this patch, every phone reconnect
   walks through config_state=14 and hits
   `LOG_ERROR("Unknown module config type %d", config_state)`. On an
   active deployment that's ~1,400 LOG_ERROR lines per day per node —
   burying real errors. Added explicit `case 14: break;` so the gap
   is silently skipped.

Also: lowered the `default:` log level from LOG_ERROR to LOG_DEBUG. A
truly-new unknown type number would indicate firmware lagging the
protobuf — annoying but not an error event worth LOG_ERROR, especially
since this path runs on every phone handshake. If a new ModuleConfig
tag appears, devs will notice via the phone UI missing it, not via log.

Observed on a Station G2 fleet: 1403 "Unknown module config type 16"
and 1427 "Unknown module config type 14" LOG_ERROR lines in 24 hours
from routine phone reconnects.

* Get rid of the placeholder

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-23 06:42:05 -05:00
nightjoker7andGitHub 399dde0f4b Router: demote cross-channel decrypt failures from ERROR to DEBUG (#10259)
The "Invalid protobufs (bad psk?)" and "Invalid portnum (bad psk?)"
messages fire every time a neighbor transmits on a channel whose
8-bit hash matches one of ours but the PSK differs. In RF environments
with multiple mesh groups nearby this is routine — a single device
can see dozens of these per minute from SAR/MeshCA/private networks
sharing a hash collision.

LOG_ERROR for a benign "not our PSK" event:
- spams the log when you have any neighboring mesh group
- makes a genuine PSK misconfiguration on YOUR own channel
  indistinguishable from the constant cross-channel noise
- hides actual errors in the stream

LOG_DEBUG matches how similar decryption-failure paths are handled
elsewhere (eg. the PKC "decrypt attempted but failed" uses LOG_WARN).
Dropping the trailing "!" as well — these are expected events, not
exceptional ones.
2026-04-23 06:19:05 -05:00
66971a0a26 RadioLibInterface: clear static instance on destruction to prevent UAF (#10254)
The constructor sets `RadioLibInterface::instance = this` immediately,
before `init()` runs. `initLoRa()` in RadioInterface.cpp creates each
radio variant with `new SX1262Interface(...)` or similar, then calls
`init()`, and if init fails the `unique_ptr<RadioInterface>` is reset
to nullptr — destroying the object — while the static `instance`
pointer continues to point at the freed memory.

Main loop then checks `RadioLibInterface::instance != nullptr` and
calls `pollMissedIrqs()` or `resetAGC()` on the dangling pointer →
Guru Meditation (IllegalInstruction / LoadProhibited).

Reported in #9880 on an ESP32-S3 dev board without radio hardware
attached, where init always fails and the leftover pointer crashes
the device on the next `loop()` iteration.

Fix: add a virtual destructor to `RadioLibInterface` that clears the
static pointer iff it still references this object. A later successful
init() may have replaced `instance` with a different interface — the
`instance == this` guard preserves that case.

Fixes #9880

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-23 06:16:08 -05:00
Thomas GöttgensandGitHub 4c24218afb Revert "Update LovyanGFX to v1.2.20 (#10232)" (#10269) (#10270)
This reverts commit fb1de111d7.
2026-04-23 12:40:27 +02:00
Ben Meadors 4b4914736f Merge remote-tracking branch 'origin/master' into develop 2026-04-22 21:52:25 -05:00
nightjoker7andGitHub a6b1a69630 StoreForwardModule::historyAdd: memcpy source size, not buffer capacity (#10250)
`memcpy(... p.payload.bytes, meshtastic_Constants_DATA_PAYLOAD_LEN)`
reads past the actual payload when the incoming packet's payload is
shorter than `DATA_PAYLOAD_LEN` (237 bytes). The code just above
already records the correct size:

    this->packetHistory[...].payload_size = p.payload.size;

but then the memcpy ignores that and copies the full buffer capacity,
pulling uninitialized / adjacent memory bytes into the history entry.
Those extra bytes are later rebroadcast whenever the Store & Forward
module replays the packet.

Fix: memcpy using `p.payload.size` (the actual payload length) instead
of the constant buffer capacity.

Classification: bounded out-of-bounds READ into the protobuf scratch
buffer. Not directly exploitable for RCE (the destination buffer is
also DATA_PAYLOAD_LEN), but leaks adjacent memory into replayed
packets and is a latent correctness bug.
2026-04-22 21:04:37 -05:00
Jonathan BennettandGitHub 28e705de5c Detach power interrupts for sleep (#10230)
* Detach power interrupts for sleep

* Gate PMU IRQ behind a found PMU
2026-04-22 14:27:48 -05:00
AustinandGitHub fcb9ec0c2d t5s3-epaper: Move variant.cpp -> extra_variants/variant.cpp (#10241)
Fixes issues with #includes inherited from `configuration.h` when building for pioarduino.

Aligns t5s3_epaper with other variants like t_deck_pro.
2026-04-22 11:42:02 -05:00
AustinandGitHub a4b55bc6f2 cardputer-adv: Move variant.cpp -> extra_variants/variant.cpp (#10242)
Fixes issues with #includes inherited from `configuration.h` when building for pioarduino.

Aligns cardputer-adv with other variants like t_deck_pro.
2026-04-22 11:41:49 -05:00
Jonathan Bennett b53fe7a1e7 T watch pinfix (#10231)
* Minor button debugging bits

* pin0 is a pin, pin -1 means disabled
2026-04-21 20:03:40 -05:00
TomandGitHub db9fdd6794 Fix: filter out SKIPPED tests in PlatformIO output to improve log clarity (#10214) 2026-04-21 16:43:35 -05:00
3b4c66439d feat(t5s3-epaper): add InkHUD port for LilyGo T5 E-Paper S3 Pro (#10211)
* niche: add InkHUD port for LilyGo T5-E-Paper-S3-Pro (ED047TC1)

Add a NicheGraphics EInk driver adapter for the 4.7" ED047TC1 parallel
e-paper display used on the T5-E-Paper-S3-Pro (H752-01). The driver
wraps FastEPD and handles the polarity difference between InkHUD's
buffer format (0xFF = white) and FastEPD's (0x00 = white).

Rewrite variants/esp32s3/t5s3_epaper/nicheGraphics.h which was an
incomplete copy of the Heltec VM-E290 setup referencing undefined SPI
pin macros and a non-existent BUTTON_PIN_SECONDARY. The board uses a
parallel display, not the small SPI DEPG0290BNS800 that was referenced.

* fix: guard inputBroker null dereference in TouchScreenImpl1::init()

When MESHTASTIC_EXCLUDE_INPUTBROKER is defined (e.g. InkHUD builds),
inputBroker is nullptr. Calling inputBroker->registerSource() in that
state caused a LoadProhibited panic on any board that has both
HAS_TOUCHSCREEN=1 and the InputBroker excluded.

Add a null check before registerSource() to prevent the crash.

* niche: fix display rotation for T5-E-Paper-S3-Pro InkHUD port

Set rotation=3 (270° CW) in nicheGraphics.h to correct for FastEPD
scanning the ED047TC1 panel in portrait orientation, resulting in
correct landscape display output.

* fix: update buffer format descriptions and remove polarity inversion for InkHUD and FastEPD

* fix: update ED047TC1 driver to handle inactive pixel borders and adjust safe-area dimensions

* fix: comment out ruler diagnostic for E-Ink driver

* feat: implement TouchInkHUDBridge for direct touch event handling in InkHUD

* niche: add FreeSans 18pt/24pt Win1253 (Greek) fonts for larger InkHUD displays

Add Win1253-encoded FreeSans 18pt and 24pt font headers to support Greek
script on larger InkHUD screens (e.g., the 4.7" ED047TC1 at ~234 DPI).
Register FREESANS_24PT_WIN1253 and FREESANS_18PT_WIN1253 macros in AppletFont.h.
Set fontLarge=24pt, fontMedium=18pt, fontSmall=12pt in nicheGraphics.h for the
T5-E-Paper-S3-Pro.

* feat(ed047tc1): use true partial update for FAST refresh

Replace fullUpdate(CLEAR_FAST) with partialUpdate() for FAST display
updates. FastEPD's partialUpdate() diffs pCurrent against pPrevious
and only applies the update waveform to rows that have changed, leaving
unchanged rows with a neutral signal.

This reduces visible flicker on routine updates (new messages, position
changes) — only the affected region of the screen refreshes. Full-screen
CLEAR_SLOW updates are preserved for periodic ghosting cleanup, driven
by InkHUD's setDisplayResilience() ratio.

* feat(t5s3-epaper): enable frontlight via LatchingBacklight

Wire up BOARD_BL_EN (GPIO11) to InkHUD's LatchingBacklight driver.
Enable the backlight menu item so users can toggle "Keep Backlight On"
via Settings. The backlight turns on automatically when the menu opens
and off when it closes.

* Fix RTC chip (PCF8563 not PCF85063) and GT911 I2C address collision

- variant.h used PCF85063_RTC but the board has a PCF8563. The difference
  is the RAM register: PCF85063 has 1 byte of RAM; PCF8563 does not. The
  PCF85063 driver was trying to write this register on init, failing every
  time, and setDateTime writes were silently discarded — RTC time was
  never persisted across reboots. Switch to PCF8563_RTC/PCF8563_INT.

  Before:
    [E][SensorPCF85063.hpp:375] initImpl(): Failed to write to RAM memory
      register. Maybe this chip is pcf8563.
    Read RTC time from PCF85063 getDateTime as 2026-04-05 00:00:23
    PCF85063 setDateTime 2026-04-05 18:40:59
    Read RTC time from PCF85063 getDateTime as 2026-04-05 00:00:19  ← lost

  After:
    PCF8563 found at address 0x51
    Read RTC time from PCF8563 getDateTime as 2026-04-05 18:58:37  ← persisted
    PCF8563 setDateTime 2026-04-05 18:58:44
    Read RTC time from PCF8563 getDateTime as 2026-04-05 18:58:44  ← round-trips

- GT911 touch was initialized with GT911_SLAVE_ADDRESS_L (0x5D), which
  collides with the SFA30 air quality sensor also at 0x5D on the same
  I2C bus. Switch to GT911_SLAVE_ADDRESS_H (0x14): the library drives
  INT high during reset to program the GT911 to address 0x14,
  eliminating the address conflict.

  Before:
    SFA30 found at address 0x5d
    [I][TouchDrvGT911.hpp:568] initImpl(): Try using 0x5D as the device address

  After:
    SFA30 found at address 0x5d
    [I][TouchDrvGT911.hpp:544] initImpl(): Try using 0x14 as the device address

* t5s3_epaper: fix GT911 ghost-SFA30 via early I2C address latch

Investigation findings
----------------------
Boot logs showed "SFA30 found at address 0x5d" on every cold power-on,
and AirQualityTelemetry was registering an SFA30 sensor. However, every
readMeasuredValues() call returned error 268 (0x010C = Sensirion
WriteError | I2cAddressNack), meaning the I2C write to 0x5D was being
NACK'd — inconsistent with a real SFA30.

Root cause: the GT911 touch controller latches its I2C address from the
INT pin level at reset time (GT911 datasheet §4.3). GPIO3 (INT) defaults
LOW on ESP32-S3 cold boot → GT911 always powers up at 0x5D
(SLAVE_ADDRESS_L). The I2C scanner runs before lateInitVariant() had a
chance to reprogram the chip.

The scanner's SFA30 detection (ScanI2CTwoWire.cpp) sends the 2-byte
command 0xD060 to 0x5D and requests 48 bytes back. GT911 ACKs the
write (treating it as a register address) and returns 48 bytes of
register data, passing the length check — a false-positive SFA30
detection.

Confirmed via second cold-boot log: after the previous commit moved GT911
to 0x14 in lateInitVariant(), address 0x5D *still* appeared in the scan
because the scanner runs first. The board has no physical SFA30 fitted.

Fix
---
Add the GT911 address-latch reset sequence to earlyInitVariant(), before
Wire is initialised and before the I2C scan runs. Per the datasheet:
drive RST LOW, drive INT HIGH (selects address 0x14 / SLAVE_ADDRESS_H),
hold >100 µs, release RST, wait >5 ms startup. GPIO-only, no Wire
dependency. lateInitVariant() then repeats this sequence internally via
touch.begin(); the double-reset is harmless.

Verified in boot log:
  Before: "SFA30 found at address 0x5d", 5 I2C devices, NACK errors
  After:  no SFA30 entry, 4 I2C devices (TCA9535/PCF8563/BQ27220/BQ25896),
          GT911 found at 0x14 and touch initialised successfully,
          AirQualityTelemetry registers no sensors (correct — no SFA30 present)

* t5s3_epaper: add variant_shutdown() for touch sleep and backlight off

Put GT911 into low-power standby (command 0x05) and drive BOARD_BL_EN
LOW before deep sleep to avoid unnecessary current draw.

* t5s3_epaper: fix touch gesture routing and coordinate mapping

readTouch() now transforms raw GT911 axes to visual-frame coordinates
based on the current display rotation (rotation=3 is the hardware
identity). This ensures TouchScreenBase detects swipe direction
correctly regardless of which rotation the user has selected.

TouchInkHUDBridge dynamically sets joystick.alignment = (4-rotation)%4
on each touch event so that (rotation+alignment)%4==0 always, keeping
nav calls pass-through without remapping.

nicheGraphics.h now calls loadSettings() first so that rotation is
persisted across reboots. rotation=3 and other first-boot defaults are
only applied when tips.firstBoot is set. alignment is recomputed from
the loaded rotation on every boot.

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

* t5s3_epaper: fix GT911 sleep timing via notifyDeepSleep observer

touch.sleep() was called from variant_shutdown(), which runs inside
cpuDeepSleep() — after Wire.end() had already torn down the I2C bus in
doDeepSleep(). This caused Wire NULL TX buffer errors and left the GT911
awake during deep sleep.

Register a CallbackObserver on notifyDeepSleep, which fires before
Wire.end(), so the I2C command reaches the chip while the bus is live.
Pattern matches LatchingBacklight and other NicheGraphics components.

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

* t5s3_epaper: fix touch nav and applet defaults in nicheGraphics

Enable joystick mode post-begin so menu scroll and swipe-up/down
gestures are not silently dropped by the joystick.enabled gate in
Events.cpp. Activate DMs and Channel 0/1 applets with correct
autoshow defaults matching the mini-epaper-s3 reference pattern.

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

* Update nicheGraphics.h

* t5s3_epaper: fix ED047TC1 driver docs and remove spurious beginPolling

Addressing PR review comments:

Remove beginPolling(1, 0) after the blocking FastEPD update — it
incorrectly set updateRunning=true for one loop cycle after the
hardware was already done, causing busy() to briefly return true.
Since isUpdateDone() always returns true, no polling is needed.

Also fix stale comments: safe-area buffer size was 944×532, now
944×523; V_OFFSET_ROWS didn't exist, replaced with the actual
V_OFFSET_TOP=9 / V_OFFSET_BOTTOM=8 constant names.

* t5s3_epaper: clean up applet addition formatting in setupNicheGraphics

* t5s3_epaper: guard ED047TC1.cpp against non-T5S3 InkHUD builds

The InkHUD base config pulls in all of src/graphics/niche/ so every
InkHUD device compiled ED047TC1.cpp, triggering the #error on line 48
for boards that define neither T5_S3_EPAPER_PRO_V1 nor V2.

Wrap the file body with #ifdef T5_S3_EPAPER_PRO so it is only compiled
for T5S3 targets. The #error is preserved inside the guard to catch
future hardware revisions that forget to update the driver.

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

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com>
2026-04-21 15:35:02 -05:00
HarukiToredaandGitHub 945f4780ea BaseUI: Nodelist screen/favorite screen cleanup (#10197)
* nodelist screen cleanup

* Update UIRenderer.cpp

* Update src/graphics/draw/UIRenderer.cpp

* removed brackets from hop and made signal mutually exclusive
2026-04-21 10:31:29 -05:00
0e38a15d46 Update protobufs (#10223)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2026-04-21 17:13:55 +02:00
Jaime RoldanandGitHub 63bce1f01a fix(nodedb): force null-terminate name fields in UserLite/User conversions (#8174) (#10218) 2026-04-21 09:52:19 -05:00
nightjoker7andGitHub 4090d9f2b3 SX126x: re-apply 0x8B5 register in resetAGC() to preserve RX sensitivity (#10219)
The CALIBRATE_ALL (0x7F) command inside resetAGC() clears bit 0 of the
undocumented 0x8B5 register. That bit is set once in init() by #9571 and
#9777 to improve SX1262 RX sensitivity, and the AGC-reset path was not
re-applying it. Result: every SX1262 node silently loses the RX
sensitivity patch ~60s after boot and never recovers until reboot.

Empirically confirmed on Heltec Mesh Node T114 (nRF52840 + SX1262):
  - Post-calibration read of 0x8B5 = 0x04 (bit 0 cleared)
  - After re-apply: 0x05 (bit 0 set)
Reproducible every AGC_RESET_INTERVAL_MS tick.

Fix re-applies the register bit alongside the existing post-calibration
re-applies (setDio2AsRfSwitch, setRxBoostedGainMode).
2026-04-21 09:50:01 -05:00
d50caf231b Add encryption overview to agent instructions in AGENTS.md (#10207)
* Add encryption overview to agent instructions in AGENTS.md

* Update AGENTS.md

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

* Update copilot-instructions.md

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

* Update copilot-instructions.md

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

* Clarify nonce and wire overhead details in encryption section of copilot instructions

* Enhance encryption documentation in copilot instructions and agents guide for clarity on key management and reset behaviors

* Update .github/copilot-instructions.md

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

* Fix botched merge conflict resolution

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-19 16:05:28 -05:00
f396200d38 Add authoring guide for native unit tests in README.md (#10201)
* Add authoring guide for native unit tests in README.md

* Enhance documentation for agent tooling and native unit tests in README and related files

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-19 13:30:50 -05:00
Ben Meadors 6c04c37294 Merge remote-tracking branch 'origin/master' into develop 2026-04-19 12:50:12 -05:00
Clive BlackledgeandGitHub 34aa5e995b Fix: prompt markdownlint md040 fix for new prompts. (#10199)
* Add ESP32 Power Management lessons learned document

Documents our experimentation with ESP-IDF DFS and why it doesn't
work well for Meshtastic (RTOS locks, BLE locks, USB issues).

Proposes simpler alternative: manual setCpuFrequencyMhz() control
with explicit triggers for when to go fast vs slow.

* docs(prompts): fix markdown fence language tags

* docs: remove ESP32 power management notes
2026-04-18 08:29:30 -05:00
Ben MeadorsGitHubCopilotcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
c8dac10348 Add MCP server for interacting with meshtastic devices and testing framework / TUI (#10194)
* Start of MCP server and test suite

* Add MCP server for interacting with meshtastic devices and testing framework / TUI

* Update mcp-server/README.md

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

* fix mcp-server review feedback from thread

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/91dc128a-ed50-4d07-8bb2-3dc6623a05f7

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

* Enhance StreamAPI and PhoneAPI for improved log record handling and concurrency control

* Semgrep fixes

* Trunk and semgrep fixes

* optimize pio streaming tee file writes

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/04e26c6b-6a2b-45be-bbeb-79ae4d0be633

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

* chore: remove redundant log handle assignment

Agent-Logs-Url: https://github.com/meshtastic/firmware/sessions/04e26c6b-6a2b-45be-bbeb-79ae4d0be633

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

* Consolidate type imports and remove placeholder test files

* Add tests for config persistence and more exchange messages

* Refactor position test to validate on-demand request/reply behavior

* Remove  position request/reply test and update README for telemetry behavior

* Fix transmit history file to get removed on factory reset

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-04-18 08:17:44 -05:00
aab4cd086f Compass improvements/refactoring (#10166)
* Infinite calibration loop fix

* Save calibration

* Screen refresh

* reduce repeated code

* reduce repeated code to reduce flash

* fix Waypoint compass size and no fix no heading labels

* Don't show compass unless we have a heading and location

* If no calculated heading from moving, we should have no heading

* Slow walking calculated heading and auto stale heading when not moving

* Triming flash space

* cleanup

* show "?" when no location or heading for distance and heading screen

* cleanup

* Stale heading logic

* final trim

* Compass Calibration screen redesign

* Trunk Fix

* Compile fix

* patch

* Update src/motion/MotionSensor.cpp

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

* Update WaypointModule.cpp

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-18 06:53:22 -05:00
Jason PandGitHub 6208c243f9 BaseUI: Implementation of Status Message for Favorite and NodeList views (#9504)
* Implementation of Status Message

* Change drawNodeInfo to drawFavoriteNode

* Truncate overflow on Favorite frame

* Set MAX_RECENT_STATUSMESSAGES to 5 to meet memory usage targets
2026-04-17 08:42:56 -05:00
Jonathan BennettandGitHub 3a87e746a8 No longer need undefines, thanks to #10179 (#10180) 2026-04-16 21:34:28 -05:00
Ben Meadors cd04206334 Merge branch 'master' into develop 2026-04-16 21:22:17 -05:00
Jonathan BennettandGitHub 2768080edf More cleanly remove LED_BUILTIN (#10179)
* Test PR to remove LED_BUILTIN 

Comment out the LED_BUILTIN definition in platformio.ini

* Add LED_BUILTIN definition to nrf52840.ini
2026-04-16 13:12:31 -05:00
RuledoandGitHub 466cc4cecd Add Luckfox Pico Max Waveshare Pico LoRa config (#10175)
Add a meshtasticd config for the Luckfox Pico Max with the Waveshare Pico LoRa SX1262 TCXO HAT.

Tested on hardware with successful SX1262 init, broadcast, and direct messaging.
2026-04-16 10:41:06 -05:00
fe90c49795 fix/feat(stm32/russell): Serial2 build fix and BME680 support (#10097)
* fix(stm32/russell): define ENABLE_HWSERIAL2 and Serial2 pins

The Russell board variant was missed during Initial serialModule cleanup
(PR #9465), which began requiring Serial2 to be explicitly defined via
ENABLE_HWSERIAL2 and PIN_SERIAL2_TX/RX rather than relying on implicit
defaults, causing a build error.

Signed-off-by: Andrew Yong <me@ndoo.sg>

* feat(stm32/russell): add BME680 support, exclude modules to fit flash

The BME680 is hardware footprint compatible with the BME280 already
present on the Russell board, so add it as an additional lib dep to
enable environment sensing (temperature, humidity, pressure,
gas resistance).

The STM32 target has very limited flash. Even traceroute alone causes
overflow, so the following modules are excluded to stay within budget:

  - RANGETEST
  - DETECTIONSENSOR
  - EXTERNALNOTIFICATION
  - POWERSTRESS
  - NEIGHBORINFO
  - TRACEROUTE
  - WAYPOINT

AIR_QUALITY_SENSOR is also excluded as it requires the BSEC2 library
for real IAQ output, which alone overflows flash by ~44KB on this
target. The Adafruit BME680 library is used instead for raw sensor
readings.

Signed-off-by: Andrew Yong <me@ndoo.sg>

---------

Signed-off-by: Andrew Yong <me@ndoo.sg>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-16 10:39:37 -05:00
Chloe BethelandChloe Bethel 31418ca821 stm32wl: reserve 2KB of stack via linker script to match NRF52, change sbrkHeadroom to use the start of the reserved stack region instead of the current stack pointer
The linker script was created by merging variants/STM32WLxx/WL54JCI_WL55JCI_WLE4J(8-B-C)I_WLE5J(8-B-C)I/ldscript.ld and system/ldscript.ld from stm32duino/Arduino_Core_STM32.
2026-04-16 13:57:21 +01:00
Andrew YongandChloe Bethel 0ee5777c15 stm32wl(mem): fix getFreeHeap() underreporting on dynamic sbrk heap
mallinfo().fordblks counts only free bytes within the committed arena.
On STM32WL (newlib sbrk heap) the arena grows lazily from _end toward SP,
so fordblks reads near-zero at early boot even when ~48 KB of addressable
space remains. This caused NodeDB::isFull() to fire prematurely and evict
nodes on a freshly booted device.

Fix getFreeHeap() to include uncommitted sbrk headroom (SP - sbrk(0)) so
the returned value reflects true available memory throughout the boot
lifecycle.

Introduce MESHTASTIC_DYNAMIC_SBRK_HEAP as an opt-in build flag (set in
stm32.ini) so the fix is gated to platforms with a dynamic sbrk heap
rather than a static heap. Future platforms with the same heap model can
opt in by adding this flag.

Signed-off-by: Andrew Yong <me@ndoo.sg>
Assisted-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 13:57:21 +01:00
Andrew YongandGitHub 026213aab7 feat(stm32): Add STM32 ADC support to AnalogBatteryLevel (#9369)
Integrate STM32 battery monitoring into AnalogBatteryLevel, supporting
external GPIO ADC pins as well as internal VBAT channel.

Features:
- ADC reading using STM32 LL (Lower Layer) macros supporting external
  ADC channels and internal VBAT channel (AVBAT)
- ADC compensation using STM32 LL macros with factory-calibrated VREFINT
  (AVREF) for accurate voltage measurement
- LFP battery OCV curve for STM32WL using AVBAT (STM32 VDD absolute
  maximum supply voltage 3.9V, direct connection of Li-Po batteries is
  not supported)

Internal VBAT channel implemented in:
- Russell
- RAK3172

In these variants, ADC_MULTIPLIER = (1.01f * 3) = 3.30 as there is a
3:1 internal divider (DS13105 Rev 12 §5.3.21), and a bit of tolerance
as the actual 10% spec leads to readings much too high.


Assisted-by: Claude:sonnet-4-5

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-04-16 10:58:54 +01:00
0cab43fb43 Add PortduinoSetOptions to overwrite the realhardware bool (#10157)
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-14 21:32:48 +02:00
1341cd4078 Automated version bumps (#10159)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-04-14 13:11:47 -05:00
Jennifer SanchezandGitHub 4059202a5c Added support for Spreading Factors 5 and 6 on compatible radios (#10160) 2026-04-14 13:11:36 -05:00
Ben MeadorsandCopilot d24d8806e1 Fix heap blowout on TBeams (#10155)
* Fix heap blowout on TBeams

* Update src/graphics/draw/MessageRenderer.cpp

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

* Set MESSAGE_HISTORY_LIMIT to 10 for original ESP32 to optimize RAM usage

* Optimize message frame allocation to prevent excessive memory usage

* Refine message history limits for resource-constrained builds and cap cached lines to prevent heap overflow

* Update src/graphics/draw/MessageRenderer.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-14 13:05:58 -05:00
Thomas GöttgensandBen Meadors a67eb15ad3 fix last cppcheck issue (#10154) 2026-04-14 13:05:58 -05:00
Ben Meadors 9e182a595c Enhance release notes generation with commit range comparison 2026-04-14 13:05:58 -05:00
Tom FifieldandGitHub 4587dc2d64 Add RADIOLIB_EXCLUDE_LR2021 in places that excluded LR11x0 (#10112)
To save resources, some devices where LR11x0 was never an option
excluded it from compilation, using RADIOLIB_EXCLUDE_LR11X0 .

As we will soon have LR2021 support, apply the same treatment
to that chip to these devices, by adding RADIOLIB_EXCLUDE_LR2021
2026-04-14 16:55:11 +02:00
00fccd87f9 Update protobufs (#10161)
Co-authored-by: caveman99 <25002+caveman99@users.noreply.github.com>
2026-04-14 15:27:05 +02:00
Andrew YongandGitHub 01bd4cfb73 feat(stm32wl): add reboot-to-bootloader support via enter_dfu_mode_request (#10158) 2026-04-14 13:46:33 +02:00
Jonathan BennettandGitHub 125c1b7f13 Make consoleInit() Reentrant, and initialize it earlier on native (#10156) 2026-04-14 06:41:04 -05:00
Thomas GöttgensandGitHub d6cf67b6bc Clarify behavior when no radio instance is present
Add comment explaining silent behavior when no radio instance is available.
2026-04-14 12:19:58 +02:00
77f378dd53 Reduce key duplication by enabling hardware RNG (#8803)
* Reduce key duplication by enabling hardware RNG

* Apply suggestion from @Copilot

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

* Apply suggestion from @Copilot

Use micros() for worst case random seed for nrf52

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

* Minor cleanup, remove dead code and clarify comment

* trunk

* Add useRadioEntropy bool, default false.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-04-13 12:05:23 -05:00
Ben Meadors 16dcafa7fb Merge remote-tracking branch 'origin/master' into develop 2026-04-13 06:28:41 -05:00
4ac74edf38 fix(native): implement BinarySemaphorePosix with proper pthread synchronization (#9895)
* fix(native): implement BinarySemaphorePosix with proper pthread synchronization

The BinarySemaphorePosix class (used on all Linux/portduino/native builds)
had stub implementations: give() was a no-op and take() just called
delay(msec) and returned false. This broke the cooperative thread scheduler
on native platforms — threads could not wake the main loop, radio RX
interrupts were missed, and telemetry never transmitted over the mesh.

Replace the stubs with a proper binary semaphore using pthread_mutex_t +
pthread_cond_t + bool signaled:

- take(msec): pthread_cond_timedwait with CLOCK_REALTIME timeout, consumes
  signal atomically (binary semaphore semantics)
- give(): sets signaled=true, signals condition variable
- giveFromISR(): delegates to give(), sets pxHigherPriorityTaskWoken

Tested on Raspberry Pi 3 Model B (ARM64, Debian Bookworm) with Adafruit
LoRa Radio Bonnet (SX1276). Before fix: no radio TX/RX, no telemetry on
mesh. After fix: bidirectional LoRa, MQTT gateway, telemetry all working.

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

* ARCH_PORTDUINO

* Refactor BinarySemaphorePosix header for ARCH_PORTDUINO

* Change preprocessor directive from ifndef to ifdef

* Gate new Semaphore code to Portduino and fix STM compilation

* Binary Semaphore Posix better error handling

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
2026-04-12 22:41:25 -05:00
Jonathan Bennett 69495dcd98 Merge remote-tracking branch 'origin/master' into develop 2026-04-12 17:29:40 -05:00
Catalin PatuleaandGitHub 288d7a3e7b Revert "Add include directive for mbedtls error handling in build flags" (#10073)
This reverts commit 391928ed08.

Since esp32_https_server commit e2c9f98, this is no longer necessary.

Also, the esp32-common.ini '-include mbedtls/error.h' causes a build error
under IDF v5 (https://github.com/meshtastic/firmware/pull/9122#issuecomment-4185767085):

  <command-line>: fatal error: mbedtls/error.h: No such file or directory

so this change fixes that error.
2026-04-11 06:35:42 -05:00
Catalin PatuleaandGitHub 4990bf51e3 Delete PointerQueue::dequeuePtrFromISR, unused since commit db766f1 (#99). (#10090) 2026-04-10 16:20:25 -05:00
TomandGitHub b2c8cbb78d Enhance traffic management by adjusting position update interval and refining hop exhaustion logic based on channel congestion (#9921) 2026-04-10 10:53:04 -05:00
Jonathan BennettandGitHub 8061f83704 Modify log output to show milliseconds (#10115)
Updated timestamp format to include milliseconds.
2026-04-10 07:21:24 -05:00
TomandGitHub a3b49b9028 Add powerlimits to reconfigured radio settings as well as init settings. (#10025)
* Add powerlimits to reconfigured radio settings as well as init settings.

* Refactor preamble length handling for wide LoRa configurations

* Moved the preamble setting to the main class and made the references static
2026-04-09 16:18:05 -05:00
Ben Meadors 17945884a5 Merge remote-tracking branch 'origin/master' into develop 2026-04-09 06:21:33 -05:00
Ben Meadors 1116f06139 Merge remote-tracking branch 'origin/master' into develop 2026-04-08 13:37:25 -05:00
c728cfdaf4 Automated version bumps (#10092)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
2026-04-06 06:39:53 -05:00
9322bcdb21 fix: redact MQTT password from log output (#10064)
MQTT password was logged in cleartext via LOG_INFO when connecting to
the broker, exposing credentials to anyone with log access. Replace
the password format specifier with a static mask.

Co-authored-by: Patrickschell609 <patrickschell609@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 07:54:51 -05:00
2f19a1d7a4 Consolidate SHTs into one class (#9859)
* Consolidate SHTs into one class

* Remove separate SHT imports
* Create one single SHTXX sensor type
* Let the SHTXXSensor class handle variant detection

* Minor logging improvements

* Add functions to set accuracy on SHT3X and SHT4X

* Fix variable init in constructor

* Add bus to SHT sensor init

* Update src/modules/Telemetry/Sensor/SHTXXSensor.cpp

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

* Update src/modules/Telemetry/Sensor/SHTXXSensor.cpp

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

* Fix accuracy conditions on SHTXX

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

* Merge upstream

* Add SHT2X detection on 0x40

* Read second part of SHT2X serial number

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-04 06:51:15 -05:00
71c8143f72 Update protobufs (#10074)
Co-authored-by: fifieldt <1287116+fifieldt@users.noreply.github.com>
2026-04-04 13:58:23 +11:00
222d3e6b62 Fix zero CR and add unit tests for applyModemConfig coding rate behavior (#10070)
* Fix zero CR and add unit tests for applyModemConfig coding rate behavior

* Apply suggestion from @Copilot

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

* fix: initialize region configuration in setUp for RadioInterface tests

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-04 08:27:39 +11:00
Chloe BethelandGitHub 2e6519bb98 Add a hardfault handler so it's more obvious when STM32 crashes. (#10071)
* Add hardfault handler so it's more obvious when STM32 crashes.

* thanks copilot
2026-04-03 14:50:39 +01:00
726d539174 fix: prevent division by zero in wind sensor averaging (#10059)
SerialModule's weather station parser divides by velCount and dirCount
to compute wind speed/direction averages. Both counters are only
incremented when their respective sensor readings arrive, but the
division runs whenever gotwind is true (set by EITHER reading) and
the averaging interval has elapsed.

If only WindDir arrives without WindSpeed (or vice versa), or if the
timer fires before any readings accumulate, the division produces
undefined behavior (floating-point divide by zero on embedded = NaN
or hardware fault depending on platform).

Fix: add velCount > 0 && dirCount > 0 guard to the averaging block.

Co-authored-by: Patrickschell609 <patrickschell609@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-03 06:07:59 -05:00
e7ee4bea18 Fix TFTDisplay::display to align pixels at 32-bit boundary (#9956)
* Fix TFTDisplay partial update: align spans to 32-bit boundary for GDMA

The device, such as esp32c6, require 32-bit alignment for the data.

The patch aligns start and end of the update pixels buffer.

* Update src/graphics/TFTDisplay.cpp

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

* Apply suggestion from @Copilot

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-04-02 20:57:19 -05:00
TomandGitHub efd2613bd7 feat: add new configuration files for LR11xx variants (#9761)
* feat: add new configuration files for LR11xx variants

* style: reformat RF switch mode table for improved readability
2026-04-01 10:46:27 +11:00
f88bc732cc Improved manual build flow to make it easier (#8839)
* Improved flow to make easier

The emojis are intentional! I had minimal LLM input!!!

* try and fix input variable sanitisation

* and again

* again

* Copilot fixed it for me

* copilot didn't fix it for me

* copilot might have fixed it and I broke it by copypasting

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-31 07:53:59 -05:00
Ben Meadors 0746f8cfff Merge remote-tracking branch 'origin/master' into develop 2026-03-30 14:37:29 -05:00
Ben Meadors 0ac95e370b Merge remote-tracking branch 'origin/master' into develop 2026-03-30 14:10:25 -05:00
283ccb83d1 Configure NFC pins as GPIO for older bootloaders (#10016)
* Configure NFC pins as GPIO for older bootloaders

Should hopefully solve the issue in https://github.com/meshtastic/firmware/issues/9986

* Fix formatting of CONFIG_NFCT_PINS_AS_GPIOS flag in platformio.ini

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-30 09:03:10 -05:00
5319bc7c2c Fix W5100S socket exhaustion blocking MQTT and additional TCP clients (#9770)
The W5100S Ethernet chip has only 4 hardware sockets. On RAK4631
Ethernet gateways with syslog and NTP enabled, all 4 sockets were
permanently consumed (NTP UDP + Syslog UDP + TCP API listener + TCP
API client), leaving none for MQTT, DHCP lease renewal, or additional
TCP connections.

- NTP: Remove permanent timeClient.begin() at startup; NTPClient::update()
  auto-initializes when needed. Add timeClient.end() after each query to
  release the UDP socket immediately.
- Syslog: Remove socket allocation from Syslog::enable(). Open and close
  the UDP socket on-demand in _sendLog() around each message send.
- MQTT: Fix socket leak in isValidConfig() where a successful test
  connection was never closed (PubSubClient destructor does not call
  disconnect). Add explicit pubSub->disconnect() before returning.

Made-with: Cursor

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-30 07:12:23 -05:00
Ben MeadorsandGitHub db694f2f24 Merge pull request #10031 from meshtastic/master
Backmerge master -> develop
2026-03-29 15:01:39 -05:00
Ben Meadors 814f1289f6 Merge remote-tracking branch 'origin/master' into develop 2026-03-28 07:26:46 -05:00
0b7556b590 MUI: WiFi map tile download: heltec V4 adaptations (#10011)
* rotated MUI

* mui-maps heltec-v4 adaptations

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-27 15:39:26 -05:00
Ben Meadors f7e4ac3e43 Merge remote-tracking branch 'origin/master' into develop 2026-03-27 08:43:19 -05:00
c36ae159ed Fix rak_wismeshtag low‑voltage reboot hang after App configuration (#9897)
* Fix TAG low‑voltage reboot hang after App configuration

* nRF52: Move low-VDD System OFF logic to variant hook

* Addressed review

* serialize SAADC access with shared mutex for VDD and battery reads

* raise LPCOMP wake threshold to ensure rising-edge wake

* Trunk fmt

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-27 06:56:19 -05:00
Chloe BethelandGitHub 33e7f16c05 Supporting STM32WL is like squeezing blood from a stone (#10015) 2026-03-27 06:52:00 -05:00
Jason PandGitHub d3a86841b9 Update External Notifications with a full redo of logic gates (#10006)
* Update External Notifications with a full redo of pathways

* Correct comments.

* Fix TWatch S3 and use HAS_DRV2605
2026-03-25 14:46:24 -05:00
Austin Lane e14b8d385a Remove unneeded GH perms
Reduce perms to least-necessary
Remove merge_queue.yml since it's never been used and is now stale
Remove comment-artifact, it hasn't worked in ages.
2026-03-24 08:13:59 -04:00
AustinandGitHub 8ce1a872eb Add timeout to PPA uploads (#9989)
Don't allow dput to run for more than 15 minutes (successful runs take about ~8 minutes)
2026-03-23 20:15:56 -05:00
Austin Lane abfa346630 Cleanup GH Actions 2026-03-23 11:17:30 -04:00
2ffe2d829c Fixes #9850: Double space issue with Cyrillic OLED font (#9971)
* Fixes double space OLEDDisplayFontsRU.cpp

* Fixes double space OLEDDisplayFontsUA.cpp

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-23 07:06:53 -05:00
bfaf6c6b20 fix(routing): prevent licensed users from rebroadcasting packets to or from unlicensed users (#9958)
* fix(routing): prevent licensed users from rebroadcasting packets from unlicensed or unknown users

* fix(routing): prevent licensed users from rebroadcasting packets to or from unlicensed users

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-22 13:47:52 -05:00
d293d654a0 add heltec_mesh_node_t096 board. (#9960)
* add heltec_mesh_node_t096 board.

* Fixed the GPS reset pin comments.

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

* Added compiles if NUM_PA_POINTS is not defined.

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

* Correct the pin description.

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

* Specify the version of the dependency library TFT_eSPI.

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

* Adding fields missing from the .ini file.

* Modify the screen SPI frequency to 40 MHz.

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-22 09:54:46 -05:00
8384659608 fix: apply all LoRa config changes live without rebooting (#9962)
* fix: apply all LoRa config changes live without rebooting

All LoRa radio settings (SF, BW, CR, frequency, power, preset,
sx126x_rx_boosted_gain) now apply immediately via reconfigure()
without requiring a node reboot.

- AdminModule: requiresReboot = false for all LoRa config changes;
  LoRa changes were already handled by the configChanged observer
  calling reconfigure() but the reboot flag was set unnecessarily
- AdminModule: validate LORA_24 region against radio hardware at
  config time; reject with BAD_REQUEST if hardware lacks 2.4 GHz
  capability (wideLora() returns false or no radio instance)
- SX126xInterface/LR11x0Interface: apply sx126x_rx_boosted_gain in
  reconfigure(); register 0x08AC is writable in STDBY mode (SX1261/2
  datasheet §9.6); retention registers written so setting survives
  warm-sleep cycles; log warning on setter failure
- DebugRenderer: show BW/SF/CR on debug screen when custom modem is
  active instead of the preset name
- DisplayFormatters: clarify comment on getModemPresetDisplayName

* fix: remove redundant reboot after LoRa config changes in on-device menus

Region, frequency slot, and radio preset pickers in MenuHandler all
called reloadConfig() then immediately set rebootAtMsec. reloadConfig()
already fires the configChanged observer which calls reconfigure(), so
the forced reboot was unnecessary — same rationale as the parent commit.

* fix: guard LORA_24 region selection against hardware capability in on-device menu

Without a reboot, reconfigure() now applies region changes directly.
Previously getRadio() caught the LORA_24-on-sub-GHz mismatch post-reboot
and reverted to UNSET — that safety net is gone. Add an explicit wideLora()
check in LoraRegionPicker so sub-GHz-only hardware silently ignores LORA_24
selection instead of attempting a live reconfigure with an invalid frequency.

---------

Co-authored-by: elwimen <elwimen@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-22 09:39:41 -05:00
Robert SasakandGitHub a632d0133f Add LED_BUILTIN for variant tlora_v1 (#9973) 2026-03-22 08:42:13 -05:00
f982b58d61 Fix: Enable touch-to-backlight on T-Echo (not just T-Echo Plus) (#9953)
The touch-to-backlight feature was gated behind TTGO_T_ECHO_PLUS, but
the regular T-Echo has the same backlight pin (PIN_EINK_EN, P1.11).
This changes the guard to use PIN_EINK_EN only, so any device with an
e-ink backlight pin gets the feature.

Fixes #7630

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-22 06:43:41 -05:00
Ben Meadors efb0b299b2 Merge remote-tracking branch 'origin/master' into develop 2026-03-22 06:42:37 -05:00
eb80d3141d Fixes #9792 : Hop with Meshtastic ffff and ?dB is added to missing hop in traceroute (#9945)
* Fix issue 9792, decode packet for TR test

* Fix 9792: Assure packet id decoded for TR test

* Potential fix for pull request finding

Log improvement for failure to decode packet.

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

* trunk fmt

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Tom Fifield <tom@tomfifield.net>
2026-03-21 07:33:45 +11:00
Ben Meadors 3604c1255d Consolidate PKI key generation logic into ensurePkiKeys method 2026-03-20 09:53:12 -05:00
22d63fa69c Lora settings expansion and validation logic improvement (#9878)
* Enhance LoRa configuration with modem presets and validation logic

* Rename bootstrapLoRaConfigFromPreset tests to validateModemConfig for clarity and consistency

* additional tidy-ups to the validateModemConfig - still fundamentally broken at this point

* Enhance region validation by adding numPresets to RegionInfo and implementing validateRegionConfig in RadioInterface

* Add validation for modem configuration in applyModemConfig

* Fix region unset handling and improve modem config validation in handleSetConfig

* Refactor LoRa configuration validation methods and introduce clamping method for invalid settings

* Update handleSetConfig to use fromOthers parameter to either correct or reject invalid settings

* Fix some of the copilot review comments for LoRa configuration validation and clamping methods; add tests for region and preset handling

* Redid the slot default checking and calculation. Should resolve the outstanding comments.

* Add bandwidth calculation for LoRa modem preset fallback in clampConfigLora

* Remove unused preset name variable in validateConfigLora and fix default frequency slot check in applyModemConfig

* update tests for region handling

* Got the synthetic colleague to add mock service for testing

* Flash savings... hopefully

* Refactor modem preset handling to use sentinel values and improve default preset retrieval

* Refactor region handling to use profile structures for modem presets and channel calculations

* added comments for clarity on parameters

* Add shadow table tests and validateConfigLora enhancements for region presets

* Add isFromUs tests for preset validation in AdminModule

* Respond to copilot github review

* address copilot comments

* address null poointers

* fix build errors

* Fix the fix, undo the silly suggestions from synthetic reviewer.

* we all float here

* Fix include path for AdminModule in test_main.cpp

* Potential fix for pull request finding

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

* More suggestion fixes

* admin module merge conflicts

* admin module fixes from merge hell

* fix: initialize default frequency slot and custom channel name; update LNA mode handling

* save some bytes...

* fix: simplify error logging for bandwidth checks in LoRa configuration

* Update src/mesh/MeshRadio.h

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-03-20 07:43:47 -05:00
Ben Meadors fc030d2d19 Merge remote-tracking branch 'origin/master' into develop 2026-03-20 07:43:15 -05:00
Ben Meadors 4a534f02a4 fix(gps): prevent GPS re-enablement in NOT_PRESENT mode 2026-03-19 15:27:59 -05:00
0ea408e12b fix: MQTT settings silently fail to persist when broker is unreachable (#9934)
* fix: MQTT settings silently fail to persist when broker is unreachable

isValidConfig() was testing broker connectivity via connectPubSub() as
part of config validation. When the broker was unreachable (network not
ready, DNS failure, server down), the function returned false, causing
AdminModule to skip saving settings entirely — silently.

This removes the connectivity test from isValidConfig(), which now only
validates configuration correctness (TLS support, default server port).
Connectivity is handled by the MQTT module's existing reconnect loop.

Fixes #9107

* Add client warning notification when MQTT broker is unreachable

Per maintainer feedback: instead of silently saving when the broker
can't be reached, send a WARNING notification to the client saying
"MQTT settings saved, but could not reach the MQTT server."

Settings still always persist regardless of connectivity — the core
fix from the previous commit is preserved. The notification is purely
advisory so users know to double-check their server address and
credentials if the connection test fails.

When the network is not available at all, the connectivity check is
skipped entirely with a log message.

* Address Copilot review feedback

- Fix warning message wording: "Settings will be saved" instead of
  "Settings saved" (notification fires before AdminModule persists)
- Add null check on clientNotificationPool.allocZeroed() to prevent
  crash if pool is exhausted (matches AdminModule::sendWarning pattern)
- Fix test comments to accurately describe conditional connectivity
  check behavior and IS_RUNNING_TESTS compile-out

* Remove connectivity check from isValidConfig entirely

Reverts the advisory connectivity check added in the previous commit.
While the intent was to warn users about unreachable brokers,
connectPubSub() mutates the isConnected state of the running MQTT
module and performs synchronous network operations that can block
the config-save path.

The cleanest approach: isValidConfig() validates config correctness
only (TLS support, default server port). The MQTT reconnect loop
handles connectivity after settings are persisted and the device
reboots. If the broker is unreachable, the user will see it in the
MQTT connection status — no special notification needed.

This returns to the simpler design from the first commit, which was
tested on hardware and confirmed working.

* Use lightweight TCP check instead of connectPubSub for validation

Per maintainer feedback: users need connectivity feedback, but
connectPubSub() mutates the module's isConnected state.

This uses a standalone MQTTClient TCP connection test that:
- Checks if the server IP/port is reachable
- Sends a WARNING notification if unreachable
- Does NOT establish an MQTT session or mutate any module state
- Does NOT block saving — isValidConfig always returns true

The TCP test client is created locally, used, and destroyed within
the function scope. No side effects on the running MQTT module.

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-19 13:00:00 -05:00
644d0d4a15 Fix for preserving pki_encrypted and public_key when relaying UDP multicast packets to radio. (#9916)
* Fix for preserving pki_encrypted and public_key when relaying UDP multicast packets to radio.

PKI DMs sent over UDP multicast had their pki_encrypted flag and public_key fields explicitly cleared before being forwarded to the LoRa radio. This caused the receiving node to treat the packet as a channel-encrypted message it couldn't decrypt, silently dropping it.

The MQTT ingress path correctly preserves these fields. The UDP multicast ingress path should behave the same way.

* Zeroize MeshPacket before decoding

Zeroize MeshPacket before decoding to prevent data leakage.

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-19 10:52:52 -05:00
f04746a928 Fix RAK4631 Ethernet gateway API connection loss after W5100S brownout (#9754)
* Fix RAK4631 Ethernet gateway API connection loss after W5100S brownout

PoE power instability can brownout the W5100S while the nRF52 MCU keeps
running, causing all chip registers (MAC, IP, sockets) to revert to
defaults. The firmware had no mechanism to detect or recover from this.

Changes:
- Detect W5100S chip reset by periodically verifying MAC address register
  in reconnectETH(); on mismatch, perform full hardware reset and
  re-initialize Ethernet interface and services
- Add deInitApiServer() for clean API server teardown during recovery
- Add ~APIServerPort destructor to prevent memory leaks
- Switch nRF52 from EthernetServer::available() to accept() to prevent
  the same connected client from being repeatedly re-reported
- Add proactive dead-connection cleanup in APIServerPort::runOnce()
- Add 15-minute TCP idle timeout to close half-open connections that
  consume limited W5100S hardware sockets

Fixes meshtastic/firmware#6970

Made-with: Cursor

* Log actual elapsed idle time instead of constant timeout value

Address Copilot review comment: log millis() - lastContactMsec to show
the real time since last client activity, rather than always logging the
TCP_IDLE_TIMEOUT_MS constant.

Made-with: Cursor

* Update src/mesh/api/ServerAPI.h

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

* Stop UDP multicast handler during W5100S brownout recovery

After a W5100S chip brownout, the udpHandler isRunning flag stays
true while the underlying socket is dead. Without calling stop(),
the subsequent start() no-ops and multicast is silently broken
after recovery.

Made-with: Cursor

* Address Copilot review: recovery flags and timeout constant

Move ethStartupComplete and ntp_renew reset to immediately after
service teardown, before Ethernet.begin(). Previously, if DHCP
failed the early return left ethStartupComplete=true, preventing
service re-initialization on subsequent retries.

Replace #define TCP_IDLE_TIMEOUT_MS with static constexpr uint32_t
for type safety and better C++ practice.

Made-with: Cursor

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-19 08:37:39 -05:00
WesselandGitHub 1be2529fb9 Enable LNA by default for Heltec v4.3 (#9906)
It should only be disabled by users that have problems with it.
2026-03-19 08:11:10 -05:00
Andrew YongandGitHub 959756abf1 fix(tlora-pager): Remove SDCARD_USE_SPI1 so SX1262 and SD card can share SPI bus (#9870)
Problem:
- Inserting a µSD card causes RadioLib to hit a critical error and reboot
- Device enters a boot loop as the SD card remains inserted

Reproduction:
- Insert a µSD card and power on
- RadioLib reports a critical error on boot
- Device reboots, repeating indefinitely

Root cause:
- On T-Lora Pager, SX1262 and the µSD slot share the same physical SPI bus
  (same SCK/MOSI/MISO pins, differentiated only by CS)
- SDCARD_USE_SPI1 is intended for boards where SD is on a separate SPI bus;
  it initializes a second ESP32 SPI peripheral (SPI3) for SD
- SPI2 is already driving those same pins for LoRa, so both controllers
  simultaneously drive the same GPIO lines, causing bus contention

Fix:
- Remove SDCARD_USE_SPI1 so both devices share a single SPI peripheral (SPI2),
  with CS pins providing device selection as intended
- Tested on a custom fork of device-ui; LoRa and SD card map tiles both work
  correctly with an SD card inserted

Signed-off-by: Andrew Yong <me@ndoo.sg>
2026-03-19 07:20:15 -05:00
c88b802e32 Remove early return during scan of BME address for BMP sensors (#9935)
* Enable pre-hop drop handling by default

* Remove early break if BME/DPS sensors are not detected at the BME address

* revert sneaky change

* Potential fix for pull request finding

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-19 07:12:50 -05:00
fw190d13andGitHub 9f74fc11de hexDump: Add const to the buf parameter in hexDump. (#9944)
The function only reads the buffer, so marking it const clarifies intent
and prevents accidental modification.
2026-03-19 06:13:34 -05:00
HarukiToredaandGitHub 2ef09d17b9 BaseUI: Emote Refactoring (#9896)
* Emote refactor for BaseUI

* Trunk Check

* Copilot suggestions
2026-03-17 20:42:37 -05:00
Ben Meadors 19d070c284 Trunk 2026-03-17 14:01:53 -05:00
Ben Meadors e24db2994b Merge remote-tracking branch 'origin/master' into develop 2026-03-17 13:28:25 -05:00
ac7a58cd45 Fix: Traceroute through MQTT misses uplink node if MQTT is encrypted (#9798)
* Attempt to fix issue 9713

* Code formatting issue.

* Remade the fix to follow Copilot observations on PR

* Rebuild after AI and GUVWAF

* Update src/mesh/Router.cpp

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

* Update src/mesh/Router.cpp

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

* trunk fmt

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: GUVWAF <thijs@havinga.eu>
Co-authored-by: GUVWAF <78759985+GUVWAF@users.noreply.github.com>
2026-03-17 06:48:29 +11:00
53c21eb30d Deprecate/block packets with a missing/invalid hop_start value (pre-hop firmware) (related to issue #7369) (#9476)
* Deprecate forwarding for invalid hop_start

* Add pre-hop packet drop policy

* Log ignored rebroadcasts for pre-hop packets

* Respect pre-hop policy ALLOW in routing gates

* Exempt local packets from pre-hop drop policy

* Format pre-hop log line

* Add MODERN_ONLY rebroadcast mode for pre-hop packets

* Simplify implementation for drop packet only behaviour

* Revert formatting-only changes

* Match ReliableRouter EOF formatting

* Make pre-hop drop a build-time flag

* Rework to compile/build flag MESHTASTIC_PREHOP_DROP

* Set MESHTASTIC_PREHOP_DROP off by default

* Inline pre-hop hop_start validity check

---------

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
Co-authored-by: Jord <650645+DivineOmega@users.noreply.github.com>
2026-03-16 06:35:33 -05:00
Tom FifieldandGitHub e51e6cad84 Remove GPS Baudrate locking for Seeed Xiao S3 Kit (#9374)
The Seeed Xiao S3 Kit's default GPS is an L76K which operates at 9600 baud, so when this variant was defined that baud rate was specified.

However, this is a development board and it is expected that users can attach their own devices. This includes GPS, which may operate at a different baud rate. The current fixed baud rate prevents this, so this patch removes that setting.

This will revert to the regular automatic probe method. This will successfully detect the L76K as before (the same speed as before since 9600 baud is the first baud rate checked), but also allow other GPSes at other baud rates to be detected.

Thanks to @ScarpMarc for the report

Fixes https://github.com/meshtastic/firmware/issues/9373#issuecomment-3774802763
2026-03-16 19:28:21 +11:00
TomandGitHub 4890f7084f Add spoof detection for UDP packets in UdpMulticastHandler (#9905)
* Add spoof detection for UDP packets in UdpMulticastHandler

* Implement isFromUs function for packet origin validation

* ampersand
2026-03-14 19:34:19 -05:00
oscgonferandGitHub 3fcbfe4370 Remove a bunch of warnings in SEN5X (#9884) 2026-03-12 06:18:56 -05:00
da808cb43b Automated version bumps (#9886)
Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-11 06:52:18 -05:00
Ben Meadors 82580c6798 Initialize LoRaFEMInterface with default fem_type 2026-03-11 06:48:46 -05:00
016e68ec53 Traffic Management Module for packet forwarding logic (#9358)
* Add ESP32 Power Management lessons learned document

Documents our experimentation with ESP-IDF DFS and why it doesn't
work well for Meshtastic (RTOS locks, BLE locks, USB issues).

Proposes simpler alternative: manual setCpuFrequencyMhz() control
with explicit triggers for when to go fast vs slow.

* Addition of traffic management module

* Fixing compile issues, but may still need to update protobufs.

* Fixing log2Floor in cuckoo hash function

* Adding support for traffic management in PhoneAPI.

* Making router_preserve_hops work without checking if the previous hop was a router. Also works for CLIENT_BASE.

* Adding station-g2 and portduino varients to be able to use this module.

* Spoofing from address for nodeinfo cache

* Changing name and behavior for zero_hop_telemetry / zero_hop_position

* Name change for exhausting telemetry packets and setting hop_limit to 1 so it will be 0 when sent.

* Updated hop logic, including exhaustRequested flag to bypass some checks later in the code.

* Reducing memory on nrf52 nodes further to 12 bytes per entry, 12KB total using 8 bit hashes with 0.4% collision. Probably ok. Adding portduino to the platforms that don't need to worry about memory as much.

* Fixing hopsAway for nodeinfo responses.

* traffic_management.nodeinfo_direct_response_min_hops -> traffic_management.nodeinfo_direct_response_max_hops

* Removing dry run mode

* Updates to UnifiedCacheEntry to use a common cache, created defaults for some values, reduced a couple bytes per entry by using a resolution-scale time selection based on configuration value.

* Enhance traffic management logging and configuration. Updated log messages in NextHopRouter and Router to include more context. Adjusted traffic management configuration checks in AdminModule and improved cache handling in TrafficManagementModule. Ensured consistent enabling of traffic management across various variants.

* Implement destructor for TrafficManagementModule and improve cache allocation handling. The destructor ensures proper deallocation of cache memory based on its allocation source (PSRAM or heap). Additionally, updated cache allocation logic to log warnings only when PSRAM allocation fails.

* Update TrafficManagementModule with enhanced comments for clarity and improve cache handling logic. Update protobuf submodule to latest commit.

* Creating consistent log messages

* Remove docs/ESP32_Power_Management.md from traffic_module

* Add unit tests for Traffic Management Module functionality

* Fixing compile issues, but may still need to update protobufs.

* Adding support for traffic management in PhoneAPI.

* Making router_preserve_hops work without checking if the previous hop was a router. Also works for CLIENT_BASE.

* Enhance traffic management logging and configuration. Updated log messages in NextHopRouter and Router to include more context. Adjusted traffic management configuration checks in AdminModule and improved cache handling in TrafficManagementModule. Ensured consistent enabling of traffic management across various variants.

* Implement destructor for TrafficManagementModule and improve cache allocation handling. The destructor ensures proper deallocation of cache memory based on its allocation source (PSRAM or heap). Additionally, updated cache allocation logic to log warnings only when PSRAM allocation fails.

* Update TrafficManagementModule with enhanced comments for clarity and improve cache handling logic. Update protobuf submodule to latest commit.

* Add mock classes and unit tests for Traffic Management Module functionality.

* Refactor setup and loop functions in test_main.cpp to include extern "C" linkage

* Update comment to include reduced  memory requirements

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

* Re-arranging comments for programmers with the attention span of less than 5 lines of code.

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

* Update comments in TrafficManagementModule to reflect changes in timestamp epoch handling and memory optimization details.

* bug: Use node-wide config_ok_to_mqtt setting for cached NodeInfo replies.

* Better way to handle clearing the ok_to_mqtt bit

* Add bucketing to cuckoo hashing, allowing for 95% occupied rate before major eviction problems.

* Extend nodeinfo cache for psram devices.

* Refactor traffic management to make hop exhaustion packet-scoped. Nice catch.

* Implement better position precision sanitization in TrafficManagementModule.

* Added logic in TrafficManagementModule to invalidate stale traffic state. Also, added some tests to avoid future me from creating a regression here.

* Fixing tests for native

* Enhance TrafficManagementModule to improve NodeInfo response handling and position deduplication logic. Added tests to ensure local packets bypass transit filters and that NodeInfo requests correctly update the requester information in the cache. Updated deduplication checks to prevent dropping valid position packets under certain conditions.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 06:12:12 -05:00
79f469ce71 pioarduino Heltec v4: fix build due to LED_BUILTIN compile error. (#9875)
Fixes this build error:

  <command-line>: error: expected unqualified-id before '-' token
  /home/<snip>/.platformio/packages/framework-arduinoespressif32/variants/esp32s3/pins_arduino.h:15:22: note: in expansion of macro 'LED_BUILTIN'
     15 | static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT + PIN_RGB_LED;
        |                      ^~~~~~~~~~~

More info: https://github.com/meshtastic/firmware/pull/9122#issuecomment-4028263894

The fix is consistent with variants/esp32s3/heltec_v3/platformio.ini

This commit is intentionally on the develop branch, because it's harmless to
develop branch, and makes us more ready for pioarduino when the time comes.

Heltec v4 introduced in: https://github.com/meshtastic/firmware/pull/7845

Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
2026-03-10 06:19:25 -05:00
0501e177e9 T-mini Eink S3 Support for both InkHUD and BaseUI (#9856)
* Tmini Eink fix

* tuning

* better refresh

* Fix to lora pins to be like the original.

* Update pins_arduino.h

* removed dead flags from previous tests

* Update src/graphics/niche/Drivers/EInk/GDEW0102T4.cpp

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

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-09 17:37:34 -05:00
432 changed files with 32598 additions and 6726 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ runs:
done
- name: PlatformIO ${{ inputs.arch }} download cache
uses: actions/cache@v6
uses: actions/cache@v5
with:
path: ~/.platformio/.cache
key: pio-cache-${{ inputs.arch }}-${{ hashFiles('.github/actions/**', '**.ini') }}
+1 -1
View File
@@ -5,7 +5,7 @@ runs:
using: composite
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+93
View File
@@ -135,6 +135,99 @@ On top of authorization, any remote admin message that **mutates** state (not a
- **Channel 0 PSK change** → every peer must re-learn the channel hash; cached NodeInfo becomes temporarily unreachable until the next broadcast.
- **`security.private_key` blanked via admin** → regenerates both halves (unless in Ham mode) and propagates the new public key via NodeInfo.
## NodeDB Layout (v25)
`DEVICESTATE_CUR_VER = 25`, `DEVICESTATE_MIN_VER = 24`. The on-device NodeDB was split in v25 into a slim header table plus four optional satellite stores. Older v24 saves auto-migrate at boot. Old training-data instincts (`node->user.long_name`, `node->position.latitude_i`, `node->is_favorite`, `node->device_metrics.battery_level`) are wrong now — the fields aren't there. Read this section before touching anything that walks `nodeDB->meshNodes`.
### Slim `NodeInfoLite`
`UserLite` is flattened onto `NodeInfoLite` (no nested sub-message); `position` and `device_metrics` are removed entirely (tags reserved). MAC address is dropped. Long names are capped at 25 chars (`max_size:25` in `deviceonly.options`); `hw_model` and `role` are `int_size:8`. Encoded size dropped from ~166 B → ~105 B per node.
Booleans are bit-packed into `NodeInfoLite.bitfield`. **Do not read or write the bits directly** — use the inline helpers in `src/mesh/NodeDB.h`:
```cpp
nodeInfoLiteHasUser(n) // bit 5 — user fields populated
nodeInfoLiteIsFavorite(n) // bit 3
nodeInfoLiteIsIgnored(n) // bit 4
nodeInfoLiteIsMuted(n) // bit 1
nodeInfoLiteIsLicensed(n) // bit 6 — Ham mode peer
nodeInfoLiteIsKeyManuallyVerified(n) // bit 0
nodeInfoLiteHasIsUnmessagable(n) // bit 8 — "is_unmessagable was sent"
nodeInfoLiteIsUnmessagable(n) // bit 7
// via_mqtt is bit 2 (mask exposed; predicate uses the mask directly)
nodeInfoLiteSetBit(n, NODEINFO_BITFIELD_IS_FAVORITE_MASK, true); // setter
```
### Satellite stores
Four `std::unordered_map<NodeNum, …>` members on `NodeDB`, each gated by its own build flag:
| Map | Value type | Build flag |
| ----------------- | ------------------------------- | ---------------------------------- |
| `nodePositions` | `meshtastic_PositionLite` | `MESHTASTIC_EXCLUDE_POSITIONDB` |
| `nodeTelemetry` | `meshtastic_DeviceMetrics` | `MESHTASTIC_EXCLUDE_TELEMETRYDB` |
| `nodeEnvironment` | `meshtastic_EnvironmentMetrics` | `MESHTASTIC_EXCLUDE_ENVIRONMENTDB` |
| `nodeStatus` | `meshtastic_StatusMessage` | `MESHTASTIC_EXCLUDE_STATUSDB` |
Defaults are ON (i.e., maps **excluded**) for STM32WL only — see `src/mesh/mesh-pb-constants.h`. On every other arch all four maps are present. When excluded, the map member is absent and the corresponding accessors return `false`.
All four maps are guarded by **`mutable concurrency::Lock satelliteMutex`** — concurrent access from receive threads, the phone API state machine, and the renderer is the rule, not the exception.
### Accessor convention
**Never hand out pointers into the maps.** Use the copy-out accessors on `NodeDB`:
```cpp
bool copyNodePosition(NodeNum, meshtastic_PositionLite &out) const;
bool copyNodeTelemetry(NodeNum, meshtastic_DeviceMetrics &out) const;
bool copyNodeEnvironment(NodeNum, meshtastic_EnvironmentMetrics &out) const;
bool copyNodeStatus(NodeNum, meshtastic_StatusMessage &out) const;
```
Each takes the lock, copies the value if present, returns `false` if the entry is absent or the DB is excluded. Pass-by-out-param is deliberate — pointer-style accessors would invite UAF and lock-leak bugs across the renderer. The "has any X" convenience predicates (`hasValidPosition` etc.) are implemented in terms of these.
Writers go through `setNodeStatus`, `updatePosition`, `updateTelemetry` (which dispatches on `which_variant` for device vs environment metrics) — these own the lock and the eviction hooks.
### Eviction
Every code path that drops a node from the header table must also evict the satellites. The single chokepoint is `eraseNodeSatellites(NodeNum)`; it's already called from `getOrCreateMeshNode`'s oldest-boring eviction, `removeNodeByNum`, both branches of `resetNodes`, `cleanupMeshDB`, `addFromContact`'s ignored-branch, and `AdminModule`'s `set_ignored_node`. Add new eviction sites here, not by calling `.erase()` directly.
### Sync flow: thin NodeInfo + post-COMPLETE_ID replay (no opt-in)
There is no capability flag and no special "gradient" nonce. The **default** sync flow is:
1. Config / module-config / channel / metadata segments (same as before).
2. `STATE_SEND_OWN_NODEINFO`**our own** NodeInfo, still bundled with our position and device_metrics (because the replay snapshot excludes our own NodeNum). Emitted via `ConvertToNodeInfo(lite)`.
3. `STATE_SEND_OTHER_NODEINFOS` — every other peer's NodeInfo, **always thin** (no `position`, no `device_metrics`). Emitted via `ConvertToNodeInfoThin(lite)`.
4. `STATE_SEND_FILEMANIFEST``STATE_SEND_COMPLETE_ID` — the phone sees `config_complete_id` and treats sync as done.
5. `STATE_SEND_PACKETS` — live mesh packets, with a trailing replay drain interleaved. The replay drain walks four cached satellite stores in order (positions → telemetry → environment → status) and emits each cached entry as an ordinary `MeshPacket` on the matching portnum (`POSITION_APP`, `TELEMETRY_APP` device + environment variants, `NODE_STATUS_APP`). These are indistinguishable on the wire from live mesh traffic, so clients need no special handling — any code that already updates UI on `POSITION_APP` etc. works.
`PhoneAPI::sendConfigComplete()` arms `replayPhase = REPLAY_PHASE_POSITIONS` for default/full sync and `SPECIAL_NONCE_ONLY_NODES`, while `SPECIAL_NONCE_ONLY_CONFIG` skips replay. The drain runs inside `STATE_SEND_PACKETS` via `popReplayPacket()`, lower priority than live traffic. When all four phases drain, `replayPhase` flips back to `REPLAY_PHASE_IDLE` and the snapshot vectors get `shrink_to_fit`ed.
STM32WL and any other build with all four `MESHTASTIC_EXCLUDE_*DB` flags set produces zero replay packets — `popReplayPacket` advances through each phase in microseconds without emitting anything.
Special nonces that still mean something:
- `SPECIAL_NONCE_ONLY_CONFIG` (69420) — skip node sync entirely, just config.
- `SPECIAL_NONCE_ONLY_NODES` (69421) — skip config segments, jump straight to `STATE_SEND_OWN_NODEINFO`. Still gets the post-COMPLETE_ID replay drain.
There are no other reserved nonces; everything else is a fresh random `want_config_id` from the client.
### v24 → v25 migration
The legacy migration code lives in **`src/mesh/NodeDBLegacyMigration.cpp`**, not in `NodeDB.cpp`. It owns the `meshtastic_NodeDatabase_Legacy` callback and `NodeDB::migrateLegacyNodeDatabase()`. The legacy proto descriptor is `protobufs/meshtastic/deviceonly_legacy.proto` (only included by the migration TU). The boot path peeks the file's leading version tag, runs the migration if `version < 25`, then re-saves in v25 layout. The legacy descriptor is scheduled for removal once `DEVICESTATE_MIN_VER` is bumped.
### Read-site rules of thumb
- Never `node->position.X` / `node->device_metrics.X` — those fields no longer exist. Pull from the satellite map via `copyNodePosition` / `copyNodeTelemetry`.
- Never `node->user.long_name``long_name`, `short_name`, `public_key`, `hw_model`, `role`, `macaddr` (gone), `is_licensed`, `is_unmessagable` are flat on `NodeInfoLite`.
- Never `node->is_favorite` / `node->is_ignored` / `node->via_mqtt` / `node->is_key_manually_verified` — use the bitfield helpers.
- Never assume `nodeDB->getMeshNode(num)->position.time` — call `copyNodePosition` and check the return.
- Don't lock `satelliteMutex` yourself in renderer code; the copy-out accessors already do.
Unit tests for the conversion layer live in `test/test_type_conversions/test_main.cpp` (Unity) — bitfield round-trips, `long_name` truncation, thin-vs-full conversions. Add cases there when extending the schema.
## Project Structure
```
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
outputs:
artifact-id: ${{ steps.upload-firmware.outputs.artifact-id }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
runs-on: macos-${{ inputs.macos_ver }}
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+42 -28
View File
@@ -4,9 +4,14 @@ on:
workflow_dispatch:
inputs:
# trunk-ignore(checkov/CKV_GHA_7)
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
arch:
type: choice
options:
- all
- esp32
- esp32s3
- esp32c3
@@ -15,35 +20,21 @@ on:
- rp2040
- rp2350
- stm32
target:
type: string
required: false
description: Choose the target board, e.g. nrf52_promicro_diy_tcxo. If blank, will find available targets.
# find-target:
# type: boolean
# default: true
# description: 'Find the available targets'
description: Choose an arch to limit the search, or 'all' to search all architectures.
default: all
permissions: read-all
jobs:
find-targets:
if: ${{ inputs.target == '' }}
strategy:
fail-fast: false
matrix:
arch:
- esp32
- esp32s3
- esp32c3
- esp32c6
- nrf52840
- rp2040
- rp2350
- stm32
- all
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -51,20 +42,43 @@ jobs:
- run: pip install -U platformio
- name: Generate matrix
id: jsonStep
env:
BUILDTARGET: ${{ inputs.target }}
MATRIXARCH: ${{ inputs.arch }}
run: |
TARGETS=$(./bin/generate_ci_matrix.py ${{matrix.arch}} --level extra)
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: ${{matrix.arch}}" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "Targets:" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) |.[] | "- " + .board' >> $GITHUB_STEP_SUMMARY
if [ "$BUILDTARGET" = "" ]; then
echo "Name: $GITHUB_REF_NAME" >> $GITHUB_STEP_SUMMARY
echo "Base: $GITHUB_BASE_REF" >> $GITHUB_STEP_SUMMARY
echo "Arch: $MATRIXARCH" >> $GITHUB_STEP_SUMMARY
echo "Ref: $GITHUB_REF" >> $GITHUB_STEP_SUMMARY
echo "## 🎯 The following target boards are available to build:" >> $GITHUB_STEP_SUMMARY
echo "| Platform | Board |" >> $GITHUB_STEP_SUMMARY
echo "| -------- | ----- |" >> $GITHUB_STEP_SUMMARY
echo $TARGETS | jq -r 'sort_by(.board) | sort_by(.platform) |.[] | "| " + .platform + " | " + .board + " |" ' >> $GITHUB_STEP_SUMMARY
else
echo "We build this one:" >> $GITHUB_STEP_SUMMARY
ARCH=$(echo "$TARGETS" | jq --arg BUILDTARGET "$BUILDTARGET" -r '.[] | select(.board==$BUILDTARGET) | .platform')
echo "| Platform | Board |" >> $GITHUB_STEP_SUMMARY
echo "| -------- | ----- |" >> $GITHUB_STEP_SUMMARY
echo "| $ARCH | "$BUILDTARGET" |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [[ "$ARCH" == "" ]]; then
echo "## ❌ Error: Target "$BUILDTARGET" not found!" >> $GITHUB_STEP_SUMMARY
else
echo "## ✅ Target "$BUILDTARGET" found, proceeding to build." >> $GITHUB_STEP_SUMMARY
fi
echo "You may need to refresh this page to make the built firmware appear below." >> $GITHUB_STEP_SUMMARY
echo "arch=$ARCH" >> $GITHUB_OUTPUT
fi
outputs:
arch: ${{ steps.jsonStep.outputs.arch }}
version:
if: ${{ inputs.target != '' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -78,12 +92,12 @@ jobs:
build:
if: ${{ inputs.target != '' && inputs.arch != 'native' }}
needs: [version]
needs: [version, find-targets]
uses: ./.github/workflows/build_firmware.yml
with:
version: ${{ needs.version.outputs.long }}
pio_env: ${{ inputs.target }}
platform: ${{ inputs.arch }}
platform: ${{ needs.find-targets.outputs.arch }}
gather-artifacts:
permissions:
@@ -93,7 +107,7 @@ jobs:
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
ref: ${{github.event.pull_request.head.ref}}
repository: ${{github.event.pull_request.head.repo.full_name}}
+1 -1
View File
@@ -47,7 +47,7 @@ jobs:
runs-on: ${{ inputs.runs-on }}
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -103,7 +103,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
-190
View File
@@ -1,190 +0,0 @@
name: Post Web Flasher Link Comment
on:
workflow_run:
workflows: [CI]
types: [completed]
permissions:
pull-requests: write
actions: read
jobs:
post-flasher-link:
if: >
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion != 'cancelled' &&
github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
steps:
# Per-board manifests carry the firmware's own metadata (activelySupported,
# displayName, ...) generated from each target's custom_meshtastic_* config.
- name: Download board manifests
uses: actions/download-artifact@v8
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
pattern: manifest-*
path: ./manifests
merge-multiple: true
- name: Post or update web flasher link comment
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- web-flasher-link -->';
const run = context.payload.workflow_run;
const { owner, repo } = context.repo;
// Resolve the PR number (run.pull_requests is empty for fork PRs)
let prNumber = run.pull_requests?.[0]?.number;
if (!prNumber) {
const { data: prs } = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner, repo, commit_sha: run.head_sha,
});
prNumber = (prs.find((pr) => pr.head.sha === run.head_sha) ?? prs[0])?.number;
}
if (!prNumber) {
core.info('No pull request associated with this run; skipping.');
return;
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
// Only comment on PRs authored by members of the organization.
// author_association MEMBER is computed by GitHub and reflects org
// membership (including concealed members); OWNER covers a repo owner.
const allowedAssociations = ['OWNER', 'MEMBER'];
if (!allowedAssociations.includes(pr.author_association)) {
core.info(`Author association ${pr.author_association} is not an org member; skipping.`);
return;
}
if (pr.state !== 'open') {
core.info('Pull request is not open; skipping.');
return;
}
if (pr.head.sha !== run.head_sha) {
core.info('Run is for an outdated commit; skipping.');
return;
}
// Require at least one per-arch firmware artifact from gather-artifacts
const artifacts = await github.paginate(github.rest.actions.listWorkflowRunArtifacts, {
owner, repo, run_id: run.id, per_page: 100,
});
const archRe = /^firmware-(esp32|esp32s3|esp32c3|esp32c6|nrf52840|rp2040|rp2350|stm32)-(\d+\.\d+\.\d+\.[0-9a-f]+)$/;
const archArtifacts = artifacts.filter((a) => archRe.test(a.name) && !a.expired);
if (archArtifacts.length === 0) {
core.info('No per-arch firmware artifacts found; skipping.');
return;
}
const version = archRe.exec(archArtifacts[0].name)[2];
const expiresAt = archArtifacts[0].expires_at
? new Date(archArtifacts[0].expires_at).toISOString().slice(0, 10)
: null;
// Read each built board's manifest (.mt.json). activelySupported,
// displayName and architecture come straight from the board's
// custom_meshtastic_* platformio config, so the list is in sync with
// the firmware itself — no external device database needed.
const fs = require('fs');
let boards = [];
try {
boards = fs.readdirSync('./manifests')
.filter((f) => f.endsWith('.mt.json'))
.map((f) => {
try { return JSON.parse(fs.readFileSync(`./manifests/${f}`, 'utf8')); }
catch { return null; }
})
.filter((m) => m && m.activelySupported === true && m.platformioTarget)
.map((m) => ({
board: m.platformioTarget,
platform: m.architecture || '',
// displayName is maintainer-authored text; escape table-breaking pipes
displayName: String(m.displayName || m.platformioTarget).replace(/\|/g, '\\|'),
image: Array.isArray(m.images) && m.images[0] ? String(m.images[0]) : '',
}))
.sort((a, b) => a.board.localeCompare(b.board));
} catch (e) {
core.warning(`Could not read board manifests: ${e.message}`);
}
const flasherUrl = `https://flasher.meshtastic.org/?pr=${prNumber}`;
// Device illustrations are served by the flasher from the same image
// names the manifest declares (custom_meshtastic_images). The flasher
// serves its SPA shell (HTML, 200) for unknown paths, so confirm each
// image really resolves to an image before linking it.
const imageBase = 'https://flasher.meshtastic.org/img/devices/';
await Promise.all(boards.map(async (b) => {
if (!b.image) return;
try {
const res = await fetch(`${imageBase}${encodeURIComponent(b.image)}`);
const type = res.headers.get('content-type') || '';
if (!res.ok || !type.startsWith('image/')) b.image = '';
} catch { b.image = ''; }
}));
const boardLines = boards
.map((b) => {
const img = b.image ? `<img src="${imageBase}${encodeURIComponent(b.image)}" alt="" height="34">` : '';
return `| ${img} | ${b.displayName} | [\`${b.board}\`](${flasherUrl}&device=${encodeURIComponent(b.board)}) | ${b.platform} |`;
})
.join('\n');
// Shields.io badges. Only non-user-controlled, charset-constrained values
// (version, commit sha, counts, dates) go into badge URLs — never board
// names or the PR title — so the rendered comment cannot be spoofed.
const shieldText = (s) =>
encodeURIComponent(String(s).replace(/-/g, '--').replace(/_/g, '__').replace(/ /g, '_'));
const shield = (label, message, color) =>
`https://img.shields.io/badge/${shieldText(label)}-${shieldText(message)}-${color}`;
const buttonUrl =
`https://img.shields.io/badge/${shieldText('Flash this PR in the Web Flasher')}-2C2D3C?style=for-the-badge`;
const badges = [
`![firmware](${shield('firmware', version, '67EA94')})`,
`![commit](${shield('commit', run.head_sha.slice(0, 7), '2C2D3C')})`,
`![boards](${shield('boards', boards.length, '5C6BC0')})`,
];
if (expiresAt) badges.push(`![expires](${shield('expires', expiresAt, '9A4E00')})`);
// Only render the board table when there are supported boards to list
const boardTable = boards.length > 0 ? [
`<details><summary>Supported boards built by this PR (${boards.length})</summary>`,
'',
'| | Device | Board | Platform |',
'| --- | --- | --- | --- |',
boardLines,
'',
'</details>',
'',
] : [];
const body = [
marker,
'## ⚡ Try this PR in the Web Flasher',
'',
`[![Flash this PR in the Web Flasher](${buttonUrl})](${flasherUrl})`,
'',
badges.join(' '),
'',
'> [!WARNING]',
'> This is an automated, unreviewed CI test build. Back up your device configuration',
'> before flashing, and only flash devices you are able to recover.',
'',
...boardTable,
`*Build artifacts expire${expiresAt ? ` on ${expiresAt}` : ' after 30 days'}. Updated for \`${run.head_sha.slice(0, 7)}\`.*`,
].join('\n');
// Sticky comment: update in place when the marker is found
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: prNumber, per_page: 100,
});
const existing = comments.find((c) => c.body?.includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: prNumber, body });
}
@@ -1,60 +0,0 @@
name: Post Web Flasher Build Placeholder
# Drops an immediate "build in progress" comment when a PR opens, so the web
# flasher entry shows up right away. The real CI-driven workflow
# (flasher-link-comment.yml) later replaces it in place via the shared marker.
#
# SECURITY: this uses pull_request_target (write token, runs for fork PRs) but is
# safe because it never checks out or runs PR code and posts a fully static body
# — no PR title, branch name, or other untrusted input is used anywhere.
on:
pull_request_target:
types: [opened, reopened]
permissions:
pull-requests: write
jobs:
post-placeholder:
if: github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
steps:
- name: Post web flasher build-in-progress placeholder
uses: actions/github-script@v9
with:
script: |
const marker = '<!-- web-flasher-link -->';
const { owner, repo } = context.repo;
const pr = context.payload.pull_request;
// Only org members get the flasher comment (matches the real workflow)
const allowedAssociations = ['OWNER', 'MEMBER'];
if (!allowedAssociations.includes(pr.author_association)) {
core.info(`Author association ${pr.author_association} is not an org member; skipping.`);
return;
}
// Only seed a placeholder when no flasher comment exists yet — never
// overwrite a real (or existing placeholder) comment.
const comments = await github.paginate(github.rest.issues.listComments, {
owner, repo, issue_number: pr.number, per_page: 100,
});
if (comments.some((c) => c.body?.includes(marker))) {
core.info('Flasher comment already exists; nothing to do.');
return;
}
const body = [
marker,
'## ⚡ Try this PR in the Web Flasher',
'',
'> [!NOTE]',
'> Building this pull request… the flash button, badges and supported-board',
'> list will appear here automatically once CI finishes.',
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: pr.number, body,
});
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+49 -8
View File
@@ -40,7 +40,7 @@ jobs:
- check
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- uses: actions/setup-python@v6
with:
python-version: 3.x
@@ -64,7 +64,7 @@ jobs:
version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Get release version string
run: |
echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
@@ -86,7 +86,7 @@ jobs:
runs-on: ${{ github.repository_owner == 'meshtastic' && 'arctastic' || 'ubuntu-latest' }}
if: ${{ github.event_name != 'workflow_dispatch' && github.repository == 'meshtastic/firmware' }}
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
- name: Check ${{ matrix.check.board }}
@@ -189,7 +189,7 @@ jobs:
needs: [version, build]
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
- uses: actions/download-artifact@v8
with:
@@ -245,6 +245,48 @@ jobs:
path: ./*.elf
retention-days: 30
shame:
if: github.repository == 'meshtastic/firmware'
continue-on-error: true
runs-on: ubuntu-latest
needs: [build]
steps:
- uses: actions/checkout@v6
if: github.event_name == 'pull_request'
with:
filter: blob:none # means we download all the git history but none of the commit (except ones with checkout like the head)
fetch-depth: 0
- name: Download the current manifests
uses: actions/download-artifact@v8
with:
path: ./manifests-new/
pattern: manifest-*
merge-multiple: true
- name: Upload combined manifests for later commit and global stats crunching.
uses: actions/upload-artifact@v7
id: upload-manifest
with:
name: manifests-${{ github.sha }}
overwrite: true
path: manifests-new/*.mt.json
- name: Find the merge base
if: github.event_name == 'pull_request'
run: echo "MERGE_BASE=$(git merge-base "origin/$base" "$head")" >> $GITHUB_ENV
env:
base: ${{ github.base_ref }}
head: ${{ github.sha }}
# Currently broken (for-loop through EVERY artifact -- rate limiting)
# - name: Download the old manifests
# if: github.event_name == 'pull_request'
# run: gh run download -R "$repo" --name "manifests-$merge_base" --dir manifest-old/
# env:
# GH_TOKEN: ${{ github.token }}
# merge_base: ${{ env.MERGE_BASE }}
# repo: ${{ github.repository }}
# - name: Do scan and post comment
# if: github.event_name == 'pull_request'
# run: python3 bin/shame.py ${{ github.event.pull_request.number }} manifests-old/ manifests-new/
release-artifacts:
permissions: # Needed for 'gh release upload'.
contents: write
@@ -261,7 +303,7 @@ jobs:
# - MacOS
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -291,7 +333,6 @@ 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
@@ -362,7 +403,7 @@ jobs:
needs: [release-artifacts, version]
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Setup Python
uses: actions/setup-python@v6
@@ -417,7 +458,7 @@ jobs:
esp32,esp32s3,esp32c3,esp32c6,nrf52840,rp2040,rp2350,stm32
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0
+5 -5
View File
@@ -14,16 +14,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1.3.1
uses: trunk-io/trunk-action@v1
with:
trunk-token: ${{ secrets.TRUNK_TOKEN }}
trunk_upgrade:
if: github.repository == 'meshtastic/firmware'
# See: https://github.com/trunk-io/trunk-action/blob/main/readme.md#automatic-upgrades
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#automatic-upgrades
name: Trunk Upgrade (PR)
runs-on: ubuntu-24.04
permissions:
@@ -31,9 +31,9 @@ jobs:
pull-requests: write # For trunk to create PRs
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Upgrade
uses: trunk-io/trunk-action/upgrade@v1.3.1
uses: trunk-io/trunk-action/upgrade@v1
with:
base: master
+1 -1
View File
@@ -33,7 +33,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
+1 -1
View File
@@ -23,7 +23,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -34,7 +34,7 @@ jobs:
needs: build-debian-src
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: recursive
path: meshtasticd
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
checks: write
pull-requests: write
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
+1 -1
View File
@@ -91,7 +91,7 @@ jobs:
shell: bash
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
# Always use master branch for version bumps
ref: master
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v7
uses: actions/checkout@v6
# step 2
- name: full scan
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
steps:
# step 1
- name: clone application source code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
fetch-depth: 0
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
steps:
- name: Stale PR+Issues
uses: actions/stale@v10.3.0
uses: actions/stale@v10.2.0
with:
days-before-stale: 45
stale-issue-message: This issue has not had any comment or update in the last month. If it is still relevant, please post update comments. If no comments are made, this issue will be closed automagically in 7 days.
+10 -4
View File
@@ -14,7 +14,7 @@ jobs:
name: Native Simulator Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
@@ -68,7 +68,7 @@ jobs:
name: Native PlatformIO Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
with:
submodules: recursive
@@ -86,7 +86,13 @@ jobs:
run: sed -i 's/-DBUILD_EPOCH=$UNIX_TIME/#-DBUILD_EPOCH=$UNIX_TIME/' platformio.ini
- name: PlatformIO Tests
run: platformio test -e coverage -v --junit-output-path testreport.xml
run: |
set -o pipefail
# Filter out SKIPPED summary rows for hardware variants that can't run on the
# native host. They flood the log and make it harder to spot real failures.
# The JUnit XML is written directly to testreport.xml before the pipe, so
# the test artifact is unaffected.
platformio test -e coverage -v --junit-output-path testreport.xml 2>&1 | grep -v "[[:space:]]SKIPPED$"
- name: Save test results
if: always() # run this step even if previous step failed
@@ -123,7 +129,7 @@ jobs:
- platformio-tests
if: always()
steps:
- uses: actions/checkout@v7
- uses: actions/checkout@v6
- name: Get release version string
run: echo "long=$(./bin/buildinfo.py long)" >> $GITHUB_OUTPUT
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
runs-on: test-runner
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
# - uses: actions/setup-python@v6
# with:
+3 -3
View File
@@ -1,5 +1,5 @@
name: Annotate PR with trunk issues
# See: https://github.com/trunk-io/trunk-action/blob/main/readme.md#getting-inline-annotations-for-fork-prs
# See: https://github.com/trunk-io/trunk-action/blob/v1/readme.md#getting-inline-annotations-for-fork-prs
on:
workflow_run:
@@ -18,9 +18,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1.3.1
uses: trunk-io/trunk-action@v1
with:
post-annotations: true
+2 -2
View File
@@ -16,9 +16,9 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v7
uses: actions/checkout@v6
- name: Trunk Check
uses: trunk-io/trunk-action@v1.3.1
uses: trunk-io/trunk-action@v1
with:
save-annotations: true
+5 -10
View File
@@ -11,23 +11,18 @@ jobs:
pull-requests: write
steps:
- name: Checkout code
uses: actions/checkout@v7
uses: actions/checkout@v6
with:
submodules: true
- name: Update submodule
if: ${{ github.ref_name == 'master' || github.ref_name == 'develop' }}
working-directory: protobufs
env:
# Use the branch that triggered the workflow as the protobuf branch.
GIT_BRANCH: ${{ github.ref_name }}
if: ${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/develop' }}
run: |
git fetch --prune origin $GIT_BRANCH
git checkout origin/$GIT_BRANCH
git submodule update --remote protobufs
- name: Download nanopb
run: |
wget https://github.com/nanopb/nanopb/releases/download/nanopb-0.4.9.1/nanopb-0.4.9.1-linux-x86.tar.gz
wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.9.1-linux-x86.tar.gz
tar xvzf nanopb-0.4.9.1-linux-x86.tar.gz
mv nanopb-0.4.9.1-linux-x86 nanopb-0.4.9
@@ -38,7 +33,7 @@ jobs:
- name: Create pull request
uses: peter-evans/create-pull-request@v8
with:
branch: create-pull-request/update-protobufs-${{ github.ref_name }}
branch: create-pull-request/update-protobufs
labels: submodules
title: Update protobufs and classes
commit-message: Update protobufs
+13
View File
@@ -56,3 +56,16 @@ CMakeLists.txt
.python3
.claude/scheduled_tasks.lock
userPrefs.jsonc.mcp-session-bak
# Fake-NodeDB fixture pipeline (bin/regen-fake-nodedbs.sh)
# JSONL seeds are committed (test/fixtures/nodedb/seed_v25_*.jsonl);
# compiled .proto outputs are ephemeral build artifacts.
build/fixtures/
bin/_generated/
# Build artifacts: anything compiling .o/.a outside .pio/ is accidental.
# Explicit exceptions for vendored binaries (Morse Micro mm-iot-esp32 SDK).
*.o
*.a
!lib/MorseWlan/lib/**/*.a
!lib/MorseWlan/src/*.mbin.o
+16 -9
View File
@@ -4,21 +4,21 @@ cli:
plugins:
sources:
- id: trunk
ref: v1.10.2
ref: v1.9.0
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- checkov@3.3.2
- renovate@43.242.0
- prettier@3.8.4
- trufflehog@3.95.6
- checkov@3.2.528
- renovate@43.150.0
- prettier@3.8.3
- trufflehog@3.95.3
- yamllint@1.38.0
- bandit@1.9.4
- trivy@0.71.2
- trivy@0.70.0
- taplo@0.10.0
- ruff@0.15.19
- ruff@0.15.12
- isort@8.0.1
- markdownlint@0.49.0
- markdownlint@0.48.0
- oxipng@10.1.1
- svgo@4.0.1
- actionlint@1.7.12
@@ -26,7 +26,7 @@ lint:
- hadolint@2.14.0
- shfmt@3.6.0
- shellcheck@0.11.0
- black@26.5.1
- black@26.3.1
- git-diff-check
- gitleaks@8.30.1
- clang-format@16.0.3
@@ -34,6 +34,13 @@ lint:
- linters: [ALL]
paths:
- bin/**
# Fake-NodeDB fixture JSONL files contain deterministic synthetic
# public_key_hex (64-char hex) values that gitleaks misidentifies as
# generic-api-key. These are not secrets — they're test fixtures
# produced by bin/gen-fake-nodedb-seed.py with a fixed RNG seed.
- linters: [gitleaks]
paths:
- test/fixtures/nodedb/seed_v25_*.jsonl
runtimes:
enabled:
- python@3.14.4
+2 -2
View File
@@ -4,7 +4,7 @@
# trunk-ignore-all(hadolint/DL3013): Do not pin pip package versions
# Ensure the Alpine version is updated in both stages of the container!
FROM alpine:3.24 AS builder
FROM alpine:3.23 AS builder
ARG PIO_ENV=native
# Enable Alpine community repository (for 'py3-grpcio-tools')
@@ -35,7 +35,7 @@ RUN bash ./bin/build-native.sh "$PIO_ENV" && \
# ##### PRODUCTION BUILD #############
FROM alpine:3.24
FROM alpine:3.23
LABEL org.opencontainers.image.title="Meshtastic" \
org.opencontainers.image.description="Alpine Meshtastic daemon" \
org.opencontainers.image.url="https://meshtastic.org" \
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Post-process protoc-generated Python files to live under a local namespace.
Called by bin/regen-py-protos.sh. Walks the generated *_pb2.py files in the
target directory and rewrites every `meshtastic` reference (imports, dotted
attribute access) to use the new namespace (e.g., `meshtastic_v25`).
Why: the .proto files declare `package meshtastic;`, so protoc emits
`from meshtastic import mesh_pb2 as ...` lines. That would shadow the PyPI
`meshtastic` package which other parts of the mcp-server depend on. Renaming
to a local namespace keeps both available.
Usage:
_rewrite_proto_namespace.py <generated_dir> <new_namespace>
"""
from __future__ import annotations
import pathlib
import re
import sys
def rewrite(dir_path: pathlib.Path, new_ns: str) -> int:
# Standard protoc import forms:
# from meshtastic.X_pb2 import ... (rare, for direct symbol pulls)
# from meshtastic import X_pb2 as ... (common, the cross-file ref)
# import meshtastic.X_pb2 (also possible)
pattern_dotted_from = re.compile(r"^from meshtastic\.", re.MULTILINE)
pattern_bare_from = re.compile(r"^from meshtastic import ", re.MULTILINE)
pattern_dotted_import = re.compile(r"^import meshtastic\.", re.MULTILINE)
count = 0
for p in dir_path.glob("*.py"):
text = p.read_text(encoding="utf-8")
new = pattern_dotted_from.sub(f"from {new_ns}.", text)
new = pattern_bare_from.sub(f"from {new_ns} import ", new)
new = pattern_dotted_import.sub(f"import {new_ns}.", new)
# NOTE: we deliberately leave `meshtastic/X.proto` source-filename
# references inside descriptor strings alone. The descriptor pool is
# keyed by source filename (independent of Python package layout), so
# those don't collide with the PyPI package's descriptors.
if new != text:
p.write_text(new, encoding="utf-8")
count += 1
return count
def main(argv: list[str]) -> int:
if len(argv) != 2:
print("usage: _rewrite_proto_namespace.py <generated_dir> <new_namespace>", file=sys.stderr)
return 2
dir_path = pathlib.Path(argv[0])
new_ns = argv[1]
if not dir_path.is_dir():
print(f"directory not found: {dir_path}", file=sys.stderr)
return 2
n = rewrite(dir_path, new_ns)
print(f"rewrote {n} file(s) in {dir_path} → namespace {new_ns}", file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
@@ -0,0 +1,30 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins: [DIO5, DIO6, DIO7]
MODE_STBY: [LOW, LOW, LOW]
MODE_RX: [LOW, HIGH, LOW]
MODE_TX: [HIGH, HIGH, LOW]
MODE_TX_HP: [HIGH, LOW, LOW]
MODE_TX_HF: [LOW, LOW, LOW]
MODE_GNSS: [LOW, LOW, HIGH]
MODE_WIFI: [LOW, LOW, LOW]
General:
MACAddressSource: eth0
@@ -0,0 +1,46 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins:
- DIO5
- DIO6
MODE_STBY:
- LOW
- LOW
MODE_RX:
- HIGH
- LOW
MODE_TX:
- HIGH
- HIGH
MODE_TX_HP:
- LOW
- HIGH
MODE_TX_HF:
- LOW
- LOW
MODE_GNSS:
- LOW
- LOW
MODE_WIFI:
- LOW
- LOW
General:
MACAddressSource: eth0
@@ -0,0 +1,30 @@
---
Lora:
## Ebyte E80-900M22S
## This is a bit experimental
##
##
Module: lr1121
gpiochip: 1 # subtract 32 from the gpio numbers
DIO3_TCXO_VOLTAGE: 1.8
CS: 16 #pin6 / GPIO48 1C0
IRQ: 23 #pin17 / GPIO55 1C7
Busy: 22 #pin16 / GPIO54 1C6
Reset: 25 #pin13 / GPIO57 1D1
spidev: spidev0.0 #pins are (CS=16, CLK=17, MOSI=18, MISO=19)
spiSpeed: 2000000
rfswitch_table:
pins: [DIO5, DIO6, DIO7]
MODE_STBY: [LOW, LOW, LOW]
MODE_RX: [LOW, LOW, LOW]
MODE_TX: [LOW, HIGH, LOW]
MODE_TX_HP: [HIGH, LOW, LOW]
# MODE_TX_HF: []
# MODE_GNSS: []
MODE_WIFI: [LOW, LOW, LOW]
General:
MACAddressSource: eth0
-19
View File
@@ -1,19 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/NebraHat
# Use for 1 watt hat
Meta:
name: NebraHat 1W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Nebra SX1262 Pi Hat - 1W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
# CS: 8 # Newer version of MeshtasticD do not need this? If issues uncomment this line
IRQ: 22
Busy: 4
Reset: 18
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
-20
View File
@@ -1,20 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/NebraHat
# Use for 2 watt hat
Meta:
name: NebraHat 2W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Nebra SX1262 Pi Hat - 2W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
# CS: 8 # Newer version of MeshtasticD do not need this? If issues uncomment this line
IRQ: 22
Busy: 4
Reset: 18
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
+1 -1
View File
@@ -18,4 +18,4 @@ Lora:
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: 8
# CS: 8
+2 -6
View File
@@ -5,9 +5,7 @@ Meta:
- raspberry-pi
Lora:
### RAK13300 in Slot 2
Module: sx1262
### RAK13300 in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
@@ -15,7 +13,5 @@ Lora:
Enable_Pins:
- 26
- 23
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: 7
# CS: 7
+2 -6
View File
@@ -5,18 +5,14 @@ Meta:
- raspberry-pi
Lora:
### RAK13302 in Slot 2
Module: sx1262
### RAK13302 in Slot 2 pins
IRQ: 18 #IO6
Reset: 24 # IO4
Busy: 19 # IO5
# Ant_sw: 23 # IO3
# Ant_sw: 23 # IO3
Enable_Pins:
- 26
- 23
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: 7
TX_GAIN_LORA: [9, 9, 10, 11, 9, 8, 9, 10, 10, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12, 10, 9, 8]
-19
View File
@@ -1,19 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/ZebraHAT
# Use for 1 watt hat
Meta:
name: ZebraHat 1W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Zebra SX1262 Pi Hat - 1W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 18
CS: 24
IRQ: 22
Busy: 27
Reset: 17
I2C:
I2CDevice: /dev/i2c-1
-20
View File
@@ -1,20 +0,0 @@
# https://github.com/wehooper4/Meshtastic-Hardware/tree/main/ZebraHAT
# Use for 2 watt hat
Meta:
name: ZebraHat 2W
support: community
compatible:
- raspberry-pi
Lora:
Module: sx1262 # Zebra SX1262 Pi Hat - 2W
DIO2_AS_RF_SWITCH: true
DIO3_TCXO_VOLTAGE: true
SX126X_MAX_POWER: 8
CS: 24
IRQ: 22
Busy: 27
Reset: 17
RXen: 25
I2C:
I2CDevice: /dev/i2c-1
@@ -29,8 +29,6 @@ Lora:
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
@@ -29,8 +29,6 @@ Lora:
- pin: 50 # GPIO1_C2 (physical 16)
gpiochip: 1
line: 18
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_A7 (SPI1_CSN1, physical 26)
# pin: 7
@@ -1,29 +0,0 @@
# Station G3 + BQ35LORA900V1M Primary Slot
# Board Doc: https://wiki.bqvoy.com/en/devkits/station-g3
Meta:
name: B&Q Station G3 + BQ35LORA900V1M Primary Slot
support: official
compatible:
- luckfox-lyra-zero-w # Armbian
Lora:
Module: sx1262
IRQ: # GPIO0_A5 (physical 15)
pin: 5
gpiochip: 0
line: 5
Reset: # GPIO1_D1 (physical 36)
pin: 57
gpiochip: 1
line: 25
Busy: # GPIO0_B4 (physical 18)
pin: 12
gpiochip: 0
line: 12
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
# CS: # GPIO0_B2 (physical 24)
# pin: 10
# gpiochip: 0
# line: 10
@@ -29,8 +29,6 @@ Lora:
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
@@ -29,8 +29,6 @@ Lora:
- pin: 13 # GPIO0_B5
gpiochip: 0
line: 13
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B1
# pin: 9
@@ -31,8 +31,6 @@ Lora:
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
@@ -31,8 +31,6 @@ Lora:
- pin: 103 # GPIO3_A7 (physical 16)
gpiochip: 3
line: 7
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.1
# CS: # GPIO0_B7 (SPI0_CSN1, physical 26)
# pin: 15
@@ -1,17 +0,0 @@
# Station G3 + BQ35LORA900V1M Primary Slot
# Board Doc: https://wiki.bqvoy.com/en/devkits/station-g3
Meta:
name: B&Q Station G3 + BQ35LORA900V1M Primary Slot
support: official
compatible:
- raspberry-pi
Lora:
Module: sx1262
IRQ: 22
Reset: 16
Busy: 24
DIO3_TCXO_VOLTAGE: true
DIO2_AS_RF_SWITCH: true
spidev: spidev0.0
#CS: 8
-2
View File
@@ -1,5 +1,3 @@
# This config works with all revisions of the Meshtoad USB radio.
Meta:
name: meshtoad-e22
support: official
+439
View File
@@ -0,0 +1,439 @@
#!/usr/bin/env python3
"""Deterministic seed-data generator for the fake NodeDB fixture pipeline.
Writes a JSONL file describing N fake-but-realistic Meshtastic peers.
The output is hand-editable and committed; a sibling compile step
(bin/seed-json-to-proto.py) turns it into a binary `meshtastic_NodeDatabase`
v25 protobuf with fresh "now-relative" timestamps.
Determinism contract:
Same --seed -> byte-identical JSONL output, regardless of wall clock.
All timestamps are stored as `*_offset_sec` (seconds before "now"); the
compile step resolves them to absolute epochs at compile time.
Structural fields covered:
* NodeInfoLite header: num, long_name, short_name, hw_model, role,
public_key, snr, channel, hops_away, next_hop, bitfield flags
* PositionLite: lat/long Gaussian around --centroid, altitude, source
* DeviceMetrics: battery/voltage/util/uptime
* EnvironmentMetrics: temp/humidity/pressure/iaq
* StatusMessage: error_code (usually zero)
Active-board allow-list:
hw_model values are restricted to the intersection of
(a) variants with `custom_meshtastic_support_level = 1` in
variants/*/*/platformio.ini, AND
(b) values present in the `HardwareModel` enum in mesh.proto.
See HW_MODEL_WEIGHTS below. Deprecated boards (legacy TLORA / Heltec V1-2 /
classic TBEAM / TBEAM_V0P7 / Nano G1 / etc.) and fuzzer-only sentinels
(PORTDUINO, ANDROID_SIM, DIY_V1, ...) are excluded.
Active-role allow-list:
Excludes ROUTER_CLIENT (deprecated v2.3.15) and REPEATER (deprecated v2.7.11).
"""
from __future__ import annotations
import argparse
import datetime as _dt
import json
import math
import pathlib
import random
import sys
# --------------------------------------------------------------------------
# Active-board allow-list (intersection of tier-1 variants + HardwareModel enum).
# Refresh by running:
# for f in $(find variants -name 'platformio.ini' | xargs grep -lE 'custom_meshtastic_support_level = 1'); do
# grep custom_meshtastic_hw_model_slug $f | awk -F= '{print $2}' | tr -d ' ';
# done | sort -u | comm -12 - <(python3 -c "from meshtastic.protobuf.mesh_pb2 import HardwareModel; print('\\n'.join(HardwareModel.keys()))" | sort)
# --------------------------------------------------------------------------
HW_MODEL_WEIGHTS: dict[str, float] = {
"HELTEC_V3": 14.0,
"T_DECK": 9.0,
"HELTEC_V4": 8.0,
"RAK4631": 8.0,
"HELTEC_MESH_POCKET": 6.0,
"TRACKER_T1000_E": 5.0,
"HELTEC_MESH_NODE_T114": 5.0,
"T_DECK_PRO": 5.0,
"LILYGO_TBEAM_S3_CORE": 4.0,
"HELTEC_WIRELESS_PAPER": 4.0,
"HELTEC_WSL_V3": 3.0,
"T_ECHO": 3.0,
"HELTEC_WIRELESS_TRACKER": 3.0,
"HELTEC_WIRELESS_TRACKER_V2": 2.0,
"HELTEC_VISION_MASTER_E290": 2.0,
"HELTEC_MESH_SOLAR": 2.0,
"SEEED_WIO_TRACKER_L1": 2.0,
"T_LORA_PAGER": 1.5,
"HELTEC_VISION_MASTER_E213": 1.5,
"T_ECHO_PLUS": 1.0,
"MUZI_BASE": 1.0,
"WISMESH_TAP_V2": 1.0,
"THINKNODE_M2": 1.0,
"THINKNODE_M5": 1.0,
"TLORA_T3_S3": 1.0,
# Long tail (uniform low weight across remaining tier-1 boards):
"HELTEC_V4_R8": 0.3,
"HELTEC_VISION_MASTER_T190": 0.3,
"HELTEC_HT62": 0.3,
"HELTEC_MESH_NODE_T096": 0.3,
"M5STACK_C6L": 0.3,
"MINI_EPAPER_S3": 0.3,
"MUZI_R1_NEO": 0.3,
"NOMADSTAR_METEOR_PRO": 0.3,
"RAK3312": 0.3,
"RAK3401": 0.3,
"SEEED_SOLAR_NODE": 0.3,
"SEEED_WIO_TRACKER_L1_EINK": 0.3,
"SENSECAP_INDICATOR": 0.3,
"TBEAM_1_WATT": 0.3,
"THINKNODE_M1": 0.3,
"THINKNODE_M3": 0.3,
"THINKNODE_M6": 0.3,
"T_ECHO_LITE": 0.3,
"WISMESH_TAG": 0.3,
"WISMESH_TAP": 0.3,
"XIAO_NRF52_KIT": 0.3,
"CROWPANEL": 0.3,
}
# Non-deprecated roles only.
ROLE_WEIGHTS: dict[str, float] = {
"CLIENT": 75.0,
"CLIENT_MUTE": 5.0,
"ROUTER": 7.0,
"TRACKER": 3.0,
"SENSOR": 2.0,
"CLIENT_HIDDEN": 2.0,
"ROUTER_LATE": 2.0,
"CLIENT_BASE": 2.0,
"TAK": 1.0,
"TAK_TRACKER": 0.5,
"LOST_AND_FOUND": 0.5,
}
# Name pools — 60 firsts × 60 lasts = 3600 combinations.
FIRSTS = [
"Quick", "Brave", "Silent", "Wild", "Lone", "Bright", "Red", "Blue",
"Green", "Black", "White", "Iron", "Steel", "Copper", "Silver", "Gold",
"Stone", "River", "Forest", "Mountain", "Canyon", "Desert", "Storm", "Sky",
"Solar", "Lunar", "Dawn", "Dusk", "Misty", "Frosty", "Sunny", "Shady",
"Happy", "Sleepy", "Drowsy", "Sneaky", "Sharp", "Smooth", "Rough", "Loud",
"Soft", "Slow", "Fast", "Tall", "Short", "Old", "New", "Tiny",
"Giant", "Hidden", "Lost", "Found", "Wandering", "Roving", "Drifting", "Floating",
"Burning", "Frozen", "Whispering", "Howling",
]
LASTS = [
"Phoenix", "Lion", "Bear", "Wolf", "Hawk", "Eagle", "Fox", "Lynx",
"Cougar", "Coyote", "Raven", "Owl", "Crow", "Falcon", "Heron", "Crane",
"Otter", "Badger", "Bison", "Elk", "Moose", "Stag", "Doe", "Hare",
"Marmot", "Mole", "Beaver", "Squirrel", "Mustang", "Bronco", "Pony", "Colt",
"Cobra", "Viper", "Mamba", "Adder", "Gecko", "Iguana", "Tortoise", "Turtle",
"Salmon", "Trout", "Bass", "Pike", "Shark", "Whale", "Dolphin", "Seal",
"Cactus", "Yucca", "Sage", "Juniper", "Pine", "Cedar", "Aspen", "Oak",
"Bluff", "Mesa", "Arroyo", "Ridge",
]
# Brief callsign pool for licensed-looking suffixes.
CALLSIGN_PREFIXES = ["KX", "WD", "N5", "KE", "AB", "W5", "K1", "KQ", "AE", "NM"]
# Only emojis that fit in 4 UTF-8 bytes (no variation selectors). short_name's
# nanopb max_size:5 (incl. NUL) limits content to 4 bytes. ❄️ / ☀️ would be
# 6 bytes due to U+FE0F variation selector — explicitly excluded.
EMOJI_SHORTNAMES = ["🦊", "🐺", "🦅", "🐢", "🌵", "🔥", "🌙",
"🌊", "🗻", "🌲", "🦌", "🐝", "🦂", "🦉",
"🦇", "🦋"]
# --------------------------------------------------------------------------
# Helpers
# --------------------------------------------------------------------------
NUM_RESERVED = 4 # firmware reserves 0..3 (per NodeDB constants)
NUM_MAX_EXCLUSIVE = 0x80000000 # restrict to positive int32 range for readability
def _weighted_choice(rng: random.Random, weights: dict[str, float]) -> str:
"""Deterministic weighted pick. Uses sorted keys so dict order is fixed."""
keys = sorted(weights.keys())
totals = [weights[k] for k in keys]
return rng.choices(keys, weights=totals, k=1)[0]
def _gen_long_name(rng: random.Random, is_licensed: bool) -> str:
base = f"{rng.choice(FIRSTS)} {rng.choice(LASTS)}"
if is_licensed:
prefix = rng.choice(CALLSIGN_PREFIXES)
# Two trailing alpha chars after the digit; keep within 25 - len(base) - 1
suffix = f" {prefix}{rng.randint(0,9)}{rng.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}{rng.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ')}"
# nanopb max_size:25 means C string fits 24 bytes + NUL.
if len(base) + len(suffix) <= 24:
base = base + suffix
# Hard cap to 24 chars (nanopb max_size:25 minus NUL).
return base[:24]
def _gen_short_name(rng: random.Random, long_name: str) -> str:
# 10% emoji-only short_name
if rng.random() < 0.10:
return rng.choice(EMOJI_SHORTNAMES)
first_char = long_name[0].upper() if long_name else "X"
alphanums = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
return first_char + "".join(rng.choices(alphanums, k=3))
def _gen_hops_away(rng: random.Random) -> int:
# Geometric-ish: 0→55%, 1→25%, 2→12%, 3→5%, 4→2%, 5+→1%
r = rng.random()
if r < 0.55:
return 0
if r < 0.80:
return 1
if r < 0.92:
return 2
if r < 0.97:
return 3
if r < 0.99:
return 4
return rng.randint(5, 7)
def _gen_position(
rng: random.Random,
centroid_lat: float,
centroid_lon: float,
spread_km: float,
last_heard_offset_sec: int,
) -> dict:
# 1 deg ≈ 111 km at the equator; we use this as a flat approximation.
lat = centroid_lat + rng.gauss(0.0, spread_km / 111.0)
lon = centroid_lon + rng.gauss(0.0, spread_km / 111.0)
altitude = max(0, round(rng.gauss(1376.0, 250.0))) # T or C valley floor + relief
# Position was reported up to 300s before last_heard.
time_offset_sec = last_heard_offset_sec + rng.randint(0, 300)
return {
"latitude": round(lat, 6),
"longitude": round(lon, 6),
"altitude": altitude,
"time_offset_sec": time_offset_sec,
"location_source": "LOC_INTERNAL",
}
def _gen_telemetry(rng: random.Random) -> dict:
# 5% plugged-in (battery_level == 101); rest uniform [10..100].
if rng.random() < 0.05:
battery_level = 101
voltage = 4.20
else:
battery_level = rng.randint(10, 100)
voltage = round(3.3 + (battery_level / 100.0) * 0.9, 3)
# Beta distributions for low/right-skewed metrics; randomly draw via gammavariate.
def _beta(a: float, b: float) -> float:
x = rng.gammavariate(a, 1.0)
y = rng.gammavariate(b, 1.0)
return x / (x + y)
channel_utilization = round(_beta(2.0, 15.0) * 100.0, 2)
air_util_tx = round(_beta(1.5, 20.0) * 10.0, 3)
uptime_seconds = int(rng.expovariate(1.0 / 86400.0))
return {
"battery_level": battery_level,
"voltage": voltage,
"channel_utilization": channel_utilization,
"air_util_tx": air_util_tx,
"uptime_seconds": uptime_seconds,
}
def _gen_environment(rng: random.Random) -> dict:
return {
"temperature": round(rng.gauss(22.0, 8.0), 2),
"relative_humidity": round(min(100.0, max(0.0, rng.gauss(55.0, 20.0))), 2),
"barometric_pressure": round(rng.gauss(1013.0, 8.0), 2),
"iaq": int(min(500, max(0, round(rng.gauss(50.0, 30.0))))),
}
def _gen_status(rng: random.Random) -> dict:
# `StatusMessage` (mesh.proto:1445) has a single free-form `string status`.
# Most peers report a healthy short status; occasional alert string.
healthy = ["OK", "online", "active", "running", "ready", "nominal"]
alert = ["low-batt", "no-gps", "weak-signal", "rebooted", "offline-soon"]
if rng.random() < 0.92:
return {"status": rng.choice(healthy)}
return {"status": rng.choice(alert)}
def _gen_node(
rng: random.Random,
num: int,
centroid_lat: float,
centroid_lon: float,
spread_km: float,
coverage: dict[str, float],
last_heard_mean_sec: int,
last_heard_max_sec: int,
) -> dict:
is_licensed = rng.random() < 0.05
long_name = _gen_long_name(rng, is_licensed)
short_name = _gen_short_name(rng, long_name)
hw_model = _weighted_choice(rng, HW_MODEL_WEIGHTS)
role = _weighted_choice(rng, ROLE_WEIGHTS)
has_public_key = rng.random() < 0.92
public_key_hex = (
"".join(f"{rng.randint(0,255):02x}" for _ in range(32)) if has_public_key else ""
)
snr = round(max(-20.0, min(12.0, rng.gauss(6.0, 4.0))), 2)
channel = 0 if rng.random() < 0.90 else rng.randint(1, 7)
hops_away = _gen_hops_away(rng)
next_hop = rng.randint(0, 255) if hops_away > 0 else 0
last_heard_offset_sec = int(min(rng.expovariate(1.0 / last_heard_mean_sec), last_heard_max_sec))
bitfield = {
"has_user": True,
"is_favorite": rng.random() < 0.08,
"is_muted": rng.random() < 0.03,
"via_mqtt": rng.random() < 0.12,
"is_ignored": rng.random() < 0.01,
"is_licensed": is_licensed,
"has_is_unmessagable": True,
"is_unmessagable": rng.random() < 0.02,
"is_key_manually_verified": rng.random() < 0.04,
}
node: dict = {
"num": f"0x{num:08x}",
"long_name": long_name,
"short_name": short_name,
"hw_model": hw_model,
"role": role,
"public_key_hex": public_key_hex,
"snr": snr,
"channel": channel,
"hops_away": hops_away,
"next_hop": next_hop,
"last_heard_offset_sec": last_heard_offset_sec,
"bitfield": bitfield,
"position": (
_gen_position(rng, centroid_lat, centroid_lon, spread_km, last_heard_offset_sec)
if rng.random() < coverage["position"]
else None
),
"telemetry": _gen_telemetry(rng) if rng.random() < coverage["telemetry"] else None,
"environment": _gen_environment(rng) if rng.random() < coverage["environment"] else None,
"status": _gen_status(rng) if rng.random() < coverage["status"] else None,
}
return node
def _parse_my_node_num(s: str | None) -> int | None:
if s is None:
return None
s = s.strip()
if s.startswith("0x") or s.startswith("0X"):
return int(s, 16)
return int(s)
def main(argv: list[str]) -> int:
p = argparse.ArgumentParser(
description="Deterministic JSONL seed for the fake NodeDB fixture.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p.add_argument("--count", type=int, required=True, help="Number of fake nodes to emit.")
p.add_argument("--seed", type=int, required=True, help="Deterministic seed.")
p.add_argument("--out", required=True, help="Output JSONL path.")
p.add_argument(
"--centroid",
default="33.1284,-107.2528",
help="LAT,LON centroid (default: Truth or Consequences, NM).",
)
p.add_argument("--spread-km", type=float, default=60.0, help="Gaussian std-dev in km.")
p.add_argument("--position-coverage", type=float, default=0.85)
p.add_argument("--telemetry-coverage", type=float, default=0.70)
p.add_argument("--environment-coverage", type=float, default=0.25)
p.add_argument("--status-coverage", type=float, default=0.40)
p.add_argument("--my-node-num", default=None, help="Exclude this NodeNum from generated set (hex or dec).")
p.add_argument("--last-heard-mean-sec", type=int, default=3600)
p.add_argument("--last-heard-max-sec", type=int, default=7 * 86400)
args = p.parse_args(argv)
if args.count <= 0:
print("--count must be positive", file=sys.stderr)
return 2
try:
centroid_lat, centroid_lon = (float(s) for s in args.centroid.split(","))
except ValueError:
print(f"--centroid must be LAT,LON; got {args.centroid!r}", file=sys.stderr)
return 2
my_node_num = _parse_my_node_num(args.my_node_num)
rng = random.Random(args.seed)
# 1) Generate a unique deterministic set of NodeNums.
nums: set[int] = set()
while len(nums) < args.count:
n = rng.randrange(NUM_RESERVED, NUM_MAX_EXCLUSIVE)
if my_node_num is not None and n == my_node_num:
continue
nums.add(n)
ordered_nums = sorted(nums) # sort to fix output order independent of set hash
# 2) Per-node generation (in num order, single RNG continues).
coverage = {
"position": args.position_coverage,
"telemetry": args.telemetry_coverage,
"environment": args.environment_coverage,
"status": args.status_coverage,
}
nodes = [
_gen_node(
rng,
n,
centroid_lat,
centroid_lon,
args.spread_km,
coverage,
args.last_heard_mean_sec,
args.last_heard_max_sec,
)
for n in ordered_nums
]
# 3) Write JSONL.
out_path = pathlib.Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
# `generated_at_iso` is informational; it does NOT affect determinism because
# we derive it from the seed, not from wall clock. (Same seed -> same string.)
generated_at = _dt.datetime.fromtimestamp(args.seed, tz=_dt.timezone.utc).isoformat().replace("+00:00", "Z")
meta = {
"_meta": {
"version": 25,
"seed": args.seed,
"count": args.count,
"centroid": [centroid_lat, centroid_lon],
"spread_km": args.spread_km,
"generated_at_iso": generated_at,
"my_node_num_excluded": (None if my_node_num is None else f"0x{my_node_num:08x}"),
"coverage": coverage,
"last_heard_mean_sec": args.last_heard_mean_sec,
"last_heard_max_sec": args.last_heard_max_sec,
}
}
with out_path.open("w", encoding="utf-8") as f:
# `ensure_ascii=False` so emoji short_names survive. `sort_keys=True` for
# determinism (insertion order varies by Python version otherwise).
f.write(json.dumps(meta, ensure_ascii=False, sort_keys=True) + "\n")
for node in nodes:
f.write(json.dumps(node, ensure_ascii=False, sort_keys=True) + "\n")
print(f"wrote {args.count} nodes to {out_path} ({out_path.stat().st_size} bytes)", file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
@@ -87,15 +87,6 @@
</screenshots>
<releases>
<release version="2.7.27" date="2026-06-24">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.27</url>
</release>
<release version="2.7.26" date="2026-06-10">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.26</url>
</release>
<release version="2.7.25" date="2026-05-23">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.25</url>
</release>
<release version="2.7.24" date="2026-05-08">
<url type="details">https://github.com/meshtastic/firmware/releases?q=tag%3Av2.7.24</url>
</release>
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
# Regenerate the fake-NodeDB fixtures: produces 250 / 500 / 1000 / 2000-node
# JSONL seed files + their compiled v25 protobufs.
#
# Layout:
# test/fixtures/nodedb/seed_v25_<N>.jsonl — COMMITTED, hand-editable.
# build/fixtures/nodedb/nodes_v25_<N>.proto — .gitignored, build artifact.
# Drop into /prefs/nodes.proto.
#
# Daily use: ./bin/regen-fake-nodedbs.sh
# - Recompiles protos from committed seeds (fresh wall-clock timestamps).
# Intentional seed bump: REGEN_SEEDS=yes ./bin/regen-fake-nodedbs.sh
# - Overwrites the committed JSONL files with freshly-seeded data.
set -euo pipefail
cd "$(dirname "$0")/.."
# 1) Make sure the Python protobuf bindings exist (in-tree generation; .gitignored).
if [[ ! -d bin/_generated/meshtastic ]]; then
echo "regenerating Python protobuf bindings (one-time)..."
./bin/regen-py-protos.sh
fi
# 2) Pick a Python interpreter that has the meshtastic deps installed.
# Prefer the mcp-server venv (most likely to be set up by the operator).
PY="python3"
for cand in mcp-server/.venv/bin/python3 .venv/bin/python3; do
if [[ -x "$cand" ]]; then
PY="$cand"
break
fi
done
# 3) Pinned seeds per size — bump only when you intentionally want different
# structural data committed. Parallel arrays so the script works on
# macOS bash 3.2 (no `declare -A`).
SIZES=(250 500 1000 2000)
SEEDS=(20260511 20260512 20260513 20260514)
REGEN_SEEDS="${REGEN_SEEDS:-no}"
mkdir -p build/fixtures/nodedb test/fixtures/nodedb
for i in 0 1 2 3; do
n="${SIZES[$i]}"
seed="${SEEDS[$i]}"
jsonl=$(printf "test/fixtures/nodedb/seed_v25_%04d.jsonl" "$n")
proto=$(printf "build/fixtures/nodedb/nodes_v25_%04d.proto" "$n")
if [[ "$REGEN_SEEDS" == "yes" || ! -f "$jsonl" ]]; then
$PY bin/gen-fake-nodedb-seed.py \
--count "$n" \
--seed "$seed" \
--out "$jsonl" \
--centroid 33.1284,-107.2528 \
--spread-km 60 \
--position-coverage 0.85 \
--telemetry-coverage 0.70 \
--environment-coverage 0.25 \
--status-coverage 0.40
echo " seed: $jsonl ($(wc -c < "$jsonl") bytes)"
fi
$PY bin/seed-json-to-proto.py --in "$jsonl" --out "$proto"
echo " proto: $proto ($(wc -c < "$proto") bytes)"
done
echo ""
echo "Done. To load on Portduino native:"
echo " cp build/fixtures/nodedb/nodes_v25_1000.proto ~/.portduino/default/prefs/nodes.proto"
echo ""
echo "To push to a hardware device:"
echo " Use the mcp-server tool: push_fake_nodedb(size=1000, target=\"hardware\", port=\"/dev/cu.usbmodemXXXX\", confirm=True)"
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Regenerate Python protobuf bindings from the in-tree `protobufs/` submodule
# into `bin/_generated/`. Called by bin/regen-fake-nodedbs.sh; also useful as
# a standalone refresh after any change to a .proto file.
#
# Output is .gitignored — bindings are a build artifact.
#
# Namespace rewrite:
# The .proto files declare `package meshtastic;`, which makes protoc emit
# imports like `from meshtastic import mesh_pb2`. That conflicts with the
# PyPI `meshtastic` package (which the mcp-server relies on for its
# SerialInterface/BLEInterface transport). We post-process the generated
# files to live under `meshtastic_v25` instead — both the directory layout
# and all internal imports — so they coexist cleanly with the PyPI package.
set -euo pipefail
cd "$(dirname "$0")/.."
if ! command -v protoc >/dev/null 2>&1; then
echo "ERROR: protoc not found in PATH." >&2
echo " macOS: brew install protobuf" >&2
echo " Ubuntu/Debian: apt install protobuf-compiler" >&2
exit 1
fi
OUT=bin/_generated
LOCAL_NS=meshtastic_v25
rm -rf "$OUT"
mkdir -p "$OUT"
# 1) Generate from the in-tree protos. nanopb.proto first so its descriptor
# is available for the [(nanopb).*] options on other messages.
protoc \
--proto_path=protobufs \
--python_out="$OUT" \
protobufs/nanopb.proto \
protobufs/meshtastic/*.proto
# 2) Move the generated `meshtastic/` directory to `meshtastic_v25/`.
mv "$OUT/meshtastic" "$OUT/$LOCAL_NS"
# 3) Rewrite internal imports: any reference to `meshtastic.X_pb2` or
# `from meshtastic import X_pb2` becomes `meshtastic_v25.*`.
python3 bin/_rewrite_proto_namespace.py "$OUT/$LOCAL_NS" "$LOCAL_NS"
# 4) Make the package importable.
touch "$OUT/__init__.py"
touch "$OUT/$LOCAL_NS/__init__.py"
echo "regenerated Python protobuf bindings -> $OUT/$LOCAL_NS/ (namespace: $LOCAL_NS)" >&2
+342
View File
@@ -0,0 +1,342 @@
#!/usr/bin/env python3
"""Compile a committed seed JSONL into a binary meshtastic_NodeDatabase v25 proto.
The input is produced by `bin/gen-fake-nodedb-seed.py`. Timestamps in the JSONL
are stored as `*_offset_sec` (seconds before "now"); this script resolves them
to absolute epochs using `--now-epoch` (default: current wall clock).
Output is a raw `pb_encode`-compatible binary that can be dropped at
`/prefs/nodes.proto` on the device (Portduino prefs dir or hardware via
XModem) and loaded by `NodeDB::loadFromDisk` at boot.
Wire format reference:
protobufs/meshtastic/deviceonly.proto (NodeDatabase, NodeInfoLite, sat entries)
src/mesh/NodeDB.h:467-484 (bitfield bit positions)
src/mesh/NodeDB.cpp:1523-1524 (pb_decode entry point)
"""
from __future__ import annotations
import argparse
import json
import pathlib
import sys
import time
from typing import Any
# Prefer the in-tree generated Python protobuf bindings (bin/_generated/meshtastic_v25/)
# because the firmware branch's protos (v25 NodeDatabase satellite arrays, slim
# NodeInfoLite) are typically newer than what the PyPI `meshtastic` package
# ships. Run `bin/regen-py-protos.sh` to (re)generate.
#
# Namespace note: the local bindings live under `meshtastic_v25` (NOT `meshtastic`)
# to avoid shadowing the PyPI `meshtastic` package — bin/regen-py-protos.sh
# post-processes the protoc output to rename the package.
_HERE = pathlib.Path(__file__).resolve().parent
_LOCAL_PROTO_DIR = _HERE / "_generated"
if _LOCAL_PROTO_DIR.is_dir():
sys.path.insert(0, str(_LOCAL_PROTO_DIR))
try:
from meshtastic_v25.deviceonly_pb2 import ( # type: ignore[import-not-found]
NodeDatabase,
NodeInfoLite,
NodePositionEntry,
NodeTelemetryEntry,
NodeEnvironmentEntry,
NodeStatusEntry,
PositionLite,
)
from meshtastic_v25.mesh_pb2 import HardwareModel, Position, StatusMessage # type: ignore[import-not-found]
from meshtastic_v25.config_pb2 import Config # type: ignore[import-not-found]
from meshtastic_v25.telemetry_pb2 import DeviceMetrics, EnvironmentMetrics # type: ignore[import-not-found]
except ImportError as local_err:
# Fall back to the PyPI package if in-tree bindings haven't been generated.
# Will fail the v25 assertion below if the PyPI package predates the
# satellite-DB schema, but at least gives a clear "run regen-py-protos.sh"
# error message instead of an opaque ImportError.
try:
from meshtastic.protobuf.deviceonly_pb2 import (
NodeDatabase,
NodeInfoLite,
NodePositionEntry,
NodeTelemetryEntry,
NodeEnvironmentEntry,
NodeStatusEntry,
PositionLite,
)
from meshtastic.protobuf.mesh_pb2 import HardwareModel, Position, StatusMessage
from meshtastic.protobuf.config_pb2 import Config
from meshtastic.protobuf.telemetry_pb2 import DeviceMetrics, EnvironmentMetrics
except ImportError as pypi_err:
print(
"ERROR: could not import meshtastic protobuf bindings.\n"
" In-tree generation: run `bin/regen-py-protos.sh` (requires protoc).\n"
" PyPI fallback: `pip install meshtastic` (may lag firmware branch).\n"
f" local error (meshtastic_v25): {local_err}\n"
f" pypi error (meshtastic.protobuf): {pypi_err}",
file=sys.stderr,
)
sys.exit(1)
# Fail loudly if bindings predate v25 (no satellite arrays).
assert (
hasattr(NodeDatabase, "DESCRIPTOR")
and "positions" in NodeDatabase.DESCRIPTOR.fields_by_name
), (
"Loaded meshtastic bindings are older than v25 (NodeDatabase.positions missing). "
"Run `bin/regen-py-protos.sh` against the in-tree protobufs/ submodule."
)
# ---------------------------------------------------------------------------
# Bitfield bit positions (mirror src/mesh/NodeDB.h:467-484).
# ---------------------------------------------------------------------------
BIT_IS_KEY_MANUALLY_VERIFIED = 0
BIT_IS_MUTED = 1
BIT_VIA_MQTT = 2
BIT_IS_FAVORITE = 3
BIT_IS_IGNORED = 4
BIT_HAS_USER = 5
BIT_IS_LICENSED = 6
BIT_IS_UNMESSAGABLE = 7
BIT_HAS_IS_UNMESSAGABLE = 8
BITFIELD_LAYOUT = (
# JSON key bit position
("is_key_manually_verified", BIT_IS_KEY_MANUALLY_VERIFIED),
("is_muted", BIT_IS_MUTED),
("via_mqtt", BIT_VIA_MQTT),
("is_favorite", BIT_IS_FAVORITE),
("is_ignored", BIT_IS_IGNORED),
("has_user", BIT_HAS_USER),
("is_licensed", BIT_IS_LICENSED),
("is_unmessagable", BIT_IS_UNMESSAGABLE),
("has_is_unmessagable", BIT_HAS_IS_UNMESSAGABLE),
)
def _pack_bitfield(bf: dict[str, bool]) -> int:
out = 0
for key, shift in BITFIELD_LAYOUT:
if bf.get(key, False):
out |= (1 << shift)
return out
def _validate_node(node: dict[str, Any]) -> None:
"""Friendly errors so hand-editors get clear feedback."""
if "num" not in node or not isinstance(node["num"], str):
raise ValueError(f"node missing/invalid 'num' (must be hex string): {node!r}")
if "long_name" not in node:
raise ValueError(f"node {node['num']}: missing 'long_name'")
if len(node["long_name"]) > 24:
raise ValueError(
f"node {node['num']}: long_name {node['long_name']!r} is "
f"{len(node['long_name'])} chars; max 24 (nanopb max_size:25 minus NUL)"
)
if "short_name" in node:
# short_name max_size:5 (incl. NUL) → 4 bytes of content.
# Char count is irrelevant — emojis with variation selectors (e.g. ❄️ = 6 B)
# would slip past a `len(str) > 4` check. Always measure bytes.
b = node["short_name"].encode("utf-8")
if len(b) > 4:
raise ValueError(
f"node {node['num']}: short_name {node['short_name']!r} is "
f"{len(b)} bytes UTF-8; max 4 (nanopb max_size:5 minus NUL)"
)
pk = node.get("public_key_hex", "")
if pk and len(pk) != 64:
raise ValueError(
f"node {node['num']}: public_key_hex must be 64 hex chars or empty; "
f"got {len(pk)} chars"
)
if pk:
try:
bytes.fromhex(pk)
except ValueError as e:
raise ValueError(f"node {node['num']}: public_key_hex is not valid hex: {e}")
def _resolve_time(
node: dict[str, Any],
field_absolute: str,
field_offset: str,
now_epoch: int,
) -> int:
"""If `field_absolute` is set, use it; else compute `now_epoch - offset`."""
if field_absolute in node and node[field_absolute] is not None:
return int(node[field_absolute])
offset = node.get(field_offset, 0)
return max(0, int(now_epoch) - int(offset))
def _build_node_info_lite(node: dict[str, Any], now_epoch: int) -> NodeInfoLite:
_validate_node(node)
info = NodeInfoLite()
info.num = int(node["num"], 16) if isinstance(node["num"], str) else int(node["num"])
info.long_name = node.get("long_name", "")
info.short_name = node.get("short_name", "")
# Enum lookups will raise ValueError on unknown names — that's exactly what we want.
info.hw_model = HardwareModel.Value(node.get("hw_model", "UNSET"))
info.role = Config.DeviceConfig.Role.Value(node.get("role", "CLIENT"))
pk_hex = node.get("public_key_hex", "")
if pk_hex:
info.public_key = bytes.fromhex(pk_hex)
info.snr = float(node.get("snr", 0.0))
info.channel = int(node.get("channel", 0))
if "hops_away" in node:
# `optional uint32 hops_away = 9;` — in Python protobuf, assigning the
# field implicitly sets HasField("hops_away") to True. No has_hops_away
# setter exists (unlike the C++ nanopb-generated header).
info.hops_away = int(node["hops_away"])
info.next_hop = int(node.get("next_hop", 0))
info.last_heard = _resolve_time(node, "last_heard", "last_heard_offset_sec", now_epoch)
info.bitfield = _pack_bitfield(node.get("bitfield", {}))
return info
def _build_position_entry(num: int, pos: dict[str, Any], now_epoch: int) -> NodePositionEntry:
entry = NodePositionEntry()
entry.num = num
pl = PositionLite()
# Firmware stores lat/long as int32 in 1e-7 degrees.
pl.latitude_i = int(round(float(pos["latitude"]) * 1e7))
pl.longitude_i = int(round(float(pos["longitude"]) * 1e7))
pl.altitude = int(pos.get("altitude", 0))
pl.time = _resolve_time(pos, "time", "time_offset_sec", now_epoch)
pl.location_source = Position.LocSource.Value(pos.get("location_source", "LOC_UNSET"))
entry.position.CopyFrom(pl)
return entry
def _build_telemetry_entry(num: int, tel: dict[str, Any]) -> NodeTelemetryEntry:
entry = NodeTelemetryEntry()
entry.num = num
dm = DeviceMetrics()
if "battery_level" in tel:
dm.battery_level = int(tel["battery_level"])
if "voltage" in tel:
dm.voltage = float(tel["voltage"])
if "channel_utilization" in tel:
dm.channel_utilization = float(tel["channel_utilization"])
if "air_util_tx" in tel:
dm.air_util_tx = float(tel["air_util_tx"])
if "uptime_seconds" in tel:
dm.uptime_seconds = int(tel["uptime_seconds"])
entry.device_metrics.CopyFrom(dm)
return entry
def _build_environment_entry(num: int, env: dict[str, Any]) -> NodeEnvironmentEntry:
entry = NodeEnvironmentEntry()
entry.num = num
em = EnvironmentMetrics()
if "temperature" in env:
em.temperature = float(env["temperature"])
if "relative_humidity" in env:
em.relative_humidity = float(env["relative_humidity"])
if "barometric_pressure" in env:
em.barometric_pressure = float(env["barometric_pressure"])
if "iaq" in env:
em.iaq = int(env["iaq"])
entry.environment_metrics.CopyFrom(em)
return entry
def _build_status_entry(num: int, status: dict[str, Any]) -> NodeStatusEntry:
# `StatusMessage` (mesh.proto:1445) has a single `string status` field.
entry = NodeStatusEntry()
entry.num = num
sm = StatusMessage()
if "status" in status:
sm.status = str(status["status"])
entry.status.CopyFrom(sm)
return entry
def compile_jsonl_to_proto(jsonl_path: pathlib.Path, now_epoch: int) -> bytes:
"""Read a seed JSONL and return the encoded NodeDatabase bytes."""
lines = jsonl_path.read_text(encoding="utf-8").splitlines()
if not lines:
raise ValueError(f"{jsonl_path} is empty")
meta_line = lines[0]
meta_obj = json.loads(meta_line)
meta = meta_obj.get("_meta", {})
version = meta.get("version")
if version != 25:
raise ValueError(
f"{jsonl_path}: meta version is {version!r}; this compiler "
f"requires version=25. Regenerate the seed with the matching tooling."
)
db = NodeDatabase()
db.version = 25
for ln, raw in enumerate(lines[1:], start=2):
raw = raw.strip()
if not raw:
continue
try:
node = json.loads(raw)
except json.JSONDecodeError as e:
raise ValueError(f"{jsonl_path}:{ln} JSON parse error: {e}")
num = int(node["num"], 16) if isinstance(node["num"], str) else int(node["num"])
# Header
info = _build_node_info_lite(node, now_epoch)
db.nodes.append(info)
# Satellites (nullable)
if node.get("position"):
db.positions.append(_build_position_entry(num, node["position"], now_epoch))
if node.get("telemetry"):
db.telemetry.append(_build_telemetry_entry(num, node["telemetry"]))
if node.get("environment"):
db.environment.append(_build_environment_entry(num, node["environment"]))
if node.get("status"):
db.status.append(_build_status_entry(num, node["status"]))
return db.SerializeToString()
def main(argv: list[str]) -> int:
p = argparse.ArgumentParser(
description="Compile a seed JSONL into a binary v25 NodeDatabase proto.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
p.add_argument("--in", dest="in_path", required=True, help="Input seed JSONL.")
p.add_argument("--out", required=True, help="Output binary .proto path.")
p.add_argument(
"--now-epoch",
type=int,
default=None,
help="Pin 'now' to this Unix epoch (for byte-identical CI). Default: time.time().",
)
args = p.parse_args(argv)
in_path = pathlib.Path(args.in_path)
if not in_path.is_file():
print(f"input not found: {in_path}", file=sys.stderr)
return 2
now_epoch = args.now_epoch if args.now_epoch is not None else int(time.time())
try:
encoded = compile_jsonl_to_proto(in_path, now_epoch)
except ValueError as e:
print(f"ERROR: {e}", file=sys.stderr)
return 3
out_path = pathlib.Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_bytes(encoded)
print(
f"compiled {in_path} -> {out_path} ({len(encoded)} bytes, now_epoch={now_epoch})",
file=sys.stderr,
)
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
-54
View File
@@ -1,54 +0,0 @@
{
"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"
}
-53
View File
@@ -1,53 +0,0 @@
{
"build": {
"arduino": {
"ldscript": "nrf52840_s140_v6.ld"
},
"core": "nRF5",
"cpu": "cortex-m4",
"extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA",
"f_cpu": "64000000L",
"hwids": [["0x239A", "0x8029"]],
"usb_product": "T-Impulse-Plus-nRF52840",
"mcu": "nrf52840",
"variant": "t-impulse-plus",
"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"
},
"frameworks": ["arduino"],
"name": "Lilygo T-Impulse-Plus-nRF52840",
"upload": {
"maximum_ram_size": 248832,
"maximum_size": 815104,
"require_upload_port": true,
"speed": 115200,
"protocol": "nrfutil",
"protocols": [
"jlink",
"nrfjprog",
"nrfutil",
"stlink",
"cmsis-dap",
"blackmagic"
]
},
"url": "https://www.lilygo.cc/",
"vendor": "Lilygo"
}
-18
View File
@@ -1,21 +1,3 @@
meshtasticd (2.7.27.0) unstable; urgency=medium
* Version 2.7.27
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 24 Jun 2026 11:20:05 +0000
meshtasticd (2.7.26.0) unstable; urgency=medium
* Version 2.7.26
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Wed, 10 Jun 2026 00:19:23 +0000
meshtasticd (2.7.25.0) unstable; urgency=medium
* Version 2.7.25
-- GitHub Actions <github-actions[bot]@users.noreply.github.com> Sat, 23 May 2026 01:16:20 +0000
meshtasticd (2.7.24.0) unstable; urgency=medium
* Version 2.7.24
+1 -1
View File
@@ -33,5 +33,5 @@ if [[ -n $GPG_KEY_ID ]]; then
debuild -S -nc -k"$GPG_KEY_ID"
else
# Build the source deb without signing (forks)
debuild -S -nc -us -uc
debuild -S -nc
fi
+15 -2
View File
@@ -82,5 +82,18 @@ if esp32_kind == "esp32":
]
)
else:
# For newer ESP32 targets, using newlib nano works better.
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
# For newer ESP32 targets, using newlib nano works better. Skip on
# variants that explicitly opt out — the IDF 5.1 framework override the
# HaLow variant uses already includes nano.specs, so re-adding it triggers
# a duplicate spec definition error at link time.
cppdefines = env.get("CPPDEFINES", [])
if not any(
(isinstance(d, str) and d == "MESHTASTIC_SKIP_NANO_SPECS")
or (
isinstance(d, (list, tuple))
and len(d) > 0
and d[0] == "MESHTASTIC_SKIP_NANO_SPECS"
)
for d in cppdefines
):
env.Append(LINKFLAGS=["--specs=nano.specs", "-u", "_printf_float"])
+28
View File
@@ -0,0 +1,28 @@
"""
PlatformIO doesn't natively link .o files vendored inside a library directory.
The Morse Micro SDK ships the chip firmware (mm6108.mbin.o) and per-region BCF
(bcf_mf08651_us.mbin.o) as pre-built object files containing data sections.
This script appends them to LINKFLAGS so they land in the final ELF.
US region only for now — when we add EU/JP/KR variants, gate the BCF here on a
build flag and pick the matching .o file.
"""
import os
Import("env", "projenv")
LIB_DIR = os.path.join(env.subst("$PROJECT_DIR"), "lib", "MorseWlan")
mbin_objects = [
os.path.join(LIB_DIR, "src", "mm6108.mbin.o"),
os.path.join(LIB_DIR, "src", "bcf_mf08651_us.mbin.o"),
]
# Only add objects that actually exist; missing ones surface as link errors,
# not silent corruption.
for obj in mbin_objects:
if not os.path.isfile(obj):
print("warning: mm-iot-esp32 blob missing: %s" % obj)
env.Append(LINKFLAGS=mbin_objects)
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MBIN Morse BINary Loader API
*
* This file defines the structure of the @c MBIN file.
*
* @{
*/
#pragma once
#ifndef PACKED
/** Macro for the compiler packed attribute */
#define PACKED __attribute__((packed))
#endif
/** Enumeration of TLV field types */
enum mbin_tlv_types {
FIELD_TYPE_FW_TLV_BCF_ADDR = 0x0001,
FIELD_TYPE_MAGIC = 0x8000,
FIELD_TYPE_FW_SEGMENT = 0x8001,
FIELD_TYPE_FW_SEGMENT_DEFLATED = 0x8002,
FIELD_TYPE_BCF_BOARD_CONFIG = 0x8100,
FIELD_TYPE_BCF_REGDOM = 0x8101,
FIELD_TYPE_BCF_BOARD_DESC = 0x8102,
FIELD_TYPE_BCF_BUILD_VER = 0x8103,
FIELD_TYPE_SW_SEGMENT = 0x8201,
FIELD_TYPE_SW_SEGMENT_DEFLATED = 0x8202,
FIELD_TYPE_EOF = 0x8f00,
FIELD_TYPE_EOF_WITH_SIGNATURE = 0x8f01,
};
/** TLV header data structure. */
struct PACKED mbin_tlv_hdr {
/** Type (see mbin_tlv_types). */
uint16_t type;
/** Length of payload (excludes header). */
uint16_t len;
};
/** Data header in a FIELD_TYPE_XX_SEGMENT field. */
struct PACKED mbin_segment_hdr {
/** Destination base address at which the data should be loaded. */
uint32_t base_address;
};
/** Data header in a FIELD_TYPE_XX_SEGMENT_DEFLATED field. */
struct PACKED mbin_deflated_segment_hdr {
/** Destination base address at which the data should be loaded. */
uint32_t base_address;
/** Size of deflated data, infer size of compressed data from TLV length */
uint16_t chunk_size;
/** ZLib header */
uint8_t zlib_header[2];
};
/** Data header in a @c FIELD_TYPE_BCF_REGDOM field. */
struct PACKED mbin_regdom_hdr {
/** Country code that this @c regdom applies to. */
uint8_t country_code[2];
/** Reserved */
uint16_t reserved;
};
/** Expected value of the magic field for a SW image @c MMSW. */
#define MBIN_SW_MAGIC_NUMBER (0x57534d4d)
/** Expected value of the magic field for a firmware image @c MMFW. */
#define MBIN_FW_MAGIC_NUMBER (0x57464d4d)
/** Expected value of the magic field for a BCF @c MMBC. */
#define MBIN_BCF_MAGIC_NUMBER (0x43424d4d)
/** @} */
+124
View File
@@ -0,0 +1,124 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This file should be included from the application mbedtls_config.h file to ensure that
* the mbedTLS features necessary for morselib functionality are enabled.
*
* It is recommended to include this file at the _end_ of the application mbedtls_config.h file
* to avoid redefinition of macros.
*/
/* Cipher modes */
#ifndef MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_CIPHER_MODE_CBC
#endif
#ifndef MBEDTLS_CIPHER_MODE_CTR
#define MBEDTLS_CIPHER_MODE_CTR
#endif
/* EC curves */
#ifndef MBEDTLS_ECP_DP_SECP256R1_ENABLED
#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
#endif
#ifndef MBEDTLS_ECP_DP_SECP384R1_ENABLED
#define MBEDTLS_ECP_DP_SECP384R1_ENABLED
#endif
#ifndef MBEDTLS_ECP_DP_SECP521R1_ENABLED
#define MBEDTLS_ECP_DP_SECP521R1_ENABLED
#endif
/* Features */
#ifndef MBEDTLS_AES_C
#define MBEDTLS_AES_C
#endif
#ifndef MBEDTLS_ASN1_PARSE_C
#define MBEDTLS_ASN1_PARSE_C
#endif
#ifndef MBEDTLS_ASN1_WRITE_C
#define MBEDTLS_ASN1_WRITE_C
#endif
#ifndef MBEDTLS_BIGNUM_C
#define MBEDTLS_BIGNUM_C
#endif
#ifndef MBEDTLS_CIPHER_C
#define MBEDTLS_CIPHER_C
#endif
#ifndef MBEDTLS_CMAC_C
#define MBEDTLS_CMAC_C
#endif
#ifndef MBEDTLS_CTR_DRBG_C
#define MBEDTLS_CTR_DRBG_C
#endif
#ifndef MBEDTLS_ECDH_C
#define MBEDTLS_ECDH_C
#endif
#ifndef MBEDTLS_ECP_C
#define MBEDTLS_ECP_C
#endif
#ifndef MBEDTLS_ENTROPY_C
#define MBEDTLS_ENTROPY_C
#endif
#ifndef MBEDTLS_MD_C
#define MBEDTLS_MD_C
#endif
#ifndef MBEDTLS_NIST_KW_C
#define MBEDTLS_NIST_KW_C
#endif
#ifndef MBEDTLS_OID_C
#define MBEDTLS_OID_C
#endif
#ifndef MBEDTLS_PK_C
#define MBEDTLS_PK_C
#endif
#ifndef MBEDTLS_PK_PARSE_C
#define MBEDTLS_PK_PARSE_C
#endif
#ifndef MBEDTLS_PK_WRITE_C
#define MBEDTLS_PK_WRITE_C
#endif
#ifndef MBEDTLS_PKCS5_C
#define MBEDTLS_PKCS5_C
#endif
#ifndef MBEDTLS_SHA1_C
#define MBEDTLS_SHA1_C
#endif
#ifndef MBEDTLS_SHA224_C
#define MBEDTLS_SHA224_C
#endif
#ifndef MBEDTLS_SHA256_C
#define MBEDTLS_SHA256_C
#endif
#ifndef MBEDTLS_SHA384_C
#define MBEDTLS_SHA384_C
#endif
#ifndef MBEDTLS_SHA512_C
#define MBEDTLS_SHA512_C
#endif
+331
View File
@@ -0,0 +1,331 @@
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMHAL Morse Micro Hardware Abstraction Layer (mmhal) API
*
* This API provides abstraction from the underlying hardware/BSP.
*
* @{
*/
#pragma once
#include "mmhal_flash.h"
#include "mmhal_wlan.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/** Initialization before RTOS scheduler starts. */
void mmhal_early_init(void);
/** Initialization after RTOS scheduler started. */
void mmhal_init(void);
/** Enumeration of ISR states (i.e., whether in ISR or not). */
enum mmhal_isr_state {
MMHAL_NOT_IN_ISR, /**< The function was not executed from ISR context. */
MMHAL_IN_ISR, /**< The function was executed from ISR context. */
MMHAL_ISR_STATE_UNKNOWN, /**< The HAL does not support checking ISR state. */
};
/**
* Enumeration for different LED's on the board.
*
* @note Some of these LED's may not be available on all boards and some of these values
* may refer to the same LED.
*/
enum mmhal_led_id { LED_RED, LED_GREEN, LED_BLUE, LED_WHITE };
/** Enumeration of MCU sleep state. */
enum mmhal_sleep_state {
MMHAL_SLEEP_DISABLED, /**< Disable MCU sleep. */
MMHAL_SLEEP_SHALLOW, /**< MCU to enter shallow sleep. */
MMHAL_SLEEP_DEEP, /**< MCU can enter deep sleep. */
};
/** A value of 0 turns OFF an LED */
#define LED_OFF 0
/**
* A value of 255 turns an LED ON fully.
*
* Some boards support varying an LED's brightness. For these boards a value between 1 and 255
* will result in proportionately varying levels of brightness. LED's that do not have a
* brightness control feature will just turn ON fully for any non zero value.
*/
#define LED_ON 255
/**
* Get the current ISR state (i.e., whether in ISR or not).
*
* @returns the current ISR state, or @c MMHAL_ISR_STATE_UNKNOWN if the HAL does not support
* checking ISR state.
*/
enum mmhal_isr_state mmhal_get_isr_state(void);
/**
* Write to the debug log.
*
* It is assumed the caller will have mechanisms in place to prevent concurrent access.
*
* @param data Buffer containing data to write.
* @param len Length of data in buffer.
*/
void mmhal_log_write(const uint8_t *data, size_t len);
/**
* Flush the debug log before returning.
*
* @warning Implementations of this function must support being invoked with interrupts disabled.
*/
void mmhal_log_flush(void);
/**
* Generate a random 32 bit integer within the given range.
*
* @param min Minimum value (inclusive).
* @param max Maximum value (inclusive).
*
* @returns a randomly generated integer (min <= i <= max).
*/
uint32_t mmhal_random_u32(uint32_t min, uint32_t max);
/** Reset the microcontroller. */
void mmhal_reset(void);
/**
* Set the specified LED to the requested level. Do nothing if the requested LED does not exist.
*
* @param led The LED to set, if the platform supports it. See @ref mmhal_led_id
* @param level The level to set it to. 0 means OFF and non-zero means ON. If the platform supports
* brightness levels then 255 is full. Defines @ref LED_ON and @ref LED_OFF are
* provided for ease of use.
*/
void mmhal_set_led(uint8_t led, uint8_t level);
/**
* Set the error LED to the requested state.
*
* @note This function is called by the bootloader and so needs to do all the initialization
* required to set the LED's as the bootloader does not use the regular BSP initialization
* located in main.c for configuring @c GPIO's and setting clock gates as required.
*
* @param state Set to true if the LED needs to be turned on,
* or false if the led needs to be turned off.
*/
void mmhal_set_error_led(bool state);
/**
* Enumeration for buttons on the board.
*
* @note The support for each button is platform dependent.
*/
enum mmhal_button_id { BUTTON_ID_USER0 };
/**
* Enumeration for button states
*/
enum mmhal_button_state { BUTTON_RELEASED, BUTTON_PRESSED };
/** Button state callback function prototype. */
typedef void (*mmhal_button_state_cb_t)(enum mmhal_button_id button_id, enum mmhal_button_state button_state);
/**
* Registers a callback handler for button state changes.
*
* @note The callback will be executed in an Interrupt Service Routine context
*
* @param button_id The button whose state should be reported to the callback
* @param button_state_cb The function to call on button state change, or NULL to disable.
* @returns True if the callback is registered successfully, False if not supported
*/
bool mmhal_set_button_callback(enum mmhal_button_id button_id, mmhal_button_state_cb_t button_state_cb);
/**
* Returns the registered callback handler for button state changes.
*
* @param button_id The button whose callback should be returned
* @returns The registered callback or NULL if no callback registered
*/
mmhal_button_state_cb_t mmhal_get_button_callback(enum mmhal_button_id button_id);
/**
* Reads the state of the specified button.
*
* @param button_id The button state to read
* @returns The current button state, or BUTTON_RELEASED if not supported
*/
enum mmhal_button_state mmhal_get_button(enum mmhal_button_id button_id);
/**
* Reads information that can be used to identify the hardware platform, such as
* hardware ID and version number, in the form of a user readable string.
*
* This function attempts to detect the correct hardware and version.
* The actual means of detecting the correct hardware and version will vary from
* implementation to implementation and may use means such as identification
* information stored in EEPROM or devices detected on GPIO, SPI or I2C interfaces.
* Returns false if the hardware could not be identified correctly.
*
* @param version_buffer The pre-allocated buffer to return the hardware ID and version in.
* @param version_buffer_length The length of the pre-allocated buffer.
* @returns True if the hardware was correctly identified and returned.
*/
bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length);
/**
* Macro to simplify debug pin mask/value definition.
*
* @param _pin_num The pin number to set in the mask. Must be 0-31 (inclusive).
*
* Example:
*
* mmhal_set_debug_pins(MMHAL_DEBUG_PIN(0), MMHAL_DEBUG_PIN(0));
*/
#define MMHAL_DEBUG_PIN(_pin_num) (1ul << (_pin_num))
/** Bit mask with all debug pins selected. */
#define MMHAL_ALL_DEBUG_PINS (UINT32_MAX)
/**
* Set the value one or more debug pins.
*
* Each platform can define up to 32 GPIOs for application use. If a GPIO is not supported
* by a platform then attempts to set it will be silently ignored. These GPIOs are intended
* for debug/test purposes.
*
* @param mask Mask of GPIOs to modify. Each bit in this mask that is set will result in
* the corresponding GPIO being being set to the corresponding value given in
* @p values.
* @param values Bit field, where each bit corresponds to a GPIO, specifying the direction
* to drive each GPIO with 1 meaning drive high and 0 meaning drive low.
* Only GPIOs with the corresponding bit set in @p mask will be modified.
*
* @sa MM_DEBUG_PIN_MASK
*/
void mmhal_set_debug_pins(uint32_t mask, uint32_t values);
/**
* Returns the time of day as set in the RTC.
* Time is in UTC.
*
* @return Epoch time (seconds since 1 Jan 1970) or 0 on failure.
*/
time_t mmhal_get_time();
/**
* Sets the RTC to the specified time in UTC.
*
* @note While Unix epoch time supports years from 1970, most Real Time Clock
* chips support years from 2000 only as they store the year as years
* from 2000. So do not attempt to set any years below 2000 as this could cause
* the year to wrap around to an unreasonably high value. Definitely do not do:
* @code
* mmhal_set_time(0);
* @endcode
*
* @param epoch Time in Unix epoch time (seconds since 1 Jan 1970).
*/
void mmhal_set_time(time_t epoch);
/**
* Function to prepare MCU to enter sleep.
* This will halt the timer that generates the RTOS tick.
*
* @param expected_idle_time_ms Expected time to sleep in milliseconds.
*
* @returns the type of sleep permitted by the current system state.
*/
enum mmhal_sleep_state mmhal_sleep_prepare(uint32_t expected_idle_time_ms);
/**
* Function to enter MCU sleep.
*
* @param sleep_state Sleep state to enter into.
* @param expected_idle_time_ms Expected time to sleep in milliseconds.
*
* @returns Elapsed sleep time in milliseconds.
*/
uint32_t mmhal_sleep(enum mmhal_sleep_state sleep_state, uint32_t expected_idle_time_ms);
/**
* Function to abort the MCU sleep state.
*
* @note This must only be invoked after @ref mmhal_sleep_prepare()
* and before @ref mmhal_sleep().
*
* @param sleep_state Sleep state to abort.
*/
void mmhal_sleep_abort(enum mmhal_sleep_state sleep_state);
/**
* Function to cleanup on exit from the MCU sleep state.
*/
void mmhal_sleep_cleanup(void);
/** Enumeration of veto_id ranges for use with @ref mmhal_set_deep_sleep_veto() and
* @ref mmhal_clear_deep_sleep_veto(). */
enum mmhal_veto_id {
/** Start of deep sleep veto ID range that is available for application use. */
MMHAL_VETO_ID_APP_MIN = 0,
/** End of deep sleep veto ID range that is available for application use. */
MMHAL_VETO_ID_APP_MAX = 7,
/** Start of deep sleep veto ID range that is available for HAL use. */
MMHAL_VETO_ID_HAL_MIN = 8,
/** End of deep sleep veto ID range that is available for HAL use. */
MMHAL_VETO_ID_HAL_MAX = 15,
/** Start of deep sleep veto ID range that is allocated for morselib use. Note that this
* must not be changed as it is built into morselib. */
MMHAL_VETO_ID_MORSELIB_MIN = 16,
/** End of deep sleep veto ID range that is allocated for morselib use. Note that this must not
* be changed as it is built into morselib. */
MMHAL_VETO_ID_MORSELIB_MAX = 19,
/** Deep sleep veto ID for data-link subsystem. */
MMHAL_VETO_ID_DATALINK = 20,
/** Deep sleep veto ID allocated to @ref MMCONFIG. */
MMHAL_VETO_ID_MMCONFIG = 21,
/** Start of deep sleep veto ID range reserved for future use. */
MMHAL_VETO_ID_RESERVED_MIN = 22,
/** End of deep sleep veto ID range reserved for future use. */
MMHAL_VETO_ID_RESERVED_MAX = 31,
};
/**
* Sets a deep sleep veto that will prevent the device from entering deep sleep. The device
* will not enter deep sleep until there are no vetoes remaining. This veto can be cleared
* by a call to @ref mmhal_clear_deep_sleep_veto() with the same veto_id.
*
* Up to 32 vetoes are supported (ID numbers 0-31). Each veto should be used exclusively by
* a given aspect of the system (e.g., to prevent deep sleep when a DMA transfer is in progress,
* or to prevent deep sleep when there is log data buffered for transmit, etc.).
*
* @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges
* for use by different subsystems -- see @ref mmhal_veto_id.
*/
void mmhal_set_deep_sleep_veto(uint8_t veto_id);
/**
* Clears a deep sleep veto that was preventing the device from entering deep sleep (see
* @ref mmhal_set_deep_sleep_veto()). If the given veto was not already set then this has
* no effect.
*
* @param veto_id The veto identifier. Valid values are 0-31, and these are split up into ranges
* for use by different subsystems -- see @ref mmhal_veto_id.
*/
void mmhal_clear_deep_sleep_veto(uint8_t veto_id);
#ifdef __cplusplus
}
#endif
/** @} */
+145
View File
@@ -0,0 +1,145 @@
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMHAL Morse Micro Flash Hardware Abstraction Layer (mmhal_flash) API
*
* This API provides abstraction from the underlying flash hardware/.
*
* @{
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* This is the value erased flash bytes are set to. This shall be @c 0xFF as this is the
* value that hardware flash erases to.
*/
#define MMHAL_FLASH_ERASE_VALUE 0xFF
/** LittleFS configuration structure. Include @c lfs.h for definition. */
struct lfs_config;
/**
* Flash partition configuration structure
*
* This should be initialized using @c MMHAL_FLASH_PARTITION_CONFIG_DEFAULT.
* For example:
*
* @code{.c}
* struct mmhal_flash_partition_config partition = MMHAL_FLASH_PARTITION_CONFIG_DEFAULT;
* @endcode
*/
struct mmhal_flash_partition_config {
/** The start address of the partition, this may be a physical address
* or a relative address depending on implementation
*/
uint32_t partition_start;
/** The size of the partition */
uint32_t partition_size;
/**
* If true, then the partition is not memory mapped and cannot be directly accessed
* at the physical @c partition_start address
*/
bool not_memory_mapped;
};
/** Initial values for @ref mmhal_flash_partition_config. */
#define MMHAL_FLASH_PARTITION_CONFIG_DEFAULT \
{ \
0, 0, false \
}
/**
* Get MMCONFIG flash partition configuration.
*
* MMCONFIG initialization is done by @c mmconfig_init() in @c mmconfig.c.
* which in turn calls this function to fetch the partition configuration for config store
* from the HAL layer. If config store is not supported by the platform then we just
* return NULL. This function returns a static pointer to
* @c struct @c mmhal_flash_partition_config.
*
* @return A static pointer to the partition config for MMCONFIG, or NULL if not supported.
*/
const struct mmhal_flash_partition_config *mmhal_get_mmconfig_partition(void);
/**
* Erases a block of Flash pointed to by the block_address.
*
* The block address may be anywhere within the block to erase. The entire block gets erased.
* Once erased all bytes in the block shall be @c MMHAL_FLASH_ERASE_VALUE (@c 0xFF).
*
* @param block_address The address of the block of Flash to erase.
* @return 0 on success, negative number on failure
*/
int mmhal_flash_erase(uint32_t block_address);
/**
* Returns the size of the Flash block at the specified address.
*
* @param block_address The address of the Flash block.
* @return The size of the Flash block in bytes.
* Returns 0 if an invalid address is specified.
*/
uint32_t mmhal_flash_getblocksize(uint32_t block_address);
/**
* Read a block of data from the specified Flash address into the buffer.
*
* @param read_address The address in Flash to read from.
* @param buf The buffer to read into.
* @param size The number of bytes to read.
* @return 0 on success, or a negative number on failure.
*/
int mmhal_flash_read(uint32_t read_address, uint8_t *buf, size_t size);
/**
* Write a block of data to the specified Flash address.
*
* There is no alignment or minimum size requirement. This function will
* take care of aligning the data and merging with existing Flash contents.
* The Flash block is not erased, it is up to the application to determine
* if the block needs to be erased before programming.
*
* @param write_address The address in Flash to write to.
* @param data A pointer to the block of data to write.
* @param size The number of bytes to write.
* @return 0 on success, or a negative number on failure.
*/
int mmhal_flash_write(uint32_t write_address, const uint8_t *data, size_t size);
/**
* Get LittleFS configuration.
*
* LittleFS initialization is done by @c littlefs_init() in @c mmosal_shim_fileio.c.
* which in turn calls this function to fetch the hardware configuration for LittleFS
* from the HAL layer. The LittleFS configuration will vary from platform to platform.
* If LittleFS is not supported by the platform then we just return NULL. This function
* returns a static pointer to @c struct @c lfs_config which is defined in @c lfs.h.
*
* See @c mmhal_littlefs.c for the full HAL layer implementation for your platform.
* See @c mmosal_shim_fileio.c for the @c libc shims for LittleFS.
* See @c README.md in the @c src/littlefs folder for detailed information on LittleFS.
*
* @return A static pointer to the LittleFS config structure, or NULL if not supported.
*/
const struct lfs_config *mmhal_get_littlefs_config(void);
#ifdef __cplusplus
}
#endif
/** @} */
+89
View File
@@ -0,0 +1,89 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMHAL
* @defgroup MMHAL_UART Morse Micro Abstraction Layer API for UART
*
* This provides an abstraction layer for a UART. This is used by MM-IoT-SDK example
* applications.
*
* This is a very simple API and leaves UART configuration to the HAL.
*
* @{
*/
#pragma once
#include "mmhal.h"
#include "mmosal.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Function type for UART RX callback.
*
* @note The UART HAL must not invoke this function from interrupt context. However, the
* implementation should not block for long periods of time or received data may be lost.
*
* @param data The received data.
* @param length Length of the received data.
* @param arg Opaque argument (as passed in to @ref mmhal_uart_init()).
*/
typedef void (*mmhal_uart_rx_cb_t)(const uint8_t *data, size_t length, void *arg);
/**
* Initialize the UART HAL and perform any setup necessary.
*
* @param rx_cb Optional callback to be invoked on receive (may be NULL).
* This callback may be invoked from interrupt context so should return
* quickly.
* @param rx_cb_arg Optional opaque argument to be passed to the RX callback. May be NULL.
*/
void mmhal_uart_init(mmhal_uart_rx_cb_t rx_cb, void *rx_cb_arg);
/**
* Deinitialize the UART HAL, and disable the UART.
*/
void mmhal_uart_deinit(void);
/**
* Transmit data on the UART. This will block until all data is buffered for transmit (but may
* return before transmission has completed).
*
* @param data Data to transmit.
* @param length Length of @p data.
*/
void mmhal_uart_tx(const uint8_t *data, size_t length);
/** Enumeration of deep sleep modes for the UART HAL. */
enum mmhal_uart_deep_sleep_mode {
/** Deep sleep mode is disabled. */
MMHAL_UART_DEEP_SLEEP_DISABLED,
/** Enable deep sleep until activity occurs on data-link transport. */
MMHAL_UART_DEEP_SLEEP_ONE_SHOT,
};
/**
* Set the deep sleep mode for the UART. See @ref mmhal_uart_deep_sleep_mode for possible deep
* sleep modes. Note that a given platform may not support all modes.
*
* @param mode The deep sleep mode to set.
*
* @returns true if the mode was set successfully; false on failure (e.g., unsupported mode).
*/
bool mmhal_uart_set_deep_sleep_mode(enum mmhal_uart_deep_sleep_mode mode);
#ifdef __cplusplus
}
#endif
/** @} */
+640
View File
@@ -0,0 +1,640 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMHAL
* @defgroup MMHAL_WLAN WLAN HAL
*
* API for communicating with the WLAN transceiver.
*
* There are different interfaces supported for communicating with the transceiver:
*
* * @ref MMHAL_WLAN_SDIO
* * @ref MMHAL_WLAN_SPI
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
#pragma once
#include "mmpkt.h"
#include "mmwlan.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Function prototype for interrupt handler callbacks.
*/
typedef void (*mmhal_irq_handler_t)(void);
/**
* Initialize the WLAN HAL.
*
* Things to do here may include:
* * Enable SPI peripheral
* * Configure GPIOs
* * Enable power to the Morse Micro transceiver
*
* @note If enabling power for the Morse Micro transceiver in this function you may need to add a
* blocking delay to allow the power rail to stabilize. This is hardware specific so is not
* accounted for in the calling function.
*/
void mmhal_wlan_init(void);
/**
* Deinitialize the WLAN HAL.
*
* Things to do here may include:
* * Disable SPI peripheral
* * Disable GPIOs
* * Disable power to the Morse Micro transceiver
*/
void mmhal_wlan_deinit(void);
/**
* Get MAC address override.
*
* This function allows the HAL to override the MAC address to be used by the device. The
* MAC address override should be written to @p mac_addr. If no override is required then
* @p mac_addr should be left untouched.
*
* @param[out] mac_addr Location where the MAC address will be stored. This will be initialized
* to zero the first time this function is invoked, and to the previously
* configured MAC address on subsequent invocations.
*/
void mmhal_read_mac_addr(uint8_t *mac_addr);
/**
* Assert the WLAN wake pin.
*/
void mmhal_wlan_wake_assert(void);
/**
* Deassert the WLAN wake pin.
*/
void mmhal_wlan_wake_deassert(void);
/**
* Tests whether the busy pin is currently asserted.
*
* @note This is whether it is logically asserted and does not necessarily
* represent the level of the GPIO pin.
*
* @returns @c true if asserted, else @c false.
*/
bool mmhal_wlan_busy_is_asserted(void);
/**
* Register a handler for busy interrupts.
*
* @param handler The handler to register.
*/
void mmhal_wlan_register_busy_irq_handler(mmhal_irq_handler_t handler);
/**
* Sets whether the busy interrupt is enabled.
*
* @warning The interrupt handler function must be configured using
* mmhal_wlan_register_busy_irq_handler() before enabling the interrupt.
*
* @param enabled @c true to enable or @c false to disable.
*/
void mmhal_wlan_set_busy_irq_enabled(bool enabled);
/**
* Read-only buffer data structure.
*
* The design of this data structure allows the buffer to exist either in statically or
* dynamically allocated memory.
*
* For statically allocated memory, the field @c free_cb may be set to @c NULL and @c free_arg
* ignored. For example:
*
* @code{.c}
* const uint8_t some_data[] = { 0x00, 0x01, 0x02 };
*
* void put_some_data_into_buf(struct mmhal_robuf *robuf)
* {
* robuf->buf = some_data;
* robuf->len = sizeof(some_data);
* robuf->free_cb = NULL;
* }
* @endcode
*
* For dynamically allocated memory, the field @c free_cb is set to the appropriate function to
* free the buffer and @c free_arg is an opaque argument to the free function. This approach might
* be used, for example, when reading into a temporary buffer from storage that is not memory
* mapped. For example:
*
* @code{.c}
* #define SOME_DATA_MAXLEN (64)
*
* void put_some_data_into_buf(struct mmhal_robuf *robuf)
* {
* uint8_t *buf = malloc(SOME_DATA_MAXLEN);
* robuf->buf = buf;
* if (robuf->buf == NULL)
* return;
*
* // HERE: copy data into buf and set robuf->len as appropriate
*
* robuf->free_cb = free;
* robuf->free_arg = buf;
* }
* @endcode
*
*/
struct mmhal_robuf {
/** Pointer to the start of the read-only buffer. May be NULL only if @c len is zero. */
const uint8_t *buf;
/** Length of the buffer contents. */
uint32_t len;
/**
* Optional callback to be invoked by the consumer to release the buffer when it is
* no longer required. If not required, set to @c NULL.
*
* @note The values of @c buf and @c len in this structure may be modified before
* @c free_cb() is invoked. However, the value of @c free_arg will be passed
* to @c free_cb().
*/
void (*free_cb)(void *arg);
/** Optional argument to @c free_cb. Ignored if @c free_cb is @c NULL. */
void *free_arg;
};
/** Minimum length of data to be returned by @ref mmhal_wlan_read_bcf_file() and
* @ref mmhal_wlan_read_fw_file(). */
#define MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH (4)
/**
* Retrieves the content of the Morse Micro Board Configuration File and places it into the given
* buffer.
*
* @param offset Offset from which to start reading the bcf
* @param requested_len Length of data we would like to read. The length of the data returned
* by this function may be less than @p requested_len, but must be at
* least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH.
* @param robuf Read-only buffer data structure to be filled out by this function.
*
* @note On error, this function should set @c robuf->buf to @c NULL.
* @note The caller must zero @p robuf before invoking the function.
* @note The BCF must be in mbin format.
*
* @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer is
* no longer required. Ignored if @c robuf->free_cb is @c NULL.
*/
void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf);
/**
* Retrieves the content of the Morse Micro Chip Firmware and places it into the given buffer.
*
* @param offset Offset from which to start reading the bcf
* @param requested_len Length of data we would like to read. The length of the data returned
* by this function may be less than @p requested_len, but must be at
* least @ref MMHAL_WLAN_FW_BCF_MIN_READ_LENGTH.
* @param robuf Read-only buffer data structure to be filled out by this function.
*
* @note On error, this function should set @c robuf->buf to @c NULL.
* @note The caller must zero @p robuf before invoking the function.
* @note The firmware must be in mbin format.
*
* @warning The caller is responsible for checking @c robuf->free_cb and calling when the buffer
* is no longer required. Ignored if @c robuf->free_cb is @c NULL.
*/
void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf);
/**
* @defgroup MMHAL_WLAN_SPI WLAN HAL API for SPI interface
*
* API for communicating with the WLAN transceiver over an SPI interface.
*
* @note These functions should only be implemented if using a SPI interface. They are not
* required when using the @ref MMHAL_WLAN_SDIO.
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
/**
* Assert the WLAN SPI chip select pin.
*/
void mmhal_wlan_spi_cs_assert(void);
/**
* Deassert the WLAN SPI chip select pin.
*/
void mmhal_wlan_spi_cs_deassert(void);
/**
* Simultaneously read and write on the SPI bus.
*
* @param data Data to be written.
*
* @return the value that was read.
*/
uint8_t mmhal_wlan_spi_rw(uint8_t data);
/**
* Receive multiple octets of data from SPI bus.
*
* @param buf The buffer to receive into.
* @param len The number of octets to receive.
*/
void mmhal_wlan_spi_read_buf(uint8_t *buf, unsigned len);
/**
* Transmit multiple octets of data to SPI bus.
*
* @param buf The buffer to transmit from.
* @param len The number of octets to transmit.
*
* @note Blocks until transfer complete.
*/
void mmhal_wlan_spi_write_buf(const uint8_t *buf, unsigned len);
/**
* Hard reset the chip by asserting and then releasing the reset pin.
*
* @warning This function must return with the chip in a fully booted state. i.e only return once
* the reset_n line has been high for at least the boot time specified in the data sheet.
* Failure to do so may lead to undefined behavior.
*/
void mmhal_wlan_hard_reset(void);
/**
* Invoked by the driver to check whether the external crystal initialization sequence is required.
*
* Implementation of this function is optional if the external crystal initialization sequence
* is not required. If this function is not implemented then the external crystal initialization
* sequence will be disabled. Refer to the data sheet for your module to check if this initialization
* is required.
*
* @returns true if the external crystal initialization sequence is required else false.
*/
bool mmhal_wlan_ext_xtal_init_is_required(void);
/**
* Issue the training sequence required to put the transceiver into SPI mode.
*/
void mmhal_wlan_send_training_seq(void);
/**
* Register a handler for SPI interrupts.
*
* @param handler The handler to register.
*/
void mmhal_wlan_register_spi_irq_handler(mmhal_irq_handler_t handler);
/**
* Sets whether the SPI interrupt is enabled.
*
* @warning The interrupt handler function must be configured using
* @ref mmhal_wlan_register_spi_irq_handler() before enabling the interrupt.
*
* @param enabled @c true to enable or @c false to disable.
*/
void mmhal_wlan_set_spi_irq_enabled(bool enabled);
/**
* Tests whether the SPI interrupt pin is currently asserted.
*
* @note This is whether it is logically asserted and does not necessarily
* represent the level of the GPIO pin.
*
* @returns @c true if asserted, else @c false.
*/
bool mmhal_wlan_spi_irq_is_asserted(void);
/**
* Clear the SPI IRQ.
*
* @deprecated Do not invoke this function because it is deprecated and will be removed from the
* mmhal API in a future release. This function need not be implemented as a weak
* stub is used in morselib.
*/
void mmhal_wlan_clear_spi_irq(void);
/** @} */
/**
* @defgroup MMHAL_WLAN_PKT WLAN HAL API for packet memory allocation
*
* API for allocating and freeing packet memory.
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
/**
* Flow control callback that can be invoked by the transmit packet memory manager to pause
* and resume the data path in response to resource availability.
*
* @param state Current flow control state.
*/
typedef void (*mmhal_wlan_pktmem_tx_flow_control_cb_t)(enum mmwlan_tx_flow_control_state state);
/** Initialization arguments passed to @ref mmhal_wlan_pktmem_init(). */
struct mmhal_wlan_pktmem_init_args {
/** Flow control callback that can be used by the transmit packet memory manager. */
mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb;
};
/**
* Invoked by the driver to initialize the packet memory in the HAL.
*
* @param args Initialization arguments.
*/
void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args);
/**
* Invoked by the driver to deinitialize the packet memory in the HAL.
*
* This can free reserved memory and check for memory leaks.
*/
void mmhal_wlan_pktmem_deinit(void);
/**
* Enumeration of packet classes used by @ref mmhal_wlan_alloc_mmpkt_for_tx().
* These definitions must match the corresponding values in @c mmdrv_pkt_class.
*/
enum mmhal_wlan_pkt_class {
MMHAL_WLAN_PKT_DATA_TID0, /**< Data TID0 */
MMHAL_WLAN_PKT_DATA_TID1, /**< Data TID1 */
MMHAL_WLAN_PKT_DATA_TID2, /**< Data TID2 */
MMHAL_WLAN_PKT_DATA_TID3, /**< Data TID3 */
MMHAL_WLAN_PKT_DATA_TID4, /**< Data TID4 */
MMHAL_WLAN_PKT_DATA_TID5, /**< Data TID5 */
MMHAL_WLAN_PKT_DATA_TID6, /**< Data TID6 */
MMHAL_WLAN_PKT_DATA_TID7, /**< Data TID7 */
MMHAL_WLAN_PKT_MANAGEMENT, /**< 802.11 Management and other important frames */
MMHAL_WLAN_PKT_COMMAND, /**< Commands from driver to chip */
};
/**
* Allocates an mmpkt for transmission.
*
* When the pool of mmpkt buffers available for TX is exhausted, the HAL should pause the TX
* path using the flow control callback that was registered when @ref mmhal_wlan_pktmem_init()
* was invoked. Similarly, when the buffers become available again (and assuming the TX path is
* not otherwise blocked) the driver should unpause the TX path.
*
* @param pkt_class The class of packet (to allow for prioritization).
* @param space_at_start Amount of space to allocate at start of mmpkt (for prepend).
* @param space_at_end Amount of space to allocate at end of mmpkt (for append).
* @param metadata_length Amount of space to allocate for metadata (used internally by the
* Morse driver).
*
* @returns a pointer to the allocated packet on success or @c NULL on allocation failure.
*/
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end,
uint32_t metadata_length);
/**
* Allocates an mmpkt for reception.
*
* @param capacity Amount of space to allocate for data.
* @param metadata_length Amount of space to allocate for metadata (used internally by the
* Morse driver).
*
* @returns a pointer to the allocated packet on success or @c NULL on allocation failure.
*/
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length);
/** @} */
/**
* @defgroup MMHAL_WLAN_SDIO WLAN HAL API for SDIO interface
*
* API for communicating with the WLAN transceiver over an SDIO interface
*
* @warning These functions shall not be called directly by the end application they are for use
* by Morselib.
*
* @{
*/
/** Enumeration of error codes that may be returned from @c mmhal_wlan_sdio_XXX() functions. */
enum mmhal_sdio_error_codes {
/** Invalid argument given (e.g., incorrect buffer alignment). */
MMHAL_SDIO_INVALID_ARGUMENT = -1,
/** Local hardware error (e.g., issue with SDIO controller). */
MMHAL_SDIO_HW_ERROR = -2,
/** Timeout executing SDIO command. */
MMHAL_SDIO_CMD_TIMEOUT = -3,
/** CRC error executing SDIO command. */
MMHAL_SDIO_CMD_CRC_ERROR = -4,
/** Timeout transferring data. */
MMHAL_SDIO_DATA_TIMEOUT = -5,
/** CRC error transferring data. */
MMHAL_SDIO_DATA_CRC_ERROR = -6,
/** Underflow filling SDIO controller FIFO. */
MMHAL_SDIO_DATA_UNDERFLOW = -7,
/** Overflow reading from SDIO controller FIFO. */
MMHAL_SDIO_DATA_OVERRUN = -8,
/** Another error not covered by the above error codes. */
MMHAL_SDIO_OTHER_ERROR = -9,
};
/**
* Perform transport specific startup.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_startup(void);
/**
* Execute an SDIO command without data.
*
* @param[in] cmd_idx The Command Index.
* @param[in] arg Command argument. This corresponds to the 32 bits of the command between
* the Command Index field and the CRC7 field.
* @param[out] rsp The contents of the command response between the Command Index field
* and the CRC7 field. May be @c NULL if the response is not required.
* The returned value is undefined if the return code is not zero.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_cmd(uint8_t cmd_idx, uint32_t arg, uint32_t *rsp);
/**
* Arguments structure for @ref mmhal_wlan_sdio_cmd53_write().
*/
struct mmhal_wlan_sdio_cmd53_write_args {
/** The SDIO argument. This corresponds to the 32 bits of the command between
* the Command Index field and the CRC7 field. */
uint32_t sdio_arg;
/** Pointer to the data buffer. 32 bit word aligned. */
const uint8_t *data;
/** Transfer length measured in blocks if block_size is non-zero otherwise in bytes.
* If transfer_length is measured in bytes, it will be a multiple of 4. */
uint16_t transfer_length;
/**
* If non-zero this indicates that the data should be transferred in block mode with
* the given block size. If zero then the data should be transferred in byte mode and
* @c transfer_length is guaranteed to not exceed the block size of the function.
*/
uint16_t block_size;
};
/**
* Execute an SDIO CMD53 write.
*
* @param args The write arguments.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_cmd53_write(const struct mmhal_wlan_sdio_cmd53_write_args *args);
/**
* Arguments structure for @ref mmhal_wlan_sdio_cmd53_read().
*/
struct mmhal_wlan_sdio_cmd53_read_args {
/** The SDIO argument. This corresponds to the 32 bits of the command between
* the Command Index field and the CRC7 field. */
uint32_t sdio_arg;
/** Pointer to the data buffer to receive the data. 32 bit word aligned. */
uint8_t *data;
/** Transfer length measured in blocks if block_size is non-zero otherwise in bytes.
* If transfer_length is measured in bytes, it will be a multiple of 4. */
uint16_t transfer_length;
/**
* If non-zero this indicates that the data should be transferred in block mode with
* the given block size. If zero then the data should be transferred in byte mode and
* @c transfer_length is guaranteed to not exceed the block size of the function.
*/
uint16_t block_size;
};
/**
* Execute an SDIO CMD53 read.
*
* @param args The read arguments.
*
* @returns 0 on success, an error code from @ref mmhal_sdio_error_codes on failure.
*/
int mmhal_wlan_sdio_cmd53_read(const struct mmhal_wlan_sdio_cmd53_read_args *args);
/**
* @defgroup MMHAL_WLAN_SDIO_UTILS SDIO Utilities
*
* Useful macros and inline utilities function for use by SDIO and SPI HALs.
*
* @{
*/
/*
* SDIO argument definition, per SDIO Specification Version 4.10, Part E1, Section 5.3.
*/
/** SDIO CMD52/CMD53 R/W flag. */
enum mmhal_sdio_rw {
MMHAL_SDIO_READ = 0, /**< Read operation */
MMHAL_SDIO_WRITE = (1ul << 31), /**< Write operation */
};
/** SDIO CMD52/CMD53 function number. */
enum mmhal_sdio_function {
MMHAL_SDIO_FUNCTION_0 = 0, /** Function 0 */
MMHAL_SDIO_FUNCTION_1 = (1ul << 28), /** Function 1 */
MMHAL_SDIO_FUNCTION_2 = (2ul << 28), /** Function 2 */
};
/** SDIO CMD53 block mode*/
enum mmhal_sdio_mode {
MMHAL_SDIO_MODE_BYTE = 0, /** Byte mode */
MMHAL_SDIO_MODE_BLOCK = (1ul << 27), /** Block mode */
};
/** SDIO CMD53 OP code */
enum mmhal_sdio_opcode {
/** Operate on a single, fixed address. */
MMHAL_SDIO_OPCODE_FIXED_ADDR = 0,
/** Increment address by 1 after each byte. */
MMHAL_SDIO_OPCODE_INC_ADDR = (1ul << 26),
};
/** CMD52/53 Register Address (17 bit) offset. */
#define MMHAL_SDIO_ADDRESS_OFFSET (9)
/** CMD52/53 Register Address maximum value. */
#define MMHAL_SDIO_ADDRESS_MAX ((1ul << 18) - 1)
/** CMD53 Byte/block count offset (9 bit). */
#define MMHAL_SDIO_COUNT_OFFSET (0)
/**CMD53 Byte/block count maximum value. */
#define MMHAL_SDIO_COUNT_MAX ((1ul << 10) - 1)
/** CMD52 Data (8 bit) offset */
#define MMHAL_SDIO_CMD52_DATA_OFFSET (0)
/**
* Construct an SDIO CMD52 argument based on the given arguments.
*
* @param rw Flag indication direction (read or write).
* @param fn The applicable function.
* @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX.
* @param write_data The data to write if this is a write operation. Should be set to zero
* for a read operation.
*
* @return the SDIO CMD52 argument generated based on the given arguments.
*/
static inline uint32_t mmhal_make_cmd52_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, uint32_t address,
uint8_t write_data)
{
uint32_t arg;
arg = rw | fn;
arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET);
arg |= (write_data << MMHAL_SDIO_CMD52_DATA_OFFSET);
return arg;
}
/**
* Construct an SDIO CMD53 argument based on the given arguments.
*
* @param rw Flag indication direction (read or write).
* @param fn The applicable function.
* @param mode Selects between byte and block mode.
* @param address The address to read/write. Must be <= @c MMHAL_SDIO_ADDRESS_MAX.
* @param count The count of bytes/blocks (depending on @p mode) to transfer. Must
* be <= @c MMHAL_SDIO_COUNT_MAX.
*
* @note OP Code 1 (incrementing address) is assumed. See also @ref MMHAL_SDIO_OPCODE_INC_ADDR.
*
* @return the SDIO CMD53 argument generated based on the given arguments.
*/
static inline uint32_t mmhal_make_cmd53_arg(enum mmhal_sdio_rw rw, enum mmhal_sdio_function fn, enum mmhal_sdio_mode mode,
uint32_t address, uint16_t count)
{
uint32_t arg;
arg = rw | fn | MMHAL_SDIO_OPCODE_INC_ADDR | mode;
arg |= (address << MMHAL_SDIO_ADDRESS_OFFSET);
arg |= (count << MMHAL_SDIO_COUNT_OFFSET);
return arg;
}
/** @} */
/** @} */
#ifdef __cplusplus
}
#endif
/** @} */
+383
View File
@@ -0,0 +1,383 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMIPAL Morse Micro IP Stack Abstraction Layer (MMIPAL) API
*
* This API provides a layer of abstraction from the underlying IP stack for common operations
* such as configuring the link and getting link status.
*
* @{
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
/** Maximum length of an IP address string, including null-terminator. */
#ifndef MMIPAL_IPADDR_STR_MAXLEN
#define MMIPAL_IPADDR_STR_MAXLEN (48)
#endif
/** Maximum number of IPv6 addresses supported. */
#ifndef MMIPAL_MAX_IPV6_ADDRESSES
#define MMIPAL_MAX_IPV6_ADDRESSES (3)
#endif
/** Enumeration of status codes returned by MMIPAL functions. */
enum mmipal_status {
/** Completed successfully. */
MMIPAL_SUCCESS,
/** One or more arguments were invalid. */
MMIPAL_INVALID_ARGUMENT,
/** The operation could not complete because the link is not up. */
MMIPAL_NO_LINK,
/** Failed due to memory allocation failure. */
MMIPAL_NO_MEM,
/** This functionality is not supported (e.g., due to build configuration). */
MMIPAL_NOT_SUPPORTED,
};
/** Enumeration of link states. */
enum mmipal_link_state {
/** Link is down. */
MMIPAL_LINK_DOWN,
/** Link is up. */
MMIPAL_LINK_UP,
};
/** Enumeration of IP address allocation modes. */
enum mmipal_addr_mode {
/** Disabled. */
MMIPAL_DISABLED,
/** Static IP address. */
MMIPAL_STATIC,
/** IP address allocated via DHCP. @c LWIP_DHCP must be set to 1 if using LWIP. */
MMIPAL_DHCP,
/** IP address allocated via AutoIP. @c LWIP_DHCP must be set to 1 if using LWIP. */
MMIPAL_AUTOIP,
/** DHCP offloaded to chip. */
MMIPAL_DHCP_OFFLOAD,
};
/** IP address string type. */
typedef char mmipal_ip_addr_t[MMIPAL_IPADDR_STR_MAXLEN];
/**
* IPv4 configuration structure.
*
* This should be initialized using @c MMIPAL_IP_CONFIG_DEFAULT.
* For example:
*
* @code{.c}
* struct mmipal_ip_config config = MMIPAL_IP_CONFIG_DEAFULT;
* @endcode
*/
struct mmipal_ip_config {
/** IP address allocation mode. */
enum mmipal_addr_mode mode;
/** local IP address */
mmipal_ip_addr_t ip_addr;
/** Netmask address */
mmipal_ip_addr_t netmask;
/** Gateway address */
mmipal_ip_addr_t gateway_addr;
};
/** Initializer for @ref mmipal_ip_config. */
#define MMIPAL_IP_CONFIG_DEFAULT \
{ \
MMIPAL_DHCP, "", "", "", \
}
/** Enumeration of IPv6 address allocation modes. */
enum mmipal_ip6_addr_mode {
/** Disabled. */
MMIPAL_IP6_DISABLED,
/** Static IPv6 addresses. */
MMIPAL_IP6_STATIC,
/** IPv6 address allocated via autoconfiguration.
* @c LWIP_IPV6_AUTOCONFIG must be set to 1 if using LWIP. */
MMIPAL_IP6_AUTOCONFIG,
/** IPv6 address allocated via stateless DHCPv6.
* @c LWIP_IPV6_DHCP6_STATELESS must be set to 1 if using LWIP. */
MMIPAL_IP6_DHCP6_STATELESS,
};
/**
* IPv6 configuration structure.
*
* This should be initialized using @c MMIPAL_IP6_CONFIG_DEFAULT.
* For example:
*
* @code{.c}
* struct mmipal_ip6_config config = MMIPAL_IP6_CONFIG_DEFAULT;
* @endcode
*/
struct mmipal_ip6_config {
/** IPv6 addresses allocation mode. */
enum mmipal_ip6_addr_mode ip6_mode;
/** Array of IPv6 addresses. */
mmipal_ip_addr_t ip6_addr[MMIPAL_MAX_IPV6_ADDRESSES];
};
/** Initializer for @ref mmipal_ip6_config. */
#define MMIPAL_IP6_CONFIG_DEFAULT \
{ \
MMIPAL_IP6_AUTOCONFIG \
}
/**
* Initialize arguments structure.
*
* This should be initialized using @c MMIPAL_INIT_ARGS_DEFAULT.
* For example:
*
* @code{.c}
* struct mmipal_init_args args = MMIPAL_INIT_ARGS_DEFAULT;
* @endcode
*/
struct mmipal_init_args {
/** IP address allocation mode to use. */
enum mmipal_addr_mode mode;
/** IP address to use (if @c mode is @c MMIPAL_STATIC). */
mmipal_ip_addr_t ip_addr;
/** Netmask to use (if @c mode is @c MMIPAL_STATIC). */
mmipal_ip_addr_t netmask;
/** Gateway IP address to use (if @c mode is @c MMIPAL_STATIC). */
mmipal_ip_addr_t gateway_addr;
/** IPv6 address allocation mode to use. */
enum mmipal_ip6_addr_mode ip6_mode;
/** IPv6 address to use (if @c ip6_mode is @c MMIPAL_IP6_STATIC). */
mmipal_ip_addr_t ip6_addr;
/** Flag requesting ARP response offload feature */
bool offload_arp_response;
/** ARP refresh offload interval in seconds */
uint32_t offload_arp_refresh_s;
};
/**
* Default values for @ref mmipal_init_args. This should be used when initializing the
* @ref mmipal_init_args structure.
*/
#define MMIPAL_INIT_ARGS_DEFAULT \
{ \
MMIPAL_DHCP, {0}, {0}, {0}, MMIPAL_IP6_DISABLED, {0}, false, 0 \
}
/**
* Initialize the IP stack and enable the MMWLAN interface.
*
* This will implicitly initialize and boot MMWLAN, and will block until this has completed.
*
* @note This function will boot the Morse Micro transceiver using @ref mmwlan_boot() in order
* to read the MAC address. It is the responsibility of the caller to shut down the
* transceiver using @ref mmwlan_shutdown() as required.
*
* @warning @ref mmwlan_init() must be called before invoking this function.
*
* @param args Initialization arguments.
*
* @return @c MMIPAL_SUCCESS on success. otherwise a vendor specific error code.
*/
enum mmipal_status mmipal_init(const struct mmipal_init_args *args);
/**
* Structure representing the current status of the link.
*/
struct mmipal_link_status {
/** State of the link (up/down). */
enum mmipal_link_state link_state;
/** Current IP address. */
mmipal_ip_addr_t ip_addr;
/** Current netmask. */
mmipal_ip_addr_t netmask;
/** Current gateway IP address. */
mmipal_ip_addr_t gateway;
};
/**
* Prototype for callback function invoked on link status changes.
*
* @param link_status The current link status.
*/
typedef void (*mmipal_link_status_cb_fn_t)(const struct mmipal_link_status *link_status);
/**
* Sets the callback function to be invoked on link status changes.
*
* This will be used when DHCP is enabled.
*
* @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config.
* @note If an opaque argument is required then use @ref mmipal_set_ext_link_status_callback()
* instead.
*
* @param fn Function pointer to the callback function.
*/
void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn);
/**
* Prototype for callback function invoked on link status changes.
*
* This is similar to @ref mmipal_link_status_cb_fn_t but with the addition of the @p arg
* parameter.
*
* @param link_status The current link status.
* @param arg Opaque argument that was provided when the callback was registered.
*/
typedef void (*mmipal_ext_link_status_cb_fn_t)(const struct mmipal_link_status *link_status, void *arg);
/**
* Sets the extended link status callback function to be invoked on link status changes.
* This is similar to @ref mmipal_set_link_status_callback() with the exception that
* an opaque argument may also be specified.
*
* This will be used when DHCP is enabled.
*
* @note This is for IPv4 only. To get IPv6 status use @c mmipal_get_ip6_config.
*
* @param fn Function pointer to the callback function.
* @param arg Opaque argument to be passed to the callback.
*/
void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg);
/**
* Get the total number of transmitted and received packets on the MMWLAN interface
*
* @note If using LWIP, this function requires LWIP_STATS to be defined in your application,
* otherwise packet counters will always return as 0.
*
* @param tx_packets Pointer to location to store total tx packets
* @param rx_packets Pointer to location to store total rx packets
*/
void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets);
/**
* Set the QoS Traffic ID to use when transmitting.
*
* @param tid The QoS TID to use (0 - @ref MMWLAN_MAX_QOS_TID).
*/
void mmipal_set_tx_qos_tid(uint8_t tid);
/**
* Gets the local address for the MMWLAN interface that is appropriate for a given
* destination address.
*
* The following table shows how the returned @c local_addr is selected:
*
* | @p dest_addr | @c local_addr returned |
* |--------------|---------------------------|
* | type is IPv4 | IPv4 address |
* | type is IPv6 | An IPv6 source address selected from interface's IPv6 addresses or ERR_CONN |
*
* (X = don't care)
*
* If the given parameters would result in a @p local_addr type of IPv4 and IPv4 is not enabled,
* or IPv6 and IPv6 is not enabled, then @c MMIPAL_INVALID_ARGUMENT will be returned.
*
* @param[out] local_addr Output local address for the MMWLAN interface, as noted above.
* @param[in] dest_addr Destination address.
*
* @return @c MMIPAL_SUCESS if @p local_addr successfully set. otherwise an
* appropriate error code.
*/
enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr);
/**
* Get the IP configurations.
*
* This can be used to get the local IP configurations.
*
* @param config Pointer to the IP configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
*/
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config);
/**
* Set the IP configurations.
*
* This can be used to set the local IP configurations.
*
* @param config Pointer to the IP configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
*/
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config);
/**
* Gets the current IPv4 broadcast address.
*
* @param[out] broadcast_addr Buffer to receive the broadcast address as a string.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv4 is not supported.
*/
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr);
/**
* Get the IP configurations.
*
* This can be used to get the local IP configurations.
*
* @param config Pointer to the IP configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported..
*/
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config);
/**
* Set the IPv6 configurations.
*
* This can be used to set the local IPv6 configurations.
*
* @param config Pointer to the IPv6 configurations.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_NOT_SUPPORTED if IPv6 is not supported.
*/
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config);
/**
* Get current IPv4 link state.
*
* @returns the current IPv4 link state (up or down).
*/
enum mmipal_link_state mmipal_get_link_state(void);
/**
* Set the DNS server at the given index.
*
* @warning Depending on IP stack implementation, this setting may be overridden by DHCP.
*
* @param[in] index Index of the DNS server to set.
* @param[out] addr Address of the DNS server to set.
*
* @returns @c MMIPAL_SUCCESS on success, @c MMIPAL_INVALID_ARGUMENT if an invalid index or IP
* address was given.
*/
enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr);
/**
* Get the DNS server at the given index.
*
* @param[in] index Index of the DNS server to set.
* @param[out] addr IP address buffer to receive the IP address of the DNS server at the given
* index. Will be set to empty string if no server at the given index.
*
* @returns @c MMIPAL_SUCCESS on success.
*/
enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr);
#ifdef __cplusplus
}
#endif
/** @} */
+64
View File
@@ -0,0 +1,64 @@
/*
* Morse logging API
*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
/**
* Macro for printing a @c uint64_t as two separate @c uint32_t values. This is to allow printing of
* these values even when the @c printf implementation doesn't support it.
*/
#define MM_X64_VAL(value) ((uint32_t)(value >> 32)), ((uint32_t)value)
/** Macro for format specifier to print @ref MM_X64_VAL */
#define MM_X64_FMT "%08lx%08lx"
/**
* Macro for printing a MAC address. This saves writing it out by hand.
*
* Must be used in conjunction with @ref MM_MAC_ADDR_FMT. For example:
*
* @code
* uint8_t mac_addr[] = { 0, 1, 2, 3, 4, 5 };
* printf("MAC address: " MM_MAC_ADDR_FMT "\n", MM_MAC_ADDR_VAL(mac_addr));
* @endcode
*/
#define MM_MAC_ADDR_VAL(value) ((value)[0]), ((value)[1]), ((value)[2]), ((value)[3]), ((value)[4]), ((value)[5])
/** Macro for format specifier to print @ref MM_MAC_ADDR_VAL */
#define MM_MAC_ADDR_FMT "%02x:%02x:%02x:%02x:%02x:%02x"
/**
* Initialize Morse logging API.
*
* This should be invoked after OS initialization since it will create a mutex for
* logging.
*/
void mm_logging_init(void);
/**
* Dumps a binary buffer in hex.
*
* @param level A single character indicating log level.
* @param function Name of function this was invoked from.
* @param line_number Line number this was invoked from.
* @param title Title of the buffer.
* @param buf The buffer to dump.
* @param len Length of the buffer.
*/
void mm_hexdump(char level, const char *function, unsigned line_number, const char *title, const uint8_t *buf, size_t len);
#ifdef __cplusplus
}
#endif
File diff suppressed because it is too large Load Diff
+515
View File
@@ -0,0 +1,515 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMPKT Morse Micro Packet Buffer (mmpkt) API
*
* This API provides support for buffers tailored towards packets.
*
* @{
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "mmosal.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* Round @p x up to the next multiple of @p m (where @p m is a power of 2).
*
* @warning @p m must be a power of 2.
*/
#ifndef MM_FAST_ROUND_UP
#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1)
#endif
struct mmdrv_cmd_metadata;
struct mmdrv_tx_metadata;
struct mmdrv_rx_metadata;
struct mmpkt_ops;
/**
* Union of pointer types for mmpkt metadata.
*
* The metadata is accessed through one of these pointers, depending on which context the packet
* is being used in.
*/
union mmpkt_metadata_ptr {
/** Opaque pointer for contexts which are unaware of the specific metadata structure. */
void *opaque;
/** Metadata for a packet which is being transmitted. */
struct mmdrv_tx_metadata *tx;
/** Metadata for a packet which is being received. */
struct mmdrv_rx_metadata *rx;
/** Control block for a command response sent to the host. */
struct mmdrv_cmd_metadata *cmd;
};
/**
* Core mmpkt data structure.
*
* @note The contents of this data structure should never need to be accessed directly. Rather
* the various functions provided as part of this API should be used.
*
* @code
* +----------------------------------------------------------+--------------+
* | RESERVED | Data | RESERVED | METADATA |
* +----------------------------------------------------------+--------------+
* ^ ^ ^ ^
* | | | |
* | |<-----------data_len--------->| |
* | start_offset |
* | |
* |<-----------------------buf_len-------------------------->|
* buf
* @endcode
*/
struct mmpkt {
/** The buffer where data is stored. */
uint8_t *buf;
/** Length of the buffer. */
uint32_t buf_len;
/** Offset where actual data starts in the buffer. */
uint32_t start_offset;
/** Length of actual data in the buffer. */
uint32_t data_len;
/** Packet metadata used by driver (context dependent). */
union mmpkt_metadata_ptr metadata;
/** Reference to operations data structure for this mmpkt. */
const struct mmpkt_ops *ops;
/** Pointer that can be used to construct linked lists. */
struct mmpkt *volatile next;
};
/** Operations data structure for mmpkt. */
struct mmpkt_ops {
/** Free the given mmpkt. */
void (*free_mmpkt)(void *mmpkt);
};
/**
* Opened view of an mmpkt.
*
* In this implementation, this structure does not actually exist. We only use it as a pointer type
* which is incompatible with @ref mmpkt, to distinguish between functions which operate on opened
* packet views versus functions which can operate on unopened packets.
*
* In other implementations of this API, packets must be "opened" (mapped into memory) before their
* contents can be accessed. Thus the distinction between opened and unopened packets is important
* for those implementations.
*/
struct mmpktview;
/**
* Initialize an mmpkt header with the given values.
*
* @param mmpkt mmpkt to initialize.
* @param buf Pointer to buffer.
* @param buf_len Length of @p buf.
* @param data_start_offset Initial value for @c start_offset.
* @param ops Operations data structure.
*/
static inline void mmpkt_init(struct mmpkt *mmpkt, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset,
const struct mmpkt_ops *ops)
{
memset(mmpkt, 0, sizeof(*mmpkt));
mmpkt->buf = buf;
mmpkt->buf_len = buf_len;
mmpkt->start_offset = data_start_offset;
mmpkt->ops = ops;
}
/**
* Initialize an mmpkt in a single buffer using the given values.
*
* @param buf Pointer to buffer.
* @param buf_len Length of @p buf.
* @param space_at_start Amount of space to reserve at start of buffer.
* @param space_at_end Amount of space to reserve at end of buffer.
* @param metadata_size Size of metadata (0 for no metadata).
*
* @param ops Operations data structure.
*
* @note @p buf_len must be large enough to contain the @c mmkpt header, the data buffer
* (rounded up to the nearest 4 bytes) and metadata (rounded up to the nearest 4 bytes).
*
* @returns a pointer to the initialized @c mmpkt (will be the same address as @p buf) or @c NULL
* on error (@p buf length too short).
*/
static inline struct mmpkt *mmpkt_init_buf(uint8_t *buf, uint32_t buf_len, uint32_t space_at_start, uint32_t space_at_end,
uint32_t metadata_size, const struct mmpkt_ops *ops)
{
struct mmpkt *mmpkt = (struct mmpkt *)buf;
uint8_t *data_start;
uint32_t header_size = MM_FAST_ROUND_UP(sizeof(*mmpkt), 4);
uint32_t data_len = MM_FAST_ROUND_UP(space_at_start + space_at_end, 4);
metadata_size = MM_FAST_ROUND_UP(metadata_size, 4);
if (header_size + data_len + metadata_size > buf_len) {
return NULL;
}
data_start = ((uint8_t *)mmpkt) + header_size;
mmpkt_init(mmpkt, data_start, data_len, space_at_start, ops);
if (metadata_size != 0) {
mmpkt->metadata.opaque = data_start + data_len;
memset(mmpkt->metadata.opaque, 0, metadata_size);
}
return mmpkt;
}
/**
* Allocate a new mmpkt on the heap (using @ref mmosal_malloc()).
*
* @param space_at_start Amount of space to reserve at start of buffer.
* @param space_at_end Amount of space to reserve at end of buffer.
* @param metadata_size Size of metadata (0 for no metadata).
*
* @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum
* of @p space_at_start and @p space_at_end (rounded up to a multiple of 4).
*
* @returns newly allocated mmpkt on success or @c NULL on failure.
*/
struct mmpkt *mmpkt_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_size);
/**
* Release a reference to the given mmpkt. If this was the last reference (@c addition_ref_cnt
* was 0) then the mmpkt will be freed using the appropriate op callback.
*
* @param mmpkt The mmpkt to release reference to. May be @c NULL.
*/
void mmpkt_release(struct mmpkt *mmpkt);
/**
* Open a view of the given mmpkt.
*
* Packets must be opened before the contents of their buffer can be accessed. Most of the
* functions below expect to be passed an opened view of a packet to operate on.
*
* The view must be closed by calling @ref mmpkt_close() before the packet is released by
* @ref mmpkt_release().
*
* @param mmpkt The mmpkt to be opened.
*
* @returns a pointer representing the opened view.
*/
static inline struct mmpktview *mmpkt_open(struct mmpkt *mmpkt)
{
return (struct mmpktview *)mmpkt;
}
/**
* Close the given view.
*
* @param[in,out] view Pointer to a variable holding the view to be closed.
* This will be modified to indicate it is no longer valid.
*/
static inline void mmpkt_close(struct mmpktview **view)
{
(void)(view);
}
/**
* Get the underlying mmpkt from an opened view.
*
* @param view View of an mmpkt.
*
* @returns the underlying mmpkt.
*/
static inline struct mmpkt *mmpkt_from_view(struct mmpktview *view)
{
return (struct mmpkt *)view;
}
/**
* Gets a pointer to the start of the data in the mmpkt.
*
* @param view The opened mmpkt to operate on.
*
* @returns a pointer to the start of the data in the mmpkt.
*/
static inline uint8_t *mmpkt_get_data_start(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->buf + mmpkt->start_offset;
}
/**
* Gets a pointer to the end of the data in the mmpkt.
*
* @param view The opened mmpkt to operate on.
*
* @returns a pointer to the end of the data in the mmpkt.
*/
static inline uint8_t *mmpkt_get_data_end(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->buf + mmpkt->start_offset + mmpkt->data_len;
}
/**
* Peek the length of the data currently from an unopened mmpkt.
*
* @param mmpkt The unopened mmpkt to operate on.
*
* @returns the length of the data currently in the mmpkt (note that this is different from the
* length of the available buffer space).
*/
static inline uint32_t mmpkt_peek_data_length(struct mmpkt *mmpkt)
{
return mmpkt->data_len;
}
/**
* Gets the length of the data currently in the mmpkt.
*
* @param view The opened mmpkt to operate on.
*
* @returns the length of the data currently in the mmpkt (note that this is different from the
* length of the available buffer space).
*/
static inline uint32_t mmpkt_get_data_length(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->data_len;
}
/**
* Returns the amount of space available for prepending to the data in the buffer.
*
* @param view The opened mmpkt to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmpkt_available_space_at_start(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->start_offset;
}
/**
* Returns the amount of space available for appending to the data in the buffer.
*
* @param view The opened mmpkt to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmpkt_available_space_at_end(struct mmpktview *view)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return mmpkt->buf_len - (mmpkt->start_offset + mmpkt->data_len);
}
/**
* Reserves space immediately before the data currently in the given mmpkt and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmpkt_prepend_data().
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to be prepended.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmpkt_prepend(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
MMOSAL_ASSERT(len <= mmpkt_available_space_at_start(view));
mmpkt->start_offset -= len;
mmpkt->data_len += len;
return mmpkt->buf + mmpkt->start_offset;
}
/**
* Prepends the given data to the data already in the mmpkt.
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
*
* @warning The memory area pointed to by data must not overlap with the mmpkt data.
*
* @param view The opened mmpkt to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmpkt_prepend_data(struct mmpktview *view, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmpkt_prepend(view, len);
memcpy(dest, data, len);
}
/**
* Reserves space immediately after the data currently in the given mmpkt and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmpkt_append_data().
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_end().
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to be append.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmpkt_append(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
uint8_t *ret = mmpkt_get_data_end(view);
MMOSAL_ASSERT(len <= mmpkt_available_space_at_end(view));
mmpkt->data_len += len;
return ret;
}
/**
* Appends the given data to the data already in the mmpkt.
*
* @warning @p len must be less than or equal to @ref mmpkt_available_space_at_start().
*
* @param view The opened mmpkt to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmpkt_append_data(struct mmpktview *view, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmpkt_append(view, len);
memcpy(dest, data, len);
}
/**
* Retrieve a reference to the metadata associated with the given mmpkt.
*
* @param mmpkt The mmpkt to operate on.
*
* @returns a reference to the mmpkt metadata.
*/
static inline union mmpkt_metadata_ptr mmpkt_get_metadata(struct mmpkt *mmpkt)
{
return mmpkt->metadata;
}
/**
* Remove data from the start of the mmpkt.
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len.
*/
static inline uint8_t *mmpkt_remove_from_start(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
uint8_t *ret;
if (mmpkt_get_data_length(view) < len) {
return NULL;
}
ret = mmpkt_get_data_start(view);
mmpkt->start_offset += len;
mmpkt->data_len -= len;
return ret;
}
/**
* Remove data from the end of the mmpkt.
*
* @param view The opened mmpkt to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmpkt data length was less than @p len.
*/
static inline uint8_t *mmpkt_remove_from_end(struct mmpktview *view, uint32_t len)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
uint8_t *ret;
if (mmpkt_get_data_length(view) < len) {
return NULL;
}
ret = mmpkt_get_data_end(view) - len;
mmpkt->data_len -= len;
return ret;
}
/**
* Truncate the mmpkt data to the given length.
*
* @param mmpkt mmpkt to operate on.
* @param len New data length. (Must be less than or equal to the data length
* of the mmpkt).
*/
static inline void mmpkt_truncate(struct mmpkt *mmpkt, uint32_t len)
{
MMOSAL_ASSERT(len <= mmpkt->data_len);
mmpkt->data_len = len;
}
/**
* Get the `next` pointer embedded in the mmpkt.
*
* Used by the mmpkt_list structure for making linked lists of mmpkts.
*
* @param mmpkt The mmpkt to operate on.
*
* @returns pointer to the next mmpkt in the chain (may be NULL).
*/
static inline struct mmpkt *mmpkt_get_next(struct mmpkt *mmpkt)
{
return mmpkt->next;
}
/**
* Set the `next` pointer embedded in the mmpkt.
*
* Used by the mmpkt_list structure for making linked lists of mmpkts.
*
* @param mmpkt The mmpkt to operate on.
* @param next The next mmpkt in the chain.
*/
static inline void mmpkt_set_next(struct mmpkt *mmpkt, struct mmpkt *next)
{
mmpkt->next = next;
}
/**
* Check whether the given pointer is pointing inside the mmpkt's buffer.
*
* @note This checks against the full buffer, which includes any unused regions at the beginning and
* end of the buffer which do not contain valid packet data.
*
* @param view The opened mmpkt to operate on.
* @param ptr The pointer to check.
*
* @returns true if the pointer points into the buffer.
*/
static inline bool mmpkt_contains_ptr(struct mmpktview *view, const void *ptr)
{
struct mmpkt *mmpkt = (struct mmpkt *)view;
return ((const uint8_t *)ptr >= &mmpkt->buf[0] && (const uint8_t *)ptr < &mmpkt->buf[mmpkt->buf_len]);
}
#ifdef __cplusplus
}
#endif
/** @} */
+172
View File
@@ -0,0 +1,172 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @ingroup MMPKT
*
* @{
*/
#pragma once
#include <stddef.h>
#include "mmpkt.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Structure that can be used as the head of a linked list of mmpkts that counts its length. */
struct mmpkt_list {
/** First mmpkt in the list. */
struct mmpkt *volatile head;
/** Last mmpkt in the list. */
struct mmpkt *volatile tail;
/** Length of the list. */
volatile uint32_t len;
};
/** Static initializer for @ref mmpkt_list. */
#define MMPKT_LIST_INIT \
{ \
NULL, NULL, 0 \
}
/**
* Initialization function for @ref mmpkt_list, for cases where @c MMPKT_LIST_INIT
* cannot be used.
*
* @param list The mmpkt_list to init.
*/
static inline void mmpkt_list_init(struct mmpkt_list *list)
{
list->head = NULL;
list->tail = NULL;
list->len = 0;
}
/**
* Add an mmpkt to the start of an mmpkt list.
*
* @param list The list to prepend to.
* @param mmpkt The mmpkt to prepend.
*/
void mmpkt_list_prepend(struct mmpkt_list *list, struct mmpkt *mmpkt);
/**
* Add an mmpkt to the end of an mmpkt list.
*
* @param list The list to append to.
* @param mmpkt The mmpkt to append.
*/
void mmpkt_list_append(struct mmpkt_list *list, struct mmpkt *mmpkt);
/**
* Remove an mmpkt from an mmpkt list.
*
* @param list The list to remove from.
* @param mmpkt The mmpkt to remove.
*/
void mmpkt_list_remove(struct mmpkt_list *list, struct mmpkt *mmpkt);
/**
* Remove the mmpkt at the head of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmpkt, or @c NULL if the list is empty.
*/
struct mmpkt *mmpkt_list_dequeue(struct mmpkt_list *list);
/**
* Remove the mmpkt at the tail of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmpkt, or @c NULL if the list is empty.
*/
struct mmpkt *mmpkt_list_dequeue_tail(struct mmpkt_list *list);
/**
* Remove all mmpkts from the list and return as a linked list.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmpkts, or @c NULL if the list is empty.
*/
static inline struct mmpkt *mmpkt_list_dequeue_all(struct mmpkt_list *list)
{
struct mmpkt *head = list->head;
list->head = NULL;
list->tail = NULL;
list->len = 0;
return head;
}
/**
* Checks whether the given mmpkt list is empty.
*
* @param list The list to check.
*
* @returns @c true if the list is empty, else @c false.
*/
static inline bool mmpkt_list_is_empty(struct mmpkt_list *list)
{
return (list->head == NULL);
}
/**
* Returns the head of the mmpkt list.
*
* @param list The list to peek into.
*
* @returns the mmpkt at the head of the list.
*/
static inline struct mmpkt *mmpkt_list_peek(struct mmpkt_list *list)
{
return list->head;
}
/**
* Returns the tail of the mmpkt list.
*
* @param list The list to peek into.
*
* @returns the mmpkt at the tail of the list.
*/
static inline struct mmpkt *mmpkt_list_peek_tail(struct mmpkt_list *list)
{
return list->tail;
}
/**
* Free all the packets in the given list and reset the list to empty state.
*
* @param list The list to clear.
*/
void mmpkt_list_clear(struct mmpkt_list *list);
/**
* Safely walk the mmpkt list.
*
* @warning This macro cannot be used following an if statement with no parentheses if there
* is an else clause. For example, do not do:
* `if (x) MMPKT_LIST_WALK(a,b,c) else foo();` -- instead:
* `if (x) { MMPKT_LIST_WALK(a,b,c) } else foo();`
*/
#define MMPKT_LIST_WALK(_lst, _wlk, _nxt) \
if ((_lst)->head != NULL) /* NOLINT(readability/braces) */ \
for (_wlk = (_lst)->head, _nxt = mmpkt_get_next(_wlk); _wlk != NULL; \
_wlk = _nxt, _nxt = _wlk ? mmpkt_get_next(_wlk) : NULL)
#ifdef __cplusplus
}
#endif
/**
* @}
*/
File diff suppressed because it is too large Load Diff
+302
View File
@@ -0,0 +1,302 @@
/*
*
* Copyright 2022-2023 Morse Micro
*/
/**
* @ingroup MMWLAN_REGDB
* @defgroup MMWLAN_REGDB_TEMPLATE Template S1G regulatory database
*
* \{
*
* @section MMWLAN_REGDB_TEMPLATE_DISCLAIMER Disclaimer
*
* While every effort has been made to maintain accuracy of this database, no guarantee is
* given as to the accuracy of the information contained herein.
*
* @section MMWLAN_REGDB_TEMPLATE_COUNTRIES Country code list
*
* | Country Code | Country |
* | ------------ | ------- |
* | AU | Australia |
* | EU | EU |
* | IN | India |
* | JP | Japan |
* | KR | South Korea |
* | NZ | New Zealand |
* | SG | Singapore |
* | US | USA |
*/
#include "mmwlan.h"
/** List of valid S1G channels for Australia. */
static const struct mmwlan_s1g_channel s1g_channels_AU[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 915500000, 10000, false, 68, 22, 27, 1, 30, 0, 0, 0 },
{ 916500000, 10000, false, 68, 22, 29, 1, 30, 0, 0, 0 },
{ 917500000, 10000, false, 68, 22, 31, 1, 30, 0, 0, 0 },
{ 918500000, 10000, false, 68, 22, 33, 1, 30, 0, 0, 0 },
{ 919500000, 10000, false, 68, 22, 35, 1, 30, 0, 0, 0 },
{ 920500000, 10000, false, 68, 22, 37, 1, 30, 0, 0, 0 },
{ 921500000, 10000, false, 68, 22, 39, 1, 30, 0, 0, 0 },
{ 922500000, 10000, false, 68, 22, 41, 1, 30, 0, 0, 0 },
{ 923500000, 10000, false, 68, 22, 43, 1, 30, 0, 0, 0 },
{ 924500000, 10000, false, 68, 22, 45, 1, 30, 0, 0, 0 },
{ 925500000, 10000, false, 68, 22, 47, 1, 30, 0, 0, 0 },
{ 926500000, 10000, false, 68, 22, 49, 1, 30, 0, 0, 0 },
{ 927500000, 10000, false, 68, 22, 51, 1, 30, 0, 0, 0 },
{ 917000000, 10000, false, 69, 23, 30, 2, 30, 0, 0, 0 },
{ 919000000, 10000, false, 69, 23, 34, 2, 30, 0, 0, 0 },
{ 921000000, 10000, false, 69, 23, 38, 2, 30, 0, 0, 0 },
{ 923000000, 10000, false, 69, 23, 42, 2, 30, 0, 0, 0 },
{ 925000000, 10000, false, 69, 23, 46, 2, 30, 0, 0, 0 },
{ 927000000, 10000, false, 69, 23, 50, 2, 30, 0, 0, 0 },
{ 918000000, 10000, false, 70, 24, 32, 4, 30, 0, 0, 0 },
{ 922000000, 10000, false, 70, 24, 40, 4, 30, 0, 0, 0 },
{ 926000000, 10000, false, 70, 24, 48, 4, 30, 0, 0, 0 },
{ 924000000, 10000, false, 71, 25, 44, 8, 30, 0, 0, 0 },
};
/** Channel list structure for Australia. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_AU = {
.country_code = "AU",
.num_channels = (sizeof(s1g_channels_AU)/sizeof(s1g_channels_AU[0])),
.channels = s1g_channels_AU,
};
/** List of valid S1G channels for EU. */
static const struct mmwlan_s1g_channel s1g_channels_EU[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 863500000, 280, false, 66, 6, 1, 1, 16, 0, 0, 0 },
{ 864500000, 280, false, 66, 6, 3, 1, 16, 0, 0, 0 },
{ 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 },
{ 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 },
{ 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 },
};
/** Channel list structure for EU. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_EU = {
.country_code = "EU",
.num_channels = (sizeof(s1g_channels_EU)/sizeof(s1g_channels_EU[0])),
.channels = s1g_channels_EU,
};
/** List of valid S1G channels for India. */
static const struct mmwlan_s1g_channel s1g_channels_IN[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 865500000, 280, false, 66, 6, 5, 1, 16, 0, 0, 0 },
{ 866500000, 280, false, 66, 6, 7, 1, 16, 0, 0, 0 },
{ 867500000, 280, false, 66, 6, 9, 1, 16, 0, 0, 0 },
};
/** Channel list structure for India. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_IN = {
.country_code = "IN",
.num_channels = (sizeof(s1g_channels_IN)/sizeof(s1g_channels_IN[0])),
.channels = s1g_channels_IN,
};
/** List of valid S1G channels for Japan. */
static const struct mmwlan_s1g_channel s1g_channels_JP[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 921000000, 1000, true, 73, 8, 9, 1, 16, 2000, 2000, 100000 },
{ 923000000, 1000, true, 73, 8, 13, 1, 16, 2000, 2000, 100000 },
{ 924000000, 1000, true, 73, 8, 15, 1, 16, 2000, 2000, 100000 },
{ 925000000, 1000, true, 73, 8, 17, 1, 16, 2000, 2000, 100000 },
{ 926000000, 1000, true, 73, 8, 19, 1, 16, 2000, 2000, 100000 },
{ 927000000, 1000, true, 73, 8, 21, 1, 16, 2000, 2000, 100000 },
{ 923500000, 1000, true, 64, 9, 2, 2, 16, 2000, 2000, 100000 },
{ 924500000, 1000, true, 64, 10, 4, 2, 16, 2000, 2000, 100000 },
{ 925500000, 1000, true, 64, 9, 6, 2, 16, 2000, 2000, 100000 },
{ 926500000, 1000, true, 64, 10, 8, 2, 16, 2000, 2000, 100000 },
{ 924500000, 1000, true, 65, 11, 36, 4, 16, 2000, 2000, 100000 },
{ 925500000, 1000, true, 65, 12, 38, 4, 16, 2000, 2000, 100000 },
};
/** Channel list structure for Japan. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_JP = {
.country_code = "JP",
.num_channels = (sizeof(s1g_channels_JP)/sizeof(s1g_channels_JP[0])),
.channels = s1g_channels_JP,
};
/** List of valid S1G channels for South Korea. */
static const struct mmwlan_s1g_channel s1g_channels_KR[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 918000000, 10000, false, 74, 14, 1, 1, 4, 50000, 0, 4000000 },
{ 919000000, 10000, false, 74, 14, 3, 1, 4, 50000, 0, 4000000 },
{ 920000000, 10000, false, 74, 14, 5, 1, 4, 50000, 0, 4000000 },
{ 921000000, 10000, false, 74, 14, 7, 1, 4, 50000, 0, 4000000 },
{ 922000000, 10000, false, 74, 14, 9, 1, 10, 50000, 0, 4000000 },
{ 923000000, 10000, false, 74, 14, 11, 1, 10, 50000, 0, 4000000 },
{ 918500000, 10000, false, 75, 15, 2, 2, 4, 50000, 0, 4000000 },
{ 920500000, 10000, false, 75, 15, 6, 2, 4, 50000, 0, 4000000 },
{ 922500000, 10000, false, 75, 15, 10, 2, 10, 50000, 0, 4000000 },
{ 921500000, 10000, false, 76, 16, 8, 4, 4, 50000, 0, 4000000 },
{ 926500000, 10000, false, 74, 14, 18, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 927500000, 10000, false, 74, 14, 20, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 928500000, 10000, false, 74, 14, 22, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 929500000, 10000, false, 74, 14, 24, 1, 17, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 927000000, 10000, false, 75, 15, 19, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
{ 929000000, 10000, false, 75, 15, 23, 2, 20, 264, 0, 220000 }, /* Warning: regulatory requirements may not be met */
};
/** Channel list structure for South Korea. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_KR = {
.country_code = "KR",
.num_channels = (sizeof(s1g_channels_KR)/sizeof(s1g_channels_KR[0])),
.channels = s1g_channels_KR,
};
/** List of valid S1G channels for New Zealand. */
static const struct mmwlan_s1g_channel s1g_channels_NZ[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 915500000, 10000, false, 68, 26, 27, 1, 30, 0, 0, 0 },
{ 916500000, 10000, false, 68, 26, 29, 1, 30, 0, 0, 0 },
{ 917500000, 10000, false, 68, 26, 31, 1, 30, 0, 0, 0 },
{ 918500000, 10000, false, 68, 26, 33, 1, 30, 0, 0, 0 },
{ 919500000, 10000, false, 68, 26, 35, 1, 30, 0, 0, 0 },
{ 920500000, 10000, false, 68, 26, 37, 1, 36, 0, 0, 0 },
{ 921500000, 10000, false, 68, 26, 39, 1, 36, 0, 0, 0 },
{ 922500000, 10000, false, 68, 26, 41, 1, 36, 0, 0, 0 },
{ 923500000, 10000, false, 68, 26, 43, 1, 36, 0, 0, 0 },
{ 924500000, 10000, false, 68, 26, 45, 1, 36, 0, 0, 0 },
{ 925500000, 10000, false, 68, 26, 47, 1, 36, 0, 0, 0 },
{ 926500000, 10000, false, 68, 26, 49, 1, 36, 0, 0, 0 },
{ 927500000, 10000, false, 68, 26, 51, 1, 36, 0, 0, 0 },
{ 917000000, 10000, false, 69, 27, 30, 2, 30, 0, 0, 0 },
{ 919000000, 10000, false, 69, 27, 34, 2, 30, 0, 0, 0 },
{ 921000000, 10000, false, 69, 27, 38, 2, 36, 0, 0, 0 },
{ 923000000, 10000, false, 69, 27, 42, 2, 36, 0, 0, 0 },
{ 925000000, 10000, false, 69, 27, 46, 2, 36, 0, 0, 0 },
{ 927000000, 10000, false, 69, 27, 50, 2, 36, 0, 0, 0 },
{ 918000000, 10000, false, 70, 28, 32, 4, 30, 0, 0, 0 },
{ 922000000, 10000, false, 70, 28, 40, 4, 36, 0, 0, 0 },
{ 926000000, 10000, false, 70, 28, 48, 4, 36, 0, 0, 0 },
{ 924000000, 10000, false, 71, 29, 44, 8, 36, 0, 0, 0 },
};
/** Channel list structure for New Zealand. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_NZ = {
.country_code = "NZ",
.num_channels = (sizeof(s1g_channels_NZ)/sizeof(s1g_channels_NZ[0])),
.channels = s1g_channels_NZ,
};
/** List of valid S1G channels for Singapore. */
static const struct mmwlan_s1g_channel s1g_channels_SG[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 866500000, 277, false, 66, 17, 7, 1, 29, 100000, 0, 1000000 },
{ 867500000, 277, false, 66, 17, 9, 1, 29, 100000, 0, 1000000 },
{ 868500000, 277, false, 66, 17, 11, 1, 29, 100000, 0, 1000000 },
{ 868000000, 277, false, 67, 19, 10, 2, 29, 100000, 0, 1000000 },
{ 920500000, 10000, false, 68, 18, 37, 1, 22, 0, 0, 0 },
{ 921500000, 10000, false, 68, 18, 39, 1, 22, 0, 0, 0 },
{ 922500000, 10000, false, 68, 18, 41, 1, 22, 0, 0, 0 },
{ 923500000, 10000, false, 68, 18, 43, 1, 22, 0, 0, 0 },
{ 924500000, 10000, false, 68, 18, 45, 1, 22, 0, 0, 0 },
{ 921000000, 10000, false, 69, 20, 38, 2, 22, 0, 0, 0 },
{ 923000000, 10000, false, 69, 20, 42, 2, 22, 0, 0, 0 },
{ 922000000, 10000, false, 70, 21, 40, 4, 22, 0, 0, 0 },
};
/** Channel list structure for Singapore. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_SG = {
.country_code = "SG",
.num_channels = (sizeof(s1g_channels_SG)/sizeof(s1g_channels_SG[0])),
.channels = s1g_channels_SG,
};
/** List of valid S1G channels for USA. */
static const struct mmwlan_s1g_channel s1g_channels_US[] = {
/* Ctr Freq (Hz), Duty Cycle (%/100), Omit Control Response, Global Op Class, S1G Op Class, S1G Chan #, Op BW, Max Tx EIRP (dBm), Min Packet Spacing Window (microsec), airtime_min (microsec), airtime_max (microsec) */
{ 902500000, 10000, false, 68, 1, 1, 1, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */
{ 903500000, 10000, false, 68, 1, 3, 1, 36, 0, 0, 0 },
{ 904500000, 10000, false, 68, 1, 5, 1, 36, 0, 0, 0 },
{ 905500000, 10000, false, 68, 1, 7, 1, 36, 0, 0, 0 },
{ 906500000, 10000, false, 68, 1, 9, 1, 36, 0, 0, 0 },
{ 907500000, 10000, false, 68, 1, 11, 1, 36, 0, 0, 0 },
{ 908500000, 10000, false, 68, 1, 13, 1, 36, 0, 0, 0 },
{ 909500000, 10000, false, 68, 1, 15, 1, 36, 0, 0, 0 },
{ 910500000, 10000, false, 68, 1, 17, 1, 36, 0, 0, 0 },
{ 911500000, 10000, false, 68, 1, 19, 1, 36, 0, 0, 0 },
{ 912500000, 10000, false, 68, 1, 21, 1, 36, 0, 0, 0 },
{ 913500000, 10000, false, 68, 1, 23, 1, 36, 0, 0, 0 },
{ 914500000, 10000, false, 68, 1, 25, 1, 36, 0, 0, 0 },
{ 915500000, 10000, false, 68, 1, 27, 1, 36, 0, 0, 0 },
{ 916500000, 10000, false, 68, 1, 29, 1, 36, 0, 0, 0 },
{ 917500000, 10000, false, 68, 1, 31, 1, 36, 0, 0, 0 },
{ 918500000, 10000, false, 68, 1, 33, 1, 36, 0, 0, 0 },
{ 919500000, 10000, false, 68, 1, 35, 1, 36, 0, 0, 0 },
{ 920500000, 10000, false, 68, 1, 37, 1, 36, 0, 0, 0 },
{ 921500000, 10000, false, 68, 1, 39, 1, 36, 0, 0, 0 },
{ 922500000, 10000, false, 68, 1, 41, 1, 36, 0, 0, 0 },
{ 923500000, 10000, false, 68, 1, 43, 1, 36, 0, 0, 0 },
{ 924500000, 10000, false, 68, 1, 45, 1, 36, 0, 0, 0 },
{ 925500000, 10000, false, 68, 1, 47, 1, 36, 0, 0, 0 },
{ 926500000, 10000, false, 68, 1, 49, 1, 36, 0, 0, 0 },
{ 927500000, 10000, false, 68, 1, 51, 1, 36, 0, 0, 0 },
{ 903000000, 10000, false, 69, 2, 2, 2, 36, 0, 0, 0 }, /* Warning: regulatory requirements may not be met */
{ 905000000, 10000, false, 69, 2, 6, 2, 36, 0, 0, 0 },
{ 907000000, 10000, false, 69, 2, 10, 2, 36, 0, 0, 0 },
{ 909000000, 10000, false, 69, 2, 14, 2, 36, 0, 0, 0 },
{ 911000000, 10000, false, 69, 2, 18, 2, 36, 0, 0, 0 },
{ 913000000, 10000, false, 69, 2, 22, 2, 36, 0, 0, 0 },
{ 915000000, 10000, false, 69, 2, 26, 2, 36, 0, 0, 0 },
{ 917000000, 10000, false, 69, 2, 30, 2, 36, 0, 0, 0 },
{ 919000000, 10000, false, 69, 2, 34, 2, 36, 0, 0, 0 },
{ 921000000, 10000, false, 69, 2, 38, 2, 36, 0, 0, 0 },
{ 923000000, 10000, false, 69, 2, 42, 2, 36, 0, 0, 0 },
{ 925000000, 10000, false, 69, 2, 46, 2, 36, 0, 0, 0 },
{ 927000000, 10000, false, 69, 2, 50, 2, 36, 0, 0, 0 },
{ 906000000, 10000, false, 70, 3, 8, 4, 36, 0, 0, 0 },
{ 910000000, 10000, false, 70, 3, 16, 4, 36, 0, 0, 0 },
{ 914000000, 10000, false, 70, 3, 24, 4, 36, 0, 0, 0 },
{ 918000000, 10000, false, 70, 3, 32, 4, 36, 0, 0, 0 },
{ 922000000, 10000, false, 70, 3, 40, 4, 36, 0, 0, 0 },
{ 926000000, 10000, false, 70, 3, 48, 4, 36, 0, 0, 0 },
{ 908000000, 10000, false, 71, 4, 12, 8, 36, 0, 0, 0 },
{ 916000000, 10000, false, 71, 4, 28, 8, 36, 0, 0, 0 },
{ 924000000, 10000, false, 71, 4, 44, 8, 36, 0, 0, 0 },
};
/** Channel list structure for USA. */
static const struct mmwlan_s1g_channel_list s1g_channel_list_US = {
.country_code = "US",
.num_channels = (sizeof(s1g_channels_US)/sizeof(s1g_channels_US[0])),
.channels = s1g_channels_US,
};
/** Array of all channel list structs used for the regulatory database. */
static const struct mmwlan_s1g_channel_list *regulatory_db_domains[] = {
&s1g_channel_list_AU,
&s1g_channel_list_EU,
&s1g_channel_list_IN,
&s1g_channel_list_JP,
&s1g_channel_list_KR,
&s1g_channel_list_NZ,
&s1g_channel_list_SG,
&s1g_channel_list_US,
};
/** Regulatory database. */
static const struct mmwlan_regulatory_db regulatory_db = {
.num_domains = (sizeof(regulatory_db_domains)/sizeof(regulatory_db_domains[0])),
.domains = regulatory_db_domains,
};
/**
* Get a pointer to regulatory_db. This function isn't strictly necessary, since regulatory_db
* can be accessed directly, but will prevent the compiler from generated warnings about
* regulatory_db being unused.
*
* @return Reference to the regulatory database
*/
static inline const struct mmwlan_regulatory_db *get_regulatory_db(void)
{
return &regulatory_db;
}
/** \} */
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
{
"name": "MorseWlan",
"version": "0.1.0",
"description": "Morse Micro mm-iot-esp32 SDK vendored for Meshtastic HaLow transport. Static morselib (Apache-2.0) + open-source shims.",
"frameworks": ["arduino", "espidf"],
"platforms": ["espressif32"],
"build": {
"srcDir": "src",
"srcFilter": ["+<*.c>"],
"includeDir": "include",
"libArchive": false,
"extraScript": "extra_script.py"
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+148
View File
@@ -0,0 +1,148 @@
/*
* Copyright 2022-2024 Morse Micro
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#pragma once
#include <stdint.h>
/*
* ----
* Endianness operations
* ----
*/
#ifdef __big_endian__
#define __BYTE_ORDER __BIG_ENDIAN
#else
#define __BYTE_ORDER __LITTLE_ENDIAN
#endif
#define bswap_16(x) __builtin_bswap16(x)
#define bswap_32(x) __builtin_bswap32(x)
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
/* Protocol families. */
#define PF_INET 2 /* IP protocol family. */
#define PF_INET6 10 /* IP version 6. */
/* Address families. */
#define AF_INET PF_INET
#define AF_INET6 PF_INET6
/*
* ----
* Type definitions
* ----
*/
typedef signed char __s8;
typedef unsigned char __u8;
typedef signed short __s16;
typedef unsigned short __u16;
typedef signed int __s32;
typedef unsigned int __u32;
struct in_addr {
__u32 s_addr;
};
struct in6_addr {
union {
__u32 u32_addr[4];
__u8 u8_addr[16];
} un;
#define s6_addr un.u8_addr
};
/* Stub function declaration for inet_ntop which is called from write_ipv4_info
* function in robust_av.c. Since they are not used we will create a dummy
* function declarion here.
*/
const char *inet_ntop(int __af, const void *__cp, char *__buf, int __len);
/* Rename crypto functions to match symbol name mangling in morselib for avoidance of namespace
* collisions. */
#define aes_decrypt mmint_aes_decrypt
#define aes_decrypt_deinit mmint_aes_decrypt_deinit
#define aes_decrypt_init mmint_aes_decrypt_init
#define crypto_bignum_add mmint_crypto_bignum_add
#define crypto_bignum_addmod mmint_crypto_bignum_addmod
#define crypto_bignum_cmp mmint_crypto_bignum_cmp
#define crypto_bignum_deinit mmint_crypto_bignum_deinit
#define crypto_bignum_div mmint_crypto_bignum_div
#define crypto_bignum_exptmod mmint_crypto_bignum_exptmod
#define crypto_bignum_init mmint_crypto_bignum_init
#define crypto_bignum_init_set mmint_crypto_bignum_init_set
#define crypto_bignum_init_uint mmint_crypto_bignum_init_uint
#define crypto_bignum_inverse mmint_crypto_bignum_inverse
#define crypto_bignum_is_odd mmint_crypto_bignum_is_odd
#define crypto_bignum_is_one mmint_crypto_bignum_is_one
#define crypto_bignum_is_zero mmint_crypto_bignum_is_zero
#define crypto_bignum_legendre mmint_crypto_bignum_legendre
#define crypto_bignum_mod mmint_crypto_bignum_mod
#define crypto_bignum_mulmod mmint_crypto_bignum_mulmod
#define crypto_bignum_rand mmint_crypto_bignum_rand
#define crypto_bignum_rshift mmint_crypto_bignum_rshift
#define crypto_bignum_sqrmod mmint_crypto_bignum_sqrmod
#define crypto_bignum_sub mmint_crypto_bignum_sub
#define crypto_bignum_to_bin mmint_crypto_bignum_to_bin
#define crypto_ec_deinit mmint_crypto_ec_deinit
#define crypto_ec_get_a mmint_crypto_ec_get_a
#define crypto_ec_get_b mmint_crypto_ec_get_b
#define crypto_ec_get_generator mmint_crypto_ec_get_generator
#define crypto_ec_get_order mmint_crypto_ec_get_order
#define crypto_ec_get_prime mmint_crypto_ec_get_prime
#define crypto_ec_init mmint_crypto_ec_init
#define crypto_ec_order_len mmint_crypto_ec_order_len
#define crypto_ec_point_add mmint_crypto_ec_point_add
#define crypto_ec_point_cmp mmint_crypto_ec_point_cmp
#define crypto_ec_point_x mmint_crypto_ec_point_x
#define crypto_ec_point_compute_y_sqr mmint_crypto_ec_point_compute_y_sqr
#define crypto_ec_point_deinit mmint_crypto_ec_point_deinit
#define crypto_ec_point_from_bin mmint_crypto_ec_point_from_bin
#define crypto_ec_point_init mmint_crypto_ec_point_init
#define crypto_ec_point_invert mmint_crypto_ec_point_invert
#define crypto_ec_point_is_at_infinity mmint_crypto_ec_point_is_at_infinity
#define crypto_ec_point_is_on_curve mmint_crypto_ec_point_is_on_curve
#define crypto_ec_point_mul mmint_crypto_ec_point_mul
#define crypto_ec_point_to_bin mmint_crypto_ec_point_to_bin
#define crypto_ec_prime_len mmint_crypto_ec_prime_len
#define crypto_ec_prime_len_bits mmint_crypto_ec_prime_len_bits
#define crypto_ecdh_deinit mmint_crypto_ecdh_deinit
#define crypto_ecdh_get_pubkey mmint_crypto_ecdh_get_pubkey
#define crypto_ecdh_init mmint_crypto_ecdh_init
#define crypto_ecdh_init2 mmint_crypto_ecdh_init2
#define crypto_ecdh_set_peerkey mmint_crypto_ecdh_set_peerkey
#define crypto_ecdh_prime_len mmint_crypto_ecdh_prime_len
#define crypto_get_random mmint_crypto_get_random
#define crypto_unload mmint_crypto_unload
#define hmac_md5 mmint_hmac_md5
#define hmac_sha1 mmint_hmac_sha1
#define hmac_sha1_vector mmint_hmac_sha1_vector
#define hmac_sha256 mmint_hmac_sha256
#define hmac_sha256_vector mmint_hmac_sha256_vector
#define hmac_sha384 mmint_hmac_sha384
#define hmac_sha384_vector mmint_hmac_sha384_vector
#define hmac_sha512 mmint_hmac_sha512
#define hmac_sha512_vector mmint_hmac_sha512_vector
#define omac1_aes_vector mmint_omac1_aes_vector
#define omac1_aes_128 mmint_omac1_aes_128
#define pbkdf2_sha1 mmint_pbkdf2_sha1
#define sha1_prf mmint_sha1_prf
#define sha1_vector mmint_sha1_vector
#define sha256_prf mmint_sha256_prf
#define sha256_prf_bits mmint_sha256_prf_bits
#define sha256_vector mmint_sha256_vector
#define sha384_prf mmint_sha384_prf
#define sha384_vector mmint_sha384_vector
#define sha512_prf mmint_sha512_prf
#define sha512_vector mmint_sha512_vector
#define wpabuf_alloc mmint_wpabuf_alloc
#define wpabuf_alloc_copy mmint_wpabuf_alloc_copy
#define wpabuf_clear_free mmint_wpabuf_clear_free
#define wpabuf_put mmint_wpabuf_put
+27
View File
@@ -0,0 +1,27 @@
#ifdef USE_MM_IOT_ESP32
/*
* Stubs for LWIP netif callback API that Arduino-ESP32's prebuilt LWIP omits
* (CONFIG_LWIP_NETIF_STATUS_CALLBACK / _LINK_CALLBACK are disabled in its
* sdkconfig, so the real functions are absent from the static library).
*
* mmipal calls these once during init to register a single callback for
* link-up / IP-configured events. With these stubs, the callback never fires —
* mmwlan_register_link_state_cb (driven by the radio firmware) remains the
* authoritative source of link state, so this only costs us LWIP-level
* notifications (e.g. "DHCP got an address"). HaLowInterface polls
* mmipal_get_ip_config when it needs to know.
*/
#include "lwip/netif.h"
void __attribute__((weak)) netif_set_link_callback(struct netif *netif, netif_status_callback_fn link_callback)
{
(void)netif;
(void)link_callback;
}
void __attribute__((weak)) netif_set_status_callback(struct netif *netif, netif_status_callback_fn status_callback)
{
(void)netif;
(void)status_callback;
}
#endif
Binary file not shown.
+216
View File
@@ -0,0 +1,216 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include "mmbuf.h"
#include "mmosal.h"
#include "mmutils.h"
static const struct mmbuf_ops mmbuf_heap_ops = {.free_mmbuf = mmosal_free};
struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end)
{
struct mmbuf *mmbuf;
uint8_t *buf;
uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*mmbuf), 4) + MM_FAST_ROUND_UP(space_at_start + space_at_end, 4);
mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len);
if (mmbuf == NULL) {
return NULL;
}
/* We zero the buffer as a defensive measure to reduce the likelihood of unintentionally
* leaking information. */
memset((uint8_t *)mmbuf, 0, alloc_len);
buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4);
mmbuf_init(mmbuf, buf, MM_FAST_ROUND_UP(space_at_start + space_at_end, 4), space_at_start, &mmbuf_heap_ops);
return mmbuf;
}
struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original)
{
struct mmbuf *mmbuf;
uint8_t *buf;
uint32_t alloc_len = MM_FAST_ROUND_UP(sizeof(*original), 4) + original->buf_len;
mmbuf = (struct mmbuf *)mmosal_malloc(alloc_len);
if (mmbuf == NULL) {
return NULL;
}
buf = ((uint8_t *)mmbuf) + MM_FAST_ROUND_UP(sizeof(*mmbuf), 4);
mmbuf_init(mmbuf, buf, original->buf_len, original->start_offset, &mmbuf_heap_ops);
mmbuf->data_len = original->data_len;
if (original->data_len) {
memcpy(mmbuf_get_data_start(mmbuf), mmbuf_get_data_start(original), mmbuf_get_data_length(original));
}
return mmbuf;
}
void mmbuf_release(struct mmbuf *mmbuf)
{
if (mmbuf == NULL) {
return;
}
MMOSAL_ASSERT(mmbuf->ops != NULL && mmbuf->ops->free_mmbuf != NULL);
mmbuf->ops->free_mmbuf(mmbuf);
}
#ifdef MMBUF_SANITY
static void mmbuf_list_sanity_check(struct mmbuf_list *list)
{
unsigned cnt = 0;
struct mmbuf *walk;
struct mmbuf *prev = NULL;
for (walk = list->head; walk != NULL; walk = walk->next) {
cnt++;
prev = walk;
}
MMOSAL_ASSERT(cnt == list->len);
MMOSAL_ASSERT(prev == list->tail);
}
#endif
void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
mmbuf->next = list->head;
list->head = mmbuf;
list->len++;
if (list->tail == NULL) {
list->tail = list->head;
}
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
}
void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
mmbuf->next = NULL;
if (list->head == NULL) {
list->head = mmbuf;
list->tail = mmbuf;
} else {
list->tail->next = mmbuf;
list->tail = mmbuf;
}
list->len++;
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
}
static struct mmbuf *mmbuf_find_prev(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
struct mmbuf *walk, *next;
for (walk = list->head, next = walk->next; next != NULL; walk = next, next = walk->next) {
if (next == mmbuf) {
return walk;
}
}
return NULL;
}
bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf)
{
struct mmbuf *prev = NULL;
if (list->head == NULL) {
return false;
}
if (list->head == mmbuf) {
list->head = mmbuf->next;
} else {
prev = mmbuf_find_prev(list, mmbuf);
if (prev == NULL) {
return false;
}
prev->next = mmbuf->next;
}
if (list->tail == mmbuf) {
list->tail = prev;
}
list->len--;
mmbuf->next = NULL;
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
return true;
}
struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list)
{
if (list->head == NULL) {
return NULL;
} else {
struct mmbuf *mmbuf = list->head;
list->head = mmbuf->next;
list->len--;
if (list->tail == mmbuf) {
list->tail = NULL;
}
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
if (mmbuf != NULL) {
mmbuf->next = NULL;
}
return mmbuf;
}
}
struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list)
{
if (list->tail == NULL) {
return NULL;
}
struct mmbuf *mmbuf = list->tail;
mmbuf_list_remove(list, mmbuf);
return mmbuf;
}
void mmbuf_list_clear(struct mmbuf_list *list)
{
struct mmbuf *walk;
struct mmbuf *next;
#ifdef MMBUF_SANITY
mmbuf_list_sanity_check(list);
#endif
if (list->head == NULL) {
return;
}
for (walk = list->head, next = walk->next; walk != NULL; walk = next, next = walk ? walk->next : NULL) {
mmbuf_release(walk);
}
list->len = 0;
list->head = NULL;
list->tail = NULL;
}
#endif /* USE_MM_IOT_ESP32 */
+457
View File
@@ -0,0 +1,457 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMBUF Morse Micro Buffer (mmbuf) API
*
* This API provides support for buffers tailored towards packet-like data that has
* headers and trailers that are applied at subsequent layers.
*
* It is designed to support various backends for memory allocation. The default
* is allocation on the heap, but other methods could be used due to the flexible API.
*
* @{
*/
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "mmosal.h"
#ifdef __cplusplus
extern "C" {
#endif
struct mmbuf_ops;
/**
* Core mmbuf data structure.
*
* @note The contents of this data structure should never need to be accessed directly. Rather
* the various functions provided as part of this API should be used.
*
* @code
* +----------------------------------------------------------+
* | RESERVED | Data | RESERVED |
* +----------------------------------------------------------+
* ^ ^ ^ ^
* | | | |
* | |<-----------data_len--------->| |
* | start_offset |
* | |
* |<-----------------------buf_len-------------------------->|
* buf
* @endcode
*/
struct mmbuf {
/** The buffer where data is stored. */
uint8_t *buf;
/** Length of the buffer. */
uint32_t buf_len;
/** Offset where actual data starts in the buffer. */
uint32_t start_offset;
/** Length of actual data in the buffer. */
uint32_t data_len;
/** Reference to operations data structure for this mmbuf. */
const struct mmbuf_ops *ops;
/** Pointer that can be used to construct linked lists. */
struct mmbuf *volatile next;
};
/** Operations data structure for mmbuf. */
struct mmbuf_ops {
/** Free the given mmbuf. */
void (*free_mmbuf)(void *mmbuf);
};
/**
* Initialize an mmbuf header with the given values.
*
* @param mmbuf mmbuf to initialize.
* @param buf Pointer to buffer.
* @param buf_len Length of @p buf.
* @param data_start_offset Initial value for @c start_offset.
* @param ops Operations data structure.
*/
static inline void mmbuf_init(struct mmbuf *mmbuf, uint8_t *buf, uint32_t buf_len, uint32_t data_start_offset,
const struct mmbuf_ops *ops)
{
memset(mmbuf, 0, sizeof(*mmbuf));
mmbuf->buf = buf;
mmbuf->buf_len = buf_len;
mmbuf->start_offset = data_start_offset;
mmbuf->ops = ops;
}
/**
* Allocate a new mmbuf on the heap (using @ref mmosal_malloc()).
*
* @param space_at_start Amount of space to reserve at start of buffer.
* @param space_at_end Amount of space to reserve at end of buffer.
*
* @note @c start_offset will be set to @p space_at_start, and @c buf_len will be the sum
* of @p space_at_start and @p space_at_end (rounded up to a multiple of 4).
*
* @returns newly allocated mmbuf on success or @c NULL on failure.
*/
struct mmbuf *mmbuf_alloc_on_heap(uint32_t space_at_start, uint32_t space_at_end);
/**
* Make a copy of the given mmbuf. Note that regardless of the backend that allocated
* @p original, the newly allocated mmbuf will be allocated on the heap using
* @ref mmbuf_alloc_on_heap().
*
* @param original mmbuf to copy.
*
* @returns newly allocated mmbuf on success or @c NULL on failure.
*/
struct mmbuf *mmbuf_make_copy_on_heap(struct mmbuf *original);
/**
* Release a reference to the given mmbuf. If this was the last reference (@c addition_ref_cnt
* was 0) then the mmbuf will be freed using the appropriate op callback.
*
* @param mmbuf The mmbuf to release reference to. May be @c NULL.
*/
void mmbuf_release(struct mmbuf *mmbuf);
/**
* Gets a pointer to the start of the data in the mmbuf.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns a pointer to the start of the data in the mmbuf.
*/
static inline uint8_t *mmbuf_get_data_start(struct mmbuf *mmbuf)
{
return mmbuf->buf + mmbuf->start_offset;
}
/**
* Gets a pointer to the end of the data in the mmbuf.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns a pointer to the end of the data in the mmbuf.
*/
static inline uint8_t *mmbuf_get_data_end(struct mmbuf *mmbuf)
{
return mmbuf->buf + mmbuf->start_offset + mmbuf->data_len;
}
/**
* Gets the length of the data currently in the mmbuf.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns the length of the data currently in the mmbuf (note that this is different from the
* length of the available buffer space).
*/
static inline uint32_t mmbuf_get_data_length(struct mmbuf *mmbuf)
{
return mmbuf->data_len;
}
/**
* Returns the amount of space available for prepending to the data in the buffer.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmbuf_available_space_at_start(struct mmbuf *mmbuf)
{
return mmbuf->start_offset;
}
/**
* Returns the amount of space available for appending to the data in the buffer.
*
* @param mmbuf The mmbuf to operate on.
*
* @returns the available space in bytes.
*/
static inline uint32_t mmbuf_available_space_at_end(struct mmbuf *mmbuf)
{
return mmbuf->buf_len - (mmbuf->start_offset + mmbuf->data_len);
}
/**
* Reserves space immediately before the data currently in the given mmbuf and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmbuf_prepend_data().
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
*
* @param mmbuf The mmbuf to operate on.
* @param len Length of data to be prepended.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmbuf_prepend(struct mmbuf *mmbuf, uint32_t len)
{
MMOSAL_ASSERT(len <= mmbuf_available_space_at_start(mmbuf));
mmbuf->start_offset -= len;
mmbuf->data_len += len;
return mmbuf->buf + mmbuf->start_offset;
}
/**
* Prepends the given data to the data already in the mmbuf.
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
*
* @warning The memory area pointed to by data must not overlap with the mmbuf data.
*
* @param mmbuf The mmbuf to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmbuf_prepend_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmbuf_prepend(mmbuf, len);
memcpy(dest, data, len);
}
/**
* Reserves space immediately after the data currently in the given mmbuf and returns
* a pointer to this space.
*
* For a function that also copies data in, see @ref mmbuf_append_data().
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_end().
*
* @param mmbuf The mmbuf to operate on.
* @param len Length of data to be append.
*
* @returns a pointer to the place in the buffer where the data should be put.
*/
static inline uint8_t *mmbuf_append(struct mmbuf *mmbuf, uint32_t len)
{
uint8_t *ret = mmbuf_get_data_end(mmbuf);
MMOSAL_ASSERT(len <= mmbuf_available_space_at_end(mmbuf));
mmbuf->data_len += len;
return ret;
}
/**
* Appends the given data to the data already in the mmbuf.
*
* @warning @p len must be less than or equal to @ref mmbuf_available_space_at_start().
*
* @param mmbuf The mmbuf to operate on.
* @param data The data to be prepended.
* @param len Length of data to be prepended.
*/
static inline void mmbuf_append_data(struct mmbuf *mmbuf, const uint8_t *data, uint32_t len)
{
uint8_t *dest = mmbuf_append(mmbuf, len);
memcpy(dest, data, len);
}
/**
* Remove data from the start of the mmbuf.
*
* @param mmbuf mmbuf to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len.
*/
static inline uint8_t *mmbuf_remove_from_start(struct mmbuf *mmbuf, uint32_t len)
{
uint8_t *ret;
if (mmbuf_get_data_length(mmbuf) < len) {
return NULL;
}
ret = mmbuf_get_data_start(mmbuf);
mmbuf->start_offset += len;
mmbuf->data_len -= len;
return ret;
}
/**
* Remove data from the end of the mmbuf.
*
* @param mmbuf mmbuf to operate on.
* @param len Length of data to remove.
*
* @returns a pointer to the removed data or NULL if the mmbuf data length was less than @p len.
*/
static inline uint8_t *mmbuf_remove_from_end(struct mmbuf *mmbuf, uint32_t len)
{
uint8_t *ret;
if (mmbuf_get_data_length(mmbuf) < len) {
return NULL;
}
ret = mmbuf_get_data_end(mmbuf) - len;
mmbuf->data_len -= len;
return ret;
}
/**
* Truncate the mmbuf data to the given length.
*
* @param mmbuf mmbuf to operate on.
* @param len New data length. (Must be less than or equal to the data length
* of the mmbuf).
*/
static inline void mmbuf_truncate(struct mmbuf *mmbuf, uint32_t len)
{
MMOSAL_ASSERT(len <= mmbuf->data_len);
mmbuf->data_len = len;
}
/* --------------------------------------------------------------------------------------------- */
/** Structure that can be used as the head of a linked list of mmbufs that counts its length. */
struct mmbuf_list {
/** First mmbuf in the list. */
struct mmbuf *volatile head;
/** Last mmbuf in the list. */
struct mmbuf *volatile tail;
/** Length of the list. */
volatile uint32_t len;
};
/** Static initializer for @ref mmbuf_list. */
#define MMBUF_LIST_INIT \
{ \
NULL, NULL, 0 \
}
/**
* Initialization function for @ref mmbuf_list, for cases where @c MMBUF_LIST_INIT
* cannot be used.
*
* @param list The mmbuf_list to init.
*/
static inline void mmbuf_list_init(struct mmbuf_list *list)
{
list->head = NULL;
list->tail = NULL;
list->len = 0;
}
/**
* Add an mmbuf to the start of an mmbuf list.
*
* @param list The list to prepend to.
* @param mmbuf The mmbuf to prepend.
*/
void mmbuf_list_prepend(struct mmbuf_list *list, struct mmbuf *mmbuf);
/**
* Add an mmbuf to the end of an mmbuf list.
*
* @param list The list to append to.
* @param mmbuf The mmbuf to append.
*/
void mmbuf_list_append(struct mmbuf_list *list, struct mmbuf *mmbuf);
/**
* Remove an mmbuf from an mmbuf list.
*
* @param list The list to remove from.
* @param mmbuf The mmbuf to remove.
*
* @returns @c true if the given @c mmbuf was present in @c list, else @c fase.
*/
bool mmbuf_list_remove(struct mmbuf_list *list, struct mmbuf *mmbuf);
/**
* Remove the mmbuf at the head of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmbuf, or @c NULL if the list is empty.
*/
struct mmbuf *mmbuf_list_dequeue(struct mmbuf_list *list);
/**
* Remove the mmbuf at the tail of the list and return it.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmbuf, or @c NULL if the list is empty.
*/
struct mmbuf *mmbuf_list_dequeue_tail(struct mmbuf_list *list);
/**
* Remove all mmbufs from the list and return as a linked list.
*
* @param list The list to dequeue from.
*
* @returns the dequeued mmbufs, or @c NULL if the list is empty.
*/
static inline struct mmbuf *mmbuf_list_dequeue_all(struct mmbuf_list *list)
{
struct mmbuf *head = list->head;
list->head = NULL;
list->tail = NULL;
list->len = 0;
return head;
}
/**
* Checks whether the given mmbuf list is empty.
*
* @param list The list to check.
*
* @returns @c true if the list is empty, else @c false.
*/
static inline bool mmbuf_list_is_empty(struct mmbuf_list *list)
{
return (list->head == NULL);
}
/**
* Returns the head of the mmbuf list.
*
* @param list The list to peek into.
*
* @returns the mmbuf at the head of the list.
*/
static inline struct mmbuf *mmbuf_list_peek(struct mmbuf_list *list)
{
return list->head;
}
/**
* Returns the tail of the mmbuf list.
*
* @param list The list to peek into.
*
* @returns the mmbuf at the tail of the list.
*/
static inline struct mmbuf *mmbuf_list_peek_tail(struct mmbuf_list *list)
{
return list->tail;
}
/**
* Free all the packets in the given list and reset the list to empty state.
*
* @param list The list to clear.
*/
void mmbuf_list_clear(struct mmbuf_list *list);
#ifdef __cplusplus
}
#endif
/** @} */
+50
View File
@@ -0,0 +1,50 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmcrc.h"
/**
* Static table used for the table_driven implementation.
*/
static const uint16_t crc16_xmodem_lookup_table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce,
0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c,
0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee,
0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738,
0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e,
0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc,
0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae,
0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e,
0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c,
0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e,
0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8,
0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e,
0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c,
0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e,
0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1,
0x1ef0};
/**
* @note This implementation(with a few modifications) and corresponding table was generated using
* pycrc v0.9.2 (MIT) using the XMODEM model. https://pycrc.org/. The code generated by pycrc
* is not considered a substantial portion of the software, therefore the licence does not
* cover the generated code, and the author of pycrc will not claim any copyright on the
* generated code (https://pypi.org/project/pycrc/0.9.2/).
*/
uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len)
{
const uint8_t *d = (const uint8_t *)data;
while (data_len--) {
crc = (crc16_xmodem_lookup_table[((crc >> 8) ^ *d++)] ^ (crc << 8));
}
return crc;
}
#endif /* USE_MM_IOT_ESP32 */
+40
View File
@@ -0,0 +1,40 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMCRC Morse Micro Cyclic Redundancy Check (mmcrc) API
*
* This API provides support for CRC algorithms used by Morse Micro code.
*
* @{
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Compute the CRC-16 for the data buffer using the XMODEM model.
*
* @param crc Seed for CRC calc, zero in most cases this is zero (0). If chaining calls
* then this is the output from the previous invocation.
* @param data Pointer to the start of the data to calculate the crc over.
* @param data_len Length of the data array in bytes.
*
* @return Returns the CRC value.
*/
uint16_t mmcrc_16_xmodem(uint16_t crc, const void *data, size_t data_len);
#ifdef __cplusplus
}
#endif
/** @} */
+100
View File
@@ -0,0 +1,100 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmhal.h"
#include "mmosal.h"
#include "mmutils.h"
#include "mmwlan.h"
#include "driver/gpio.h"
#include "esp_random.h"
#include "esp_system.h"
#include "sdkconfig.h"
void mmhal_init(void)
{
/* We initialise the MM_RESET_N Pin here so that we can hold the MM6108 in reset regardless of
* whether the mmhal_wlan_init/deinit function have been called. This allows us to ensure the
* chip is in its lowest power state. You may want to revise this depending on your particular
* hardware configuration. */
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = (1 << CONFIG_MM_RESET_N);
io_conf.pull_down_en = 0;
io_conf.pull_up_en = 0;
gpio_config(&io_conf);
gpio_set_level(CONFIG_MM_RESET_N, 0);
/* Initialise the gpio ISR handler service. This allows per-pin GPIO interrupt handlers and is
* what is used to register all the wlan related interrupt. */
gpio_install_isr_service(0);
}
void mmhal_log_write(const uint8_t *data, size_t length)
{
while (length--) {
putc(*data++, stdout);
}
}
void mmhal_log_flush(void) {}
void mmhal_read_mac_addr(uint8_t *mac_addr)
{
/* We do not override the MAC address here. Therefore the driver will attempt to read it from
* the chip and failing that will assign a randomly generated address. */
(void)(mac_addr);
}
uint32_t mmhal_random_u32(uint32_t min, uint32_t max)
{
/* Note: the below implementation does not guarantee a uniform distribution. */
uint32_t random_value = esp_random();
if (min == 0 && max == UINT32_MAX) {
return random_value;
} else {
/* Calculate the range and shift required to fit within [min, max] */
return (random_value % (max - min + 1)) + min;
}
}
void mmhal_reset(void)
{
esp_restart();
while (1) {
}
}
void mmhal_set_deep_sleep_veto(uint8_t veto_id)
{
MM_UNUSED(veto_id);
}
void mmhal_clear_deep_sleep_veto(uint8_t veto_id)
{
MM_UNUSED(veto_id);
}
void mmhal_set_led(uint8_t led, uint8_t level)
{
MM_UNUSED(led);
MM_UNUSED(level);
}
bool mmhal_get_hardware_version(char *version_buffer, size_t version_buffer_length)
{
/* Note: You need to identify the correct hardware and or version
* here using whatever means available (GPIO's, version number stored in EEPROM, etc)
* and return the correct string here. */
return !mmosal_safer_strcpy(version_buffer, "MM-ESP32S3 V1.0", version_buffer_length);
}
#endif /* USE_MM_IOT_ESP32 */
+72
View File
@@ -0,0 +1,72 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmhal_wlan.h"
#include "mmosal.h"
/*
* ---------------------------------------------------------------------------------------------
* BCF Retrieval
* ---------------------------------------------------------------------------------------------
*/
/*
* The following implementation reads the BCF File from the config store.
*/
void mmhal_wlan_read_bcf_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf)
{
/** Points to the start of the BCF binary image. Defined as part of the Makefile */
extern uint8_t bcf_binary_start;
/** Points to the end of the BCF binary image. Defined as part of the Makefile */
extern uint8_t bcf_binary_end;
size_t bcf_len = &bcf_binary_end - &bcf_binary_start;
/* Initialise robuf */
robuf->buf = NULL;
robuf->len = 0;
robuf->free_arg = NULL;
robuf->free_cb = NULL;
/* Sanity check */
if (bcf_len < offset) {
printf("Detected an attempt to start reading off the end of the bcf file.\n");
return;
}
robuf->buf = (uint8_t *)&bcf_binary_start + offset;
robuf->len = bcf_len - offset;
robuf->len = (robuf->len < requested_len) ? robuf->len : requested_len;
}
/*
* ---------------------------------------------------------------------------------------------
* Firmware Retrieval
* ---------------------------------------------------------------------------------------------
*/
/** Points to the start of the firmware binary image. Defined as part of the Makefile */
extern uint8_t firmware_binary_start;
/** Points to the end of the firmware binary image. Defined as part of the Makefile */
extern uint8_t firmware_binary_end;
void mmhal_wlan_read_fw_file(uint32_t offset, uint32_t requested_len, struct mmhal_robuf *robuf)
{
uint32_t firmware_len = &firmware_binary_end - &firmware_binary_start;
if (offset > firmware_len) {
printf("Detected an attempt to start read off the end of the firmware file.\n");
robuf->buf = NULL;
return;
}
robuf->buf = (&firmware_binary_start + offset);
firmware_len -= offset;
robuf->len = (firmware_len < requested_len) ? firmware_len : requested_len;
}
#endif /* USE_MM_IOT_ESP32 */
+726
View File
@@ -0,0 +1,726 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmipal.h"
#include "mmnetif.h"
#include "mmosal.h"
#include "mmutils.h"
#include "mmwlan.h"
#include "lwip/api.h"
#include "lwip/autoip.h"
#include "lwip/def.h"
#include "lwip/dhcp.h"
#include "lwip/dhcp6.h"
#include "lwip/dns.h"
#include "lwip/etharp.h"
#include "lwip/ethip6.h"
#include "lwip/igmp.h"
#include "lwip/inet.h"
#include "lwip/ip4_frag.h"
#include "lwip/ip6_frag.h"
#include "lwip/ip_addr.h"
#include "lwip/mem.h"
#include "lwip/sockets.h"
#include "lwip/stats.h"
#include "lwip/tcp.h"
#include "lwip/tcpip.h"
#include "lwip/udp.h"
static struct mmipal_data {
struct netif lwip_mmnetif;
/** This stores the IPv4 link state for the IP stack. I.e., do we have an IP address or not. */
enum mmipal_link_state ip_link_state;
/** Flag requesting ARP response offload feature */
bool offload_arp_response;
/** ARP refresh offload interval in seconds */
uint32_t offload_arp_refresh_s;
bool dhcp_offload_init_complete;
/** The link status callback function that has been registered. */
mmipal_link_status_cb_fn_t link_status_callback;
/** The extended link status callback function that has been registered. */
mmipal_ext_link_status_cb_fn_t ext_link_status_callback;
/** Argument for the extended link status callback function that has been registered. */
void *ext_link_status_callback_arg;
#if LWIP_IPV4
enum mmipal_addr_mode ip4_mode;
#endif
#if LWIP_IPV6
enum mmipal_ip6_addr_mode ip6_mode;
#endif
} mmipal_data = {};
/** Getter function to retrieve the global mmipal data structure.*/
static inline struct mmipal_data *mmipal_get_data(void)
{
return &mmipal_data;
}
static void netif_status_callback(struct netif *netif);
#if LWIP_IPV4
/**
* DHCP Lease update callback, invoked when we get a new DHCP lease.
*
* @param lease_info The new DHCP lease.
*/
static void mmipal_dhcp_lease_updated(const struct mmwlan_dhcp_lease_info *lease_info, void *arg)
{
struct mmipal_data *data = mmipal_get_data();
ip4_addr_t ip_addr, netmask, gateway;
ip_addr_t dns_addr = ip_addr_any;
MM_UNUSED(arg);
data->dhcp_offload_init_complete = true;
ip4_addr_set_u32(&ip_addr, lease_info->ip4_addr);
ip4_addr_set_u32(&netmask, lease_info->mask4_addr);
ip4_addr_set_u32(&gateway, lease_info->gw4_addr);
ip4_addr_set_u32(ip_2_ip4(&dns_addr), lease_info->dns4_addr);
LOCK_TCPIP_CORE();
netif_set_addr(&data->lwip_mmnetif, &ip_addr, &netmask, &gateway);
dns_setserver(0, &dns_addr);
UNLOCK_TCPIP_CORE();
netif_status_callback(&data->lwip_mmnetif);
}
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config)
{
struct mmipal_data *data = mmipal_get_data();
char *result;
config->mode = data->ip4_mode;
result = ipaddr_ntoa_r(&data->lwip_mmnetif.ip_addr, config->ip_addr, sizeof(config->ip_addr));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&data->lwip_mmnetif.netmask, config->netmask, sizeof(config->netmask));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&data->lwip_mmnetif.gw, config->gateway_addr, sizeof(config->gateway_addr));
LWIP_ASSERT("IP buf too short", result != NULL);
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config)
{
struct mmipal_data *data = mmipal_get_data();
int result;
ip_addr_t ip_addr = ip_addr_any;
ip_addr_t netmask = ip_addr_any;
ip_addr_t gateway = ip_addr_any;
struct netif *netif = &data->lwip_mmnetif;
if (config->mode != MMIPAL_DHCP_OFFLOAD && data->ip4_mode == MMIPAL_DHCP_OFFLOAD) {
printf("Once enabled DHCP offload mode cannot be disabled\n");
return MMIPAL_NOT_SUPPORTED;
}
switch (config->mode) {
case MMIPAL_DISABLED:
printf("%s mode not supported\n", "DISABLED");
return MMIPAL_INVALID_ARGUMENT;
case MMIPAL_AUTOIP:
printf("%s mode not supported\n", "AutoIP");
return MMIPAL_INVALID_ARGUMENT;
case MMIPAL_DHCP_OFFLOAD:
/* Currently we only support enabling DHCP offload when initialising */
printf("%s mode not supported\n", "DHCP_OFFLOAD");
return MMIPAL_INVALID_ARGUMENT;
case MMIPAL_STATIC:
result = ipaddr_aton(config->ip_addr, &ip_addr);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
result = ipaddr_aton(config->netmask, &netmask);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
result = ipaddr_aton(config->gateway_addr, &gateway);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
break;
case MMIPAL_DHCP:
break;
}
LOCK_TCPIP_CORE();
if (config->mode != MMIPAL_DHCP && data->ip4_mode == MMIPAL_DHCP) {
/* Stop DHCP if it was started earlier before setting static IP */
dhcp_stop(netif);
}
data->ip4_mode = config->mode;
netif_set_addr(netif, ip_2_ip4(&ip_addr), ip_2_ip4(&netmask), ip_2_ip4(&gateway));
if (data->ip4_mode == MMIPAL_DHCP) {
result = dhcp_start(netif);
LWIP_ASSERT("DHCP start error", result == ERR_OK);
}
UNLOCK_TCPIP_CORE();
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr)
{
struct mmipal_data *data = mmipal_get_data();
char *result;
uint32_t ip_addr = ip_addr_get_ip4_u32(&data->lwip_mmnetif.ip_addr);
uint32_t netmask = ip_addr_get_ip4_u32(&data->lwip_mmnetif.netmask);
uint32_t broadcast_u32 = (ip_addr & netmask) | (0xffffffff & ~netmask);
ip_addr_t broadcast_ip_addr;
ip_addr_t *_broadcast_ip_addr = &broadcast_ip_addr;
ip_addr_set_ip4_u32(_broadcast_ip_addr, broadcast_u32);
result = ipaddr_ntoa_r(&broadcast_ip_addr, broadcast_addr, MMIPAL_IPADDR_STR_MAXLEN);
LWIP_ASSERT("IP buf too short", result != NULL);
return MMIPAL_SUCCESS;
}
#else
enum mmipal_status mmipal_get_ip_config(struct mmipal_ip_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv4 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
enum mmipal_status mmipal_set_ip_config(const struct mmipal_ip_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv4 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
enum mmipal_status mmipal_get_ip_broadcast_addr(mmipal_ip_addr_t broadcast_addr)
{
MM_UNUSED(broadcast_addr);
LWIP_ASSERT("IPv4 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
#endif
#if LWIP_IPV6
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config)
{
struct mmipal_data *data = mmipal_get_data();
unsigned ii;
struct netif *netif = &data->lwip_mmnetif;
if (config == NULL) {
return MMIPAL_INVALID_ARGUMENT;
}
config->ip6_mode = data->ip6_mode;
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
char *result;
const ip_addr_t *addr = &ip6_addr_any;
if (ip6_addr_isvalid(netif_ip6_addr_state(netif, ii))) {
addr = &data->lwip_mmnetif.ip6_addr[ii];
}
result = ipaddr_ntoa_r(addr, config->ip6_addr[ii], sizeof(config->ip6_addr[ii]));
LWIP_ASSERT("IP buf too short", result != NULL);
}
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config)
{
struct mmipal_data *data = mmipal_get_data();
struct netif *netif = &data->lwip_mmnetif;
err_t result;
unsigned ii;
ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
int result = ipaddr_aton(config->ip6_addr[ii], &ip6_addr[ii]);
if (!result) {
return MMIPAL_INVALID_ARGUMENT;
}
}
LOCK_TCPIP_CORE();
if (config->ip6_mode == MMIPAL_IP6_STATIC) {
if (data->ip6_mode != MMIPAL_IP6_STATIC) {
#if LWIP_IPV6_DHCP6
dhcp6_disable(netif);
#endif
netif_set_ip6_autoconfig_enabled(netif, 0);
data->ip6_mode = MMIPAL_IP6_STATIC;
}
if (!ip6_addr_islinklocal(ip_2_ip6(&(ip6_addr[0])))) {
printf("First address must be linklocal address (address start with fe80)\n");
}
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
if (ip_addr_isany_val(ip6_addr[ii])) {
netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6);
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID);
} else {
netif_ip6_addr_set(netif, ii, ip_2_ip6(&(ip6_addr[ii])));
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_TENTATIVE);
netif_ip6_addr_set_valid_life(netif, ii, IP6_ADDR_LIFE_STATIC);
}
}
} else {
if (data->ip6_mode == MMIPAL_IP6_STATIC) {
for (ii = 0; ii < LWIP_IPV6_NUM_ADDRESSES; ii++) {
netif_ip6_addr_set(netif, ii, IP6_ADDR_ANY6);
netif_ip6_addr_set_state(netif, ii, IP6_ADDR_INVALID);
}
}
netif_set_ip6_autoconfig_enabled(netif, 1);
netif_create_ip6_linklocal_address(netif, 1);
data->ip6_mode = MMIPAL_IP6_AUTOCONFIG;
}
if (config->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS)
#if LWIP_IPV6_DHCP6
{
result = dhcp6_enable_stateless(netif);
LWIP_ASSERT("Stateless DHCP6 start error", result == ERR_OK);
data->ip6_mode = MMIPAL_IP6_DHCP6_STATELESS;
} else {
dhcp6_disable(netif);
}
#else
{
printf("LWIP_IPV6_DHCP6 is not enabled\n");
}
#endif
UNLOCK_TCPIP_CORE();
return MMIPAL_SUCCESS;
}
#else
enum mmipal_status mmipal_get_ip6_config(struct mmipal_ip6_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv6 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
enum mmipal_status mmipal_set_ip6_config(const struct mmipal_ip6_config *config)
{
MM_UNUSED(config);
LWIP_ASSERT("IPv6 not enabled", false);
return MMIPAL_NOT_SUPPORTED;
}
#endif
static bool mmipal_link_status_check(struct netif *netif)
{
bool ip4_addr_check = true;
#if LWIP_IPV4
ip4_addr_check = !ip_addr_isany(&(netif->ip_addr));
#endif
return ip4_addr_check && netif_is_link_up(netif);
}
/** Handler for @c netif status callbacks from LWIP. */
static void netif_status_callback(struct netif *netif)
{
struct mmipal_data *data = mmipal_get_data();
enum mmipal_link_state new_link_state = MMIPAL_LINK_DOWN;
#if LWIP_IPV4
if (data->ip4_mode == MMIPAL_DHCP_OFFLOAD) {
/* Initialize DHCP offload on link up */
if (mmwlan_enable_dhcp_offload(mmipal_dhcp_lease_updated, NULL) != MMWLAN_SUCCESS) {
printf("Failed to enable DHCP offload!\n");
}
if (!data->dhcp_offload_init_complete) {
/* This just prevents a spurious 'Link Up' message on very first call */
return;
}
}
#endif
if (mmipal_link_status_check(netif)) {
new_link_state = MMIPAL_LINK_UP;
}
if (data->ip_link_state != new_link_state) {
data->ip_link_state = new_link_state;
if (data->link_status_callback || data->ext_link_status_callback) {
struct mmipal_link_status link_status;
memset(&link_status, 0, sizeof(link_status));
link_status.link_state = data->ip_link_state;
#if LWIP_IPV4
char *result = ipaddr_ntoa_r(&netif->ip_addr, link_status.ip_addr, sizeof(link_status.ip_addr));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&netif->netmask, link_status.netmask, sizeof(link_status.netmask));
LWIP_ASSERT("IP buf too short", result != NULL);
result = ipaddr_ntoa_r(&netif->gw, link_status.gateway, sizeof(link_status.gateway));
LWIP_ASSERT("IP buf too short", result != NULL);
if (data->ip_link_state == MMIPAL_LINK_UP) {
/* Check if ARP response offload feature is enabled */
if (data->offload_arp_response) {
mmwlan_enable_arp_response_offload(ip4_addr_get_u32(netif_ip4_addr(netif)));
}
/* Check if ARP refresh offload feature is enabled */
if (data->offload_arp_refresh_s > 0) {
mmwlan_enable_arp_refresh_offload(data->offload_arp_refresh_s, ip4_addr_get_u32(netif_ip4_gw(netif)), true);
}
}
#endif
if (data->link_status_callback) {
data->link_status_callback(&link_status);
}
if (data->ext_link_status_callback) {
data->ext_link_status_callback(&link_status, data->ext_link_status_callback_arg);
}
}
}
}
void mmipal_set_link_status_callback(mmipal_link_status_cb_fn_t fn)
{
struct mmipal_data *data = mmipal_get_data();
data->link_status_callback = fn;
}
void mmipal_set_ext_link_status_callback(mmipal_ext_link_status_cb_fn_t fn, void *arg)
{
struct mmipal_data *data = mmipal_get_data();
data->ext_link_status_callback = fn;
data->ext_link_status_callback_arg = arg;
}
static volatile bool tcpip_init_done = false;
struct lwip_init_args {
enum mmipal_addr_mode mode;
enum mmipal_ip6_addr_mode ip6_mode;
ip_addr_t ip_addr;
ip_addr_t netmask;
ip_addr_t gateway_addr;
ip_addr_t ip6_addr;
};
static void tcpip_init_done_handler(void *arg)
{
struct mmipal_data *data = mmipal_get_data();
struct netif *netif = &data->lwip_mmnetif;
struct lwip_init_args *args = (struct lwip_init_args *)arg;
netif_add_noaddr(netif, NULL, mmnetif_init, tcpip_input);
netif_set_default(netif);
netif_set_up(netif);
#if LWIP_IPV4
err_t result;
data->ip4_mode = args->mode;
if (args->mode == MMIPAL_DHCP) {
result = dhcp_start(netif);
LWIP_ASSERT("DHCP start error", result == ERR_OK);
} else if (args->mode == MMIPAL_STATIC) {
netif_set_addr(netif, ip_2_ip4(&(args->ip_addr)), ip_2_ip4(&(args->netmask)), ip_2_ip4(&(args->gateway_addr)));
}
#endif
netif_set_link_callback(netif, netif_status_callback);
netif_set_status_callback(netif, netif_status_callback);
#if LWIP_IPV6
err_t result6;
data->ip6_mode = args->ip6_mode;
if (args->ip6_mode == MMIPAL_IP6_STATIC) {
netif_ip6_addr_set(netif, 0, ip_2_ip6(&(args->ip6_addr)));
netif_ip6_addr_set_state(netif, 0, IP6_ADDR_TENTATIVE);
} else if (data->ip6_mode == MMIPAL_IP6_AUTOCONFIG) {
netif_set_ip6_autoconfig_enabled(netif, 1);
netif_create_ip6_linklocal_address(netif, 1);
} else if (data->ip6_mode == MMIPAL_IP6_DHCP6_STATELESS)
#if LWIP_IPV6_DHCP6
{
result6 = dhcp6_enable_stateless(netif);
LWIP_ASSERT("Stateless DHCP6 start error", result6 == ERR_OK);
}
#else
{
printf("LWIP_IPV6_DHCP6 is not enabled\n");
}
#endif
#endif
mmosal_free(args);
tcpip_init_done = true;
}
enum mmipal_status mmipal_init(const struct mmipal_init_args *args)
{
struct mmipal_data *data = mmipal_get_data();
enum mmipal_status status = MMIPAL_INVALID_ARGUMENT;
int result;
struct lwip_init_args *lwip_args = (struct lwip_init_args *)mmosal_malloc(sizeof(*lwip_args));
if (lwip_args == NULL) {
printf("malloc failure\n");
return MMIPAL_NO_MEM;
}
memset(lwip_args, 0, sizeof(*lwip_args));
lwip_args->mode = args->mode;
lwip_args->ip6_mode = args->ip6_mode;
data->link_status_callback = NULL;
data->offload_arp_response = args->offload_arp_response;
data->offload_arp_refresh_s = args->offload_arp_refresh_s;
/* Validate arguments */
#if LWIP_IPV4
switch (args->mode) {
case MMIPAL_DISABLED:
printf("%s mode not supported\n", "DISABLED");
goto exit;
case MMIPAL_DHCP_OFFLOAD:
case MMIPAL_STATIC:
result = ipaddr_aton(args->ip_addr, &lwip_args->ip_addr);
if (!result) {
goto exit;
}
result = ipaddr_aton(args->netmask, &lwip_args->netmask);
if (!result) {
goto exit;
}
result = ipaddr_aton(args->gateway_addr, &lwip_args->gateway_addr);
if (!result) {
goto exit;
}
if (ip_addr_isany_val(lwip_args->ip_addr)) {
printf("IP address not specified\n");
goto exit;
}
break;
case MMIPAL_DHCP:
if (LWIP_DHCP == 0) {
printf("DHCP not compiled in\n");
goto exit;
}
break;
case MMIPAL_AUTOIP:
printf("%s mode not supported\n", "AutoIP");
break;
}
#endif
#if LWIP_IPV6
switch (args->ip6_mode) {
case MMIPAL_IP6_DISABLED:
break;
case MMIPAL_IP6_STATIC:
result = ipaddr_aton(args->ip6_addr, &lwip_args->ip6_addr);
if (!result) {
goto exit;
}
if (ip_addr_isany_val(lwip_args->ip6_addr)) {
printf("IP address not specified\n");
goto exit;
}
break;
case MMIPAL_IP6_AUTOCONFIG:
if (LWIP_IPV6_AUTOCONFIG == 0) {
printf("AUTOCONFIG not compiled in\n");
goto exit;
}
break;
case MMIPAL_IP6_DHCP6_STATELESS:
if (LWIP_IPV6_DHCP6_STATELESS == 0) {
printf("DHCP6_STATELESS not compiled in\n");
goto exit;
}
break;
}
#endif
tcpip_init(tcpip_init_done_handler, lwip_args);
/* Block until initialisation is complete */
while (!tcpip_init_done) {
mmosal_task_sleep(10);
}
return MMIPAL_SUCCESS;
exit:
mmosal_free(lwip_args);
return status;
}
void mmipal_get_link_packet_counts(uint32_t *tx_packets, uint32_t *rx_packets)
{
#if LWIP_STATS
*tx_packets = lwip_stats.link.xmit;
*rx_packets = lwip_stats.link.recv;
#else
*tx_packets = 0;
*rx_packets = 0;
#endif
}
void mmipal_set_tx_qos_tid(uint8_t tid)
{
struct mmipal_data *data = mmipal_get_data();
bool ok = tcpip_init_done;
MMOSAL_ASSERT(ok);
mmnetif_set_tx_qos_tid(&data->lwip_mmnetif, tid);
}
enum mmipal_link_state mmipal_get_link_state(void)
{
struct mmipal_data *data = mmipal_get_data();
return data->ip_link_state;
}
static enum mmipal_status mmipal_get_local_addr_(ip_addr_t *local_addr, const ip_addr_t *dest_addr)
{
struct mmipal_data *data = mmipal_get_data();
struct netif *netif = &data->lwip_mmnetif;
#if LWIP_IPV6
if (IP_IS_V6(dest_addr)) {
const ip_addr_t *src_addr = ip6_select_source_address(netif, ip_2_ip6(dest_addr));
if (src_addr == NULL) {
return MMIPAL_NO_LINK;
}
ip_addr_copy(*local_addr, *src_addr);
return MMIPAL_SUCCESS;
} else {
MM_UNUSED(dest_addr);
}
#endif
#if LWIP_IPV4
if (IP_IS_V4(dest_addr)) {
ip_addr_copy(*local_addr, netif->ip_addr);
return MMIPAL_SUCCESS;
} else {
MM_UNUSED(dest_addr);
}
#endif
#if !LWIP_IPV4 && !LWIP_IPV6
MM_UNUSED(local_addr);
MM_UNUSED(dest_addr);
#endif
return MMIPAL_INVALID_ARGUMENT;
}
enum mmipal_status mmipal_get_local_addr(mmipal_ip_addr_t local_addr, const mmipal_ip_addr_t dest_addr)
{
ip_addr_t lwip_dest_addr;
ip_addr_t lwip_local_addr;
int ok;
enum mmipal_status status;
if (dest_addr == NULL) {
return MMIPAL_INVALID_ARGUMENT;
}
ok = ipaddr_aton(dest_addr, &lwip_dest_addr);
if (!ok) {
return MMIPAL_INVALID_ARGUMENT;
}
status = mmipal_get_local_addr_(&lwip_local_addr, &lwip_dest_addr);
if (status != 0) {
return status;
}
if (ipaddr_ntoa_r(&lwip_local_addr, local_addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) {
return MMIPAL_NO_MEM;
} else {
return MMIPAL_SUCCESS;
}
}
enum mmipal_status mmipal_set_dns_server(uint8_t index, const mmipal_ip_addr_t addr)
{
ip_addr_t dns_addr;
int ok;
if (index >= DNS_MAX_SERVERS) {
return MMIPAL_INVALID_ARGUMENT;
}
ok = ipaddr_aton(addr, &dns_addr);
if (!ok) {
return MMIPAL_INVALID_ARGUMENT;
}
dns_setserver(index, &dns_addr);
return MMIPAL_SUCCESS;
}
enum mmipal_status mmipal_get_dns_server(uint8_t index, mmipal_ip_addr_t addr)
{
const ip_addr_t *dns_addr;
if (index >= DNS_MAX_SERVERS) {
return MMIPAL_INVALID_ARGUMENT;
}
dns_addr = dns_getserver(index);
#if LWIP_IPV4
/* dns_getserver() returns ip_addr_any if no address configured. */
if (!memcmp(dns_addr, &ip_addr_any, sizeof(*dns_addr))) {
addr[0] = '\0';
return MMIPAL_SUCCESS;
}
#endif
if (ipaddr_ntoa_r(dns_addr, addr, MMIPAL_IPADDR_STR_MAXLEN) == NULL) {
return MMIPAL_NO_MEM;
} else {
return MMIPAL_SUCCESS;
}
}
#endif
+218
View File
@@ -0,0 +1,218 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "mmnetif.h"
#include "mmosal.h"
#include "mmwlan.h"
#include "lwip/etharp.h"
#include "lwip/ethip6.h"
#include "lwip/tcpip.h"
#if LWIP_SNMP
#include "lwip/snmp.h"
#endif
struct netif_state {
volatile uint8_t tx_qos_tid;
};
static struct netif_state *get_netif_state(struct netif *netif)
{
MMOSAL_ASSERT(netif->state != NULL);
return (struct netif_state *)netif->state;
}
/** pbuf wrapper around an mmpkt. */
struct mmpkt_pbuf_wrapper {
struct pbuf_custom p;
struct mmpkt *pkt;
struct mmpktview *pktview;
};
LWIP_MEMPOOL_DECLARE(RX_POOL, MMPKTMEM_RX_POOL_N_BLOCKS, sizeof(struct mmpkt_pbuf_wrapper), "mmpkt_rx");
static void mmpkt_pbuf_wrapper_free(struct pbuf *p)
{
struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)p;
if (p == NULL) {
return;
}
mmpkt_close(&pbuf->pktview);
mmpkt_release(pbuf->pkt);
LWIP_MEMPOOL_FREE(RX_POOL, pbuf);
}
static void mmnetif_rx(struct mmpkt *rxpkt, void *arg)
{
struct netif *netif = (struct netif *)arg;
LWIP_ASSERT("arg NULL", netif != NULL);
LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: packet received\n"));
struct mmpkt_pbuf_wrapper *pbuf = (struct mmpkt_pbuf_wrapper *)LWIP_MEMPOOL_ALLOC(RX_POOL);
if (pbuf != NULL) {
struct pbuf *p;
pbuf->p.custom_free_function = mmpkt_pbuf_wrapper_free;
pbuf->pkt = rxpkt;
pbuf->pktview = mmpkt_open(pbuf->pkt);
p = pbuf_alloced_custom(PBUF_RAW, mmpkt_get_data_length(pbuf->pktview), PBUF_REF, &pbuf->p,
mmpkt_get_data_start(pbuf->pktview), mmpkt_get_data_length(pbuf->pktview));
int ret = tcpip_input(p, netif);
if (ret == ERR_OK) {
LINK_STATS_INC(link.recv);
} else {
LWIP_DEBUGF(NETIF_DEBUG, ("mmnetif: input error\n"));
pbuf_free(p);
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
}
} else {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: alloc error\n"));
LINK_STATS_INC(link.memerr);
mmpkt_release(rxpkt);
}
}
static void mmnetif_link_state(enum mmwlan_link_state link_state, void *arg)
{
struct netif *netif = (struct netif *)arg;
LWIP_ASSERT("arg NULL", netif != NULL);
LOCK_TCPIP_CORE();
if (link_state == MMWLAN_LINK_DOWN) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link down\n"));
/* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn
* has a "void *" parameter and netif_set_link_down has "struct netif *". */
err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_down, netif, 0);
LWIP_ASSERT("sched callback failed", err == ERR_OK);
} else {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: link up\n"));
/* Note: we cast netif_set_link_down to tcpip_callback_fn since the tcpip_callback_fn
* has a "void *" parameter and netif_set_link_down has "struct netif *". */
err_t err = tcpip_callback_with_block((tcpip_callback_fn)netif_set_link_up, netif, 0);
LWIP_ASSERT("sched callback failed", err == ERR_OK);
}
UNLOCK_TCPIP_CORE();
}
static err_t mmnetif_tx(struct netif *netif, struct pbuf *p)
{
struct mmpkt *pkt;
struct mmpktview *pktview;
enum mmwlan_status status;
struct pbuf *walk;
struct mmwlan_tx_metadata metadata = {
.tid = get_netif_state(netif)->tx_qos_tid,
};
status = mmwlan_tx_wait_until_ready(1000);
if (status != MMWLAN_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: transmit blocked\n"));
LINK_STATS_INC(link.drop);
return ERR_BUF;
}
pkt = mmwlan_alloc_mmpkt_for_tx(p->tot_len, metadata.tid);
if (pkt == NULL) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: allocation failure\n"));
LINK_STATS_INC(link.memerr);
return ERR_MEM;
}
pktview = mmpkt_open(pkt);
for (walk = p; walk != NULL; walk = walk->next) {
mmpkt_append_data(pktview, (const uint8_t *)walk->payload, walk->len);
}
mmpkt_close(&pktview);
status = mmwlan_tx_pkt(pkt, &metadata);
if (status != MMWLAN_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SERIOUS, ("mmnetif: error sending packet\n"));
LINK_STATS_INC(link.drop);
return ERR_BUF;
}
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: packet sent\n"));
LINK_STATS_INC(link.xmit);
return ERR_OK;
}
err_t mmnetif_init(struct netif *netif)
{
static bool initialised = false;
if (initialised) {
return ERR_IF;
}
LWIP_MEMPOOL_INIT(RX_POOL);
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_ALL, ("mmnetif: initialising mmnetif\n"));
#if LWIP_SNMP
NETIF_INIT_SNMP(netif, snmp_ifType_ethernet_csmacd, 1000000UL);
#endif
enum mmwlan_status status;
/* Boot the transceiver so that we can read the MAC address. */
struct mmwlan_boot_args boot_args = MMWLAN_BOOT_ARGS_INIT;
status = mmwlan_boot(&boot_args);
if (status != MMWLAN_SUCCESS) {
LWIP_DEBUGF(NETIF_DEBUG | LWIP_DBG_LEVEL_SEVERE, ("mmwlan_boot failed with code %d\n", status));
}
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
/* Set MAC hardware address */
netif->hwaddr_len = MMWLAN_MAC_ADDR_LEN;
status = mmwlan_get_mac_addr(netif->hwaddr);
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
netif->mtu = 1500;
#if LWIP_IPV4 && !LWIP_IPV6
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP;
#else
netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_IGMP | NETIF_FLAG_MLD6;
#endif
netif->state = NULL;
netif->name[0] = 'M';
netif->name[1] = 'M';
#if LWIP_IPV4
netif->output = etharp_output;
#endif
#if LWIP_IPV6
netif->output_ip6 = ethip6_output;
#endif
netif->linkoutput = mmnetif_tx;
struct netif_state *state = (struct netif_state *)mmosal_malloc(sizeof(*state));
MMOSAL_ASSERT(state != NULL);
state->tx_qos_tid = MMWLAN_TX_DEFAULT_QOS_TID;
netif->state = state;
status = mmwlan_register_rx_pkt_cb(mmnetif_rx, netif);
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
status = mmwlan_register_link_state_cb(mmnetif_link_state, netif);
MMOSAL_ASSERT(status == MMWLAN_SUCCESS);
printf("Morse LwIP interface initialised. MAC address %02x:%02x:%02x:%02x:%02x:%02x\n", netif->hwaddr[0], netif->hwaddr[1],
netif->hwaddr[2], netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]);
initialised = true;
return ERR_OK;
}
void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid)
{
MMOSAL_ASSERT(tid <= MMWLAN_MAX_QOS_TID);
get_netif_state(netif)->tx_qos_tid = tid;
}
#endif
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "lwip/err.h"
#include "lwip/netif.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Initializer for the Morse Micro network interface */
err_t mmnetif_init(struct netif *netif);
/**
* Configure the QoS TID for the @c netif. QoS data will be sent using this TID.
*
* @param netif The @c netif to configure.
* @param tid The TID value to set.
*/
void mmnetif_set_tx_qos_tid(struct netif *netif, uint8_t tid);
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,563 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2021-2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_debug_helpers.h"
#include "esp_private/startup_internal.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "rom/ets_sys.h"
#include "mmhal.h"
#include "mmosal.h"
/* --------------------------------------------------------------------------------------------- */
/** Maximum number of failure records to store (must be a power of 2). */
#define MAX_FAILURE_RECORDS 4
/** Fast implementation of _x % _m where _m is a power of 2. */
#define FAST_MOD(_x, _m) ((_x) & ((_m)-1))
/** Duration to delay before resetting the device on assert. */
#define DELAY_BEFORE_RESET_MS 1000
/** Data structure for assertion information to be preserved. */
struct mmosal_preserved_failure_info {
/** Magic number, to check if the info is valid. */
uint32_t magic;
/** Number of failures recorded. */
uint32_t failure_count;
/** Number of most recently displayed failure. */
uint32_t displayed_failure_count;
/** Preserved information from the most recent failure(s). */
struct mmosal_failure_info info[MAX_FAILURE_RECORDS];
};
/** Magic number to put in @c mmosal_assert_info.magic to indicate that the assertion info
* is valid. */
#define ASSERT_INFO_MAGIC (0xabcd1234)
/* Persistent assertion info. Linker script should put this into memory that is not
* zeroed on boot. Be careful to update linker script if renaming. */
struct mmosal_preserved_failure_info preserved_failure_info __attribute__((section(".noinit")));
void mmosal_log_failure_info(const struct mmosal_failure_info *info)
{
uint32_t record_num;
if (preserved_failure_info.magic != ASSERT_INFO_MAGIC) {
preserved_failure_info.failure_count = 0;
preserved_failure_info.displayed_failure_count = 0;
}
preserved_failure_info.magic = ASSERT_INFO_MAGIC;
record_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS);
preserved_failure_info.failure_count++;
memcpy(&preserved_failure_info.info[record_num], info, sizeof(*info));
}
static void mmosal_dump_failure_info(void)
{
unsigned first_failure_num = preserved_failure_info.displayed_failure_count;
unsigned new_failure_count = preserved_failure_info.failure_count - preserved_failure_info.displayed_failure_count;
unsigned failure_offset;
if (new_failure_count >= MAX_FAILURE_RECORDS) {
first_failure_num = FAST_MOD(preserved_failure_info.failure_count, MAX_FAILURE_RECORDS);
new_failure_count = MAX_FAILURE_RECORDS;
}
for (failure_offset = 0; failure_offset < new_failure_count; failure_offset++) {
unsigned ii;
unsigned idx = FAST_MOD(first_failure_num + failure_offset, MAX_FAILURE_RECORDS);
struct mmosal_failure_info *info = &preserved_failure_info.info[idx];
ets_printf("Failure %u logged at pc 0x%08lx, lr 0x%08lx, line %ld in %08lx\n", first_failure_num + failure_offset,
info->pc, info->lr, info->line, info->fileid);
for (ii = 0; ii < sizeof(info->platform_info) / sizeof(info->platform_info[0]); ii++) {
ets_printf(" 0x%08lx\n", info->platform_info[ii]);
}
}
preserved_failure_info.displayed_failure_count = preserved_failure_info.failure_count;
}
void mmosal_impl_assert(void)
{
ets_printf("MMOSAL Assert, CPU %d (current core) backtrace", xPortGetCoreID());
(void)esp_backtrace_print(100);
#ifdef HALT_ON_ASSERT
if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) {
mmosal_dump_failure_info();
}
mmosal_disable_interrupts();
mmhal_log_flush();
MMPORT_BREAKPOINT();
#else
mmosal_task_sleep(DELAY_BEFORE_RESET_MS);
mmhal_reset();
#endif
while (1) {
}
}
/* Function to be called as part of the secondary initialization. See [System
* Initialization](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/startup.html#system-initialization)
* for more information. */
ESP_SYSTEM_INIT_FN(mmosal_dump_failure_info, BIT(0), 999)
{
if (preserved_failure_info.magic == ASSERT_INFO_MAGIC) {
mmosal_dump_failure_info();
}
return ESP_OK;
}
/* --------------------------------------------------------------------------------------------- */
void *mmosal_malloc_(size_t size)
{
return pvPortMalloc(size);
}
#ifdef MMOSAL_TRACK_ALLOCATIONS
void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number)
{
return pvPortMalloc_dbg(size, name, line_number);
}
#else
void *mmosal_malloc_dbg(size_t size, const char *name, unsigned line_number)
{
(void)name;
(void)line_number;
return pvPortMalloc(size);
}
#endif
void mmosal_free(void *p)
{
vPortFree(p);
}
void *mmosal_realloc(void *ptr, size_t size)
{
return realloc(ptr, size);
}
void *mmosal_calloc(size_t nitems, size_t size)
{
void *ptr = pvPortMalloc(nitems * size);
if (ptr == NULL) {
return NULL;
}
memset(ptr, 0, nitems * size);
return ptr;
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_task_arg {
mmosal_task_fn_t task_fn;
void *task_fn_arg;
};
void mmosal_task_main(void *arg)
{
struct mmosal_task_arg task_arg = *(struct mmosal_task_arg *)arg;
mmosal_free(arg);
task_arg.task_fn(task_arg.task_fn_arg);
mmosal_task_delete(NULL);
}
struct mmosal_task *mmosal_task_create(mmosal_task_fn_t task_fn, void *argument, enum mmosal_task_priority priority,
unsigned stack_size_u32, const char *name)
{
TaskHandle_t handle;
UBaseType_t freertos_priority = tskIDLE_PRIORITY + priority;
struct mmosal_task_arg *task_arg = (struct mmosal_task_arg *)mmosal_malloc(sizeof(*task_arg));
if (task_arg == NULL) {
return NULL;
}
task_arg->task_fn = task_fn;
task_arg->task_fn_arg = argument;
BaseType_t result = xTaskCreate(mmosal_task_main, name, stack_size_u32 * 4, task_arg, freertos_priority, &handle);
if (result == pdFAIL) {
mmosal_free(task_arg);
return NULL;
}
return (struct mmosal_task *)handle;
}
void mmosal_task_delete(struct mmosal_task *task)
{
vTaskDelete((TaskHandle_t)task);
}
/*
* Warning: this function should not be used since eTaskGetState() is not a reliable
* means of testing whether a task has completed.
*
* This function will be removed in future.
*/
void mmosal_task_join(struct mmosal_task *task)
{
while (eTaskGetState((TaskHandle_t)task) != eDeleted) {
mmosal_task_sleep(10);
}
}
struct mmosal_task *mmosal_task_get_active(void)
{
return (struct mmosal_task *)xTaskGetCurrentTaskHandle();
}
void mmosal_task_yield(void)
{
taskYIELD();
}
void mmosal_task_sleep(uint32_t duration_ms)
{
vTaskDelay(duration_ms / portTICK_PERIOD_MS);
}
static portMUX_TYPE task_spinlock = portMUX_INITIALIZER_UNLOCKED;
void mmosal_task_enter_critical(void)
{
taskENTER_CRITICAL(&task_spinlock);
}
void mmosal_task_exit_critical(void)
{
taskEXIT_CRITICAL(&task_spinlock);
}
void mmosal_disable_interrupts(void)
{
taskDISABLE_INTERRUPTS();
}
void mmosal_enable_interrupts(void)
{
taskENABLE_INTERRUPTS();
}
const char *mmosal_task_name(void)
{
TaskHandle_t t = xTaskGetCurrentTaskHandle();
return pcTaskGetName(t);
}
bool mmosal_task_wait_for_notification(uint32_t timeout_ms)
{
TickType_t wait = portMAX_DELAY;
if (timeout_ms < UINT32_MAX) {
wait = pdMS_TO_TICKS(timeout_ms);
}
uint32_t ret = ulTaskNotifyTake(pdTRUE, /* Act as binary semaphore */
wait);
return (ret != 0);
}
void mmosal_task_notify(struct mmosal_task *task)
{
xTaskNotifyGive((TaskHandle_t)task);
}
void mmosal_task_notify_from_isr(struct mmosal_task *task)
{
BaseType_t higher_priority_task_woken = pdFALSE;
vTaskNotifyGiveFromISR((TaskHandle_t)task, &higher_priority_task_woken);
portYIELD_FROM_ISR(higher_priority_task_woken);
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_mutex *mmosal_mutex_create(const char *name)
{
struct mmosal_mutex *mutex = (struct mmosal_mutex *)xSemaphoreCreateMutex();
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetMutexName(mutex, name);
}
#else
(void)name;
#endif
return mutex;
}
void mmosal_mutex_delete(struct mmosal_mutex *mutex)
{
if (mutex != NULL) {
vQueueDelete((SemaphoreHandle_t)mutex);
}
}
bool mmosal_mutex_get(struct mmosal_mutex *mutex, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xSemaphoreTake((SemaphoreHandle_t)mutex, timeout_ticks) == pdPASS);
}
bool mmosal_mutex_release(struct mmosal_mutex *mutex)
{
return (xSemaphoreGive((SemaphoreHandle_t)mutex) == pdPASS);
}
bool mmosal_mutex_is_held_by_active_task(struct mmosal_mutex *mutex)
{
return xSemaphoreGetMutexHolder((SemaphoreHandle_t)mutex) == xTaskGetCurrentTaskHandle();
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_sem *mmosal_sem_create(unsigned max_count, unsigned initial_count, const char *name)
{
struct mmosal_sem *sem = (struct mmosal_sem *)xSemaphoreCreateCounting(max_count, initial_count);
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetSemaphoreName(sem, name);
}
#else
(void)name;
#endif
return sem;
}
void mmosal_sem_delete(struct mmosal_sem *sem)
{
vQueueDelete((SemaphoreHandle_t)sem);
}
bool mmosal_sem_give(struct mmosal_sem *sem)
{
return xSemaphoreGive((SemaphoreHandle_t)sem);
}
bool mmosal_sem_give_from_isr(struct mmosal_sem *sem)
{
BaseType_t task_woken = false;
BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)sem, &task_woken);
if (ret == pdPASS) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
bool mmosal_sem_wait(struct mmosal_sem *sem, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xSemaphoreTake((SemaphoreHandle_t)sem, timeout_ticks) == pdPASS);
}
uint32_t mmosal_sem_get_count(struct mmosal_sem *sem)
{
return uxSemaphoreGetCount((SemaphoreHandle_t)sem);
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_semb *mmosal_semb_create(const char *name)
{
struct mmosal_semb *semb = (struct mmosal_semb *)xSemaphoreCreateBinary();
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetSemaphoreName(semb, name);
}
#else
(void)name;
#endif
return semb;
}
void mmosal_semb_delete(struct mmosal_semb *semb)
{
vQueueDelete((SemaphoreHandle_t)semb);
}
bool mmosal_semb_give(struct mmosal_semb *semb)
{
return (xSemaphoreGive((SemaphoreHandle_t)semb) == pdPASS);
}
bool mmosal_semb_give_from_isr(struct mmosal_semb *semb)
{
BaseType_t task_woken = pdFALSE;
BaseType_t ret = xSemaphoreGiveFromISR((SemaphoreHandle_t)semb, &task_woken);
if (ret == pdPASS) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
bool mmosal_semb_wait(struct mmosal_semb *semb, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xSemaphoreTake((SemaphoreHandle_t)semb, timeout_ticks) == pdPASS);
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_queue *mmosal_queue_create(size_t num_items, size_t item_size, const char *name)
{
struct mmosal_queue *queue = (struct mmosal_queue *)xQueueCreate(num_items, item_size);
#if (configUSE_TRACE_FACILITY == 1) && defined(ENABLE_TRACEALYZER) && ENABLE_TRACEALYZER
if (name != NULL) {
vTraceSetQueueName(queue, name);
}
#else
(void)name;
#endif
return queue;
}
void mmosal_queue_delete(struct mmosal_queue *queue)
{
vQueueDelete((SemaphoreHandle_t)queue);
}
bool mmosal_queue_pop(struct mmosal_queue *queue, void *item, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xQueueReceive((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS);
}
bool mmosal_queue_push(struct mmosal_queue *queue, const void *item, uint32_t timeout_ms)
{
uint32_t timeout_ticks = portMAX_DELAY;
if (timeout_ms != UINT32_MAX) {
timeout_ticks = timeout_ms / portTICK_PERIOD_MS;
}
return (xQueueSendToBack((SemaphoreHandle_t)queue, item, timeout_ticks) == pdPASS);
}
bool mmosal_queue_pop_from_isr(struct mmosal_queue *queue, void *item)
{
BaseType_t task_woken = pdFALSE;
if (xQueueReceiveFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
bool mmosal_queue_push_from_isr(struct mmosal_queue *queue, const void *item)
{
BaseType_t task_woken = pdFALSE;
if (xQueueSendToBackFromISR((SemaphoreHandle_t)queue, item, &task_woken) == pdTRUE) {
portYIELD_FROM_ISR(task_woken);
return true;
} else {
return false;
}
}
/* --------------------------------------------------------------------------------------------- */
uint32_t mmosal_get_time_ms(void)
{
return xTaskGetTickCount() * portTICK_PERIOD_MS;
}
uint32_t mmosal_get_time_ticks(void)
{
return xTaskGetTickCount();
}
uint32_t mmosal_ticks_per_second(void)
{
return portTICK_PERIOD_MS * 1000;
}
/* --------------------------------------------------------------------------------------------- */
struct mmosal_timer *mmosal_timer_create(const char *name, uint32_t timer_period, bool auto_reload, void *arg,
timer_callback_t callback)
{
/*
* The software timer callback functions execute in the context of a task that is
* created automatically when the FreeRTOS scheduler is started. Therefore, it is essential that
* software timer callback functions never call FreeRTOS API functions that will result in the
* calling task entering the Blocked state. It is ok to call functions such as xQueueReceive(), but
* only if the functions xTicksToWait parameter (which specifies the functions block time) is set
* to 0. It is not ok to call functions such as vTaskDelay(), as calling vTaskDelay() will always
* place the calling task into the Blocked state.
*/
return (struct mmosal_timer *)xTimerCreate(name, pdMS_TO_TICKS(timer_period), (UBaseType_t)auto_reload, arg,
(TimerCallbackFunction_t)callback);
}
void mmosal_timer_delete(struct mmosal_timer *timer)
{
if (timer != NULL) {
BaseType_t ret = xTimerDelete((TimerHandle_t)timer, 0);
configASSERT(ret == pdPASS);
}
}
bool mmosal_timer_start(struct mmosal_timer *timer)
{
BaseType_t ret = xTimerStart((TimerHandle_t)timer, 0);
return (ret == pdPASS);
}
bool mmosal_timer_stop(struct mmosal_timer *timer)
{
BaseType_t ret = xTimerStop((TimerHandle_t)timer, 0);
return (ret == pdPASS);
}
bool mmosal_timer_change_period(struct mmosal_timer *timer, uint32_t new_period)
{
BaseType_t ret = xTimerChangePeriod((TimerHandle_t)timer, pdMS_TO_TICKS(new_period), 0);
return (ret == pdPASS);
}
void *mmosal_timer_get_arg(struct mmosal_timer *timer)
{
return pvTimerGetTimerID((TimerHandle_t)timer);
}
bool mmosal_is_timer_active(struct mmosal_timer *timer)
{
BaseType_t ret = xTimerIsTimerActive((TimerHandle_t)timer);
return (ret != pdFALSE);
}
#endif /* USE_MM_IOT_ESP32 */
+245
View File
@@ -0,0 +1,245 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdatomic.h>
#include <stdint.h>
#include "mmhal.h"
#include "mmosal.h"
#include "mmpkt.h"
#include "mmpkt_list.h"
#include "mmutils.h"
/* MMPKTMEM_TX_POOL_N_BLOCKS and MMPKTMEM_RX_POOL_N_BLOCKS provide an upper bound on the number
* of packets we will allocate in the transmit and receive directions respectively. */
#ifndef MMPKTMEM_TX_POOL_N_BLOCKS
#error MMPKTMEM_TX_POOL_N_BLOCKS not defined
#endif
#ifndef MMPKTMEM_RX_POOL_N_BLOCKS
#error MMPKTMEM_RX_POOL_N_BLOCKS not defined
#endif
/* Packet pool for data/management frames configuration. */
#define TX_DATA_POOL_UNPAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 2)
#define TX_DATA_POOL_PAUSE_THRESHOLD (MMPKTMEM_TX_POOL_N_BLOCKS - 1)
/* Packet pool for commands configuration. */
#define TX_COMMAND_POOL_BLOCK_SIZE (256)
#define TX_COMMAND_POOL_N_BLOCKS (2)
#ifndef MMPKT_LOG
#define MMPKT_LOG(...) printf(__VA_ARGS__)
#endif
struct pktmem_data {
/** Count of allocated tx packets (excluding command pool -- see below). */
volatile atomic_int_least32_t tx_data_pool_allocated;
/** Boolean tracking whether the data path is currently paused. */
volatile atomic_uint_fast8_t tx_data_pool_tx_paused;
/** Count of allocated rx packets. */
volatile atomic_int_least32_t rx_pool_allocated;
/** Command pool free (unallocated) packet list. */
struct mmpkt_list tx_command_pool_free_list;
/** Statically allocated memory for the command pool. */
uint8_t tx_command_pool[TX_COMMAND_POOL_BLOCK_SIZE * TX_COMMAND_POOL_N_BLOCKS];
/** Flow control callback function pointer. */
mmhal_wlan_pktmem_tx_flow_control_cb_t tx_flow_control_cb;
};
static struct pktmem_data pktmem;
void mmhal_wlan_pktmem_init(struct mmhal_wlan_pktmem_init_args *args)
{
unsigned ii;
memset(&pktmem, 0, sizeof(pktmem));
pktmem.tx_flow_control_cb = args->tx_flow_control_cb;
/* Initialize the free (unallocated) packet list of the transmit command pool. */
for (ii = 0; ii < TX_COMMAND_POOL_N_BLOCKS; ii++) {
size_t offset = TX_COMMAND_POOL_BLOCK_SIZE * ii;
mmpkt_list_append(&pktmem.tx_command_pool_free_list, (struct mmpkt *)(pktmem.tx_command_pool + offset));
}
}
void mmhal_wlan_pktmem_deinit(void)
{
size_t ii;
/* If there is still memory allocated, allow some time for other threads to clean up. */
for (ii = 0; ii < 100; ii++) {
if ((pktmem.tx_command_pool_free_list.len | pktmem.tx_data_pool_allocated | pktmem.tx_data_pool_allocated) == 0) {
break;
}
mmosal_task_sleep(10);
}
/* Check for memory leaks. */
if (pktmem.tx_data_pool_allocated != 0) {
MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n", (int)pktmem.tx_data_pool_allocated, "data");
}
if (pktmem.tx_command_pool_free_list.len != TX_COMMAND_POOL_N_BLOCKS) {
MMPKT_LOG("Potential memory leak: %d %s pool allocations at deinit\n",
TX_COMMAND_POOL_N_BLOCKS - (int)pktmem.tx_command_pool_free_list.len, "command");
}
}
/*
* --------------------------------------------------------------------------------------
* Command pool
* --------------------------------------------------------------------------------------
*/
static void tx_command_reserved_free(void *mmpkt)
{
struct mmpkt *pkt = (struct mmpkt *)mmpkt;
MMOSAL_TASK_ENTER_CRITICAL();
mmpkt_list_append(&pktmem.tx_command_pool_free_list, pkt);
MMOSAL_TASK_EXIT_CRITICAL();
}
static const struct mmpkt_ops tx_command_pool_ops = {
.free_mmpkt = tx_command_reserved_free,
};
static struct mmpkt *alloc_pkt_from_list(struct mmpkt_list *list, uint32_t pktbufsize, uint32_t space_at_start,
uint32_t space_at_end, uint32_t metadata_length)
{
struct mmpkt *mmpkt_buf;
struct mmpkt *mmpkt;
MMOSAL_TASK_ENTER_CRITICAL();
mmpkt_buf = mmpkt_list_dequeue(list);
MMOSAL_TASK_EXIT_CRITICAL();
if (mmpkt_buf == NULL) {
return NULL;
}
mmpkt = mmpkt_init_buf((uint8_t *)mmpkt_buf, pktbufsize, space_at_start, space_at_end, metadata_length, &tx_command_pool_ops);
if (mmpkt == NULL) {
/* Command was too big for the reserved buffer. Return the reserved buffer. */
tx_command_reserved_free(mmpkt_buf);
}
return mmpkt;
}
static struct mmpkt *command_pool_alloc(uint32_t space_at_start, uint32_t space_at_end, uint32_t metadata_length)
{
return alloc_pkt_from_list(&pktmem.tx_command_pool_free_list, TX_COMMAND_POOL_BLOCK_SIZE, space_at_start, space_at_end,
metadata_length);
}
/*
* --------------------------------------------------------------------------------------
* Data pool
* --------------------------------------------------------------------------------------
*/
static void tx_data_pool_pkt_free(void *mmpkt)
{
atomic_int_least32_t old_value = atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
MMOSAL_ASSERT(old_value > 0);
mmosal_free(mmpkt);
if (pktmem.tx_data_pool_allocated < TX_DATA_POOL_UNPAUSE_THRESHOLD) {
atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 0);
if (old_tx_paused) {
pktmem.tx_flow_control_cb(MMWLAN_TX_READY);
}
}
}
static const struct mmpkt_ops tx_data_pool_pkt_ops = {
.free_mmpkt = tx_data_pool_pkt_free,
};
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_tx(uint8_t pkt_class, uint32_t space_at_start, uint32_t space_at_end,
uint32_t metadata_length)
{
atomic_int_least32_t old_value;
struct mmpkt *mmpkt;
/* For command packets, try allocating from the command pool first. If that fails then
* we proceed to allocate from the data pool. */
if (pkt_class == MMHAL_WLAN_PKT_COMMAND) {
mmpkt = command_pool_alloc(space_at_start, space_at_end, metadata_length);
if (mmpkt != NULL) {
return mmpkt;
}
}
old_value = atomic_fetch_add(&pktmem.tx_data_pool_allocated, 1);
if (old_value >= MMPKTMEM_TX_POOL_N_BLOCKS) {
/* Maximum allocations reached. Do not attempt to increase further. */
atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
return NULL;
}
mmpkt = mmpkt_alloc_on_heap(space_at_start, space_at_end, metadata_length);
if (mmpkt == NULL) {
atomic_fetch_sub(&pktmem.tx_data_pool_allocated, 1);
return NULL;
}
mmpkt->ops = &tx_data_pool_pkt_ops;
if (pktmem.tx_data_pool_allocated > TX_DATA_POOL_PAUSE_THRESHOLD) {
atomic_uint_fast8_t old_tx_paused = atomic_exchange(&pktmem.tx_data_pool_tx_paused, 1);
if (!old_tx_paused) {
pktmem.tx_flow_control_cb(MMWLAN_TX_PAUSED);
}
}
return mmpkt;
}
static void rx_pkt_free(void *mmpkt)
{
if (mmpkt != NULL) {
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
mmosal_free(mmpkt);
}
}
static const struct mmpkt_ops mmpkt_rx_ops = {.free_mmpkt = rx_pkt_free};
struct mmpkt *mmhal_wlan_alloc_mmpkt_for_rx(uint32_t capacity, uint32_t metadata_length)
{
atomic_int_least32_t old_value;
struct mmpkt *mmpkt;
old_value = atomic_fetch_add(&pktmem.rx_pool_allocated, 1);
if (old_value >= MMPKTMEM_RX_POOL_N_BLOCKS) {
/* Maximum allocations reached. Do not attempt to increase further. */
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
return NULL;
}
/* For now we do not put an explicit limit on the of packets buffers on the RX path. */
mmpkt = mmpkt_alloc_on_heap(0, capacity, metadata_length);
if (mmpkt == NULL) {
atomic_fetch_sub(&pktmem.rx_pool_allocated, 1);
return NULL;
}
/* Override packet ops to use a custom free function that also decrements the
* allocation count. */
mmpkt->ops = &mmpkt_rx_ops;
return mmpkt;
}
#endif
+12
View File
@@ -0,0 +1,12 @@
/*
* Copyright 2023 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#define MMPORT_BREAKPOINT() while (1)
#define MMPORT_GET_LR() (__builtin_return_address(0))
#define MMPORT_GET_PC(_a) ((_a) = 0) // TODO
#define MMPORT_MEM_SYNC() __sync_synchronize()
+301
View File
@@ -0,0 +1,301 @@
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @defgroup MMUTILS Morse Micro Utilities
*
* Utility macros and functions to improve quality of life.
*
* @{
*/
#pragma once
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Get the minimum of 2 values.
*
* Note that this macro is not ideal and should be used with caution. Caveats include:
* * The two parameters may be evaluated more than once, so should be constant values that
* do not have side effects. For example, do NOT do `MM_MIN(a++, b)`.
* * There are no explicit constraints on types, so be careful of comparing different integer
* types, etc.
*
* @param _x The first value to compare.
* @param _y The second value to compare.
*
* @returns the minimum of @p _x and @p _y.
*/
#define MM_MIN(_x, _y) (((_x) < (_y)) ? (_x) : (_y))
/**
* Get the maximum of 2 values.
*
* Note that this macro is not ideal and should be used with caution. Caveats include:
* * The two parameters may be evaluated more than once, so should be constant values that
* do not have side effects. For example, do NOT do `MM_MAX(a++, b)`.
* * There are no explicit constraints on types, so be careful of comparing different integer
* types, etc.
*
* @param _x The first value to compare.
* @param _y The second value to compare.
*
* @returns the maximum of @p _x and @p _y.
*/
#define MM_MAX(_x, _y) (((_x) > (_y)) ? (_x) : (_y))
/**
* Round @p x up to the next multiple of @p m (where @p m is a power of 2).
*
* @warning @p m must be a power of 2.
*/
#ifndef MM_FAST_ROUND_UP
#define MM_FAST_ROUND_UP(x, m) ((((x)-1) | ((m)-1)) + 1)
#endif
/** Casts the given expression to void to avoid "unused" warnings from the compiler. */
#define MM_UNUSED(_x) (void)(_x)
/** Tells the compiler to pack the structure. */
#ifndef MM_PACKED
#define MM_PACKED __attribute__((packed))
#endif
/** Used to declare a weak symbol. */
#ifndef MM_WEAK
#define MM_WEAK __attribute__((weak))
#endif
#ifndef MM_STATIC_ASSERT
/**
* Assertion check that is evaluated at compile time.
*
* The constant expression, @p _expression, is evaluted at compile time. If zero then
* it triggers a compilation error and @p _message is displayed. If non-zero, no code
* is emitted.
*
* @param _expression Constant expression to evaluate. If zero then a compilation error
* is triggered.
* @param _message Message to display on error.
*/
#define MM_STATIC_ASSERT(_expression, _message) _Static_assert((_expression), _message)
#endif
/**
* Return the number of elements in the given array.
*
* @param _a The array to get the element count for. Note that this must be an _array_
* and not a pointer. Beware that array-type function arguments are
* actually treated as pointers by the compiler. Must not be NULL.
*
* @returns the count of elements in the given array.
*/
#define MM_ARRAY_COUNT(_a) (sizeof(_a) / sizeof((_a)[0]))
/**
* Convert the least significant 4 bits of the given argument to a character representing their
* hexadecimal value.
*
* For example, for input 0xde this will return 'E', for 0x01 it will return '1'.
*
* @param nibble The input nibble (upper 4 bits will be discarded).
*
* @return The character that represents the hexadecimal value of the lower 4 bits of @p nibble.
* Values greater than 0x09 will be represented with upper case characters.
*/
static inline char mm_nibble_to_hex_char(uint8_t nibble)
{
nibble &= 0x0f;
if (nibble < 0x0a) {
return '0' + nibble;
} else {
return 'A' + nibble - 0x0a;
}
}
/**
* @defgroup MMUTILS_WLAN WLAN Utilities
*
* Utility macros and functions relating to WLAN.
*
* @{
*/
/** Enumeration of Authentication Key Management (AKM) Suite OUIs as BE32 integers. */
enum mm_akm_suite_oui {
/** Open (no security) */
MM_AKM_SUITE_NONE = 0,
/** Pre-shared key (WFA OUI) */
MM_AKM_SUITE_PSK = 0x506f9a02,
/** Simultaneous Authentication of Equals (SAE) */
MM_AKM_SUITE_SAE = 0x000fac08,
/** OWE */
MM_AKM_SUITE_OWE = 0x000fac12,
/** Another suite not in this enum */
MM_AKM_SUITE_OTHER = 1,
};
/** Enumeration of Cipher Suite OUIs as BE32 integers. */
enum mm_cipher_suite_oui {
/** Open (no security) */
MM_CIPHER_SUITE_AES_CCM = 0x000fac04,
/** Another cipher suite not in this enum */
MM_CIPHER_SUITE_OTHER = 1,
};
/** Maximum number of pairwise cipher suites our parser will process. */
#ifndef MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES
#define MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES (2)
#endif
/** Maximum number of AKM suites our parser will process. */
#ifndef MM_RSN_INFORMATION_MAX_AKM_SUITES
#define MM_RSN_INFORMATION_MAX_AKM_SUITES (2)
#endif
/** Tag number of the RSN information element, in which we can find security details of the AP. */
#define MM_RSN_INFORMATION_IE_TYPE (48)
/** Tag number of the Vendor Specific information element. */
#define MM_VENDOR_SPECIFIC_IE_TYPE (221)
/** Explicitly defined errno values to obviate the need to include errno.h. MM prefix to
* avoid namespace collision in case errno.h gets included. */
enum mm_errno {
MM_ENOMEM = 12,
MM_EFAULT = 14,
MM_ENODEV = 19,
MM_EINVAL = 22,
MM_ETIMEDOUT = 110,
};
/**
* Data structure to represent information extracted from an RSN information element.
*
* All integers in host order.
*/
struct mm_rsn_information {
/** The group cipher suite OUI. */
uint32_t group_cipher_suite;
/** Pairwise cipher suite OUIs. Count given by @c num_pairwise_cipher_suites. */
uint32_t pairwise_cipher_suites[MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES];
/** AKM suite OUIs. Count given by @c num_akm_suites. */
uint32_t akm_suites[MM_RSN_INFORMATION_MAX_AKM_SUITES];
/** Number of pairwise cipher suites in @c pairwise_cipher_suites. */
uint16_t num_pairwise_cipher_suites;
/** Number of AKM suites in @c akm_suites. */
uint16_t num_akm_suites;
/** Version number of the RSN IE. */
uint16_t version;
/** RSN Capabilities field of the RSN IE (in host order). */
uint16_t rsn_capabilities;
};
/**
* Get the name of the given AKM Suite as a string.
*
* @param akm_suite_oui The OUI of the AKM suite as a big endian integer.
*
* @returns the string representation.
*/
const char *mm_akm_suite_to_string(uint32_t akm_suite_oui);
/**
* Search a list of Information Elements (IEs) from the given starting offset and find the first
* instance of matching the given type.
*
* @warning A @p search_offset that is not aligned to the start of an IE header will result in
* undefined behaviour.
*
* @param ies Buffer containing the information elements.
* @param ies_len Length of @p ies
* @param search_offset Offset to start searching from. This **must** point to a IE header.
* @param ie_type The type of the IE to look for.
*
* @return If the information element is found, the offset of the start of the IE within @p ies; if
* no match is found then -1; if the IE is found but is malformed then -2.
*/
int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type);
/**
* Search a list of Information Elements (IEs) and find the first instance of matching the
* given type.
*
* @param ies Buffer containing the information elements.
* @param ies_len Length of @p ies
* @param ie_type The type of the IE to look for.
*
* @return If the information element is found, the offset of the start of the IE within @p ies;
* if no match is found then -1; if the IE is found but is malformed then -2.
*/
static inline int mm_find_ie(const uint8_t *ies, uint32_t ies_len, uint8_t ie_type)
{
return mm_find_ie_from_offset(ies, ies_len, 0, ie_type);
}
/**
* Search through the given list of Information Elements (IEs) from the given starting offset to
* find the first Vendor Specific IE that matches the given id.
*
* @warning A @p search_offset that is not aligned to the start of an IE header will result in
* undefined behaviour.
*
* @param[in] ies Buffer containing the information elements.
* @param[in] ies_len Length of @p ies
* @param[in] search_offset Offset to start searching from. This **must** point to a IE header.
* @param[in] id Buffer containing the IE ID, usually OUI+TYPE.
* @param[in] id_len Length of the ID.
*
* @return If the information element is found, the offset of the start of the IE within @p ies; if
* no match is found then -1; if the IE is found but is malformed then -2.
*/
int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id,
size_t id_len);
/**
* Search through the given list of Information Elements (IEs) to find the first Vendor Specific IE
* that matches the given id.
*
* @param[in] ies Buffer containing the information elements.
* @param[in] ies_len Length of @p ies
* @param[in] id Buffer containing the IE ID, usually OUI+TYPE.
* @param[in] id_len Length of the ID.
*
* @return If the information element is found, the offset of the start of the IE within @p ies; if
* no match is found then -1; if the IE is found but is malformed then -2.
*/
static inline int mm_find_vendor_specific_ie(const uint8_t *ies, uint32_t ies_len, const uint8_t *id, size_t id_len)
{
return mm_find_vendor_specific_ie_from_offset(ies, ies_len, 0, id, id_len);
}
/**
* Search through the given list of information elements to find the RSN IE then parse it
* to extract relevant information into an instance of @ref mm_rsn_information.
*
* @param[in] ies Buffer containing the information elements.
* @param[in] ies_len Length of @p ies
* @param[out] output Pointer to an instance of @ref mm_rsn_information to receive output.
*
* @returns -2 on parse error, -1 if the RSN IE was not found, 0 if the RSN IE was found.
*/
int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output);
/** @} */
#ifdef __cplusplus
}
#endif
/** @} */
+163
View File
@@ -0,0 +1,163 @@
#ifdef USE_MM_IOT_ESP32
/*
* Copyright 2024 Morse Micro
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "mmutils.h"
const char *mm_akm_suite_to_string(uint32_t akm_suite_oui)
{
switch (akm_suite_oui) {
case MM_AKM_SUITE_NONE:
return "None";
case MM_AKM_SUITE_PSK:
return "PSK";
case MM_AKM_SUITE_SAE:
return "SAE";
case MM_AKM_SUITE_OWE:
return "OWE";
default:
return "Other";
}
}
int mm_find_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, uint8_t ie_type)
{
while ((search_offset + 2) <= ies_len) {
uint8_t type = ies[search_offset];
uint8_t length = ies[search_offset + 1];
if (type == ie_type) {
if ((search_offset + 2 + length) > ies_len) {
return -2;
}
return search_offset;
}
search_offset += 2 + length;
}
return -1;
}
int mm_find_vendor_specific_ie_from_offset(const uint8_t *ies, uint32_t ies_len, uint32_t search_offset, const uint8_t *id,
size_t id_len)
{
int offset = 0;
while ((search_offset + id_len) <= ies_len) {
offset = mm_find_ie_from_offset(ies, ies_len, search_offset, MM_VENDOR_SPECIFIC_IE_TYPE);
if (offset < 0) {
return offset;
}
uint8_t ie_type = ies[offset];
uint8_t ie_length = ies[offset + 1];
const uint8_t *ie_data = ies + (offset + 2);
if (ie_type == MM_VENDOR_SPECIFIC_IE_TYPE && id_len <= ie_length && (memcmp(id, ie_data, id_len) == 0)) {
if (((uint32_t)offset + 2 + ie_length) > ies_len) {
return -2;
}
return offset;
}
search_offset = 2 + ie_length + (uint32_t)offset;
}
return -1;
}
int mm_parse_rsn_information(const uint8_t *ies, uint32_t ies_len, struct mm_rsn_information *output)
{
uint8_t length;
uint16_t num_pairwise_cipher_suites;
uint16_t num_akm_suites;
uint16_t ii;
int offset = mm_find_ie(ies, ies_len, MM_RSN_INFORMATION_IE_TYPE);
memset(output, 0, sizeof(*output));
if (offset < 0) {
return offset;
}
/* Note that we rely on mm_find_ie() to validate that the IE does not extend past the end
* of the given buffer. */
length = ies[offset + 1];
offset += 2;
if (length < 8) {
printf("*WRN* RSN IE too short\n");
return -2;
}
/* Skip version field */
output->version = ies[offset] | ies[offset + 1] << 8;
offset += 2;
length -= 2;
output->group_cipher_suite = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
offset += 4;
length -= 4;
num_pairwise_cipher_suites = ies[offset] | ies[offset + 1] << 8;
offset += 2;
length -= 2;
output->num_pairwise_cipher_suites = num_pairwise_cipher_suites;
if (num_pairwise_cipher_suites > MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES) {
output->num_pairwise_cipher_suites = MM_RSN_INFORMATION_MAX_PAIRWISE_CIPHER_SUITES;
}
if (length < 4 * num_pairwise_cipher_suites + 2) {
printf("*WRN* RSN IE too short\n");
return -2;
}
for (ii = 0; ii < num_pairwise_cipher_suites; ii++) {
if (ii < output->num_pairwise_cipher_suites) {
output->pairwise_cipher_suites[ii] =
ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
}
offset += 4;
length -= 4;
}
num_akm_suites = ies[offset] | ies[offset + 1] << 8;
offset += 2;
length -= 2;
output->num_akm_suites = num_akm_suites;
if (num_akm_suites > MM_RSN_INFORMATION_MAX_AKM_SUITES) {
output->num_akm_suites = MM_RSN_INFORMATION_MAX_AKM_SUITES;
}
if (length < 4 * num_akm_suites + 2) {
printf("*WRN* RSN IE too short\n");
return -2;
}
for (ii = 0; ii < num_akm_suites; ii++) {
if (ii < output->num_akm_suites) {
output->akm_suites[ii] = ies[offset] << 24 | ies[offset + 1] << 16 | ies[offset + 2] << 8 | ies[offset + 3];
}
offset += 4;
length -= 4;
}
output->rsn_capabilities = ies[offset] | ies[offset + 1] << 8;
return 0;
}
#endif /* USE_MM_IOT_ESP32 */

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