Hi, I have a working sketch for a pressure transducer, which includes the ability to modify the tx_interval via downlink. I also use a reset after a certain number of uplinks. The problem I encounter is when I manually reset the device or it resets via the count, the tx_interval resets to the original timing…
Is it possible to commit this interval to memory so it calls that instead? Here’s the sketch…
#include <Arduino.h>
#include <SPI.h>
#include <LoRaWan-RAK4630.h>
#include <nrf_nvic.h>
/* Time the device is sleeping in milliseconds = 2 minutes * 60 seconds * 1000 milliseconds */
uint32_t tx_interval_start = 60000;
int tx_interval = 7200000;
int change_tx_after = 10;
int tx_reset_count = 2500;
int current_sensor_payload;
TimerEvent_t appTimer;
static uint32_t timers_init(void);
static uint32_t count = 1;
//Battery Voltage************************************************************************
#define PIN_VBAT WB_A0
uint32_t vbat_pin = PIN_VBAT;
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12 - bit ADC resolution = 3000mV / 4096
#define VBAT_DIVIDER_COMP (1.73) // Compensation factor for the VBAT divider, depend on the board
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
/** DIO1 GPIO pin for RAK4631 */
#define PIN_LORA_DIO_1 47
//Battery Voltage************************************************************************
// LoRaWan stuff
int8_t initLoRaWan(void);
bool sendLoRaFrame(void);
extern SemaphoreHandle_t loraEvent;
// Main loop stuff
void periodicWakeup(TimerHandle_t unused);
extern SemaphoreHandle_t taskEvent;
extern uint8_t rcvdLoRaData[];
extern uint8_t rcvdDataLen;
extern uint8_t eventType;
extern SoftwareTimer taskWakeupTimer;
/** Semaphore used by events to wake up loop task */
SemaphoreHandle_t taskEvent = NULL;
/** Timer to wakeup task frequently and send message */
SoftwareTimer taskWakeupTimer;
/** Buffer for received LoRaWan data */
uint8_t rcvdLoRaData[256];
/** Length of received data */
uint8_t rcvdDataLen = 0;
uint8_t eventType = -1;
/**
* @brief Timer event that wakes up the loop task frequently
*
* @param unused
*/
void periodicWakeup(TimerHandle_t unused) {
// Switch off blue LED to save power during sleep
digitalWrite(LED_CONN, LOW);
eventType = 1;
// Give the semaphore, so the loop task will wake up
xSemaphoreGiveFromISR(taskEvent, pdFALSE);
}
/** Max size of the data to be transmitted. */
#define LORAWAN_APP_DATA_BUFF_SIZE 64
/** Number of trials for the join request. */
#define JOINREQ_NBTRIALS 8
/** Lora application data buffer. */
static uint8_t m_lora_app_data_buffer[LORAWAN_APP_DATA_BUFF_SIZE];
/** Lora application data structure. */
static lmh_app_data_t m_lora_app_data = { m_lora_app_data_buffer, 0, 0, 0, 0 };
// LoRaWan event handlers
/** LoRaWan callback when join network finished */
static void lorawan_has_joined_handler(void);
/** LoRaWan callback when join failed */
static void lorawan_join_failed_handler(void);
/** LoRaWan callback when data arrived */
static void lorawan_rx_handler(lmh_app_data_t *app_data);
/** LoRaWan callback after class change request finished */
static void lorawan_confirm_class_handler(DeviceClass_t Class);
/** LoRaWan Function to send a package */
bool sendLoRaFrame(void);
/**@brief Structure containing LoRaWan parameters, needed for lmh_init()
*
* Set structure members to
* LORAWAN_ADR_ON or LORAWAN_ADR_OFF to enable or disable adaptive data rate
* LORAWAN_DEFAULT_DATARATE OR DR_0 ... DR_5 for default data rate or specific data rate selection
* LORAWAN_PUBLIC_NETWORK or LORAWAN_PRIVATE_NETWORK to select the use of a public or private network
* JOINREQ_NBTRIALS or a specific number to set the number of trials to join the network
* LORAWAN_DEFAULT_TX_POWER or a specific number to set the TX power used
* LORAWAN_DUTYCYCLE_ON or LORAWAN_DUTYCYCLE_OFF to enable or disable duty cycles
* Please note that ETSI mandates duty cycled transmissions.
*/
static lmh_param_t lora_param_init = { LORAWAN_ADR_ON, DR_0, LORAWAN_PUBLIC_NETWORK, JOINREQ_NBTRIALS, LORAWAN_DEFAULT_TX_POWER, LORAWAN_DUTYCYCLE_OFF };
/** Structure containing LoRaWan callback functions, needed for lmh_init() */
static lmh_callback_t lora_callbacks = { BoardGetBatteryLevel, BoardGetUniqueId, BoardGetRandomSeed,
lorawan_rx_handler, lorawan_has_joined_handler, lorawan_confirm_class_handler, lorawan_join_failed_handler };
// !!!! KEYS ARE MSB !!!!
/** Device EUI required for OTAA network join */
uint8_t nodeDeviceEUI[8] = { };
/** Application EUI required for network join */
uint8_t nodeAppEUI[8] = { };
/** Application key required for network join */
uint8_t nodeAppKey[16] = { };
/** Device address required for ABP network join */
uint32_t nodeDevAddr = 0x26021FB6;
/** Network session key required for ABP network join */
uint8_t nodeNwsKey[16] = { };
/** Application session key required for ABP network join */
uint8_t nodeAppsKey[16] = { };
/** Flag whether to use OTAA or ABP network join method */
bool doOTAA = true;
DeviceClass_t gCurrentClass = CLASS_A; /* class definition*/
LoRaMacRegion_t gCurrentRegion = LORAMAC_REGION_AU915; /* Region:EU868*/
/**
* @brief Initialize LoRa HW and LoRaWan MAC layer
*
* @return int8_t result
* 0 => OK
* -1 => SX126x HW init failure
* -2 => LoRaWan MAC initialization failure
* -3 => Subband selection failure
* -4 => LoRaWan handler task start failure
*/
int8_t initLoRaWan(void) {
Serial.printf("<<>> lora_rak4630_init called at %dms\n", millis());
// Initialize LoRa chip.
if (lora_rak4630_init() != 0) {
return -1;
}
Serial.printf("<<>> lora_rak4630_init finished at %dms\n", millis());
delay(100);
// Setup the EUIs and Keys
if (doOTAA) {
lmh_setDevEui(nodeDeviceEUI);
lmh_setAppEui(nodeAppEUI);
lmh_setAppKey(nodeAppKey);
} else {
lmh_setNwkSKey(nodeNwsKey);
lmh_setAppSKey(nodeAppsKey);
lmh_setDevAddr(nodeDevAddr);
}
Serial.printf("<<>> lmh_init called at %dms\n", millis());
// Initialize LoRaWan
if (lmh_init(&lora_callbacks, lora_param_init, doOTAA, gCurrentClass, gCurrentRegion) != 0) {
return -2;
}
Serial.printf("<<>> lmh_init finished at %dms\n", millis());
delay(100);
// For some regions we might need to define the sub band the gateway is listening to
// This must be called AFTER lmh_init()
if (!lmh_setSubBandChannels(2)) {
return -3;
}
// Start Join procedure
#ifndef MAX_SAVE
Serial.printf("<<>> lmh_join called at %dms\n", millis());
#endif
lmh_join();
Serial.printf("<<>> lmh_join finished at %dms\n", millis());
delay(100);
return 0;
}
/**
* @brief LoRa function for handling HasJoined event.
*/
static void lorawan_has_joined_handler(void) {
Serial.printf("<<>> lorawan_has_joined_handler called at %dms\n", millis());
delay(100);
if (doOTAA) {
uint32_t otaaDevAddr = lmh_getDevAddr();
#ifndef MAX_SAVE
Serial.printf("OTAA joined and got dev address %08X\n", otaaDevAddr);
#endif
} else {
#ifndef MAX_SAVE
Serial.println("ABP joined");
#endif
}
// Default is Class A, where the SX1262 transceiver is in sleep mode unless a package is sent
// If switched to Class C the power consumption is higher because the SX1262 chip remains in RX mode
// lmh_class_request(CLASS_C);
digitalWrite(LED_CONN, LOW);
// Now we are connected, start the timer that will wakeup the loop frequently
taskWakeupTimer.begin(tx_interval_start, periodicWakeup);
taskWakeupTimer.start();
eventType = 1;
// Notify task about the event
if (taskEvent != NULL) {
Serial.printf("<<>> Waking up task at %dms\n", millis());
xSemaphoreGive(taskEvent);
}
}
/**@brief LoRa function for handling OTAA join failed
*/
static void lorawan_join_failed_handler(void) {
Serial.println("OVER_THE_AIR_ACTIVATION failed!");
Serial.println("Check your EUI's and Keys's!");
Serial.println("Check if a Gateway is in range!");
}
/**
* @brief Function for handling LoRaWan received data from Gateway
*
* @param app_data Pointer to rx data
*/
static void lorawan_rx_handler(lmh_app_data_t *app_data) {
#ifndef MAX_SAVE
Serial.printf("LoRa Packet received on port %d, size:%d, rssi:%d, snr:%d\n",
app_data->port, app_data->buffsize, app_data->rssi, app_data->snr);
#endif
switch (app_data->port) {
case 3:
// Port 3 switches the class
if (app_data->buffsize == 1) {
switch (app_data->buffer[0]) {
case 0:
lmh_class_request(CLASS_A);
#ifndef MAX_SAVE
Serial.println("Request to switch to class A");
#endif
break;
case 1:
lmh_class_request(CLASS_B);
#ifndef MAX_SAVE
Serial.println("Request to switch to class B");
#endif
break;
case 2:
lmh_class_request(CLASS_C);
#ifndef MAX_SAVE
Serial.println("Request to switch to class C");
#endif
break;
default:
break;
}
}
break;
case LORAWAN_APP_PORT:
// Copy the data into loop data buffer
memcpy(rcvdLoRaData, app_data->buffer, app_data->buffsize);
rcvdDataLen = app_data->buffsize;
eventType = 0;
// Assuming the new time is encoded as 3 bytes. e.g. 30=> 0x00, 0x00, 0x1F
// Downlink must be sent on Port 2
// Downlink is in seconds
uint32_t new_time = app_data->buffer[0] << 16;
new_time |= app_data->buffer[1] << 8;
new_time |= app_data->buffer[2];
tx_interval = new_time * 1000;
Serial.print("New Time Set ");
Serial.println(new_time);
Serial.print("Sleep Time ");
Serial.println(tx_interval);
taskWakeupTimer.stop();
taskWakeupTimer.setPeriod(tx_interval_start);
taskWakeupTimer.start();
// Notify task about the event
if (taskEvent != NULL) {
#ifndef MAX_SAVE
Serial.println("Waking up loop task");
#endif
xSemaphoreGive(taskEvent);
}
}
}
/**
* @brief Callback for class switch confirmation
*
* @param Class The new class
*/
static void lorawan_confirm_class_handler(DeviceClass_t Class) {
#ifndef MAX_SAVE
Serial.printf("switch to class %c done\n", "ABC"[Class]);
#endif
// Informs the server that switch has occurred ASAP
m_lora_app_data.buffsize = 0;
m_lora_app_data.port = LORAWAN_APP_PORT;
lmh_send(&m_lora_app_data, LMH_UNCONFIRMED_MSG);
}
/**
* @brief Send a LoRaWan package
*
* @return result of send request
*/
bool sendLoRaFrame(void) {
if (lmh_join_status_get() != LMH_SET) {
//Not joined, try again later
#ifndef MAX_SAVE
Serial.println("Did not join network, skip sending frame");
#endif
return false;
}
//Battery Voltage**********************************************************************************
// Get a raw ADC reading
analogReference(AR_INTERNAL_3_0);
delay(50);
int vbat_mv = readVBAT();
int vbat_VOLT = (vbat_mv / 10) - 300;
analogReference(AR_INTERNAL); //This takes it back to default (3.6V), so wind direction has full resistance
// Convert from raw mv to percentage (based on LIPO chemistry)
uint8_t vbat_per = mvToPercent(vbat_mv);
m_lora_app_data.port = LORAWAN_APP_PORT;
/* WisBLOCK 5801 Power On*/
pinMode(WB_IO1, OUTPUT);
digitalWrite(WB_IO1, HIGH);
delay(1000);
/* WisBLOCK 5801 Power On*/
int i;
int sensor_pin = A1; // select the input pin for the potentiometer
int mcu_ain_value = 0;
int pressure; //KPa as unit
int average_value;
float voltage_ain;
float current_sensor; // variable to store the value coming from the sensor
for (i = 0; i < 10; i++) {
mcu_ain_value += analogRead(sensor_pin);
}
/* WisBLOCK 5801 Power Off*/
digitalWrite(WB_IO1, LOW);
/* WisBLOCK 5801 Power Off*/
average_value = mcu_ain_value / i;
voltage_ain = average_value * 3.6 / 1024; //raef 3.6v / 10bit ADC
current_sensor = voltage_ain / 149.9 * 1000; //WisBlock RAK5801 I=U/149.9\*1000 (mA)
current_sensor_payload = current_sensor * 1000;
// Serial.printf("-------average_value------- = %d\n", average_value);
// Serial.printf("-------current_sensor------ = %f\n", current_sensor);
// Serial.printf("--current_sensor_payload--- = %d\n", current_sensor_payload);
// Serial.printf("----------Voltage (mV)----- = %d\n", vbat_mv);
uint32_t buffSize = 0;
m_lora_app_data_buffer[buffSize++] = highByte(current_sensor_payload);
m_lora_app_data_buffer[buffSize++] = lowByte(current_sensor_payload);
m_lora_app_data_buffer[buffSize++] = vbat_mv / 20;
// m_lora_app_data_buffer[buffSize++] = 'l';
// m_lora_app_data_buffer[buffSize++] = 'o';
m_lora_app_data.buffsize = buffSize;
lmh_error_status error = lmh_send(&m_lora_app_data, LMH_UNCONFIRMED_MSG);
return (error == 0);
}
void setup(void) {
pinMode(17, OUTPUT);
digitalWrite(17, HIGH);
//Battery Voltage*****************************************************************
// Set the analog reference to 3.0V (default = 3.6V)
// analogReference(AR_INTERNAL_3_0);
// Let the ADC settle
delay(1);
// Get a single ADC sample and throw it away
float readVBAT();
//******************************************************************************
// Create the LoRaWan event semaphore
taskEvent = xSemaphoreCreateBinary();
// Initialize semaphore
xSemaphoreGive(taskEvent);
// Initialize the built in LED
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LOW);
// Initialize the connection status LED
pinMode(LED_CONN, OUTPUT);
digitalWrite(LED_CONN, LOW);
#ifndef MAX_SAVE
// Initialize Serial for debug output
Serial.begin(115200);
time_t timeout = millis();
// On nRF52840 the USB serial is not available immediately
while (!Serial) {
if ((millis() - timeout) < 5000) {
delay(100);
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
} else {
break;
}
}
#endif
digitalWrite(LED_BUILTIN, LOW);
#ifndef MAX_SAVE
#endif
// Initialize LoRaWan and start join request
int8_t loraInitResult = initLoRaWan();
#ifndef MAX_SAVE
if (loraInitResult != 0) {
switch (loraInitResult) {
case -1:
Serial.println("SX126x init failed");
break;
case -2:
Serial.println("LoRaWan init failed");
break;
case -3:
Serial.println("Subband init error");
break;
case -4:
Serial.println("LoRa Task init error");
break;
default:
Serial.println("LoRa init unknown error");
break;
}
// Without working LoRa we just stop here
while (1) {
Serial.println("Nothing I can do, just loving you");
delay(5000);
}
}
Serial.println("LoRaWan init success");
#endif
// Take the semaphore so the loop will go to sleep until an event happens
xSemaphoreTake(taskEvent, 10);
}
/**
* @brief Arduino loop task. Called in a loop from the FreeRTOS task handler
*
*/
void loop(void) {
// Switch off blue LED to show we go to sleep
// digitalWrite(LED_BUILTIN, LOW);
// Sleep until we are woken up by an event
if (xSemaphoreTake(taskEvent, portMAX_DELAY) == pdTRUE) {
// Switch on blue LED to show we are awake
digitalWrite(LED_BUILTIN, HIGH);
delay(500); // Only so we can see the blue LED
// Check the wake up reason
switch (eventType) {
case 0: // Wakeup reason is package downlink arrived
#ifndef MAX_SAVE
Serial.println("Received package over LoRaWan");
#endif
if (rcvdLoRaData[0] > 0x1F) {
#ifndef MAX_SAVE
Serial.printf("%s\n", (char *)rcvdLoRaData);
#endif
} else {
#ifndef MAX_SAVE
for (int idx = 0; idx < rcvdDataLen; idx++) {
Serial.printf("%X ", rcvdLoRaData[idx]);
}
Serial.println("");
#endif
}
break;
case 1: // Wakeup reason is timer
#ifndef MAX_SAVE
Serial.println("Timer wakeup");
#endif
/// \todo read sensor or whatever you need to do frequently
// Send the data package
if (sendLoRaFrame()) {
#ifndef MAX_SAVE
Serial.println("LoRaWan package sent successfully");
count++;
Serial.printf("lmh_send ok count %d\n", count - 1);
if (count > change_tx_after) {
tx_interval_start = tx_interval;
Serial.printf("Frame count over set limit - interval now set to %d\n", tx_interval);
taskWakeupTimer.stop();
taskWakeupTimer.setPeriod(tx_interval_start);
taskWakeupTimer.start();
}
if (count > tx_reset_count) {
sd_nvic_SystemReset();
Serial.printf("Frame count over set due for restart - number of counts = %d\n", tx_reset_count);
taskWakeupTimer.stop();
taskWakeupTimer.setPeriod(tx_interval_start);
taskWakeupTimer.start();
}
#endif
} else {
#ifndef MAX_SAVE
Serial.println("LoRaWan package send failed");
/// \todo maybe you need to retry here?
#endif
}
break;
default:
#ifndef MAX_SAVE
Serial.println("This should never happen ;-)");
#endif
break;
}
// Go back to sleep
Serial.println("Going back to sleep ;-)");
digitalWrite(LED_BUILTIN, LOW);
xSemaphoreTake(taskEvent, 10);
}
}
float readVBAT(void) {
float raw;
// Get the raw 12-bit, 0..3000mV ADC value
analogReadResolution(12);
raw = analogRead(vbat_pin);
delay(50);
analogReadResolution(10);
return raw * REAL_VBAT_MV_PER_LSB;
}
uint8_t mvToPercent(float mvolts) {
if (mvolts < 3300)
return 0;
if (mvolts < 3600) {
mvolts -= 3300;
return mvolts / 30;
}
mvolts -= 3600;
return 10 + (mvolts * 0.15F); // thats mvolts /6.66666666
}
/**
@brief get LoRaWan Battery value
@param mvolts
Raw Battery Voltage
*/
uint8_t mvToLoRaWanBattVal(float mvolts) {
if (mvolts < 3300)
return 0;
if (mvolts < 3600) {
mvolts -= 3300;
return mvolts / 30 * 2.55;
}
mvolts -= 3600;
return (10 + (mvolts * 0.15F)) * 2.55;
}
//***************************************************************************************