Posts in this project
Pallet Tracker - 01 - Project description
Pallet Tracker - 02 - Development environment
Pallet Tracker - 03 - Application skeleton
Pallet Tracker - 04 - PSoC6 power modes and LPComp
Pallet Tracker - 05 - Evaluating power consumption
Pallet Tracker - 06 - Indoor localization
Since a working prototype of the "position sensor" has now been completed, we can now direct our efforts to implement the backend, or, in other words, the application that will receive RSSI data from the sensor and make the proper computation to localize the pallet
Indoor localization
Many applications depend on being able to reliably localize a target in order to provide their respective services. This is especially true in the field of robotics or asset tracking. GPS sensors have been able to reliably localize various devices in many cases, however, this is not true when a GPS signal cannot be established due to environmental conditions such as being indoors. WiFi fingerprinting has been a very popular approach to solve this problem by utilizing the Received Signal Strength Indicator values across multiple Wireless Access Points (WAPs) to localize a target with respect to the locations of the WAPs. Because the Wireless Local Area Networks using WiFi technology are becoming more and more ubiquitous, indoor localization can be implemented using this existing infrastructure.
The two main options for doing Wi-Fi localization are triangulation and fingerprinting
Triangulation
Wi-Fi triangulation’s goal is to map RSSI as a function of distance. This method requires a steep linear characterization curve in order to be properly implemented. Functions describing these curves are then used with live RSSI values as input to generate an (x,y) location prediction. This method was considered first due to its relatively simple implementation.
Fingerprinting
The basic method of this kind of location scheme is to receive RSS (received signal) received by each receiver for the target space to be located Strength) is used as a fingerprint vector to measure each location, and a fingerprint vector database of each location is established. Then, the mobile device is located by matching the RSS vector received by the mobile device with the database. Specifically, the positioning process is divided into two stages: offline training stage and real-time positioning stage. In the off-line training stage, several WiFi access points are fixed in the room, and then the mobile devices (such as mobile phones, laptops, etc.) are carried out in different indoor locations for RSS measurement. In the real-time positioning stage, the mobile terminal finds the coordinates closest to the K positions of the fingerprint database by matching the received RSS vector with the established fingerprint database data, and then obtains the user location coordinates by taking the geometric average.
Basically, Wi-Fi Fingerprinting creates a radio map of a given area based on the RSSI data from several access points and generates a probability distribution of RSSI values for a given(x,y) location. Live RSSI values are then compared to the fingerprint to find the closest match and generate a predicted (x,y) location.
Fingerprinting typically provides more accurate results than triangulation because classifiers may be applied to the collected fingerprinting data to compensate for the inevitable variations in RSSI signals (due to external radio interferences or other uncontrollable environment conditions -like air temperature and humidity). For this reason, fingerprinting looks more promising: probability distributions and/or machine learning algorithms can be used to provide a better estimation of the most probable position, thus "overcoming" the limitations due to unreliable and approximate measures
Data collection
The first step for creating and indoor localization system is collecting data to create the above-mentioned RSSI fingerprint of the environment. To accomplish this task, I started from this Android application and made the following changes
- Changed the format collected RSSI data is stored
- Added "SEND" button to send collected RSSI data to the server
- Added "Navigation" section, where the app polls the server for the latest assets locations
Then, I created a test environment with three WiFi access points: my laptop and two Raspberry Pi. I placed the access points in my garden (because I didn't have enough room in my house to make significant RSSI measurements) and walked along an imaginary grid
Here is a video of myself collecting RSSI fingerprints
The final result, was a test file with five columns
- position "identifier"
- X distance from origin
- Y distance from origin
- Laptop WiFi RSSI readings
- Raspberry 1 RSSI readings
- Raspberry 2 RSSI readings
Here is an abstract of the file
Relative Position,Position X,Position Y,laptopap,rp1ap,rp2ap Relative Position,Position X,Position Y,2437,2437,2437 Relative Position,Position X,Position Y,00:0b:6b:de:ea:23,00:0b:6b:de:ea:36,00:23:eb:08:cf:c3 1,1,1,-39,-41,-70 1,1,1,-40,-40,-72 1,1,1,-33,-38,-72 1,1,1,-35,-44,-69 1,1,1,-35,-37,-70 2,1,2,-30,-47,-71 2,1,2,-29,-43,-69 2,1,2,-73,-41,-69 2,1,2,-31,-34,-66 2,1,2,-36,-42,-70
The file has then been processed to create the heatmap. From terminal, I launched
pyhton3 heatmap.py
This Python scripts computes the average of the five RSSI readings for each position and compute the best fitting curve by invoking function scipy.optimize.curve_fit
popt, pcov = curve_fit(func, distances, network_mean_rssi)
where network is the access point's name and network_mean_rssi is the array of RSSI mean values
A color heatmap is also drawn by means of matplotlib.pyplot.colorbar function
minx = min(network_mean_rssi) maxx = max(network_mean_rssi) N = len(network_mean_rssi) norm = mpl.colors.Normalize(vmin=minx,vmax=maxx) sm = plt.cm.ScalarMappable(cmap=cm, norm=norm) sm.set_array([]) plt.colorbar(sm)
heatmap.py finally saves RSSI mean values to a CSV file for later use by localization script
Localization
As we saw in previous posts, the PSoC6 board sends RSSI readings to the server. Server will use the RSSI mean values computed in previous phase to estimate pallet position
The estimation uses a k-nearest-neighbor algorithm. The concept behind KNN algorithm is quite simple. Suppose we have a dataset that can be plotted as follows
Now, we need to classify new data point with black dot (at point 60,60) into blue or red class. We are assuming K = 3 i.e. it would find three nearest data points. It is shown in the next diagram
We can see in the above diagram the three nearest neighbors of the data point with black dot. Among those three, two of them lies in Red class hence the black dot will also be assigned in red class.
Using a KNN classifier in Python is quite simple, thanks to sklearn. First, let's instatiate a KNN object
from sklearn.neighbors import KNeighborsClassifier as kNN clf = KNN(n_neighbors=2)
and initialize it
clf.fit(features,labels)
Here, features are the pre-calculated RSSI mean values and labels are the position identifiers. These two sets are extracted from the CSV file generated by heatmap.py
dataset = pd.read_csv("mean_positions.csv",header = [0,1,2]) features = np.asarray(dataset.iloc[:,3:]) labels = np.asarray(dataset["Relative Position"])
We can now use the KNN object to make a prediction of the position. Just as a reminder, the Json document sent by the PSoC6 board looks like this
[ {'ssid': 'rp1ap', 'rssi': -47}, {'ssid': 'rp2ap', 'rssi': -49 }, ... ]
Network SSID is in the first line of the CSV file, so we can get a list of networks detected during offline training by means of the following code
networks = [x for (x,_,z) in list(dataset)[3:]]
The list of networks to pass to the KNN object must have the same length of pre-trained networks and values must be initialized to an invalid value (for example 100)
found_networks = [0]*len(networks) for i in range(len(found_networks)): found_networks[i] = 100
Assuming that cells contains to Json document received from the PSoC6 board, we can predict the position
for cell in cells: ssid = cell['ssid'] if mac not in networks: continue rssi = cell['rssi'] found_networks[networks.index(mac)] = rssi position = clf.predict([found_networks])[0] pos = Positions[str(position)] pos_x = pos['Position_X'] pos_y = pos['Position_Y']