Cerca codice

Progetto ESP32: Costruisci un dispositivo di chat LoRa sicuro e a lunga portata Heltec WiFi LoRa 32 su N33

Questa lezione fa parte di: Introduzione al WiFi LoRa

Progetto ESP32: Costruisci un dispositivo di chat LoRa sicuro e a lunga portata Heltec WiFi LoRa 32 su N33

Costruisci un Messenger LoRa Sicuro e Off-Grid con una Tastiera Personalizzata

In un mondo che dipende dalla connettività costante, cosa fai quando il Wi-Fi è giù, manca la corrente e il servizio cellulare è sparito? Questo progetto affronta esattamente quel problema creando un dispositivo di comunicazione completamente autonomo e off-grid che ti permette di inviare e ricevere messaggi di testo sicuri e privati su distanze fino a 13 miglia (21 km) o anche di più.

Questo dispositivo è uno strumento pratico per la preparazione alle emergenze, avventure all'aperto come escursioni, o semplicemente per chiunque sia interessato a costruire una rete di comunicazione robusta e privata che controlli. Utilizzando un potente modulo Heltec WiFi LoRa 32 V3 racchiuso in una custodia resistente Meshnology N33 con una batteria a lunga durata da 3000mAh, questo progetto è costruito per l'uso nel mondo reale. La caratteristica più unica è la sua tastiera—un semplice encoder rotativo che fornisce un modo intuitivo e affidabile per digitare messaggi ovunque, in qualsiasi momento.


Come Funziona: La Magia di LoRa e un Encoder Rotativo

Il progetto combina tre concetti chiave per creare un messenger funzionale:

  • Radio LoRa: LoRa (Long Range) è una tecnologia radio che consente la comunicazione su vaste distanze con pochissima energia. A differenza di Wi-Fi o cellulare, opera su frequenze radio senza licenza, il che significa che non ci sono SIM card o costi mensili. Il nostro dispositivo funziona come un ricetrasmettitore completo, il che significa che può sia inviare che ricevere messaggi. Quando è inattivo, ascolta i pacchetti in arrivo. Dopo aver inviato o ricevuto un messaggio, mette la radio in sleep momentaneamente per risparmiare energia prima di tornare in modalità di ascolto.
  • Tastiera con Encoder Rotativo: Invece di una tastiera ingombrante o ad alto consumo energetico, usiamo un semplice encoder rotativo per l'input di testo. Ruotando il knob si scorre attraverso un set completo di caratteri (A-Z, a-z, numeri e simboli). L'interfaccia è sorprendentemente veloce e reattiva, permettendoti di comporre facilmente messaggi. Il pulsante integrato dell'encoder ha tre funzioni:
    • Pressione Breve (Click): Aggiunge il carattere selezionato al tuo messaggio.
    • Pressione Lunga (Hold): Aggiunge uno spazio, come una barra spaziatrice.
    • Doppio Click: Funziona come un backspace per eliminare l'ultimo carattere.
  • Ricetrasmettitore & Logica di Visualizzazione: Il dispositivo ha due modalità principali: "Componi" e "Leggi". Scrivi il tuo messaggio in modalità Componi. Quando arriva un nuovo messaggio, appare una notifica sullo schermo (>> NUOVO MSG <<). Puoi quindi premere il pulsante utente integrato (sulla scheda Heltec) per passare alla modalità Leggi e visualizzare il messaggio. Un'altra pressione ti riporta alla modalità Componi per scrivere la tua risposta. Tutti i messaggi sono protetti con crittografia AES, garantendo che le tue conversazioni rimangano private.

Componenti e Lista delle Parti

Per costruire questo progetto, avrai bisogno dei seguenti componenti. Come menzionato nel video, acquistare tramite link affiliati è un ottimo modo per supportare i creatori.

  • Modulo Heltec WiFi LoRa 32 V3
  • Custodia Meshtastic N33 con Batteria da 3000mAh
  • Antenna LoRa da 915MHz o appropriata per la regione
  • Modulo Encoder Rotativo (5 pin con VCC, GND, CLK, DT, SW)
  • Opzionale (per avvisi audio):
    • Buzzer Attivo da 5V
    • Transistor NPN 2N2222
    • Resistenza da 1kΩ
  • Pin Header e Cavi Jumper
  • Saldatore e accessori

Configurazione del Tuo Arduino IDE

Prima di poter caricare il codice, devi configurare l'Arduino IDE con le schede e le librerie corrette. Questo processo è dettagliato nel video a partire da circa 05:38.

Passo 1: Installa le Schede ESP32

Se non l'hai già fatto, devi aggiungere il supporto per i microcontrollori ESP32.

  1. Apri l'Arduino IDE, vai su File > Preferenze.
  2. Nel campo "URL aggiuntivi per il gestore schede", aggiungi il seguente URL: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
  3. Vai su Strumenti > Scheda > Gestore Schede....
  4. Cerca esp32 e installa il pacchetto di Espressif Systems.

Passo 2: Aggiungi il Supporto per le Schede Heltec ESP32

  1. In File > Preferenze, aggiungi l'URL JSON di Heltec su una nuova riga nel campo "URL aggiuntivi per il gestore schede" (il video mostra come fare questo a 06:27): https://github.com/Heltec-Aaron-Lee/WiFi_Kit_series/releases/download/0.0.7/package_heltec_esp32_index.json
  2. Torna su Strumenti > Scheda > Gestore Schede....
  3. Cerca Heltec ESP32 e installa il pacchetto.

Passo 3: Installa le Librerie Richieste dal Gestore Librerie

Utilizzando il Gestore Librerie dell'Arduino IDE (Sketch > Includi Libreria > Gestisci Librerie...), cerca e installa le seguenti librerie:

  1. Ai Esp32 Rotary Encoder: La libreria specifica usata per la nostra tastiera reattiva.
  2. Heltec ESP32 Dev-Boards: La libreria ufficiale per l'hardware Heltec.
  3. Adafruit GFX Library: Una dipendenza per il display OLED.

Passo 4: Installa la Libreria Helper Robojax da .ZIP

Come spiegato nel video a 18:53, una libreria helper personalizzata viene usata per questo progetto. Devi installarla da un file .zip.

  1. Scarica la libreria .zip "Robojax HealthTech Lora 32" dal link fornito nella pagina delle risorse.
  2. Nell'Arduino IDE, clicca su Sketch > Includi Libreria > Aggiungi Libreria .ZIP....
  3. Naviga fino a dove hai salvato il file .zip scaricato, selezionalo e clicca "Apri".
  4. La libreria verrà installata e pronta all'uso.

Dopo aver installato tutto, assicurati di selezionare la scheda corretta dal menu: Strumenti > Scheda > Heltec ESP32 Arduino > Heltec WiFi LoRa 32 V3.


Cablaggio dei Componenti

Dovrai saldare i connettori pin alla tua scheda Heltec per collegare l'encoder rotativo. Come mostrato nel video (11:25), è necessaria una pianificazione attenta per garantire che i pin siano accessibili una volta che la scheda è nella sua custodia.

Helte_Wifi_LoRA_Rotary_Encoder

Diagramma 1: Messenger Principale (Solo Encoder Rotativo)

Collega i 5 pin del modulo encoder rotativo alla scheda Heltec come segue:

  • GND sull'encoder → GND sulla scheda Heltec
  • VCC sull'encoder → 3.3V sulla scheda Heltec
  • SW (Interruttore) → GPIO 4
  • DT (Dati/Pin B) → GPIO 6
  • CLK (Clock/Pin A) → GPIO 5
Helte_Wifi_LoRA_Rotary_Encoder con buzzer

Diagramma 2: Aggiunta di un Avviso Acustico (Circuito del Buzzer)

Per ottenere un segnale acustico quando arriva un nuovo messaggio, puoi aggiungere un semplice circuito a transistor. Questo è necessario perché i pin dell'ESP32 non possono fornire corrente sufficiente per pilotare direttamente un buzzer.

  • Collega GPIO 40 dalla scheda Heltec a un'estremità della resistenza da 1kΩ.
  • Collega l'altra estremità della resistenza al pin Base del transistor 2N2222.
  • Collega il pin Emitter del transistor a GND.
  • Collega la gamba positiva (+) del buzzer da 5V al pin 3.3V sulla scheda Heltec.
  • Collega la gamba negativa (-) del buzzer al pin Collector del transistor.

Comprendere e Personalizzare il Codice

Il codice fornito è pronto per il caricamento, ma ci sono alcune impostazioni chiave che dovresti conoscere per personalizzare il progetto. Queste definizioni si trovano all'inizio del codice.


// Definizioni per le connessioni fisiche dei pin
#define ROTARY_ENCODER_A_PIN 5 //pin A dell'encoder rotativo
#define ROTARY_ENCODER_B_PIN 6 //pin B dell'encoder rotativo
#define ROTARY_ENCODER_BUTTON_PIN 4 //pin dell'interruttore dell'encoder rotativo
#define BUZZER_PIN 40 //pin del buzzer o relè da attivare
const bool DEBUG = false;

// Definizioni per le impostazioni LoRa e di Sicurezza
#define RF_FREQUENCY 915432000 //frequenza operativa di LoRa
#define TX_OUTPUT_POWER 2 //la potenza di uscita dovrebbe essere tra 2 e 21 in dBm. 
const char *userKey = "xxxBg^Tr%43232";//chiave di sicurezza per comunicazione privata
  • ROTARY_ENCODER_...: Questi pin definiscono dove hai collegato il tuo encoder rotativo. Assicurati che questi numeri corrispondano al tuo cablaggio nei diagrammi sopra.
  • BUZZER_PIN: Questo definisce quale pin attiverà il circuito del buzzer.
  • RF_FREQUENCY: Questa è la frequenza operativa LoRa in Hertz. Il valore 915432000 (915 MHz) è standard per il Nord America. Devi cambiare questo valore in base alla tua regione (es. 868MHz per l'Europa, 433MHz per l'Asia). Tutti i dispositivi che vogliono comunicare devono usare la stessa frequenza.
  • TX_OUTPUT_POWER: Questa è la potenza di trasmissione in dBm. Può variare da 2 a 20. Un numero più basso come 2 è perfetto per test a corto raggio e risparmia una quantità significativa di batteria. Per la massima portata, puoi aumentare questo valore, ma tieni presente che una potenza maggiore scarica la batteria molto più velocemente.
  • userKey: Questa è la tua chiave di crittografia privata. Può essere qualsiasi combinazione di caratteri. Tutti i dispositivi devono avere esattamente la stessa chiave per decrittare i messaggi degli altri. Cambiala con la tua password unica e segreta.

Ottieni il Codice

Il codice Arduino completo e finale per questo progetto, come referenziato nel video, è disponibile per il download in questa pagina.

Capitoli del Video

  • 00:00 - Introduzione
  • 00:56 - Schema del Progetto
  • 01:29 - Panoramica dei Componenti
  • 05:25 - Unboxing e Assemblaggio Hardware
  • 11:19 - Modifica della Custodia per Cavi Esterni
  • 15:06 - Guida al Cablaggio
  • 15:38 - Configurazione di Arduino IDE e Librerie
  • 20:21 - Impostazioni del Codice Spiegate
  • 21:38 - Come Usare l'Encoder Rotativo per Chattare
  • 24:26 - Test di Portata dal Vivo
785-Secure, Long-Range LoRa Chat Device Heltec WiFi LoRa 32
Lingua: C++
/*
 * Progetto ESP32: Costruisci un dispositivo di chat LoRa sicuro e a lungo raggio Heltec WiFi LoRa 32
 * 
 * File: Heltec_ESP32_LoRa_V3_chat_TX_RX.ino
 * scritto il 29 giugno 2025 da Ahmad Shamshiri www.Roboajx.com
 * Si prega di guardare il video tutorial su: https://youtu.be/IsOSvrF60J4
 * Pagina delle risorse per questo video: https://robojax.com/RJT642
 * 
 * =====================================================================
 * DESCRIZIONE DEL CODICE ARDUINO: TRASCEIVER SICURO LoRa (sistema di chat)
 * =====================================================================
 * 
 * COMPONENTI HARDWARE:
 * -------------------
 * - Microcontrollore principale: Heltec WiFi LoRa 32 V3
 * - Involucro: case Meshnology N33 con batteria da 3000mAh
 * - Input: encoder rotativo con pulsante
 * - Feedback: display OLED integrato
 * 
 * FUNZIONALITÀ DEL SISTEMA:
 * -------------------
 * NUOVI CONTROLLI:
 * - Manopola di Rotazione: Seleziona carattere.
 * - Pressione Breve (Click): Aggiungi carattere selezionato.
 * - Pressione Lunga (Hold): Inserisci uno spazio (come una barra spaziatrice).
 * - Doppio Click: Backspace.
 * 
 * [2] TRASMISSIONE WIRELESS SICURA:
 * - Tutti i messaggi sono crittografati prima della trasmissione LoRa
 * - Il comando di posizione iniziale viene trasmesso come pacchetto sicuro speciale
 * - Utilizza la banda LoRa 433MHz/915MHz per comunicazioni affidabili
 * 
 * [3] GESTIONE DELL'ENERGIA:
 * - Ottimizzato per funzionamento a batteria (3000mAh)
 * - Modalità a basso consumo tra le trasmissioni
 * 
 * PER ISTRUZIONI COMPLETE DI CONFIGURAZIONE:
 * Si prega di guardare il video tutorial su: https://youtu.be/IsOSvrF60J4
 * Pagina delle risorse per questo video: https://robojax.com/RJT642
 * =====================================================================
 * 
 * DISCLAIMER:
 * Questo codice è fornito "COSÌ COM'È" senza alcuna garanzia. L'autore
 * non sarà ritenuto responsabile per eventuali danni derivanti dall'uso di questo codice.
 * 
 * LICENZA:
 * Quest'opera è sotto licenza GNU General Public License v3.0
 * Permessi al di fuori dell'ambito di questa licenza potrebbero essere disponibili su Robojax.com
 * 
 * TERMINI DI CONDIVISIONE:
 * Sei libero di condividere, copiare e modificare questo codice per scopi non commerciali
 * FAI SALVO che:
 * 1. Mantieni intatto questo intero blocco di commento con il codice originale
 * 2. Includi il link originale di Robojax.com
 * 3. Mantieni il link del tutorial di YouTube (se applicabile)
 * 4. Indica chiaramente eventuali modifiche apportate
 * 
 * /
 * // Librerie core Heltec e display
 */
#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
Lingua: C++
/*
 * Progetto ESP32: Costruire un dispositivo di chat LoRa sicuro e a lungo raggio Heltec WiFi LoRa 32
 * 
 * File: Heltec_ESP32_LoRa_V3_chat_TX_RX_buzzer.ino
 * scritto il 29 giugno 2025 da Ahmad Shamshiri www.Roboajx.com
 * Si prega di guardare il video tutorial su: https://youtu.be/IsOSvrF60J4
 * Pagina delle risorse per questo video: https://robojax.com/RJT642
 * 
 * =====================================================================
 * DESCRIZIONE DEL CODICE ARDUINO: TRASCEIVER TASTIERA LoRa SICURO (sistema di chat)
 * =====================================================================
 * 
 * COMPONENTI HARDWARE:
 * -------------------
 * - Controller principale: Heltec WiFi LoRa 32 V3
 * - Involucro: Custodia Meshnology N33 con batteria da 3000mAh
 * - Input: Encoder rotativo con pulsante
 * - Udibile: Buzzer attivo (collegato al pin 40)
 * - Feedback: Display OLED integrato
 * 
 * FUNZIONALITÀ DEL SISTEMA:
 * -------------------
 * NUOVI CONTROLLI:
 * - Manopola di rotazione: Seleziona carattere.
 * - Pressione breve (Clic): Aggiungi carattere selezionato.
 * - Pressione prolungata (Tieni premuto): Inserisci uno spazio (come un tasto barra spaziatrice).
 * - Doppio clic: Cancella.
 * 
 * [2] TRASMISSIONE WIRELESS SICURA:
 * - Tutti i messaggi crittografati prima della trasmissione LoRa
 * - Comando di posizione home trasmesso come pacchetto speciale sicuro
 * - Utilizza la banda LoRa 433MHz/915MHz per comunicazioni affidabili
 * 
 * [3] GESTIONE DELL'ENERGIA:
 * - Ottimizzato per funzionamento a batteria (3000mAh)
 * - Modalità a basso consumo tra le trasmissioni
 * 
 * PER ISTRUZIONI COMPLETE DI CONFIGURAZIONE:
 * Si prega di guardare il video tutorial su: https://youtu.be/IsOSvrF60J4
 * Pagina delle risorse per questo video: https://robojax.com/RJT642
 * =====================================================================
 * 
 * DICHIARAZIONE DI NON RESPONSABILITÀ:
 * Questo codice è fornito "COSÌ COM'È" senza alcuna garanzia di alcun tipo. L'autore
 * non sarà ritenuto responsabile per eventuali danni derivanti dall'uso di questo codice.
 * 
 * LICENZA:
 * Questo lavoro è concesso in licenza secondo la GNU General Public License v3.0
 * Permessi al di fuori dell'ambito di questa licenza potrebbero essere disponibili su Robojax.com
 * 
 * TERMINI DI CONDIVISIONE:
 * Sei libero di condividere, copiare e modificare questo codice per scopi non commerciali
 * PURCHÉ tu:
 * 1. Mantenga questo intero blocco di commento intatto con il codice originale
 * 2. Includa il link originale di Robojax.com
 * 3. Mantenga il link del tutorial di YouTube (se applicabile)
 * 4. Indichi chiaramente eventuali modifiche apportate
 * 
 * /
 * 
 *  // Librerie principali Heltec e di visualizzazione
 */
#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);
}

Risorse e riferimenti

File📁

Altri file