How to count rain meter pulses without messing up LMIC's timing?

I have a Rain Meter, based on a tilting bucket mechanism. So, on my view at least, it have to be very time critical, so I don’t lose a reading. But a the moment it’s not working properly, the LoRa send job is taking a good part of the runtime.
So, is there a tip that I could use for doing the measurement asynchronous?
Bellow is my code: (if there’s something wrong, please point out)

/* 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

static const u1_t PROGMEM APPEUI[8] = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx;
void os_getArtEui (u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

static const u1_t PROGMEM DEVEUI[8] = xxxxxxxxxxxxxxxx;
void os_getDevEui (u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

static const u1_t PROGMEM APPKEY[16] = xxxxxxxxxxxxxxxxxxxxxxxxx};
void os_getDevKey (u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

uint8_t port = 2;
byte payload[6];

static osjob_t sendjob;
static osjob_t readjob;

int16_t sent_temperature;
uint16_t sent_rain_mm;

const unsigned TX_INTERVAL = 60;

// Pin mapping
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 printHex2(unsigned v) {
  v &= 0xff;
  if (v < 16)
    Serial.print('0');
  Serial.print(v, HEX);
}

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"));
      {
        u4_t netid = 0;
        devaddr_t devaddr = 0;
        u1_t nwkKey[16];
        u1_t artKey[16];
        LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
        Serial.print("netid: ");
        Serial.println(netid, DEC);
        Serial.print("devaddr: ");
        Serial.println(devaddr, HEX);
        Serial.print("AppSKey: ");
        for (size_t i = 0; i < sizeof(artKey); ++i) {
          if (i != 0)
            Serial.print("-");
          printHex2(artKey[i]);
        }
        Serial.println("");
        Serial.print("NwkSKey: ");
        for (size_t i = 0; i < sizeof(nwkKey); ++i) {
          if (i != 0)
            Serial.print("-");
          printHex2(nwkKey[i]);
        }
        Serial.println();
      }
    
      LMIC_setLinkCheckMode(0);
      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.print(F("Received "));
        Serial.print(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
      // 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;
  }
}
/**************************************************************************/

/* 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;
  }
}

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() {
  Serial.begin(9600);
  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);

  do_read(&readjob);
  // Start job (sending automatically starts OTAA too)
  do_send(&sendjob);
}

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

  update_rain_mm_readings();
  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();
  }

  sent_temperature = int(temperature);
  sent_rain_mm = int((rain_mm * 100));
}

void do_send(osjob_t* j) {
  payload[0] = sent_temperature >> 8;
  payload[1] = sent_temperature;
  payload[2] = humidity >> 8;
  payload[3] = humidity;
  payload[4] = ldrLux >> 8;
  payload[5] = ldrLux;
  payload[6] = sent_rain_mm >> 8;
  payload[7] = sent_rain_mm;
  
  // 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(port, payload, sizeof(payload), 0);
    Serial.println(F("Packet queued"));
  }
  do_read(&readjob);
  // Next TX is scheduled after TX_COMPLETE event.
}

void loop() {
  os_runloop();
}

Thanks for all the help in advance!!
Best regards

1 Like

Use an interrupt that only increments a counter - it should be fast enough that even if it’s raining hard, it won’t impact the LMIC timings enough to prevent transmission.

3 Likes

Worked like a charm! Thank you very much!

1 Like

Care to explain what you implemented for future readers? (And for review, of course.)

2 Likes

Sure!
Here is the current version, I still have a lot of improvements to do, but if it helps in any way I’ll be happy!
Best regards.

/* 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

static const u1_t PROGMEM APPEUI[8] = xxxxxxxx;
void os_getArtEui (u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

static const u1_t PROGMEM DEVEUI[8] = xxxxxxx;
void os_getDevEui (u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

static const u1_t PROGMEM APPKEY[16] = xxxxxxxx;
void os_getDevKey (u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

uint8_t port = 2;
byte payload[6];

static osjob_t sendjob;
static osjob_t readjob;

int16_t sent_temperature;
uint16_t sent_rain_mm;

const unsigned TX_INTERVAL = 60;

// Pin mapping
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 printHex2(unsigned v) {
  v &= 0xff;
  if (v < 16)
    Serial.print('0');
  Serial.print(v, HEX);
}

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"));
      {
        u4_t netid = 0;
        devaddr_t devaddr = 0;
        u1_t nwkKey[16];
        u1_t artKey[16];
        LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
        Serial.print("netid: ");
        Serial.println(netid, DEC);
        Serial.print("devaddr: ");
        Serial.println(devaddr, HEX);
        Serial.print("AppSKey: ");
        for (size_t i = 0; i < sizeof(artKey); ++i) {
          if (i != 0)
            Serial.print("-");
          printHex2(artKey[i]);
        }
        Serial.println("");
        Serial.print("NwkSKey: ");
        for (size_t i = 0; i < sizeof(nwkKey); ++i) {
          if (i != 0)
            Serial.print("-");
          printHex2(nwkKey[i]);
        }
        Serial.println();
      }

      LMIC_setLinkCheckMode(0);
      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.print(F("Received "));
        Serial.print(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
      // 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;
  }
}
/**************************************************************************/

/* 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()
{
  rain_mm += SCALAR_MM;
}

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() {
  Serial.begin(9600);
  Serial.println(F("Starting"));

  pinMode(LDR_PIN, INPUT);
  pinMode(RAIN_PIN, INPUT);
  dht.begin();
  
  attachInterrupt(digitalPinToInterrupt(RAIN_PIN), update_rain_mm_readings, RISING);

#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);

  do_read(&readjob);
  // Start job (sending automatically starts OTAA too)
  do_send(&sendjob);
}

void do_read(osjob_t* j)
{
  (currentMillis >= ms_per_hour) ? reset_rain_mm_readings(): 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();
  }

  sent_temperature = int(temperature);
  sent_rain_mm = int((rain_mm * 100));
}

void do_send(osjob_t* j) {
  payload[0] = sent_temperature >> 8;
  payload[1] = sent_temperature;
  payload[2] = humidity >> 8;
  payload[3] = humidity;
  payload[4] = ldrLux >> 8;
  payload[5] = ldrLux;
  payload[6] = sent_rain_mm >> 8;
  payload[7] = sent_rain_mm;

  // 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(port, payload, sizeof(payload), 0);
    Serial.println(F("Packet queued"));
  }
  do_read(&readjob);
  // Next TX is scheduled after TX_COMPLETE event.
}

void loop() {
  os_runloop();
}
1 Like

Nice. In short, you’re using:

Not my cup of tea, but the Arduino documentation for attachInterrupt() claims:

You should declare as volatile any variables that you modify within the attached function.

Typically global variables are used to pass data between an ISR and the main program. To make sure variables shared between an ISR and the main program are updated correctly, declare them as volatile.

For more information on interrupts, see Nick Gammon’s notes.

The latter explains:

What are “volatile” variables?

Variables shared between ISR functions and normal functions should be declared “volatile”. This tells the compiler that such variables might change at any time, and thus the compiler must reload the variable whenever you reference it, rather than relying upon a copy it might have in a processor register.

(That full article is a nice read, especially the details about how much time an ISR will take.)

1 Like

Thanks! I didn’t read it all the time I found it, my mistake. It worked a lot better, but still, if there is room for improvement, I’ll do it.
Thank you, I’m going to update the code and post it again.

You might also want to make sure that no debouncing of the input is needed?

1 Like

I came up with this solution:

const uint8_t intervalRain = 130;
void update_rain_mm_readings()
{
  if ((currentMillis - rainMillis) >= intervalRain) {
    rain_mm += SCALAR_MM;
  }
  rainMillis = millis();
  update_time_counter();
}

Maybe I should use a capacitor instead? So it’s one less process that the device have to do.

RainGauge

From the manufacturer’s documentation.

Don’t forget the volatile modifier and I’d try not to call a function in the interrupt if you can avoid it. But you may not need the debounce code with this circuit.

2 Likes

A capacitor should not be needed, software debouncing is good enough. Your solution is not bad, but it has a drawback: When there are multiple interrupts fired by a bouncing signal, then processing time is used for each of them. Better to switch off the interrupt after the first signal and switch it on after a holdoff time.

#define DEBOUNCE_HOLDOFF ms2osticks(100)

volatile osjob_t debouncejob;

void debouncefunc (osjob_t* j);

void update_rain_mm_readings()
{
    rain_mm += SCALAR_MM;
    detachInterrupt(digitalPinToInterrupt(RAIN_PIN);
    os_setTimedCallback(&debouncejob, os_getTime() + DEBOUNCE_HOLDOFF, debouncefunc);
}

void debouncefunc (osjob_t* j)
{
    attachInterrupt(digitalPinToInterrupt(RAIN_PIN), 
          update_rain_mm_readings, RISING);
}
1 Like

Also, if you find using an interrupt is taxing your firmware, you can look into using a TIMER to count the pulses. I count pulses via a flow sensor using a hardware timer which allows me to offload the counting from the CPU. I end up with 1 timer to count the pulses and another timer that reads the value (I’m using an nRF52840). I don’t think you’ll have that problem with rain, but it’s something to consider.

1 Like

Sounds really good!
I will make some tests with the capacitor and the code solutions.

And the device is running a ESP32, so I think it can handles quite a lot of processes at the same time without loosing performance, but truly a bouncing signal calling a function multiple times, just to be ignored then is not optimal.
This tip was golden.
Thank you very much!

Yes it is something I will consider, specially when using less powerful micro-controllers, as in the future I’ll have to for some more time critical measurements. Thank you!

Okay, so I did some reworking!
The library: https://github.com/mcci-catena/arduino-lmic
The code:

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

const int interval = ms_per_sec * 5;
volatile unsigned long previousMillis;
volatile unsigned long currentMillis;
volatile unsigned long rainMillis;

/* 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

static const u1_t PROGMEM APPEUI[8] =xxxxxxx;
void os_getArtEui (u1_t* buf) {
  memcpy_P(buf, APPEUI, 8);
}

static const u1_t PROGMEM DEVEUI[8] = xxxxxxx;
void os_getDevEui (u1_t* buf) {
  memcpy_P(buf, DEVEUI, 8);
}

static const u1_t PROGMEM APPKEY[16] = xxxxxxxxxxxxx;
void os_getDevKey (u1_t* buf) {
  memcpy_P(buf, APPKEY, 16);
}

uint8_t port = 2;
byte payload[8];

static osjob_t sendjob;
static osjob_t readjob;

int16_t sent_temperature;
int sent_rain_mm;

const unsigned TX_INTERVAL = 60;

// Pin mapping
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 printHex2(unsigned v) {
  v &= 0xff;
  if (v < 16)
    Serial.print('0');
  Serial.print(v, HEX);
}

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"));
      {
        u4_t netid = 0;
        devaddr_t devaddr = 0;
        u1_t nwkKey[16];
        u1_t artKey[16];
        LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
        Serial.print("netid: ");
        Serial.println(netid, DEC);
        Serial.print("devaddr: ");
        Serial.println(devaddr, HEX);
        Serial.print("AppSKey: ");
        for (size_t i = 0; i < sizeof(artKey); ++i) {
          if (i != 0)
            Serial.print("-");
          printHex2(artKey[i]);
        }
        Serial.println("");
        Serial.print("NwkSKey: ");
        for (size_t i = 0; i < sizeof(nwkKey); ++i) {
          if (i != 0)
            Serial.print("-");
          printHex2(nwkKey[i]);
        }
        Serial.println();
      }
      // Disable link check validation (automatically enabled
      // during join, but because slow data rates change max TX
      // size, we don't use it in this example.
      LMIC_setLinkCheckMode(0);
      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.print(F("Received "));
        Serial.print(LMIC.dataLen);
        Serial.println(F(" bytes of payload"));
      }
      // 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;
  }
}
/**************************************************************************/

/* 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

static osjob_t debouncejob;
void debouncefunc(osjob_t* j);

#define DEBOUNCE_HOLDOFF ms2osticks(100)

volatile float rain_mm;

void update_rain_mm_readings()
{
  rain_mm += SCALAR_MM;
  detachInterrupt(digitalPinToInterrupt(RAIN_PIN));
  os_setTimedCallback(&debouncejob, os_getTime() + DEBOUNCE_HOLDOFF, debouncefunc);
}

void debouncefunc(osjob_t* j)
{
  attachInterrupt(digitalPinToInterrupt(RAIN_PIN),
                  update_rain_mm_readings, RISING);
}

void reset_rain_mm_readings()
{
  rain_mm = 0;
}

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

void(* reset_arduino) (void) = 0;

void setup() {
  Serial.begin(9600);
  Serial.println(F("Starting"));

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

  attachInterrupt(digitalPinToInterrupt(RAIN_PIN), update_rain_mm_readings, HIGH);

#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);

  do_read(&readjob);
  // Start job (sending automatically starts OTAA too)
  do_send(&sendjob);
}

void do_read(osjob_t* j)
{
  (currentMillis >= ms_per_hour) ? reset_rain_mm_readings() : 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();
  }

  sent_temperature = int(temperature);
  sent_rain_mm = int((rain_mm * 100));
}

void do_send(osjob_t* j) {
  payload[0] = sent_temperature >> 8;
  payload[1] = sent_temperature;
  payload[2] = humidity >> 8;
  payload[3] = humidity;
  payload[4] = ldrLux >> 8;
  payload[5] = ldrLux;
  payload[6] = sent_rain_mm >> 8;
  payload[7] = sent_rain_mm;

  // 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(port, payload, sizeof(payload), 0);
    Serial.println(F("Packet queued"));
  }
  do_read(&readjob);
  // Next TX is scheduled after TX_COMPLETE event.
}

void loop() {
  os_runloop();
}

It works pretty well! :smiley:
If someone have more tips to share I would be very grateful!
And if there’s something wrong with the code, feel free to point out.
Should I post the circuit too?
Thanks for everyone involved