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.
- Apri l'Arduino IDE, vai su
File > Preferenze. - 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 - Vai su
Strumenti > Scheda > Gestore Schede.... - Cerca
esp32e installa il pacchetto di Espressif Systems.
Passo 2: Aggiungi il Supporto per le Schede Heltec ESP32
- 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 - Torna su
Strumenti > Scheda > Gestore Schede.... - Cerca
Heltec ESP32e 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:
- Ai Esp32 Rotary Encoder: La libreria specifica usata per la nostra tastiera reattiva.
- Heltec ESP32 Dev-Boards: La libreria ufficiale per l'hardware Heltec.
- 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.
- Scarica la libreria .zip "Robojax HealthTech Lora 32" dal link fornito nella pagina delle risorse.
- Nell'Arduino IDE, clicca su
Sketch > Includi Libreria > Aggiungi Libreria .ZIP.... - Naviga fino a dove hai salvato il file .zip scaricato, selezionalo e clicca "Apri".
- 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.

Diagramma 1: Messenger Principale (Solo Encoder Rotativo)
Collega i 5 pin del modulo encoder rotativo alla scheda Heltec come segue:
GNDsull'encoder →GNDsulla scheda HeltecVCCsull'encoder →3.3Vsulla scheda HeltecSW(Interruttore) →GPIO 4DT(Dati/Pin B) →GPIO 6CLK(Clock/Pin A) →GPIO 5

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 40dalla 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.3Vsulla 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 come2è 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
/*
* 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);
}
/*
* 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);
}
Common Course Links
Common Course Files
Risorse e riferimenti
-
EsternoAcquista Wi-Fi LoRa 32 da Meshnologymeshnology.com
File📁
Altri file
-
Robojax Heltec WiFi LoRa 32 Libreria Versione 1.1.0 20250703
Robojax_HeltecLoRa32_1.1.0_20250702.zip0.09 MB -