This repository contains the code created for the paper "Smartphone-Based Real-Time Indoor Positioning Using BLE Beacons", which can be accessed here: https://ieeexplore.ieee.org/document/9926639.
There are four top-level directories: measurements-replay, plotting, positioning-app, and positioning-backend. The measurements-replay directory contains the Java code used to explore different parameter combinations by replaying RSSI measurements. The plotting directory contains all the experimental data, as well as the Python code used to process and plot this data. The positioning-app directory contains the Java code for the Android Application, and the positioning-backend directory contains the code for the Express RESTful API and the databases.
To run the back-end, Docker and Docker Compose are required. When Docker Compose is installed, simply run docker-compose up -d
from the positioning-backend directory to run all containers defined in docker-compose.yml. This will start the Express RESTful API, as well as a Cassandra database instance and a MongoDB database instance.
To run the Android Application, Android Studio has to be installed and setup. When Android Studio is installed, simply open the positioning-app directory as a new project, and update BASE_URL
from RetrofitClient.java to the URL/IP corresponding to your instance of the Express API.
To install the packages used for plotting, run python -m pip install -r requirements.txt
from the plotting directory.
The architecture of the indoor positioning system consists of four main components: the Android application, the Express RESTful API, the Cassandra database and the MongoDB database. These components are shown in the architecture diagram, and elaborated upon in the following subsections.
The Android application continuously scans for Bluetooth Low-Energy (BLE) beacons. When beacons are detected, they are displayed in the beacons list. The estimated position of the smartphone is also continuously calculated using the different positioning methods. The results of these calculations are listed in the "Positioning" tab.
The app bar contains a “Start Recording” button in the top right. When this button is pressed the application starts recording the Received Signal Strength Indicator (RSSI) values for the detected beacons along with their timestamps, the calculated distances, the estimated position (using the configurable default positioning method), and an advertising channel estimation. These measurements are send to the Express back-end.
Furthermore, when recording is started, the positioning tab changes to the state in the third image. A "Checkpoint" button is now visible along with a checkpoint counter. Whenever the checkpoint button is pressed the checkpoint counter is incremented, and the last checkpoint timestamp is send to the back-end. Finally, when the user is done recording they can tap the button in the app bar again to stop recording.
All the parameters related to the measurements window, RSSI filtering, distance estimation, positioning and more can be configured in the settings of the application.
The Express back-end server is currently mainly in place to serve as a RESTful API that provides a CRUD interface for the two databases. A short summary of the implemented endpoints is given below.
Endpoint | Description |
---|---|
GET /beacons |
Retrieve beacon information for all beacons |
GET /beacons/rssi |
Retrieve all beacon measurements |
GET /beacons/{beaconAddress} |
Retrieve beacon information for a specific beacon |
GET /beacons/{beaconAddress}/rssi |
Retrieve beacon measurements for a specific beacon |
POST /beacons |
Create a new beacon |
POST /beacons/{beaconAddress}/rssi |
Create a new beacon measurement |
PUT /beacons/{beaconAddress} |
Update/create a beacon |
DELETE /beacons |
Delete all beacons |
DELETE /beacons/rssi |
Delete all beacon measurements |
DELETE /beacons/{beaconAddress} |
Delete a specific beacon |
DELETE /beacons/{beaconAddress}/rssi |
Delete measurements for a specific beacon |
Endpoint | Description |
---|---|
GET /positioning |
Retrieve all predicted coordinates |
GET /positioning/checkpoints |
Retrieve all checkpoint timestamps |
POST /positioning |
Create new predicted coordinates |
POST /positioning/checkpoints |
Create a new checkpoint timestamp |
DELETE /positioning |
Delete all predicted coordinates |
DELETE /positioning/checkpoints |
Delete all checkpoint timestamps |
NOTE: These endpoints are implemented, but not used by the Android Application.
Endpoint | Description |
---|---|
GET /points-of-interest |
Retrieve all points of interest |
GET /points-of-interest/{id} |
Retrieve a specific point of interest |
POST /points-of-interest |
Create a new point of interest |
PUT /points-of-interest/{id} |
Update/create a point of interest |
DELETE /points-of-interest |
Delete all points of interest |
DELETE /points-of-interest/{id} |
Delete a specific point of interest |
There are two databases in which data send to the API is stored. A Cassandra database, and a MongoDB database.
The Cassandra database is used to store time-series data. Specifically, the beacon measurements, predicted coordinates and checkpoint timestamps. These are stored in three separate tables. The schemas are defined in schema.cql. The beacon measurements are stored in the measurements_by_beacon
table, which has the following schema:
CREATE TABLE IF NOT EXISTS beacons.measurements_by_beacon (
beacon_address text,
timeuuid timeuuid,
rssi int,
distance double,
channel int,
PRIMARY KEY ((beacon_address), timeuuid) )
WITH CLUSTERING ORDER BY (timeuuid DESC);
The predicted coordinates are stored in the predicted_coordinates
table, with the following schema:
CREATE TABLE IF NOT EXISTS positioning.predicted_coordinates (
timeuuid timeuuid,
x int,
y int,
confidence double,
PRIMARY KEY (timeuuid)
);
As evident from the schema, the confidence indicator is stored together with the coordinates.
Lastly, the checkpoint timestamps are stored in the checkpoint_timestamps
table, with the following schema:
CREATE TABLE IF NOT EXISTS positioning.checkpoint_timestamps (
timeuuid timeuuid,
checkpoint int,
PRIMARY KEY (timeuuid)
);
For the final component we have the MongoDB database. The MongoDB database has two so-called collections, one for the beacon information, and one for the points of interest. The beacon information consists of the beacon address (Bluetooth MAC address) serving as a unique identifier, the TX power (transmission power) and the beacon coordinates (x, y), as defined in beacon.js by the following Mongoose schema:
const BeaconSchema = mongoose.Schema({
beaconAddress: { type: String, required: true, index: true, unique: true },
txPower: Number,
coordinates: {
x: { type: Number, required: true },
y: { type: Number, required: true }
}
});
The points of interest are defined in point-of-interest.js by a name, description, coordinates and a radius:
const PointOfInterestSchema = mongoose.Schema({
name: { type: String, required: true },
description: String,
coordinates: {
type: {
x: { type: Number, required: true },
y: { type: Number, required: true }
},
required: true
},
radius: { type: Number, required: true }
});