Simple Cellular Signal Site Survey Project


#1

All,

I am looking a putting some of my Electron based sensors in some new remote locations. I needed to be able to send a simple kit to a remote user and have them do a “walk about” while the device collected signal strength information. Ideally, I could use this to help select better placement for the device and anticipate problems before they arise.

This simple sketch collects and reports signal strength data from the Electron and pushes it to Ubidots with a GPS “Context”. That way you can easily map the data and see where there is good / bad signal. I happened to have an Adafruit ultimate GPS module on-hand so I used it. However, this sketch uses the TinyGPS++ library and should work just fine with the official Particle Asset Tracker Board.

I hope this is useful and, as always, comments / suggestions are welcome. I may well be updating this in the coming weeks so, best to get it from the GitHub repo here:

/*
 * Project GPS-Tracker - Simple Site Survey Tool
 * Description: For determining signal quality at propsective Electron sites
 * Author: Chip McClelland
 * Date: 12-12-17
 */

 // Variables I want to change often and pull them all together here
 #define SOFTWARERELEASENUMBER "0.5"

 #include "Particle.h"
 #include "TinyGPS++.h"                       // https://github.com/mikalhart/TinyGPSPlus
 #include "electrondoc.h"                     // Documents pinout

 // Prototypes and System Mode calls
 SYSTEM_THREAD(ENABLED);         // Means my code will not be held up by Particle processes.
 FuelGauge batteryMonitor;       // Prototype for the fuel gauge (included in Particle core library)
 TinyGPSPlus gps;                // The TinyGPS++ object

 // State Maching Variables
 enum State { INITIALIZATION_STATE, ERROR_STATE, IDLE_STATE, REPORTING_STATE, RESP_WAIT_STATE };
 State state = INITIALIZATION_STATE;

 // Pin definitions
 const int enablePin = B4;      // Hold low to power down the device
 const int fixPin = B3;         // From the GPS modlue - tracks the status "red" LED
 const int ledPin = D7;         // To give a visual indication when a datapoint is recorded

 // Reporting intervals
 const unsigned long Ubidots_Frequency = 30000;     // How often will we report to Ubidots
 const unsigned long webhookWaitTime = 10000;     // How long will we wait for a webhook response
 const unsigned long Particle_Frequency = 1000;     // Will limit how often we send updates

 // Program control valriables
 unsigned long ubidotsPublish = 0;               // When was the last time we published
 unsigned long lastPublish = 0;
 bool dataInFlight = false;                       // Tells us we send data but it has not yet been acknowledged by Ubidots
 volatile bool fixFlag = false;                   // Tracks fixLED interrupts
 unsigned long lastFixFlash = 0;                  // when was the last flash
 bool gpsFix = false;                             // keeps track of our fix Status
 int resetCount = 0;                              // Can track resets for future reporting
 int errorCount = 0;                              // So we don't reset everytime a datapoint gets lost
 int RSSI = 0;                                    // Signal strength in dBi
 int gpsSamples = 0;                              // Counts the nubmer of samples received by Ubidots

 // Battery monitor
 int stateOfCharge = 0;                           // Stores battery charge level value

 //Menu and Program Variables
 const char* releaseNumber = SOFTWARERELEASENUMBER;   // Displays the release on the menu
 retained char Signal[17];                            // Used to communicate Wireless RSSI and Description
 char Status[17] = "";                                // Used to communciate updates on System Status
 const char* levels[6] = {"Poor", "Low", "Medium", "Good", "Very Good", "Great"};

 void setup()
 {
  Serial1.begin(9600);                 // The GPS module is connected to Serial 1
  pinMode(enablePin,OUTPUT);           // Can be used to turn the GPS on and off - not used in this version
  digitalWrite(enablePin,HIGH);        // Initially we will have the GPS on
  pinMode(ledPin,OUTPUT);              // So we can signal when a datapoint is received
  digitalWrite(ledPin,LOW);
  pinMode(fixPin,INPUT);               // Tied to the red indicator LED on the GPS Module

  char responseTopic[125];
  String deviceID = System.deviceID();                                // Multiple Electrons share the same hook - keeps things straight
  deviceID.toCharArray(responseTopic,125);
  Particle.subscribe(responseTopic, UbidotsHandler, MY_DEVICES);      // Subscribe to the integration response event

  Particle.variable("Signal", Signal);                    // You will be able to monitor the device using the Particle mobile app
  Particle.variable("ResetCount", resetCount);
  Particle.variable("Release",releaseNumber);
  Particle.variable("stateOfChg", stateOfCharge);
  Particle.variable("Samples",gpsSamples);

  Particle.function("Reset",resetNow);

  attachInterrupt(fixPin,fixISR,RISING);                    // Going to see when we have a fix
  lastFixFlash = millis();

  Time.zone(-5);                                            // Set time zone to Eastern USA daylight saving time
  takeMeasurements();

  state = IDLE_STATE;                                     // IDLE unless error from above code
  waitUntil(meterParticlePublish);                       // Meter our Particle publishes
  Particle.publish("State","Idle");
  lastPublish = millis();
 }

 void loop()
 {
   switch(state) {

   case IDLE_STATE:
     if (fixFlag) {
       fixFlag = false;                                     // Clear the fixFlag
       if (millis()-lastFixFlash >= 10000)  gpsFix = true;  // Flashes every 15 sec when it has a fix
       else  gpsFix = false;                                // Flashes every second when it is looking
       lastFixFlash = millis();                 // Reset the flag timer
     }
     if ((millis() - ubidotsPublish >= Ubidots_Frequency) && gpsFix) {
       state = REPORTING_STATE;
       waitUntil(meterParticlePublish);     // Meter our Particle publishes
       Particle.publish("State","Reporting");
       lastPublish = millis();
     }
   break;

   case REPORTING_STATE:
     while (Serial1.available() > 0) {
       if (gps.encode(Serial1.read())) {
         displayInfo();
         takeMeasurements();
         sendEvent();
         state = RESP_WAIT_STATE;                            // Wait for Response
         waitUntil(meterParticlePublish);     // Meter our Particle publishes
         Particle.publish("State","Waiting for Response");
         lastPublish = millis();
       }
     }
   break;

   case RESP_WAIT_STATE:
     if (!dataInFlight)                                  // Response received
     {
       state = IDLE_STATE;
       waitUntil(meterParticlePublish);     // Meter our Particle publishes
       Particle.publish("State","Idle");
       lastPublish = millis();
       digitalWrite(ledPin,HIGH);                         // So you can see when a datapoint is received
       delay(2000);
       digitalWrite(ledPin,LOW);
       break;
     }
     else if (millis() - ubidotsPublish >= webhookWaitTime) {
       state = ERROR_STATE;                               // Response timed out
       waitUntil(meterParticlePublish);     // Meter our Particle publishes
       Particle.publish("State","Response Timeout Error");
       lastPublish = millis();
     }
   break;

   case ERROR_STATE:                                      // To be enhanced - where we deal with errors
     if (errorCount >= 3) {
       waitUntil(meterParticlePublish);     // Meter our Particle publishes
       Particle.publish("State","Too many errors - resetting Electron");
       lastPublish = millis();
       delay(3000);                                         // Give time to publish
       System.reset();
     }
     errorCount++;                                      // Today, only way out is reset
   break;
   }
 }

void displayInfo()
{
  char buf[128];
  if (gps.location.isValid()) snprintf(buf, sizeof(buf), "%f,%f,%f", gps.location.lat(), gps.location.lng(), gps.altitude.meters());
  else strcpy(buf, "no location");
  waitUntil(meterParticlePublish);     // Meter our Particle publishes
  Particle.publish("gps", buf);
  lastPublish = millis();
}


void sendEvent()
{
  char data[256];                                         // Store the date in this character array - not global
  snprintf(data, sizeof(data), "{\"battery\":%i, \"signal\":%i, \"lat\":%f, \"lng\":%f}",stateOfCharge, RSSI, gps.location.lat(), gps.location.lng());
  Particle.publish("GPSlog_hook", data, PRIVATE);
  ubidotsPublish = millis();            // This is how we meter out publishing to Ubidots
  dataInFlight = true;                  // set the data inflight flag - cleared when we get the 201 response
}

void UbidotsHandler(const char *event, const char *data)  // Looks at the response from Ubidots - Will reset Photon if no successful response
{
  // Response Template: "{{hourly.0.status_code}}"
  if (!data) {                                            // First check to see if there is any data
    waitUntil(meterParticlePublish);     // Meter our Particle publishes
    Particle.publish("Webhook Response","Empty");
    lastPublish = millis();
    return;
  }
  int responseCode = atoi(data);                          // Response is only a single number thanks to Template
  if ((responseCode == 200) || (responseCode == 201))
  {
    waitUntil(meterParticlePublish);     // Meter our Particle publishes
    Particle.publish("State","Response Received");
    lastPublish = millis();
    dataInFlight = false;                                 // Data has been received
    gpsSamples++;                                         // So you can see how many samples are recorded
  }
  else {
    waitUntil(meterParticlePublish);     // Meter our Particle publishes
    Particle.publish("Webhook Response",data);
    lastPublish = millis();
  }
}

void getSignalStrength()
{
    CellularSignal sig = Cellular.RSSI();  // Prototype for Cellular Signal Montoring
    RSSI = sig.rssi;
    int strength = map(RSSI, -131, -51, 0, 5);
    snprintf(Signal,17, "%s: %d", levels[strength], RSSI);
    waitUntil(meterParticlePublish);     // Meter our Particle publishes
    Particle.publish("Signal",Signal);
    lastPublish = millis();
}

void takeMeasurements() {
  if (Cellular.ready()) getSignalStrength();                // Test signal strength if the cellular modem is on and ready
  stateOfCharge = int(batteryMonitor.getSoC());             // Percentage of full charge
}

int resetNow(String command)   // Will reset the Electron
{
  if (command == "1")
  {
    System.reset();
    return 1;
  }
  else return 0;
}

void fixISR()
{
  fixFlag = true;
}

bool meterParticlePublish(void)
{
  if(millis() - lastPublish >= Particle_Frequency) return 1;
  else return 0;
}

Here is the information you need to create the webhook “GPSlog_hook”.

{
  "event": "GPSlog_hook",
  "url": "https://things.ubidots.com/api/v1.6/devices/{{PARTICLE_DEVICE_ID}}/?token={{Your Token Here}}",
  "requestType": "POST",
  "mydevices": true,
  "noDefaults": true,
  "responseTemplate": "{{battery.0.status_code}}",
  "responseTopic": "{{PARTICLE_DEVICE_ID}}_GPSlog_hook",
  "json":{
    "battery": "{{battery}}",
    "signal":{
      "value": "{{signal}}",
      "context": {
        "lat":"{{lat}}",
        "lng":"{{lng}}"
        }
    }
  }
}

I hope some folks find this useful.

Thanks,

Chip


GPS interference from Electron
GPS module for Photon?
#2

@chipmc,
Good Stuff! That might help me out.
Thanks for sharing.


#3

Cool stuff, I love pairing GPS and cellular data together for a useful purpose.

Is this for more park like applications?


#4

@RWB,

Yes, it is. I was thinking that having an easy way to measure the signal strength at a prospective site would help me prompt later issues.

I have one placement with very poor signal quality and the data consumption and resets are very high. I will likely need to move to a directional external antenna and I would like to avoid this cost and complexity in the future.

I wil likely add on to this later - like adding air quality measurements to a bike mounted sensor for example. But, for now, it will just collect signal strength.

Chip


#5

Sounds like a plan.

The CCS811 Air Quality VOC Sensor sensor is a really cool sensor to have on board. I’ve used one for a while now with great success.


#6

@RWB,

Cool, looks like it is a good sensor especially if you pair it with a temp / humidity / pressure sensor.

Have you looked at this 4-in-1 sensor: Bosch BME680 ?

Chip


#7

Originally I bought this BME280 + CCS811 combo but the BME’s temp readings were way off.

I am using the CCS811 + SHT31 temp and humidity sensor combo now with great accuracy.

Below are readings from a garage. It looks like the cat box needs to be changed because VOC normally its very low.

Voc Readings - powered by Losant https://app.losant.com/dashboards/599c9a05b36c040007c6e20c


#8

@RWB,

Very cool, will order one! Also, like the Losant dashboard! Need to find the time to explore this service.

Chip


#9

Thank you very much for these useful information :smile:, i have followed the same steps, and i cannot see any data in the particle console, it only shows the (State) as Idle.
I have also included my ubidots Token in the json file, but i cannot see any data coming, do i need to define the {{PARTICLE_DEVICE_ID}} in the json file?, i probably doing silly mistake :grin:as i am new to all this. and advice?
thanks a lot for your cooperation


#10

@tracking,

OK, you are making progress. Here are some additional pointers:

  1. The program will stay in the IDLE state until it gets a GPS fix. Is the red light on the GPS flashing once every second? If so, you need to move outdoors or near a window so it can see the sky. Especially for the first fix. Make sure you have put the coin cell on the GPS too so it remembers the time / satellites. If it is bringing every 15 seconds, you have a wiring error - make sure TX goes to RX for example.

  2. You will need to use the JSON file to create the webhook using the command line utilities (can be found here) once installed, navigate to the directory with the JSON file and follow these instructions.. You can also create the Webhook in the portal, just take the information in the JSON file and put it into the right places in the form (use JSON and advanced).

  3. The Adafruit GPS is very sensitive to noise on the power line. I used a separate LiPo battery to power the GPS than the one for the Electron. Make sure to connect the grounds from both batteries if you do this! I am going to play with another approach to allow the Electron and the GPS to share a battery but if you are not getting a fix even outdoors, power is the likely culprit.

Take a look and let me know how you are doing.

Thanks,

Chip


#11

Hi Chipmc,

thanks for your reply :slight_smile:. . i have done the following steps
1- i have created the JSON file using command line utilities, and i can see it online (OK)
2- I have GPS fix, the LED flashing every 15 sec, but still the console shows IDLE.
3- New device was created automaticity in ubidots called (api) with two variable signal & battery, but with no any data.

i found example in Asset Tracking Library called GPS_Feautres, it is the only example that shows my Lat & Lon in the console, i am struggling now to send these data to ubidots as context. (( Does the TinyGPS++ Libaray works with Adafruit Ultimate GSP))
please see the code in the link below, that gives me the Lat & Lon data.

Thanks alot for your support :slight_smile:


#12

heyy it is start working now. i connected the Fixpin of the GPS to the B3, and Enable to B4. my mistake :grin:
what does 80 means in the battery data, as my battery is fully charaged, i guess i should show 100 right?

thanks alot for your support.


#13

@tracking,

Almost there!

  1. The Electron only charges its battery to a about 80% for safety and longevity reasons. You can change this but it is not recommended

  2. Ubidots treats GPS as the context for a Dara point. Go to the dashboard asd create a map widget with Signal as the variable. You will see the latest datapoint on the map. When you mouse over it, you will see the Signal data.

Great progrsss, keep it up

Chip


#14

hey,
it is all working now, thanks alot :grin:.
i have notice that when i loss the GPS signal, the (Lat & Lon = 0), which then updated in the ubidots and i loss the last location data. is there anyway not to send the data to ubidots if the Lat & Lon =0.

Thanks alot :+1:


#15

@tracking,

Glad to hear it is working!

Great idea about not sending when lat or long is zero.

As long as you are not on the prime meridian and equator both, this should work.

I can test this tomorrow but it should do the trick.

Chip