Kod Ara

ESP32 Projesi: Heltec WiFi LoRa 32 ile N33 üzerinde Güvenli, Uzun Menzilli LoRa Sohbet Cihazı Oluşturun

Bu ders, bir parçasıdır: WiFi LoRa'ya Giriş

ESP32 Projesi: Heltec WiFi LoRa 32 ile N33 üzerinde Güvenli, Uzun Menzilli LoRa Sohbet Cihazı Oluşturun

Kendi Özel Klavyesiyle Güvenli, Şebekeden Bağımsız Bir LoRa Mesajlaşma Cihazı Oluşturun

Sürekli bağlantıya dayanan bir dünyada, Wi-Fi kesildiğinde, elektrik gittiğinde ve hücresel hizmet ortadan kalktığında ne yaparsınız? Bu proje, 13 mil (21 km) veya daha da fazla mesafelere kadar güvenli, özel metin mesajları gönderip almanızı sağlayan tamamen kendi kendine yeten, şebekeden bağımsız bir iletişim cihazı oluşturarak tam olarak bu sorunu ele alıyor.

Bu cihaz; acil durum hazırlığı, yürüyüş gibi açık hava maceraları veya sadece kontrol ettikleri sağlam, özel bir iletişim ağı kurmakla ilgilenen herkes için pratik bir araçtır. Dayanıklı bir Meshnology N33 kasa içinde uzun ömürlü 3000mAh pil ile paketlenmiş güçlü bir Heltec WiFi LoRa 32 V3 modülü kullanan bu proje, gerçek dünya kullanımı için tasarlanmıştır. En benzersiz özelliği klavyesidir—her yerde, her zaman mesaj yazmanın sezgisel ve güvenilir bir yolunu sağlayan basit bir döner kodlayıcı.


Nasıl Çalışır: LoRa ve Döner Kodlayıcının Büyüsü

Proje, işlevsel bir mesajlaşma cihazı oluşturmak için üç temel kavramı birleştirir:

  • LoRa Radyo: LoRa (Uzun Menzil), çok az güçle çok geniş mesafeler arasında iletişime olanak tanıyan bir radyo teknolojisidir. Wi-Fi veya hücreselden farklı olarak, lisanssız radyo frekanslarında çalışır; yani SIM kart veya aylık ücret yoktur. Cihazımız tam bir alıcı-verici olarak çalışır; yani hem mesaj gönderebilir hem de alabilir. Boştayken gelen paketleri dinler. Bir mesaj gönderdikten veya aldıktan sonra, dinleme moduna dönmeden önce güç tasarrufu için radyoyu kısa bir süre uyutur.
  • Döner Kodlayıcı Klavyesi: Hantal veya güç tüketen bir klavye yerine, metin girişi için basit bir döner kodlayıcı kullanıyoruz. Kolu döndürmek, tam bir karakter kümesi (A-Z, a-z, sayılar ve semboller) arasında gezinir. Arayüz şaşırtıcı derecede hızlı ve duyarlıdır; mesajları kolayca oluşturmanızı sağlar. Kodlayıcının dahili butonunun üç işlevi vardır:
    • Kısa Basış (Tıklama): Seçili karakteri mesajınıza ekler.
    • Uzun Basış (Basılı Tutma): Boşluk çubuğu gibi bir boşluk ekler.
    • Çift Tıklama: Son karakteri silmek için geri alım tuşu gibi çalışır.
  • Alıcı-Verici ve Ekran Mantığı: Cihazın iki ana modu vardır: "Oluştur" ve "Oku". Mesajınızı Oluştur modunda yazarsınız. Yeni bir mesaj geldiğinde, ekranda bir bildirim görünür (>> YENİ MSJ <<). Ardından, mesajı görüntülemek için Read moduna geçmek üzere (Heltec kartındaki) dahili Kullanıcı Butonuna basabilirsiniz. Başka bir basış, yanıtınızı yazmak için sizi Oluştur moduna geri döndürür. Tüm mesajlar AES şifrelemesi kullanılarak korunur ve konuşmalarınızın gizli kalmasını sağlar.

Bileşenler ve Parça Listesi

Bu projeyi oluşturmak için aşağıdaki bileşenlere ihtiyacınız olacak. Videoda belirtildiği gibi, bağlı kuruluş bağlantılarından satın almak içerik üreticilerini desteklemenin harika bir yoludur.

  • Heltec WiFi LoRa 32 V3 Modülü
  • 3000mAh Pil ile Meshtastic N33 Kasa
  • 915MHz veya bölgeye uygun LoRa Anteni
  • Döner Kodlayıcı Modülü (VCC, GND, CLK, DT, SW ile 5 pinli)
  • İsteğe bağlı (sesli uyarılar için):
    • 5V Aktif Buzzer
    • 2N2222 NPN Transistör
    • 1kΩ Direnç
  • Pin Başlıkları ve Jumper Kablolar
  • Havya ve aksesuarları

Arduino IDE'nizi Kurma

Kodu yüklemeden önce, Arduino IDE'yi doğru kartlar ve kütüphanelerle yapılandırmanız gerekir. Bu işlem videoda yaklaşık 05:38'den itibaren ayrıntılı olarak anlatılmaktadır.

Adım 1: ESP32 Kartlarını Kurun

Henüz yapmadıysanız, ESP32 mikrodenetleyicileri için destek eklemeniz gerekir.

  1. Arduino IDE'yi açın, Dosya > Tercihler bölümüne gidin.
  2. "Ek kart yöneticisi URL'leri" alanına şu URL'yi ekleyin: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. Araçlar > Kart > Kart Yöneticisi... bölümüne gidin.
  4. esp32 arayın ve Espressif Systems paketini kurun.

Adım 2: Heltec ESP32 Kart Desteğini Ekleyin

  1. Dosya > Tercihler bölümünde, "Ek kart yöneticisi URL'leri" kutusuna yeni bir satıra Heltec JSON URL'sini ekleyin (video bunun nasıl yapılacağını 06:27'de gösterir): https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json
  2. Araçlar > Kart > Kart Yöneticisi... bölümüne geri dönün.
  3. Heltec ESP32 arayın ve paketi kurun.

Adım 3: Kütüphane Yöneticisinden Gerekli Kütüphaneleri Kurun

Arduino Kütüphane Yöneticisini (Eskiz > Kütüphane Ekle > Kütüphaneleri Yönet...) kullanarak aşağıdaki kütüphaneleri arayın ve kurun:

  1. Ai Esp32 Rotary Encoder: Duyarlı klavyemiz için kullanılan özel kütüphane.
  2. Heltec ESP32 Dev-Boards: Heltec donanımı için resmi kütüphane.
  3. Adafruit GFX Library: OLED ekran için bir bağımlılık.

Adım 4: Robojax Yardımcı Kütüphanesini .ZIP'ten Kurun

Videoda 18:53'te açıklandığı gibi, bu proje için özel bir yardımcı kütüphane kullanılır. Bunu bir .zip dosyasından kurmanız gerekir.

  1. Kaynaklar sayfasındaki bağlantıdan "Robojax HealthTech Lora 32" .zip kütüphanesini indirin.
  2. Arduino IDE'de Eskiz > Kütüphane Ekle > .ZIP Kütüphanesi Ekle... seçeneğine tıklayın.
  3. İndirdiğiniz .zip dosyasını kaydettiğiniz konuma gidin, seçin ve "Aç"a tıklayın.
  4. Kütüphane kurulacak ve kullanıma hazır olacaktır.

Her şeyi kurduktan sonra, menüden doğru kartı seçtiğinizden emin olun: Araçlar > Kart > Heltec ESP32 Arduino > Heltec WiFi LoRa 32 V3.


Bileşenlerin Bağlanması

Döner kodlayıcıyı bağlamak için Heltec kartınıza pin başlıklarını lehimlemeniz gerekecektir. Videoda gösterildiği gibi (11:25), kart kutusuna yerleştirildiğinde pinlere erişilebilir olmasını sağlamak için dikkatli bir planlama gereklidir.

Helte_Wifi_LoRA_Rotary_Encoder

Şema 1: Çekirdek Mesajlaşma (Yalnızca Döner Kodlayıcı)

Döner kodlayıcı modülündeki 5 pini Heltec kartına aşağıdaki gibi bağlayın:

  • Kodlayıcıdaki GND → Heltec kartındaki GND
  • Kodlayıcıdaki VCC → Heltec kartındaki 3.3V
  • SW (Anahtar) → GPIO 4
  • DT (Veri/Pin B) → GPIO 6
  • CLK (Saat/Pin A) → GPIO 5
Helte_Wifi_LoRA_Rotary_Encoder with buzzer

Şema 2: Sesli Uyarı Ekleme (Buzzer Devresi)

Yeni bir mesaj geldiğinde sesli bir uyarı almak için basit bir transistör devresi ekleyebilirsiniz. Bu gereklidir çünkü ESP32'nin pinleri bir buzzer'ı doğrudan sürmek için yeterli akımı sağlayamaz.

  • Heltec kartındaki GPIO 40 pinini 1kΩ direncin bir ucuna bağlayın.
  • Direncin diğer ucunu 2N2222 transistörünün Base pinine bağlayın.
  • Transistörün Emitter pinini GND'ye bağlayın.
  • 5V buzzer'ın pozitif (+) bacağını Heltec kartındaki 3.3V pinine bağlayın.
  • Buzzer'ın negatif (-) bacağını transistörün Collector pinine bağlayın.

Kodu Anlama ve Özelleştirme

Sağlanan kod yüklemeye hazırdır, ancak projeyi özelleştirebilmeniz için bilmeniz gereken birkaç önemli ayar vardır. Bu tanımlar kodun en üstündedir.


// Fiziksel pin bağlantıları için tanımlar
#define ROTARY_ENCODER_A_PIN 5 //döner kodlayıcının A pini
#define ROTARY_ENCODER_B_PIN 6 //döner kodlayıcının B pini
#define ROTARY_ENCODER_BUTTON_PIN 4 //döner kodlayıcının anahtar pini
#define BUZZER_PIN 40 //tetiklenecek buzzer pini veya röle
const bool DEBUG = false;

// LoRa ve Güvenlik ayarları için tanımlar
#define RF_FREQUENCY 915432000 //LoRa'nın çalışma frekansı
#define TX_OUTPUT_POWER 2 //çıkış gücü dBm cinsinden 2 ile 21 arasında olmalıdır. 
const char *userKey = "xxxBg^Tr%43232";//özel iletişim için güvenlik anahtarı
  • ROTARY_ENCODER_...: Bu pinler döner kodlayıcınızı nereye bağladığınızı tanımlar. Bu sayıların yukarıdaki şemalardaki bağlantılarınızla eşleştiğinden emin olun.
  • BUZZER_PIN: Bu, buzzer devresini hangi pinin tetikleyeceğini tanımlar.
  • RF_FREQUENCY: Bu, LoRa'nın Hertz cinsinden çalışma frekansıdır. 915432000 (915 MHz) değeri Kuzey Amerika için standarttır. Bu değeri bölgenize göre değiştirmelisiniz (örneğin, Avrupa için 868MHz, Asya için 433MHz). İletişim kurmak isteyen tüm cihazlar aynı frekansı kullanmalıdır.
  • TX_OUTPUT_POWER: Bu, dBm cinsinden iletim gücüdür. 2 ile 20 arasında değişebilir. 2 gibi daha düşük bir sayı kısa mesafe testleri için mükemmeldir ve önemli ölçüde pil tasarrufu sağlar. Maksimum menzil için bu değeri artırabilirsiniz, ancak daha yüksek gücün pili çok daha hızlı tükettiğini unutmayın.
  • userKey: Bu sizin özel şifreleme anahtarınızdır. Herhangi bir karakter kombinasyonu olabilir. Tüm cihazların birbirinin mesajlarını çözebilmesi için birebir aynı anahtara sahip olması gerekir. Bunu kendi benzersiz ve gizli şifrenizle değiştirin.

Kodu Edinin

Bu proje için videoda atıfta bulunulan eksiksiz ve son Arduino kodu bu sayfada indirilebilir.

Video Bölümleri

  • 00:00 - Giriş
  • 00:56 - Proje Özeti
  • 01:29 - Bileşenlere Genel Bakış
  • 05:25 - Kutudan Çıkarma ve Donanım Montajı
  • 11:19 - Harici Kablolar için Kasa Modifikasyonu
  • 15:06 - Bağlantı Rehberi
  • 15:38 - Arduino IDE ve Kütüphane Kurulumu
  • 20:21 - Kod Ayarları Açıklandı
  • 21:38 - Sohbet Etmek için Döner Kodlayıcı Nasıl Kullanılır
  • 24:26 - Canlı Menzil Testi
785-Secure, Long-Range LoRa Chat Device Heltec WiFi LoRa 32
Dil: 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
Dil: C++
/*
 * ESP32 Projesi: Güvenli, Uzun Menzilli Bir LoRa Sohbet Cihazı Oluşturun Heltec WiFi LoRa 32
 * 
 * Dosya: Heltec_ESP32_LoRa_V3_chat_TX_RX_buzzer.ino
 * 29 Haziran 2025 tarihinde Ahmad Shamshiri tarafından yazılmıştır www.Roboajx.com
 * Lütfen eğitim videosunu şu adresten izleyin: https://youtu.be/IsOSvrF60J4
 * Bu videonun kaynak sayfası: https://robojax.com/RJT642
 * 
 * ==============================================================================
 * ARDUINO KOD AÇIKLAMASI: GÜVENLİ LoRa Klavye Alıcı-Verici (sohbet sistem)
 * ===========================================================================
 * 
 * DONANIM BİLEŞENLERİ:
 * -------------------
 * - Ana Kontrol Cihazı: Heltec WiFi LoRa 32 V3
 * - Muhafaza: 3000mAh pil ile Meshnology N33 kasa
 * - Giriş: Basmalı düğmeli döner kodlayıcı
 * - Sesli: Aktif buzzer (40. pine bağlı)
 * - Geri Bildirim: Dahili OLED ekran
 * 
 * SİSTEM İŞLEVSELLİĞİ:
 * -------------------
 * YENİ KONTROLLER:
 * - Döndürme Düğmesi: Karakteri seçin.
 * - Kısa Basma (Tıklama): Seçilen karakteri ekleyin. - Uzun Basma (Basılı Tutma): Boşluk (boşluk çubuğu gibi) ekler.
 * - Çift Tıklama: Geri Alma.
 * 
 * [2] GÜVENLİ KABLOSUZ İLETİM:
 * - LoRa iletiminden önce tüm mesajlar şifrelenir
 * - Ana konum komutu özel güvenli paket olarak iletilir
 * - Güvenilir iletişim için 433MHz/915MHz LoRa bandı kullanılır
 * 
 * [3] GÜÇ YÖNETİMİ:
 * - Pil ile çalışma için optimize edilmiştir (3000mAh)
 * - İletimler arasında düşük güç modları
 * 
 * TAM KURULUM TALİMATLARI İÇİN:
 * Lütfen eğitim videosunu şu adresten izleyin: https://youtu.be/IsOSvrF60J4
 * Bu videonun kaynak sayfası: https://robojax.com/RJT642
 * ==========================================================================
 * 
 * SORUMLULUK REDDİ:
 * Bu kod "ŞU ŞEKİLDE" sağlanmıştır "IS" herhangi bir garanti olmaksızın geçerlidir. Yazar, bu kodun kullanımından kaynaklanan herhangi bir zarardan sorumlu tutulamaz.
 * 
 * LİSANS:
 * Bu çalışma, GNU Genel Kamu Lisansı v3.0 kapsamında lisanslanmıştır.
 * Bu lisansın kapsamı dışındaki izinler Robojax.com adresinde bulunabilir.
 * 
 * PAYLAŞIM ŞARTLARI:
 * Bu kodu ticari olmayan amaçlarla paylaşabilir, kopyalayabilir ve değiştirebilirsiniz. ŞARTLAR:
 * 1. Bu yorum bloğunun tamamını orijinal kodla birlikte saklayın.
 * 2. Orijinal Robojax.com bağlantısını ekleyin.
 * 3. YouTube eğitim bağlantısını (varsa) saklayın.
 * 4. Yapılan değişiklikleri açıkça belirtin.
 * 
 * /
 * 
 * // Core Heltec ve görüntüleme kütüphaneleri
 */
#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);
}

Kaynaklar ve referanslar

Dosyalar📁

Diğer dosyalar