کوډ ولټوئ

د ESP32 پروژه: د خوندي، اوږد واټن لورا چیٹ وسیله جوړول Heltec WiFi LoRa 32 په N33 باندې

دا درس برخه ده له: د WiFi LoRa پیژندنه

د 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 مایکروکنټرولرونو لپاره ملاتړ اضافه کړئ.

  1. د Arduino IDE خلاص کړئ، File > Preferences ته لاړ شئ.
  2. د "Additional boards manager URLs" په ساحه کې، لاندې URL اضافه کړئ: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. Tools > Board > Boards Manager... ته لاړ شئ.
  4. د esp32 لټون وکړئ او د Espressif Systems لخوا بسته نصب کړئ.

دوهم ګام: د Heltec ESP32 بورډ ملاتړ اضافه کړئ

  1. په 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
  2. بیرته Tools > Board > Boards Manager... ته لاړ شئ.
  3. د Heltec ESP32 لټون وکړئ او بسته نصب کړئ.

درېیم ګام: د کتابتون مدیر څخه اړین کتابتونونه نصب کړئ

د Arduino کتابتون مدیر په کارولو سره (Sketch > Include Library > Manage Libraries...)، لاندې کتابتونونه لټون او نصب کړئ:

  1. Ai Esp32 Rotary Encoder: هغه ځانګړی کتابتون چې زموږ د ځواب ویونکي کیبورډ لپاره کارول کیږي.
  2. Heltec ESP32 Dev-Boards: د Heltec هارډویر لپاره رسمي کتابتون.
  3. Adafruit GFX Library: د OLED ډسپلې لپاره یو انحصار.

څلورم ګام: د Robojax مرستندویه کتابتون د .ZIP څخه نصب کړئ

لکه څنګه چې په ویډیو کې په ۱۸:۵۳ کې تشریح شوي، د دې پروژې لپاره یو دودیز مرستندویه کتابتون کارول کیږي. تاسو باید دا د .zip فایل څخه نصب کړئ.

  1. د "Robojax HealthTech Lora 32" .zip کتابتون د سرچینو په پاڼه کې د ورکړل شوي لینک څخه ډاونلوډ کړئ.
  2. په Arduino IDE کې، Sketch > Include Library > Add .ZIP Library... باندې کلیک وکړئ.
  3. هغه ځای ته لاړ شئ چیرې چې تاسو ډاونلوډ شوی .zip فایل خوندي کړی، هغه وټاکئ، او "Open" باندې کلیک وکړئ.
  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);
}

سرچینې او حوالې

فایلونه📁

نور فایلونه