WiFi Coverage Map

Mapping WiFi Coverage Map with Gaussian Processes

In this case study, we built an application with the BeeCluster API to create a WiFi coverage map of an open area. Our application adapts an efficient information gathering algorithm based on Gaussian Processes [Paper].

In our setup, the algorithm first divides the region of interest into an equal sized grid (i.e., 5 meters by 5 meters). Then, the algorithm issues tasks (using the newTask() primitive) to request a WiFi signal strength measurement from all grid cells (in parallel). When the WiFi signal strength is measured at a grid, the algorithm updates the Gaussian Process model (which models the WiFi signal strength field) and updates the uncertainty estimate on all other grids. If the uncertainty estimation of a grid falls below a threshold, the measurement request in that grid is cancelled (using the cancelTask() primitive). The algorithm stops when all the grid cells are measured or have satisfied the uncertainty threshold.

In this case study, we demonstrate the predictive optimization feature of BeeCluster. BeeCluster forecasts the task cancellation behavior of the application and uses this forecast information to minimize the execution time of the application. Below, we show a demo on a real-world evaluation where we map the WiFi coverage map from a single WiFi hotspot in an open area. The baseline approach in this demo uses a simple greedy routing algorithm without taking any future information into account.



Walking through the code

We show a simplied version of this application in testcases/testapp1.py. This version can run in the simulator without real drones.

In the main function of this application, we create a task for each grid and store the mappign from grid location to task name in a python dictionary (loc2taskname). Here, both locs and loc2taskname are global variables.

bc = beecluster.Session(appID = "testapp1")
# generate all the tasks (grid_size * grid_size)
for i in range(len(locs)):
loc = (locs[i][0], locs[i][1], 5)
h = bc.newTask(SenseAndUpdate, bc, loc, NonInterruptible = True, SameDrone = True)
loc2taskname[loc] = h.getTaskName()
bc.waitUnfinishedTasks()
bc.close(SaveDTG=False)

Next, we show the source code of SenseAndUpdate(). Inside SenseAndUpdate(), we first fly the virtual drone to the target location (bc.act("flyto", loc)), then we measure the wifi signal strength three times and compute the average value.

def SenseAndUpdate(bc, loc):
global sample_loc # store the location of each sample
global sample_value # store the value of each samples
global loc2taskname # store the mapping from location to task name
bc.act("flyto", loc)
h1 = bc.act("wifi:sense", None)
h2 = bc.act("wifi:sense", None)
h3 = bc.act("wifi:sense", None)
value = (float(h1.val) + float(h2.val) + float(h3.val))/3.0
print(loc, value)

After this, between bc.Lock() and bc.UnLock(), we update the Gaussian Process model with the new samples and compute the locations where we don't need to collect samples. This code block is locked because all the task threads are running in parallel and we want to update the Gaussian process model sequentially (by design). Of course, the lock is not necessary if we only have one drone, but in a more general case, the code could run with different numbers of drones in the backend. So we do need the lock.

# continue from the previous code block
bc.Lock()
sample_loc.append((loc[0], loc[1]))
sample_value.append(value)
removed_tasks = GaussianProcessUpdate()
bc.UnLock()

In the last part of the function, we use the bc.cancelTask() API to cancel all the tasks that don't need to execute. Here, we use the loc2taskname dictionary to convert locations to their corresponding tasks (defined in the main function).

# continue from the previous code block
if removed_tasks is not None:
cancel_list = [loc2taskname[(locs[removed_tasks[0][i]][0], locs[removed_tasks[0][i]][1], 5)] for i in range(len(removed_tasks[0]))]
bc.cancelTask(cancel_list)
return