To sharpen my Rust skills for embedded systems, I embarked on creating a project that uses Discord (and soon MQTT) to receive commands for network tasks. Users can monitor devices, perform network scans, execute pings, send WOL packets and get alerts when applications running on edge devices fail.

In this project, I’m using the LILYGO T-Dongle S3, powered by the ESP32S3 chip. Rather than diving straight into the code I’ve developed (I am developing at the moment), I would like to share my thought process, design decisions, and the roadmap for this project. By the end, you’ll see how everything ties together. Here’s how the project is organized:

  • Configuration
  • Peripherals
  • Integrations
  • Core Application

diagram

Configuration

For configuration management, I opted for a Singleton Design Pattern. In Rust, a popular variation of this is the Lazy Singleton, which allows static variables to be initialized on first access. I’ve implemented this pattern with a global CONFIG variable initialized in main.rs. This CONFIG variable doesn’t require a mutex since it’s only written during the device boot and is read-only afterward. On boot, the device retrieves configuration parameters from NVS (Non-Volatile Storage). But where do these parameters come from? Initially, these parameters are set through a predefined set of global variables when flashing the device. However, if the user presses a button for at least 10 seconds, the device switches to Access Point mode, allowing the user to connect to a web page and update the parameters dynamically, like the following image:

wifi config web page

Peripherals

I manage peripherals through an object named SystemPeripherals, which abstracts access to key hardware components like the display, RGB LED, modem, and button. To support different boards, I rely on Rust’s attribute #[cfg], which allows you to compile code based on a flag passed to the compiler:

#[cfg(feature = "lilygo_t_dongle_s3")]
mod lilygo_t_dongle_s3;
#[cfg(feature = "lilygo_t_dongle_s3")]
use lilygo_t_dongle_s3::*;

With this setup, integrating a new board involves adding a feature flag, defining the appropriate I/O mappings, and ensuring compatibility with SystemPeripherals.

Integrations

At the moment, the project integrates with Discord, though I plan to add MQTT in the future. For Discord, users can set up a bot in the Discord Developer Portal and use its token to interact with specific channels. Here’s how to configure the bot:

  1. Create a new application: Discord Developer Portal
  2. Create a bot:
    2.1 Go to the Bot tab and enable the MESSAGE CONTENT INTENT.
  3. Set up OAuth2:
    3.1 Navigate to the OAuth2 URL Generator, select the “Bot” scope, and assign permissions like Manage Channels and Read Message History.
    3.2 Copy the generated URL and open it in a browser to add the bot to your desired channel.
  4. Enable Developer Mode:
    3.1 In Discord, go to Settings > Advanced > Developer Mode and enable it.
  5. Retrieve the Channel ID:
    5.1 Right-click the desired channel, select Copy ID, and use this in your bot’s configuration.

With the token and Channel ID in hand, you’re now ready to configure them and start using the Discord integration.

Core Application

The Core Application is the heart of the project—but it’s currently a work-in-progress. Initially, I had a simple idea, but the scope has expanded with new features, resulting in some messy code. Here’s what the core application currently handles:

  • Discord Message Management: Fetching and sending messages.
  • AT Command Execution: Processing commands received from the user.
  • Job Scheduling: Executing alert rules (referred to as “Jobs”).

Supported AT commands include:

  • AT+PING - Ping an IP address.
  • AT+WOL - Wake-on-LAN feature.
  • AT+SCAN - Scan for open ports.
  • AT+RGB - Control the RGB LED.

In main.rs, I manage WiFi connectivity, monitor for new commands, and spawn the list of configured jobs.

Roadmap

  1. Refactor Core Application: Organize the codebase to improve maintainability and scalability.
  2. Expand Integrations: Add MQTT support and explore new APIs.
  3. Enhance Features: Introduce more advanced AT commands and improve user interface for configuration.
  4. Optimize Performance: Focus on reducing latency and improving resource management.

Through this process, I’m combining the flexibility of the ESP32S3 with Rust’s powerful abstractions to create a robust and extensible system. Stay tuned for updates as the project evolves!