Faulty connection / Connects once then drops

My board is a TTGO LoRa32 V2
I am building a simple weather station, and need to send some data to TTN, in order to do a integration later.
The problem is, the node makes the connection one or two times, then it doesn’t comeback.
The code is as follows:

/* LoRa */
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>

#ifdef COMPILE_REGRESSION_TEST
# define FILLMEIN 0
#else
# warning "You must replace the values marked FILLMEIN with real values from the TTN control panel!"
# define FILLMEIN (#dont edit this, edit the lines that use FILLMEIN)
#endif

uint8_t counter = 0;

static const PROGMEM u1_t NWKSKEY[16] = xxxxxxxxxxxxxxxxxxxx;
static const u1_t PROGMEM APPSKEY[16] = xxxxxxxxxxxxxxxxxxxxxxx;
static const u4_t DEVADDR = xxxxxxxxxxxxxxxxx;

void os_getArtEui (u1_t* buf) { }
void os_getDevEui (u1_t* buf) { }
void os_getDevKey (u1_t* buf) { }

uint8_t mydata[32];
char payload[32];

static osjob_t sendjob;
static osjob_t keepalivejob;


const unsigned TX_INTERVAL = 60;
#define KeepAlive_TX_INTERVAL 600

const lmic_pinmap lmic_pins = {
  .nss = 18,
  .rxtx = LMIC_UNUSED_PIN,
  .rst = LMIC_UNUSED_PIN,
  .dio = {/*dio0*/ 26, /*dio1*/ 33, /*dio2*/ LMIC_UNUSED_PIN}
};

void onEvent (ev_t ev) {
  Serial.print(os_getTime());
  Serial.print(": ");
  switch (ev) {
    case EV_SCAN_TIMEOUT:
      Serial.println(F("EV_SCAN_TIMEOUT"));
      break;
    case EV_BEACON_FOUND:
      Serial.println(F("EV_BEACON_FOUND"));
      break;
    case EV_BEACON_MISSED:
      Serial.println(F("EV_BEACON_MISSED"));
      break;
    case EV_BEACON_TRACKED:
      Serial.println(F("EV_BEACON_TRACKED"));
      break;
    case EV_JOINING:
      Serial.println(F("EV_JOINING"));
      break;
    case EV_JOINED:
      Serial.println(F("EV_JOINED"));
      break;
    case EV_JOIN_FAILED:
      Serial.println(F("EV_JOIN_FAILED"));
      break;
    case EV_REJOIN_FAILED:
      Serial.println(F("EV_REJOIN_FAILED"));
      break;
    case EV_TXCOMPLETE:
      Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
      if (LMIC.txrxFlags & TXRX_ACK)
        Serial.println(F("Received ack"));
      if (LMIC.dataLen) {
        Serial.println(F("Received "));
        Serial.println(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
      (counter == 4) ? counter = 1 : counter++;
      // Schedule next transmission
      os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send);
      break;
    case EV_LOST_TSYNC:
      Serial.println(F("EV_LOST_TSYNC"));
      break;
    case EV_RESET:
      Serial.println(F("EV_RESET"));
      break;
    case EV_RXCOMPLETE:
      // data received in ping slot
      Serial.println(F("EV_RXCOMPLETE"));
      break;
    case EV_LINK_DEAD:
      Serial.println(F("EV_LINK_DEAD"));
      break;
    case EV_LINK_ALIVE:
      Serial.println(F("EV_LINK_ALIVE"));
      break;
    case EV_TXSTART:
      Serial.println(F("EV_TXSTART"));
      break;
    case EV_TXCANCELED:
      Serial.println(F("EV_TXCANCELED"));
      break;
    case EV_RXSTART:
      /* do not print anything -- it wrecks timing */
      break;
    case EV_JOIN_TXCOMPLETE:
      Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept"));
      break;
    default:
      Serial.print(F("Unknown event: "));
      Serial.println((unsigned) ev);
      break;
  }
}

void do_keepalive(osjob_t* k)
{
  // Re-Schedule a timed job to run this task again at the given timestamp (absolute system time)
  os_setTimedCallback(k, os_getTime() + sec2osticks(KeepAlive_TX_INTERVAL), do_keepalive);
}

/**************************************************************************/

/* DHT */
#include "DHT.h"
#define DHTPIN 4
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);

int humidity;
float temperature;

void update_dht_readings()
{
  humidity = int(dht.readHumidity());
  temperature = dht.readTemperature();
}

void serial_print_dht()
{
  Serial.println(String(humidity) + " %H | " + String(temperature) + " *C");
}
/**************************************************************************/

/* Photoressitor */
#define LDR_PIN 15
#define MAX_ADC_READING 4095
#define ADC_REF_VOLTAGE 3.3
#define REF_RESISTANCE 4860
#define LUX_CALC_SCALAR 12518931
#define LUX_CALC_EXPONENT -1.405

int ldrRawData;
float resistorVoltage, ldrVoltage;
float ldrResistance;
int ldrLux;

void update_ldr_readings()
{
  ldrRawData = analogRead(LDR_PIN);
  resistorVoltage = (float)ldrRawData / MAX_ADC_READING * ADC_REF_VOLTAGE;
  ldrVoltage = ADC_REF_VOLTAGE - resistorVoltage;
  ldrResistance = ldrVoltage / resistorVoltage * REF_RESISTANCE;
  ldrLux = int(LUX_CALC_SCALAR * pow(ldrResistance, LUX_CALC_EXPONENT));
}

void serial_print_ldr()
{
  Serial.println(String(ldrLux) + " lux");
}
/**************************************************************************/

/* Rainmeter */
#define RAIN_PIN 13
#define SCALAR_MM 0.3

float rain_mm;

void update_rain_mm_readings()
{
  if (digitalRead(RAIN_PIN))
  {
    rain_mm += SCALAR_MM;
  }
  delay(120);
}

void reset_rain_mm_readings()
{
  rain_mm = 0;
}

void serial_print_rain_mm()
{
  Serial.println(String(rain_mm) + " mm");
}
/**************************************************************************/

#define ms_per_hour 3600000
#define ms_per_min 60000
#define ms_per_sec 1000

const long interval = ms_per_sec * 5;
unsigned long previousMillis;
unsigned long currentMillis;

void update_time_counter()
{
  currentMillis = millis();
}

void(* reset_arduino) (void) = 0;

void setup()
{
  while (!Serial); // wait for Serial to be initialized
  Serial.begin(115200);
  delay(100);     // per sample code on RF_95 test
  Serial.println(F("Starting"));

  pinMode(LDR_PIN, INPUT);
  pinMode(RAIN_PIN, INPUT);
  dht.begin();

#ifdef VCC_ENABLE
  // For Pinoccio Scout boards
  pinMode(VCC_ENABLE, OUTPUT);
  digitalWrite(VCC_ENABLE, HIGH);
  delay(1000);
#endif

  os_init();
  LMIC_reset();
  LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);

#ifdef PROGMEM
  // On AVR, these values are stored in flash and only copied to RAM
  // once. Copy them to a temporary buffer here, LMIC_setSession will
  // copy them into a buffer of its own again.
  uint8_t appskey[sizeof(APPSKEY)];
  uint8_t nwkskey[sizeof(NWKSKEY)];
  memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
  memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
  LMIC_setSession (0x13, DEVADDR, nwkskey, appskey);
#else
  // If not running an AVR with PROGMEM, just use the arrays directly
  LMIC_setSession (0x13, DEVADDR, NWKSKEY, APPSKEY);
#endif

  LMIC_setLinkCheckMode(0);

  LMIC.dn2Dr = DR_SF9;

  LMIC_setDrTxpow(DR_SF7, 14);

  do_send(&sendjob);

  do_keepalive(&keepalivejob);
}

void do_send(osjob_t* j) {
  (currentMillis >= ms_per_hour) ? reset_arduino() : update_time_counter();

  update_dht_readings();
  update_ldr_readings();

  if (currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    (isnan(temperature) || isnan(humidity)) ? void(Serial.println("Failed to read from DHT")) : serial_print_dht();
    (isnan(ldrLux)) ? void(Serial.println("Failed to read from LDR")) : serial_print_ldr();
    (isnan(rain_mm)) ? void(Serial.println("Failed to read from Rain Meter")) : serial_print_rain_mm();
  }

  switch (counter)
  {
    case 1:
      sprintf(payload, "temp|%f", temperature);
      break;
    case 2:
      sprintf(payload, "humid|%d", humidity);
      break;
    case 3:
      sprintf(payload, "lux|%d", ldrLux);
      break;
    case 4:
      sprintf(payload, "rain|%f", rain_mm);
      break;
    default:
      break;
  }

  for (int i = 0; i <= sizeof(payload); i++) {
    mydata[i] = payload[i];
  }

  Serial.println(F(mydata));

  // Check if there is not a current TX/RX job running
  if (LMIC.opmode & OP_TXRXPEND) {
    Serial.println(F("OP_TXRXPEND, not sending"));
  } else {
    // Prepare upstream data transmission at the next possible time.
    LMIC_setTxData2(1, mydata, sizeof(mydata) - 1, 0);
    Serial.println(F("Packet queued"));
  }
  // Next TX is scheduled after TX_COMPLETE event.
}

void loop()
{
  update_rain_mm_readings();
  os_runloop_once();
}

Serial:

ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 188777542, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:8896
load:0x40080400,len:5816
entry 0x400806ac
Starting

12269: EV_TXSTART
Packet queued
162473: EV_TXCOMPLETE (includes waiting for RX windows)
55 %H | 25.50 *C
443 lux
0.00 mm
temp|25.500000
3921597: EV_TXSTART
Packet queued
4071783: EV_TXCOMPLETE (includes waiting for RX windows)
55 %H | 25.50 *C
435 lux
0.00 mm
temp|25.500000
7830883: EV_TXSTART
Packet queued
7981095: EV_TXCOMPLETE (includes waiting for RX windows)
55 %H | 25.50 *C
443 lux
0.00 mm
temp|25.500000
11740191: EV_TXSTART
Packet queued
11890408: EV_TXCOMPLETE (includes waiting for RX windows)

Here is the payload:
Screenshot from 2020-05-19 16-38-01

  1. Temporarily modify the code to log the raw packet, frequency, and spreading factor each time it transmits. You’ll want to do this in a way where you can take it back out again though, as it may interfere with the timing to receive, but so far you aren’t seemingly doing anything which would depend on reception and need to get uplink working reliably first

  2. What is your gateway? What does the raw gateway view show?

  3. Please don’t send measurements as ASCII-ended textual strings, this is very wasteful. Pack them into a binary struct or even more densely.

Node additionally that your node seems to be restarting the frame counter from zero each time you start up the program. TTN won’t accept packets until they reach a frame count value never before seen, so your node would have to cycle through all the packet numbers it has previously used while being ignored, before TTN would start to decode it again.

You can disable frame counter checks as a debug option, but “real” nodes need to maintain the utilized frame count and increment forward from there, even if you restart them.

1 Like

Yes, it is something I am thinking on changing, could you give me some insights on how to do it more properly? I came with an ideia that the first byte is the identifier (like: 0xFA for temperature), and the remaining ones the information. But it’s only a guess. If you could help me I would be grateful. Thanks

Sure thing! I will implement as a debug measure, it will help a lot

I was unaware of this fact, I will read more on the matter, thanks for pointing out!

Which version of LMIC are you using?

You appear to be resetting the whole module once per hour but more importantly, as the keep_alive may well be running very hard against the whole mini-RTOS which I’ve found from experience is a bit of a challenge to keep stable.

As to your data format, your own suggestion is fine, give it a go.

1 Like

If you need multiple distinct packet types, you should probably use the “port” number which you are already “paying” to send in order to tell them apart.

But its best to put multiple measurements in a single packet.

Often you would do this with a struct, and then pass a pointer to the struct and its size to the send function.

Struct packing can get tricky between computer types but will probably be sane with your platform if you do one specific thing, and that is start with the larger data types and move to the smaller ones.

So for example

struct measurements {

  uint32_t something big;
  uint16_t something medium;
  uint8_t something_small;
  uint8_t another_small;

};

2 Likes


I am using the master branch, downloaded it today.

The reset have to do with the rain meter, which triggers every hour, so I can have a more flexible measurement on timestamps on my Grafana dashboard.
I didn’t know about the problems with of the keep_alive, could you give a word of your experience with it? I am kinda new to the whole RTOS ecosystem, thank you very much in advance!

Thanks! I will give it a try, never occurred to me that it was a good practice to have multiple measurements in a single packet, guess I was wrong all along, best regards!

1 Like

Indeed, the framing of a LoRaWAN packet costs at least 13 bytes (added and then removed in the lower layers) so you really want to pack all of your measurements together rather than send a header and checksum again for each one.

1 Like

How did this get added - it’s not in the library’s examples?

Basically it’s not initialised properly so may well be running all the time. And I’m not sure it’s even going to help, I’ve several home made devices running LMIC with various watchdogs but nothing that keeps hitting the OS like that, which I find can be a handful to get running smoothly already, but once working, just works.

1 Like

It wasn’t in the examples, I found it on a GitHub issue. Anyway, I removed it, and the program seems to run better now! Thanks!

3 posts were split to a new topic: How to count rain meter pulses without messing up LMIC’s timing?

Though it has seen some small commit early this year, I don’t think it’s being maintained? I think https://github.com/mcci-catena/arduino-lmic has fixed many issues.

It does have some issues opened a week ago, which were responded to. But progress seems to be stalled as you said.

I will give it a try, thanks! The code looks very similar, so it will not take a lot of time, and if it works better, I am gona move to it.