Indoor Positioning Using Arduino and Machine Learning in 4 Easy Steps

Learn how to do WiFi indoor position with an ESP8266, ESP32 or MKR WiFi 1010 board and machine learning in the Arduino environment.

Eloquent Arduino
3 years ago β€’ Machine Learning & AI
WiFi indoor positioning re-elaborated. (πŸ“·: Accuware.com)

Please refer to the updated article at https://eloquentarduino.com/projects/arduino-indoor-positioning/

In this Arduino machine learning project, we're going to use the nearby WiFi access points to locate where we are. For this project to work, you will need a WiFi-equipped board, such as ESP8266, ESP32, or MKR WiFI 1010.

What Is Indoor Positioning?

We all are used to GPS positioning, where our device will use satellites to track our position on Earth. GPS works very well and with a very high accuracy (you can expect only a few meters of error).

But it suffers a problem: it requires Line of Sight (a clear path from your device to the satellites). If you're not in an open place, like inside a building, you're out of luck.

The task of detecting where you are when GPS localization is not an option is called indoor positioning, which could be in a building, an airport, or even a parking garage.

There are lots of different approaches to this task (Wikipedia lists more than 10 of them), each with a varying level of commitment, difficulty, cost and accuracy.

For this tutorial, I opted for one that is cost-efficient, easy to implement, and readily available in most of the locations: WiFi indoor positioning.

What Will We Use It For?

In this project, we're going to use WiFi indoor positioning to detect in which room of our house/office we are. This is the most basic task we can accomplish and will get us a feeling of level of accuracy that we can achieve with such a simple setup.

On this basis, we'll construct more sophisticated projects in future posts.

What You Need

To accomplish this tutorial, you really need two things:

  • A WiFi-equipped board (ESP8266, ESP32, Arduino MKR WiFi 1010, etc.)
  • Be in a place with a few WiFi networks around.

If you're doing this at home or in your office, there's a good chance your neighbors have WiFi networks in their apartments that you can leverage. If you live in an isolated contryside, sorry, this will not work for you.

How It Works

So, how exactly does WiFi indoor positioning works in conjunction with machine learning? Let's pretend there are five different WiFi networks around you, similar to the picture below.

As you can see, there are two markers on the map β€” each of these markers will "see" different networks, with different signal strengths (a.k.a RSSI).

As you move around, those numbers will change. Each room will be identified by the unique combination of the RSSIs.

1. Features Definition

The features for this project are going to be the RSSIs (received signal strength indication) of the known WiFi networks. If a network is out of range, it will have an RSSI equal to 0.

2. Record Sample Data

Before actually recording the sample data to train our classifier, we need to perform some preliminary work. This is because not all networks will be visible all the time β€” we have to work, however, with a fixed number of features.

2.1 Enumerate the Access Points

First of all, we need to enumerate all the networks we will encounter during the inference process.

To begin, we take a "reconnaissance tour" of the locations that we want to predict and log all the networks we detect. Load the following sketch and take note of all the networks that appear in the serial monitor.

#include "WiFi.h"

void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();
}

void loop() {
int numNetworks = WiFi.scanNetworks();

for (int i = 0; i < numNetworks; i++) {
Serial.println(WiFi.SSID(i));

delay(3000);
}

2.2 Create an Access Point Array

Now that we have a bunch of SSIDs, we need to assign each SSID to a fixed index, from 0 to MAX_NETWORKS.

You can implement this part as you like, but in this demo I'll make use of a class I wrote called Array (you can see the source code and example on Github), which implements two useful functions:

  • push() to add an element to the array.
  • indexOf() to get the index of an element.

See how to install the Eloquent library if you don't have it already installed.

At this point we populate the array with all the networks we saved from the reconnaissance tour.

#include "eDataStructures.h"

#define MAX_NETWORKS 10

using namespace Eloquent::DataStructures;

double features[MAX_NETWORKS];
Array knownNetworks("");

void setup() {
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.disconnect();

knownNetworks.push("SSID #0");
knownNetworks.push("SSID #1");
knownNetworks.push("SSID #2");
knownNetworks.push("SSID #3");
// and so on
}

2.3 Convert to Features Vector

The second step is to convert the scan results into a features vector. Each feature will be the RSSI of the given SSID, in the exact order we populated the knownNetworks array.

In practice:

features[0] == RSSI of SSID #0;
features[1] == RSSI of SSID #1;
features[2] == RSSI of SSID #2;
features[3] == RSSI of SSID #3;
// and so on

The code below will do the job.

void loop() {
scan();
printFeatures();
delay(3000);
}

void scan() {
int numNetworks = WiFi.scanNetworks();

resetFeatures();

// assign RSSIs to feature vector
for (int i = 0; i < numNetworks; i++) {
String ssid = WiFi.SSID(i);
uint16_t networkIndex = knownNetworks.indexOf(ssid);

// only create feature if the current SSID is a known one
if (!isnan(networkIndex))
features[networkIndex] = WiFi.RSSI(i);
}
}

// reset all features to 0
void resetFeatures() {
const uint16_t numFeatures = sizeof(features) / sizeof(double);

for (int i = 0; i < numFeatues; i++)
features[i] = 0;
}
void printFeatures() {
const uint16_t numFeatures = sizeof(features) / sizeof(double);

for (int i = 0; i < numFeatures; i++) {
Serial.print(features[i]);
Serial.print(i == numFeatures - 1 ? 'n' : ',');
}
}

Grab some recordings just staying in a location for a few seconds and save the serial output to a file, then move to the next location and repeat β€” 10-15 samples for each location will suffice.

If you do a good job, you should end with distinguible features, as shown in the plot below.

RSSIs may be a little noisy, mostly on the boundaries where weak networks may appear and disappear with a very low RSSI. This was not a problem for me, but if you're getting poor results you may filter out those low values.

// replace
features[networkIndex] = WiFi.RSSI(i);

// with
#define MIN_RSSI -90 // adjust to your needs

features[networkIndex] = WiFi.RSSI(i) > MIN_RSSI ? WiFi.RSSI(i) : 0;

3. Train and Export the SVM Classifier

For a detailed guide refer to this tutorial.

from sklearn.svm import SVC
from micromlgen import port

# put your samples in the dataset folder
# one class per file
# one feature vector per line, in CSV format
features, classmap = load_features('dataset/')
X, y = features[:, :-1], features[:, -1]
classifier = SVC(kernel='linear').fit(X, y)
c_code = port(classifier, classmap=classmap)
print(c_code)

At this point, you have to copy the printed code and import it in your Arduino project, in a file called model.h.

4. Run the Inference

#include "model.h"

void loop() {
scan();
classify();
delay(3000);
}

void classify() {
Serial.print("You are in ");
Serial.println(classIdxToName(predict(features)));
}

Move around your house/office/whatever and see your location printed on the serial monitor!

Latest articles
Sponsored articles
Related articles
Latest articles
Read more
Related articles