Projects · MiniMe

MiniMe

Discord bot firmware for a WeAct Studio ESP32-S3-N16R8 - joins Wi-Fi and the Discord Gateway, reads sensors, drives GPIO from chat, shows a live 128×128 SSD1327 OLED dashboard, and serves a LAN web UI. v0.5.1 shipped breadboard firmware.

Open on GitHub For full project details, go to GitHub.
Full write-up (commands, OLED, LAN web, hardware, setup)

MiniMe ESP32-S3 breadboard with SSD1327 OLED, touch pad, and DS18B20

Breadboard prototype: WeAct Studio ESP32-S3-N16R8, 128×128 SSD1327 (GND / VCC / SCL / SDA), two discrete LEDs, GPIO 4 touch wake pad, and DS18B20 on GPIO 10. Sensor fail on the OLED is T:--Error--.

Status: shipped breadboard firmware · v0.5.1 · green CI compile · PCB / desk case still planned.

I find this working well and have not found any bugs. Unless I find something to add to its function, or a bug, this is now shipped code.

After Wi-Fi connects, MiniMe serves a LAN web dashboard at http://<board-ip>/ (Display, SysInfo, LOG, Serial). Theme matches k9dtv.com (light/dark). IC chips: theme (sun/moon) and Display / Log layout.

MiniMe LAN web UI dark theme - Display, SysInfo, LOG, and Serial MiniMe LAN web UI light theme - Display, SysInfo, LOG, and Serial

LAN web UI (v0.5.1): light/dark + Display/Log layout chips; Sig / Heap / Srv bars share one left edge and stop at the Display panel edge. SysInfo shows live CPU MHz. Screenshot follows the site theme toggle.

This is my first big modern MCU / Discord bot project on ESP32. AI helped with firmware edits, multi-file layout, and GitHub updates. I owned the architecture, wiring, Discord Gateway/OLED design, commands, power/idle trade-offs, and what shipped on the board.

Ongoing project

Firmware on the GitHub repo is shipped. Hardware and optional extras still on the list:

  • Mention / DM indicators on set1 / set2 - DM to the bot turns set1 on; @mention of OWNER_ID_STR turns set2 on. Owner !clear (or !set1/!set2 off) turns them off
  • PCB and desk case - move off the breadboard onto a custom board and enclosure that can sit on my desk

Done in v0.5.00: Wi-Fi ArduinoOTA (!ota), LAN Display/Log layout.

What this bot can do

Same list Discord shows for !help.

Public commands

DMs, TARGET_CHANNEL_ID, or TARGET_CHANNEL_ID1:

  • !apod - NASA Astronomy Picture of the Day
  • !ask <question> - DeepSeek text reply in chat
  • !display <text> - payload on OLED rows 15-16 (not the word !display)
  • !help - command list
  • !iss - International Space Station position
  • !news - space / high-tech headlines
  • !physics - latest arXiv physics papers
  • !sys - uptime (d h m s), heap (SRAM + PSRAM), Wi-Fi RSSI, gateway, USB VBUS
  • !ota - Wi-Fi ArduinoOTA info (IP / hostname / port 3232)
  • !temp - indoor DS18B20 temperature
  • !time - bot local time (US Pacific, DST aware)
  • !weather <zip> - US ZIP weather (OpenWeatherMap)

Owner-only

OWNER_ID_STR:

  • !led on/off / !led <r> <g> <b> - RGB NeoPixel (GPIO 48)
  • !servo <0-90> - servo angle (updates the Srv: bar)
  • !set1 on / !set1 off - digital output pin 1
  • !set2 on / !set2 off - digital output pin 2
  • !clear - turn set1/set2 off

Automatic posts

Sent once to TARGET_CHANNEL_ID after Gateway connects (boot only):

  • !sys diagnostics
  • !help command list

No repeating 4-hour sys posts or daily temperature summaries. Commands still work in TARGET_CHANNEL_ID, TARGET_CHANNEL_ID1, and DMs.

Bot Discord presence

  • Starts Online when the Gateway identifies
  • Goes Idle after 5 minutes with no activity
  • Returns to Online on commands and the boot channel announce (touch does not set Online)

How it works

Everything runs on one ESP32-S3. Discord stays in the cloud; MiniMe talks to it over Gateway (websocket) and REST (HTTPS), paints the OLED, serves a LAN web dashboard, and wakes the panel from a touch pad.

  • Gateway - live link for chat commands, presence, Online/Idle, heartbeats (must not stall during long HTTPS). Heartbeats start after Hello (jittered first send); a missing OP11 ACK before the next interval forces disconnect (HB_ACK_TIMEOUT). Resume is skipped; one beginSSL at boot, then library reconnect only (no second bind on drop / OP7 / OP9)
  • REST - bot posts replies and loads member names; also pulls science/weather/AI over HTTPS/HTTP (setInsecure for Discord/API outbound). One shared WiFiClientSecure; httpsInUse is claimed in the transport (sendDiscordMessage, discordRestGet, httpsGetOpen / httpsRelease) so overlapping HTTPS cannot stop() each other
  • OLED - always the status board; sleep blanks the panel only (Wi-Fi and Gateway stay up). Sig / Heap / Srv bar fills are computed once in display helpers; the LAN API exposes the same fills as percents (JS only paints width)
  • LAN web UI - same board status in a browser at http://<board-ip>/ (Display, SysInfo, LOG, Serial); refreshes about once a second; does not replace OLED. LOG is a 200-line ring with an accurate byte counter; if the next line would push past 20 KB, LOG is wiped then that line is kept. Serial stays a fixed 12-line ring. MmLog feeds the web LOG/Serial panels only (no USB Serial / UART0 traffic)
  • Touch - wakes the OLED only; does not change Discord status or fire GPIO commands

Why this is hard (on one MCU)

  • Discord Gateway heartbeats must keep running while long HTTPS calls (!ask, weather, NASA) use the same TLS client (httpsInUse + Gateway deferral while DeepSeek holds it)
  • Large Gateway JSON lives in PSRAM; small Wi-Fi/TLS buffers must not - wrong placement crashes this board
  • OLED can dim and power-save while Wi-Fi and the Gateway stay up (panel sleep ≠ chip sleep)
  • Capacitive touch trip point tracks USB VBUS so port sag does not false-trigger or go dead
  • Eight live presence rows + 24h command counts share a tiny dashboard with no Serial debug path

OLED dashboard

128×128 SSD1327 status board (U8g2). Font is 5×7 with 1px padding (8px per row).

RowBaseline yWhat it shows
07MiniMe, GW:Good / GW:Bad, time
215Bot Online/Idle; date (right)
323Up:ddd hh mm T:... (days 3-wide, hours/mins 2-wide, spaces; no seconds); fail: T:--Error--
431Sig: Wi-Fi RSSI bar
539Heap: memory bar (SRAM + PSRAM)
647Srv: servo bar 0-90°
7-1455...111Eight user rows: name, presence, command count (24h)
15-16119 / 127Command text or !display payload (blank when idle)

After 1 minute idle the panel dims, then turns off (setPowerSave). Touch on GPIO 4, commands, and the boot channel announce wake it. CPU stays locked at 240 MHz (v0.5.1; no idle downclock when OLED is blank).

Secrets and IDs

Copy MiniMe_Discord_Bot/secrets.example.h to secrets.h and fill in:

#define WIFI_SSID            "ssid"
#define WIFI_PASSWORD        "password"
#define BOT_TOKEN            "bot token"
#define WEATHER_API_KEY      "WEATHER_API_KEY"
#define NASA_API_KEY         "NASA_API_KEY"
#define DEEPSEEK_API_KEY     "DEEPSEEK_API_KEY"
#define BOT_GUILD_ID         "GUILD_ID"
#define OWNER_ID_STR         "OWNER_ID_STR"
#define TARGET_CHANNEL_ID    "TARGET_CHANNEL_ID"
#define TARGET_CHANNEL_ID1   "TARGET_CHANNEL_ID1"

Use #define (not const char*) so every .cpp can include secrets.h without linker errors. IDs are digits only as C strings. OTA password (if used) also lives in secrets.h.

How to get a Discord bot token
  1. Open the Discord Developer Portal.
  2. New Application → name it → Create.
  3. Bot → Add Bot if needed → copy token into BOT_TOKEN.
  4. Enable Message Content, Server Members, and Presence intents.
  5. OAuth2 → URL Generator: scope bot, permissions View Channels, Send Messages, Read Message History → invite to your server.
Owner, channel, and guild IDs

Enable Discord Developer Mode, then:

  • Owner ID: right-click your avatar → Copy User ID → OWNER_ID_STR
  • Channel ID: right-click text channel → Copy Channel ID
  • Guild ID: right-click server icon → Copy Server ID → BOT_GUILD_ID

Hardware (default pins)

WeAct Studio ESP32-S3-N16R8 (16MB flash, 8MB PSRAM). Display: SSD1327 128×128 I2C.

DeviceGPIO
RGB NeoPixel (!led)48
Servo47
Digital out 1 (!set1)6
Digital out 2 (!set2)7
DS18B20 data10
OLED SDA / SCL8 / 9
Touch wake pad4
USB VBUS ADC1

OLED labels: GND, VCC, SCL, SDA. Change pins in minime_config.h if your wiring differs.

Touch wake pad (GPIO 4)

Capacitive pad on GPIO 4 wakes the OLED after dim/off. Connect copper tape or a short wire to GPIO 4; no external pull-up needed. USB VBUS on GPIO 1 (10 kΩ divider) compensates touch thresholds when port voltage sags.

Touch only wakes the OLED - it does not send Discord messages, change Online/Idle, or move GPIO outputs. Serial logging to USB is off (MmLog → web UI only).

Arduino IDE setup

  1. Install Arduino IDE and the Espressif esp32 board package.
  2. Open only MiniMe_Discord_Bot/MiniMe_Discord_Bot.ino (plus the sketch .cpp / .h files and partitions.csv).
  3. Board: ESP32S3 Dev Module; enable USB CDC On Boot (port for upload / OTA; Monitor stays quiet - logs are on the LAN web UI), OPI PSRAM, 16MB flash; use the sketch partitions.csv (dual OTA apps, no filesystem).
  4. Libraries: WebSockets, ArduinoJson, U8g2, OneWire, DallasTemperature, Adafruit NeoPixel, NTPClient.
  5. Copy secrets.example.h to secrets.h and fill in.
  6. Upload. Confirm with Discord !help, the OLED, and http://<board-ip>/.

GitHub Actions compiles the sketch on every push to master (compile check only; does not upload to the board).

Safety

Never commit live bot tokens, API keys, passwords, or Discord snowflake IDs. Reset leaked tokens in the Developer Portal immediately.

Original MiniMe source and docs in the repo are under the MIT License. See LICENSE on GitHub.