WiFiScanner

Project Description

After building the RSSI Scanner for Muenchen.freifunk.net I was urged to add a logging and GPS tracking to it.
This the smallest Wifi Scanner I have used up today.

I used almost all GPIO of the ESP8266 (only unused is the ADC). The used GPIOs are:

  • GPIO 4 & 5 Used for the RTC for I2C Wire SDA and SCL
  • GPIO 0 & 16 Used for the software serial to talk to the GPS Module
  • GPIO 12, 13, 14 & 15 Used for the SPI connection to the SD Card reader
  • GPIO 2 Used to drive the NeoPixel array

You have to break the default connection on the Adafruit NeoPixel FeatherWing and solder the correct connection to enable GPIO 2 connection.

The micro SD card needs to be formatted as a FAT16. There are some tweaks to make this happen.

Add a CR1220 battery to feed the RTC, while system is unpowered.

The Sourcecode:

/*
   RSSI Mapper V1.0

   Copyright 2016, -mat- filid brandy, brandy@linuxpinguin.de
   See /project/wifiscanner

   As I use a Adafruit ESP8266 Huzzah Feather this is the uses PIN usage

   For the GPS I use:
   GPIO #00 = SoftwareSerial RX
   GPIO #16 = SoftwareSerial TX

   For the Adafruit NeoPixel FeatherWing you will need to cut the default and
   enable it for use of Pin 2
   GPIO #02 = stripPin

   For the Adafruit Datalogger FeatherWing we will use the following Pins:
   SD will use:
   GPIO #15 = ChipSelect
   GPIO #14 = SPI SCK
   GPIO #13 = SPI MOSI
   GPIO #12 = SPI MISO

   For the RT Clock on the same Adafruit Datalogger FeatherWing we will use the following Pins:
   GPIO #05 = I2C SCL
   GPIO #04 = I2C SDA

   And then there are no more GPIOs left for the ESP 8266 (okay only the ADC analog one)
*/

#define DEBUG false

// GPS
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#define ssRX 0
#define ssTX 16
#define GPSresolution 4

// NeoPixel
#include <Adafruit_NeoPixel.h>
#define stripPin 2
#define stripSize 32

// SD
#include <SPI.h>
#include <SD.h>
#define chipSelect 15

// WiFi
#include "ESP8266WiFi.h"
#include <WiFiClient.h>
//#include <ESP8266WebServer.h>
//#include <ESP8266mDNS.h>

// Real Time Clock
#include <Wire.h>
#include "RTClib.h"
#define SDA 4
#define SCL 5

/*
   Init the hardware related variables
*/

// The multitasking looping support via timeslots
// [0] = search for new ssids
// [1] = find out the RSSI for a known BSSID
// [2] = glowing orb effect
//
uint32_t cTick;     // current Tick in millis()
uint32_t pTick[3];  // previous Tick in millis()
uint32_t dTick[3];  // difference between current and previous Tick
bool fTick[3];      // first time

// GPS
TinyGPS gps;
SoftwareSerial ss(ssRX, ssTX);

// NeoPixel
Adafruit_NeoPixel strip = Adafruit_NeoPixel( stripSize, stripPin, NEO_GRB + NEO_KHZ800);

// WiFi
String searchForSSID1 = "muenchen.freifunk.net";
String searchForSSID2 = "mesh.ffmuc";

// SD
Sd2Card card;
SdVolume volume;
SdFile root;
boolean SDworks = false;

// Realtime Clock
RTC_PCF8523 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

// WatchDog
// Use a watchdog timer to catch issue inside the WiFi module
// This sometime triggers, when scanning for new networks and none are in the neighboorhood
// The WiFi subsystem in the chip hangs, the watchdog catches this and reset the whole system
#include <Ticker.h>
Ticker secondTick;
int watchdogCount = 0;
void ISRwatchdog() {
  watchdogCount++;
  if (watchdogCount > 5) {
    Serial.println();
    Serial.println("watchdog triggered reset!");
    ESP.reset();
  }
}

/*
   Handling GPS --------------------------------------------------------------------
*/
namespace myGPS {
typedef struct {
  bool newGPSData;
  bool debug;
  bool debugComm;
  unsigned long chars;
  unsigned short sentences;
  unsigned short failed;
  float lat;
  float lon;
  unsigned long age;
} GPSData;

static void debug(GPSData& gpsd) {
  gpsd.debug = !gpsd.debug;
}
static void debugComm(GPSData& gpsd) {
  gpsd.debugComm = !gpsd.debugComm;
}

static String print_date() {
  DateTime now = rtc.now();
  int year;
  byte month, day, hour, minute, second, hsec;
  unsigned long age;
  String ret;
  gps.crack_datetime(&year, &month, &day, &hour, &minute, &second, &hsec, &age );
  if (TinyGPS::GPS_INVALID_AGE != age) {
    rtc.adjust(DateTime(year, month, day, hour, minute, second));
    if (DEBUG) Serial.println("Adjusted time from GPS");
  }
  char sz[32];
  sprintf(sz, "%02d/%02d/%02d %02d:%02d:%02d",
          now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second());
  ret = sz;
  return ret;
}

static void handleGPS(GPSData& gpsd) {
  gpsd.newGPSData = false;

  // Every 1 second = 1000 milliseconds
  for (unsigned long start = millis(); millis() - start < 1000; ) {
    while (ss.available()) {
      char c = ss.read();
      if (gpsd.debugComm)
        Serial.write(c);
      if (gps.encode(c))
        gpsd.newGPSData = true;
    }
  }
  if (gpsd.newGPSData) {
    gps.f_get_position( &gpsd.lat, &gpsd.lon, &gpsd.age);
    if (gpsd.debug) {
      Serial.print("\n LAT=");
      Serial.print(gpsd.lat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : gpsd.lat, 6);
      Serial.print(" LON=");
      Serial.print(gpsd.lon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : gpsd.lon, 6);
      Serial.print(" SAT=");
      Serial.print(gps.satellites() == TinyGPS::GPS_INVALID_SATELLITES ? 0 : gps.satellites());
      Serial.print(" PREC=");
      Serial.print(gps.hdop() == TinyGPS::GPS_INVALID_HDOP ? 0 : gps.hdop());
      Serial.print(" Date=");
      Serial.print(print_date());
    }
    strip.setPixelColor(0, (0x0F << 8) + 0x07);
  } else {
    strip.setPixelColor(0, (0x07 << 16) + 0x07);
  }
  strip.show();

  gps.stats( &gpsd.chars, &gpsd.sentences, &gpsd.failed);
  if (0 == gpsd.chars) {
    Serial.println("\n\n** No characters received from GPS: check wiring **\n");
  }
  if (gpsd.debugComm) {
    Serial.print("\n CHARS=");
    Serial.print(gpsd.chars);
    Serial.print(" SENTENCES=");
    Serial.print(gpsd.sentences);
    Serial.print(" CSUM ERR=");
    Serial.println(gpsd.failed);
  }
}

}
myGPS::GPSData myGPSData;

/*
   Wifi
*/
String encryptionTypes(int which) {
  switch (which) {
    case ENC_TYPE_WEP: return "WEP"; break;
    case ENC_TYPE_TKIP: return "WPA/TKIP"; break;
    case ENC_TYPE_CCMP: return "WPA2/CCMP"; break;
    case ENC_TYPE_NONE: return "None"; break;
    case ENC_TYPE_AUTO: return "Auto"; break;
    default: return "Unknown"; break;
  }
}

String bssidToString(uint8_t *thisBSSID) {
  String result;
  for (int i = 0; i < 6; i++ ) {
    char r[5];
    sprintf(r, "%X:", thisBSSID[i]);
    result = result + r;
  }
  return result.substring(1, result.length() - 1);
}

void displayNodes(int ffmucmesh, int ffmucnodes, int opennodes, int nodes) {

  allColor(0);
  int maxAPs = strip.numPixels() - 1;
  maxAPs = ( maxAPs < nodes) ? nodes : maxAPs;
  maxAPs = ( maxAPs <= 0 ) ? strip.numPixels() - 1 : maxAPs;
  for (int i = 0; i < map(nodes, 0, maxAPs, 0, strip.numPixels() - 1); i++) {
    strip.setPixelColor(i + 1, 0x0F << 8);
  }
  for (int i = 0; i < map(opennodes, 0, maxAPs, 0, strip.numPixels() - 1); i++) {
    strip.setPixelColor(i + 1, 0x0F << 16);
  }
  for (int i = 0; i < map(ffmucnodes + ffmucmesh, 0, maxAPs, 0, strip.numPixels() - 1); i++) {
    strip.setPixelColor(i + 1, 0x0F);
  }
  for (int i = 0; i < map(ffmucmesh, 0, maxAPs, 0, strip.numPixels() - 1); i++) {
    strip.setPixelColor(i + 1, 0x02);
  }

  strip.show();
}

/*
   NeoPixel
*/
void allColor(uint32_t c) {
  for (int i = 0; i < strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
  }
  strip.show();
}

/*
    SETUP --------------------------------------------------------------------------
*/
void setup() {
  Serial.begin(9600);
  // GPS
  ss.begin(9600);
  // NeoPixel
  strip.begin();
  strip.show();

  // Setup the WiFi module
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();

  dTick[0] = 60 * 1000; // Every 60 seconds
  fTick[0] = HIGH;

  // SD card setup
  Serial.print("\nInitializing SD card...");
  if (!card.init(SPI_HALF_SPEED, chipSelect)) {
    SDworks = false;
    Serial.println("initialization failed. Things to check:");
    Serial.println("* is a card inserted?");
    Serial.println("* is your wiring correct?");
    Serial.println("* did you change the chipSelect pin to match your shield or module?");
    return;
  } else {
    SDworks = true;
    Serial.println("Wiring is correct and a card is present.");

    // print the type of card
    Serial.print("\nCard type: ");
    switch (card.type()) {
      case SD_CARD_TYPE_SD1:
        Serial.println("SD1");
        break;
      case SD_CARD_TYPE_SD2:
        Serial.println("SD2");
        break;
      case SD_CARD_TYPE_SDHC:
        Serial.println("SDHC");
        break;
      default:
        Serial.println("Unknown");
    }
    // Now we will try to open the 'volume'/'partition' - it should be FAT16 or FAT32
    if (!volume.init(card)) {
      Serial.println("Could not find FAT16/FAT32 partition.\nMake sure you've formatted the card");
      SDworks = false;
      return;
    }
    // print the type and size of the first FAT-type volume
    uint32_t volumesize;
    Serial.print("\nVolume type is FAT");
    Serial.println(volume.fatType(), DEC);
    Serial.println();

    volumesize = volume.blocksPerCluster();    // clusters are collections of blocks
    volumesize *= volume.clusterCount();       // we'll have a lot of clusters
    volumesize *= 512;                            // SD card blocks are always 512 bytes
    Serial.print("Volume size (bytes): ");
    Serial.println(volumesize);
    Serial.print("Volume size (Kbytes): ");
    volumesize /= 1024;
    Serial.println(volumesize);
    Serial.print("Volume size (Mbytes): ");
    volumesize /= 1024;
    Serial.println(volumesize);

    SD.begin(chipSelect);
  }

  // Real Time Clock
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    while (1);
  }

  if (! rtc.initialized()) {
    Serial.println("RTC is NOT running!");
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  }

  // Attach the interrupt 1 to the watchdog
  secondTick.attach(1, ISRwatchdog);

  Serial.println("Setup done");
  allColor(0);
}

/*
    WORK --------------------------------------------------------------------------
*/
void loop() {
  watchdogCount = 0;  // Feed the watchdog
  yield();            // Give the WiFi subsystem time to do its things
  cTick = millis();
  File dataFile;

  // GPS
  using namespace myGPS;
  myGPS::handleGPS(myGPSData);
  if (DEBUG) myGPS::debug(myGPSData);
  if (DEBUG) myGPS::debugComm(myGPSData);

  // WiFi
  if ( ((cTick - pTick[0]) >= dTick[0] || HIGH == fTick[0])) {
    pTick[0] = cTick;
    fTick[0] = LOW;
    int networks = WiFi.scanNetworks();
    yield();

    if ( 0 == networks) {
      Serial.println("No networks found");
    } else {
      Serial.println("");
      int kinds[4];
      kinds[0] = 0; kinds[1] = 0; kinds[2] = 0; kinds[3] = 0;
      for (int network = 0; network < networks; network++) {
        String ssid_scan;
        int32_t rssi_scan;
        uint8_t sec_scan;
        uint8_t* BSSID_scan;
        int32_t chan_scan;
        bool hidden_scan;
        WiFi.getNetworkInfo(network, ssid_scan, sec_scan, rssi_scan, BSSID_scan, chan_scan, hidden_scan);
        Serial.print("\""); Serial.print(ssid_scan); Serial.print("\",");
        Serial.print("\""); Serial.print(bssidToString(BSSID_scan)); Serial.print("\",");
        Serial.print(""); Serial.print(rssi_scan); Serial.print(",");
        Serial.print(""); Serial.print(chan_scan); Serial.print(",");
        Serial.print("\""); Serial.print(encryptionTypes(sec_scan)); Serial.print("\",");
        Serial.print(""); Serial.print(myGPSData.lat, GPSresolution); Serial.print(",");
        Serial.print(""); Serial.print(myGPSData.lon, GPSresolution); Serial.print(",");
        Serial.print("\""); Serial.print(myGPS::print_date()); Serial.print("\"");
        Serial.println("");

        if (SDworks)
          dataFile = SD.open("ssids.txt", FILE_WRITE);
        if (dataFile) {
          dataFile.print("\""); dataFile.print(ssid_scan); dataFile.print("\",");
          dataFile.print("\""); dataFile.print(bssidToString(BSSID_scan)); dataFile.print("\",");
          dataFile.print("");   dataFile.print(rssi_scan); dataFile.print(",");
          dataFile.print("");   dataFile.print(chan_scan); dataFile.print(",");
          dataFile.print("\""); dataFile.print(encryptionTypes(sec_scan)); dataFile.print("\",");
          dataFile.print("");   dataFile.print(myGPSData.lat, GPSresolution); dataFile.print(",");
          dataFile.print("");   dataFile.print(myGPSData.lon, GPSresolution); dataFile.print(",");
          dataFile.print("\""); dataFile.print(myGPS::print_date()); dataFile.print("\"");
          dataFile.println("");
          dataFile.close();
        } else {
          Serial.println("error opening ssids.txt");
          SDworks = false;
        }

        if (ENC_TYPE_NONE == sec_scan)
          kinds[0]++;
        else
          kinds[1]++;
        if (searchForSSID1.equals(ssid_scan))
          kinds[2]++;
        if (searchForSSID2.equals(ssid_scan))
          kinds[3]++;
      }
      displayNodes(kinds[3], kinds[2], kinds[0], networks);
      Serial.print("\t\"");
      Serial.print(networks);
      Serial.print(" networks (");
      Serial.print(kinds[0]);
      Serial.print(" open (");
      Serial.print(kinds[2] + kinds[3]);
      Serial.print(" ffmuc), ");
      Serial.print(kinds[1]);
      Serial.println(" closed)\"");
    }
  }
}