د ESP32 پروژه: د خوندي، اوږد واټن لورا چیٹ وسیله جوړول Heltec WiFi LoRa 32 په N33 باندې
یو خوندي، له گرډ څخه بهر لورا مېسنجر د دودیزې کیبورډ سره جوړ کړئ
په داسې نړۍ کې چې پر دوامداره اړیکو تکیه کوي، کله چې وای-فای بند وي، برېښنا لاړه وي، او سیلولر خدمت ختم شوی وي، تاسو څه کوئ؟ دا پروژه همدا ستونزه حل کوي د یوې بشپړې ځان بسيا، له گرډ څخه بهر د اړیکو د وسیلې په جوړولو سره چې تاسو ته اجازه درکوي خوندي او شخصي متن پيغامونه تر ۱۳ مایله (۲۱ کیلومتره) یا حتی زیات واټن پورې واستوئ او ترلاسه کړئ.
دا وسیله د بیړنیو حالاتو لپاره یوه عملي وسیله ده، د بهرنیو سفرونو لکه پیدل تګ لپاره، یا په ساده ډول د هر هغه چا لپاره چې غواړي یوه پیاوړې، شخصي اړیکو شبکه جوړه کړي چې کنټرول یې د هغوی په لاس کې وي. د یو پیاوړي Heltec WiFi LoRa 32 V3 ماډل په کارولو سره چې د پایښت لرونکي Meshnology N33 کېس دننه د اوږد مهاله 3000mAh بیټرۍ سره ډک شوی، دا پروژه د ریښتینې نړۍ کارونې لپاره جوړه شوې ده. تر ټولو ځانګړې بڼه یې کیبورډ دی — یو ساده څرخېدونکی انکوډر چې په هر ځای او هر وخت کې د پیغامونو د ټایپ کولو لپاره یوه هوښیارانه او باوري لار برابروي.
دا څنګه کار کوي: د لورا او څرخېدونکي انکوډر جادو
دا پروژه درې مهم مفکورې سره یوځای کوي ترڅو یو فعال مېسنجر جوړ کړي:
- لورا راډیو: لورا (اوږد واټن) یوه راډیويي ټکنالوژي ده چې د ډېر لږ برېښنا سره د لویو واټنونو په اوږدو کې د اړیکو اجازه ورکوي. د وای-فای یا سیلولر برعکس، دا د جواز څخه پاکه راډیويي فریکونسیو باندې کار کوي، پدې معنی چې هیڅ سیم کارت یا میاشتني فیس پکې شامل نه دی. زموږ وسیله د بشپړ ټرانسیور په توګه کار کوي، پدې معنی چې کولی شي پیغامونه واستوي او ترلاسه کړي. کله چې بې کاره وي، دا د راتلونکو پاکټونو لپاره اوریدنه کوي. د پیغام له لیږلو یا ترلاسه کولو وروسته، دا راډیو لنډ وخت لپاره خوب ته اړوي ترڅو برېښنا خوندي کړي مخکې له دې چې بیا د اوریدنې حالت ته راستون شي.
- د څرخېدونکي انکوډر کیبورډ: د لوی یا ډېر برېښنا مصرفوونکي کیبورډ پر ځای، موږ د متن د ننوتلو لپاره یو ساده څرخېدونکی انکوډر کاروو. د دستګاه څرخول د بشپړ حروفو له سیټ څخه تیریږي (A-Z، a-z، شمېرې، او نښې). انٹرفیس په حیرانونکي ډول چټک او ځواب ویونکی دی، چې تاسو ته اجازه درکوي په اسانۍ سره پیغامونه جوړ کړئ. د انکوډر دننه جوړ شوی پش-بټن درې دندې لري:
- لنډ فشار (کلیک): غوره شوی حرف ستاسو پیغام ته اضافه کوي.
- اوږد فشار (نیول): یو ځای اضافه کوي، لکه د سپیس بار.
- دوه ځله کلیک: د بیک اسپیس په توګه کار کوي ترڅو وروستی حرف حذف کړي.
- ټرانسیور او ډسپلې منطق: وسیله دوه اصلي حالتونه لري: "جوړول" او "لوستل". تاسو خپل پیغام د جوړولو په حالت کې ټایپ کوئ. کله چې نوی پیغام راشي، یو خبرتیا په سکرین باندې ښکاره کیږي (
>> نوی پیغام <<). بیا تاسو کولی شئ د بورډ دننه د کارن بټن (د Heltec بورډ باندې) فشار کړئ ترڅو د لوستلو حالت ته لاړ شئ او پیغام وګورئ. بل فشار تاسو بیرته د جوړولو حالت ته راوړي ترڅو خپل ځواب ټایپ کړئ. ټول پیغامونه د AES کوډ کولو په کارولو سره خوندي کیږي، چې ډاډ ترلاسه کوي ستاسو خبرې اترې شخصي پاتې کیږي.
اجزاوې او د پرزو لیست
د دې پروژې د جوړولو لپاره، تاسو به لاندې اجزاوو ته اړتیا ولرئ. لکه څنګه چې په ویډیو کې یادونه شوې، د اړوندو لینکونو له لارې پیرود د جوړونکو د ملاتړ لپاره یوه ښه لار ده.
- Heltec WiFi LoRa 32 V3 ماډل
- Meshtastic N33 کېس د 3000mAh بیټرۍ سره
- 915MHz یا د سیمې سره سم لورا انتن
- د څرخېدونکي انکوډر ماډل (۵-پن د VCC، GND، CLK، DT، SW سره)
- اختیاري (د غږیزو خبرتیاوو لپاره):
- 5V فعال بوزر
- 2N2222 NPN ټرانزیسټر
- 1kΩ رزیسټر
- پن هیډرونه او جمپر تارونه
- سولډرینګ اوسپنه او لوازم
ستاسو د Arduino IDE تنظیمول
مخکې له دې چې تاسو کوډ اپلوډ کړئ، تاسو اړتیا لرئ د Arduino IDE د سمې بورډونو او کتابتونونو سره تنظیم کړئ. دا پروسه په ویډیو کې د ۰۵:۳۸ شاوخوا څخه په تفصیل سره ښودل شوې ده.
لومړی ګام: د ESP32 بورډونه نصب کړئ
که تاسو لا تر اوسه نه وي کړي، تاسو اړتیا لرئ د ESP32 مایکروکنټرولرونو لپاره ملاتړ اضافه کړئ.
- د Arduino IDE خلاص کړئ،
File > Preferencesته لاړ شئ. - د "Additional boards manager URLs" په ساحه کې، لاندې URL اضافه کړئ:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json Tools > Board > Boards Manager...ته لاړ شئ.- د
esp32لټون وکړئ او د Espressif Systems لخوا بسته نصب کړئ.
دوهم ګام: د Heltec ESP32 بورډ ملاتړ اضافه کړئ
- په
File > Preferencesکې، د "Additional boards manager URLs" په بکس کې په نوې کرښه کې د Heltec JSON URL اضافه کړئ (ویډیو ښیي چې دا څنګه په ۰۶:۲۷ کې ترسره کیږي):https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json - بیرته
Tools > Board > Boards Manager...ته لاړ شئ. - د
Heltec ESP32لټون وکړئ او بسته نصب کړئ.
درېیم ګام: د کتابتون مدیر څخه اړین کتابتونونه نصب کړئ
د Arduino کتابتون مدیر په کارولو سره (Sketch > Include Library > Manage Libraries...)، لاندې کتابتونونه لټون او نصب کړئ:
- Ai Esp32 Rotary Encoder: هغه ځانګړی کتابتون چې زموږ د ځواب ویونکي کیبورډ لپاره کارول کیږي.
- Heltec ESP32 Dev-Boards: د Heltec هارډویر لپاره رسمي کتابتون.
- Adafruit GFX Library: د OLED ډسپلې لپاره یو انحصار.
څلورم ګام: د Robojax مرستندویه کتابتون د .ZIP څخه نصب کړئ
لکه څنګه چې په ویډیو کې په ۱۸:۵۳ کې تشریح شوي، د دې پروژې لپاره یو دودیز مرستندویه کتابتون کارول کیږي. تاسو باید دا د .zip فایل څخه نصب کړئ.
- د "Robojax HealthTech Lora 32" .zip کتابتون د سرچینو په پاڼه کې د ورکړل شوي لینک څخه ډاونلوډ کړئ.
- په Arduino IDE کې،
Sketch > Include Library > Add .ZIP Library...باندې کلیک وکړئ. - هغه ځای ته لاړ شئ چیرې چې تاسو ډاونلوډ شوی .zip فایل خوندي کړی، هغه وټاکئ، او "Open" باندې کلیک وکړئ.
- کتابتون به نصب شي او د کارونې لپاره چمتو وي.
د هر څه له نصبولو وروسته، ډاډ ترلاسه کړئ چې تاسو له مینو څخه سمه بورډ غوره کوئ: Tools > Board > Heltec ESP32 Arduino > Heltec WiFi LoRa 32 V3.
د اجزاوو تارونه نښلول
تاسو اړتیا لرئ چې د خپل Heltec بورډ سره د پن هیډرونو سولډر کړئ ترڅو روټري انکوډر وصل کړئ. لکه څنګه چې په ویډیو کې ښودل شوي (11:25)، محتاط پلان جوړونې ته اړتیا ده ترڅو ډاډ ترلاسه شي چې پنونه د بورډ په قضیه کې د نصبولو وروسته د لاسرسي وړ وي.

ډیاګرام 1: اصلي پیغام رسونکی (یوازې روټري انکوډر)
د روټري انکوډر ماډل څخه 5 پنونه د Heltec بورډ سره په لاندې ډول وصل کړئ:
- په انکوډر کې
GND→ د Heltec بورډ پهGNDسره - په انکوډر کې
VCC→ د Heltec بورډ په3.3Vسره SW(سویچ) →GPIO 4DT(ډاټا/پن B) →GPIO 6CLK(کلاک/پن A) →GPIO 5

ډیاګرام 2: د اوریدونکي خبرتیا اضافه کول (بزر سرکټ)
د نوي پیغام د راتلو پر مهال د اوریدونکي بیپ ترلاسه کولو لپاره، تاسو کولی شئ یو ساده ټرانزیسټر سرکټ اضافه کړئ. دا اړین دی ځکه چې د ESP32 پنونه نشي کولی مستقیم د بزر چلولو لپاره کافي جریان چمتو کړي.
- د Heltec بورډ څخه
GPIO 40د 1kΩ ریزسټر یوې پای ته وصل کړئ. - د ریزسټر بله پایه د 2N2222 ټرانزیسټر د بیس پن سره وصل کړئ.
- د ټرانزیسټر ایمیټر پن د
GNDسره وصل کړئ. - د 5V بزر مثبت (+) پښه د Heltec بورډ په
3.3Vپن سره وصل کړئ. - د بزر منفي (-) پښه د ټرانزیسټر د کلیکټر پن سره وصل کړئ.
د کوډ پوهیدل او شخصي کول
چمتو شوی کوډ د اپلوډ لپاره چمتو دی، مګر ځینې مهم ترتیبات شتون لري چې تاسو یې باید په اړه پوه شئ ترڅو پروژه شخصي کړئ. دا تعریفونه د کوډ په پورتنۍ برخه کې دي.
// د فزیکي پن اړیکو لپاره تعریفونه
#define ROTARY_ENCODER_A_PIN 5 //د روټري انکوډر پن A
#define ROTARY_ENCODER_B_PIN 6 //د روټري انکوډر پن B
#define ROTARY_ENCODER_BUTTON_PIN 4 //د روټري انکوډر سویچ پن
#define BUZZER_PIN 40 //د بزر پن یا ریلې چې باید فعاله شي
const bool DEBUG = false;
// د LoRa او امنیتي ترتیباتو لپاره تعریفونه
#define RF_FREQUENCY 915432000 //د LoRa عملیاتي فریکونسي
#define TX_OUTPUT_POWER 2 //د تولید ځواک باید د 2 څخه تر 21 پورې په dBm کې وي.
const char *userKey = "xxxBg^Tr%43232";//د خصوصي اړیکو لپاره امنیتي کیلي
ROTARY_ENCODER_...: دا پنونه مشخص کوي چې تاسو خپل روټري انکوډر چیرته وصل کړی. ډاډ ترلاسه کړئ چې دا شمیرې د پورته ډیاګرامونو څخه ستاسو د تارونو سره سمون لري.BUZZER_PIN: دا مشخص کوي چې کوم پن به د بزر سرکټ فعال کړي.RF_FREQUENCY: دا د LoRa عملیاتي فریکونسي په هرټز کې ده. ارزښت 915432000 (915 MHz) د شمالي امریکا لپاره معیاري دی. تاسو باید دا ارزښت د خپلې سیمې سره سم بدل کړئ (د بیلګې په توګه، د اروپا لپاره 868MHz، د آسیا لپاره 433MHz). ټول وسایل چې غواړي اړیکه ونیسي باید ورته فریکونسي وکاروي.TX_OUTPUT_POWER: دا د لیږد ځواک په dBm کې دی. دا کولی شي له 2 څخه تر 20 پورې وي. یو ټیټ شمیر لکه2د لنډ واټن ازموینې لپاره مناسب دی او د بیټرۍ د پام وړ اندازه خوندي کوي. د اعظمي واټن لپاره، تاسو کولی شئ دا ارزښت زیات کړئ، مګر په یاد ولرئ چې لوړ ځواک بیټرۍ ډیر ژر خالي کوي.userKey: دا ستاسو خصوصي کوډ کولو کیلي ده. دا کیدای شي د حروفو هر ډول ترکیب وي. ټول وسایل باید د یو بل د پیغامونو د کوډ کولو لپاره دقیقا ورته کیلي ولري. دا خپل ځانګړي او پټ پاسورډ ته بدل کړئ.
کوډ ترلاسه کړئ
د دې پروژې لپاره بشپړ او وروستی Arduino کوډ، لکه څنګه چې په ویډیو کې اشاره شوې، په دې پاڼه کې د ډاونلوډ لپاره شتون لري.
د ویډیو فصلونه
- 00:00 - پیژندنه
- 00:56 - د پروژې لنډیز
- 01:29 - د اجزاوو کتنه
- 05:25 - د بکس پرانیستل او د هارډویر راټولول
- 11:19 - د بهرنیو تارونو لپاره د قضیې بدلون
- 15:06 - د تارونو نښلولو لارښود
- 15:38 - د Arduino IDE او کتابتون تنظیم
- 20:21 - د کوډ ترتیبات تشریح شوي
- 21:38 - د چیٹ کولو لپاره د روټري انکوډر کارولو څرنګوالی
- 24:26 - ژوندی واټن ازموینه
/*
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
سرچینې او حوالې
-
بهرنیPurchase Wi-Fi LoRa 32 from Meshnologymeshnology.com
فایلونه📁
نور فایلونه
-
Robojax Heltec WiFi LoRa 32 Library Version 1.1.0 20250703
Robojax_HeltecLoRa32_1.1.0_20250702.zip0.09 MB -