Search Code

ESP32 پروجیکٹ: ایک محفوظ، طویل فاصلے تک چلنے والا LoRa چیٹ ڈیوائس بنائیں Heltec WiFi LoRa 32 on N33

یہ سبق کا حصہ ہے: وائی فائی LoRa کا تعارف

ESP32 پروجیکٹ: ایک محفوظ، طویل فاصلے تک چلنے والا LoRa چیٹ ڈیوائس بنائیں Heltec WiFi LoRa 32 on N33

اپنی مرضی کے کی بورڈ کے ساتھ ایک محفوظ، آف گرڈ لوڑا میسنجر بنائیں

ایسی دنیا میں جو مسلسل کنیکٹیویٹی پر انحصار کرتی ہے، جب وائی فائی بند ہو، بجلی چلی جائے، اور سیلولر سروس ختم ہو جائے تو آپ کیا کرتے ہیں؟ یہ پروجیکٹ بالکل اسی مسئلے کو حل کرتا ہے ایک مکمل طور پر خود کفیل، آف گرڈ کمیونیکیشن ڈیوائس بنا کر جو آپ کو 13 میل (21 کلومیٹر) یا اس سے بھی زیادہ فاصلے تک محفوظ، نجی ٹیکسٹ پیغامات بھیجنے اور وصول کرنے کی اجازت دیتا ہے۔

یہ ڈیوائس ایمرجنسی تیاری، بیرونی مہم جوئی جیسے پیدل سفر، یا صرف ان لوگوں کے لیے ایک عملی آلہ ہے جو ایک مضبوط، نجی کمیونیکیشن نیٹ ورک بنانے میں دلچسپی رکھتے ہیں جس پر ان کا کنٹرول ہو۔ ایک طاقتور Heltec WiFi LoRa 32 V3 ماجیول استعمال کرتے ہوئے جو ایک پائیدار Meshnology N33 کیس میں 3000mAh کی طویل مدتی بیٹری کے ساتھ نصب ہے، یہ پروجیکٹ حقیقی دنیا کے استعمال کے لیے بنایا گیا ہے۔ سب سے منفرد خصوصیت اس کا کی بورڈ ہے—ایک سادہ روٹری انکوڈر جو کہیں بھی، کسی بھی وقت پیغامات ٹائپ کرنے کا ایک بدیہی اور قابل اعتماد طریقہ فراہم کرتا ہے۔


یہ کیسے کام کرتا ہے: لوڑا اور روٹری انکوڈر کا جادو

یہ پروجیکٹ ایک فعال میسنجر بنانے کے لیے تین اہم تصورات کو یکجا کرتا ہے:

  • لوڑا ریڈیو: لوڑا (لانگ رینج) ایک ریڈیو ٹیکنالوجی ہے جو بہت کم بجلی کے ساتھ وسیع فاصلوں پر مواصلات کی اجازت دیتی ہے۔ وائی فائی یا سیلولر کے برعکس، یہ لائسنس سے پاک ریڈیو فریکوئنسیوں پر کام کرتا ہے، یعنی اس میں کوئی سم کارڈ یا ماہانہ فیس شامل نہیں ہے۔ ہمارا ڈیوائس ایک مکمل ٹرانسیور کے طور پر کام کرتا ہے، یعنی یہ پیغامات بھیج اور وصول دونوں کر سکتا ہے۔ جب یہ غیر فعال ہوتا ہے، تو یہ آنے والے پیکٹس کو سنتا ہے۔ پیغام بھیجنے یا وصول کرنے کے بعد، یہ بجلی بچانے کے لیے ریڈیو کو عارضی طور پر سلا دیٹا ہے اور پھر سننے کے موڈ میں واپس آ جاتا ہے۔
  • روٹری انکوڈر کی بورڈ: بھاری یا زیادہ بجلی استعمال کرنے والے کی بورڈ کے بجائے، ہم ٹیکسٹ ان پٹ کے لیے ایک سادہ روٹری انکوڈر استعمال کرتے ہیں۔ نوب کو گھمانے سے ایک مکمل کریکٹر سیٹ (A-Z، a-z، نمبرز، اور علامات) میں سے گزرتا ہے۔ انٹرفیس حیرت انگیز طور پر تیز اور ردعمل دینے والا ہے، جس سے آپ آسانی سے پیغامات تحریر کر سکتے ہیں۔ انکوڈر کا بلٹ ان پش بٹن تین افعال رکھتا ہے:
    • مختصر دباؤ (کلک): منتخب کردہ کریکٹر کو آپ کے پیغام میں شامل کرتا ہے۔
    • لمبا دباؤ (ہولڈ): اسپیس شامل کرتا ہے، جیسے اسپیس بار۔
    • ڈبل کلک: آخری کریکٹر کو حذف کرنے کے لیے بیک اسپیس کا کام کرتا ہے۔
  • ٹرانسیور اور ڈسپلے منطق: ڈیوائس کے دو اہم موڈ ہیں: "کمپوز" اور "ریڈ"۔ آپ کمپوز موڈ میں اپنا پیغام ٹائپ کرتے ہیں۔ جب کوئی نیا پیغام آتا ہے، تو اسکرین پر ایک اطلاع ظاہر ہوتی ہے (>> نیا پیغام <<)۔ پھر آپ آن بورڈ یوزر بٹن (Heltec بورڈ پر) دبا کر ریڈ موڈ میں جا کر پیغام دیکھ سکتے ہیں۔ ایک اور دباؤ آپ کو جواب ٹائپ کرنے کے لیے کمپوز موڈ میں واپس لے جاتا ہے۔ تمام پیغامات AES انکرپشن کے ذریعے محفوظ کیے جاتے ہیں، اس بات کو یقینی بناتے ہوئے کہ آپ کی گفتگو نجی رہے۔

اجزاء اور پرزوں کی فہرست

اس پروجیکٹ کو بنانے کے لیے، آپ کو درج ذیل اجزاء کی ضرورت ہوگی۔ جیسا کہ ویڈیو میں ذکر کیا گیا ہے، ایفیلی ایٹ لنکس کے ذریعے خریدنا تخلیق کاروں کی حمایت کرنے کا ایک بہترین طریقہ ہے۔

  • Heltec WiFi LoRa 32 V3 ماجیول
  • Meshtastic N33 کیس 3000mAh بیٹری کے ساتھ
  • 915MHz یا علاقے کے مطابق لوڑا اینٹینا
  • روٹری انکوڈر ماجیول (5 پن VCC، GND، CLK، DT، SW کے ساتھ)
  • اختیاری (آڈیو الرٹس کے لیے):
    • 5V ایکٹو بزر
    • 2N2222 NPN ٹرانزسٹر
    • 1kΩ ریزسٹر
  • پن ہیڈرز اور جمپر وائرز
  • سولڈرنگ آئرن اور لوازمات

اپنا Arduino IDE ترتیب دینا

کوڈ اپ لوڈ کرنے سے پہلے، آپ کو Arduino IDE کو صحیح بورڈز اور لائبریریوں کے ساتھ ترتیب دینا ہوگا۔ یہ عمل ویڈیو میں تقریباً 05:38 سے تفصیل سے دکھایا گیا ہے۔

مرحلہ 1: ESP32 بورڈز انسٹال کریں

اگر آپ نے پہلے نہیں کیا، تو آپ کو ESP32 مائیکروکنٹرولرز کے لیے سپورٹ شامل کرنے کی ضرورت ہے۔

  1. Arduino IDE کھولیں، فائل > ترجیحات پر جائیں۔
  2. "اضافی بورڈز مینیجر URLs" فیلڈ میں، درج ذیل URL شامل کریں: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. ٹولز > بورڈ > بورڈز مینیجر... پر جائیں۔
  4. esp32 تلاش کریں اور Espressif Systems کا پیکیج انسٹال کریں۔

مرحلہ 2: Heltec ESP32 بورڈ سپورٹ شامل کریں

  1. فائل > ترجیحات میں، "اضافی بورڈز مینیجر URLs" باکس میں ایک نئی لائن پر Heltec JSON URL شامل کریں (ویڈیو 06:27 پر یہ کرنے کا طریقہ دکھاتی ہے): https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json
  2. ٹولز > بورڈ > بورڈز مینیجر... پر واپس جائیں۔
  3. Heltec ESP32 تلاش کریں اور پیکیج انسٹال کریں۔

مرحلہ 3: لائبریری مینیجر سے مطلوبہ لائبریریاں انسٹال کریں

Arduino لائبریری مینیجر (اسکیچ > لائبریری شامل کریں > لائبریریاں منظم کریں...) کا استعمال کرتے ہوئے، درج ذیل لائبریریوں کو تلاش کریں اور انسٹال کریں:

  1. Ai Esp32 روٹری انکوڈر: ہمارے ردعمل دینے والے کی بورڈ کے لیے استعمال ہونے والی مخصوص لائبریری۔
  2. Heltec ESP32 ڈیو بورڈز: Heltec ہارڈویئر کے لیے سرکاری لائبریری۔
  3. Adafruit GFX لائبریری: OLED ڈسپلے کے لیے ایک انحصار۔

مرحلہ 4: Robojax ہیلپر لائبریری کو .ZIP سے انسٹال کریں

جیسا کہ ویڈیو میں 18:53 پر وضاحت کی گئی ہے، اس پروجیکٹ کے لیے ایک حسب ضرورت ہیلپر لائبریری استعمال کی جاتی ہے۔ آپ کو اسے .zip فائل سے انسٹال کرنا ہوگا۔

  1. وسائل کے صفحے پر دیے گئے لنک سے "Robojax HealthTech Lora 32" .zip لائبریری ڈاؤن لوڈ کریں۔
  2. Arduino IDE میں، اسکیچ > لائبریری شامل کریں > .ZIP لائبریری شامل کریں... پر کلک کریں۔
  3. جہاں آپ نے ڈاؤن لوڈ کردہ .zip فائل محفوظ کی ہے وہاں جائیں، اسے منتخب کریں، اور "اوپن" پر کلک کریں۔
  4. لائبریری انسٹال ہو جائے گی اور استعمال کے لیے تیار ہو جائے گی۔

ہر چیز انسٹال کرنے کے بعد، یقینی بنائیں کہ آپ مینو سے صحیح بورڈ منتخب کرتے ہیں: Tools > Board > Heltec ESP32 Arduino > Heltec WiFi LoRa 32 V3۔


اجزاء کی وائرنگ

روٹری انکوڈر کو جوڑنے کے لیے آپ کو اپنے Heltec بورڈ پر پن ہیڈرز کو سولڈر کرنا ہوگا۔ جیسا کہ ویڈیو (11:25) میں دکھایا گیا ہے، احتیاط سے منصوبہ بندی کی ضرورت ہے تاکہ بورڈ اپنے کیس میں ہونے کے بعد پن قابل رسائی رہیں۔

Helte_Wifi_LoRA_Rotary_Encoder

ڈایاگرام 1: کور میسنجر (صرف روٹری انکوڈر)

روٹری انکوڈر ماجیول کے 5 پنوں کو Heltec بورڈ سے درج ذیل طریقے سے جوڑیں:

  • انکوڈر پر GND → Heltec بورڈ پر GND
  • انکوڈر پر VCC → Heltec بورڈ پر 3.3V
  • SW (سوئچ) → GPIO 4
  • DT (ڈیٹا/پن B) → GPIO 6
  • CLK (کلاک/پن A) → GPIO 5
Helte_Wifi_LoRA_Rotary_Encoder with buzzer

ڈایاگرام 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 - لائیو رینج ٹیسٹ
785-Secure, Long-Range LoRa Chat Device Heltec WiFi LoRa 32
زبان: C++
/*
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);
}
786-Secure, Long-Range LoRa Chat Device Heltec WiFi LoRa 32 with buzzer
زبان: C++
/*
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);
}

وسائل اور حوالہ جات

فائلیں📁

دیگر فائلیں