PMTK command to L80 GPS

Hello,

I have about finished my Arduino UNO High Altitude Balloon tracking device, it is based on the L80-M39 GPS from Quectel, it transmits with the LoRa Bee.

I am aware I have to enable the balloon mode for the GPS to keep on working over 18km altitude, so I have done it as shown below.

BUT ... How can I be sure it is really enabled?

It is not cheap to send a balloon, and I would hate to find out the command did not work after the flight....

Can someone help?

TinyGPSPlus gps;
SoftwareSerial ss(3, 4); 

#define PMTK_SET_NMEA_886_PMTK_FR_MODE  "$PMTK886,3*2B" 


void balloonModeGPS() {
    if ((ss.available())) {
      ss.println(PMTK_SET_NMEA_886_PMTK_FR_MODE);
      Serial.println("GPS balloon mode configured on L80");
    }
    else{
      Serial.println("Error in GPS, balloon mode not configured");
      }
}

Can someone help?

Not really, you have only posted a minute fragment of the code you are using.

And the minute fragment you posted achieves nothing meaningful that I can see.

In that if the GPS has a problem or is otherwise not working, you have no way of knowing.

Perhaps the detail of that is in the rest of the code ?

Well, full code doesn't fit. Here it is with some content removed

#include <lmic.h>
#include <dht.h>
#include <hal/hal.h>
#include <SPI.h>
#include <SoftwareSerial.h>
#include <TinyGPS++.h>

TinyGPSPlus gps;
SoftwareSerial ss(3, 4); // Arduino RX, TX to connect to GPS module TX, RX


//Command activation Balloon mode
#define PMTK_SET_NMEA_886_PMTK_FR_MODE  "$PMTK886,3*2B" 

static void smartdelay(unsigned long ms);
unsigned int GPS_count = 0;     //To count GPS measurements
unsigned int Temp_count = 0;    // To count DHT measurements
unsigned int Sats = 0;          // Number of Satellites locked
unsigned int Speed = 0;         // Speed of device
unsigned int Heading = 0;       // Heading of the device

float flat,flon,falt = 0;

// Cayenne formatted data
static uint8_t mydata[] = {0x01,0x88,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x67,0x00,0x00,0x03,0x68,0x00};     


// Pin mapping
const lmic_pinmap lmic_pins = {
    .nss = 10,
    .rxtx = LMIC_UNUSED_PIN,
    .rst = 9,
    .dio = {2, 6, 7},
};

void do_send(osjob_t* j){
    // Check if there is not a current TX/RX job running
    if (LMIC.opmode & OP_TXRXPEND) {
        Serial.println("OP_TXRXPEND, not sending");
    } else {
        GPSRead();
        dhtTem();
//        light();
        printdata();
        // Prepare upstream data transmission at the next possible time.
        //  LMIC_setTxData2(1,datasend,sizeof(datasend)-1,0);
        LMIC_setTxData2(1, mydata, sizeof(mydata), 0);
        Serial.println("Packet queued");
        Serial.print("LMIC.freq:");
        Serial.println(LMIC.freq);
        Serial.println("Receive data:");  
    } 
    // Next TX is scheduled after TX_COMPLETE event.
}

void onEvent (ev_t ev) {
    Serial.print(os_getTime());
    Serial.print(": ");
    Serial.println(ev);
    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"));
            break;
        case EV_RFU1:
            Serial.println(F("EV_RFU1"));
            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)"));
            Serial.println(F(""));
            if(LMIC.dataLen) {
                // data received in rx slot after tx
                Serial.print(F("Data Received: "));
                Serial.write(LMIC.frame+LMIC.dataBeg, LMIC.dataLen);
                Serial.println();
            }
            // 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;
         default:
            Serial.println(F("Unknown event"));
            break;
    }
}

void balloonModeGPS() {
    if ((ss.available())) {
      ss.println(PMTK_SET_NMEA_886_PMTK_FR_MODE);
      Serial.println("GPS balloon mode configured on L80");
    }
    else{
      Serial.println("Error in GPS, balloon mode not configured");
      }
}

void setup() {
     // initialize digital pin  as an output.
   
    Serial.begin(9600);
    ss.begin(9600);
    while(!Serial);
    Serial.println(F("LoRa GPS Example---- "));
    Serial.println(F("Connect to TTN"));
    #ifdef VCC_ENABLE
    // For Pinoccio Scout boards
    pinMode(VCC_ENABLE, OUTPUT);
    digitalWrite(VCC_ENABLE, HIGH);
    delay(1000);
    #endif

    // LMIC init
    os_init();
    // Reset the MAC state. Session and pending data transfers will be discarded.
    LMIC_reset();
    /*LMIC_setClockError(MAX_CLOCK_ERROR * 1/100);
     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
    
    // 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 seems to be ignored by the library)
    LMIC_setDrTxpow(DR_SF9,18);

    // Send balloon mode command to L80
    balloonModeGPS();

    // Start job
    do_send(&sendjob);
}
void GPSRead()
{
  unsigned long age;

  flat=gps.location.lat();
  flon=gps.location.lng();
  falt=gps.altitude.meters();  
  
  int32_t lat = flat * 10000;
  int32_t lon = flon * 10000;
  int32_t alt = falt * 100;

  mydata[2] = lat >> 16;
  mydata[3] = lat >> 8;
  mydata[4] = lat;
  mydata[5] = lon >> 16;
  mydata[6] = lon >> 8;
  mydata[7] = lon;
  mydata[8] = alt >> 16;
  mydata[9] = alt >> 8;
  mydata[10] = alt;
}
void dhtTem()
{
     ....
}

void printdata(){
       ...
}

static void smartdelay(unsigned long ms)
{
  unsigned long start = millis();
  do 
  {
    while (ss.available())
    {
      gps.encode(ss.read());
    }
  } while (millis() - start < ms);
}


void loop() {
    os_runloop_once(); 
}

There is not enough of the code to make sense of, you can post long code as an attachement.

But from your first post the code is saying;

If there is possibly good stuff or just random noise being read on the GPS pins, assume the GPS is working (when it might not be) and send a command to configure balloon mode and assume its worked OK on the GPS that might not be there.

The sending of the set balloon mode will elicit a response from the GPS, you need to check for it.

The sending of the set balloon mode will elicit a response from the GPS, you need to check for it.

Good one.
I found this in the specs:

Data Field:

$PMTK886,CmdType
Example:
$PMTK886,3*2B<CR><LF>

Response:
$PMTK001,886,3*36

I now wrote the mode setting as follows to grab the response:

//Command activation Balloon mode
#define PMTK_SET_NMEA_886_PMTK_FR_MODE  "$PMTK886,3*2B\r\n"


void balloonModeGPS() {
    boolean done=0;
    
    while (!done) {
      if (ss.available()) {
        ss.println(PMTK_SET_NMEA_886_PMTK_FR_MODE);
        Serial.println("GPS balloon mode sent to L80");
        done=1;
      }
    }
    delay (250);
    while (ss.available()) {
        Serial.print(ss.read());
    }
  Serial.println(F(""));
}
The log is as follows:

14:58:06.494 -> LoRa GPS Example---- 
14:58:06.541 -> Connect to TTN
14:58:06.541 -> RXMODE_RSSI
14:58:07.196 -> GPS balloon mode sent to L80
14:58:07.444 -> 36160735398154202957757515044484619319313014981301701301381941309898981141695256131036718086847144484648484484444477444846484844784448464848
14:58:07.544 -> __________________________________________________

Not really what I was expecting. I always get the same sequence, so it is not random numbers.

I attached the full code.

The reply will be mixed in with the normal GPS output, you need write the code so you can find it.

You will have a heap of trouble mixing SoftwareSerial and standard serial prints to the serial monitor, NeoSWserial does not have the same problems.

You dont appear to be making any checks as to the validity of the data being read from the GPS and transmitted.

As well as legal duty cycle issues, you need to be sure the transmissions do not exceed the TTN fair access limit of 30 seconds of air time per day.

Did you intend to publish the keys used ?

Thanks very good points...

Working on sorting out the GPS response, making progress but still could not find any PMTK message, only the GPs so far.
I make no tests on the data indeed, I never had a problem.
I transmit a lot less than 30s per day right now, I keep it on air only for testing code or back office.

What do you mean by:

You will have a heap of trouble mixing SoftwareSerial and standard serial prints to the serial monitor, NeoSWserial does not have the same problems.

nilaim:
I make no tests on the data indeed, I never had a problem.

Then you need to.

The checks for valid data, and location, are simple enough, why would you want to send a location that was not valid ?

SoftwareSerial can be made to work reliably in an application like this, but it involves a great deal of care in how you use it. I first came across the issue a few years back around the time TTN started up, although no-one thought of using it for balloon tracking at that time, there were only a handful of gateways around anyway.

I recently wrote the problem up;

SoftWare Serial Problems

VICTORY!

18:42:24.137 -> ⸮LoRa GPS Example----
18:42:24.184 -> Connect to TTN
18:42:24.184 -> RXMODE_RSSI
18:42:24.732 -> GPS balloon mode sent to L80
18:42:24.779 -> $GPR⸮⸮⸮⸮⸮r⸮⸮&% ⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮b⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮b⸮⸮b⸮b⸮⸮r⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮4⸮A⸮⸮
⸮⸮⸮
18:42:24.865 -> $GPVTG,0.00,T,,M,0.00,N,0.00,K,N32
18:42:24.906 -> $GPGGA,000000.800,,,,,0,0,,,M,,M,,40
18:42:24.946 -> $GP⸮GSA,A,1,,,,,,,,,,,,,,,1E
18:42:24.982 -> $GPGSV,1,1,00
79
18:42:24.982 -> $GPGLL,,,,,000000.800,V,N
72
18:42:25.025 -> $⸮GPTXT,01,01,02,ANTSTATUS=OPEN
2B
18:42:25.065 -> $PMTK001,886,336
18:42:25.065 -> ⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮$GPRMC,000001.800,V,,,,,0.00,0.00,060180,,,N
4B
18:42:25.905 -> $GPVTG,0.00,T,,
18:42:25.905 -> __________________________________________________

Whats happening here;

18:42:24.137 -> ⸮LoRa GPS Example----
18:42:24.779 -> $GPR⸮⸮⸮⸮⸮r⸮⸮&% ⸮
⸮⸮⸮⸮⸮⸮⸮⸮⸮b⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮b⸮⸮b⸮b⸮⸮r⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮⸮4⸮A⸮⸮
⸮⸮⸮
18:42:24.946 -> $GP⸮GSA,A,1,,,,,,,,,,,,,,,1E
18:42:25.025 -> $⸮GPTXT,01,01,02,ANTSTATUS=OPEN
2B

Why the '⸮' characters ?

It comes directly from the GPS guts.
There is a line of them at the end of each sequence.

This is what it looks like.

#define PMTK_SET_NMEA_886_PMTK_FR_MODE  "$PMTK886,3*2B\r\n"  // For balloon mode

void balloonModeGPS() {
    unsigned long ms=1000;
    unsigned long t;
    bool done,ack=false;
    char c;
    String str="";

    while (!ack) {
      done=false;
      // Send the PMTK command
      while (!done) {
        if (ss.available()) {
          done=true;
          ack=false;
          Serial.println("GPS balloon mode sent to L80.");
          ss.print(PMTK_SET_NMEA_886_PMTK_FR_MODE);
        }
      }
      // Check the acknowledgement of the command
      t=millis();
      while ((ss.available() || millis()-t < ms) && !ack) {
        c=ss.read();
        str.concat(c);
          if ((str.indexOf("$PMTK001,886,3*36"))!=-1) {
          ack=true;
          Serial.println(F("Balloon mode acknowledged by L80."));
        }
      }
      Serial.println(str);
    }
}