some fixes [multithreading,..]

needed interface changes [new options]
logger for android
wifi-ap-optimization
new test-cases
This commit is contained in:
2016-09-28 12:19:14 +02:00
parent 91e3367372
commit 4f511d907e
62 changed files with 1418 additions and 175 deletions

View File

@@ -15,11 +15,11 @@ struct GPSData {
float accuracy; // m [might be NAN]
float speed; // m/s [might be NAN]
GPSData() : ts(), lat(NAN), lon(NAN), alt(NAN), accuracy(NAN), speed(NAN) {;}
GPSData() : tsReceived(), lat(NAN), lon(NAN), alt(NAN), accuracy(NAN), speed(NAN) {;}
GPSData(const Timestamp ts, const float lat, const float lon, const float alt) : ts(ts), lat(lat), lon(lon), alt(alt), accuracy(NAN), speed(NAN) {;}
GPSData(const Timestamp tsReceived, const float lat, const float lon, const float alt) : tsReceived(tsReceived), lat(lat), lon(lon), alt(alt), accuracy(NAN), speed(NAN) {;}
GPSData(const Timestamp ts, const float lat, const float lon, const float alt, const float accuracy) : ts(ts), lat(lat), lon(lon), alt(alt), accuracy(accuracy), speed(NAN) {;}
GPSData(const Timestamp tsReceived, const float lat, const float lon, const float alt, const float accuracy) : tsReceived(tsReceived), lat(lat), lon(lon), alt(alt), accuracy(accuracy), speed(NAN) {;}
};

View File

@@ -2,6 +2,8 @@
#define ACCELEROMETERDATA_H
#include <cmath>
#include <sstream>
/** data received from an accelerometer sensor */
struct AccelerometerData {
@@ -40,6 +42,12 @@ struct AccelerometerData {
return AccelerometerData(x/val, y/val, z/val);
}
std::string asString() const {
std::stringstream ss;
ss << "(" << x << "," << y << "," << z << ")";
return ss.str();
}
};
#endif // ACCELEROMETERDATA_H

View File

@@ -2,8 +2,12 @@
#define GYROSCOPEDATA_H
#include <cmath>
#include <sstream>
/** data received from a gyroscope sensor */
/**
* data received from a gyroscope sensor
* IN RADIANS!
*/
struct GyroscopeData {
float x;
@@ -12,12 +16,19 @@ struct GyroscopeData {
GyroscopeData() : x(0), y(0), z(0) {;}
/** ctor from RADIANS */
GyroscopeData(const float x, const float y, const float z) : x(x), y(y), z(z) {;}
float magnitude() const {
return std::sqrt( x*x + y*y + z*z );
}
std::string asString() const {
std::stringstream ss;
ss << "(" << x << "," << y << "," << z << ")";
return ss.str();
}
};
#endif // GYROSCOPEDATA_H

View File

@@ -12,6 +12,7 @@
#include "../radio/WiFiMeasurements.h"
#include "../imu/AccelerometerData.h"
#include "../imu/GyroscopeData.h"
#include "../pressure/BarometerData.h"
template <typename SensorData> struct OfflineEntry {
@@ -41,6 +42,7 @@ public:
virtual void onAccelerometer(const Timestamp ts, const AccelerometerData data) = 0;
virtual void onGravity(const Timestamp ts, const AccelerometerData data) = 0;
virtual void onWiFi(const Timestamp ts, const WiFiMeasurements data) = 0;
virtual void onBarometer(const Timestamp ts, const BarometerData data) = 0;
};
/** read recorded android sensor data files */
@@ -56,6 +58,8 @@ private:
std::vector<OfflineEntry<AccelerometerData>> accel;
std::vector<OfflineEntry<AccelerometerData>> gravity;
std::vector<OfflineEntry<BarometerData>> barometer;
WalkedPath walkedPath;
const char* name = "OfflineData";
@@ -82,6 +86,9 @@ public:
/** get all gravity readings */
const std::vector<OfflineEntry<AccelerometerData>>& getGravity() const {return gravity;}
/** get all barometer readings */
const std::vector<OfflineEntry<BarometerData>>& getBarometer() const {return barometer;}
/** get the walked path */
const WalkedPath& getWalkedPath() const {return walkedPath;}
@@ -171,8 +178,15 @@ private:
break;
}
case 5: {
const BarometerData data = parseBarometer(sensorData);
barometer.push_back(OfflineEntry<BarometerData>(ts, data));
if (listener) {listener->onBarometer(ts, data);}
break;
}
case 8: {
const WiFiMeasurements data = parseWiFi(sensorData);
const WiFiMeasurements data = parseWiFi(ts, sensorData);
wifi.push_back(OfflineEntry<WiFiMeasurements>(ts, data));
if (listener) {listener->onWiFi(ts, data);}
break;
@@ -196,7 +210,7 @@ private:
/** parse the given WiFiObservation string "MAC;freq;RSSI;MAC;freq;RSSI;...." */
static inline WiFiMeasurements parseWiFi(std::string data) {
static inline WiFiMeasurements parseWiFi(const Timestamp ts, std::string data) {
WiFiMeasurements obs;
@@ -219,7 +233,7 @@ private:
Assert::isTrue(data[0] == ';', "unexpected character");
data = data.substr(1);
const WiFiMeasurement e(mac, std::stof(rssi));
const WiFiMeasurement e(mac, std::stof(rssi), ts);
obs.entries.push_back(e);
}
@@ -265,6 +279,16 @@ private:
}
/** parse the given Barometer entry */
static inline BarometerData parseBarometer(const std::string& data) {
BarometerData baro;
const int pos = data.find(';');
baro.hPa = std::stof(data.substr(0, pos));
return baro;
}
/** parse the given GroundTruth entry */
static inline GroundTruthID parseGroundTruthTick(const std::string& data) {

View File

@@ -40,9 +40,9 @@ public:
/** change this values for much success */
const bool additionalLowpassFilter = false;
const int diffSize = 20; //the number values used for finding the activity.
const int diffSize = 20; //the number values used for finding the activity.
const float threshold = 0.025; // if diffSize is getting smaller, treshold needs to be adjusted in the same direction!
Filter::ButterworthLP<float> butter = Filter::ButterworthLP<float>(10,0.05f,2);
Filter::ButterworthLP<float> butter = Filter::ButterworthLP<float>(10,0.1f,2);
Filter::ButterworthLP<float> butter2 = Filter::ButterworthLP<float>(10,0.1f,2);
FixedFrequencyInterpolator<float> ffi = FixedFrequencyInterpolator<float>(Timestamp::fromMS(100));
@@ -89,7 +89,7 @@ public:
if(newInterpolatedValues == true){
//getActivity
if(output.size() > diffSize){
if((int)output.size() > diffSize){
//diff
std::vector<float> diff;
for(int i = output.size() - diffSize; i < output.size() - 1; ++i){

View File

@@ -18,23 +18,23 @@ class WiFiGridEstimator {
public:
/**
* convenience method
*/
template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const Floorplan::IndoorMap* im) {
// /**
// * convenience method
// */
// template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const Floorplan::IndoorMap* im) {
// list of all APs
std::vector<LocatedAccessPoint> aps;
for (const Floorplan::Floor* f : im->floors) {
for (const Floorplan::AccessPoint* ap : f->accesspoints) {
aps.push_back(LocatedAccessPoint(*ap));
}
}
// // list of all APs
// std::vector<LocatedAccessPoint> aps;
// for (const Floorplan::Floor* f : im->floors) {
// for (const Floorplan::AccessPoint* ap : f->accesspoints) {
// aps.push_back(LocatedAccessPoint(*ap));
// }
// }
// perform estimation
estimate(grid, mdl, aps);
// // perform estimation
// estimate(grid, mdl, aps);
}
// }
/**
* perform a signal-strength estimation for all of the given access points
@@ -42,39 +42,50 @@ public:
* store the estimated strength onto each node.
* as nodes only provide a limited number of rssi-entries,
* store only the strongest ones.
*
* as the smartphone is held above the ground, we do NOT want to estimate
* the signal strength for the nodes (on the ground) but for the nodes
* + the height the smartphone is held at
*
*/
template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const std::vector<AccessPoint> aps) {
template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const float smartphoneAtHeight) {
// sanity checks
Assert::isTrue(Node::getMapAPs().empty(), "there are already some processed APs available!");
Assert::isTrue(Node::getMapAPs().empty(), "there are already APs stored on the grid nodes!");
// attach the access-points to the shared node-vector
// all APs known to the model
std::vector<AccessPoint> aps = mdl.getAllAPs();
// attach each access-points to a vector shared for all grid-nodes
for (const AccessPoint& ap : aps) {
Node::getMapAPs().push_back(ap);
}
// smartphone offset (meter above ground)
const Point3 smartphoneOffset(0,0,smartphoneAtHeight);
// process each node
for (Node& n : grid) {
// keep the strongest APs to attach to this node
std::vector<WiFiGridNodeAP> nodeAPs;
// process each AP
// process each AP known to the model
for (int apIdx = 0; apIdx < (int) aps.size(); ++apIdx) {
// estimate the signal-strength
const float rssi = mdl.getRSSI(aps[apIdx].getMAC(), n.inMeter());
const float rssi = mdl.getRSSI(aps[apIdx].getMAC(), n.inMeter() + smartphoneOffset);
// keep it
// (temporarily) keep it
nodeAPs.push_back(WiFiGridNodeAP(apIdx, rssi));
}
// sort all APs by signal strength
// now sort all the visible APs by signal strength
auto comp = [] (const WiFiGridNodeAP& ap1, const WiFiGridNodeAP& ap2) {return ap1.getRSSI() > ap2.getRSSI();};
std::sort(nodeAPs.begin(), nodeAPs.end(), comp);
// attach the strongest X to the node
// and finally attach the strongest X to the node
const int cnt = std::min( n.getMaxAPs(), (int) nodeAPs.size() );
for (int i = 0; i < cnt; ++i) {
n.strongestAPs[i] = nodeAPs[i];

View File

@@ -3,6 +3,7 @@
#include <cstdint>
#include "AccessPoint.h"
#include "../../misc/Debug.h"
/**
* rssi model-estimation for one AP, denoted by its index [among all APs present within the map]
@@ -100,9 +101,11 @@ template <int maxAccessPoints> struct WiFiGridNode {
* returns 0 if unknown
*/
float getRSSI(const MACAddress mac) const {
for (const WiFiGridNodeAP ap : strongestAPs) {
if (!ap.isValid()) {break;} // reached the end
if (getMapAPs()[ap.getAPIdx()].getMAC() == mac) {return ap.getRSSI();}
for (const WiFiGridNodeAP& ap : strongestAPs) {
//std::cout << getMapAPs()[ap.getAPIdx()].getMAC().asString() << std::endl;
//std::cout << mac.asString() << std::endl;
if (!ap.isValid()) {break;} // reached the end of all APs visible on this node
if (getMapAPs()[ap.getAPIdx()].getMAC() == mac) {return ap.getRSSI();} // does this APs MAC match with the requested MAC? -> found!
}
return 0;
}
@@ -124,6 +127,8 @@ template <int maxAccessPoints> struct WiFiGridNode {
protected:
static constexpr const char* name = "WiFiGridNode";
/** serialize static members */
static void staticSerialize(std::ostream& out) {
@@ -134,6 +139,8 @@ protected:
out.write((const char*) &numAPs, sizeof(numAPs));
out.write((const char*) getMapAPs().data(), sizeof(getMapAPs()[0])*numAPs);
Log::add(name, "serialized " + std::to_string(numAPs) + " APs");
}
/** deserialize static members */
@@ -149,6 +156,10 @@ protected:
// deserialize APs within map
inp.read((char*) getMapAPs().data(), sizeof(getMapAPs()[0])*numAPs);
Log::add(name, "de-serialized " + std::to_string(numAPs) + " APs");
std::string aps; for (const AccessPoint& ap : getMapAPs()) {aps += ap.getMAC().asString() + " ";}
Log::add(name, aps);
}

View File

@@ -9,7 +9,7 @@
*/
class WiFiMeasurement {
private:
public:
friend class VAPGrouper;

View File

@@ -13,6 +13,15 @@ struct WiFiMeasurements {
/** all contained measurements */
std::vector<WiFiMeasurement> entries;
/** convert to string */
std::string asString() const {
std::string res;
for (const WiFiMeasurement& m : entries) {
res += m.getAP().getMAC().asString() + ": " + std::to_string(m.getRSSI()) + "\n";
}
return res;
}
};
#endif // WIFIMEASUREMENTS_H

View File

@@ -4,6 +4,7 @@
#include "WiFiProbability.h"
#include "model/WiFiModel.h"
#include "../../math/Distributions.h"
#include "VAPGrouper.h"
#include <unordered_map>
@@ -17,7 +18,7 @@ private:
const float sigma = 8.0f;
const float sigmaPerSecond = 1.5f;
const float sigmaPerSecond = 3.0f;
/** the RSSI prediction model */
WiFiModel& model;
@@ -25,23 +26,25 @@ private:
/** the map's floorplan */
Floorplan::IndoorMap* map;
public:
WiFiObserverFree(const float sigma, WiFiModel& model) : sigma(sigma), model(model) {
}
double getProbability(const Point3& pos, const Timestamp curTime, const WiFiMeasurements& obs) const {
double getProbability(const Point3& pos_m, const Timestamp curTime, const WiFiMeasurements& obs) const {
double prob = 1.0;
int numMatchingAPs = 0;
// process each measured AP
for (const WiFiMeasurement& entry : obs.entries) {
// sanity check
Assert::isFalse(entry.getTimestamp().isZero(), "wifi measurement without timestamp. coding error?");
// get the model's RSSI (if possible!)
const float modelRSSI = model.getRSSI(entry.getAP().getMAC(), pos);
const float modelRSSI = model.getRSSI(entry.getAP().getMAC(), pos_m);
// NaN? -> AP not known to the model -> skip
if (modelRSSI != modelRSSI) {continue;}
@@ -53,21 +56,29 @@ public:
// the measurement's age
const Timestamp age = curTime - entry.getTimestamp();
Assert::isTrue(age.ms() >= 0, "found a negative wifi measurement age. this does not make sense");
Assert::isTrue(age.ms() <= 40000, "found a 40 second old wifi measurement. maybe there is a coding error?");
// sigma grows with measurement age
const float sigma = this->sigma + this->sigmaPerSecond * age.sec();
// update probability
prob *= Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI);
//prob *= Distribution::Region<double>::getProbability(modelRSSI, sigma, scanRSSI);
++numMatchingAPs;
}
// sanity check
Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
return prob;
}
template <typename Node> double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs, const int age_ms = 0) const {
throw "todo??";
throw Exception("todo??");
}
};

View File

@@ -6,14 +6,17 @@
#include "../../math/Distributions.h"
#include "../../data/Timestamp.h"
#include "WiFiGridNode.h"
#include "WiFiProbability.h"
#include <unordered_set>
/**
* probability is calculated by comparing pre-calculated wifi-signal-strengths
* attached to each grid-node with a given WiFiMeasurements data structure
*/
class WiFiObserverGrid : public WiFiProbability {
template <typename Node> class WiFiObserverGrid : public WiFiProbability {
private:
@@ -21,14 +24,23 @@ private:
float sigma = 8.0f;
/** additional sigma-per-second (measurement age) to*/
float sigmaPerSecond = 1.5;
float sigmaPerSecond = 3;
std::unordered_set<MACAddress> knownAPs;
public:
/** ctor with uncertainty */
WiFiObserverGrid(const float sigma) : sigma(sigma) {
;
//StaticAssert::AinheritsB<Node, WiFiGridNode>();
for (const AccessPoint& ap : Node::getMapAPs()) {
knownAPs.insert(ap.getMAC());
}
Assert::isFalse(knownAPs.empty(), "no APs known to the grid nodes?!");
}
/**
@@ -36,43 +48,66 @@ public:
* compares the predicted signal-strengths stored on the given node
* with the provided WiFi measurements
*/
template <typename Node> double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs) const {
double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs) const {
double prob = 0;
// compile-time sanity check. Node must be a subclass off WiFiGridNode
//StaticAssert::AinheritsB<Node, WiFiGridNode>();
double prob = 1;
int numMatchingAPs = 0;
// process each observed measurement
for (const WiFiMeasurement& measurement : obs.entries) {
// if an AP is not known to any of the nodes, just skip it
if (knownAPs.find(measurement.getAP().getMAC()) == knownAPs.end()) {
continue;}
// determine the age for this measurement
const Timestamp age = curTime - measurement.getTimestamp();
// sigma grows with measurement age
const float sigma = this->sigma + this->sigmaPerSecond * age.sec();
float sigma = this->sigma + this->sigmaPerSecond * age.sec();
// the RSSI from the scan
const float measuredRSSI = measurement.getRSSI();
// the RSSI from the model (if available!)
const float modelRSSI = n.getRSSI(measurement.getAP().getMAC());
float modelRSSI = n.getRSSI(measurement.getAP().getMAC());
// no model RSSI available?
if (modelRSSI == 0) {continue;}
// if no model RSSI is available, that means,
// the AP in question is not / only barely visible at this location
// assume a very low signal-strength and increase the sigma
if (modelRSSI == 0) {
modelRSSI = -100;
sigma *= 2;
}
// compare both
const double p = Distribution::Normal<double>::getProbability(measuredRSSI, sigma, modelRSSI);
//const double p = Distribution::Region<double>::getProbability(measuredRSSI, sigma, modelRSSI);
// adjust using log
prob += std::log(p);
//prob += std::log(p);
prob *= p;
++numMatchingAPs;
}
//return std::pow(std::exp(prob), 0.1);
return std::exp(prob);
// sanity check
// Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
// if (numMatchingAPs == 0) {return 0;}
// as not every node has the same number of visible/matching APs
// we MUST return something like the average probability
return prob;
//return std::pow(prob, 1.0/3.0);
}
/** gnuplot debug dump */
template <typename Node> void dump(Grid<Node>& grid, const Timestamp curTime, const WiFiMeasurements& obs, const std::string& fileName) {
void dump(Grid<Node>& grid, const Timestamp curTime, const WiFiMeasurements& obs, const std::string& fileName) {
std::ofstream out(fileName);
out << "splot '-' with points palette\n";

View File

@@ -16,6 +16,9 @@ public:
// /** get the given access-point's RSSI at the provided location */
// virtual float getRSSI(const LocatedAccessPoint& ap, const Point3 p) = 0;
/** get a list of all APs known to the model */
virtual std::vector<AccessPoint> getAllAPs() const = 0;
/**
* get the RSSI expected at the given location (in meter)
* for an AP identified by the given MAC.

View File

@@ -36,6 +36,13 @@ public:
;
}
/** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const {
std::vector<AccessPoint> aps;
for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));}
return aps;
}
/** make the given AP (and its parameters) known to the model */
void addAP(const MACAddress& accessPoint, const APEntry& params) {

View File

@@ -39,9 +39,12 @@ private:
public:
/** ctor */
/** ctor with floorplan (needed for ceiling position) */
WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) {
// sanity checks
Assert::isTrue(map->floors.size() >= 1, "map has no floors?!");
// position of all ceilings
for (Floorplan::Floor* f : map->floors) {
ceilingsAtHeight_m.push_back(f->atHeight);
@@ -49,6 +52,25 @@ public:
}
/** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const {
std::vector<AccessPoint> aps;
for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));}
return aps;
}
/** load AP information from the floorplan. use the given fixed TXP/EXP/WAF for all APs */
void loadAPs(const Floorplan::IndoorMap* map, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f) {
for (const Floorplan::Floor* floor : map->floors) {
for (const Floorplan::AccessPoint* ap : floor->accesspoints) {
const APEntry ape(ap->getPos(floor), txp, exp, waf);
addAP(MACAddress(ap->mac), ape);
}
}
}
/** make the given AP (and its parameters) known to the model */
void addAP(const MACAddress& accessPoint, const APEntry& params) {
@@ -57,11 +79,18 @@ public:
Assert::isBetween(params.txp, -50.0f, -30.0f, "TXP out of bounds [-50:-30]");
Assert::isBetween(params.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]");
Assert::equal(accessPoints.find(accessPoint), accessPoints.end(), "AccessPoint already present!");
// add
accessPoints.insert( std::pair<MACAddress, APEntry>(accessPoint, params) );
}
/** remove all added APs */
void clear() {
accessPoints.clear();
}
float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
// try to get the corresponding parameters
@@ -90,6 +119,28 @@ public:
protected:
FRIEND_TEST(LogDistanceCeilingModel, numCeilings);
FRIEND_TEST(LogDistanceCeilingModel, numCeilingsFloat);
/** get the number of ceilings between z1 and z2 */
float numCeilingsBetweenFloat(const Point3 pos1, const Point3 pos2) const {
const float zMin = std::min(pos1.z, pos2.z);
const float zMax = std::max(pos1.z, pos2.z);
float cnt = 0;
for (const float z : ceilingsAtHeight_m) {
if (zMin < z && zMax > z) {
const float dmax = zMax - z;
cnt += (dmax > 1) ? (1) : (dmax);
}
}
return cnt;
}
/** get the number of ceilings between z1 and z2 */
int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const {
@@ -98,6 +149,24 @@ protected:
const float zMin = std::min(pos1.z, pos2.z);
const float zMax = std::max(pos1.z, pos2.z);
#ifdef WITH_ASSERTIONS
static int numNear = 0;
static int numFar = 0;
for (const float z : ceilingsAtHeight_m) {
const float diff = std::min( std::abs(z-zMin), std::abs(z-zMax) );
if (diff < 0.1) {++numNear;} else {++numFar;}
}
if ((numNear + numFar) > 150000) {
Assert::isTrue(numNear < numFar*0.1,
"many requests to the WiFiModel address nodes (very) near to a ground! \
due to rounding issues, determining the number of floors between AP and point-in-question is NOT possible! \
expect very wrong outputs! \
consider adding the person's height to the questioned positions: p += Point3(0,0,1.3) "
);
}
#endif
for (const float z : ceilingsAtHeight_m) {
if (zMin < z && zMax > z) {++cnt;}
}

View File

@@ -0,0 +1,94 @@
#ifndef WIFIFINGERPRINT_H
#define WIFIFINGERPRINT_H
#include "../../../geo/Point3.h"
#include "../WiFiMeasurements.h"
#include <unordered_map>
/**
* denotes a wifi fingerprint
* known position and several measurements conducted at this position
*
* as several measurements were conducted, each AP is usually contained more than once!
*/
struct WiFiFingerprint {
/** real-world-position that was measured */
Point3 pos_m;
/** measurements (APs) at the given location */
WiFiMeasurements measurements;
/** ctor */
WiFiFingerprint() {;}
/** ctor */
WiFiFingerprint(const Point3 pos_m) : pos_m(pos_m) {;}
/** as each AP is contained more than once (scanned more than once), group them by MAC and use the average RSSI */
WiFiMeasurements average() {
// group scans by MAC (all measurements for one AP)
std::unordered_map<MACAddress, WiFiMeasurements> group;
for (WiFiMeasurement& m : measurements.entries) {
group[m.getAP().getMAC()].entries.push_back(m);
}
// create the output that contains the AP's average
WiFiMeasurements res;
for (auto& it : group) {
const WiFiMeasurements& apMeasurements = it.second;
WiFiMeasurement avg = apMeasurements.entries.front(); // average starts with a copy of the first entry (to get all data-fields beside the rssi)
for (int i = 1; i < (int)apMeasurements.entries.size(); ++i) { // sum up all other entries [1:end]
avg.rssi += apMeasurements.entries[i].rssi;
}
avg.rssi /= apMeasurements.entries.size();
res.entries.push_back(avg); // add to output
}
// done
return res;
}
/** serialize */
void write(std::ostream& out) const {
out << "pos: " << pos_m.x << " " << pos_m.y << " " << pos_m.z << "\n";
out << "num: " << measurements.entries.size() << "\n";
for (const WiFiMeasurement& wm : measurements.entries) {
out << wm.getTimestamp().ms() << " " << wm.ap.getMAC().asString() << " " << wm.getRSSI() << "\n";
}
}
/** deserialize */
void read(std::istream& inp) {
std::string tmp;
// read the position
inp >> tmp; if ("pos:" != tmp) {throw "error";}
inp >> pos_m.x >> pos_m.y >> pos_m.z;
// number of entries
inp >> tmp; if ("num:" != tmp) {throw "error";}
int numEntries; inp >> numEntries;
// read the entries
for (int i = 0; i < numEntries; ++i) {
uint64_t ms; inp >> ms;
std::string mac; inp >> mac;
float rssi; inp >> rssi;
WiFiMeasurement wm(AccessPoint(MACAddress(mac)), rssi, Timestamp::fromMS(ms));
measurements.entries.push_back(wm);
}
}
};
#endif // WIFIFINGERPRINT_H

View File

@@ -0,0 +1,247 @@
#ifndef OPTIMIZER_H
#define OPTIMIZER_H
#include "../../../floorplan/v2/Floorplan.h"
#include "../../../floorplan/v2/FloorplanHelper.h"
#include "../VAPGrouper.h"
#include "../../../geo/BBox3.h"
#include "../../../misc/Debug.h"
#include "WiFiFingerprint.h"
#include "../model/WiFiModel.h"
#include "../model/WiFiModelLogDistCeiling.h"
#include <KLib/math/optimization/NumOptAlgoDownhillSimplex.h>
#include <KLib/math/optimization/NumOptAlgoGenetic.h>
#include <KLib/math/optimization/NumOptAlgoRangeRandom.h>
#include <string>
#include <sstream>
struct WiFiOptimizer {
private:
/** combine one RSSI measurement with the position the signal was measured at */
struct RSSIatPosition {
/** real-world position (in meter) */
const Point3 pos_m;
/** measured signal strength (for one AP) */
const float rssi;
/** ctor */
RSSIatPosition(const Point3 pos_m, const float rssi) : pos_m(pos_m), rssi(rssi) {;}
};
public:
struct APParams {
float x;
float y;
float z;
float txp;
float exp;
float waf;
Point3 getPos() const {return Point3(x,y,z);}
APParams() {;}
APParams(float x, float y, float z, float txp, float exp, float waf) : x(x), y(y), z(z), txp(txp), exp(exp), waf(waf) {;}
std::string asString() {
std::stringstream ss;
ss << "Pos:" << getPos().asString() << " TXP:" << txp << " EXP:" << exp << " WAF:" << waf;
return ss.str();
}
};
/** add MAC-info to params */
struct APParamsMAC {
MACAddress mac;
APParams params;
APParamsMAC(const MACAddress mac, const APParams& params) : mac(mac), params(params) {;}
};
private:
Floorplan::IndoorMap* map;
const VAPGrouper vg;
/** each MAC-Adress has several position->rssi entries */
std::unordered_map<MACAddress, std::vector<RSSIatPosition>> apMap;
const char* name = "WiFiOptimizer";
public:
/** ctor */
WiFiOptimizer(Floorplan::IndoorMap* map, const VAPGrouper& vg) : map(map), vg(vg) {
;
}
/** add a new fingerprint to the optimizers data-source */
void addFingerprint(const WiFiFingerprint& fp) {
// group the fingerprint's measurements by VAP (if configured)
const WiFiMeasurements measurements = vg.group(fp.measurements);
// add each available AP to its slot (lookup map)
for (const WiFiMeasurement& m : measurements.entries) {
const RSSIatPosition rap(fp.pos_m, m.rssi);
apMap[m.getAP().getMAC()].push_back(rap);
}
}
/** get a list of all to-be-optimized access-points (given by their mac-address) */
std::vector<MACAddress> getAllMACs() const {
std::vector<MACAddress> res;
for (const auto& it : apMap) {res.push_back(it.first);}
return res;
}
/** optimize all known APs */
std::vector<APParamsMAC> optimizeAll() const {
// sanity chekc
Assert::isFalse(getAllMACs().empty(), "no APs found for optimization!");
float errSum = 0;
std::vector<APParamsMAC> res;
for (const MACAddress& mac : getAllMACs()) {
float err;
const APParams params = optimize(mac, err);
res.push_back(APParamsMAC(mac, params));
errSum += err;
}
const float avgErr = errSum / getAllMACs().size();
Log::add(name, "average AP error is: " + std::to_string(avgErr) + " dB");
return res;
}
/** optimize the given AP */
APParams optimize(const MACAddress& mac, float& errResult) const {
// starting parameters do not matter for the current optimizer!
APParams params(0,0,0, -40, 2.5, -4.0);
constexpr float hugeError = 1e10;
// get all position->rssi measurements for this AP to compare them with the corresponding model estimations
const std::vector<RSSIatPosition>& entries = apMap.find(mac)->second;
// signal-strength-prediction-model...
WiFiModelLogDistCeiling model(map);
auto func = [&] (const float* data) {
const APParams* params = (APParams*) data;
// some sanity checks
if (params->waf > 0) {return hugeError;}
if (params->txp < -50) {return hugeError;}
if (params->txp > -30) {return hugeError;}
if (params->exp > 4) {return hugeError;}
if (params->exp < 1) {return hugeError;}
// current position guess for the AP;
const Point3 apPos_m = params->getPos();
// add the AP [described by the current guess] to the signal-strength-prediction model
model.clear();
model.addAP(mac, WiFiModelLogDistCeiling::APEntry(apPos_m, params->txp, params->exp, params->waf));
float err = 0;
int cnt = 0;
// process each measurement
for (const RSSIatPosition& reading : entries) {
// get the model-estimation for the fingerprint's position
const float rssiModel = model.getRSSI(mac, reading.pos_m);
// difference between estimation and measurement
const float diff = std::abs(rssiModel - reading.rssi);
// adjust the error
err += diff*diff;
++cnt;
// max distance penality
// [unlikely to get a reading for this AP here!]
if (apPos_m.getDistance(reading.pos_m) > 150) {err += 999999;}
}
err /= cnt;
err = std::sqrt(err);
if (params->txp < -50) {err += 999999;}
if (params->txp > -35) {err += 999999;}
if (params->exp > 3.5) {err += 999999;}
if (params->exp < 1.0) {err += 999999;}
return err;
};
//
const BBox3 mapBBox = FloorplanHelper::getBBox(map);
using LeOpt = K::NumOptAlgoRangeRandom<float>;
const std::vector<LeOpt::MinMax> valRegion = {
LeOpt::MinMax(mapBBox.getMin().x - 20, mapBBox.getMax().x + 20), // x
LeOpt::MinMax(mapBBox.getMin().y - 20, mapBBox.getMax().y + 20), // y
LeOpt::MinMax(mapBBox.getMin().z - 5, mapBBox.getMax().z + 5), // z
LeOpt::MinMax(-50,-30), // txp
LeOpt::MinMax(1,3), // exp
LeOpt::MinMax(-10,-4), // waf
};
// log
Log::add(name, "optimizing parameters for AP " + mac.asString() + " by using " + std::to_string(entries.size()) + " fingerprints", false);
Log::tick();
LeOpt opt(valRegion);
opt.setPopulationSize(500); // USE MORE FOR PRODUCTION
opt.setNumIerations(150);
opt.calculateOptimum(func, (float*) &params);
// using LeOpt = K::NumOptAlgoGenetic<float>;
// LeOpt opt(6);
// opt.setPopulationSize(750);
// opt.setMaxIterations(50);
// opt.setElitism(0.05f);
// opt.setMutation(0.75f);
// //opt.setValRange({0.5, 0.5, 0.5, 0.1, 0.1, 0.1});
// opt.setValRegion(valRegion);
// K::NumOptAlgoDownhillSimplex<float, 6> opt;
// opt.setMaxIterations(100);
// opt.setNumRestarts(10);
opt.calculateOptimum(func, (float*) &params);
errResult = func((float*)&params);
Log::tock();
Log::add(name, mac.asString() + ": " + params.asString() + " @ " + std::to_string(errResult) +" dB err");
return params;
}
};
#endif // OPTIMIZER_H