Raadi Koodka

Mashruuc ESP32: Samee Aalab LoRa Wadaal Sugan, Fog-dheer Heltec WiFi LoRa 32 on N33

Casharradan qayb ka mid ah: Hordhac WiFi LoRa

Mashruuc ESP32: Samee Aalab LoRa Wadaal Sugan, Fog-dheer Heltec WiFi LoRa 32 on N33

Dhis Sameecayaal, LoRa Messenger Ka Ammaanka Leh oo leh Keyboard Gaar ah

Adduunka ku tiirsan isku xirnaan joogto ah, maxaad samaynaysaa marka Wi-Fi uu joogsado, korontadu ay go'do, iyo adeegga taleefanka uu waayo? Mashruucani wuxuu xalliyaa dhibaatadaas dhabta ah isagoo abuuraya qalab isgaarsiineed oo gabi ahaanba isku filan, kaas oo kuu oggolaanaya inaad dirto oo aad hesho fariimo qarsoodi ah oo gaar ah masaafo dhan ilaa 13 mayl (21km) ama xitaa ka badan.

Qalabkani waa qalab wax ku ool u ah diyaarinta xaaladaha degdegga ah, tacaburrada bannaanka sida socodka buuraha, ama si fudud qof kasta oo xiiseynaya inuu dhiso shabakad isgaarsiineed oo gaar ah oo uu xakameeyo. Iyadoo la isticmaalayo module-ka xoogga leh ee Heltec WiFi LoRa 32 V3 oo lagu xidhay kiishad adag oo Meshnology N33 ah oo leh baytari 3000mAh oo waara, mashruucani wuxuu u dhisanyahay isticmaalka dhabta ah. Muuqaalka ugu gaarka ah waa kiiboodhkiisa—rotary encoder fudud oo bixiya hab fudud oo lagu qoro fariimaha meel kasta, waqti kasta.


Sida Ay U Shaqeyso: Sixirka LoRa iyo Rotary Encoder

Mashruucani wuxuu isku darayaa saddex fikradood oo muhiim ah si uu u abuuro messenger shaqeeya:

  • LoRa Radio: LoRa (Long Range) waa tignoolajiyada raadiyaha oo u oggolaanaysa isgaarsiin masaafo aad u dheer iyadoo koronto yar la isticmaalayo. Si ka duwan Wi-Fi ama taleefanka gacanta, waxay ku shaqeysaa mawjadaha raadiyaha ee aan shati u baahnayn, taasoo la macno ah inaysan jirin SIM cards ama khidmado bil ah. Qalabkeennu wuxuu u shaqeeyaa sidii transceiver buuxa, taasoo la macno ah inuu awood u leeyahay inuu soo diro oo uu helo fariimaha. Marka uu firaaqo yahay, wuxuu dhageystaa baakadaha soo socda. Ka dib marka uu soo diro ama uu helo fariin, wuxuu seexiyaa raadiyaha daqiiqad si uu u badbaadiyo korontada ka hor inta uusan ku laaban habka dhageysiga.
  • Kiiboodhka Rotary Encoder: Halkii laga isticmaali lahaa kiiboodh weyn ama koronto badan, waxaan u isticmaalaynaa rotary encoder fudud oo lagu qoro qoraalka. Wareejinta knob-ka waxay ku wareejinaysaa xuruuf dhamaystiran (A-Z, a-z, tirooyin, iyo calaamado). Interface-ku waa mid la yaab leh oo degdeg ah oo jawaab celin leh, kaasoo kuu oggolaanaya inaad si fudud u qorto fariimaha. Badhanka ku dhisan ee encoder-ka wuxuu leeyahay saddex shaqoood:
    • Riixitaan Gaaban (Click): Wuxuu ku darayaa xarafka la doortay fariintaada.
    • Riixitaan Dheer (Hold): Wuxuu ku darayaa meel bannaan, sida spacebar.
    • Labo Riixitaan (Double-Click): Wuxuu u shaqeeyaa sidii backspace oo uu tirtiro xarafka ugu dambeeyay.
  • Transceiver & Display Logic: Qalabku wuxuu leeyahay laba hab oo waaweyn: "Compose" iyo "Read". Waxaad ku qortaa fariintaada habka Compose. Marka fariin cusub soo gaadho, ogaysiis ayaa ku soo baxa shaashadda (>> NEW MSG <<). Kadib waxaad riixi kartaa Badhanka Isticmaalaha (User Button) ee ku yaal gudaha (Heltec board) si aad ugu beddesho habka Read oo aad u aragto fariinta. Riixitaan kale ayaa kugu celinaya habka Compose si aad u qorto jawaabtaada. Dhammaan fariimaha waxaa lagu ilaaliyaa AES encryption, taasoo hubinaysa in wadahadaladaadu ay qarsoodi ahaadaan.

Qaybaha iyo Liiska Qalabka

Si aad u dhisto mashruucan, waxaad u baahan doontaa qaybaha soo socda. Sida lagu sheegay fiidiyowga, iibsashada iyada oo loo marayo affiliate links waa hab fiican oo lagu taageero abuurayaasha.

  • Heltec WiFi LoRa 32 V3 Module
  • Meshtastic N33 Case oo leh 3000mAh Battery
  • 915MHz ama Antenna LoRa oo ku habboon gobolka
  • Rotary Encoder Module (5-pin oo leh VCC, GND, CLK, DT, SW)
  • Ikhtiyaar (oo loogu talagalay digniinta codka):
    • 5V Active Buzzer
    • 2N2222 NPN Transistor
    • 1kΩ Resistor
  • Pin Headers iyo Jumper Wires
  • Birta lagu xidho iyo qalabka

Dejinta Arduino IDE-gaaga

Ka hor intaadan soo gelin karin koodhka, waxaad u baahan tahay inaad ku habboonaato Arduino IDE-ga boards-ka iyo maktabadaha saxda ah. Nidaamkan waxaa si faahfaahsan loogu sheegay fiidiyowga laga bilaabo 05:38.

Tallaabada 1: Ku rakib ESP32 Boards-ka

Haddii aadan weli samayn, waxaad u baahan tahay inaad ku darto taageerada ESP32 microcontrollers-ka.

  1. Fur Arduino IDE-ga, tag File > Preferences.
  2. Gudaha "Additional boards manager URLs" goobta, ku dar URL-ka soo socda: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. Tag Tools > Board > Boards Manager....
  4. Raadi esp32 oo ku rakib baakadka Espressif Systems.

Tallaabada 2: Ku dar Taageerada Heltec ESP32 Board

  1. Gudaha File > Preferences, ku dar Heltec JSON URL-ka sadar cusub oo ku yaal "Additional boards manager URLs" sanduuqa (fiidiyowgu wuxuu muujinayaa sida loo sameeyo 06:27): https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json
  2. Ku noqo Tools > Board > Boards Manager....
  3. Raadi Heltec ESP32 oo ku rakib baakadka.

Tallaabada 3: Ku rakib Maktabadaha Loo Baahan Yahay ee Library Manager-ka

Iyadoo la isticmaalayo Arduino Library Manager (Sketch > Include Library > Manage Libraries...), raadi oo ku rakib maktabadaha soo socda:

  1. Ai Esp32 Rotary Encoder: Maktabadda gaarka ah ee loo isticmaalay kiiboodhkeenna jawaab celinta leh.
  2. Heltec ESP32 Dev-Boards: Maktabadda rasmiga ah ee qalabka Heltec.
  3. Adafruit GFX Library: Waxay ku xidhan tahay OLED display-ka.

Tallaabada 4: Ku rakib Robojax Helper Library-ka .ZIP-ka

Sida lagu sharaxay fiidiyowga 18:53, maktabad caawimaad gaar ah ayaa loo isticmaalaa mashruucan. Waa inaad ka rakibtaa .zip file.

  1. Soo deji "Robojax HealthTech Lora 32" .zip maktabadda linkiga lagu bixiyay bogga kheyraadka.
  2. Gudaha Arduino IDE-ga, guji Sketch > Include Library > Add .ZIP Library....
  3. U gudub meesha aad ku kaydisay .zip file-ka la soo dejiyay, dooro, oo guji "Open".
  4. Maktabadda ayaa la rakibi doonaa oo diyaar u ah isticmaalka.

Marka aad dhammeystiso wax kasta oo la rakibo, hubi inaad ka doorato guddiga saxda ah liiska: Tools > Board > Heltec ESP32 Arduino > Heltec WiFi LoRa 32 V3.


Ku Xiridda Qaybaha

Waxaad u baahan doontaa inaad ku dhejiso pin-yaasha madaxa guddigaaga Heltec si aad ugu xirto rotary encoder-ka. Sida lagu muujiyay fiidiyowga (11:25), qorsheyn taxaddar leh ayaa loo baahan yahay si loo hubiyo in pin-yada la heli karo marka guddigu ku jiro kiiskiisa.

Helte_Wifi_LoRA_Rotary_Encoder

Jaantuska 1: Core Messenger (Rotary Encoder Kaliya)

Ku xir 5ta pin ee qaybta rotary encoder-ka guddiga Heltec sida soo socota:

  • GND ee encoder-ka → GND ee guddiga Heltec
  • VCC ee encoder-ka → 3.3V ee guddiga Heltec
  • SW (Switch) → GPIO 4
  • DT (Data/Pin B) → GPIO 6
  • CLK (Clock/Pin A) → GPIO 5
Helte_Wifi_LoRA_Rotary_Encoder with buzzer

Jaantuska 2: Ku Darista Digitaal Maqal ah (Wareegga Buzzer-ka)

Si aad u hesho dhawaq maqal ah marka farriin cusub soo gaadho, waxaad ku dari kartaa wareeg transistor fudud. Tani waa lagama maarmaan sababtoo ah pin-yada ESP32 ma siin karaan koronto ku filan si ay si toos ah u wadaan buzzer-ka.

  • Ku xir GPIO 40 ee guddiga Heltec dhamaadka 1kΩ resistor-ka.
  • Ku xir dhamaadka kale ee resistor-ka pin-ka Base ee transistor-ka 2N2222.
  • Ku xir pin-ka Emitter ee transistor-ka GND.
  • Ku xir lugta positive (+) ee buzzer-ka 5V pin-ka 3.3V ee guddiga Heltec.
  • Ku xir lugta negative (-) ee buzzer-ka pin-ka Collector ee transistor-ka.

Fahanka iyo Habaynta Koodhka

Koodhka la bixiyay waa diyaar in la soo geliyo, laakiin waxaa jira dhawr goobood oo muhiim ah oo aad ka warqabto si aad u habayn karto mashruuca. Qeexitaannadani waxay ku yaalliin dusha koodhka.


// Qeexitaannada ku xirnaanshaha pin-ka jirka
#define ROTARY_ENCODER_A_PIN 5 //pin A ee rotary encoder-ka
#define ROTARY_ENCODER_B_PIN 6 //pin B ee rotary encoder-ka
#define ROTARY_ENCODER_BUTTON_PIN 4 //pin-ka switch-ka ee rotary encoder-ka
#define BUZZER_PIN 40 //pin-ka buzzer-ka ama relay-ga la kiciyo
const bool DEBUG = false;

// Qeexitaannada LoRa iyo Amniga
#define RF_FREQUENCY 915432000 //soo noqnoqoshada shaqada ee LoRa
#define TX_OUTPUT_POWER 2 //awoodda soo saarista waa inay u dhaxaysaa 2 ilaa 21 dBm.
const char *userKey = "xxxBg^Tr%43232";//furaha amniga ee isgaarsiinta gaarka ah
  • ROTARY_ENCODER_...: Pin-yadani waxay qeexayaan halka aad ku xirtay rotary encoder-kaaga. Hubi in tirooyinkani ay la mid yihiin xiriddaada jaantusyada kor ku xusan.
  • BUZZER_PIN: Tani waxay qeexaysaa pin-ka kiciya wareegga buzzer-ka.
  • RF_FREQUENCY: Kani waa soo noqnoqoshada shaqada ee LoRa ee Hertz. Qiimaha 915432000 (915 MHz) waa heerka Waqooyiga Ameerika. Waa inaad beddeshaa qiimahan marka loo eego gobolkaaga (tusaale, 868MHz Yurub, 433MHz Aasiya). Dhammaan qalabka raba inay isgaarsiiyaan waa inay isticmaalaan isla soo noqnoqoshada.
  • TX_OUTPUT_POWER: Kani waa awoodda gudbinta ee dBm. Wuxuu u dhexayn karaa 2 ilaa 20. Tiro hooseysa sida 2 ayaa ku fiican tijaabinta masaafada gaaban waxayna badbaadisaa batari badan. Masaafada ugu badan, waxaad kordhin kartaa qiimahan, laakiin ogow in awoodda sare ay batariga si dhakhso ah u shubto.
  • userKey: Kani waa furahaaga sirta ah ee gaarka ah. Wuxuu noqon karaa isku-dar kasta oo xarfo ah. Dhammaan qalabka waa inay leeyihiin isla furaha si ay u furaan farriimaha midba midka kale. Beddel kan si aad u hesho eraygaaga sirta ah ee gaarka ah.

Soo Hel Koodhka

Koodhka dhammaystiran iyo kan ugu dambeeya ee Arduino ee mashruucan, sida lagu xusay fiidiyowga, wuxuu diyaar u yahay in laga soo dejiyo boggan.

Qaybaha Fiidiyowga

  • 00:00 - Hordhac
  • 00:56 - Qorshaha Mashruuca
  • 01:29 - Dulmarka Qaybaha
  • 05:25 - Furitaanka iyo Isku-dhafka Qalabka
  • 11:19 - Wax-ka-beddelka Kiiska ee Siligyada Dibadda
  • 15:06 - Hagaha Xiridda
  • 15:38 - Arduino IDE iyo Rakibaadda Maktabadda
  • 20:21 - Qeexida Goobaha Koodhka
  • 21:38 - Sida Loogu Isticmaalo Rotary Encoder-ka Xaashi
  • 24:26 - Tijaabada Masaafada Tooska ah
785-Secure, Long-Range LoRa Chat Device Heltec WiFi LoRa 32
Luqadda: 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
Luqadda: 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);
}

Kheyraadka & Tixraacyada

Faylasha📁

Faylal kale