LMIC os_runloop() conflicts with sensor reading function

Yes, sorry.

hive.ino

#include "hive.h"
 
 
void setup() {
  while (!Serial); // wait for Serial to be initialized
  Serial.begin(115200);
  delay(100);
 
  initSensors();
 
  initAndSetupOS();
 
  // Start job
  do_read_critical(&readCriticalJob);
  do_update(&updatejob);
  do_send(&sendjob);
 
  // The packet indicating that the node has been turned on/reset has already been sent (do_sent(...)), now we set it back to 0
  isReset = 0;
}
 
void loop() {
    os_runloop_once();
}

hive.h

#ifndef HIVE_H
#define HIVE_H
 
/******************************************************************************
 *
 * Note: LoRaWAN per sub-band duty-cycle limitation is enforced (1% in
 * g1, 0.1% in g2), but not the TTN fair usage policy (which is probably
 * violated by this sketch when left running for longer)!
 *
 * Do not forget to define the radio type correctly in
 * arduino-lmic/project_config/lmic_project_config.h or from your BOARDS.txt.
 *
 *******************************************************************************/
 
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
 
// Temp sensor MPU6050 ,accel gyro temp
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
 
// Temp humid sensor, SHT31
#include <Adafruit_SHT31.h>
 
 
 
 
// LoRaWAN NwkSKey, network session key
// This should be in big-endian (aka msb).
constexpr PROGMEM u1_t NWKSKEY[16] = {X};
 
// LoRaWAN AppSKey, application session key
// This should also be in big-endian (aka msb).
constexpr u1_t PROGMEM APPSKEY[16] = {X};
 
// LoRaWAN end-device address (DevAddr)
// See http://thethingsnetwork.org/wiki/AddressSpace
// The library converts the address to network byte order as needed, so this should be in big-endian (aka msb) too.
constexpr u4_t DEVADDR = X ; // <-- Change this address for every node!
 
 
// Pin mapping
extern const lmic_pinmap lmic_pins; 
 
// Schedule TX every this many seconds (might become longer due to duty
// cycle limitations).
constexpr unsigned TX_INTERVAL = 60; // seconds
constexpr unsigned CRIT_CHECK_INTERVAL = 500; // milliseconds
constexpr unsigned CRIT_CHECK_DURATION = 20; // milliseconds -- just a largely generous guess
constexpr unsigned UPDATE_INTERVAL = TX_INTERVAL;
constexpr unsigned UPDATE_DURATION = 150; // milliseconds -- I2C readings take time but it's a generous guess too
 
//Adafruit_MPU6050 mpu;
extern Adafruit_SHT31 sht31;
 
//Limit switch (fin de course)
constexpr int limitSwitchPin = 5;
extern uint16_t openCount;
extern uint16_t closeCount;
extern uint32_t lastOpen;
 
//Added to reduce the processing load (updating mydata with no eventual changes)
extern bool updateOpen; // true : means openCount and lastOpen have changed and we should update mydata[] with the new data
extern bool updateClose; // true : means closeCount have changed and we should update mydata[] with the new data
 
extern uint8_t mydata[]; // For some reason we need an extra unused byte (for 13 slots "0-12" used we need 14)
extern osjob_t sendjob;
extern osjob_t readCriticalJob;
extern osjob_t updatejob;
 
// Power/Reset flag
extern uint8_t isReset; // 1 : the device has just been turned ON or reset
 
 
void do_send(osjob_t*);
void do_update(osjob_t*);
void do_read_critical(osjob_t*);
void onEvent (ev_t);
void initSensors();
void initAndSetupOS();
void checkTimeCriticalSensors();
 
 
 
 
 
 
 
#endif

hive.cpp

#include "hive.h"
 
const lmic_pinmap lmic_pins = {
    .nss = 10,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 9,
    .dio = {2, 6, 7},
};
 
Adafruit_SHT31 sht31 = Adafruit_SHT31();
 
//Limit switch (fin de course) 
//TO UPDATE : Reduce openCount, closeCount to uint8_t
uint16_t openCount = 0;
uint16_t closeCount = 0;
uint32_t lastOpen = 0;
 
bool updateOpen = true;
bool updateClose = true; 
 
uint8_t mydata[15];
osjob_t sendjob;
osjob_t readCriticalJob;
osjob_t updatejob;
 
uint8_t isReset = 1;
 
 
 
 
 
 
void onEvent (ev_t ev) {
    Serial.print("ticks : " + String(os_getTime()) + ", ms : " + String(osticks2ms(os_getTime())));
    Serial.print(": ");
    switch(ev) {
        case EV_TXCOMPLETE:
            Serial.println(F("EV_TXCOMPLETE (includes waiting for RX windows)"));
            // Schedule next transmission
            os_setTimedCallback(&updatejob, os_getTime()+ (sec2osticks(UPDATE_INTERVAL) - ms2osticks(UPDATE_DURATION)), do_update);
            os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send);
            //if (!os_queryTimeCriticalJobs(ms2osticks(CRIT_CHECK_INTERVAL))) { // Had to make sure there is nothing
              os_setTimedCallback(&readCriticalJob, os_getTime()+ms2osticks(CRIT_CHECK_INTERVAL - CRIT_CHECK_DURATION), do_read_critical); 
//              Serial.println(F("nthn dont worry"));}
            break;
        case EV_TXSTART:
            Serial.println(F("EV_TXSTART"));
            break;
        default:
            Serial.print(F("Unknown event: "));
            Serial.println((unsigned) ev);
            break;
    }
}
 
 
 
//MPU6050
/*void updateMPU() {
    sensors_event_t a, g, temp;
    mpu.getEvent(&a, &g, &temp);
    int16_t tempInt = temp.temperature * 100; // Convert temperature to an integer to avoid floating point in payload
    mydata[0] = tempInt >> 8;
    mydata[1] = tempInt & 0xFF;
}*/
 
 
 
//SHT31, read and pack temperature and humidity data (mydata : 0-3)
void readAndPackTempHum(){
  float temperature = sht31.readTemperature();
  float humidity = sht31.readHumidity();
 
  // Check if readings failed and if so, use a sentinel value
  if (isnan(temperature) || isnan(humidity)) {
      Serial.println("Failed to read from SHT31 sensor!");
      return;
  }
  
  // Convvert temperature and humidity to integers for transmission
  int16_t tempInt = temperature  * 100; // e.g., 23.45 degrees -> 2345
  int16_t humInt = humidity * 100;     // e.g., 45.67% -> 4567
 
  // Pack the temperature into first two bytes of mydata
  mydata[0] = tempInt >> 8;
  mydata[1] = tempInt & 0xFF;
 
  // Pack the humidity into next two bytes of mydata
  mydata[2] = humInt >> 8;
  mydata[3] = humInt & 0xFF;
 
  Serial.println("temp : " + String(temperature) +", humid : " + String (humidity));
}
 
 
 
 
 
//LimitSwitch, packs switch related data (mydata : 4-11) 
//TO UPDATE : packaging of openCount/closeCount to hold one slot only
void packSwitch() {
  if (updateOpen) {
    mydata[4] = openCount >> 8;
    mydata[5] = openCount & 0xFF;
 
    mydata[6] = lastOpen >> 24;
    mydata[7] = (lastOpen >> 16) & 0xFF;
    mydata[8] = (lastOpen >> 8) & 0xFF;
    mydata[9] = lastOpen & 0xFF;
 
    updateOpen = false;
  }
  else if (updateClose) {
    mydata[10] = closeCount >> 8;
    mydata[11] = closeCount & 0xFF;
 
    updateClose = false;
  }
  Serial.println("openCount : " + String(openCount) + ", lastOpen : " + String(lastOpen) + ", closeCount : " + String(closeCount));
}
 
 
 
// Updates switch related data
void checkSwitch(s4_t currTime) {
  int switchState = digitalRead(limitSwitchPin);
  if (openCount == closeCount && switchState == HIGH) { // openCount == closeCount : means last time we checked it was closed
    openCount ++;
    lastOpen = currTime / 1000;
    updateOpen = true;
  }
 
  else if (openCount > closeCount && switchState == LOW) { // openCount > closeCount : means last time we checked it was open
    closeCount ++;
    updateClose = true;
  }
}
 
 
//reads all data off of the time critical sensors, eventually limitswitch and accelerometer
void checkTimeCriticalSensors() {
  s4_t currTime = osticks2ms(os_getTime()); // We dont use millis() cuz it's interrupt dependant and LMIC by default has interrupts disabled
 
  checkSwitch(currTime);
}
 
 
 
 
 
void updateData() { 
  readAndPackTempHum(); // 0-3
  packSwitch(); //4-11
 
  //Pack the reset flag
  mydata[12] = isReset; 
  Serial.println("reset : " + String(isReset));
}
 
/* Decoder for TTN :
//TO UPDATE : packaging of openCount/closeCount to hold one slot only
function Decoder(bytes, port) {
    var decoded = {};
  
    // Decode temperature
    var tempRaw = (bytes[0] << 8) | bytes[1];
    decoded.temperature = tempRaw / 100.0;
  
    // Decode humidity
    var humRaw = (bytes[2] << 8) | bytes[3];
    decoded.humidity = humRaw / 100.0;
 
    // Decode openCount
    decoded.openCnt = (bytes[4] << 8) | bytes[5];
 
    //Decode lastOpen
    decoded.lastOpen = (bytes[6] << 24) | (bytes[7] << 16) | (bytes[8] << 8) | bytes[9];
  
    //Decode closeCount
    decoded.closeCnt = (bytes[10] << 8) | bytes[11];
 
    //Decode resetFLag; 
    decoded.reset = bytes[12];
 
 
  
    return decoded;
}
*/
 
 
 
 
void do_send(osjob_t* j){
    // 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 do_update(osjob_t* j){
  Serial.println(F("Start update!"));
  updateData();
 
  //If in (UPDATE_INTERVAL) milliseconds no jobs are scheduled, schedule the next check in (UPDATE_INTERVAL - UPDATE_DURATION) milliseconds. 
  //It'll (take UPDATE_DURATION) milliseconds for the function to complete running.
  if (!os_queryTimeCriticalJobs(ms2osticks(UPDATE_INTERVAL))) 
    os_setTimedCallback(&updatejob, os_getTime()+ms2osticks(UPDATE_INTERVAL - UPDATE_DURATION), do_update);
 
  Serial.println(F("Updated!"));
}
 
 
 
void do_read_critical(osjob_t* j){ 
  Serial.println(F("Start critical!"));
  checkTimeCriticalSensors();
 
  //If in (CRIT_CHECK_INTERVAL) milliseconds no jobs are scheduled, schedule the next check in (CRIT_CHECK_INTERVAL - CRIT_CHECK_DURATION) milliseconds. 
  //It'll take (CRIT_CHECK_DURATION) milliseconds for the function to complete running.
  if (!os_queryTimeCriticalJobs(ms2osticks(CRIT_CHECK_INTERVAL))) {
    os_setTimedCallback(&readCriticalJob, os_getTime()+ms2osticks(CRIT_CHECK_INTERVAL - CRIT_CHECK_DURATION), do_read_critical);
  }
 
  Serial.println(F("Read critical!"));
}
 
 
 
void initSensors() {
  /*if (!mpu.begin()) {
        Serial.println(F("Failed to find MPU6050 chip"));
        while (1) {
            delay(10);
        }
    }
  Serial.println(F("MPU6050 Found!"));*/
 
  if (!sht31.begin(0x44)) {   // Set to 0x45 for alternate i2c addr
    Serial.println(F("Couldn't find SHT31"));
    while (1) delay(1);
  }
  Serial.println(F("SHT31 Found!"));
  
  pinMode(limitSwitchPin, INPUT_PULLUP);
}
 
 
 
 
 
void initAndSetupOS() {
  Serial.println(F("Starting LMIC OS"));
 
  // LMIC init
  os_init();
  // Reset the MAC state. Session and pending data transfers will be discarded.
  LMIC_reset();
 
  // Set static session parameters. Instead of dynamically establishing a session
  // by joining the network, precomputed session parameters are be provided.
  #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
 
  #if defined(CFG_eu868)
  // Set up the channels used by the Things Network, which corresponds
  // to the defaults of most gateways. Without this, only three base
  // channels from the LoRaWAN specification are used, which certainly
  // works, so it is good for debugging, but can overload those
  // frequencies, so be sure to configure the full frequency range of
  // your network here (unless your network autoconfigures them).
  // Setting up channels should happen after LMIC_setSession, as that
  // configures the minimal channel set. The LMIC doesn't let you change
  // the three basic settings, but we show them here.
  for (int channel = 0; channel < 9; channel++) {
      if (channel == 0) {
          LMIC_setupChannel(channel, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7), BAND_CENTI);
      } else {
          LMIC_disableChannel(channel);
      }
  }
  // TTN defines an additional channel at 869.525Mhz using SF9 for class B
  // devices' ping slots. LMIC does not have an easy way to define set this
  // frequency and support for class B is spotty and untested, so this
  // frequency is not configured here.
  #else
  # error Region not supported
  #endif
 
  // Disable link check validation
  LMIC_setLinkCheckMode(0);
 
  // Set data rate and transmit power for uplink
  LMIC_setDrTxpow(DR_SF7,14);
}

This may help:

Bit out of date but the structures stand, there are nodes (one battery change) on about 300,000 uplinks now.

For those arriving via Google, LMIC has put on weight so you’ll need a larger device - like an ATmega4808 to accommodate.

Interrupt? Yeah, only two pins available for that, so pin 3 should be OK.

Share the interrupt pin as the accelerometer can tell you if there is an event - you can then read it, if no event, it was the switch.


As LMIC doesn’t use interrupts, if you can hack the board you could free up pin 2.

For the moment, for the limit switch (accelerometer too) Im just continuously polling (on the switch pin to see if there any state changes, on the acceleration data to see if it surpasses a certain threshold).

If i’m not mistaken and from what I understood, I should attach an interrupt to pin 3 (RISING) (the pin 2 isn’t showing in my node it’s plugged internally to the rfm95), and have both the accel INT pin and limitswitch plugged on it, idk if there will be problems if they both go HIGH at the same time.

Once triggered, It should wake up to check fast on both the limit switch and the accel, and then proceed to treat each event accordingly by scheduling jobs for them (in doubt that it may coincide with the time for a repack+transmit that has been scheduled ages ago, also in this case the data sent by that transmit ain’t gonna be accurate since it’s gonna transmit before responding to the interrupt).

I’ll also need to stay awake till the crate is closed (which means I’ll have to keep polling), and till the accel goes below the specified threshold. (Idk if you agree).

Correct me if I’m mistaken.

Thank you in advance.

Most of this you can try out different combos, such is the nature of prototyping - but a design doc / grid of events is the first step.

Then you can think about rising or falling or both edges, so the switch could tell you if the hive is opened or closed - and a state machine to decide what to do when - like not doing anything when a transmit is in progress - which is outlined in my example code.

As to the matter of if the switch & the accelerometer can be wired in parallel, look at the output of the accelerometer so that you don’t generate any potential short circuits with the switch. Think Push-Pull vs Open Drain.

Most importantly, make it modular, so you can re-arrange the functionality once you get some user feedback.

Sleep on the ATmega328 is shown in my code - all 8 seconds of it - if you can move to something like the Adafruit Feather M0 with RFM95 you can run LMIC on it with plenty of room for code and IO for everything including interrupts plus proper sleep.