forked from iarv/mesh-forge
353 lines
12 KiB
Diff
353 lines
12 KiB
Diff
diff --git a/.gitignore b/.gitignore
|
|
index cc742c6c1..545b0923a 100644
|
|
--- a/.gitignore
|
|
+++ b/.gitignore
|
|
@@ -41,3 +41,11 @@ src/mesh/raspihttp/private_key.pem
|
|
|
|
# Ignore logo (set at build time with platformio-custom.py)
|
|
data/boot/logo.*
|
|
+
|
|
+# Ignore 3rd party plugins
|
|
+plugins/*
|
|
+!plugins/README.md
|
|
+!plugins/sample-plugin
|
|
+
|
|
+# Ignore Python vendor directory
|
|
+pyvendor
|
|
\ No newline at end of file
|
|
diff --git a/bin/mpm_pio.py b/bin/mpm_pio.py
|
|
new file mode 100644
|
|
index 000000000..48c088acc
|
|
--- /dev/null
|
|
+++ b/bin/mpm_pio.py
|
|
@@ -0,0 +1,55 @@
|
|
+#!/usr/bin/env python3
|
|
+"""
|
|
+Mesh Plugin Manager (MPM) - PlatformIO build integration shim.
|
|
+
|
|
+This script is intended to be used only as a PlatformIO extra_script.
|
|
+It imports the real `mpm` package (vendored via `pyvendor/`) and
|
|
+exposes the build helpers expected by the firmware build.
|
|
+"""
|
|
+
|
|
+import os
|
|
+import sys
|
|
+
|
|
+try:
|
|
+ # Provided by PlatformIO/SCons when used as `pre:` extra_script
|
|
+ Import("env") # type: ignore[name-defined] # noqa: F821
|
|
+ IS_PLATFORMIO = True
|
|
+except Exception:
|
|
+ IS_PLATFORMIO = False
|
|
+
|
|
+# Add vendored Python dependencies (installed with `pip install -r requirements.txt -t pyvendor`)
|
|
+if IS_PLATFORMIO:
|
|
+ # Use PROJECT_DIR from PlatformIO env (__file__ is not available when exec'd by SCons)
|
|
+ project_dir = env["PROJECT_DIR"] # type: ignore[name-defined] # noqa: F821
|
|
+ _pyvendor = os.path.join(project_dir, "pyvendor")
|
|
+else:
|
|
+ # Standalone execution: use __file__ if available, otherwise cwd
|
|
+ try:
|
|
+ _here = os.path.dirname(__file__)
|
|
+ _pyvendor = os.path.abspath(os.path.join(_here, "..", "pyvendor"))
|
|
+ except NameError:
|
|
+ _pyvendor = os.path.join(os.getcwd(), "pyvendor")
|
|
+
|
|
+if os.path.isdir(_pyvendor) and _pyvendor not in sys.path:
|
|
+ sys.path.insert(0, _pyvendor)
|
|
+
|
|
+if IS_PLATFORMIO:
|
|
+ # Use the installed `mpm` package
|
|
+ from mpm.build import init_plugins # type: ignore[import]
|
|
+ from mpm.build_utils import scan_plugins # type: ignore[import]
|
|
+ from mpm.proto import generate_all_protobuf_files # type: ignore[import]
|
|
+
|
|
+ # Auto-initialize when imported by PlatformIO (not when run as __main__)
|
|
+ if __name__ != "__main__":
|
|
+ try:
|
|
+ init_plugins(env) # type: ignore[name-defined] # noqa: F821
|
|
+ except NameError:
|
|
+ # If env is missing for some reason, just skip auto-init
|
|
+ pass
|
|
+else:
|
|
+ # Standalone execution: delegate to the real CLI for convenience
|
|
+ if __name__ == "__main__":
|
|
+ from mpm.cli import main # type: ignore[import]
|
|
+
|
|
+ main()
|
|
+
|
|
diff --git a/platformio.ini b/platformio.ini
|
|
index d6ff155e4..b92615db3 100644
|
|
--- a/platformio.ini
|
|
+++ b/platformio.ini
|
|
@@ -14,7 +14,9 @@ description = Meshtastic
|
|
|
|
[env]
|
|
test_build_src = true
|
|
-extra_scripts = bin/platformio-custom.py
|
|
+extra_scripts =
|
|
+ bin/platformio-custom.py
|
|
+ pre:bin/mpm_pio.py
|
|
; note: we add src to our include search path so that lmic_project_config can override
|
|
; note: TINYGPS_OPTION_NO_CUSTOM_FIELDS is VERY important. We don't use custom fields and somewhere in that pile
|
|
; of code is a heap corruption bug!
|
|
@@ -24,6 +26,7 @@ build_flags = -Wno-missing-field-initializers
|
|
|
|
-Wno-format
|
|
-Isrc -Isrc/mesh -Isrc/mesh/generated -Isrc/gps -Isrc/buzz -Wl,-Map,"${platformio.build_dir}"/output.map
|
|
+ -Isrc/modules
|
|
-DUSE_THREAD_NAMES
|
|
-DTINYGPS_OPTION_NO_CUSTOM_FIELDS
|
|
-DPB_ENABLE_MALLOC=1
|
|
diff --git a/plugins/README.md b/plugins/README.md
|
|
new file mode 100644
|
|
index 000000000..84e8e8fdf
|
|
--- /dev/null
|
|
+++ b/plugins/README.md
|
|
@@ -0,0 +1,92 @@
|
|
+# Plugin Development Guide
|
|
+
|
|
+This directory houses plugins that extend the Meshtastic firmware. Plugins are automatically discovered and integrated into the build system.
|
|
+
|
|
+## Plugin Structure
|
|
+
|
|
+The only requirement for a plugin is that it must have a `./src` directory:
|
|
+
|
|
+```
|
|
+src/plugins/
|
|
+└── myplugin/
|
|
+ └── src/
|
|
+ ├── MyModule.h
|
|
+ ├── MyModule.cpp
|
|
+ └── mymodule.proto
|
|
+```
|
|
+
|
|
+- Plugin directory name can be anything
|
|
+- All source files must be placed in `./src`
|
|
+- Only files in `./src` are compiled (the root plugin directory and all other subdirectories are excluded from the build)
|
|
+
|
|
+## Python Dependencies
|
|
+
|
|
+Before building or working with plugins, install the Python tooling into a local vendor directory so PlatformIO can import it:
|
|
+
|
|
+```bash
|
|
+# From the firmware repo root (directory containing platformio.ini)
|
|
+python -m pip install -r requirements.txt -t pyvendor
|
|
+```
|
|
+
|
|
+This vendors the Mesh Plugin Manager (MPM) and its dependencies (including `nanopb`) into `pyvendor/`. The build scripts automatically add `pyvendor/` to `sys.path` when PlatformIO runs.
|
|
+
|
|
+## Automatic Protobuf Generation
|
|
+
|
|
+For convenience, the Meshtastic Plugin Manager (MPM) automatically scans for and generates protobuf files:
|
|
+
|
|
+- **Discovery**: MPM recursively scans plugin directories for `.proto` files
|
|
+- **Options file**: Auto-detects matching `.options` files (e.g., `mymodule.proto` → `mymodule.options`)
|
|
+- **Generation**: Uses the vendored `nanopb` tooling from `pyvendor/` to generate C++ files
|
|
+- **Output**: Generated files are placed in the same directory as the `.proto` file
|
|
+- **Timing**: Runs during PlatformIO pre-build phase (configured in `platformio.ini`)
|
|
+
|
|
+**Note**: Once `pyvendor/` is populated as described above, you can also use the Mesh Plugin Manager CLI from a Python environment that has `pyvendor/` on its `PYTHONPATH` to inspect or manage plugins.
|
|
+
|
|
+Example protobuf structure:
|
|
+
|
|
+```
|
|
+src/plugins/myplugin/src/
|
|
+├── mymodule.proto # Protobuf definition
|
|
+├── mymodule.options # Nanopb options (optional)
|
|
+├── mymodule.pb.h # Generated header
|
|
+└── mymodule.pb.c # Generated implementation
|
|
+```
|
|
+
|
|
+## Include Path Setup
|
|
+
|
|
+The plugin's `src/` directory is automatically added to the compiler's include path (`CPPPATH`) during build:
|
|
+
|
|
+- Headers in `src/` can be included directly: `#include "MyModule.h"`
|
|
+- No need to specify relative paths from other plugin files
|
|
+- The build system handles this automatically via `bin/mpm.py`
|
|
+
|
|
+## Module Registration
|
|
+
|
|
+If your plugin implements a Meshtastic module, you can use the automatic registration system:
|
|
+
|
|
+1. Include `ModuleRegistry.h` in your module `.cpp` file
|
|
+2. Place `MESHTASTIC_REGISTER_MODULE(ModuleClassName)` at the end of your implementation file
|
|
+3. Your module will be automatically initialized when the firmware starts
|
|
+
|
|
+Example:
|
|
+
|
|
+```cpp
|
|
+#include "MyModule.h"
|
|
+#include "ModuleRegistry.h"
|
|
+
|
|
+// ... module implementation ...
|
|
+
|
|
+MESHTASTIC_REGISTER_MODULE(MyModule);
|
|
+```
|
|
+
|
|
+**Note**: Module registration is optional. Plugins that don't implement Meshtastic modules (e.g., utility libraries) don't need this.
|
|
+
|
|
+For details on writing Meshtastic modules, see the [Module API documentation](https://meshtastic.org/docs/development/device/module-api/).
|
|
+
|
|
+## Example Plugin
|
|
+
|
|
+See the `lobbs` plugin for a complete example that demonstrates:
|
|
+
|
|
+- Protobuf definitions with options file
|
|
+- Module implementation with automatic registration
|
|
+- Proper source file organization
|
|
diff --git a/plugins/sample-plugin/README.md b/plugins/sample-plugin/README.md
|
|
new file mode 100644
|
|
index 000000000..f60ee9b9c
|
|
--- /dev/null
|
|
+++ b/plugins/sample-plugin/README.md
|
|
@@ -0,0 +1 @@
|
|
+Sample plugin
|
|
\ No newline at end of file
|
|
diff --git a/plugins/sample-plugin/src/SampleModule.cpp b/plugins/sample-plugin/src/SampleModule.cpp
|
|
new file mode 100644
|
|
index 000000000..647c78bd5
|
|
--- /dev/null
|
|
+++ b/plugins/sample-plugin/src/SampleModule.cpp
|
|
@@ -0,0 +1,13 @@
|
|
+#include "ModuleRegistry.h"
|
|
+#include "SinglePortModule.h"
|
|
+
|
|
+class MySampleModule : public SinglePortModule
|
|
+{
|
|
+ public:
|
|
+ MySampleModule() : SinglePortModule("my_sample_module", meshtastic_PortNum_REPLY_APP) {
|
|
+ LOG_INFO("MySampleModule constructor");
|
|
+ }
|
|
+};
|
|
+
|
|
+
|
|
+MESHTASTIC_REGISTER_MODULE(MySampleModule)
|
|
\ No newline at end of file
|
|
diff --git a/plugins/sample-plugin/src/SampleModule.h b/plugins/sample-plugin/src/SampleModule.h
|
|
new file mode 100644
|
|
index 000000000..4faef7774
|
|
--- /dev/null
|
|
+++ b/plugins/sample-plugin/src/SampleModule.h
|
|
@@ -0,0 +1,12 @@
|
|
+#ifndef SAMPLE_MODULE_H
|
|
+#define SAMPLE_MODULE_H
|
|
+
|
|
+#include "SinglePortModule.h"
|
|
+
|
|
+class MySampleModule : public SinglePortModule
|
|
+{
|
|
+ public:
|
|
+ MySampleModule() : SinglePortModule("my_sample_module", meshtastic_PortNum_REPLY_APP);
|
|
+};
|
|
+
|
|
+#endif
|
|
\ No newline at end of file
|
|
diff --git a/requirements.txt b/requirements.txt
|
|
new file mode 100644
|
|
index 000000000..e45155ab7
|
|
--- /dev/null
|
|
+++ b/requirements.txt
|
|
@@ -0,0 +1,2 @@
|
|
+../mpm
|
|
+
|
|
diff --git a/src/modules/ModuleRegistry.cpp b/src/modules/ModuleRegistry.cpp
|
|
new file mode 100644
|
|
index 000000000..5c83dc70f
|
|
--- /dev/null
|
|
+++ b/src/modules/ModuleRegistry.cpp
|
|
@@ -0,0 +1,30 @@
|
|
+// src/modules/ModuleRegistry.cpp
|
|
+
|
|
+#include "ModuleRegistry.h"
|
|
+#include "DebugConfiguration.h"
|
|
+
|
|
+// Initialize the global vector in static storage.
|
|
+// This vector will be populated by the constructor-attributed functions.
|
|
+std::vector<ModuleInitFunc> g_module_init_functions;
|
|
+
|
|
+/**
|
|
+ * @brief Called by a module's constructor-attributed function to add
|
|
+ * its setup routine to the central list.
|
|
+ */
|
|
+void register_module_initializer(ModuleInitFunc func) {
|
|
+ // This push_back happens during C++ static initialization, before main().
|
|
+ g_module_init_functions.push_back(func);
|
|
+}
|
|
+
|
|
+/**
|
|
+ * @brief Initializes all modules that have self-registered.
|
|
+ * Called once by the core Meshtastic firmware setup routine.
|
|
+ */
|
|
+void init_dynamic_modules() {
|
|
+ LOG_INFO("Initializing dynamic modules via vector...\n");
|
|
+
|
|
+ // Loop through the collected pointers and execute the setup functions
|
|
+ for (ModuleInitFunc func : g_module_init_functions) {
|
|
+ func(); // Executes the module's initialization code (e.g., new MyModule())
|
|
+ }
|
|
+}
|
|
\ No newline at end of file
|
|
diff --git a/src/modules/ModuleRegistry.h b/src/modules/ModuleRegistry.h
|
|
new file mode 100644
|
|
index 000000000..82f571a5e
|
|
--- /dev/null
|
|
+++ b/src/modules/ModuleRegistry.h
|
|
@@ -0,0 +1,31 @@
|
|
+// src/modules/ModuleRegistry.h
|
|
+
|
|
+#ifndef MODULE_REGISTRY_H
|
|
+#define MODULE_REGISTRY_H
|
|
+
|
|
+#include <vector> // Required for std::vector
|
|
+
|
|
+// Define the function pointer type for module initialization
|
|
+typedef void (*ModuleInitFunc)(void);
|
|
+
|
|
+// The central list to hold pointers to the initialization functions.
|
|
+// This is defined externally in the CPP file.
|
|
+extern std::vector<ModuleInitFunc> g_module_init_functions;
|
|
+
|
|
+// Function that all modules will call to register themselves
|
|
+void register_module_initializer(ModuleInitFunc func);
|
|
+
|
|
+// Function called by the core firmware setup to initialize all modules
|
|
+void init_dynamic_modules();
|
|
+
|
|
+/**
|
|
+ * @brief Macro used by module authors to self-register a new Meshtastic Module.
|
|
+ * This creates a lambda that instantiates the module and automatically applies the constructor attribute.
|
|
+ * * @param ModuleClassName The name of the module's C++ class (e.g., MySensorModule).
|
|
+ */
|
|
+#define MESHTASTIC_REGISTER_MODULE(ModuleClassName) \
|
|
+ static void __attribute__((constructor)) register_##ModuleClassName() { \
|
|
+ register_module_initializer([]() { new ModuleClassName(); }); \
|
|
+ }
|
|
+
|
|
+#endif // MODULE_REGISTRY_H
|
|
\ No newline at end of file
|
|
diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp
|
|
index e477574dd..49df725dd 100644
|
|
--- a/src/modules/Modules.cpp
|
|
+++ b/src/modules/Modules.cpp
|
|
@@ -9,6 +9,8 @@
|
|
#include "input/UpDownInterruptImpl1.h"
|
|
#include "input/i2cButton.h"
|
|
#include "modules/SystemCommandsModule.h"
|
|
+#include "modules/ModuleRegistry.h"
|
|
+
|
|
#if HAS_TRACKBALL
|
|
#include "input/TrackballInterruptImpl1.h"
|
|
#endif
|
|
@@ -298,6 +300,9 @@ void setupModules()
|
|
if (moduleConfig.has_range_test && moduleConfig.range_test.enabled)
|
|
new RangeTestModule();
|
|
#endif
|
|
+
|
|
+ init_dynamic_modules();
|
|
+
|
|
// NOTE! This module must be added LAST because it likes to check for replies from other modules and avoid sending extra
|
|
// acks
|
|
routingModule = new RoutingModule();
|