Aikin ESP32: Gina Na’urar LoRa Chat Mai Tsaro, Mai Dogon Kewayon Heltec WiFi LoRa 32 akan N33
Gina Ƙirƙira Na'urar LoRa Mai Tsaro, Ba Tare da Intanet Ba, Tare da Madannai Na Musamman
A cikin duniyar da ke dogaro da sadarwa akai-akai, me za ka yi idan Wi-Fi ya lalace, wutar lantarki ta ƙare, kuma sabis na wayar hannu ya ɓace? Wannan aikin yana magance wannan matsala ta hanyar ƙirƙirar na'urar sadarwa mai cikakken dogaro da kanta, ba tare da intanet ba, wadda ke ba ka damar aika da karɓar saƙon rubutu na sirri, mai tsaro, a nisan kilomita 13 (21km) ko ma fiye da haka.
Wannan na'urar kayan aiki ne mai amfani don shirye-shiryen gaggawa, balaguron daji kamar tafiya, ko kuma ga duk wanda ke sha'awar gina hanyar sadarwa mai ƙarfi, mai zaman kanta wadda ke ƙarƙashin ikonka. Ta amfani da na'urar Heltec WiFi LoRa 32 V3 mai ƙarfi da ke cikin akwati mai ɗorewa na Meshnology N33 tare da baturi mai ƙarfi na 3000mAh, wannan aikin an gina shi ne don amfani a rayuwa ta yau da kullum. Mafi kyawun fasalin shi ne madannai—mai sauya juzu'i mai sauƙi wanda ke ba da hanya mai sauƙi kuma abin dogaro don rubuta saƙonni a ko'ina, a kowane lokaci.
Yadda Yake Aiki: Sihirin LoRa da Mai Sauya Juzu'i
Aikin yana haɗa mahimman ra'ayoyi guda uku don ƙirƙirar na'urar saƙo mai aiki:
- Rediyon LoRa: LoRa (Dogon Zango) fasahar rediyo ce wadda ke ba da damar sadarwa a nisa mai nisa da ƙaramin ƙarfi. Ba kamar Wi-Fi ko wayar hannu ba, tana aiki a kan mitocin rediyo marasa lasisi, ma'ana babu SIM card ko kuɗin wata-wata. Na'urarmu tana aiki a matsayin mai aikawa da karɓa, ma'ana tana iya aika da karɓar saƙonni. Lokacin da ba ta aiki, tana sauraron saƙonni masu shigowa. Bayan aika ko karɓar saƙo, tana sa barci na ɗan lokaci don adana ƙarfi kafin ta koma yanayin sauraro.
- Madannai na Mai Sauya Juzu'i: Maimakon madannai mai girma ko mai amfani da ƙarfi, muna amfani da mai sauya juzu'i mai sauƙi don shigar da rubutu. Juya ƙugiyar yana zagaya cikin cikakken jerin haruffa (A-Z, a-z, lambobi, da alamomi). Hanyar tana da sauri kuma tana amsawa da kyau, tana ba ka damar rubuta saƙonni cikin sauƙi. Maɓallin da ke cikin mai sauya juzu'i yana da ayyuka uku:
- Gajeren Dannawa (Danna): Yana ƙara zaɓaɓɓen harafin a cikin saƙonka.
- Doguwar Dannawa (Riƙe): Yana ƙara sarari, kamar maɓallin sarari.
- Danna Sau Biyu: Yana aiki azaman maɓallin gogewa don share harafin ƙarshe.
- Dabaru na Aikawa da Karɓa da Nuni: Na'urar tana da manyan yanayi biyu: "Rubutu" da "Karatu". Kana rubuta saƙonka a yanayin Rubutu. Lokacin da sabon saƙo ya zo, sanarwa tana bayyana a kan allo (
>> SABON SAƘO <<). Sannan za ka iya danna Maɓallin Mai Amfani (a kan Heltec board) don canzawa zuwa yanayin Karatu don ganin saƙon. Wani dannawa yana mayar da kai zuwa yanayin Rubutu don rubuta amsarka. Duk saƙonni an tsare su ta amfani da AES encryption, yana tabbatar da cewa tattaunawarka ta kasance ta sirri.
Abubuwan Da Ake Bukata da Jerin Kayayyaki
Don gina wannan aikin, za ka buƙaci waɗannan abubuwan. Kamar yadda aka ambata a cikin bidiyon, siyan ta hanyar haɗin haɗin gwiwa hanya ce mai kyau don tallafawa masu ƙirƙira.
- Heltec WiFi LoRa 32 V3 Module
- Meshtastic N33 Case tare da Baturi na 3000mAh
- Eriya LoRa na 915MHz ko wadda ta dace da yankin
- Mai Sauya Juzu'i Module (5-pin tare da VCC, GND, CLK, DT, SW)
- Na Zaɓi (don faɗakarwar sauti):
- Buzzer Mai Aiki na 5V
- Transistor NPN na 2N2222
- Resistor na 1kΩ
- Pin Headers da Jumper Wires
- Ƙarfe na Ƙarfe da kayan aiki
Saita Arduino IDE ɗinka
Kafin ka iya loda lambar, kana buƙatar saita Arduino IDE tare da daidaitattun boards da libraries. Wannan tsari an yi bayani dalla-dalla a cikin bidiyon tun daga 05:38.
Mataki na 1: Shigar da ESP32 Boards
Idan ba ka riga ka yi ba, kana buƙatar ƙara tallafi ga ESP32 microcontrollers.
- Buɗe Arduino IDE, je zuwa
File > Preferences. - A cikin filin "Additional boards manager URLs", ƙara wannan URL:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json - Je zuwa
Tools > Board > Boards Manager.... - Nemo
esp32kuma shigar da package daga Espressif Systems.
Mataki na 2: Ƙara Tallafin Heltec ESP32 Board
- A cikin
File > Preferences, ƙara Heltec JSON URL a sabon layi a cikin akwatin "Additional boards manager URLs" (bidiyon yana nuna yadda ake yin haka a 06:27):https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json - Koma zuwa
Tools > Board > Boards Manager.... - Nemo
Heltec ESP32kuma shigar da package.
Mataki na 3: Shigar da Libraries Da Ake Bukata daga Library Manager
Ta amfani da Arduino Library Manager (Sketch > Include Library > Manage Libraries...), nemo kuma shigar da waɗannan libraries:
- Ai Esp32 Rotary Encoder: Library na musamman da aka yi amfani da shi don madannai mai amsawa.
- Heltec ESP32 Dev-Boards: Library na hukuma don kayan aikin Heltec.
- Adafruit GFX Library: Abin dogaro don nunin OLED.
Mataki na 4: Shigar da Robojax Helper Library daga .ZIP
Kamar yadda aka bayyana a cikin bidiyon a 18:53, ana amfani da library na musamman na taimako don wannan aikin. Dole ne ka shigar da shi daga fayil ɗin .zip.
- Sauke library ɗin "Robojax HealthTech Lora 32" .zip daga haɗin da aka bayar a shafin albarkatu.
- A cikin Arduino IDE, danna
Sketch > Include Library > Add .ZIP Library.... - Je zuwa inda ka ajiye fayil ɗin .zip da ka sauke, zaɓe shi, kuma danna "Open".
- Za a shigar da library ɗin kuma yana shirye don amfani.
Tools > Board > Heltec ESP32 Arduino > Heltec WiFi LoRa 32 V3.
Haɗa Abubuwan Kayan Aiki
Za ka buƙaci ka yi soldering ga pin headers a kan board ɗin Heltec don haɗa rotary encoder ɗin. Kamar yadda aka nuna a bidiyon (11:25), ana buƙatar tsari mai kyau don tabbatar da cewa pins ɗin suna samuwa idan board ɗin ya kasance a cikin akwatin sa.

Zane na 1: Core Messenger (Rotary Encoder Kawai)
Haɗa pins 5 daga rotary encoder module ɗin zuwa board ɗin Heltec kamar haka:
GNDa kan encoder →GNDa kan board ɗin HeltecVCCa kan encoder →3.3Va kan board ɗin HeltecSW(Switch) →GPIO 4DT(Data/Pin B) →GPIO 6CLK(Clock/Pin A) →GPIO 5

Zane na 2: Ƙara Faɗakarwar Sauti (Buzzer Circuit)
Don samun ƙarar sauti lokacin da saƙo ya zo, za ka iya ƙara wani transistor circuit mai sauƙi. Wannan ya zama dole domin pins ɗin ESP32 ba su iya samar da isasshen wutar lantarki don kunna buzzer kai tsaye ba.
- Haɗa
GPIO 40daga board ɗin Heltec zuwa ƙarshen ɗaya na resistor 1kΩ. - Haɗa ƙarshen resistor ɗin zuwa pin ɗin Base na transistor 2N2222.
- Haɗa pin ɗin Emitter na transistor ɗin zuwa
GND. - Haɗa ƙafar positive (+) na buzzer 5V zuwa pin ɗin
3.3Va kan board ɗin Heltec. - Haɗa ƙafar negative (-) na buzzer ɗin zuwa pin ɗin Collector na transistor ɗin.
Fahimta da Keɓance Code ɗin
Code ɗin da aka bayar yana shirye don upload, amma akwai wasu mahimman saituna da ya kamata ka sani don ka iya keɓance aikin. Waɗannan ma'anoni suna a saman code ɗin.
// Ma'anoni don haɗin pin na zahiri
#define ROTARY_ENCODER_A_PIN 5 //pin A na rotary encoder
#define ROTARY_ENCODER_B_PIN 6 //pin B na rotary encoder
#define ROTARY_ENCODER_BUTTON_PIN 4 //pin switch na rotary encoder
#define BUZZER_PIN 40 //pin buzzer ko relay da za a kunna
const bool DEBUG = false;
// Ma'anoni don saitunan LoRa da Tsaro
#define RF_FREQUENCY 915432000 //mitar aiki na LoRa
#define TX_OUTPUT_POWER 2 //ƙarfin fitarwa ya kamata ya kasance tsakanin 2 zuwa 21 a dBm.
const char *userKey = "xxxBg^Tr%43232";//maɓallin tsaro don sadarwa ta sirri
ROTARY_ENCODER_...: Waɗannan pins suna bayyana inda ka haɗa rotary encoder ɗinka. Ka tabbatar waɗannan lambobin sun dace da haɗin da ka yi a zanen da ke sama.BUZZER_PIN: Wannan yana bayyana wane pin zai kunna buzzer circuit ɗin.RF_FREQUENCY: Wannan ita ce mitar aiki ta LoRa a Hertz. Ƙimar 915432000 (915 MHz) daidai ce ga Arewacin Amurka. Dole ne ka canza wannan ƙimar gwargwadon yankinka (misali, 868MHz ga Turai, 433MHz ga Asiya). Duk na'urorin da suke son sadarwa dole ne su yi amfani da mitar ɗaya.TX_OUTPUT_POWER: Wannan ƙarfin watsawa ne a dBm. Yana iya kasancewa daga 2 zuwa 20. Ƙaramin lamba kamar2ya fi dacewa don gwaji na ɗan gajeren zango kuma yana adana batir mai yawa. Don iyakar zango, za ka iya ƙara wannan ƙimar, amma ka sani cewa ƙarfin mafi girma yana rage batir da sauri sosai.userKey: Wannan maɓallin ɓoye na sirri ne. Yana iya zama kowane haɗin haruffa. Duk na'urori dole ne su kasance da maɓalli ɗaya daidai don su iya buɗe saƙon juna. Canza wannan zuwa kalmar sirri ta musamman da ta keɓanta.
Samo Code ɗin
Cikakken kuma na ƙarshe na Arduino code ɗin don wannan aikin, kamar yadda aka ambata a bidiyon, yana samuwa don saukewa a wannan shafin.
Babban Fim ɗin Bidiyo
- 00:00 - Gabatarwa
- 00:56 - Tsarin Aikin
- 01:29 - Duban Abubuwan Kayan Aiki
- 05:25 - Buɗewa da Haɗa Kayan Aiki
- 11:19 - Gyaran Akwati don Wayoyi na Waje
- 15:06 - Jagorar Haɗin Waya
- 15:38 - Saitin Arduino IDE da Library
- 20:21 - Bayanin Saitunan Code
- 21:38 - Yadda Ake Amfani da Rotary Encoder don Taɗi
- 24:26 - Gwajin Zango na Kai Tsaye
/*
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
Albarkatun da kuma shafukan da za a duba
-
WajePurchase Wi-Fi LoRa 32 from Meshnologymeshnology.com
Fayiloli📁
Sauran fayiloli
-
Robojax Heltec WiFi LoRa 32 Library Version 1.1.0 20250703
Robojax_HeltecLoRa32_1.1.0_20250702.zip0.09 MB -