Meshtastic Firmware Source Code Practical Tutorial
This tutorial covers a basic Meshtastic firmware workflow on Windows and macOS: clone the repository, build seeed_wio_tracker_L1, make a small UI change, and flash the result.
If Git, Python, and PlatformIO are already installed, you can skip ahead to the hands-on section.
Commands are provided for both Windows and macOS. Most screenshots use Windows, but the workflow is the same on macOS.
Prerequisites
Prepare the following tools:
- Git
- Python 3
- VS Code
- PlatformIO
1. Install Git
- Windows
- macOS
Open the official Git for Windows download page:
The installer usually starts downloading automatically when you open the page. After the download is complete, double-click the installer and follow the setup wizard.
During installation, the most important step is Adjusting your PATH environment. Choose:
Git from the command line and also from 3rd-party software
For the other options, the default values are usually fine. Just keep clicking Next.

Wait until the installation finishes.
After installation, close all current PowerShell and VS Code terminal windows, then open a new PowerShell window and run:
& "C:\Program Files\Git\cmd\git.exe" --version

If a Git version number is displayed, Git has been installed successfully.
If the git command is still unavailable
You can first run the following commands in PowerShell to confirm the default Git installation paths:
$gitCmd = "C:\Program Files\Git\cmd"
$gitBin = "C:\Program Files\Git\bin"
Write-Host $gitCmd
Write-Host $gitBin

Then manually add Git to the system environment variables.
GUI fix steps
- Press
Win - Search for "Edit the system environment variables"
- Open it and click Environment Variables
- Find
Pathunder System variables - Click Edit
- Click New and add the following two paths:
C:\Program Files\Git\cmd
C:\Program Files\Git\bin
- Click OK all the way to save

After saving, you still need to:
- Close all PowerShell windows
- Open PowerShell again
Then run:
git --version

If a version number appears, the installation is complete.
On macOS, Git can be installed in more than one way, but using Homebrew is usually the easiest option:
- Install the Command Line Tools first:
xcode-select --install
- If Homebrew is not installed yet, install it first:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- Install Git:
brew install git
- Check the installed version:
git --version
If your terminal already returns a valid Git version, you do not need to install it again.
Configure your Git identity
Next, configure your Git user information. Replace the example values with your own name and email address:
git config --global user.name "your name"
git config --global user.email "your [email protected]"
Then run:
git config --global --list
to confirm the configuration has taken effect.
2. Install Python 3
Install Python from the command line
- Windows
- macOS
Run the following commands in the terminal:
winget search --id Python.Python.3.13 --source winget
winget install -e --id Python.Python.3.13 --source winget
If the first command can find Python, the second one should normally install it directly.
After installation, close the terminal and open it again, then run:
python --version
pip --version

If version numbers are shown, Python and pip are ready to use.
macOS often already includes a Python environment. Before installing a new version, check whether python3 and pip3 are already available:
python3 --version
pip3 --version
If they are not available, or if you want a newer version, install Python with Homebrew:
brew install python
After installation, reopen the terminal and run:
python3 --version
pip3 --version
If you prefer using python and pip, you can set shell aliases yourself. On macOS, however, using python3 and pip3 is usually the more reliable choice.
3. Install PlatformIO
PlatformIO downloads dependencies automatically during installation, so this step may take some time. If errors occur, review them one at a time.
Search for PlatformIO in the VS Code Extensions marketplace and install it.

After installation, an ant-shaped icon usually appears in the left toolbar.

4. Clone the Meshtastic firmware repository
The official Meshtastic firmware repository is meshtastic/firmware.
- Windows
- macOS
Run the following commands in your working directory terminal:
git clone https://github.com/meshtastic/firmware.git
cd firmware
git submodule update --init
If your project directory is on a different drive or under a different path, switch to that location first.


If the output looks similar to the screenshots above, the repository has been cloned successfully.
Run the following commands in your working directory terminal:
cd ~/workplace
git clone https://github.com/meshtastic/firmware.git
cd firmware
git submodule update --init
If ~/workplace does not exist yet, create it first:
mkdir -p ~/workplace
If the commands complete normally, the repository has been cloned successfully.
5. Hands-on practice
At this stage, do not rush into editing the code. First, make sure the project can run through the complete build process successfully.
It is recommended to start with three tasks:
- Open
firmware - Check
platformio.ini - Find the build environment for your target board
One important detail: do not focus only on the root platformio.ini. It actually includes additional configuration files, for example:
extra_configs =
variants/*/*.ini
variants/*/*/platformio.ini
variants/*/diy/*/platformio.ini
That means the real board-level environment definitions are usually located under variants/.../platformio.ini.
When identifying the target board, pay special attention to these two directories:
variants/boards/
Here we use Wio Tracker L1 Pro as the example target.

This shows that, in Meshtastic, the build target for Wio Tracker L1 / L1 Pro is seeed_wio_tracker_L1.
Step 1: Confirm that the project builds successfully
Here we use the PlatformIO Core CLI for building.

For the first build, it is recommended to run the following command:
- Windows
- macOS
cd D:\workplace\firmware # Adjust to your actual project path
pio run -e seeed_wio_tracker_L1
cd ~/workplace/firmware # Adjust to your actual project path
pio run -e seeed_wio_tracker_L1

If the interface looks similar to the screenshot above, the build process has started correctly. The first build often takes a while, so be patient.
If the build fails
When a build fails, you can first ask PlatformIO to install the dependencies required by the current environment:
- Windows
- macOS
cd D:\workplace\firmware # Adjust to your actual project path
pio pkg install -e seeed_wio_tracker_L1
cd ~/workplace/firmware # Adjust to your actual project path
pio pkg install -e seeed_wio_tracker_L1
This approach has several benefits:
- It installs dependencies only, without immediately starting a full build.
- It makes it easier to see which package is causing the problem.
- The error messages are usually more focused and easier to troubleshoot.
After the dependencies are installed, run:
- Windows
- macOS
pio run -e seeed_wio_tracker_L1 -v
pio run -e seeed_wio_tracker_L1 -v

Once dependency installation is complete, run the normal build again:
- Windows
- macOS
pio run -e seeed_wio_tracker_L1
pio run -e seeed_wio_tracker_L1

If the build passes at this point, your firmware output has been generated successfully.

Step 2: Modify the code
Practice 1: Modify the UI display
Start by tracing the display implementation from the board-level configuration. You can first check:
variants/nrf52840/seeed_wio_tracker_L1/platformio.inivariants/nrf52840/seeed_wio_tracker_L1/variant.h

From these configuration files, you can see that L1 defines HAS_SCREEN and USE_SSD1306. That means it uses the standard OLED display pipeline, not a screenless configuration and not an E-Ink solution.
If you continue tracing the display logic, most of the related code is located under:
src/graphics/src/graphics/draw/
Here we use a simple example: add a custom label to the home screen header.
Update src/graphics/SharedUIDisplay.cpp with the following changes:
// Track the end of the battery text
int batteryX = 1;
int batteryY = HEADER_OFFSET_Y + 1;
int batteryTextEndX = batteryX - 1;
// Update the boundary while drawing the battery percentage
if (chargePercent != 101) {
char chargeStr[4];
snprintf(chargeStr, sizeof(chargeStr), "%d", chargePercent);
int chargeNumWidth = display->getStringWidth(chargeStr);
int percentWidth = display->getStringWidth("%");
display->drawString(batteryX, textY, chargeStr);
display->drawString(batteryX + chargeNumWidth - 1, textY, "%");
if (isBold) {
display->drawString(batteryX + 1, textY, chargeStr);
display->drawString(batteryX + chargeNumWidth, textY, "%");
}
batteryTextEndX = batteryX + chargeNumWidth + percentWidth - 1 + (isBold ? 1 : 0);
} else {
batteryTextEndX = batteryX - 1;
}
// In the branch that displays time
int iconRightEdge = timeX - 2;
int headerLabelRight = timeX - 4;
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
if (titleStr && titleStr[0] == '\0') {
static const char *yclLabel = "made by AE";
int labelWidth = display->getStringWidth(yclLabel);
int labelLeft = batteryTextEndX + 4;
if (labelLeft + labelWidth <= headerLabelRight) {
int labelX = labelLeft + ((headerLabelRight - labelLeft) - labelWidth) / 2;
display->drawString(labelX, textY, yclLabel);
if (isBold)
display->drawString(labelX + 1, textY, yclLabel);
}
}
#endif
// In the branch that does not display time
int iconRightEdge = screenW - xOffset;
int headerLabelRight = screenW - xOffset - 2;
#if defined(SEEED_WIO_TRACKER_L1) && !defined(SEEED_WIO_TRACKER_L1_EINK)
if (titleStr && titleStr[0] == '\0') {
static const char *yclLabel = "made by AE";
int labelWidth = display->getStringWidth(yclLabel);
int labelLeft = batteryTextEndX + 4;
if (labelLeft + labelWidth <= headerLabelRight) {
int labelX = labelLeft + ((headerLabelRight - labelLeft) - labelWidth) / 2;
display->drawString(labelX, textY, yclLabel);
if (isBold)
display->drawString(labelX + 1, textY, yclLabel);
}
}
#endif
This update does three things:
- Records the right edge of the battery text.
- Reserves space between the battery area and the right-side icons.
- Draws
made by AEonly onSEEED_WIO_TRACKER_L1when the title is empty.
You can find the complete code here:
Step 3: Build your own firmware
After finishing the modification, return to the project root and build the same target again:
- Windows
- macOS
cd D:\workplace\firmware # Adjust to your actual project path
pio run -e seeed_wio_tracker_L1
cd ~/workplace/firmware # Adjust to your actual project path
pio run -e seeed_wio_tracker_L1
The display logic has changed, but the build target is still the same:
seeed_wio_tracker_L1
After a successful build, the output is usually located in:
- Windows
- macOS
D:\workplace\firmware\.pio\build\seeed_wio_tracker_L1\
~/workplace/firmware/.pio/build/seeed_wio_tracker_L1/
The file you should confirm has been updated is:
firmware-seeed_wio_tracker_L1-*.uf2
6. Flash the firmware
After the build is complete, open the official flashing page:
In most cases, you should perform an erase operation first.

Then select the firmware file you just built and flash it to the device.


At this point, the practical Meshtastic source code exercise is complete. You have gone through the full workflow: environment setup, repository cloning, board configuration discovery, firmware compilation, display logic modification, and final flashing verification.
If you want to go further, you can continue exploring these directions:
- Modify more elements on the home screen
- Adjust the behavior of buttons, GPS, Bluetooth, and other modules
- Add an independent
variantfor your own board - Continue tracing the relationships between
src/,variants/, andboards/
Common issues
The git command is unavailable
- On Windows, first check whether Git has been added to
PATH. - On macOS, run
git --versionfirst. If the system asks you to install the Command Line Tools, follow the prompt.
python3 or pip3 is unavailable
- On Windows, confirm that Python was added to
PATH, or reopen the terminal and try again. - On macOS, first check whether
python3/pip3already exists, and install Python with Homebrew only if needed.
The pio command is unavailable
- Run
pio --versionfirst. - If the command is still unavailable, restart VS Code and the terminal, then try again.
- If necessary, reinstall the PlatformIO extension and confirm that PlatformIO Core has been initialized correctly.
The code still looks incomplete after git submodule update --init
- First make sure you are in the root directory of the
firmwarerepository. - If the network connection is unstable, try again with:
git submodule update --init --recursive
The first build takes too long
- It is normal for the first build to download many dependencies.
- If it seems stuck for too long, try installing the packages separately first:
pio pkg install -e seeed_wio_tracker_L1
Then run the build again.