Files
firmware/.github/prompts/new-module.prompt.md
Clive Blackledge 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

4.1 KiB

New Meshtastic Module

Guide for developing a new Meshtastic firmware module.

Module Hierarchy

Choose the appropriate base class:

  1. MeshModule — Raw base class. Override wantPacket() and handleReceived(). Returns ProcessMessage::STOP or ProcessMessage::CONTINUE.
  2. SinglePortModule — Handles a single meshtastic_PortNum. Constructor takes (name, portNum). Simplified wantPacket() checking decoded.portnum. Use allocDataPacket() to create outgoing packets.
  3. ProtobufModule<T> — Template for protobuf-encoded modules. Constructor takes (name, portNum, fields). Override handleReceivedProtobuf(). Use allocDataProtobuf(payload) to create outgoing packets.

Most modules also mix in concurrency::OSThread for periodic background tasks.

Implementation Pattern

// src/modules/MyModule.h
#pragma once
#include "ProtobufModule.h"
#include "concurrency/OSThread.h"

class MyModule : public ProtobufModule<meshtastic_MyMessage>, private concurrency::OSThread
{
  public:
    MyModule();

  protected:
    // Process incoming protobuf packet. Return true to stop further processing.
    virtual bool handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg) override;

    // Generate response packet (optional)
    virtual meshtastic_MeshPacket *allocReply() override;

    // Periodic task — return next run interval in ms, or disable()
    virtual int32_t runOnce() override;

    // Modify packet in-flight before delivery (optional)
    virtual bool alterReceivedProtobuf(meshtastic_MeshPacket &mp, meshtastic_MyMessage *msg);

    // Request a UI display frame (optional)
    virtual bool wantUIFrame();
};

Registration

Register in src/modules/Modules.cpp inside setupModules():

#if !MESHTASTIC_EXCLUDE_MYMODULE
    new MyModule();
#endif

If other code needs to reference the module instance:

#if !MESHTASTIC_EXCLUDE_MYMODULE
    myModule = new MyModule();
#endif

And declare the global in the header:

extern MyModule *myModule;

Some modules also conditionally instantiate based on moduleConfig:

#if !MESHTASTIC_EXCLUDE_MYMODULE
    if (moduleConfig.has_my_module && moduleConfig.my_module.enabled) {
        new MyModule();
    }
#endif

Conditional Compilation

Add a MESHTASTIC_EXCLUDE_MYMODULE guard. This allows the module to be excluded from constrained builds. The flag name must follow the pattern: MESHTASTIC_EXCLUDE_ + uppercase module name.

Protobuf Messages (if needed)

  1. Define messages in protobufs/meshtastic/ (e.g., mymodule.proto)
  2. Add a .options file for nanopb field size constraints
  3. Regenerate with bin/regen-protos.sh
  4. Generated code appears in src/mesh/generated/meshtastic/
  5. Assign a meshtastic_PortNum if the module uses a new port number

Timing and Defaults

Use Default class helpers for configurable intervals:

int32_t MyModule::runOnce()
{
    uint32_t interval = Default::getConfiguredOrDefaultMs(moduleConfig.my_module.update_interval,
                                                          default_my_module_interval);
    // ... do work ...
    return interval;
}

On public/default channels, enforce minimums with Default::getConfiguredOrMinimumValue().

Observer Pattern

Subscribe to system events:

CallbackObserver<MyModule, const meshtastic::Status *> statusObserver =
    CallbackObserver<MyModule, const meshtastic::Status *>(this, &MyModule::handleStatusUpdate);

Testing

Add test suite in test/test_mymodule/:

test/
└── test_mymodule/
    └── test_main.cpp

Run with: pio test -e native

Checklist

  • Header and implementation files in src/modules/
  • Inherit from appropriate base class (MeshModule / SinglePortModule / ProtobufModule)
  • Mix in OSThread if periodic work is needed
  • Register in src/modules/Modules.cpp with MESHTASTIC_EXCLUDE_ guard
  • Add protobuf definitions if needed (protobufs/meshtastic/)
  • Use Default::getConfiguredOrDefaultMs() for timing
  • Respect bandwidth limits on public channels
  • Add test suite in test/