* 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
4.1 KiB
New Meshtastic Module
Guide for developing a new Meshtastic firmware module.
Module Hierarchy
Choose the appropriate base class:
MeshModule— Raw base class. OverridewantPacket()andhandleReceived(). ReturnsProcessMessage::STOPorProcessMessage::CONTINUE.SinglePortModule— Handles a singlemeshtastic_PortNum. Constructor takes(name, portNum). SimplifiedwantPacket()checkingdecoded.portnum. UseallocDataPacket()to create outgoing packets.ProtobufModule<T>— Template for protobuf-encoded modules. Constructor takes(name, portNum, fields). OverridehandleReceivedProtobuf(). UseallocDataProtobuf(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)
- Define messages in
protobufs/meshtastic/(e.g.,mymodule.proto) - Add a
.optionsfile for nanopb field size constraints - Regenerate with
bin/regen-protos.sh - Generated code appears in
src/mesh/generated/meshtastic/ - Assign a
meshtastic_PortNumif 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.cppwithMESHTASTIC_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/