ESP are cool. Let s bend them to our needs.

ESP

Meshtastic

Heltec v3

Linux setup

First you ll need Python and Pip installed. You ll figure it out.
Then install esptool

yay -S esptool

Clone the repo :

git clone git@github.com:meshtastic/firmware.git
cd firmware
git switch tags/v2.5.19.f9876cf # which is the latest tag at the moment, adapt

Da build

in order to build and especially upload, we ll use Platformio. I tried to install it system wide, it did compile the firmware, but during the upload process, it tried to pip-install some modules and failed. Therefore I would recommend the following :

# Setting up the env
uv venv .venv
source .venv/bin/activate
uv pip install platformio

# Now We can play with pio
pio system info
pio pkg list

# List all available envs
pio project config | grep env: [ | grep heltec ]

# Teh build
pio run -e heltec-v3

# Teh upload
pio run -e heltec-v3 -t upload --upload-port /dev/ttyUSB0

Bonus

Change the name on the splash screen.
Edit the following file : src/graphics/Screen.cpp. Around line 168, look for ‘const char *title = “meshtastic.org”;’ and replace it by whatever name you want.
Side note :
The line before is : “#ifdef USERPREFS_SPLASH_TITLE”, so I am pretty sure there is better way to make a custom splashscreen

T114

The process is pretty similar. I had an issue while trying to upload the firmware using the USB port, as it seemed that none was found (plug on the same port as before, using the same cable). By removing the –upload-port, it worked, using /dev/ttyACM0. I am not sure why one is discovered on the USB port and the other on the ACM port.

pio run -e heltec-mesh-node-t114 -t upload

Edit : This difference would come from the different chip used on the board. Heltec v3 is using a USB-UART converter and appears on USB, whereas the t114 uses native USB (CDC-ACM) and appears on ACM.

ESP-32

It follows pretty much the same principles, except… We are going to build the firmware.
We can use different framework to do so. We ll first explore the Arduino style, then the ESP-IDF style and finally we ll use Rust.

Arduino style

Step 1

mkdir arduino
cd arduino

# setup the project
uv venv .venv
source .venv/bin/activate
uv pip install platformio

Step 2

# first try !
pio init --board esp32dev # TODO : add board list

This will create the directory structure and download the necessary lib. For the sake of the example lets copy this code inside the src folder :

#include <Arduino.h>

#define LED_PIN 2

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(500);  // 500ms
  digitalWrite(LED_PIN, LOW);
  delay(500);
}

And finally build + upload

pio run -t upload

ESP-IDF C-style

Step 1

Same same

Step 2

# first try !
pio init --board esp32dev --project-option="framework=espidf"

And now copy this code inside of src/main.c

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"

#define LED_PIN GPIO_NUM_2

void app_main(void)
{
    gpio_reset_pin(LED_PIN);
    gpio_set_direction(LED_PIN, GPIO_MODE_OUTPUT);

    while (1) {
        gpio_set_level(LED_PIN, 1);
        vTaskDelay(500 / portTICK_PERIOD_MS);

        gpio_set_level(LED_PIN, 0);
        vTaskDelay(500 / portTICK_PERIOD_MS);
    }
}

Compile and upload

pio run
pio run -t upload

Rust-IDF style

Setup the ESP ecosystem

You do have Rust installed, right ?

# Install tools
cargo install espup cargo-generate
# Install the ESP toolchain
espup install

read carefully the output, it will tell you where is the script you should run to load the env. You can add it to your .zshrc conf
espup is …
cargo-generate is…
espup installs the toolchains like xtensa which is…
and generate a script…

Create the ready-to-go Rust ESP32 project

cargo generate \
  --git https://github.com/esp-rs/esp-idf-template.git \
  --name esp32-rs

Template : cargo
MCU : esp32

cd esp32-rs

Build

Lets try to build that

cargo build

if the build crashes because of a lib it might be because of a recent change of name, try to installlibxml12-legacy (and possibly ldproxy)

And finally flash

espflash flash --speed 460800 /dev/ttyUSB0 target/xtensa-esp32-espidf/debug/esp32-rs

Upload

cargo install espflash
espflash flash -p /dev/ttyUSB0 target/xtensa-esp32-espidf/debug/esp32-rs

Note that platformio can also be used to upload

Coding

Just to get started, let s make it blink. Yes it was a pain… and yes it can be better. But not tonight. src/main.rs :

use esp_idf_hal::prelude::*;
use esp_idf_hal::gpio::{Gpio2, PinDriver}; // On utilise PinDriver
use esp_idf_svc::log::EspLogger;
use esp_idf_svc::sys::link_patches;
use std::thread;
use std::time::Duration;
use anyhow::Error;

fn main() -> Result<(), Error> {
    // Necessary runtime patch call
    link_patches();

    // Initialize the logger
    EspLogger::initialize_default();

    // Set up the GPIO2 pin (typically the on-board LED)
    let peripherals = Peripherals::take().unwrap();
    let mut gpio2 = PinDriver::output(peripherals.pins.gpio2)?; // Utilisation de PinDriver::output()

    // Blink the LED
    loop {
        gpio2.set_high()?; // Turn the LED on
        log::info!("LED ON");
        thread::sleep(Duration::from_secs(1)); // Wait for 1 second

        gpio2.set_low()?; // Turn the LED off
        log::info!("LED OFF");
        thread::sleep(Duration::from_secs(1)); // Wait for 1 second
    }
}

Cargo.toml :


[package]
name = "esp32-rs"
version = "0.1.0"
authors = ["cristalcorp <cristal@cristalcorp.com>"]
edition = "2021"
resolver = "2"
rust-version = "1.77"

[[bin]]
name = "esp32-rs"
harness = false # do not use the built-in cargo test harness -> resolve rust-analyzer errors

[profile.release]
opt-level = "s"

[profile.dev]
debug = true    # Symbols are nice, and they don't increase the size on Flash
opt-level = "z"

[features]
default = []

[dependencies]
log = "0.4"
esp-idf-sys = "*"
esp-idf-hal = "*"
esp-idf-svc = "*"
anyhow = "*"


# --- Optional Embassy Integration ---
# esp-idf-svc = { version = "0.51", features = ["critical-section", "embassy-time-driver", "embassy-sync"] }

# If you enable embassy-time-driver, you MUST also add one of:

# a) Standalone Embassy libs ( embassy-time, embassy-sync etc.) with a foreign async runtime:
# embassy-time = { version = "0.4.0", features = ["generic-queue-8"] } # NOTE: any generic-queue variant will work

# b) With embassy-executor:
# embassy-executor = { version = "0.7", features = ["executor-thread", "arch-std"] }

# NOTE: if you use embassy-time with embassy-executor you don't need the generic-queue-8 feature

# --- Temporary workaround for embassy-executor < 0.8 ---
# esp-idf-svc = { version = "0.51", features = ["embassy-time-driver", "embassy-sync"] }
# critical-section = { version = "1.1", features = ["std"], default-features = false }

[build-dependencies]
embuild = "0.33"