added LoRa communication device code

This commit is contained in:
Angoosh Leviocki 2025-03-28 01:17:38 +01:00
parent 13daf61863
commit f230dc841e
Signed by: angoosh
GPG Key ID: 2DAE446D291BD8D3

View File

@ -0,0 +1,618 @@
#include <WiFi.h>
#include <WebServer.h>
#include <ArduinoJson.h>
#include <SPI.h>
#include <SPIFFS.h>
#include <HardwareSerial.h>
HardwareSerial uart(0);
// WiFi credential
// Use different ssid for both module to avoid confusion
// example:
// const char* ssid = "ESP32-LoRa-Chat1"; for user 1
// const char* ssid = "ESP32-LoRa-Chat2"; for user 2
const char* ssid = "ESP32-LoRa-Chat";
const char* password = "chatpass123";
WebServer server(80);
#define MAX_MESSAGES 50
String messageBuffer[MAX_MESSAGES];
int messageCount = 0;
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP32 LoRa Chat</title>
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--background-gradient-1: #667eea;
--background-gradient-2: #764ba2;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background: linear-gradient(135deg, var(--background-gradient-1), var(--background-gradient-2));
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 0;
}
#username-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 10px;
text-align: center;
}
#username-input {
padding: 10px;
margin: 10px 0;
border: 1px solid #ddd;
border-radius: 5px;
width: 200px;
}
#status-bar {
background: rgba(255, 255, 255, 0.1);
padding: 10px;
border-radius: 10px;
margin-bottom: 10px;
color: white;
font-size: 0.9em;
text-align: center;
width: 90%;
max-width: 400px;
}
#chat-container {
width: 90%;
max-width: 400px;
height: 65vh;
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
overflow: hidden;
}
#message-area {
flex-grow: 1;
overflow-y: auto;
padding: 15px;
display: flex;
flex-direction: column;
gap: 10px;
}
.message {
max-width: 80%;
padding: 10px 15px;
border-radius: 15px;
margin-bottom: 10px;
word-wrap: break-word;
}
.sent-message {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
align-self: flex-end;
border-bottom-right-radius: 5px;
}
.received-message {
background: linear-gradient(135deg, #ecf0f1, #bdc3c7);
color: #2c3e50;
align-self: flex-start;
border-bottom-left-radius: 5px;
}
.username {
font-weight: bold;
font-size: 0.8em;
margin-bottom: 4px;
}
.message-info {
font-size: 0.7em;
opacity: 0.7;
margin-top: 4px;
}
.input-area {
display: flex;
padding: 15px;
background: rgba(255, 255, 255, 0.1);
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
#message-input {
flex-grow: 1;
padding: 10px 15px;
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 30px;
margin-right: 10px;
color: white;
}
#message-input::placeholder {
color: rgba(255, 255, 255, 0.6);
}
button {
background: linear-gradient(135deg, #3498db, #2ecc71);
color: white;
border: none;
padding: 10px 20px;
border-radius: 30px;
cursor: pointer;
transition: transform 0.3s ease;
}
button:hover {
transform: scale(1.05);
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>
</head>
<body>
<div id="username-modal">
<div class="modal-content">
<h2>Enter your username</h2>
<input type="text" id="username-input" placeholder="Username">
<button id="username-submit">Join Chat</button>
</div>
</div>
<div id="status-bar">
Connecting...
</div>
<div id="chat-container">
<div id="message-area"></div>
<div class="input-area">
<input type="text" id="message-input" placeholder="Type a message..." disabled>
<button id="send-btn" disabled>Send</button>
</div>
</div>
<script>
const statusBar = document.getElementById('status-bar');
const messageArea = document.getElementById('message-area');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const usernameModal = document.getElementById('username-modal');
const usernameInput = document.getElementById('username-input');
const usernameSubmit = document.getElementById('username-submit');
let username = '';
let messageCheckInterval;
usernameSubmit.addEventListener('click', () => {
username = usernameInput.value.trim();
if (username) {
usernameModal.style.display = 'none';
initChat();
}
});
function initChat() {
statusBar.textContent = 'Connected';
statusBar.style.background = 'rgba(46, 204, 113, 0.2)';
messageInput.disabled = false;
sendBtn.disabled = false;
// Start checking for new messages
messageCheckInterval = setInterval(checkNewMessages, 1000);
}
function checkNewMessages() {
fetch('/messages')
.then(response => response.json())
.then(data => {
messageArea.innerHTML = ''; // Clear existing messages
data.messages.forEach(msg => {
const [sender, content] = msg.split(': ');
const type = sender === username ? 'sent' : 'received';
addMessage(content, type, sender);
});
})
.catch(error => {
console.error('Error fetching messages:', error);
statusBar.textContent = 'Connection error';
statusBar.style.background = 'rgba(231, 76, 60, 0.2)';
});
}
function addMessage(content, type, sender) {
const messageDiv = document.createElement('div');
messageDiv.classList.add('message', `${type}-message`);
const usernameDiv = document.createElement('div');
usernameDiv.classList.add('username');
usernameDiv.textContent = sender;
messageDiv.appendChild(usernameDiv);
const messageContent = document.createElement('div');
messageContent.textContent = content;
messageDiv.appendChild(messageContent);
const messageInfo = document.createElement('div');
messageInfo.classList.add('message-info');
messageInfo.textContent = new Date().toLocaleTimeString();
messageDiv.appendChild(messageInfo);
messageArea.appendChild(messageDiv);
messageArea.scrollTop = messageArea.scrollHeight;
}
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
fetch('/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: message,
sender: username
})
})
.then(response => response.json())
.then(data => {
if (data.status === 'sent') {
messageInput.value = '';
}
})
.catch(error => {
console.error('Error sending message:', error);
statusBar.textContent = 'Error sending message';
statusBar.style.background = 'rgba(231, 76, 60, 0.2)';
});
}
}
sendBtn.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
)rawliteral";
void LoRa_Join(){
uint32_t time = millis();
bool msg_done = true;
uart.println("AT+MODE=LWOTAA");
delay(1000);
while(1){
if(msg_done){
uart.println("AT+JOIN");
msg_done = false;
}
if(uart.available()){
String str = uart.readStringUntil('\n');
Serial.println(str);
if(strcmp(str.c_str(), "+JOIN: Join failed\r") == 0){
msg_done = true;
}
if(strcmp(str.c_str(), "+JOIN: Done\r") == 0){
if(!msg_done){
break;
}
}
}
}
}
void LoRa_Send_MSG(char* msg){
uart.println(msg);
while(1){
if(uart.available()){
String str = uart.readStringUntil('\n');
Serial.println(str);
if(strcmp(str.c_str(), "+MSG: Done\r") == 0){
break;
}
}
}
}
uint8_t lora_msg[32][51];
void LoRa_Send_MSGHEX(const char* msg){//parses string into multiple messages if needed
//clear send buffer
for(int i = 0; i < 32; i++){
for(int j = 0; j < 51; j++){
lora_msg[i][j] = 0;
}
}
//get how many messages we will need (32 max = 1536 characters)
int len = strlen(msg);
int msg_no = len / 48;
if((len % 48) != 0){
msg_no ++;
}
//prepare messages
for(int i = 0; i < msg_no; i++){
if(len > 48){
lora_msg[i][0] = 51;
lora_msg[i][1] = i+1;
lora_msg[i][2] = msg_no;
for(int j = 0; j < 48; j++){
lora_msg[i][3+j] = msg[j+i*48];
}
len -= 48;
}
else{
lora_msg[i][0] = len+3;
lora_msg[i][1] = i+1;
lora_msg[i][2] = msg_no;
for(int j = 0; j < len; j++){
lora_msg[i][3+j] = msg[j+i*48];
}
len = 0;
}
}
//send messages
for(int i = 0; i < 32; i++){
if(lora_msg[i][0] != 0){
uart.print("AT+MSGHEX=\"");
Serial.print("AT+MSGHEX=\"");
for(int j = 0; j < lora_msg[i][0]; j++){
uart.printf("%02X",lora_msg[i][j]);
Serial.printf("%02X",lora_msg[i][j]);
}
uart.println("\"");
Serial.println("\"");
}
else{
return;
}
while(1){
if(uart.available()){
String str = uart.readStringUntil('\n');
Serial.println(str);
if(strcmp(str.c_str(), "+MSGHEX: Done\r") == 0){
break;
}
}
}
}
}
int asciiToHex(char *asciiHexChar){
uint8_t hi, lo;
uint8_t x;
x = (asciiHexChar[0] & 0x4F);
hi = (x & 0x0F) + ((x >> 6) * 9);
x = (asciiHexChar[1] & 0x4F);
lo = (x & 0x0F) + ((x >> 6) * 9);
return (hi << 4) + lo;
}
int rx_buffer[32][51];
int rx_index = 0;
void LoRa_Receive_MSG(){
if(uart.available()){
String str = uart.readStringUntil('\n');
Serial.println(str);
if(str.indexOf("MSG: PORT: 1; RX:") > 0){
char rxBytes[3] = {0,0,0};
int rx_len = 51;
for(int i = 0; i < rx_len; i++){
for(int j = 0; j < 2; j++){
rxBytes[j] = str.c_str()[i*2+j+20];
}
rx_buffer[rx_index][i] = asciiToHex(rxBytes);
if(i == 0){
rx_len = rx_buffer[rx_index][0];
}
}
if(rx_buffer[rx_index][1] == rx_buffer[rx_index][2]){
//transmit whole message
rx_index = 0;
String rxString = "";
for(int i = 0; i < 32; i++){
for(int j = 3; j < (rx_buffer[i][0]); j++){
rxString += (char)rx_buffer[i][j];
}
if(rx_buffer[i][0] < 51){
break;
}
}
addMessageToBuffer(rxString);
}
}
}
}
void setup() {
Serial.begin(115200);
uart.begin(9600, SERIAL_8N1, RX, TX);
if(!SPIFFS.begin(true)) {
Serial.println("SPIFFS initialization failed!");
return;
}
Serial.println("Initializing LoRa...");
//add LoRa E5 module init here
pinMode(5, OUTPUT);
digitalWrite(5, LOW);
delay(1000);
digitalWrite(5, HIGH);
delay(2000);
LoRa_Join();
LoRa_Send_MSG("AT+MSG=\"hello\"");
delay(1000);
uart.println("AT+CLASS=C");
//LoRa E5 module init done
WiFi.softAP(ssid, password);
Serial.println("Access Point Started");
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP());
server.on("/", HTTP_GET, handleRoot);
server.on("/send", HTTP_POST, handleSendMessage);
server.on("/messages", HTTP_GET, handleGetMessages);
server.begin();
Serial.println("Web Server Started");
}
void handleRoot() {
server.send(200, "text/html", index_html);
}
void handleSendMessage() {
if (server.hasArg("plain")) {
StaticJsonDocument<200> doc;
DeserializationError error = deserializeJson(doc, server.arg("plain"));
if (!error) {
String message = doc["message"].as<String>();
String sender = doc["sender"].as<String>();
Serial.println("Sending message");
Serial.print("Sender: ");
Serial.println(sender);
Serial.print("Message: ");
Serial.println(message);
String lora_msg = sender + ": " + message;
LoRa_Send_MSGHEX(lora_msg.c_str());
addMessageToBuffer(sender + ": " + message);
server.send(200, "application/json", "{\"status\":\"sent\"}");
} else {
server.send(400, "application/json", "{\"error\":\"Invalid JSON\"}");
}
}
}
void handleGetMessages() {
StaticJsonDocument<2048> doc;
JsonArray array = doc.createNestedArray("messages");
for(int i = 0; i < messageCount; i++) {
array.add(messageBuffer[i]);
}
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
}
void addMessageToBuffer(String message) {
if (messageCount >= MAX_MESSAGES) {
for(int i = 0; i < MAX_MESSAGES - 1; i++) {
messageBuffer[i] = messageBuffer[i + 1];
}
messageCount--;
}
messageBuffer[messageCount++] = message;
}
String incoming = "";
int incomingByte = 0;
void loop() {
server.handleClient();
LoRa_Receive_MSG();
//if(uart.available()){
// String str = uart.readStringUntil('\n');
// Serial.println(str);
// if(str.indexOf("MSG: PORT: 1; RX:") > 0){
// Serial.println("yayy");
// }
//}
//delay(1000);
//if(Serial.available()){
// String str = Serial.readStringUntil('\n');
// uart.println(str);
//}
/*
if (Serial.available() > 0) {
incomingByte = Serial.read();
if(incomingByte != '#'){
incoming += (char)incomingByte;
}
if(incomingByte == '#'){
addMessageToBuffer(incoming);
incoming = "";
}
}
*/
/*
int packetSize = LoRa.parsePacket();
if (packetSize) {
byte recipient = LoRa.read();
byte sender = LoRa.read();
String incoming = "";
while (LoRa.available()) {
incoming += (char)LoRa.read();
}
if (recipient == localAddress) {
addMessageToBuffer(incoming);
}
}*/
}