Hi everyone, I have an arduino sketch I’m using to send to ttn from a node (esp32, lmic library). I have a problem where with every uplink, a corresponding downlink is sent. I understand it is pretty normal for the first couple of uplinks to do this, but it’s happening every time. I have an earlier version of the sketch, but fail to find the difference. Can anyone point me in the right direction?
Here is the code…
#include <lmic.h>
#include <hal/hal.h>
#include <SPI.h>
#include <MedianFilter.h>
#include <esp_system.h>
/* define keys */
#define _DEVEUI { }
#define _APPEUI { }
#define _APPKEY { }
/* define macros */
#define uS_TO_S_FACTOR 1000000 /* conversion factor for micro seconds to seconds */
#define TX_INTERVAL 1 /* time ESP32 will go to sleep (in minutes) */
#define MAX_LIMIT 3
float Vbat_Vsol = 3.3;
float STEP_Cnt = 4096;
float V_Trim = 10;
float R_01 = 6800; //Battery Resistor 1
float R_02 = 8200; //Battery Resistor 2
float R_03 = 15000; //Solar Resistor 1
float R_04 = 8200; //Solar Resistor 2
/* pin mapping */
#define VBAT_PIN 36
#define SWITCH_PIN 23
#define ANALOG_IN_PIN 34
#define DIAG_LED 0
/* LMIC session information */
RTC_DATA_ATTR u4_t netid;
RTC_DATA_ATTR devaddr_t devaddr;
RTC_DATA_ATTR u1_t nwkKey[16];
RTC_DATA_ATTR u1_t artKey[16];
/* LMIC state structure */
RTC_DATA_ATTR uint32_t seqnoUp;
RTC_DATA_ATTR uint32_t seqnoDn;
RTC_DATA_ATTR uint32_t globalDutyRate;
RTC_DATA_ATTR uint32_t globalDutyAvail;
RTC_DATA_ATTR uint32_t opmode;
RTC_DATA_ATTR uint32_t upRepeat;
RTC_DATA_ATTR uint32_t adrTxPow;
RTC_DATA_ATTR uint32_t datarate;
RTC_DATA_ATTR uint32_t devNonce;
RTC_DATA_ATTR uint32_t dnConf;
RTC_DATA_ATTR uint32_t adrAckReq;
RTC_DATA_ATTR uint32_t rxDelay;
/* global varialbles */
RTC_DATA_ATTR bool isOTAA = true;
RTC_DATA_ATTR uint32_t txInterval = TX_INTERVAL;
/* global variables */
static osjob_t sendJob;
int samples = 11;
int ptTotal = 0;
MedianFilter levels(samples, 0);
/* time tick */
int startSend;
int finishSend;
int timeToSend;
/* reset board */
void IRAM_ATTR resetModule() {
esp_restart();
}
/*
user-defined functions (essential)
the LMIC library requires the application to implement a few callback functions.
these functions will be called by the state engine to query application-specific
information and to deliver state events to the application.
*/
static const u1_t DEVEUI[8] = _DEVEUI;
static const u1_t APPEUI[8] = _APPEUI;
static const u1_t APPKEY[16] = _APPKEY;
void os_getDevEui(u1_t* buf) {
memcpy_P(buf, DEVEUI, 8);
}
void os_getArtEui(u1_t* buf) {
memcpy_P(buf, APPEUI, 8);
}
void os_getDevKey(u1_t* buf) {
memcpy_P(buf, APPKEY, 16);
}
/* LMIC pin mapping */
const lmic_pinmap lmic_pins = {
.nss = 18,
.rxtx = LMIC_UNUSED_PIN,
.rst = LMIC_UNUSED_PIN,
/* if DIO2 is not connected, dio2 as unused, otherwise 32 */
.dio = {
/*dio0*/ 26,
/*dio1*/ 33,
/*dio2*/ LMIC_UNUSED_PIN
}
};
/* event-based programming model where all protocol events are
dispatched to the application’s onEvent() callback function */
void onEvent(ev_t ev) {
Serial.print("[INFO] ");
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("[INFO] EV_JOINED\n");
{
netid = 0;
devaddr = 0;
LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey);
Serial.print("[INFO] netid: ");
Serial.println(netid, DEC);
Serial.print("[INFO] devaddr: ");
Serial.println(devaddr, HEX);
Serial.print("[INFO] artKey: ");
for (int i = 0; i < sizeof(artKey); ++i) {
Serial.print(artKey[i], HEX);
}
Serial.println("");
Serial.print("[INFO] nwkKey: ");
for (int i = 0; i < sizeof(nwkKey); ++i) {
Serial.print(nwkKey[i], HEX);
}
Serial.println("");
LMIC_setSeqnoUp(1);
}
/* disable link check validation (automatically enabled */
/* during join, but not supported by TTN at this time) */
LMIC_setLinkCheckMode(0);
break;
case EV_JOIN_FAILED:
break;
case EV_TXCOMPLETE:
Serial.println("[INFO] EV_TXCOMPLETE (includes waiting for RX windows)\n");
if (LMIC.txrxFlags & TXRX_ACK) {
Serial.println("[INFO] Received ack");
}
if (LMIC.dataLen) {
Serial.printf("[INFO] Received %d bytes of payload\n", LMIC.dataLen);
/* receive downlink */
if (LMIC.dataLen == 5) {
Serial.println("[INFO] Downlink is available");
/* get packet information */
uint8_t header = LMIC.frame[LMIC.dataBeg + 0];
uint8_t cmd = LMIC.frame[LMIC.dataBeg + 1];
uint16_t dat = 256 * LMIC.frame[LMIC.dataBeg + 2] + LMIC.frame[LMIC.dataBeg + 3];
uint8_t tail = LMIC.frame[LMIC.dataBeg + 4];
/*
analyze downlink {0x55, cmd, dat0, dat1, 0xFF}
@cmd: {set interval - 01, reboot - 02}
@dat: 2 byte data
*/
if (header == 0x55 && tail == 0xFF) {
if (cmd == 0x01) {
Serial.println("[INFO] Received CHANGE_TX_INTERVAL request");
txInterval = dat;
}
else {
Serial.println("[INFO] Received REBOOT request");
resetModule();
}
}
}
}
/* turn off PCB power */
digitalWrite(SWITCH_PIN, LOW);
Serial.println("***");
Serial.println("[INFO] Turning off PCB power");
/* Use ABP next time */
isOTAA = false;
Serial.println("[INFO] Saving LMIC State to memory");
seqnoUp = LMIC.seqnoUp;
seqnoDn = LMIC.seqnoDn;
globalDutyRate = LMIC.globalDutyRate;
globalDutyAvail = LMIC.globalDutyAvail;
opmode = LMIC.opmode;
upRepeat = LMIC.upRepeat;
adrTxPow = LMIC.adrTxPow;
datarate = LMIC.datarate;
devNonce = LMIC.devNonce;
dnConf = LMIC.dnConf;
adrAckReq = LMIC.adrAckReq;
rxDelay = LMIC.rxDelay;
finishSend = millis();
timeToSend = (finishSend - startSend) / 1000;
Serial.printf("[INFO] The board was awake for %d seconds this time\n", timeToSend);
Serial.printf("[INFO] Going to sleep now for %d minute\n", txInterval);
Serial.println("***\n\n");
Serial.flush();
esp_sleep_enable_timer_wakeup(txInterval * 60 * uS_TO_S_FACTOR);
esp_deep_sleep_start();
break;
default:
Serial.println("[INFO] Unhandled event");
break;
}
}
void doSend(osjob_t* j) {
/* check if there is not a current TX/RX job running */
if (LMIC.opmode & OP_TXRXPEND) {
Serial.println("[INFO] TX/RX was Busy, last msg not yet sent");
}
else {
/* prepare upstream data transmission at the next possible time. */
Serial.println("[INFO] Reading sensors and building payload\n");
digitalWrite(SWITCH_PIN, LOW); /* select Battery channel */
delay(500); /* small delay for settling */
float vbat_a = analogRead(VBAT_PIN)/STEP_Cnt*Vbat_Vsol;
int vbat = (vbat_a /(R_02/(R_02+R_01))*100)+V_Trim;
Serial.println(analogRead(VBAT_PIN));
Serial.println(vbat_a);
Serial.println(vbat);
digitalWrite(SWITCH_PIN, HIGH); /* select Solar channel */
delay(500);
float vsol_a = analogRead(VBAT_PIN)/STEP_Cnt*Vbat_Vsol;
int vsol = (vsol_a /(R_04/(R_03+R_04))*100)+V_Trim;
Serial.println(analogRead(VBAT_PIN));
Serial.println(vsol_a);
Serial.println(vsol);
for (int p = 1; p <= samples; p++) {
int ptLevel = analogRead(ANALOG_IN_PIN);
levels.in(ptLevel);
ptTotal = ptTotal + ptLevel;
Serial.printf("[INFO] %d: %d\n", p, ptLevel);
delay(50);
}
int level = levels.out();
Serial.printf("[INFO] Median Reading: %d\n", level);
byte payload[6];
payload[0] = highByte(vbat);
payload[1] = lowByte(vbat);
payload[2] = highByte(level);
payload[3] = lowByte(level);
payload[4] = highByte(vsol);
payload[5] = lowByte(vsol);
/* prepare upstream data transmission at the next possible time */
LMIC_setTxData2(1, payload, sizeof(payload), 0);
Serial.println("[INFO] Packet queued");
}
/* next TX is scheduled after TX_COMPLETE event */
}
/* ____ __
/ __/__ / /___ _____
_\ \/ -_) __/ // / _ \
/___/\__/\__/\_,_/ .__/
*************** /_/ ***/
void setup() {
/* serial initialize */
Serial.begin(9600);
while (!Serial);
Serial.println("[INFO] Starting");
/* pin configuration */
pinMode(SWITCH_PIN, OUTPUT);
pinMode(ANALOG_IN_PIN, INPUT);
digitalWrite(SWITCH_PIN, HIGH); /* Turn on PCB power */
delay(3000);
/* timer started */
startSend = millis();
/* LMIC init */
os_init();
/* reset the MAC state */
/* session and pending data transfers will be discarded */
LMIC_reset();
/* let LMIC compensate for +/- 1% clock error */
LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100);
LMIC_selectSubBand(1);
if (isOTAA) {
/* immediately start joining the network */
Serial.println("[INFO] Start joining using OTAA");
LMIC_startJoining();
/*
init done - onEvent() callback will be invoked
the events EV_JOINING and EV_JOINED or EV_JOIN_FAILED will be generated
*/
}
else {
/*
set static session parameters
instead of dynamically establishing a session by joining the network, precomputed session parameters can be provided.
to resume a session with precomputed parameters,
the frame sequence counters (LMIC.seqnoUp and LMIC.seqnoDn) must be restored to their latest values.
*/
Serial.println("***");
Serial.printf("[INFO] Seqnr set to %d\n", seqnoUp) ;
Serial.println("[INFO] Resuming Session using previous Join Session Keys");
LMIC_setSession( netid, devaddr, nwkKey, artKey) ;
Serial.println("[INFO] Retrieving LMIC State from memory");
Serial.println("***\n");
LMIC.seqnoUp = seqnoUp;
LMIC.seqnoDn = seqnoDn;
LMIC.globalDutyRate = globalDutyRate;
LMIC.globalDutyAvail = globalDutyAvail;
LMIC.opmode = opmode;
LMIC.upRepeat = upRepeat;
LMIC.adrTxPow = adrTxPow;
LMIC.datarate = datarate;
LMIC.devNonce = devNonce;
LMIC.dnConf = dnConf;
LMIC.adrAckReq = adrAckReq;
LMIC.rxDelay = rxDelay;
}
/* start job (sending automatically starts OTAA too) */
doSend(&sendJob);
}
/* __
/ / ___ ___ ___
/ /__/ _ \/ _ \/ _ \
/____/\___/\___/ .__/
************* /_/ ****/
void loop() {
os_runloop_once();
}