Wifi auto maximizer

I couldn't sleep the other night and while diving through the internet I came across a project to auto navigate a WIFI antenna for the best RSSI.

I found a old project from sparkfun from 2017 that works on sparkfun gear but I have been bashing my head against a wall on how to migrate this code over to the particle ecosystem.

The code was written by ShawnHymel and can also be found on his github page.

It could be an interesting project to migrate into the particle ecosystem. Just lost of the migration of the code.

D

I'm not completely sure this is correct but I was able to get it to compile and the servos do move, so that's kind of cool.

#include "Particle.h"

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Ported from:
// https://github.com/ShawnHymel/wifi_maximizer/blob/master/wifi_auto_maximizer/wifi_auto_maximizer.ino

// Requires a SSD1306 display connected by I2C (SDA=D0, SCL=D1)
// Install the library using particle library install Adafruit_SSD1306_RK

SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);
SerialLogHandler logHandler(LOG_LEVEL_INFO);


// Min and max servo settings
const int PAN_MIN = 0;
const int PAN_MAX = 180;
const int PAN_INC = 5;
const int TILT_MIN = 80;
const int TILT_MAX = 150;
const int TILT_INC = 5;
int panPos = 0;
int tiltPos = 0;


// Let's remember the maximal pan/tilt settings
int max_pan = 0;
int max_tilt = 0;
long max_rssi = -200;

// Servo objects
Servo pan_servo;
Servo tilt_servo;

const int PAN_PIN = A5;
const int TILT_PIN = A2;
int pan_pos, tilt_pos, rssi;

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

int getAndDisplayRSSI();

enum class State {
    START = 0,
    WAIT_CONNECT,
    SCAN,
    SCAN_COMPLETE,
    IDLE,
};
State state = State::START;

void setup()
{
    waitFor(Serial.isConnected, 10000);
    delay(1000);

    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
    { // Address may be 0x3D
        Log.info("display failed to initialize");
    }
    display.clearDisplay();
    display.setTextSize(1);             // Normal 1:1 pixel scale
    display.setTextColor(WHITE);        // Draw white text
    display.display();

    // Initialize servos
    pan_servo.attach(PAN_PIN);
    tilt_servo.attach(TILT_PIN);

}

void loop()
{

    switch(state) {
        case State::START:
            display.clearDisplay();
            display.setCursor(0, 0);
            display.print("Connecting...");
            Particle.connect();
            state = State::WAIT_CONNECT;
            break;

        case State::WAIT_CONNECT:
            if (Particle.connected()) {
                display.clearDisplay();
                display.setCursor(0, 0);
                display.print("Connecting...");
                delay(2000);

                max_pan = pan_pos = PAN_MIN;
                max_tilt = tilt_pos = TILT_MIN;
                state = State::SCAN;
            }
            break;

        case State::SCAN:
            pan_servo.write(pan_pos);
            tilt_servo.write(tilt_pos);
            rssi = getAndDisplayRSSI();
            Log.info("pan=%d tilt=%d rssi=%d", pan_pos, tilt_pos, rssi);

            if ( rssi > max_rssi ) {
                max_rssi = rssi;
                max_pan = pan_pos;
                max_tilt = tilt_pos;
            }
            tilt_pos += TILT_INC;
            if (tilt_pos >= TILT_MAX) {
                tilt_pos = TILT_MIN;
                pan_pos += PAN_INC;
                if (pan_pos >= PAN_MAX) {
                    state = State::SCAN_COMPLETE;
                }
            }
            delay(10);
            break;

        case State::SCAN_COMPLETE:
            display.clearDisplay();
            display.setCursor(0, 0);
            display.print("Maximizing Wi-Fi");

            Log.info("max pan=%d tilt=%d rssi=%d", max_pan, max_tilt, max_rssi);

            pan_servo.write(max_pan);
            tilt_servo.write(max_tilt);
            state = State::IDLE;
            break;

        case State::IDLE:
            getAndDisplayRSSI();
            break;
        
    }

}

/*************************************************************
 * Functions
 ************************************************************/
int getAndDisplayRSSI()
{

    int rssi = WiFi.RSSI();
    // Log.info("rssi=%d", (int)rssi);

    // Clear OLED and display RSSI
    display.clearDisplay();

    display.setCursor(0, 0);
    display.print(rssi);
    display.print(" dBm");
    display.display();
    delay(100);

    return rssi;
}
2 Likes

Awesome.

It wont compile for me, but I am at work and I do not have the focus to hammer at it right now.

I also don't have the OLED screen connected as I think it is either in the parking lot or on my kitchen counter.

Weekend project here I come.

servotracking.ino:49:72: no matching function for call to 'Adafruit_SSD1306::Adafruit_SSD1306(int, int, TwoWire*, int)'

servotracking.ino:67:23: could not convert 'display.Adafruit_SSD1306::begin(2, 60)' from 'void' to 'bool'

servotracking.ino:67:23: in argument to unary !

Make sure you're using Adafruit_SSD1306_RK with the _RK as the initialization API is different.

1 Like

I was using Adafruit_SSD1386 and not the Adafruit_SSD1306_RK.

Fixed it and it appears to be working. I just need to bring in the OLED and the antenna cable to give it an offical try on Monday.

Thank you for your assistance.

D

Hello and welcome to the start of a new week.

Thank you for your help on part 1 of this project. I have tested this multiple times this weekend and it is working great except that my OLED screen is broken. Dropping the soldering iron tip on the screen was not the best thing. So I used serial monitor and everything was great.

I went over to my in-laws place and hooked this up to a wifi access point that they have on the outskirt of their property and it is amazing how it is working.

On the drive over to the property. I started to think about how I could get this to work if I didn't have a password to their wifi.

So thinking of part 2. Next test

War Driving style. Lets say you pull up to an area where you can get WIFI, but you don't have the current password because the password had changed from the last time you were there.

Will this concept still work by directing to the RSSI.

D

WiFi.scan() returns a list of access points with the RSSI and you don't need a password. Your idea should work fine.

1 Like

I completed the initial configuration at my household SSID via https://setup.particle.io/
Example - HomeSSID

I was able to program other SSID's w passwords via code.
// Additional Wifi Networks that it can join.

creds.setSsid("InlawsSSID");
creds.setPassword("XXXXXXXXXX");
creds.setSecurity(SecurityType::WPA2);
creds.setValidate(true);
WiFi.setCredentials(creds);

The Proton2 will use InlawsSSID during its initial power on and if it cant connect it will attempt to connect to HomeSSID.


Can you think of a way or point me in the direction that even if it cant join the network that it will still pan/tilt to get the best SSID.

D

Yes - leave out the code that does the Particle.connect() and use WiFi.scan() instead.

Also you can just set both credentials - the Photon 2 stores up to 20 I think, certainly more than 2. It will automatically connect to whatever SSID has the strongest signal when connecting.

1 Like

Still a little loss on the removal of the particle.connect and the addition of the wifi.scan.

MY CODE IS BELOW


#include <Adafruit_SSD1306_RK.h>
#include "Particle.h"
#include <Adafruit_SSD1306.h>

SYSTEM_MODE(SEMI_AUTOMATIC);
SYSTEM_THREAD(ENABLED);
SerialLogHandler logHandler(LOG_LEVEL_INFO);

// Min and max servo settings

const int PAN_MIN = 0;
const int PAN_MAX = 180;
const int PAN_INC = 5;
const int TILT_MIN = 0;
const int TILT_MAX = 90;
const int TILT_INC = 5;
int panPos = 0;
int tiltPos = 0;


// Let's remember the maximal pan/tilt settings

int max_pan = 0;
int max_tilt = 0;
long max_rssi = -200;

// Servo objects

Servo pan_servo;
Servo tilt_servo;

const int PAN_PIN = A4;
const int TILT_PIN = A5;
int pan_pos, tilt_pos, rssi;

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

STARTUP(WiFi.selectAntenna(ANT_EXTERNAL)); //Force External Antenna
WiFiCredentials& setValidate(bool validate = true);

int getAndDisplayRSSI();

enum class State {
    START = 0,
    WAIT_CONNECT,
    SCAN,
    SCAN_COMPLETE,
    IDLE,
};
State state = State::START;



void setup()
{
    waitFor(Serial.isConnected, 10000);
    delay(1000);
    BLE.off();  // Turns off Bluetooth on the device.

WiFiCredentials creds;
creds.setSsid("*****DANGER*****");
creds.setPassword("XXXXXXXXXX");
creds.setSecurity(SecurityType::WPA2);
creds.setValidate(true);
WiFi.setCredentials(creds);
    

    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C))
    { // Address may be 0x3D
        Log.info("display failed to initialize");
    }
    display.clearDisplay();
    display.setTextSize(1);             // Normal 1:1 pixel scale
    display.setTextColor(WHITE);        // Draw white text
    display.display();

    // Initialize servos
    pan_servo.attach(PAN_PIN);
    tilt_servo.attach(TILT_PIN);

}

void loop()
{

    switch(state) {
        case State::START:
            display.clearDisplay();
            display.setCursor(0, 0);
            display.print("Connecting...");
            Particle.connect();
            state = State::WAIT_CONNECT;
            break;

        case State::WAIT_CONNECT:
            if (Particle.connected()) {
                display.clearDisplay();
                display.setCursor(0, 0);
                display.print("Connecting...");
                delay(2000);

                max_pan = pan_pos = PAN_MIN;
                max_tilt = tilt_pos = TILT_MIN;
                state = State::SCAN;
            }
            break;

        case State::SCAN:
            pan_servo.write(pan_pos);
            tilt_servo.write(tilt_pos);
            rssi = getAndDisplayRSSI();
            Log.info("pan=%d tilt=%d rssi=%d", pan_pos, tilt_pos, rssi);

            if ( rssi > max_rssi ) {
                max_rssi = rssi;
                max_pan = pan_pos;
                max_tilt = tilt_pos;
            }
            tilt_pos += TILT_INC;
            if (tilt_pos >= TILT_MAX) {
                tilt_pos = TILT_MIN;
                pan_pos += PAN_INC;
                if (pan_pos >= PAN_MAX) {
                    state = State::SCAN_COMPLETE;
                }
            }
            delay(10);
            break;

        case State::SCAN_COMPLETE:
            display.clearDisplay();
            display.setCursor(0, 0);
            display.print("Maximizing Wi-Fi");

            Log.info("max pan=%d tilt=%d rssi=%d", max_pan, max_tilt, max_rssi);

            pan_servo.write(max_pan);
            tilt_servo.write(max_tilt);
            state = State::IDLE;
            break;

        case State::IDLE:
            getAndDisplayRSSI();
            break;
        
    }

}

/*************************************************************
 * Functions
 ************************************************************/
int getAndDisplayRSSI()
{

    int rssi = WiFi.RSSI();
    // Log.info("rssi=%d", (int)rssi);

    // Clear OLED and display RSSI
    display.clearDisplay();

    display.setCursor(0, 0);
    display.print(rssi);
    display.print(" dBm");
    display.display();
    delay(100);

    return rssi;
}


SERIAL MONITOR OUTPUTS


000004707 [hal] INFO: WiFi on

0000004709 [ncp.rltk.client] INFO: Try to connect to ssid: *****DANGER*****

0000016418 [ncp.rltk.client] INFO: disconnected
<br>
<b>(The above repeats twice before starting the code below)</b>
<br>

0000999911 [ncp.rltk.client] INFO: Try to connect to ssid: MyWifiFi

0001011834 [ncp.rltk.client] INFO: disconnected

0001016390 [ncp.rltk.client] INFO: Try to connect to ssid: My Secret Network

0001016498 [ncp.rltk.client] INFO: Try to connect to ssid: MyWifiFi

0001028407 [ncp.rltk.client] INFO: disconnected

0001028908 [ncp.rltk.client] INFO: Try to connect to ssid: MyWifiFi

0001040829 [ncp.rltk.client] INFO: disconnected

(Serial monitor repeats and repeats and repeats because I have MyWifiFi powered down, if it was powered on it connects right away.)

Should I be looking at modify the area in the start of the Void Loop?

Since you don't need to actually connect to a network to view it's RSSI with WiFi.scan(), I believe Rick is saying you can look at that instead and not call Particle.connect() and WiFi.RSSI() at all.
So in your START and WAIT_CONNECT states, I would change it from trying to connect to a particular network and instead falling through to that you can call WiFi.scan() inside of getAndDisplayRSSI().

If you want to be connected to a known network for logging or functions you can of course keep that too, but decouple that from scanning the RSSI of all of the nearby networks.

1 Like

Hello Everyone including Rick and Jettonj

Just a little information. I camp at the same camp ground but at different camp sites. They use the same SSID's and change the password often. I believe that so far this summer I have seen at least 7 different passwords. So I want to be able to get into the site and power on the device (Proton2 with 2 servo's to find the SSID (CampersDelight) and automatically find the best RSSI to point my antenna at so that I can check my work emails daily. Depending which camp site I am on sometimes I point in completely different directions.

I believe what you are saying is that I should delete the following under void loop

    case State::START:
        display.clearDisplay();
        display.setCursor(0, 0);
        display.print("Connecting...");
    //    WiFi.scan();
        Particle.connect();
        state = State::WAIT_CONNECT;
        break;

    case State::WAIT_CONNECT:
        if (Particle.connected()) {
            display.clearDisplay();
            display.setCursor(0, 0);
            display.print("Connecting...");
            delay(2000);

            max_pan = pan_pos = PAN_MIN;
            max_tilt = tilt_pos = TILT_MIN;
            state = State::SCAN;
        }
        break;

and I should also remove

int getAndDisplayRSSI()
{

int rssi = WiFi.RSSI();
// Log.info("rssi=%d", (int)rssi);

// Clear OLED and display RSSI
display.clearDisplay();

display.setCursor(0, 0);
display.print(rssi);
display.print(" dBm");
display.display();
delay(100);

return rssi;

}

and replace it with WiFi.scan.

With WiFi.scan am I still able to direction find with the best RSSI but only link it to one SSID?

Thank you
D