Community project

Portable Touchscreen Messenger

ESP32
Photo of Portable Touchscreen Messenger
Generated with AI

Lakatos Botond

Published October 21, 2026

Build a portable touchscreen messenger that lets two ESP32 devices exchange text messages over a private MQTT network. This project combines the ESP32 microcontroller with a battery-powered touchscreen interface, creating a self-contained messaging device that can be carried anywhere within WiFi range.

This guide provides a complete parts list, wiring diagram showing battery and display connections, step-by-step assembly instructions for housing the components, and firmware with MQTT messaging logic and message history storage. Readers will learn how to configure WiFi credentials and MQTT broker settings through the on-screen interface, then deploy two devices as a private messaging pair.

Wiring diagram

Wiring diagram for Portable Touchscreen Messenger

Gather all the parts

QtyComponent
1

LaskaKit LiPol 104050 3.7 V 2500 mAh, JST-PH 2.0

3.7 V, 2500 mAh

A protected flat rechargeable battery that powers one handheld unit and can be unplugged for replacement.

1

JST-PH 2.0 to JST 1.25 mm 2-pin battery adapter cable

5 cm, factory-crimped

A factory-crimped cable that safely mates the battery plug to the display board without cutting or soldering wires.

Assemble it in 5 steps

1. Ellenőrizd a kijelzőpanelt

A csomagolás felnyitása nélkül ellenőrizd a panel feliratát: ES3C35P, 3,5 hüvelykes kapacitív érintőfelület és USB-C csatlakozó legyen rajta. Ne az ESP32-3248S035 nevű, micro-USB-s, nem ESP32-S3 változatot használd.

  • A megfelelő panel hátoldalán külön 1,25 mm-es, kétérintkezős BAT aljzat van.
  • A hasonló nevű kijelzőpanelek eltérő akkumulátor- és érintővezetékezésűek lehetnek; rossz változatra az itt megadott akkumulátorkábel nem biztonságos.

2. Kapcsold össze az akkumulátort

A battery_1 fehér JST-PH 2,0 mm-es dugóját egyenes vonalban nyomd a battery_adapter_1 nagyobb, PH aljzatába. Ne húzd a vezetékénél fogva; a műanyag csatlakozóházat fogd meg.

  • A két csatlakozó csak a megfelelő irányban illeszkedik; ha nem csúszik be kis erővel, ne erőltesd.
  • Ne vágd el, ne csupaszítsd le és ne cseréld fel az akkumulátor vezetékeit — egy zárlatos LiPo akkumulátor felmelegedhet vagy tüzet okozhat.

3. Dugd az akkumulátort a panelbe

A battery_adapter_1 kisebb, 1,25 mm-es dugóját dugd a kijelzőpanel BAT feliratú kétpólusú aljzatába. A piros vezetéknek a panelen jelölt pozitív oldalra kell esnie; a csatlakozó kulcsa vezeti a helyes irányba.

  • A lapot ekkor még ne zárd házba, hogy első bekapcsoláskor láthasd a töltésjelzőt.
  • A VCC és GND felcserélése károsíthatja a panelt és az akkumulátort; ha a kábel polaritása nem egyezik a panel nyomtatott +/− jelölésével, ne csatlakoztasd.

4. Próbáld ki USB-C-ről

Dugd az USB-C kábelt a panel USB-C aljzatába és egy szabványos 5 V-os USB-töltőbe. Ez a csatlakozó programozásra és a beépített töltőáramkörön keresztüli töltésre szolgál.

  • A gyári töltőáram nagyjából 290 mA, ezért egy teljesen lemerült 2500 mAh akkumulátor töltése nem gyors: számolj körülbelül 10–12 órával.
  • Csak sértetlen 5 V-os USB-C tápegységet és kábelt használj; ne töltsd az akkumulátort felügyelet nélkül vagy gyúlékony felületen.

5. Szereld a házba

3D-nyomtatott házban a kijelzőpanelnek hagyj legalább 55 × 102 × 11 mm-es belső helyet, az battery_1-nek pedig külön, sík 52 × 42 × 12 mm-es rekeszt. A cellát vékony habszalaggal fogd le úgy, hogy ne nyomódjon és a csatlakozója elérhető maradjon. Készíts külön USB-C kivágást és csavaros akkufedelet.

  • Az antenna környékét, a panel felső részét ne takard fémlappal vagy akkumulátorral.
  • A jövőbeli rádióhoz tervezz legalább 25 × 40 × 15 mm-es üres rekeszt és egy SMA-antennafuratot.
  • Ne szúrd át, ne hajlítsd meg és ne szorítsd a lapos akkumulátort csavar alá — a sérült LiPo akkumulátort nem szabad használni.

Review all connections

1. Connections between "battery_1" and "ESP32"

Functionbattery_1ESP32
powerBAT+ → JST-PH 2.0 to JST 1.25 mm 2-pin battery adapter cable PH+EXT
groundBAT- → JST-PH 2.0 to JST 1.25 mm 2-pin battery adapter cable PH-EXT

2. Connections between "battery_adapter_1" and "ESP32"

Functionbattery_adapter_1ESP32
powerMX+ → Plugs into the ES3C35P BAT socket positive terminalVIN
groundMX- → Plugs into the ES3C35P BAT socket negative terminalGND

Deploy the firmware

#include <WiFi.h>
#include <Preferences.h>
#include <PubSubClient.h>

// Provision these values through the final on-screen Settings page before field use.
// The broker must be a private TLS-capable MQTT service; do not use a public topic for private messages.
static const char *WIFI_SSID = "";
static const char *WIFI_PASSWORD = "";
static const char *MQTT_HOST = "";
static const uint16_t MQTT_PORT = 1883;
static const char *MQTT_USER = "";
static const char *MQTT_PASSWORD = "";
static const char *DEVICE_ID = "message-device-a";
static const char *PEER_ID = "message-device-b";

WiFiClient transport;
PubSubClient mqtt(transport);
Preferences history;
unsigned long lastReconnectAttempt = 0;

String inboxTopic() {
  return String("private-messenger/") + DEVICE_ID + "/in";
}

String outboxTopic() {
  return String("private-messenger/") + PEER_ID + "/in";
}

void appendHistory(const char *direction, const char *text) {
  String previous = history.getString("history", "");
  String line = String(direction) + ": " + text + "\n";
  previous += line;
  if (previous.length() > 3500) {
    previous.remove(0, previous.length() - 3500);
  }
  history.putString("history", previous);
}

void onMqttMessage(char *topic, byte *payload, unsigned int length) {
  String message;
  message.reserve(length);
  for (unsigned int i = 0; i < length; ++i) {
    message += static_cast<char>(payload[i]);
  }
  appendHistory("BE", message.c_str());
  Serial.printf("Uj uzenet [%s]: %s\n", topic, message.c_str());
  // The production display task reads the same stored history and redraws only its message-list region.
}

bool connectMqtt() {
  if (strlen(MQTT_HOST) == 0 || strlen(MQTT_USER) == 0) {
    Serial.println("MQTT nincs beallitva; a vegleges kezelofeluletben add meg a privat broker adatait.");
    return false;
  }
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMqttMessage);
  if (!mqtt.connect(DEVICE_ID, MQTT_USER, MQTT_PASSWORD)) {
    Serial.printf("MQTT kapcsolat sikertelen, hibakod: %d\n", mqtt.state());
    return false;
  }
  mqtt.subscribe(inboxTopic().c_str(), 1);
  Serial.println("MQTT kapcsolat letrejott.");
  return true;
}

bool sendMessage(const String &text) {
  if (!mqtt.connected() && !connectMqtt()) {
    return false;
  }
  bool sent = mqtt.publish(outboxTopic().c_str(), text.c_str(), false);
  if (sent) {
    appendHistory("EN", text.c_str());
  }
  return sent;
}

void connectWifi() {
  if (strlen(WIFI_SSID) == 0) {
    Serial.println("Wi-Fi nincs beallitva.");
    return;
  }
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  const unsigned long started = millis();
  while (WiFi.status() != WL_CONNECTED && millis() - started < 15000) {
    delay(100);
  }
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print("Wi-Fi kapcsolodva, IP: ");
    Serial.println(WiFi.localIP());
  } else {
    Serial.println("Wi-Fi kapcsolat sikertelen.");
  }
}

void setup() {
  Serial.begin(115200);
  history.begin("messages", false);
  Serial.println("ESP32-S3 uzenetkuldo kommunikacios mag indul.");
  connectWifi();
  if (WiFi.status() == WL_CONNECTED) {
    connectMqtt();
  }
}

void loop() {
  if (WiFi.status() != WL_CONNECTED && strlen(WIFI_SSID) > 0) {
    WiFi.reconnect();
  }
  if (WiFi.status() == WL_CONNECTED) {
    if (!mqtt.connected() && millis() - lastReconnectAttempt > 5000) {
      lastReconnectAttempt = millis();
      connectMqtt();
    }
    mqtt.loop();
  }
  delay(20);
}

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik