Heltec LMIC-node battery powered

Project:

  • LMIC-node example meant to send a current geophone reading as well as the current GPS location to my Things Network application.
  • I am using the Heltec Lora32 V2 board with a GPS module and a 16-bit ADC (ADS1115) which connects to a geophone (device to read seismic activity).
  • For this project, the board is supposed to be powered by an 18650 battery.
  • I have disabled the OLED screen and the boards LED to save power requirements.

Physical components:

  • Heltec Lora32 V2
  • beitain BS-280 GPS module
  • Adafruit ADS1115
  • Sparkfun SM-24 Geophone
  • Standard LED included in Arduino Starter kit.
  • 3.7v 9900 mAh 18650 rechargeable battery (connected by the jst port under the Heltec Board)

Problem:

  • The program runs exactly how it is supposed to when it is powered by the USB cable (it establishes connection and sends the first uplink message).
  • When I attempt to power the board by the battery, the built in LED on the GPS module turns on but I see no uplinks in the application. If I attach the USB cable while the battery is connected as well, it still doesn’t establish connection or sends the first uplink message. It only establishes connection when I open a serial monitor window in PlatformIO

What I have attempted:

  • I thought it may be a power issue, so I tested the fully charged battery with an unedited LMIC-node example. This worked just fine being powered by the battery.
  • I also tested the same battery with an Arduino Heltec sender and receiver sketch with the same components enabled as my LMIC-node example. This worked with no problems.

My question:

  • Is this a power issue? Why would the same board with the same components work with this battery using an Arduino sketch vs this example? Is this issue something completely different?

my full program is split into two comments below.

First Half

#include "LMIC-node.h"
#include <Adafruit_ADS1X15.h>
#include "TinyGPS++.h"

TinyGPSPlus gps;
Adafruit_ADS1115 ads;

//  β–ˆ β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„ β–ˆβ–€β–€   β–ˆβ–€β–„ β–ˆβ–€β–€ β–ˆβ–€β–€ β–€β–ˆβ–€ β–ˆβ–€β–ˆ
//  β–ˆ β–ˆ β–€β–€β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆ   β–ˆ β–ˆ β–ˆ β–ˆ β–ˆβ–€β–€   β–ˆβ–€β–„ β–ˆβ–€β–€ β–ˆ β–ˆ  β–ˆ  β–ˆ β–ˆ
//  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€   β–€β–€β–€ β–€β–€β–€ β–€β–€  β–€β–€β–€   β–€β–€  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€


const uint8_t payloadBufferLength = 4;    // Adjust to fit max payload length
const int LED_PIN = 12;
int16_t SensorRead;
float LatRead;
float LonRead;
float AltRead;
int HourRead;
int MinRead;
int SecRead;
double LatRAW;
double LonRAW;
double AltRAW;
double HourRAW;
double MinRAW;
double SecRAW;


//  β–ˆ β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„ β–ˆβ–€β–€   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„
//  β–ˆ β–ˆ β–€β–€β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆ   β–ˆ β–ˆ β–ˆ β–ˆ β–ˆβ–€β–€   β–ˆβ–€β–€ β–ˆ β–ˆ β–ˆ β–ˆ
//  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€   β–€β–€β–€ β–€β–€β–€ β–€β–€  β–€β–€β–€   β–€β–€β–€ β–€ β–€ β–€β–€ 


uint8_t payloadBuffer[payloadBufferLength];
static osjob_t doWorkJob;
uint32_t doWorkIntervalSeconds = DO_WORK_INTERVAL_SECONDS;  // Change value in platformio.ini

// Note: LoRa module pin mappings are defined in the Board Support Files.

// Set LoRaWAN keys defined in lorawan-keys.h.
#ifdef OTAA_ACTIVATION
    static const u1_t PROGMEM DEVEUI[8]  = { OTAA_DEVEUI } ;
    static const u1_t PROGMEM APPEUI[8]  = { OTAA_APPEUI };
    static const u1_t PROGMEM APPKEY[16] = { OTAA_APPKEY };
    // Below callbacks are used by LMIC for reading above values.
    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); }    
#else
    // ABP activation
    static const u4_t DEVADDR = ABP_DEVADDR ;
    static const PROGMEM u1_t NWKSKEY[16] = { ABP_NWKSKEY };
    static const u1_t PROGMEM APPSKEY[16] = { ABP_APPSKEY };
    // Below callbacks are not used be they must be defined.
    void os_getDevEui (u1_t* buf) { }
    void os_getArtEui (u1_t* buf) { }
    void os_getDevKey (u1_t* buf) { }
#endif


int16_t getSnrTenfold()
{
    // Returns ten times the SNR (dB) value of the last received packet.
    // Ten times to prevent the use of float but keep 1 decimal digit accuracy.
    // Calculation per SX1276 datasheet rev.7 Β§6.4, SX1276 datasheet rev.4 Β§6.4.
    // LMIC.snr contains value of PacketSnr, which is 4 times the actual SNR value.
    return (LMIC.snr * 10) / 4;
}


int16_t getRssi(int8_t snr)
{
    // Returns correct RSSI (dBm) value of the last received packet.
    // Calculation per SX1276 datasheet rev.7 Β§5.5.5, SX1272 datasheet rev.4 Β§5.5.5.

    #define RSSI_OFFSET            64
    #define SX1276_FREQ_LF_MAX     525000000     // per datasheet 6.3
    #define SX1272_RSSI_ADJUST     -139
    #define SX1276_RSSI_ADJUST_LF  -164
    #define SX1276_RSSI_ADJUST_HF  -157

    int16_t rssi;

    #ifdef MCCI_LMIC

        rssi = LMIC.rssi - RSSI_OFFSET;

    #else
        int16_t rssiAdjust;
        #ifdef CFG_sx1276_radio
            if (LMIC.freq > SX1276_FREQ_LF_MAX)
            {
                rssiAdjust = SX1276_RSSI_ADJUST_HF;
            }
            else
            {
                rssiAdjust = SX1276_RSSI_ADJUST_LF;   
            }
        #else
            // CFG_sx1272_radio    
            rssiAdjust = SX1272_RSSI_ADJUST;
        #endif    
        
        // Revert modification (applied in lmic/radio.c) to get PacketRssi.
        int16_t packetRssi = LMIC.rssi + 125 - RSSI_OFFSET;
        if (snr < 0)
        {
            rssi = rssiAdjust + packetRssi + snr;
        }
        else
        {
            rssi = rssiAdjust + (16 * packetRssi) / 15;
        }
    #endif

    return rssi;
}


void printEvent(ostime_t timestamp, 
                const char * const message, 
                PrintTarget target = PrintTarget::All,
                bool clearDisplayStatusRow = true,
                bool eventLabel = false)
{
    #ifdef USE_DISPLAY 
        if (target == PrintTarget::All || target == PrintTarget::Display)
        {
            display.clearLine(TIME_ROW);
            display.setCursor(COL_0, TIME_ROW);
            display.print(F("Time:"));                 
            display.print(timestamp); 
            display.clearLine(EVENT_ROW);
            if (clearDisplayStatusRow)
            {
                display.clearLine(STATUS_ROW);    
            }
            display.setCursor(COL_0, EVENT_ROW);               
            display.print(message);
        }
    #endif  
    
    #ifdef USE_SERIAL
        // Create padded/indented output without using printf().
        // printf() is not default supported/enabled in each Arduino core. 
        // Not using printf() will save memory for memory constrainted devices.
        String timeString(timestamp);
        uint8_t len = timeString.length();
        uint8_t zerosCount = TIMESTAMP_WIDTH > len ? TIMESTAMP_WIDTH - len : 0;

        if (target == PrintTarget::All || target == PrintTarget::Serial)
        {
            printChars(serial, '0', zerosCount);
            serial.print(timeString);
            serial.print(":  ");
            if (eventLabel)
            {
                serial.print(F("Event: "));
            }
            serial.println(message);
        }
    #endif   
}           

void printEvent(ostime_t timestamp, 
                ev_t ev, 
                PrintTarget target = PrintTarget::All, 
                bool clearDisplayStatusRow = true)
{
    #if defined(USE_DISPLAY) || defined(USE_SERIAL)
        printEvent(timestamp, lmicEventNames[ev], target, clearDisplayStatusRow, true);
    #endif
}


void printFrameCounters(PrintTarget target = PrintTarget::All)
{
    #ifdef USE_DISPLAY
        if (target == PrintTarget::Display || target == PrintTarget::All)
        {
            display.clearLine(FRMCNTRS_ROW);
            display.setCursor(COL_0, FRMCNTRS_ROW);
            display.print(F("Up:"));
            display.print(LMIC.seqnoUp);
            display.print(F(" Dn:"));
            display.print(LMIC.seqnoDn);        
        }
    #endif

    #ifdef USE_SERIAL
        if (target == PrintTarget::Serial || target == PrintTarget::All)
        {
            printSpaces(serial, MESSAGE_INDENT);
            serial.print(F("Up: "));
            serial.print(LMIC.seqnoUp);
            serial.print(F(",  Down: "));
            serial.println(LMIC.seqnoDn);        
        }
    #endif        
}      


void printSessionKeys()
{    
    #if defined(USE_SERIAL) && defined(MCCI_LMIC)
        u4_t networkId = 0;
        devaddr_t deviceAddress = 0;
        u1_t networkSessionKey[16];
        u1_t applicationSessionKey[16];
        LMIC_getSessionKeys(&networkId, &deviceAddress, 
                            networkSessionKey, applicationSessionKey);

        printSpaces(serial, MESSAGE_INDENT);    
        serial.print(F("Network Id: "));
        serial.println(networkId, DEC);

        printSpaces(serial, MESSAGE_INDENT);    
        serial.print(F("Device Address: "));
        serial.println(deviceAddress, HEX);

        printSpaces(serial, MESSAGE_INDENT);    
        serial.print(F("Application Session Key: "));
        printHex(serial, applicationSessionKey, 16, true, '-');

        printSpaces(serial, MESSAGE_INDENT);    
        serial.print(F("Network Session Key:     "));
        printHex(serial, networkSessionKey, 16, true, '-');
    #endif
}


void printDownlinkInfo(void)
{
    #if defined(USE_SERIAL) || defined(USE_DISPLAY)

        uint8_t dataLength = LMIC.dataLen;
        // bool ackReceived = LMIC.txrxFlags & TXRX_ACK;

        int16_t snrTenfold = getSnrTenfold();
        int8_t snr = snrTenfold / 10;
        int8_t snrDecimalFraction = snrTenfold % 10;
        int16_t rssi = getRssi(snr);

        uint8_t fPort = 0;        
        if (LMIC.txrxFlags & TXRX_PORT)
        {
            fPort = LMIC.frame[LMIC.dataBeg -1];
        }        

        #ifdef USE_DISPLAY
            display.clearLine(EVENT_ROW);        
            display.setCursor(COL_0, EVENT_ROW);
            display.print(F("RX P:"));
            display.print(fPort);
            if (dataLength != 0)
            {
                display.print(" Len:");
                display.print(LMIC.dataLen);                       
            }
            display.clearLine(STATUS_ROW);        
            display.setCursor(COL_0, STATUS_ROW);
            display.print(F("RSSI"));
            display.print(rssi);
            display.print(F(" SNR"));
            display.print(snr);                
            display.print(".");                
            display.print(snrDecimalFraction);                      
        #endif

        #ifdef USE_SERIAL
            printSpaces(serial, MESSAGE_INDENT);    
            serial.println(F("Downlink received"));

            printSpaces(serial, MESSAGE_INDENT);
            serial.print(F("RSSI: "));
            serial.print(rssi);
            serial.print(F(" dBm,  SNR: "));
            serial.print(snr);                        
            serial.print(".");                        
            serial.print(snrDecimalFraction);                        
            serial.println(F(" dB"));

            printSpaces(serial, MESSAGE_INDENT);    
            serial.print(F("Port: "));
            serial.println(fPort);
   
            if (dataLength != 0)
            {
                printSpaces(serial, MESSAGE_INDENT);
                serial.print(F("Length: "));
                serial.println(LMIC.dataLen);                   
                printSpaces(serial, MESSAGE_INDENT);    
                serial.print(F("Data: "));
                printHex(serial, LMIC.frame+LMIC.dataBeg, LMIC.dataLen, true, ' ');
            }
        #endif
    #endif
} 


void printHeader(void)
{
    #ifdef USE_DISPLAY
        display.clear();
        display.setCursor(COL_0, HEADER_ROW);
        display.print(F("LMIC-node"));
        #ifdef ABP_ACTIVATION
            display.drawString(ABPMODE_COL, HEADER_ROW, "ABP");
        #endif
        #ifdef CLASSIC_LMIC
            display.drawString(CLMICSYMBOL_COL, HEADER_ROW, "*");
        #endif
        display.drawString(COL_0, DEVICEID_ROW, deviceId);
        display.setCursor(COL_0, INTERVAL_ROW);
        display.print(F("Interval:"));
        display.print(doWorkIntervalSeconds);
        display.print("s");
    #endif

    #ifdef USE_SERIAL
        serial.println(F("\n\nLMIC-node\n"));
        serial.print(F("Device-id:     "));
        serial.println(deviceId);            
        serial.print(F("LMIC library:  "));
        #ifdef MCCI_LMIC  
            serial.println(F("MCCI"));
        #else
            serial.println(F("Classic [Deprecated]")); 
        #endif
        serial.print(F("Activation:    "));
        #ifdef OTAA_ACTIVATION  
            serial.println(F("OTAA"));
        #else
            serial.println(F("ABP")); 
        #endif
        #if defined(LMIC_DEBUG_LEVEL) && LMIC_DEBUG_LEVEL > 0
            serial.print(F("LMIC debug:    "));  
            serial.println(LMIC_DEBUG_LEVEL);
        #endif
        serial.print(F("Interval:      "));
        serial.print(doWorkIntervalSeconds);
        serial.println(F(" seconds"));
        if (activationMode == ActivationMode::OTAA)
        {
            serial.println();
        }
    #endif
}     


#ifdef ABP_ACTIVATION
    void setAbpParameters(dr_t dataRate = DefaultABPDataRate, s1_t txPower = DefaultABPTxPower) 
    {
        // 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 (0x1, DEVADDR, nwkskey, appskey);
        #else
            // If not running an AVR with PROGMEM, just use the arrays directly
            LMIC_setSession (0x1, 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.
            LMIC_setupChannel(0, 868100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
            LMIC_setupChannel(1, 868300000, DR_RANGE_MAP(DR_SF12, DR_SF7B), BAND_CENTI);      // g-band
            LMIC_setupChannel(2, 868500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
            LMIC_setupChannel(3, 867100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
            LMIC_setupChannel(4, 867300000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
            LMIC_setupChannel(5, 867500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
            LMIC_setupChannel(6, 867700000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
            LMIC_setupChannel(7, 867900000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);      // g-band
            LMIC_setupChannel(8, 868800000, DR_RANGE_MAP(DR_FSK,  DR_FSK),  BAND_MILLI);      // g2-band
            // 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.
        #elif defined(CFG_us915) || defined(CFG_au915)
            // NA-US and AU channels 0-71 are configured automatically
            // but only one group of 8 should (a subband) should be active
            // TTN recommends the second sub band, 1 in a zero based count.
            // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
            LMIC_selectSubBand(1);
        #elif defined(CFG_as923)
            // Set up the channels used in your country. Only two are defined by default,
            // and they cannot be changed.  Use BAND_CENTI to indicate 1% duty cycle.
            // LMIC_setupChannel(0, 923200000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);
            // LMIC_setupChannel(1, 923400000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_CENTI);

            // ... extra definitions for channels 2..n here
        #elif defined(CFG_kr920)
            // Set up the channels used in your country. Three are defined by default,
            // and they cannot be changed. Duty cycle doesn't matter, but is conventionally
            // BAND_MILLI.
            // LMIC_setupChannel(0, 922100000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
            // LMIC_setupChannel(1, 922300000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
            // LMIC_setupChannel(2, 922500000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);

            // ... extra definitions for channels 3..n here.
        #elif defined(CFG_in866)
            // Set up the channels used in your country. Three are defined by default,
            // and they cannot be changed. Duty cycle doesn't matter, but is conventionally
            // BAND_MILLI.
            // LMIC_setupChannel(0, 865062500, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
            // LMIC_setupChannel(1, 865402500, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);
            // LMIC_setupChannel(2, 865985000, DR_RANGE_MAP(DR_SF12, DR_SF7),  BAND_MILLI);

            // ... extra definitions for channels 3..n here.
        #endif

        // Disable link check validation
        LMIC_setLinkCheckMode(0);

        // TTN uses SF9 for its RX2 window.
        LMIC.dn2Dr = DR_SF9;

        // Set data rate and transmit power (note: txpow is possibly ignored by the library)
        LMIC_setDrTxpow(dataRate, txPower);    
    }
#endif //ABP_ACTIVATION

Second Half

void initLmic(bit_t adrEnabled = 1,
              dr_t abpDataRate = DefaultABPDataRate, 
              s1_t abpTxPower = DefaultABPTxPower) 
{
    // ostime_t timestamp = os_getTime();

    // Initialize LMIC runtime environment
    os_init();
    // Reset MAC state
    LMIC_reset();

    #ifdef ABP_ACTIVATION
        setAbpParameters(abpDataRate, abpTxPower);
    #endif

    // Enable or disable ADR (data rate adaptation). 
    // Should be turned off if the device is not stationary (mobile).
    // 1 is on, 0 is off.
    LMIC_setAdrMode(adrEnabled);

    if (activationMode == ActivationMode::OTAA)
    {
        #if defined(CFG_us915) || defined(CFG_au915)
            // NA-US and AU channels 0-71 are configured automatically
            // but only one group of 8 should (a subband) should be active
            // TTN recommends the second sub band, 1 in a zero based count.
            // https://github.com/TheThingsNetwork/gateway-conf/blob/master/US-global_conf.json
            LMIC_selectSubBand(1); 
        #endif
    }

    // Relax LMIC timing if defined
    #if defined(LMIC_CLOCK_ERROR_PPM)
        uint32_t clockError = 0;
        #if LMIC_CLOCK_ERROR_PPM > 0
            #if defined(MCCI_LMIC) && LMIC_CLOCK_ERROR_PPM > 4000
                // Allow clock error percentage to be > 0.4%
                #define LMIC_ENABLE_arbitrary_clock_error 1
            #endif    
            clockError = (LMIC_CLOCK_ERROR_PPM / 100) * (MAX_CLOCK_ERROR / 100) / 100;
            LMIC_setClockError(clockError);
        #endif

        #ifdef USE_SERIAL
            serial.print(F("Clock Error:   "));
            serial.print(LMIC_CLOCK_ERROR_PPM);
            serial.print(" ppm (");
            serial.print(clockError);
            serial.println(")");            
        #endif
    #endif

    #ifdef MCCI_LMIC
        // Register a custom eventhandler and don't use default onEvent() to enable
        // additional features (e.g. make EV_RXSTART available). User data pointer is omitted.
        LMIC_registerEventCb(&onLmicEvent, nullptr);
    #endif
}


#ifdef MCCI_LMIC 
void onLmicEvent(void *pUserData, ev_t ev)
#else
void onEvent(ev_t ev) 
#endif
{
    // LMIC event handler
    ostime_t timestamp = os_getTime(); 

    switch (ev) 
    {
#ifdef MCCI_LMIC
        // Only supported in MCCI LMIC library:
        case EV_RXSTART:
            // Do not print anything for this event or it will mess up timing.
            break;

        case EV_TXSTART:
            setTxIndicatorsOn();
            printEvent(timestamp, ev);            
            break;               

        case EV_JOIN_TXCOMPLETE:
        case EV_TXCANCELED:
            setTxIndicatorsOn(false);
            printEvent(timestamp, ev);
            break;               
#endif
        case EV_JOINED:
            setTxIndicatorsOn(false);
            printEvent(timestamp, ev);
            printSessionKeys();

            // Disable link check validation.
            // Link check validation is automatically enabled
            // during join, but because slow data rates change
            // max TX size, it is not used in this example.                    
            LMIC_setLinkCheckMode(0);

            // The doWork job has probably run already (while
            // the node was still joining) and have rescheduled itself.
            // Cancel the next scheduled doWork job and re-schedule
            // for immediate execution to prevent that any uplink will
            // have to wait until the current doWork interval ends.
            os_clearCallback(&doWorkJob);
            os_setCallback(&doWorkJob, doWorkCallback);
            break;

        case EV_TXCOMPLETE:
            // Transmit completed, includes waiting for RX windows.
            setTxIndicatorsOn(false);   
            printEvent(timestamp, ev);
            printFrameCounters();

            // Check if downlink was received
            if (LMIC.dataLen != 0 || LMIC.dataBeg != 0)
            {
                uint8_t fPort = 0;
                if (LMIC.txrxFlags & TXRX_PORT)
                {
                    fPort = LMIC.frame[LMIC.dataBeg -1];
                }
                printDownlinkInfo();
                processDownlink(timestamp, fPort, LMIC.frame + LMIC.dataBeg, LMIC.dataLen);                
            }
            break;     
          
        // Below events are printed only.
        case EV_SCAN_TIMEOUT:
        case EV_BEACON_FOUND:
        case EV_BEACON_MISSED:
        case EV_BEACON_TRACKED:
        case EV_RFU1:                    // This event is defined but not used in code
        case EV_JOINING:        
        case EV_JOIN_FAILED:           
        case EV_REJOIN_FAILED:
        case EV_LOST_TSYNC:
        case EV_RESET:
        case EV_RXCOMPLETE:
        case EV_LINK_DEAD:
        case EV_LINK_ALIVE:
#ifdef MCCI_LMIC
        // Only supported in MCCI LMIC library:
        case EV_SCAN_FOUND:              // This event is defined but not used in code 
#endif
            printEvent(timestamp, ev);    
            break;

        default: 
            printEvent(timestamp, "Unknown Event");    
            break;
    }
}


static void doWorkCallback(osjob_t* job)
{
    // Event hander for doWorkJob. Gets called by the LMIC scheduler.
    // The actual work is performed in function processWork() which is called below.

    ostime_t timestamp = os_getTime();
    #ifdef USE_SERIAL
        serial.println();
        printEvent(timestamp, "doWork job started", PrintTarget::Serial);
    #endif    

    // Do the work that needs to be performed.
    processWork(timestamp);

    // This job must explicitly reschedule itself for the next run.
    ostime_t startAt = timestamp + sec2osticks((int64_t)doWorkIntervalSeconds);
    os_setTimedCallback(&doWorkJob, startAt, doWorkCallback);    
}


lmic_tx_error_t scheduleUplink(uint8_t fPort, uint8_t* data, uint8_t dataLength, bool confirmed = false)
{
    // This function is called from the processWork() function to schedule
    // transmission of an uplink message that was prepared by processWork().
    // Transmission will be performed at the next possible time

    ostime_t timestamp = os_getTime();
    printEvent(timestamp, "Packet queued");

    lmic_tx_error_t retval = LMIC_setTxData2(fPort, data, dataLength, confirmed ? 1 : 0);
    timestamp = os_getTime();

    if (retval == LMIC_ERROR_SUCCESS)
    {
        #ifdef CLASSIC_LMIC
            // For MCCI_LMIC this will be handled in EV_TXSTART        
            setTxIndicatorsOn();  
        #endif        
    }
    else
    {
        String errmsg; 
        #ifdef USE_SERIAL
            errmsg = "LMIC Error: ";
            #ifdef MCCI_LMIC
                errmsg.concat(lmicErrorNames[abs(retval)]);
            #else
                errmsg.concat(retval);
            #endif
            printEvent(timestamp, errmsg.c_str(), PrintTarget::Serial);
        #endif
        #ifdef USE_DISPLAY
            errmsg = "LMIC Err: ";
            errmsg.concat(retval);
            printEvent(timestamp, errmsg.c_str(), PrintTarget::Display);
        #endif         
    }
    return retval;    
}


//  β–ˆ β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„ β–ˆβ–€β–€   β–ˆβ–€β–„ β–ˆβ–€β–€ β–ˆβ–€β–€ β–€β–ˆβ–€ β–ˆβ–€β–ˆ
//  β–ˆ β–ˆ β–€β–€β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆ   β–ˆ β–ˆ β–ˆ β–ˆ β–ˆβ–€β–€   β–ˆβ–€β–„ β–ˆβ–€β–€ β–ˆ β–ˆ  β–ˆ  β–ˆ β–ˆ
//  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€   β–€β–€β–€ β–€β–€β–€ β–€β–€  β–€β–€β–€   β–€β–€  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€


static volatile uint16_t counter_ = 0;

uint16_t getCounterValue()
{
    // Increments counter and returns the new value.
    delay(50);         // Fake this takes some time
    return ++counter_;
}

void resetCounter()
{
    // Reset counter to 0
    counter_ = 0;
}


void processWork(ostime_t doWorkJobTimeStamp)
{
    // This function is called from the doWorkCallback()
    // callback function when the doWork job is executed.

    // Uses globals: payloadBuffer and LMIC data structure.

    // This is where the main work is performed like
    // reading sensor and GPS data and schedule uplink
    // messages if anything needs to be transmitted.
    // Skip processWork if using OTAA and still joining.
    
    while (Serial2.available() > 0)
        if (gps.encode(Serial2.read()))
        {
            LatRAW = gps.location.lat();
            LonRAW = gps.location.lng();
            AltRAW = gps.altitude.feet();
            HourRAW = gps.time.hour();
            MinRAW = gps.time.minute();
            SecRAW = gps.time.second();
        }
    String latStr = String(gps.location.lat(), 7);
    String lonStr = String(gps.location.lng(), 7);
    String altStr = String(gps.altitude.feet(), 2);
    double latd = std::stod(latStr.c_str());
    double lond = std::stod(lonStr.c_str());
    double altd = std::stod(altStr.c_str());
    uint16_t MinRead = (MinRAW * 1);
    int16_t SensorRead = ads.getLastConversionResults();
    int LatRead = ((latd) * (conv));
    unsigned int LonRead = ((lond) * (conv));
    int AltRead = ((altd) * (conv2));


    if (LMIC.devaddr != 0)
    {

        ostime_t timestamp = os_getTime();

        if (LMIC.opmode & OP_TXRXPEND)
        {
            // TxRx is currently pending, do not send.
#ifdef USE_SERIAL
            printEvent(timestamp, "Uplink not scheduled because TxRx pending", PrintTarget::Serial);
#endif
            // #ifdef USE_DISPLAY
            //       printEvent(timestamp, "UL not scheduled", PrintTarget::Display);
            // #endif
        }
        else
        {
            // Prepare uplink payload.
            // Serial.print("String: ");
            // Serial.println(latStr);
            uint8_t fPort = 10;

            payloadBuffer[0] = SensorRead >> 8;
            payloadBuffer[1] = SensorRead & 0xFF;

            payloadBuffer[2] = Percentage >> 8;
            payloadBuffer[3] = Percentage & 0xFF;

            payloadBuffer[4] = ((LatRead & 0xFF000000) >> 24);
            payloadBuffer[5] = ((LatRead & 0x00FF0000) >> 16);
            payloadBuffer[6] = ((LatRead & 0x0000FF00) >> 8);
            payloadBuffer[7] = (LatRead & 0X000000FF);

            payloadBuffer[8] = ((LonRead & 0xFF000000) >> 24);
            payloadBuffer[9] = ((LonRead & 0x00FF0000) >> 16);
            payloadBuffer[10] = ((LonRead & 0x0000FF00) >> 8);
            payloadBuffer[11] = (LonRead & 0X000000FF);

            payloadBuffer[12] = AltRead >> 8;
            payloadBuffer[13] = AltRead & 0xFF;

            uint8_t payloadLength = 14;

            scheduleUplink(fPort, payloadBuffer, payloadLength);
        }
    }
}  
 

void processDownlink(ostime_t txCompleteTimestamp, uint8_t fPort, uint8_t *data, uint8_t dataLength)
{

    for (int i = 0; i < dataLength; i++)
    {
        // Convert the byte to a decimal number.
        int decimalValue = (int)data[i];
        // Serial.print("This is the raw message: ");
        // Serial.print(decimalValue);
        // Serial.println();
        switch (i)
        {
        case 0:
            Sensitivity = decimalValue;
            break;
        case 1:
            mode = decimalValue;
            break;
        }
    }


}



//  β–ˆ β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„ β–ˆβ–€β–€   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„
//  β–ˆ β–ˆ β–€β–€β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆ   β–ˆ β–ˆ β–ˆ β–ˆ β–ˆβ–€β–€   β–ˆβ–€β–€ β–ˆ β–ˆ β–ˆ β–ˆ
//  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€   β–€β–€β–€ β–€β–€β–€ β–€β–€  β–€β–€β–€   β–€β–€β–€ β–€ β–€ β–€β–€ 


void setup() 
{
    // boardInit(InitType::Hardware) must be called at start of setup() before anything else.
    bool hardwareInitSucceeded = boardInit(InitType::Hardware);

    #ifdef USE_DISPLAY 
        initDisplay();
    #endif

    #ifdef USE_SERIAL
        initSerial(MONITOR_SPEED, WAITFOR_SERIAL_S);
    #endif    

    boardInit(InitType::PostInitSerial);

    #if defined(USE_SERIAL) || defined(USE_DISPLAY)
        printHeader();
    #endif

    if (!hardwareInitSucceeded)
    {   
        #ifdef USE_SERIAL
            serial.println(F("Error: hardware init failed."));
            serial.flush();            
        #endif
        #ifdef USE_DISPLAY
            // Following mesage shown only if failure was unrelated to I2C.
            display.setCursor(COL_0, FRMCNTRS_ROW);
            display.print(F("HW init failed"));
        #endif
        abort();
    }

    initLmic();

//  β–ˆ β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„ β–ˆβ–€β–€   β–ˆβ–€β–„ β–ˆβ–€β–€ β–ˆβ–€β–€ β–€β–ˆβ–€ β–ˆβ–€β–ˆ
//  β–ˆ β–ˆ β–€β–€β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆ   β–ˆ β–ˆ β–ˆ β–ˆ β–ˆβ–€β–€   β–ˆβ–€β–„ β–ˆβ–€β–€ β–ˆ β–ˆ  β–ˆ  β–ˆ β–ˆ
//  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€   β–€β–€β–€ β–€β–€β–€ β–€β–€  β–€β–€β–€   β–€β–€  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€

    // Place code for initializing sensors etc. here.
    Serial2.begin(9600, SERIAL_8N1, 2, 17);
    Serial.begin(115200);
    pinMode(12, OUTPUT);
    ads.begin();
    ads.setGain(GAIN_FOUR);
    if (!ads.begin())
    {
        Serial.println("Failed to initialize ADS.");
        while (1)
             ;
    }
    ads.startComparator_SingleEnded(0, 1000);
    digitalWrite(12, HIGH);


//  β–ˆ β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„ β–ˆβ–€β–€   β–ˆβ–€β–€ β–ˆβ–€β–ˆ β–ˆβ–€β–„
//  β–ˆ β–ˆ β–€β–€β–ˆ β–ˆβ–€β–€ β–ˆβ–€β–„   β–ˆ   β–ˆ β–ˆ β–ˆ β–ˆ β–ˆβ–€β–€   β–ˆβ–€β–€ β–ˆ β–ˆ β–ˆ β–ˆ
//  β–€β–€β–€ β–€β–€β–€ β–€β–€β–€ β–€ β–€   β–€β–€β–€ β–€β–€β–€ β–€β–€  β–€β–€β–€   β–€β–€β–€ β–€ β–€ β–€β–€ 

    if (activationMode == ActivationMode::OTAA)
    {
        LMIC_startJoining();
    }

    // Schedule initial doWork job for immediate execution.
    os_setCallback(&doWorkJob, doWorkCallback);
}


void loop() 
{
    os_runloop_once();
}

If you want us to follow along, perhaps break up the text in to paragraphs. Under the post is a pencil icon that will allow you to edit it.

I think the answer is in your comment:

But my C skills are poor. There are a lot of ifdef serial in the code. So those sections of code only run when you open the serial monitor. (But my C skills are not great)

1 Like

I’m not that skilled in C either, but you may be on to something. When I just tried to disable the

USE_SERIAL line on the Heltec WiFi LoRa 32 V2 (ESP32). section of the platformio.ini file, I received the following error.

src/LMIC-node.cpp: In function β€˜void onLmicEvent(void*, ev_t)’:
src/LMIC-node.cpp:578:13: error: β€˜setTxIndicatorsOn’ was not declared in this scope
setTxIndicatorsOn();

This is because I already had the USE_DISPLAY line commented out (disabled) because I thought I needed to save power. When I uncommented the DISPLAY line, the program builds just fine. I am going to try it again with the battery in the morning. I know I will need to change the address of the ADS1115 before i can do anything.

What languages do you both know to be able to program in?

I am used to Java and python from school ( I’m still in school). The only C experience I have is from tinkering with Arduino.

So i disabled the β€œUSE_SERIAL” line on the β€œheltec_wifi_lora_32_v2” section of the platformio.ini file. Because of the β€œIfdef” lines in the main program I assumed that the program required serial connection if I left it enabled. I just left the Display section enabled but my board still does not join or send uplinks by battery power alone. The battery is a 3.7v 9900 mAh 18650 rechargeable battey.

So you speak Spanish (Java), C is just Italian with less fluff. Python is also just another procedural language like them, with indents which is insane.

The ONLY bit of C that I think that is relatively unique in terms of comprehension are pointers and these aren’t in play here.

So you may not be fluent in C, but the drinking songs for C, Java & Python are all universal, it’s more a state of mind as to whether you think you can or you can’t. And if you think you can’t, then you can’t, because your thinking is a limiting factor.

And don’t forget, if you think you are thinking, then you exist, at least in your own mind.

I suspect there is something hinky going on with the board design as I’ve already looked at the code & can’t see why it would normally grind to a half if there is nothing plugged in to the serial port. I’ll have a scout round for clues.

This is likely to a fake to be sold to those that need to fumigate their lungs so anything could be up with it - including an issue with current draw. A real 18650 runs to around 3,300mAh. Best place to get a pile of good ones is from a laptop battery pack. Or buy from a reputable seller. Not eBay.

3 Likes

So I kept at and fixed it. It seems that it was a battery issue. No matter what I did it was not working, until I powered it via GPIO and used a boost converter to increase my 3.7V battery to deliver a stable 5V. Then it worked perfectly.

2 Likes