Iṣẹ́ ESP32: Kọ́ Ẹ̀rọ̀ Ìfọ̀rọ̀wérọ̀ LoRa Tó Ni Ààbò, Tó Gùn Jìnà Heltec WiFi LoRa 32 lórí N33
Kọ́ Ẹ̀rọ LoRa Tó Ni Ààbò, Tí Kò Nílò Nẹ́tíwọọkì, Pẹ̀lú Kíbọ́ọ̀dù Tí O Ṣe Nípàtàkì
Nínú ayé tí ó gbára lé ìsopọ̀ tí kò dáwọ́ dúró, kí ni o ṣe nígbà tí Wi-Fi bá kú, tí iná kú, tí ẹ̀rọ alagbéèrè kò sì ṣiṣẹ́? Iṣẹ́-ọwọ́ yìí kojú ìṣòro yẹn ní pàtó nípa ṣíṣe ẹ̀rọ ìbánisọ̀rọ̀ tí kò nílò nẹ́tíwọọkì, tí ó ní agbára láti rán àti gba ifiranṣẹ́ ọ̀rọ̀ tó ní ààbò, tí ó ṣìkáàwé, lórí iwọ̀n tó tó 13 maili (21km) tàbí jù bẹ́ẹ̀ lọ.
Ẹ̀rọ yìí jẹ́ ohun èlò tí ó wúlò fún ìmúrasílẹ̀ pàjáwìrì, àwọn ìrìn-àjò òde bíi gígun òkè, tàbí fún ẹnikẹ́ni tí ó nífẹ̀ẹ́ sí kíkọ́ nẹ́tíwọọkì ìbánisọ̀rọ̀ aládàáni tí ó lágbára tí wọ́n ń darí. Ní lílo ẹ̀rọ Heltec WiFi LoRa 32 V3 tí ó lágbára tí a kó sínú àpótí Meshnology N33 tí ó ṣe wọ́n ní ojú ọjọ́ pẹ̀lú bátìrì 3000mAh tí ó pẹ́ títí, iṣẹ́-ọwọ́ yìí jẹ́ èyí tí a kọ́ fún ìlò gidi ní ayé. Ẹ̀yà tí ó ṣe pàtàkì jùlọ ni kíbọ́ọ̀dù rẹ̀—ẹ̀rọ yíyí tí ó rọrùn tí ó pèsè ọ̀nà tí ó yéni mọ́, tí ó sì gbẹ́kẹ̀lé láti tẹ ifiranṣẹ́ ní ibikíbi, nígbàkígbà.
Bí Ó Ṣe ń Ṣiṣẹ́: Iṣẹ́ Ìyanu ti LoRa àti Ẹ̀rọ Yíyí
Iṣẹ́-ọwọ́ yìí darapọ̀ àwọn èròǹgbà pàtàkì mẹ́ta láti ṣẹ̀dá ẹ̀rọ ìbánisọ̀rọ̀ tí ó ṣiṣẹ́:
- Rédíò LoRa: LoRa (Long Range) jẹ́ ìmọ̀-ẹ̀rọ rédíò tí ó gba ìbánisọ̀rọ̀ láàyè lórí iwọ̀n jíjìn púpọ̀ pẹ̀lú agbára díẹ̀. Kò dàbí Wi-Fi tàbí ẹ̀rọ alagbéèrè, ó ń ṣiṣẹ́ lórí àwọn ìgbì rédíò tí kò nílò ìwé-àṣẹ́, tí ó túmọ̀ sí pé kò sí SIM káàdì tàbí owó oṣooṣù tí ó kan. Ẹ̀rọ wa ṣiṣẹ́ gẹ́gẹ́ bí olùgbé-rán ni kíkún, tí ó túmọ̀ sí pé ó lè rán àti gba ifiranṣẹ́. Nígbà tí kò bá ṣiṣẹ́, ó ń fetí sí àwọn pákẹ́tì tí ń bọ̀. Lẹ́yìn tí ó bá rán tàbí gba ifiranṣẹ́, ó mú rédíò sun oorun fún ìgbà díẹ̀ láti fi pamọ́ agbára kí ó tó padà sí ipò gbígbọ́.
- Kíbọ́ọ̀dù Ẹ̀rọ Yíyí: Dípò kíbọ́ọ̀dù tí ó tóbi tàbí tí ó ń jẹ agbára púpọ̀, a lo ẹ̀rọ yíyí tí ó rọrùn fún kíkọ ọ̀rọ̀. Yíyí kọ́bọ́ọ̀dù náà yípo gbogbo àwọn lẹ́tà (A-Z, a-z, àwọn nọ́ńbà, àti àwọn àmì). Ojú-ìhù tí ó wà níbẹ̀ yára gan-an, tí ó sì ń fèsì, tí ó gba ọ́ láàyè láti kọ ifiranṣẹ́ ní ìrọ̀rùn. Bọ́tìnnì tí a kọ́ sínú ẹ̀rọ yíyí náà ní iṣẹ́ mẹ́ta:
- Títẹ̀ Kúkúrú (Click): Fi lẹ́tà tí o yàn kún ifiranṣẹ́ rẹ.
- Títẹ̀ Gùn (Hold): Fi àlàfo kún, bíi bọ́tìnnì àlàfo.
- Títẹ̀ Ẹ̀ẹ̀mejì: Ṣiṣẹ́ bíi bọ́tìnnì ìparẹ́ láti pa lẹ́tà tó kẹ́yìn.
- Ìmọ̀-ẹ̀rọ Olùgbé-rán & Ojú-ìhù: Ẹ̀rọ náà ní ipò pàtàkì méjì: "Kíkọ" àti "Kíkà". O kọ ifiranṣẹ́ rẹ ní ipò Kíkọ. Nígbà tí ifiranṣẹ́ tuntun bá dé, ìkéde yóò farahàn lórí ojú-ìhù (
>> NEW MSG <<). O lè tẹ Bọ́tìnnì Olùmúlò (User Button) tí ó wà lórí ẹ̀rọ náà (lórí Heltec board) láti yí padà sí ipò Kíkà àti wo ifiranṣẹ́ náà. Títẹ̀ mìíràn yóò yí ọ́ padà sí ipò Kíkọ láti kọ èsì rẹ. Gbogbo àwọn ifiranṣẹ́ ni a dáàbò bò pẹ̀lú ìmọ̀-ẹ̀rọ AES, tí ó ń ri i pé àwọn ìjíròrò rẹ wà ní àṣírí.
Àwọn Ẹ̀yà àti Àkójọ Àwọn Ohun Èlò
Láti kọ́ iṣẹ́-ọwọ́ yìí, o ní láti ní àwọn ẹ̀yà wọ̀nyí. Gẹ́gẹ́ bí a ti sọ nínú fídíò, ríra nípa àwọn ọ̀nà àjọṣepọ̀ (affiliate links) jẹ́ ọ̀nà tí ó dára láti ṣètìlẹ́yìn fún àwọn olùṣẹ̀dá.
- Heltec WiFi LoRa 32 V3 Module
- Meshtastic N33 Case pẹ̀lú Bátìrì 3000mAh
- 915MHz tàbí Antenna LoRa tí ó bá àgbègbè mu
- Ẹ̀rọ Yíyí Module (5-pin pẹ̀lú VCC, GND, CLK, DT, SW)
- Ìṣààyàn (fún ìkìlọ̀ ohùn):
- 5V Active Buzzer
- 2N2222 NPN Transistor
- 1kΩ Resistor
- Pin Headers àti Jumper Wires
- Iron Títà àti àwọn ohun èlò rẹ̀
Ṣíṣètò Arduino IDE Rẹ
Kí o tó lè gbé kóòdù náà sílẹ̀, o ní láti ṣètò Arduino IDE pẹ̀lú àwọn board àti àwọn ilé-ìkàwé tí ó tọ́. Ilana yìí ni a ṣàlàyé nínú fídíò náà láti ìgbà 05:38.
Ìgbésẹ̀ 1: Fi àwọn ESP32 Boards sílẹ̀
Tí o kò tíì ṣe, o ní láti fi àtìlẹ́yìn fún àwọn microcontrollers ESP32 kún.
- Ṣí Arduino IDE, lọ sí
File > Preferences. - Nínú pápá "Additional boards manager URLs", fi URL wọ̀nyí kún:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Lọ sí
Tools > Board > Boards Manager.... - Wá
esp32kí o sì fi package náà sílẹ̀ nípa Espressif Systems.
Ìgbésẹ̀ 2: Fi Àtìlẹ́yìn Heltec ESP32 Board Kún
- Nínú
File > Preferences, fi Heltec JSON URL kún lórí ìlà tuntun nínú àpótí "Additional boards manager URLs" (fídíò náà fi bí a ṣe lè ṣe èyí hàn ní 06:27):https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json - Padà sí
Tools > Board > Boards Manager.... - Wá
Heltec ESP32kí o sì fi package náà sílẹ̀.
Ìgbésẹ̀ 3: Fi Àwọn Ilé-Ìkàwé Tí A Nílò sílẹ̀ Láti Library Manager
Ní lílo Arduino Library Manager (Sketch > Include Library > Manage Libraries...), wá kí o sì fi àwọn ilé-ìkàwé wọ̀nyí sílẹ̀:
- Ai Esp32 Rotary Encoder: Ilé-ìkàwé pàtàkì tí a lò fún kíbọ́ọ̀dù wa tí ó ń fèsì.
- Heltec ESP32 Dev-Boards: Ilé-ìkàwé ìṣe àkànṣe fún ohun èlò Heltec.
- Adafruit GFX Library: Ohun tí ó gbẹ́kẹ̀lé fún ojú-ìhù OLED.
Ìgbésẹ̀ 4: Fi Robojax Helper Library sílẹ̀ Láti .ZIP
Gẹ́gẹ́ bí a ti ṣàlàyé nínú fídíò ní 18:53, a lo ilé-ìkàwé olùrànlọ́wọ́ àkànṣe fún iṣẹ́-ọwọ́ yìí. O gbọ́dọ̀ fi sílẹ̀ láti inú faili .zip.
- Ṣe ìgbàlódé ilé-ìkàwé .zip "Robojax HealthTech Lora 32" láti ọ̀nà tí a pèsè lórí ojú-ìwé àwọn ohun èlò.
- Nínú Arduino IDE, tẹ̀
Sketch > Include Library > Add .ZIP Library.... - Lọ sí ibi tí o ti fi faili .zip tí o gbà sílẹ̀, yàn án, kí o sì tẹ "Open".
- Ilé-ìkàwé náà yóò ti sílẹ̀, tí ó sì ṣetán láti lò.
Lẹ́yìn tí o bá ti fi ohun gbogbo sílẹ̀, rí i dájú pé o yan bọ́ọ̀dù tí ó tọ́ láti inú àkójọpọ̀: Tools > Board > Heltec ESP32 Arduino > Heltec WiFi LoRa 32 V3.
Ṣíṣopọ̀ Àwọn Ẹ̀yà
O ní láti fi solder àwọn pin header sí bọ́ọ̀dù Heltec rẹ láti lè sopọ̀ rotary encoder. Gẹ́gẹ́ bí a ti fi hàn nínú fídíò (11:25), a ní láti ṣètò dáradára láti rí i dájú pé àwọn pin wà ní ibi tí a lè wọ̀ wọ́n nígbà tí bọ́ọ̀dù bá wà nínú àpótí rẹ̀.

Àwòrán 1: Core Messenger (Rotary Encoder Nìkan)
Ṣopọ̀ àwọn pin 5 láti inú rotary encoder module sí bọ́ọ̀dù Heltec gẹ́gẹ́ bí àtẹ̀yìnwá:
GNDlórí encoder →GNDlórí bọ́ọ̀dù HeltecVCClórí encoder →3.3Vlórí bọ́ọ̀dù HeltecSW(Switch) →GPIO 4DT(Data/Pin B) →GPIO 6CLK(Clock/Pin A) →GPIO 5

Àwòrán 2: Fífi Ohùn Ìkìlọ̀ Kún Un (Buzzer Circuit)
Láti gba ohùn beep nígbà tí ifiranṣẹ́ tuntun bá dé, o lè fi ìyípo transistor kan kún un. Èyí pọn dandan nítorí pé àwọn pin ESP32 kò lè pèsè iná tó tó láti darí buzzer taara.
- Ṣopọ̀
GPIO 40láti bọ́ọ̀dù Heltec sí opin kan ti resistor 1kΩ. - Ṣopọ̀ opin kejì resistor náà sí pin Base ti transistor 2N2222.
- Ṣopọ̀ pin Emitter ti transistor náà sí
GND. - Ṣopọ̀ ẹsẹ̀ positive (+) ti buzzer 5V sí pin
3.3Vlórí bọ́ọ̀dù Heltec. - Ṣopọ̀ ẹsẹ̀ negative (-) ti buzzer sí pin Collector ti transistor náà.
Òye àti Ṣíṣe Àtúnṣe Kóòdù
Kóòdù tí a pèsè ti ṣetán láti gbé sílẹ̀, ṣùgbọ́n àwọn ètò pàtàkì kan wà tí o yẹ kí o mọ̀ kí o lè ṣe àtúnṣe iṣẹ́-ọnà náà. Àwọn ìtumọ̀ wọ̀nyí wà ní òkè kóòdù náà.
// Defines for physical pin connections
#define ROTARY_ENCODER_A_PIN 5 //pin A of rotary encoder
#define ROTARY_ENCODER_B_PIN 6 //pin B of rotary encoder
#define ROTARY_ENCODER_BUTTON_PIN 4 //switch pin of rotary encoder
#define BUZZER_PIN 40 //the buzzer pin or relay to be triggered
const bool DEBUG = false;
// Defines for LoRa and Security settings
#define RF_FREQUENCY 915432000 //operating frequency of LoRa
#define TX_OUTPUT_POWER 2 //output power should be between 2 to 21 in dBm.
const char *userKey = "xxxBg^Tr%43232";//security key for private communication
ROTARY_ENCODER_...: Àwọn pin wọ̀nyí ṣàlàyé ibi tí o ti sopọ̀ rotary encoder rẹ. Rí i dájú pé àwọn nọ́ńbà wọ̀nyí bá àwọn ìsopọ̀ rẹ mu láti àwọn àwòrán lókè.BUZZER_PIN: Èyí ṣàlàyé èyí tí yóò mú buzzer circuit ṣiṣẹ́.RF_FREQUENCY: Èyí ni ìgbohùnsáfẹ́fẹ́ LoRa ní Hertz. Iye 915432000 (915 MHz) jẹ́ ìwọ̀n fún Àríwá Amẹ́ríkà. O gbọ́dọ̀ yí iye yìí padà gẹ́gẹ́ bí ẹkùn rẹ (fún àpẹẹrẹ, 868MHz fún Yúróòpù, 433MHz fún Éṣíà). Gbogbo àwọn ẹ̀rọ tí wọ́n fẹ́ bára wọn sọ̀rọ̀ gbọ́dọ̀ lo ìgbohùnsáfẹ́fẹ́ kan náà.TX_OUTPUT_POWER: Èyí ni agbára ìgbóhùnsáfẹ́fẹ́ ní dBm. Ó lè wà láàárín 2 sí 20. Nọ́ńbà tí ó kéré bíi2dára fún ìdánwò ọ̀nà kúkúrú ó sì ń fi ààyè bátì pàtàkì pamọ́. Fún ìwọ̀n tí ó pọ̀ jùlọ, o lè pọ̀ sí i, ṣùgbọ́n rántí pé agbára gíga ń dín bátì kù ní kíákíá.userKey: Èyí ni bọ́tìnnì ìpamọ́ ikọ̀kọ̀ rẹ. Ó lè jẹ́ àkópọ̀ ohun kíkọ̀ kan. Gbogbo àwọn ẹ̀rọ gbọ́dọ̀ ní bọ́tìnnì kan náà gangan láti lè tú àwọn ifiranṣẹ́ ara wọn. Yí èyí padà sí ọ̀rọ̀ ìpamọ́ tí ó yàtọ̀ àti àṣírí rẹ.
Gba Kóòdù Náà
Kóòdù Arduino tí ó pé àti tí ó gbẹ̀yìn fún iṣẹ́-ọnà yìí, gẹ́gẹ́ bí a ti tọ́ka sí nínú fídíò, wà fún ìgbàlẹ̀ lórí ojú-ewé yìí.
Àwọn Abala Fídíò
- 00:00 - Ìfáàrà
- 00:56 - Àkọlé Iṣẹ́-ọnà
- 01:29 - Àkópọ̀ Àwọn Ẹ̀yà
- 05:25 - Ṣíṣí Àpótí àti Ìkórajọ Ohun Èlò
- 11:19 - Àtúnṣe Àpótí fún Àwọn Okun Ìta
- 15:06 - Ìtọ́sọ́nà Ìsopọ̀
- 15:38 - Ìṣètò Arduino IDE àti Library
- 20:21 - Àlàyé Àwọn Ètò Kóòdù
- 21:38 - Bí a Ṣe Lo Rotary Encoder Láti Bára Wa Sọ̀rọ̀
- 24:26 - Ìdánwò Ìwọ̀n Láàyè
/*
ESP32 Project: Build a Secure, Long-Range LoRa Chat Device Heltec WiFi LoRa 32
File: Heltec_ESP32_LoRa_V3_chat_TX_RX.ino
written on 29 Jun, 2025 by Ahmad Shamshiri www.Roboajx.com
* Please watch the tutorial video at: https://youtu.be/IsOSvrF60J4
* Resources page for this video: https://robojax.com/RJT642
* =====================================================================
* ARDUINO CODE DESCRIPTION: SECURE LoRa Keyboard Transceiver (chat system)
* =====================================================================
*
* HARDWARE COMPONENTS:
* -------------------
* - Main Controller: Heltec WiFi LoRa 32 V3
* - Enclosure: Meshnology N33 case with 3000mAh battery
* - Input: Rotary encoder with push-button
* - Feedback: Built-in OLED display
*
* SYSTEM FUNCTIONALITY:
* -------------------
NEW CONTROLS:
- Rotate Knob: Select character.
- Short Press (Click): Add selected character.
- Long Press (Hold): Insert a space (like a spacebar).
- Double Click: Backspace.
*
* [2] SECURE WIRELESS TRANSMISSION:
* - All message encrypted before LoRa transmission
* - Home position command transmitted as special secure packet
* - Uses 433MHz/915MHZ LoRa band for reliable communication
*
* [3] POWER MANAGEMENT:
* - Optimized for battery operation (3000mAh)
* - Low-power modes between transmissions
*
* FOR COMPLETE SETUP INSTRUCTIONS:
* Please watch the tutorial video at: https://youtu.be/IsOSvrF60J4
* Resources page for this video: https://robojax.com/RJT642
* =====================================================================
* DISCLAIMER:
* This code is provided "AS IS" without warranty of any kind. The author
* shall not be held liable for any damages arising from the use of this code.
*
* LICENSE:
* This work is licensed under the GNU General Public License v3.0
* Permissions beyond the scope of this license may be available at Robojax.com
*
* SHARING TERMS:
* You are free to share, copy and modify this code for non-commercial purposes
* PROVIDED you:
* 1. Keep this entire comment block intact with the original code
* 2. Include the original Robojax.com link
* 3. Keep the YouTube tutorial link (if applicable)
* 4. Clearly indicate any modifications made
*
*
* ********************************************************************
*/
// Core Heltec and display libraries
#include <Wire.h>
#include "HT_SSD1306Wire.h"
#include "WiFi.h"
// LoRa and security libraries
#include "LoRaWan_APP.h"
#include "mbedtls/aes.h"
#include <cstring>
// The user-approved Rotary Encoder library
#include "AiEsp32RotaryEncoder.h"
// --- PIN DEFINITIONS & CONFIGURATION ---
static SSD1306Wire display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
#define ROTARY_ENCODER_A_PIN 5 //pin A of rotary encoder
#define ROTARY_ENCODER_B_PIN 6 //pin B of rotary encoder
#define ROTARY_ENCODER_BUTTON_PIN 4 //switch pin of rotary encoder
#define RF_FREQUENCY 915432000 //operating frequey of LoRa
#define TX_OUTPUT_POWER 20 //output power should be between 2 to 21 in dBm.
const char *userKey = "wahJ8uy$gt~_3r";//secureity key for private communication
#define ROTARY_ENCODER_VCC_PIN -1
#define ROTARY_ENCODER_STEPS 4
// --- LORA PARAMETERS ---
#define LORA_BANDWIDTH 0
#define LORA_SPREADING_FACTOR 7
#define LORA_CODINGRATE 1
#define LORA_PREAMBLE_LENGTH 8
#define LORA_SYMBOL_TIMEOUT 0
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define BUFFER_SIZE 256
#define USER_BUTTON_PIN 0
// --- GLOBAL VARIABLES & STATE ---
enum AppState { COMPOSING, READING };
AppState currentState = COMPOSING;
String messageToCompose = "";
String lastReceivedMessage = "No messages yet.";
bool hasUnreadMessage = false;
volatile bool newPacketAvailable = false;
char rxpacket[BUFFER_SIZE];
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, ROTARY_ENCODER_VCC_PIN, ROTARY_ENCODER_STEPS);
const char* charset = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,!?>";
char selectedChar;
static RadioEvents_t RadioEvents;
bool lora_idle = true;
mbedtls_aes_context aes;
unsigned long shortPressAfterMiliseconds = 50;
unsigned long longPressAfterMiliseconds = 1000;
unsigned long doubleClickTimeout = 400;
static unsigned long buttonDownTime = 0;
static unsigned long buttonUpTime = 0;
static bool buttonWasDown = false;
static bool doubleClickWaiting = false;
// --- FUNCTION PROTOTYPES ---
void OnTxDone(void);
void OnTxTimeout(void);
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr);
void encryptAES(uint8_t *data, const char *key);
void decryptAES(uint8_t *data, const char *key);
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize);
void updateDisplay();
void sendData();
void VextON(void);
void processButton();
// --- INTERRUPT SERVICE ROUTINES ---
void IRAM_ATTR readEncoderISR() {
rotaryEncoder.readEncoder_ISR();
}
// --- BUTTON HANDLER ACTIONS ---
void on_button_short_click() {
if (currentState != COMPOSING) return;
if (selectedChar == '>') {
sendData();
} else {
messageToCompose += selectedChar;
}
updateDisplay();
}
void on_button_long_click() {
if (currentState != COMPOSING) return;
messageToCompose += " ";
updateDisplay();
}
void on_button_double_click() {
if (currentState != COMPOSING) return;
if (messageToCompose.length() > 0) {
messageToCompose.remove(messageToCompose.length() - 1);
}
updateDisplay();
}
// --- SETUP ---
void setup() {
Serial.begin(115200);
Serial.println("LoRa Keyboard Transceiver - Definitive Version");
VextON();
delay(100);
Mcu.begin(HELTEC_BOARD,SLOW_CLK_TPYE);
WiFi.mode(WIFI_OFF);
btStop();
display.init();
display.setFont(ArialMT_Plain_10);
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
rotaryEncoder.setBoundaries(0, strlen(charset) - 1, true);
rotaryEncoder.disableAcceleration();
pinMode(USER_BUTTON_PIN, INPUT_PULLUP);
RadioEvents.TxDone = OnTxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxDone = OnRxDone;
Radio.Init(&RadioEvents);
Radio.SetChannel(RF_FREQUENCY);
Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true);
updateDisplay();
}
// --- MAIN LOOP ---
void loop() {
if(lora_idle) {
lora_idle = false;
Serial.println("Entering RX mode...");
Radio.Rx(0);
}
Radio.IrqProcess();
if (newPacketAvailable) {
newPacketAvailable = false;
hasUnreadMessage = true;
lastReceivedMessage = String(rxpacket);
Serial.print("Received Decrypted Message: ");
Serial.println(lastReceivedMessage);
updateDisplay();
}
if (currentState == COMPOSING) {
if (rotaryEncoder.encoderChanged()) {
updateDisplay();
}
processButton();
}
if (digitalRead(USER_BUTTON_PIN) == LOW) {
delay(50);
if (digitalRead(USER_BUTTON_PIN) == LOW) {
if (currentState == COMPOSING) {
if (lastReceivedMessage != "No messages yet.") {
currentState = READING;
hasUnreadMessage = false;
updateDisplay();
}
} else {
currentState = COMPOSING;
updateDisplay();
}
while (digitalRead(USER_BUTTON_PIN) == LOW);
}
}
delay(10);
}
// --- LORA EVENT HANDLERS & HELPERS ---
void OnTxDone(void) {
Serial.println("TX done.");
Radio.Sleep();
lora_idle = true;
}
void OnTxTimeout(void) {
Serial.println("TX Timeout.");
Radio.Sleep();
lora_idle = true;
}
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr) {
Radio.Sleep();
memset(rxpacket, 0, sizeof(rxpacket));
memcpy(rxpacket, payload, size > BUFFER_SIZE ? BUFFER_SIZE : size);
decryptAES((uint8_t*)rxpacket, userKey);
newPacketAvailable = true;
lora_idle = true;
}
void sendData() {
if (messageToCompose.length() > 0) {
lora_idle = false;
Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE,
LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, LORA_IQ_INVERSION_ON, 3000);
uint8_t data[BUFFER_SIZE];
memset(data, 0, BUFFER_SIZE);
strncpy((char*)data, messageToCompose.c_str(), BUFFER_SIZE - 1);
encryptAES(data, userKey);
Serial.print("Sending message: ");
Serial.println(messageToCompose);
Radio.Send(data, 16);
messageToCompose = "";
}
}
// --- DISPLAY & BUTTON LOGIC ---
void updateDisplay() {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
if (currentState == COMPOSING) {
selectedChar = charset[rotaryEncoder.readEncoder()];
display.setFont(ArialMT_Plain_10);
// --- THIS IS THE FIX for the notification ---
String msgLabel;
if (hasUnreadMessage) {
msgLabel = ">> NEW MSG <<"; // Make the notification very obvious
} else {
msgLabel = "Msg:";
}
display.drawStringMaxWidth(0, 0, 128, msgLabel + " " + messageToCompose);
display.setFont(ArialMT_Plain_16);
int16_t labelWidth = display.getStringWidth("Select: ");
display.drawString(0, 35, "Select: ");
display.setFont(ArialMT_Plain_24);
display.drawString(labelWidth, 32, String(selectedChar));
} else { // currentState == READING
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Received:");
display.setFont(ArialMT_Plain_10);
display.drawStringMaxWidth(0, 18, 128, lastReceivedMessage);
}
display.display();
}
void processButton() {
if (doubleClickWaiting && (millis() - buttonUpTime > doubleClickTimeout)) {
doubleClickWaiting = false;
on_button_short_click();
}
bool isButtonDown = rotaryEncoder.isEncoderButtonDown();
if (isButtonDown && !buttonWasDown) {
buttonDownTime = millis();
buttonWasDown = true;
}
else if (!isButtonDown && buttonWasDown) {
unsigned long pressDuration = millis() - buttonDownTime;
if (pressDuration >= longPressAfterMiliseconds) {
on_button_long_click();
doubleClickWaiting = false;
}
else if (pressDuration >= shortPressAfterMiliseconds) {
if (doubleClickWaiting) {
on_button_double_click();
doubleClickWaiting = false;
} else {
doubleClickWaiting = true;
buttonUpTime = millis();
}
}
buttonWasDown = false;
}
}
// --- UTILITIES ---
void VextON(void) {
pinMode(Vext,OUTPUT);
digitalWrite(Vext, LOW);
}
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize) {
memset(processedKey, 0, keySize);
size_t len = strlen(userKey);
if (len > keySize) len = keySize;
memcpy(processedKey, userKey, len);
}
void encryptAES(uint8_t *data, const char *key) {
uint8_t processedKey[16];
processKey(key, processedKey, 16);
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, processedKey, 128);
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, data, data);
mbedtls_aes_free(&aes);
}
void decryptAES(uint8_t *data, const char *key) {
uint8_t processedKey[16];
processKey(key, processedKey, 16);
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_dec(&aes, processedKey, 128);
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_DECRYPT, data, data);
mbedtls_aes_free(&aes);
}
/*
ESP32 Project: Build a Secure, Long-Range LoRa Chat Device Heltec WiFi LoRa 32
File: Heltec_ESP32_LoRa_V3_chat_TX_RX_buzzer.ino
written on 29 Jun, 2025 by Ahmad Shamshiri www.Roboajx.com
* Please watch the tutorial video at: https://youtu.be/IsOSvrF60J4
* Resources page for this video: https://robojax.com/RJT642
* =====================================================================
* ARDUINO CODE DESCRIPTION: SECURE LoRa Keyboard Transceiver (chat system)
* =====================================================================
*
* HARDWARE COMPONENTS:
* -------------------
* - Main Controller: Heltec WiFi LoRa 32 V3
* - Enclosure: Meshnology N33 case with 3000mAh battery
* - Input: Rotary encoder with push-button
* - Audiable: Active buzzer (connectedt o pin 40)
* - Feedback: Built-in OLED display
*
* SYSTEM FUNCTIONALITY:
* -------------------
NEW CONTROLS:
- Rotate Knob: Select character.
- Short Press (Click): Add selected character.
- Long Press (Hold): Insert a space (like a spacebar).
- Double Click: Backspace.
*
* [2] SECURE WIRELESS TRANSMISSION:
* - All message encrypted before LoRa transmission
* - Home position command transmitted as special secure packet
* - Uses 433MHz/915MHZ LoRa band for reliable communication
*
* [3] POWER MANAGEMENT:
* - Optimized for battery operation (3000mAh)
* - Low-power modes between transmissions
*
* FOR COMPLETE SETUP INSTRUCTIONS:
* Please watch the tutorial video at: https://youtu.be/IsOSvrF60J4
* Resources page for this video: https://robojax.com/RJT642
* =====================================================================
* DISCLAIMER:
* This code is provided "AS IS" without warranty of any kind. The author
* shall not be held liable for any damages arising from the use of this code.
*
* LICENSE:
* This work is licensed under the GNU General Public License v3.0
* Permissions beyond the scope of this license may be available at Robojax.com
*
* SHARING TERMS:
* You are free to share, copy and modify this code for non-commercial purposes
* PROVIDED you:
* 1. Keep this entire comment block intact with the original code
* 2. Include the original Robojax.com link
* 3. Keep the YouTube tutorial link (if applicable)
* 4. Clearly indicate any modifications made
*
*
* ********************************************************************
*/
// Core Heltec and display libraries
#include <Wire.h>
#include "HT_SSD1306Wire.h"
#include "WiFi.h"
// LoRa and security libraries
#include "LoRaWan_APP.h"
#include "mbedtls/aes.h"
#include <cstring>
// The user-approved Rotary Encoder library
#include "AiEsp32RotaryEncoder.h"
// --- PIN DEFINITIONS & CONFIGURATION ---
static SSD1306Wire display(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED);
#define ROTARY_ENCODER_A_PIN 5 //pin A of rotary encoder
#define ROTARY_ENCODER_B_PIN 6 //pin B of rotary encoder
#define ROTARY_ENCODER_BUTTON_PIN 4 //switch pin of rotary encoder
#define BUZZER_PIN 40 //the buzzer pin or relay to be triggered
const bool DEBUG = false;
#define RF_FREQUENCY 915432000 //operating frequency of LoRa
#define TX_OUTPUT_POWER 2 //output power should be between 2 to 21 in dBm.
const char *userKey = "wahJ8uy$gt~_3r";//secureity key for private communication
#define ROTARY_ENCODER_VCC_PIN -1
#define ROTARY_ENCODER_STEPS 4
// --- LORA PARAMETERS ---
#define LORA_BANDWIDTH 0
#define LORA_SPREADING_FACTOR 7
#define LORA_CODINGRATE 1
#define LORA_PREAMBLE_LENGTH 8
#define LORA_SYMBOL_TIMEOUT 0
#define LORA_FIX_LENGTH_PAYLOAD_ON false
#define LORA_IQ_INVERSION_ON false
#define BUFFER_SIZE 256
#define LOOP_DELAY 10
// Buzzer state variables
unsigned long buzzerStartTime = 0;
bool isBuzzerActive = false;
int beepPhase = 0; // 0=idle, 1=first beep, 2=pause, 3=second beep
#define USER_BUTTON_PIN 0
// --- GLOBAL VARIABLES & STATE ---
enum AppState { COMPOSING, READING };
AppState currentState = COMPOSING;
String messageToCompose = "";
String lastReceivedMessage = "No messages yet.";
bool hasUnreadMessage = false;
volatile bool newPacketAvailable = false;
char rxpacket[BUFFER_SIZE];
AiEsp32RotaryEncoder rotaryEncoder = AiEsp32RotaryEncoder(ROTARY_ENCODER_A_PIN, ROTARY_ENCODER_B_PIN, ROTARY_ENCODER_BUTTON_PIN, ROTARY_ENCODER_VCC_PIN, ROTARY_ENCODER_STEPS);
const char* charset = " ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.,!?>";
char selectedChar;
static RadioEvents_t RadioEvents;
bool lora_idle = true;
mbedtls_aes_context aes;
unsigned long shortPressAfterMiliseconds = 50;
unsigned long longPressAfterMiliseconds = 1000;
unsigned long doubleClickTimeout = 400;
static unsigned long buttonDownTime = 0;
static unsigned long buttonUpTime = 0;
static bool buttonWasDown = false;
static bool doubleClickWaiting = false;
// --- FUNCTION PROTOTYPES ---
void OnTxDone(void);
void OnTxTimeout(void);
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr);
void encryptAES(uint8_t *data, const char *key);
void decryptAES(uint8_t *data, const char *key);
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize);
void updateDisplay();
void sendData();
void VextON(void);
void processButton();
void buzzer();
void updateBuzzer();
// --- INTERRUPT SERVICE ROUTINES ---
void IRAM_ATTR readEncoderISR() {
rotaryEncoder.readEncoder_ISR();
}
// --- BUTTON HANDLER ACTIONS ---
void on_button_short_click() {
if (currentState != COMPOSING) return;
if (selectedChar == '>') {
sendData();
} else {
messageToCompose += selectedChar;
}
updateDisplay();
}
void on_button_long_click() {
if (currentState != COMPOSING) return;
messageToCompose += " ";
updateDisplay();
}
void on_button_double_click() {
if (currentState != COMPOSING) return;
if (messageToCompose.length() > 0) {
messageToCompose.remove(messageToCompose.length() - 1);
}
updateDisplay();
}
// --- SETUP ---
void setup() {
Serial.begin(115200);
Serial.println("LoRa Keyboard Transceiver - By Robojax");
VextON();
delay(100);
Mcu.begin(HELTEC_BOARD,SLOW_CLK_TPYE);
WiFi.mode(WIFI_OFF);
btStop();
display.init();
display.setFont(ArialMT_Plain_10);
rotaryEncoder.begin();
rotaryEncoder.setup(readEncoderISR);
rotaryEncoder.setBoundaries(0, strlen(charset) - 1, true);
rotaryEncoder.disableAcceleration();
pinMode(USER_BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
RadioEvents.TxDone = OnTxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxDone = OnRxDone;
Radio.Init(&RadioEvents);
Radio.SetChannel(RF_FREQUENCY);
Radio.SetRxConfig(MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
0, true, 0, 0, LORA_IQ_INVERSION_ON, true);
updateDisplay();
}
// --- MAIN LOOP ---
void loop() {
if(lora_idle) {
lora_idle = false;
if(DEBUG)
{
Serial.println("Entering RX mode...");
}
Radio.Rx(0);
}
Radio.IrqProcess();
if (newPacketAvailable) {
newPacketAvailable = false;
hasUnreadMessage = true;
lastReceivedMessage = String(rxpacket);
if(DEBUG)
{
Serial.print("Received Decrypted Message: ");
Serial.println(lastReceivedMessage);
}
updateDisplay();
buzzer();
}
if (currentState == COMPOSING) {
if (rotaryEncoder.encoderChanged()) {
updateDisplay();
}
processButton();
}
if (digitalRead(USER_BUTTON_PIN) == LOW) {
delay(50);
if (digitalRead(USER_BUTTON_PIN) == LOW) {
if (currentState == COMPOSING) {
if (lastReceivedMessage != "No messages yet.") {
currentState = READING;
hasUnreadMessage = false;
updateDisplay();
}
} else {
currentState = COMPOSING;
updateDisplay();
}
while (digitalRead(USER_BUTTON_PIN) == LOW);
}
}
updateBuzzer(); // Update buzzer state (non-blocking)
delay(LOOP_DELAY); // Your 10ms loop delay
}
// --- LORA EVENT HANDLERS & HELPERS ---
void OnTxDone(void) {
if(DEBUG)
{
Serial.println("TX done.");
}
Radio.Sleep();
lora_idle = true;
}
void OnTxTimeout(void) {
if(DEBUG)
{
Serial.println("TX Timeout.");
}
Radio.Sleep();
lora_idle = true;
}
void OnRxDone(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr) {
Radio.Sleep();
memset(rxpacket, 0, sizeof(rxpacket));
memcpy(rxpacket, payload, size > BUFFER_SIZE ? BUFFER_SIZE : size);
decryptAES((uint8_t*)rxpacket, userKey);
newPacketAvailable = true;
lora_idle = true;
}
void sendData() {
if (messageToCompose.length() > 0) {
lora_idle = false;
Radio.SetTxConfig(MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
LORA_SPREADING_FACTOR, LORA_CODINGRATE,
LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
true, 0, 0, LORA_IQ_INVERSION_ON, 3000);
uint8_t data[BUFFER_SIZE];
memset(data, 0, BUFFER_SIZE);
strncpy((char*)data, messageToCompose.c_str(), BUFFER_SIZE - 1);
encryptAES(data, userKey);
if(DEBUG)
{
Serial.print("Sending message: ");
Serial.println(messageToCompose);
}
Radio.Send(data, 16);
messageToCompose = "";
}
}
// --- DISPLAY & BUTTON LOGIC ---
void updateDisplay() {
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
if (currentState == COMPOSING) {
selectedChar = charset[rotaryEncoder.readEncoder()];
display.setFont(ArialMT_Plain_10);
// --- THIS IS THE FIX for the notification ---
String msgLabel;
if (hasUnreadMessage) {
msgLabel = ">> NEW MSG <<"; // Make the notification very obvious
} else {
msgLabel = "Msg:";
}
display.drawStringMaxWidth(0, 0, 128, msgLabel + " " + messageToCompose);
display.setFont(ArialMT_Plain_16);
int16_t labelWidth = display.getStringWidth("Select: ");
display.drawString(0, 35, "Select: ");
display.setFont(ArialMT_Plain_24);
display.drawString(labelWidth, 32, String(selectedChar));
} else { // currentState == READING
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Received:");
display.setFont(ArialMT_Plain_10);
display.drawStringMaxWidth(0, 18, 128, lastReceivedMessage);
}
display.display();
}
void processButton() {
if (doubleClickWaiting && (millis() - buttonUpTime > doubleClickTimeout)) {
doubleClickWaiting = false;
on_button_short_click();
}
bool isButtonDown = rotaryEncoder.isEncoderButtonDown();
if (isButtonDown && !buttonWasDown) {
buttonDownTime = millis();
buttonWasDown = true;
}
else if (!isButtonDown && buttonWasDown) {
unsigned long pressDuration = millis() - buttonDownTime;
if (pressDuration >= longPressAfterMiliseconds) {
on_button_long_click();
doubleClickWaiting = false;
}
else if (pressDuration >= shortPressAfterMiliseconds) {
if (doubleClickWaiting) {
on_button_double_click();
doubleClickWaiting = false;
} else {
doubleClickWaiting = true;
buttonUpTime = millis();
}
}
buttonWasDown = false;
}
}
void buzzer()
{
if (!isBuzzerActive) {
isBuzzerActive = true;
buzzerStartTime = millis(); // Start buzzer sequence
beepPhase = 1; // First beep
digitalWrite(BUZZER_PIN, HIGH);
}
}
void updateBuzzer() {
if (!isBuzzerActive) return;
unsigned long currentTime = millis();
unsigned long elapsed = currentTime - buzzerStartTime;
switch (beepPhase) {
case 1: // First beep (300ms ON)
if (elapsed >= 300) {
digitalWrite(BUZZER_PIN, LOW);
buzzerStartTime = currentTime;
beepPhase = 2; // Move to pause
}
break;
case 2: // Pause (200ms OFF)
if (elapsed >= 200) {
digitalWrite(BUZZER_PIN, HIGH);
buzzerStartTime = currentTime;
beepPhase = 3; // Move to second beep
}
break;
case 3: // Second beep (300ms ON)
if (elapsed >= 300) {
digitalWrite(BUZZER_PIN, LOW);
isBuzzerActive = false; // Reset
beepPhase = 0;
}
break;
}
}
// --- UTILITIES ---
void VextON(void) {
pinMode(Vext,OUTPUT);
digitalWrite(Vext, LOW);
}
void processKey(const char *userKey, uint8_t *processedKey, size_t keySize) {
memset(processedKey, 0, keySize);
size_t len = strlen(userKey);
if (len > keySize) len = keySize;
memcpy(processedKey, userKey, len);
}
void encryptAES(uint8_t *data, const char *key) {
uint8_t processedKey[16];
processKey(key, processedKey, 16);
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_enc(&aes, processedKey, 128);
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_ENCRYPT, data, data);
mbedtls_aes_free(&aes);
}
void decryptAES(uint8_t *data, const char *key) {
uint8_t processedKey[16];
processKey(key, processedKey, 16);
mbedtls_aes_init(&aes);
mbedtls_aes_setkey_dec(&aes, processedKey, 128);
mbedtls_aes_crypt_ecb(&aes, MBEDTLS_AES_DECRYPT, data, data);
mbedtls_aes_free(&aes);
}
Common Course Links
Common Course Files
Orisun & itọkasi
-
Ita gbangbaPurchase Wi-Fi LoRa 32 from Meshnologymeshnology.com
Fáìlìs📁
Awọn faili miiran
-
Robojax Heltec WiFi LoRa 32 Library Version 1.1.0 20250703
Robojax_HeltecLoRa32_1.1.0_20250702.zip0.09 MB -