How can i send and receive data simultaneously in Heltec Wifi Lora 32 (V3)?

Hello. I have a Heltec Wifi Lora 32 (V3) which successfully sends GPS information to the LoRa gateway (in this case, a RAK Wisgate Edge Pro V2 7289 model) which forwards the information to a Chirpstack V4 server. I can successfully view the data in my database (MySQL) through the use of a decode function in Node-RED. This data is displayed in a web client through an API with Python. So far so good.

However, I need the web client to be able to send data to the node, that is, from the web page you can “press a button” to send an “alert” to the node, where it receives that alert and based on that some action is taken (such as sending a pulse to sound a small horn and/or turn on an LED, through GPIO).

Basically, what I need is, from the node, to be able to send real-time GPS information and at the same time be prepared to receive alert information from the web client.

Here is the code (it works fine) that send de GPS data to LoRaWAN:

#include “LoRaWan_APP.h”
#include “Arduino.h”
#include <TinyGPS++.h>
#include <Wire.h>
#include “HT_SSD1306Wire.h”

#ifdef WIRELESS_STICK_V3
SSD1306Wire pantalla(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_64_32, RST_OLED); // addr , freq , i2c group , resolution , rst
#else
SSD1306Wire pantalla(0x3c, 500000, SDA_OLED, SCL_OLED, GEOMETRY_128_64, RST_OLED); // addr , freq , i2c group , resolution , rst
#endif

TinyGPSPlus GPS;

#define VGNSS_CTRL 3
// These are no the real 0TAA values, only for this topic
uint8_t devEui[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11};
uint8_t appEui[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t appKey[] = {0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99};

uint8_t nwkSKey[] = {0x15, 0xb1, 0xd0, 0xef, 0xa4, 0x63, 0xdf, 0xbe, 0x3d, 0x11, 0x18, 0x1e, 0x1e, 0xc7, 0xda, 0x85};
uint8_t appSKey[] = {0xd7, 0x2c, 0x78, 0x75, 0x8c, 0xdc, 0xca, 0xbf, 0x55, 0xee, 0x4a, 0x77, 0x8d, 0x16, 0xef, 0x67};
uint32_t devAddr = (uint32_t)0x007e6ae1;

uint16_t userChannelsMask[6] = {0x00FF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000};

uint32_t license[4] = {0xAAAAAAAA, 0xBBBBBBBB, 0xCCCCCCCC, 0xDDDDDDDD};

LoRaMacRegion_t loraWanRegion = ACTIVE_REGION;
DeviceClass_t loraWanClass = CLASS_A;
uint32_t appTxDutyCycle = 15000;
bool overTheAirActivation = true;
bool loraWanAdr = true;
bool isTxConfirmed = true;
uint8_t appPort = 2;
uint8_t confirmedNbTrials = 4;

void GPS_test() {
while (Serial2.available() > 0)
if (GPS.encode(Serial2.read()))
displayInfo();

if (millis() > 5000 && GPS.charsProcessed() < 10)
{
pantalla.clear();
pantalla.drawString(0, 0, “No GPS detected:”);
pantalla.drawString(0, 10, “Check wiring.”);
pantalla.display();
while(true);
}
}

void prepareTxFrame(uint8_t port) {
Serial.println(“Preparing frame to send…”);
pinMode(Vext, OUTPUT);
digitalWrite(Vext, HIGH);

float lat, lon;

Serial.println("Esperando fijación del GPS...");
unsigned long startTime = millis(); // Tiempo de inicio
while (!GPS.location.isValid()) {
    if ((millis() - startTime) > 10000) {
        Serial.println("No valid GPS fix has been received within 10 seconds.");
        digitalWrite(Vext, LOW);
        return;
    }

    if (Serial2.available()) {
      lat = GPS.location.lat();
      lon = GPS.location.lng();
    }
}

lat = GPS.location.lat();
lon = GPS.location.lng();
digitalWrite(Vext, LOW);

unsigned char *puc;
appDataSize = 0;
puc = (unsigned char *)(&lat);
appData[appDataSize++] = puc[0];
appData[appDataSize++] = puc[1];
appData[appDataSize++] = puc[2];
appData[appDataSize++] = puc[3];
puc = (unsigned char *)(&lon);
appData[appDataSize++] = puc[0];
appData[appDataSize++] = puc[1];
appData[appDataSize++] = puc[2];
appData[appDataSize++] = puc[3];

Serial.print(", LAT: ");
Serial.print(GPS.location.lat());
Serial.print(", LON: ");
Serial.print(GPS.location.lng());

}

void setup() {
Serial2.begin(9600, SERIAL_8N1, 46, 45);
Serial.begin(115200);
pantalla.init();
pantalla.setFont(ArialMT_Plain_10);
pantalla.setTextAlignment(TEXT_ALIGN_LEFT);
pinMode(Vext, OUTPUT);
digitalWrite(Vext, HIGH);
Mcu.setlicense(license, HELTEC_BOARD);
Mcu.begin(HELTEC_BOARD, SLOW_CLK_TPYE);
}

void loop() {
GPS_test();
switch (deviceState) {
case DEVICE_STATE_INIT: {
#if(LORAWAN_DEVEUI_AUTO)
LoRaWAN.generateDeveuiByChipID();
#endif
LoRaWAN.init(loraWanClass, loraWanRegion);
LoRaWAN.setDefaultDR(3);
break;
}
case DEVICE_STATE_JOIN: {
Serial.println(“Joining…”);
LoRaWAN.join();
break;
}
case DEVICE_STATE_SEND: {
Serial.println(“Sending…”);
prepareTxFrame(appPort);
LoRaWAN.send();
deviceState = DEVICE_STATE_CYCLE;
break;
}
case DEVICE_STATE_CYCLE: {
txDutyCycleTime = appTxDutyCycle + randr(-APP_TX_DUTYCYCLE_RND, APP_TX_DUTYCYCLE_RND);
LoRaWAN.cycle(txDutyCycleTime);
deviceState = DEVICE_STATE_SLEEP;
break;
}
case DEVICE_STATE_SLEEP: {
Serial.println(“Sleep…”);
LoRaWAN.sleep(loraWanClass);
break;
}
default: {
deviceState = DEVICE_STATE_INIT;
break;
}
}
}

void displayInfo()
{
pantalla.clear();
pantalla.drawString(0, 0, "Location: ");
if (GPS.location.isValid())
{
pantalla.drawString(0, 10, String(GPS.location.lat(), 6) + “,” + String(GPS.location.lng(), 6));
}
else
{
pantalla.drawString(0, 10, “INVALID”);
}

pantalla.drawString(0, 20, "Date/Time: ");
if (GPS.date.isValid())
{
pantalla.drawString(0, 30, String(GPS.date.month()) + “/” + String(GPS.date.day()) + “/” + String(GPS.date.year()));
}
else
{
pantalla.drawString(0, 30, “INVALID”);
}

pantalla.drawString(0, 40, "Time: ");
if (GPS.time.isValid())
{
pantalla.drawString(0, 50, String(GPS.time.hour()) + “:” + String(GPS.time.minute()) + “:” + String(GPS.time.second()));
}
else
{
pantalla.drawString(0, 50, “INVALID”);
}

pantalla.display();
}