BG77 MQTT Connection Randomly Disconnects

Hello everyone,

I am creating a “gateway” which receives messages over LoRa and pushes them to Ubidots. The LoRa and MQTT side work reliably except for the disconnections, which give me a “+QMTSTAT: 0,1” periodically, indicating that the connection is closed by the peer. I am little confused by the terminology; for instance, does that mean the modem is closing the connection or that Ubidots is, or something else entirely? There appears to not be any problem with the cellular network, as checking Hologram during one of the disconnects shows it still live.

As of right now my solution is to just run the connection command again every 6 minutes or so, which always connects back reliably. I’d prefer not to do this forever if possible, and am wondering if anyone here has any insight into this issue. If I should be asking somewhere else please let me know!

I am using the Link.ONE WisTrio as the device, which contains an nRF52 talking serial to a BG77. The code I am using is pasted below, with some things omitted for privacy. Nothing is omitted which affects the AT commands used. Anywhere which says “PL()” is a macro for Serial.println.

#define DEBUG_DEVICE
#include "ArduinoJson-v6.21.3-custom.h"
#include <CircularBuffer.h>
#include <unordered_map>

// CONSTANTS
constexpr uint8_t BG77_POWER_KEY = WB_IO1;

// info for mqtt connection
constexpr char *SERVER = "\"169.55.61.243\""; // direct link not working as of 10/25
// constexpr char *SERVER = "\"industrial.api.ubidots.com\"";
constexpr char *PORT = "1883";
constexpr char *USERNAME = "\"API KEY OMITTED\"";
constexpr char *TOPIC = "/v1.6/devices/";

constexpr int TIME_TO_TX =
    3600000; // time for gateway to send info packet to ubidots
constexpr int TIME_TO_RECONN =
    420000; // time for gateway to reconnect to ubidots
// constexpr int TIME_TO_TX =
//     30000; // time for gateway to send info packet to ubidots

// lora stuff
constexpr uint32_t US_FREQ = 915000000;
constexpr uint16_t sf = 12, bw = 1, cr = 0, preamble = 8, txPower = 22;

// GLOBALS
GatewayInfo infoPacket;                         // info aboout gateway
CxInfoLoRa cxinfo;                              // packet for new lora info
Packet newData;                                 // packet for received info
CircularBuffer<Packet, UINT8_MAX> mainDataBuff; // static queue for rx packets
unordered_map<uint16_t, time_t>
    uniqueDevList; // table for amount of devices that have talked to gateway
time_t curr_tx_timer = 0;     // timer for sending gateway stuff
time_t curr_reconn_timer = 0; // timer for reconnecting
bool firstBoot = true;        // to use to do stuff on first boot

// FUNCTIONS
// read response from modem
String BG77_read(time_t timeout_ms) {
  String resp = "";
  time_t timeStamp = millis();
  while ((millis() - timeStamp) < timeout_ms) {
    if (Serial1.available() > 0) {
      resp += (char)Serial1.read();
      delay(1);
    }
  }
  PL(resp);
  return resp;
}

// write commnads to modem.
void BG77_write(const char *command) {
  while (*command) {
    Serial1.write(*command);
    command++;
  }
  Serial1.println();
}

// send packet of info to ubidots
void cell_cmds(const char *jsonString, const char *id) {
  // AT CMDS FOR BG77
  String command = "AT+QMTPUBEX=0,0,0,0,\"";
  command += TOPIC;
  command += id;
  command += "\",\"";
  command += jsonString;
  command += "\"\r";

  BG77_write(command.c_str());
  BG77_read(3000);
}

// generic function to send any form of struct to JSON
template <typename T> void send_to_cell(T infoPacket) {}
template <> void send_to_cell<GatewayInfo>(const GatewayInfo infoPacket) {
  StaticJsonDocument<256> infoJSON;
  infoJSON["eui"] = infoPacket.node_id;
  infoJSON["r"] = 1; // generic heartbeat send
  infoJSON["b"] = double(infoPacket.batteryVoltage / 1000.0);
  infoJSON["s1"] = infoPacket.numDevicesConnected;
  infoJSON["s2"] = infoPacket.waterDetected;
  infoJSON["s3"] = infoPacket.mainsPowerConnected;
  infoJSON["s4"] = UINT16_MAX;
  infoJSON["s5"] = UINT16_MAX;
  infoJSON["dt"] = infoPacket.device_type;

  char jsonString[256];
  serializeJson(infoJSON, jsonString);
  PL(jsonString);

  cell_cmds(jsonString, infoPacket.node_id);
}
template <> void send_to_cell<Packet>(const Packet dataPacket) {
  StaticJsonDocument<300> packetJSON;
  packetJSON["r"] = dataPacket.reason;
  packetJSON["dt"] = dataPacket.device_type;
  packetJSON["eui"] = dataPacket.node_id;
  packetJSON["s1"] = dataPacket.sns_reading_1;
  packetJSON["s2"] = dataPacket.sns_reading_2;
  packetJSON["s3"] = dataPacket.sns_reading_3;
  packetJSON["s4"] = dataPacket.sns_reading_4;
  packetJSON["s5"] = dataPacket.sns_reading_5;
  packetJSON["b"] = double(dataPacket.batt_reading / 1000.0);
  packetJSON["snr"] = dataPacket.snr;
  packetJSON["rssi"] = dataPacket.rssi;

  char jsonString[300];
  serializeJson(packetJSON, jsonString);
  PL(jsonString);

  cell_cmds(jsonString, dataPacket.node_id);
}

// lora rx callback
void recv_cb(rui_lora_p2p_recv_t data) {
  memcpy(&newData, data.Buffer, data.BufferSize);
  newData.rssi = data.Rssi;
  newData.snr = data.Snr;
  mainDataBuff.unshift(newData);
}

// check if cellular is connected
bool checkConnection() {
  // wait until cell is registered
  String command = "AT+CREG?\r";
  while (true) {
    BG77_write(command.c_str());
    String bg_rsp = BG77_read(300);
    {
      int v = bg_rsp.indexOf('0,');
      if (v > 0) {
        if (bg_rsp[v + 1] == '5') {
          PL("Registered: Roaming");
          return true;
        } else if (bg_rsp[v + 1] == '1') {
          PL("Registered: Home");
          return true;
        }
      }
    }
  }
}

// setup modem
void configureCell(const char *id) {
  // turn verbose errors on
  String command = "AT+CMEE=2\r";
  BG77_write(command.c_str());
  BG77_read(300);

  // disable gps
  command = "AT+QGPSEND\r";
  BG77_write(command.c_str());
  BG77_read(300);

  // set full functionality of radio
  command = "AT+CFUN=1,0\r";
  BG77_write(command.c_str());
  BG77_read(1000);

  // check if sim is ready or needs password
  // should return +CPIN: READY
  command = "AT+CPIN?\r";
  BG77_write(command.c_str());
  BG77_read(1000);

  command = "AT+COPS=0\r";
  BG77_write(command.c_str());
  BG77_read(1000);

  // set emtc lte mode
  command = "AT+QCFG=\"iotopmode\",0,1\r";
  BG77_write(command.c_str());
  BG77_read(1000);

  // check signal strength
  command = "AT+CSQ\r";
  BG77_write(command.c_str());
  BG77_read(1000);

  // check sim operator?
  command = "AT+COPS?\r";
  BG77_write(command.c_str());
  BG77_read(1000);

  command = "AT+CGATT=1\r";
  BG77_write(command.c_str());
  BG77_read(1000);

  // check connection to service
  checkConnection();

  // check current network info
  command = "AT+QNWINFO\r";
  BG77_write(command.c_str());
  BG77_read(3000);

  // open network for mqtt client
  command = "AT+QMTOPEN=0,";
  command += SERVER;
  command += ",";
  command += PORT;
  command += "\r";
  BG77_write(command.c_str());
  BG77_read(30000);

  // checking to make sure were good
  command = "AT+QMTOPEN?\r";
  BG77_write(command.c_str());
  BG77_read(30000);

  // connect to client
  command = "AT+QMTCONN=0,\"";
  command += "CMPS";
  command += __RAK_NODE_ID_LOWER;
  command += "\",";
  command += USERNAME;
  command += ",\"\"\r"; // password = ""
  BG77_write(command.c_str());
  BG77_read(30000);

  // check if we connected
  command = "AT+QMTCONN?\r";
  BG77_write(command.c_str());
  BG77_read(10000);
}

// if modem off, wake it up
void wakeupCell() {
  time_t timeout = millis();
  bool moduleSleeps = true;

  Serial1.println("ATI");

  while ((millis() - timeout) < 6000) {
    if (Serial1.available()) {
      String result = Serial1.readString();
      PL("Modem response after start:");
      PL(result);
      moduleSleeps = false;
    }
  }
  if (moduleSleeps) {
    // Module slept, wake it up
    pinMode(BG77_POWER_KEY, OUTPUT);
    digitalWrite(BG77_POWER_KEY, 0);
    delay(1000);
    digitalWrite(BG77_POWER_KEY, 1);
    delay(2000);
    digitalWrite(BG77_POWER_KEY, 0);
    delay(1000);
  }
  PL("BG77 power up!");
}

void setup() {
  CN(115200);
  Serial1.begin(115200);
  delay(1000);

  // setup device info
  infoPacket.device_type = Gateway; // 0 = gateway
  ID_Builder(infoPacket.node_id);
  // TOPIC += infoPacket.node_id;

  wakeupCell();
  configureCell(infoPacket.node_id);

  // setup default settings
  if (api.lorawan.nwm.get() != RAK_LORA_P2P) {
    PL("Setting device into P2P mode and restarting..");
    api.lorawan.nwm.set(RAK_LORA_P2P);
    api.system.reboot();
  }

  PL("Configuring");
  api.lorawan.pfreq.set(US_FREQ);
  api.lorawan.psf.set(sf);
  api.lorawan.pbw.set(bw);
  api.lorawan.pcr.set(cr);
  api.lorawan.ppl.set(preamble);
  api.lorawan.ptp.set(txPower);

  cxinfo.RESERVED = 0x0;
  cxinfo.sf = sf;
  cxinfo.bw = bw;
  cxinfo.cr = cr;
  cxinfo.txPower = txPower;

  // register callbacks
  api.lorawan.registerPRecvCallback(recv_cb);
  api.lorawan.precv(65533);

  // TODO: set pins for water interrupts
  // TODO: setup adc
}

void loop() {
  if (false) // TODO: commissioning mode again
  {          // commissioning mode is active
    api.lorawan.psend(sizeof(cxinfo), (uint8_t *)&cxinfo);
    delay(10000); // wait 10 seconds inbetween sends
  } else {
    while (!mainDataBuff.isEmpty()) {
      // get node from queue and remove
      Packet currData = mainDataBuff.pop();
      const char *lastFourChars =
          &currData.node_id[strnlen(currData.node_id, 17) - 4];
      uniqueDevList[atoi(lastFourChars)] = millis();

      // send info over mqtt to ubidots
      send_to_cell(currData);
    }

    if (millis() - curr_tx_timer > TIME_TO_TX || firstBoot) {
      // check if any devices have stopped communicating
      for (const auto &iterator : uniqueDevList) {
        // if not heard from in x minutes, remove from list
        if (millis() - iterator.second > 18000) // > 3600000) // one hour
        {
          uniqueDevList.erase(iterator.first);
        }
      }

      // get info to send
      firstBoot = false;
      infoPacket.numDevicesConnected = uniqueDevList.size();
      // info.batteryVoltage = []
      // {
      // 	uint16_t voltage_adc = analogRead(BATT_READ);
      // 	return (uint16_t)((3.3 / 4096) * voltage_adc) * 1000;
      // }();
      // info.mainsPowerConnected = !digitalRead(PGOOD_LOW);

      infoPacket.batteryVoltage = uint16_t(3.3F * 1000); //! set as 3.3V for now
      infoPacket.mainsPowerConnected = 1; //! leave as true for now

      // send info over mqtt to ubidots
      send_to_cell(infoPacket);
      infoPacket.waterDetected = 0;

      // reset timer
      curr_tx_timer = millis();
    }

    if (millis() - curr_reconn_timer > TIME_TO_RECONN) {
      String command = "AT+QMTOPEN=0,";
      command += SERVER;
      command += ",";
      command += PORT;
      command += "\r";
      BG77_write(command.c_str());
      BG77_read(10000);

      command = "AT+QMTCONN=0,\"";
      command += "CMPS";
      command += __RAK_NODE_ID_LOWER;
      command += "\",";
      command += USERNAME;
      command += ",\"\"\r"; // password = ""
      BG77_write(command.c_str());
      BG77_read(10000);

      curr_reconn_timer = millis();
    }
  }
}

Thank you all for your time!

1 Like

pls query and set the keepalvie parameter of in your mqtt session

if you use multiple devices make sure the “client id” on command “AT+QMTCONN” is unique for all devices. because while one is sending data another may be disconnected and vice versa. in my case, it caused the error of disconnection.

Update:

I have tried setting the keepalive parameter to 1800s, and since then I have noticed that most of my +QMTSTAT: 0,1 disconnects come immediately after running AT+QMTOPEN=0,"industrial.api.ubidots.com",1883\r, about 10 seconds or so. Here is an example of the output:

Response: AT+QMTOPEN=0,"industrial.api.ubidots.com",1883
OK

+QMTOPEN: 0,0

Response: AT+QMTOPEN?
+QMTOPEN: 0,"industrial.api.ubidots.com",1883

OK

+QMTSTAT: 0,1 

Thank you guys again for your time.