Liquid level sensor with GPS and Sigfox

Hi there,

I could really need some help to discover why my code is not giving me GPS coordinates.
The goal is to check every hour if liquid level is too high, using a bilge liquid level sensor. High means level is too high and it will send the data to Sigfox backend. The hourly trigger comes from a RTC (DS3231) with an alarm every via the external interrupt pin on ‘TheAirboard’ (Arduino Fio compatible). So far everything works fine. However I also need to send the GPS coordinates every hour. I’m using SoftwareSerial to listen to the GPS coordinates, but they won’t show. It only says INVALID. This is normal for the first 5 results (tested with example codes form TinyGPS++). Obviously I’m doing something wrong with the logic, but I can’t find what’s the main problem. According to isListening() the Arduino is listening to this port.

The ‘challenge’ is a bit below "if (clock.isAlarm1()) " in the code

Any help is much appreciated!

Best,
Freek

Code:

#include <Wire.h>
#include <DS3231.h>              
#include <avr/sleep.h>
#include <EEPROM.h>                                 
#include <TheAirBoard.h>
#include <TinyGPS++.h>
#include <SoftwareSerial.h>

TheAirBoard board;

//Pins for GPS board
static const int RXPin = 13, TXPin = 12;                            
static const uint32_t GPSBaud = 9600;

// The TinyGPS++ object
TinyGPSPlus gps;
// The serial connection to the GPS device
SoftwareSerial ss(RXPin, TXPin);

int wakePin = 3;                 // pin used for waking up  
int led=9;

#define SENSOR 10
#define BLUE    6     // The Airboard BLUE LED
int var = 0;              //helping var for one time running Sigfox test at startup
int val;              //Input value water level sensor
int sensor;           //Input value water level sensor for takeReading
int counter;          //Counter value for reducing false positives
int batstate;         //Charge state of battery

/** the current address in the EEPROM (i.e. which byte we're going to write to next) **/
int addr = 0;

char buffer[9];
byte value;

DS3231 clock;
RTCDateTime dt;

void setup()
{
  Serial.begin(115200);
  ss.begin(GPSBaud);
  
  // Initialize DS3231
  Serial.println("Initialize DS3231");
  delay(100);
  clock.begin();
  pinMode(wakePin, INPUT_PULLUP);  
  pinMode(led, OUTPUT);  
  pinMode(SENSOR, INPUT); //Set sensor pin
  pinMode(BLUE, OUTPUT);                             // initialize the BLUE LED pin as an output
  
  // Disarm alarms and clear alarms for this example, because alarms is battery backed.
  // Under normal conditions, the settings should be reset after power and restart microcontroller.
  clock.armAlarm1(false);
  clock.clearAlarm1();
  clock.armAlarm2(false);
  clock.clearAlarm2();
  
  // Set Alarm1 - Every 20s in each minute
  // setAlarm1(Date or Day, Hour, Minute, Second, Mode, Armed = true)
  clock.setAlarm1(0, 0, 0, 20, DS3231_MATCH_S);
  // Set Alarm - Every 01m:25s in each hour
  // setAlarm1(Date or Day, Hour, Minute, Second, Mode, Armed = true)
  //clock.setAlarm1(0, 0, 15, 0, DS3231_MATCH_M_S); //COMMENTED
  // Set alarm for daily 'alive' check
  // setAlarm1(Date or Day, Hour, Minute, Mode, Armed = true)
  clock.setAlarm2(0, 17, 15, DS3231_MATCH_M); //COMMENTED
  attachInterrupt(1, wakeUpNow, LOW);

  // Check alarm settings
  checkAlarms();

  //Show last reading form EEPROM memory
  value = EEPROM.read(addr);
  
}
void checkAlarms()
{
  RTCAlarmTime a1;  
  RTCAlarmTime a2;

  if (clock.isArmed1())
  {
    a1 = clock.getAlarm1();

    Serial.print("Alarm1 is triggered ");
    switch (clock.getAlarmType1())
    {
      
      case DS3231_MATCH_S:
        Serial.print("when seconds match: ");
        Serial.println(clock.dateFormat("__ __:__:s", a1));
        delay(100);
        break;
        default: 
        Serial.println("UNKNOWN RULE");
        break;
    }
  } 
} 

void wakeUpNow() {  
}  

void sleepNow() {  
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);    // sleep mode is set here  
    sleep_enable();                         // enables the sleep bit in the mcucr register  
    attachInterrupt(0,wakeUpNow, FALLING);  // use interrupt 0 (pin 2) and run function  
    //Serial.println("Woke up");
    sleep_mode();                           // here the device is actually put to sleep!!  
    // THE PROGRAM CONTINUES FROM HERE AFTER WAKING UP  
   

    sleep_disable();         // first thing after waking from sleep: disable sleep...  
    detachInterrupt(0);      // disables interrupt 0 on pin 2 so the wakeUpNow code will not be executed during normal running time.  
}  

void loop() {

  while(var < 1){
    //Send start message
    delay(1000);
    analogWrite(BLUE, 1);  
    sprintf(buffer, "AT$SS=%.2X", 3);              // acquire and format analog data
    //Serial.println("AT$SS=01CAFE");
    Serial.println(buffer);                          // send message
    delay(6000); 
    digitalWrite(BLUE, 0);                           // reset communication indicator
    var ++;
  }
  val = digitalRead(SENSOR);
  
  dt = clock.getDateTime();

  // Send daily alive message 
  if (clock.isAlarm2()) {    
    //Start communication steps
    Serial.println("Send daily alive message");
    analogWrite(BLUE, 1);                            // set communication indicator
    float vbat = board.batteryChk();
    
    Serial.println(vbat);
    if (vbat < 3.25) {
      batstate = 5;                                  //Code 5 means low battery  
    }
    else {
      batstate = 4;                                  //Code 5 means battery OK
    }
    sprintf(buffer, "AT$SS=%.2X", batstate);                // acquire and format analog data
    Serial.println(buffer);                          // send message
    delay(6000);                                     // wait until end of transmission
    digitalWrite(BLUE, 0);                           // reset communication indicator
  }
  
  Serial.println(clock.dateFormat("d-m-Y H:i:s - l", dt));
  delay(100);
    
  // Call isAlarm1(false) if you want clear alarm1 flag manualy by clearAlarm1();
  if (clock.isAlarm1())  
  { 
    Serial.println("ALARM 1 TRIGGERED!");
    digitalWrite(led, HIGH);  
    delay(1000);  
    digitalWrite(led, LOW);

    //GPS communication serial
    if (ss.isListening()) {
      Serial.println("Port One is listening!"); 
    }
    for (int i=0; i <= 2; i++){    
    while (ss.available() > 0)
      if (gps.encode(ss.read()))
        delay(100);
    
        displayInfo();
    
    if (millis() > 5000 && gps.charsProcessed() < 10)
    {
      Serial.println(F("No GPS detected: check wiring."));
      while(true);
    }
  delay(1000);
  }
  //Show last reading form EEPROM memory
  value = EEPROM.read(addr);

  //Take sensor readings here
  if (val == HIGH) {
    //Serial.println("Sensor high");
  //Reduce false positives positiveEquals 10 seconds of measurement, needs more than 20 seconds water detected state
    delay(100);
    
    //takeReading();
    for (int i=0; i <= 11; i++){
      //analogWrite(PWMpin, i);
      sensor =  digitalRead(SENSOR); //Read input value
      if (sensor == HIGH) {
        counter ++;
      }
      delay(1000);
   } 
      
    if (counter >= 10) { 
      //Start communication steps
      
      if (value != 1) {
      analogWrite(BLUE, 1);                            // set communication indicator
      sprintf(buffer, "AT$SS=%.2X", 2);                // acquire and format analog data
      Serial.println(buffer);                          // send message
      delay(6000);                                     // wait until end of transmission
      digitalWrite(BLUE, 0);                           // reset communication indicator
      EEPROM.write(addr, 1);                           //Put last state in EEPROM memory (water detected)
      counter = 0;                                     //Set counter back to zero value
      //Need to go back to long sleep to prevent endless loop of notifications
      //sleepAftersend();
      }
    }
  } 
  else {
    //Put last state in EEPROM memory
    EEPROM.write(addr, 0); //No water detected this time
  } 
     
    Serial.println("Entering sleep"); 
    delay(100); // sleep function called here
    sleepNow(); 
  }
  
 delay(900);
}

void displayInfo()
{
  Serial.print(F("Location: ")); 
  if (gps.location.isValid())
  {
    Serial.print(gps.location.lat(), 6);
    Serial.print(F(","));
    Serial.print(gps.location.lng(), 6);
  }
  else
  {
    Serial.print(F("INVALID"));
  }

  Serial.println();
}

well they won't show because you don't ask for them, do you?

long lat, lon;
unsigned long fix_age;
gps.get_position(&lat, &lon, &fix_age);

Also this is going to drive sad results and not the way you want to read data from the GPS

      while (ss.available() > 0) if (gps.encode(ss.read())) delay(100);

you need to do something like this

while (true) {
if (ss.available())
  {
    if (gps.encode(ss.read())) break; // tells us we have received a valid GPS fix
  }
}
// here query it for position/course/time/etc. information.
long lat, lon;
unsigned long fix_age, time, date, speed, course;
unsigned long chars;
unsigned short sentences, failed_checksum;
 
// retrieves +/- lat/long in 100000ths of a degree
gps.get_position(&lat, &lon, &fix_age);
 
// time in hhmmsscc, date in ddmmyy
gps.get_datetime(&date, &time, &fix_age);
 
// returns speed in 100ths of a knot
speed = gps.speed();
 
// course in 100ths of a degree
course = gps.course();

// now you have your data

Is this inside?
Are you giving the GPS enough time to get a fix before you get the coordinates? (i.e., do you turn the gps board on and off to save power?)

Hi,

pls also ensure the speed of your GPS module is correct, traditionally the bps speed is 4800, but modern ones use 9600bps @t 1Hz rate

as J-M-L says, try to find when GPS has FIX their coordinates correctly using the code example as follow

float flat, flon;
unsigned long fix_age; // returns +- latitude/longitude in degrees
gps.f_get_position(&flat, &flon, &fix_age);
if (fix_age == TinyGPS::GPS_INVALID_AGE)
  Serial.println("No fix detected");                     //this may take a while
else if (fix_age > 5000)
  Serial.println("Warning: possible stale data!"); // older fixes
else
  Serial.println("Data is current.");                 // do the bussines

Thanks for the quick reply!
I think it’s not getting enough time to get a fix, since the loop continuously shows the current time till the alarm (currently every 20th second of the minute for debugging). The GPS only becomes active after the alarm.

If I use this code it stays forever in this loop, but it’s not giving a valid location when I ask for it in the code below. Maybe I’m doing something wrong.

while (true) {
if (ss.available())
  {
    if (gps.encode(ss.read())) break; // tells us we have received a valid GPS fix
  }
}

Do you have a suggestion how to keep polling for like 5 seconds till it gives a valid location?

Something like:

   //GPS communication serial
    //while (ss.available() > 0) if (gps.encode(ss.read())) delay(100);
    boolean x;
    while (ss.available() > 0) if (gps.encode(ss.read())) delay(100);
    do
    {
      delay(500);          // wait for location fix
      x = gps.location.isValid();  // check the sensors
    
    } while (x == false);
    displayInfo();    //Has access to location etc.
    while (ss.available() > 0)
      if (gps.encode(ss.read()))
        delay(100);
    
        displayInfo();

I think you meant:

    while (ss.available() > 0)
      if (gps.encode(ss.read())) {
        delay(100);
    
        displayInfo();
    }

This isn't Python. If you want to group statements you have to put brackets around them. If you has used the Tools->Auto Format feature of the IDE as requested in "How To Use This Forum" you would have had your faulty indentation corrected and that would have made your mistake more noticeable.

   while (ss.available() > 0)
      if (gps.encode(ss.read())) {
        delay(100);
    
        displayInfo();
    }

We just got a complete sentence. Lets now stick our head in the sand for 1/10 of the typical interval between sentences before using the data received. Hmmm. Why?

PaulS:
<…
We just got a complete sentence. Lets now stick our head in the sand for 1/10 of the typical interval between sentences before using the data received. Hmmm. Why?

Maybe wait for the cart to catch up to the ostrich?

 while (ss.available() > 0)
      if (gps.encode(ss.read()))
        delay(100);
    
        displayInfo();

this won't work. really.

you will empty the ss buffer faster than the slow 4800 or 9600 bps communication link with your GPS and the while will end because available() will say "nothing is available" and thus without the gps.encode having received all it's data

if you get stuck forever in this loop that I suggested

while (true) {
if (ss.available())
  {
    if (gps.encode(ss.read())) break; // tells us we have received a valid GPS fix
  }
}

then it means your GPS is not fully sending a valid fix and that you have a pb with your GPS settings.

Finally fixed this one. The use of wake up via external interrupt (alarm via RTC) was causing the trouble. Using a detachInterrupt directly after "if (clock.isAlarm1()) { //do stuff while awake... made it work. Before letting it go back to sleep I set "attachInterrupt(1, wakeUpNow, LOW);" again.
Using a do while loop, with a boolean whether a valid location is retrieved, I could extract valid GPS locations.