merge
This commit is contained in:
@@ -45,6 +45,11 @@ public:
|
||||
;
|
||||
}
|
||||
|
||||
/** equals? */
|
||||
bool operator == (const AccessPoint& o) {
|
||||
return (o.mac == mac) && (o.ssid == ssid);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/** get the AP's MAC address */
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef LOCATEDACCESSPOINT_H
|
||||
#define LOCATEDACCESSPOINT_H
|
||||
|
||||
#include "AccessPoint.h"
|
||||
#include "../../geo/Point3.h"
|
||||
#include "../../floorplan/v2/Floorplan.h"
|
||||
|
||||
/**
|
||||
* describes an access-point including its position (in meter)
|
||||
*/
|
||||
class LocatedAccessPoint : public AccessPoint, public Point3 {
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
LocatedAccessPoint(const std::string& mac, const Point3 pos_m) : AccessPoint(mac, ""), Point3(pos_m) {
|
||||
;
|
||||
}
|
||||
|
||||
/** ctor */
|
||||
LocatedAccessPoint(const Floorplan::AccessPoint& ap) : AccessPoint(ap.mac, ap.name), Point3(ap.pos) {
|
||||
;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // LOCATEDACCESSPOINT_H
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "../../math/Stats.h"
|
||||
|
||||
#include "../../misc/Debug.h"
|
||||
|
||||
#include "WiFiMeasurements.h"
|
||||
|
||||
class VAPGrouper {
|
||||
@@ -40,24 +42,35 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
static constexpr const char* name = "VAPGrp";
|
||||
|
||||
/** the mode to use for grouping VAPs */
|
||||
const Mode mode;
|
||||
|
||||
/** the signal-strength aggregation algorithm to use */
|
||||
const Aggregation agg;
|
||||
|
||||
/** respect only outputs with at-least X occurences of one physical hardware [can be used to prevent issues] */
|
||||
int minOccurences;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
VAPGrouper(const Mode mode, const Aggregation agg) : mode(mode), agg(agg) {
|
||||
|
||||
VAPGrouper(const Mode mode, const Aggregation agg, const int minOccurences = 2) :
|
||||
mode(mode), agg(agg), minOccurences(minOccurences) {
|
||||
;
|
||||
}
|
||||
|
||||
/** set the number of needed occurences per VAP-group to be accepted */
|
||||
void setMinOccurences(const int min) {
|
||||
this->minOccurences = min;
|
||||
}
|
||||
|
||||
/** get a vap-grouped version of the given input */
|
||||
WiFiMeasurements group(const WiFiMeasurements& original) const {
|
||||
|
||||
// first, group all VAPs into a vector [one vector per VAP-group]
|
||||
// by using the modified MAC address that all VAPs have in common
|
||||
std::unordered_map<MACAddress, std::vector<WiFiMeasurement>> grouped;
|
||||
|
||||
for (const WiFiMeasurement& m : original.entries) {
|
||||
@@ -68,8 +81,9 @@ public:
|
||||
|
||||
}
|
||||
|
||||
// output
|
||||
// to-be-constructed output
|
||||
WiFiMeasurements result;
|
||||
int skipped = 0;
|
||||
|
||||
// perform aggregation on each VAP-group
|
||||
for (auto it : grouped) {
|
||||
@@ -77,6 +91,9 @@ public:
|
||||
const MACAddress& base = it.first;
|
||||
const std::vector<WiFiMeasurement>& vaps = it.second;
|
||||
|
||||
// remove entries that do not satify the min-occurences metric
|
||||
if ((int)vaps.size() < minOccurences) {++skipped; continue;}
|
||||
|
||||
// group all VAPs into one measurement
|
||||
const WiFiMeasurement groupedMeasurement = groupVAPs(base, vaps);
|
||||
|
||||
@@ -85,6 +102,13 @@ public:
|
||||
|
||||
}
|
||||
|
||||
// debug
|
||||
Log::add(name,
|
||||
"grouped " + std::to_string(original.entries.size()) + " measurements " +
|
||||
"into " + std::to_string(result.entries.size()) + " [omitted: " + std::to_string(skipped) + "]",
|
||||
true
|
||||
);
|
||||
|
||||
// done
|
||||
return result;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
class WiFiMeasurement {
|
||||
|
||||
public:
|
||||
private:
|
||||
|
||||
friend class VAPGrouper;
|
||||
|
||||
@@ -19,32 +19,55 @@ public:
|
||||
/** the measured signal strength */
|
||||
float rssi;
|
||||
|
||||
/** OPTIONAL. frequence the signal was received */
|
||||
float freq = NAN;
|
||||
|
||||
/** OPTIONAL. timestamp the measurement was recorded at */
|
||||
Timestamp ts;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
WiFiMeasurement(const AccessPoint& ap, const float rssi) : ap(ap), rssi(rssi) {
|
||||
WiFiMeasurement(const AccessPoint& ap, const float rssi) : ap(ap), rssi(rssi), freq(NAN) {
|
||||
;
|
||||
}
|
||||
|
||||
/** ctor with timestamp */
|
||||
WiFiMeasurement(const AccessPoint& ap, const float rssi, const Timestamp ts) : ap(ap), rssi(rssi), ts(ts) {
|
||||
;
|
||||
}
|
||||
/** ctor with freq*/
|
||||
WiFiMeasurement(const AccessPoint& ap, const float rssi, const float freq) : ap(ap), rssi(rssi), freq(freq) {
|
||||
;
|
||||
}
|
||||
|
||||
/** ctor with timestamp */
|
||||
WiFiMeasurement(const AccessPoint& ap, const float rssi, const Timestamp ts) : ap(ap), rssi(rssi), ts(ts) {
|
||||
;
|
||||
}
|
||||
|
||||
/** ctor with timestamp and freq*/
|
||||
WiFiMeasurement(const AccessPoint& ap, const float rssi, const float freq, const Timestamp ts) : ap(ap), rssi(rssi), freq(freq), ts(ts) {
|
||||
;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
/** get the AP we got the measurement for */
|
||||
const AccessPoint& getAP() const {return ap;}
|
||||
/** get the AP we got the measurement for */
|
||||
const AccessPoint& getAP() const {return ap;}
|
||||
|
||||
/** get the measurement's signal strength */
|
||||
float getRSSI() const {return rssi;}
|
||||
/** get the measurement's signal strength */
|
||||
float getRSSI() const {return rssi;}
|
||||
|
||||
/** OPTIONAL: get the measurement's timestamp (if known!) */
|
||||
const Timestamp& getTimestamp() const {return ts;}
|
||||
/** OPTIONAL: get the measurement's timestamp (if known!) */
|
||||
const Timestamp& getTimestamp() const {return ts;}
|
||||
|
||||
/** OPTIONAL: get the measurement's frequence (if known!) */
|
||||
float getFrequency() const {return freq;}
|
||||
|
||||
/** set another signal strength */
|
||||
void setRssi(float value){rssi = value;}
|
||||
|
||||
/** set the timestamp */
|
||||
void setTimestamp(const Timestamp& val){ts = val;}
|
||||
};
|
||||
|
||||
|
||||
|
||||
#endif // WIFIMEASUREMENT_H
|
||||
|
||||
@@ -22,6 +22,56 @@ struct WiFiMeasurements {
|
||||
return res;
|
||||
}
|
||||
|
||||
/** get the measurements for the given MAC [if available] otherwise null */
|
||||
const WiFiMeasurement* getForMac(const MACAddress& mac) const {
|
||||
for (const WiFiMeasurement& m : entries) {
|
||||
if (m.getAP().getMAC() == mac) {
|
||||
return &m;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/** remove the entry for the given MAC (if any) */
|
||||
void remove(const MACAddress& mac) {
|
||||
for (size_t i = 0; i < entries.size(); ++i) {
|
||||
if (entries[i].getAP().getMAC() == mac) {
|
||||
entries.erase(entries.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** create a combination */
|
||||
static WiFiMeasurements mix(const WiFiMeasurements& a, const WiFiMeasurements& b, float sec = 3) {
|
||||
|
||||
Timestamp max;
|
||||
WiFiMeasurements res;
|
||||
|
||||
for (const WiFiMeasurement& m : a.entries) {
|
||||
res.entries.push_back(m);
|
||||
if (m.getTimestamp() > max) {max = m.getTimestamp();}
|
||||
}
|
||||
|
||||
for (const WiFiMeasurement& m : b.entries) {
|
||||
res.entries.push_back(m);
|
||||
if (m.getTimestamp() > max) {max = m.getTimestamp();}
|
||||
}
|
||||
|
||||
std::vector<WiFiMeasurement> tmp;
|
||||
std::swap(res.entries, tmp);
|
||||
|
||||
for (const WiFiMeasurement& m : tmp) {
|
||||
if ((max - m.getTimestamp()).sec() < sec) {
|
||||
res.entries.push_back(m);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIMEASUREMENTS_H
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "model/WiFiModel.h"
|
||||
#include "../../math/Distributions.h"
|
||||
#include "VAPGrouper.h"
|
||||
#include "../../floorplan/v2/Floorplan.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -16,7 +17,7 @@ class WiFiObserverFree : public WiFiProbability {
|
||||
|
||||
private:
|
||||
|
||||
const float sigma = 8.0f;
|
||||
const float sigma;
|
||||
|
||||
const float sigmaPerSecond = 3.0f;
|
||||
|
||||
@@ -26,15 +27,25 @@ private:
|
||||
/** the map's floorplan */
|
||||
Floorplan::IndoorMap* map;
|
||||
|
||||
std::vector<AccessPoint> allAPs;
|
||||
|
||||
bool useError = false;
|
||||
|
||||
public:
|
||||
|
||||
WiFiObserverFree(const float sigma, WiFiModel& model) : sigma(sigma), model(model) {
|
||||
allAPs = model.getAllAPs();
|
||||
}
|
||||
|
||||
void setUseError(const bool useError) {
|
||||
this->useError = useError;
|
||||
}
|
||||
|
||||
double getProbability(const Point3& pos_m, const Timestamp curTime, const WiFiMeasurements& obs) const {
|
||||
|
||||
double prob = 1.0;
|
||||
//double prob = 0;
|
||||
|
||||
int numMatchingAPs = 0;
|
||||
|
||||
// process each measured AP
|
||||
@@ -49,7 +60,6 @@ public:
|
||||
// NaN? -> AP not known to the model -> skip
|
||||
if (modelRSSI != modelRSSI) {continue;}
|
||||
|
||||
|
||||
// the scan's RSSI
|
||||
const float scanRSSI = entry.getRSSI();
|
||||
|
||||
@@ -57,28 +67,85 @@ public:
|
||||
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?");
|
||||
Assert::isTrue(age.ms() <= 60000, "found a 60 second old wifi measurement. maybe there is a coding error?");
|
||||
|
||||
Assert::isBetween(scanRSSI, -100.0f, -30.0f, "scan-rssi out of range");
|
||||
//Assert::isBetween(modelRSSI, -160.0f, -10.0f, "model-rssi out of range");
|
||||
|
||||
// sigma grows with measurement age
|
||||
const float sigma = this->sigma + this->sigmaPerSecond * age.sec();
|
||||
|
||||
// probability for this AP
|
||||
double local = Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI);
|
||||
//double local = Distribution::Exponential<double>::getProbability(0.1, std::abs(modelRSSI-scanRSSI));
|
||||
|
||||
// also add the error value? [location is OK but model is wrong]
|
||||
if (useError) {
|
||||
local = 0.95 * local + 0.05 * (1.0-local);
|
||||
//local = 0.95 * local + 0.05;
|
||||
}
|
||||
|
||||
// update probability
|
||||
prob *= Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI);
|
||||
//prob *= Distribution::Region<double>::getProbability(modelRSSI, sigma, scanRSSI);
|
||||
prob *= local;
|
||||
|
||||
++numMatchingAPs;
|
||||
|
||||
}
|
||||
|
||||
// sanity check
|
||||
Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
|
||||
//Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
|
||||
|
||||
return prob;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* for some locations it might make sense to also look at APs
|
||||
* that have NOT been discovered by the smartphone but SHOULD
|
||||
* be very well receivable at a location.
|
||||
* such locations can be opted-out by using this veto-probability
|
||||
*/
|
||||
double getVeto(const Point3& pos_m, const Timestamp curTime, const WiFiMeasurements& obs) const {
|
||||
|
||||
(void) curTime;
|
||||
|
||||
struct APR {
|
||||
AccessPoint ap;
|
||||
float rssi;
|
||||
APR(const AccessPoint& ap, const float rssi) : ap(ap), rssi(rssi) {;}
|
||||
};
|
||||
|
||||
std::vector<APR> all;
|
||||
for (const AccessPoint& ap : allAPs) {
|
||||
const float rssi = model.getRSSI(ap.getMAC(), pos_m);
|
||||
if (rssi != rssi) {continue;} // unknown to the model
|
||||
all.push_back(APR(ap, rssi));
|
||||
}
|
||||
|
||||
// stort by RSSI
|
||||
auto comp = [&] (const APR& apr1, const APR& apr2) {return apr1.rssi > apr2.rssi;};
|
||||
std::sort(all.begin(), all.end(), comp);
|
||||
|
||||
int numVetos = 0;
|
||||
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
const APR& apr = all[i];
|
||||
const WiFiMeasurement* mes = obs.getForMac(apr.ap.getMAC());
|
||||
const float rssiScan = (mes) ? (mes->getRSSI()) : (-100);
|
||||
const float rssiModel = apr.rssi;
|
||||
const float diff = std::abs(rssiScan - rssiModel);
|
||||
if (diff > 20) {++numVetos;}
|
||||
}
|
||||
|
||||
if (numVetos == 0) {return 0.70;}
|
||||
if (numVetos == 1) {return 0.29;}
|
||||
else {return 0.01;}
|
||||
|
||||
}
|
||||
|
||||
template <typename Node> double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs, const int age_ms = 0) const {
|
||||
throw Exception("todo??");
|
||||
|
||||
return this->getProbability(n.inMeter() + Point3(0,0,1.3), curTime, obs);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
// after some runtime, check whether the wifi timestamps make sense
|
||||
// those must not be zero, otherwise something is wrong!
|
||||
if (!obs.entries.empty()) {
|
||||
Assert::isFalse(curTime.ms() > 10000 && obs.entries.front().ts.isZero(), "WiFiMeasurement timestamp is 0. this does not make sense...");
|
||||
Assert::isFalse(curTime.ms() > 10000 && obs.entries.front().getTimestamp().isZero(), "WiFiMeasurement timestamp is 0. this does not make sense...");
|
||||
}
|
||||
|
||||
// process each observed measurement
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
const Timestamp age = curTime - measurement.getTimestamp();
|
||||
|
||||
// sigma grows with measurement age
|
||||
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();
|
||||
@@ -102,8 +102,7 @@ public:
|
||||
}
|
||||
|
||||
// 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;}
|
||||
//Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
|
||||
|
||||
// as not every node has the same number of visible/matching APs
|
||||
// we MUST return something like the average probability
|
||||
|
||||
106
sensors/radio/WiFiQualityAnalyzer.h
Normal file
106
sensors/radio/WiFiQualityAnalyzer.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef WIFIQUALITYANALYZER_H
|
||||
#define WIFIQUALITYANALYZER_H
|
||||
|
||||
#include "WiFiMeasurements.h"
|
||||
#include <unordered_set>
|
||||
|
||||
class WiFiQualityAnalyzer {
|
||||
|
||||
private:
|
||||
|
||||
int historySize = 3;
|
||||
std::vector<WiFiMeasurements> history;
|
||||
float quality = 0;
|
||||
|
||||
public:
|
||||
|
||||
/** attach the current measurement and infer the quality */
|
||||
void add(const WiFiMeasurements& mes) {
|
||||
|
||||
// update the history
|
||||
history.push_back(mes);
|
||||
while(history.size() > historySize) {
|
||||
history.erase(history.begin());
|
||||
}
|
||||
|
||||
// recalculate
|
||||
update();
|
||||
|
||||
}
|
||||
|
||||
/** get a quality score for the WiFi */
|
||||
float getQuality() const {
|
||||
return quality;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/** re-calculate the current quality */
|
||||
void update() {
|
||||
|
||||
const float qCnt = getQualityByCount();
|
||||
const float qAvgdB = getQualityByAvgDB();
|
||||
|
||||
quality = qAvgdB;
|
||||
|
||||
}
|
||||
|
||||
/** score [0:1] based on the average sig-strength. the higher the better */
|
||||
float getQualityByAvgDB() const {
|
||||
|
||||
// stats
|
||||
float sum = 0;
|
||||
float sum2 = 0;
|
||||
float cnt = 0;
|
||||
|
||||
for (const WiFiMeasurements& mes : history) {
|
||||
for (const WiFiMeasurement& m : mes.entries) {
|
||||
const float rssi = m.getRSSI();
|
||||
sum += rssi;
|
||||
sum2 += rssi*rssi;
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
|
||||
// average sig strength
|
||||
const float avg = sum/cnt;
|
||||
const float avg2 = sum2/cnt;
|
||||
const float stdDev = std::sqrt(avg2 - avg*avg);
|
||||
|
||||
// avg rssi score
|
||||
const float minRSSI = -85;
|
||||
const float maxRSSI = -70;
|
||||
float score1 = (avg-minRSSI) / (maxRSSI-minRSSI); // min = 0; max = 1
|
||||
if (score1 > 1) {score1 = 1;}
|
||||
if (score1 < 0) {score1 = 0;}
|
||||
//const float score1 = 1.0 - std::exp(-0.07 * (avg-minRSSI));
|
||||
|
||||
// std dev score
|
||||
const float score2 = 1.0 - std::exp(-1.0 * stdDev);
|
||||
|
||||
return score1 * score2;
|
||||
|
||||
}
|
||||
|
||||
/** quality score [0:1] according to the number of seen APs */
|
||||
float getQualityByCount() const {
|
||||
|
||||
// distinct macs
|
||||
std::unordered_set<MACAddress> macs;
|
||||
for (const WiFiMeasurements& mes : history) {
|
||||
for (const WiFiMeasurement& m : mes.entries) {
|
||||
macs.insert(m.getAP().getMAC());
|
||||
}
|
||||
}
|
||||
|
||||
// number of distinct macs
|
||||
const int cnt = macs.size();
|
||||
|
||||
// see function plot. function between [0.0:1.0] has 0.5 around 7 seen APs
|
||||
return 1.0 - std::exp(-0.1 * cnt);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIQUALITYANALYZER_H
|
||||
@@ -1,7 +1,13 @@
|
||||
#ifndef WIFIMODEL_H
|
||||
#define WIFIMODEL_H
|
||||
|
||||
#include "../LocatedAccessPoint.h"
|
||||
#include "../AccessPoint.h"
|
||||
#include "../../../geo/Point3.h"
|
||||
#include <vector>
|
||||
|
||||
|
||||
#include "../../../data/XMLserialize.h"
|
||||
|
||||
|
||||
/**
|
||||
* interface for signal-strength prediction models.
|
||||
@@ -9,10 +15,13 @@
|
||||
* the model is passed a MAC-address of an AP in question, and a position.
|
||||
* hereafter the model returns the RSSI for this AP at the questioned location.
|
||||
*/
|
||||
class WiFiModel {
|
||||
class WiFiModel : public XMLserialize {
|
||||
|
||||
public:
|
||||
|
||||
/** dtor */
|
||||
virtual ~WiFiModel() {;}
|
||||
|
||||
// /** get the given access-point's RSSI at the provided location */
|
||||
// virtual float getRSSI(const LocatedAccessPoint& ap, const Point3 p) = 0;
|
||||
|
||||
@@ -27,6 +36,7 @@ public:
|
||||
*/
|
||||
virtual float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const = 0;
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIMODEL_H
|
||||
|
||||
32
sensors/radio/model/WiFiModelFactory.h
Normal file
32
sensors/radio/model/WiFiModelFactory.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef WIFIMODELFACTORY_H
|
||||
#define WIFIMODELFACTORY_H
|
||||
|
||||
|
||||
#include "WiFiModel.h"
|
||||
#include "../../../floorplan/v2/Floorplan.h"
|
||||
|
||||
/**
|
||||
* this class can instantiate WiFiModels based on serialized XML files
|
||||
*/
|
||||
class WiFiModelFactory {
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::IndoorMap* map;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor. needs the floorplan for some models */
|
||||
WiFiModelFactory(Floorplan::IndoorMap* map) : map(map) {
|
||||
;
|
||||
}
|
||||
|
||||
/** parse model from XML file */
|
||||
WiFiModel* loadXML(const std::string& file);
|
||||
|
||||
/** parse model from XML node */
|
||||
WiFiModel* readFromXML(XMLDoc* doc, XMLElem* src);
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIMODELFACTORY_H
|
||||
42
sensors/radio/model/WiFiModelFactoryImpl.h
Normal file
42
sensors/radio/model/WiFiModelFactoryImpl.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#ifndef WIFIMODELFACTORYIMPL_H
|
||||
#define WIFIMODELFACTORYIMPL_H
|
||||
|
||||
#include "WiFiModelFactory.h"
|
||||
#include "WiFiModelLogDist.h"
|
||||
#include "WiFiModelLogDistCeiling.h"
|
||||
#include "WiFiModelPerFloor.h"
|
||||
#include "WiFiModelPerBBox.h"
|
||||
|
||||
WiFiModel* WiFiModelFactory::loadXML(const std::string& file) {
|
||||
|
||||
XMLDoc doc;
|
||||
XMLserialize::assertOK(doc.LoadFile(file.c_str()), doc, "error while loading file");
|
||||
XMLElem* root = doc.FirstChildElement("root");
|
||||
|
||||
return readFromXML(&doc, root);
|
||||
|
||||
}
|
||||
|
||||
WiFiModel* WiFiModelFactory::readFromXML(XMLDoc* doc, XMLElem* src) {
|
||||
|
||||
// each model attaches its "type" during serialization
|
||||
const std::string type = src->Attribute("type");
|
||||
|
||||
WiFiModel* mdl = nullptr;
|
||||
|
||||
// create an instance for the model
|
||||
if (type == "WiFiModelLogDist") {mdl = new WiFiModelLogDist();}
|
||||
else if (type == "WiFiModelLogDistCeiling") {mdl = new WiFiModelLogDistCeiling(map);}
|
||||
else if (type == "WiFiModelPerFloor") {mdl = new WiFiModelPerFloor(map);}
|
||||
else if (type == "WiFiModelPerBBox") {mdl = new WiFiModelPerBBox(map);}
|
||||
else {throw Exception("invalid model type given: " + type);}
|
||||
|
||||
// load the model from XML
|
||||
mdl->readFromXML(doc, src);
|
||||
|
||||
// done
|
||||
return mdl;
|
||||
|
||||
}
|
||||
|
||||
#endif // WIFIMODELFACTORYIMPL_H
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "WiFiModel.h"
|
||||
#include "LogDistanceModel.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* signal-strength estimation using log-distance model
|
||||
*/
|
||||
@@ -86,6 +88,14 @@ public:
|
||||
|
||||
}
|
||||
|
||||
void readFromXML(XMLDoc* doc, XMLElem* src) override {
|
||||
throw Exception("not yet implemented");
|
||||
}
|
||||
|
||||
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
|
||||
throw Exception("not yet implemented");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIMODELLOGDIST_H
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#define WIFIMODELLOGDISTCEILING_H
|
||||
|
||||
#include "../../../floorplan/v2/Floorplan.h"
|
||||
#include "../../../floorplan/v2/FloorplanCeilings.h"
|
||||
|
||||
#include "../../../Assertions.h"
|
||||
#include "WiFiModel.h"
|
||||
@@ -9,6 +10,8 @@
|
||||
#include "../VAPGrouper.h"
|
||||
#include "../../../misc/Debug.h"
|
||||
|
||||
#include "../../../data/XMLserialize.h"
|
||||
|
||||
/**
|
||||
* signal-strength estimation using log-distance model
|
||||
* including ceilings between AP and position
|
||||
@@ -36,24 +39,28 @@ private:
|
||||
/** map of all APs (and their parameters) known to the model */
|
||||
std::unordered_map<MACAddress, APEntry> accessPoints;
|
||||
|
||||
/** position (height) of all ceilings (in meter) */
|
||||
std::vector<float> ceilingsAtHeight_m;
|
||||
// /** position (height) of all ceilings (in meter) */
|
||||
// std::vector<float> ceilingsAtHeight_m;
|
||||
Floorplan::Ceilings ceilings;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor with floorplan (needed for ceiling position) */
|
||||
WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) {
|
||||
WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** get the entry for the given mac. exception if missing */
|
||||
const APEntry& getAP(const MACAddress& mac) const {
|
||||
const auto& it = accessPoints.find(mac);
|
||||
if (it == accessPoints.end()) {throw Exception("model does not contain an entry for " + mac.asString());}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
/** get a list of all APs known to the model */
|
||||
std::vector<AccessPoint> getAllAPs() const {
|
||||
std::vector<AccessPoint> aps;
|
||||
@@ -62,38 +69,47 @@ public:
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
void loadAPs(const Floorplan::IndoorMap* map, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f, const bool assertSafe = true) {
|
||||
|
||||
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);
|
||||
addAP(MACAddress(ap->mac), ape, assertSafe);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** load AP information from the floorplan. use the given fixed TXP/EXP/WAF for all APs */
|
||||
void loadAPs(const Floorplan::IndoorMap* map, const VAPGrouper& vg, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f) {
|
||||
/**
|
||||
* load AP information from the floorplan.
|
||||
* use the given fixed TXP/EXP/WAF for all APs.
|
||||
* usually txp,exp,waf are checked for a sane range. if you know what you are doing, set assertSafe to false!
|
||||
*/
|
||||
void loadAPs(const Floorplan::IndoorMap* map, const VAPGrouper& vg, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f, const bool assertSafe = true) {
|
||||
|
||||
for (const Floorplan::Floor* floor : map->floors) {
|
||||
for (const Floorplan::AccessPoint* ap : floor->accesspoints) {
|
||||
const APEntry ape(ap->getPos(floor), txp, exp, waf);
|
||||
const MACAddress mac = vg.getBaseMAC(MACAddress(ap->mac));
|
||||
Log::add("WiModLDC", "AP: " + ap->mac + " -> " + mac.asString());
|
||||
addAP(MACAddress(mac), ape);
|
||||
Log::add("WiModLDC", "AP added! given: " + ap->mac + " -> after VAP: " + mac.asString());
|
||||
addAP(MACAddress(mac), ape, assertSafe);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** make the given AP (and its parameters) known to the model */
|
||||
void addAP(const MACAddress& accessPoint, const APEntry& params) {
|
||||
/**
|
||||
* make the given AP (and its parameters) known to the model
|
||||
* usually txp,exp,waf are checked for a sane range. if you know what you are doing, set assertSafe to false!
|
||||
*/
|
||||
void addAP(const MACAddress& accessPoint, const APEntry& params, const bool assertSafe = true) {
|
||||
|
||||
// sanity check
|
||||
Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]");
|
||||
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]");
|
||||
if (assertSafe) {
|
||||
Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]");
|
||||
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! VAP-Grouping issue?");
|
||||
|
||||
@@ -102,6 +118,14 @@ public:
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* make the given AP (and its parameters) known to the model
|
||||
* usually txp,exp,waf are checked for a sane range. if you know what you are doing, set assertSafe to false!
|
||||
*/
|
||||
void addAP(const MACAddress& accessPoint, const Point3 pos_m, const float txp, const float exp, const float waf, const bool assertSafe = true) {
|
||||
addAP(accessPoint, APEntry(pos_m, txp, exp, waf), assertSafe);
|
||||
}
|
||||
|
||||
/** remove all added APs */
|
||||
void clear() {
|
||||
accessPoints.clear();
|
||||
@@ -134,71 +158,71 @@ public:
|
||||
const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m);
|
||||
|
||||
// WAF loss (params.waf is a negative value!) -> WAF loss is also a negative value
|
||||
const float wafLoss = params.waf * numCeilingsBetween(position_m, params.position_m);
|
||||
//const float wafLoss = params.waf * ceilings.numCeilingsBetween(position_m, params.position_m);
|
||||
const float wafLoss = params.waf * ceilings.numCeilingsBetweenFloat(position_m, params.position_m);
|
||||
//const float wafLoss = params.waf * ceilings.numCeilingsBetweenLinearInt(position_m, params.position_m);
|
||||
|
||||
// combine
|
||||
return rssiLOS + wafLoss;
|
||||
const float res = rssiLOS + wafLoss;
|
||||
|
||||
// sanity check
|
||||
Assert::isNotNaN(res, "detected NaN within WiFiModelLogDistCeiling::getRSSI()");
|
||||
|
||||
// ok
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
|
||||
|
||||
// set my type
|
||||
dst->SetAttribute("type", "WiFiModelLogDistCeiling");
|
||||
|
||||
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);
|
||||
}
|
||||
for (const auto& it : accessPoints) {
|
||||
const MACAddress& mac = it.first;
|
||||
const APEntry& ape = it.second;
|
||||
XMLElem* xap = doc->NewElement("ap");
|
||||
xap->SetAttribute("mac", mac.asString().c_str());
|
||||
xap->SetAttribute("px", ape.position_m.x);
|
||||
xap->SetAttribute("py", ape.position_m.y);
|
||||
xap->SetAttribute("pz", ape.position_m.z);
|
||||
xap->SetAttribute("txp", ape.txp);
|
||||
xap->SetAttribute("exp", ape.exp);
|
||||
xap->SetAttribute("waf", ape.waf);
|
||||
dst->InsertEndChild(xap);
|
||||
}
|
||||
|
||||
return cnt;
|
||||
for (const float atHeight_m : ceilings.getCeilings()) {
|
||||
XMLElem* xceil = doc->NewElement("ceiling");
|
||||
xceil->SetAttribute("atHeight", atHeight_m);
|
||||
dst->InsertEndChild(xceil);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** get the number of ceilings between z1 and z2 */
|
||||
int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const {
|
||||
void readFromXML(XMLDoc* doc, XMLElem* src) override {
|
||||
|
||||
int cnt = 0;
|
||||
const float zMin = std::min(pos1.z, pos2.z);
|
||||
const float zMax = std::max(pos1.z, pos2.z);
|
||||
// check type
|
||||
if (std::string("WiFiModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");}
|
||||
|
||||
#ifdef WITH_ASSERTIONS
|
||||
accessPoints.clear();
|
||||
ceilings.clear();
|
||||
|
||||
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) "
|
||||
XML_FOREACH_ELEM_NAMED("ap", xap, src) {
|
||||
MACAddress mac = MACAddress(xap->Attribute("mac"));
|
||||
APEntry ape(
|
||||
Point3(xap->FloatAttribute("px"), xap->FloatAttribute("py"), xap->FloatAttribute("pz")),
|
||||
xap->FloatAttribute("txp"),
|
||||
xap->FloatAttribute("exp"),
|
||||
xap->FloatAttribute("waf")
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (const float z : ceilingsAtHeight_m) {
|
||||
if (zMin < z && zMax > z) {++cnt;}
|
||||
accessPoints.insert( std::make_pair(mac, ape) );
|
||||
}
|
||||
|
||||
return cnt;
|
||||
XML_FOREACH_ELEM_NAMED("ceiling", xceil, src) {
|
||||
const float atHeight_m = xceil->FloatAttribute("atHeight");
|
||||
ceilings.addCeiling(atHeight_m);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
167
sensors/radio/model/WiFiModelPerBBox.h
Normal file
167
sensors/radio/model/WiFiModelPerBBox.h
Normal file
@@ -0,0 +1,167 @@
|
||||
#ifndef WIFIMODELPERBBOX_H
|
||||
#define WIFIMODELPERBBOX_H
|
||||
|
||||
|
||||
#include "../AccessPoint.h"
|
||||
#include "../../../geo/Point3.h"
|
||||
#include "../../../geo/BBoxes3.h"
|
||||
#include <vector>
|
||||
|
||||
#include "WiFiModelFactory.h"
|
||||
|
||||
/**
|
||||
* FOR TESTING
|
||||
*
|
||||
* this model allows using a different sub-models
|
||||
* identified by a bound-box to reduce the error
|
||||
*/
|
||||
class WiFiModelPerBBox : public WiFiModel {
|
||||
|
||||
struct ModelForBBoxes {
|
||||
|
||||
WiFiModel* mdl;
|
||||
BBoxes3 bboxes;
|
||||
|
||||
/** ctor */
|
||||
ModelForBBoxes(WiFiModel* mdl, BBoxes3 bboxes) : mdl(mdl), bboxes(bboxes) {;}
|
||||
|
||||
/** does this entry apply to the given position? */
|
||||
bool matches(const Point3 pt) const {
|
||||
if (bboxes.get().empty()) {throw Exception("no bbox(es) given for model!");}
|
||||
return bboxes.contains(pt);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Floorplan::IndoorMap* map;
|
||||
|
||||
/** all contained models [one per bbox] */
|
||||
std::vector<ModelForBBoxes> models;
|
||||
|
||||
public:
|
||||
|
||||
WiFiModelPerBBox(Floorplan::IndoorMap* map) : map(map) {
|
||||
;
|
||||
}
|
||||
|
||||
/** dtor */
|
||||
virtual ~WiFiModelPerBBox() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** get a list of all APs known to the model */
|
||||
std::vector<AccessPoint> getAllAPs() const override {
|
||||
|
||||
// combine all submodels
|
||||
std::vector<AccessPoint> res;
|
||||
for (const ModelForBBoxes& sub : models) {
|
||||
for (const AccessPoint& ap : sub.mdl->getAllAPs()) {
|
||||
if (std::find(res.begin(), res.end(), ap) == res.end()) { // TODO use map instead?
|
||||
res.push_back(ap);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
void add(WiFiModel* mdl, const BBoxes3 bboxes) {
|
||||
if (bboxes.get().empty()) {throw Exception("no bbox(es) given for model!");}
|
||||
ModelForBBoxes mfb(mdl, bboxes);
|
||||
models.push_back(mfb);
|
||||
}
|
||||
|
||||
float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
|
||||
|
||||
for (const ModelForBBoxes& mfb : models) {
|
||||
if (mfb.matches(position_m)) {return mfb.mdl->getRSSI(accessPoint, position_m);}
|
||||
}
|
||||
|
||||
return -120;
|
||||
|
||||
}
|
||||
|
||||
void readFromXML(XMLDoc* doc, XMLElem* src) override {
|
||||
|
||||
// check type
|
||||
if (std::string("WiFiModelPerBBox") != src->Attribute("type")) {throw Exception("invalid model type");}
|
||||
|
||||
models.clear();
|
||||
|
||||
// model factory [create models based on XMl content]
|
||||
WiFiModelFactory fac(map);
|
||||
|
||||
// parse all contained models [one per floor]
|
||||
XML_FOREACH_ELEM_NAMED("entry", xentry, src) {
|
||||
|
||||
// all bboxes
|
||||
BBoxes3 bboxes;
|
||||
XML_FOREACH_ELEM_NAMED("bbox", xbbox, xentry) {
|
||||
|
||||
const float x1 = xbbox->FloatAttribute("x1");
|
||||
const float y1 = xbbox->FloatAttribute("y1");
|
||||
const float z1 = xbbox->FloatAttribute("z1");
|
||||
|
||||
const float x2 = xbbox->FloatAttribute("x2");
|
||||
const float y2 = xbbox->FloatAttribute("y2");
|
||||
const float z2 = xbbox->FloatAttribute("z2");
|
||||
|
||||
const BBox3 bbox(Point3(x1,y1,z1), Point3(x2,y2,z2));
|
||||
bboxes.add(bbox);
|
||||
|
||||
}
|
||||
|
||||
// node for the model
|
||||
XMLElem* xmodel = xentry->FirstChildElement("model");
|
||||
|
||||
// let the factory instantiate the model
|
||||
WiFiModel* mdl = fac.readFromXML(doc, xmodel);
|
||||
|
||||
// add
|
||||
models.push_back(ModelForBBoxes(mdl, bboxes));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
|
||||
|
||||
// set my type
|
||||
dst->SetAttribute("type", "WiFiModelPerBBox");
|
||||
|
||||
for (const ModelForBBoxes& mfb : models) {
|
||||
|
||||
// all models
|
||||
XMLElem* xentry = doc->NewElement("entry"); {
|
||||
|
||||
// each bbox
|
||||
for (const BBox3& bbox : mfb.bboxes.get()) {
|
||||
XMLElem* xbbox = doc->NewElement("bbox"); {
|
||||
|
||||
xbbox->SetAttribute("x1", bbox.getMin().x);
|
||||
xbbox->SetAttribute("y1", bbox.getMin().y);
|
||||
xbbox->SetAttribute("z1", bbox.getMin().z);
|
||||
|
||||
xbbox->SetAttribute("x2", bbox.getMax().x);
|
||||
xbbox->SetAttribute("y2", bbox.getMax().y);
|
||||
xbbox->SetAttribute("z2", bbox.getMax().z);
|
||||
|
||||
}; xentry->InsertFirstChild(xbbox);
|
||||
}
|
||||
|
||||
// the corresponding model
|
||||
XMLElem* xmodel = doc->NewElement("model"); {
|
||||
mfb.mdl->writeToXML(doc, xmodel);
|
||||
}; xentry->InsertEndChild(xmodel);
|
||||
|
||||
}; dst->InsertEndChild(xentry);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // WIFIMODELPERBBOX_H
|
||||
133
sensors/radio/model/WiFiModelPerFloor.h
Normal file
133
sensors/radio/model/WiFiModelPerFloor.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#ifndef WIFIMODELPERFLOOR_H
|
||||
#define WIFIMODELPERFLOOR_H
|
||||
|
||||
#include "../AccessPoint.h"
|
||||
#include "../../../geo/Point3.h"
|
||||
#include <vector>
|
||||
|
||||
#include "WiFiModelFactory.h"
|
||||
|
||||
/**
|
||||
* FOR TESTING
|
||||
*
|
||||
* this model allows using a different sub-models for each floor to reduce the error
|
||||
*/
|
||||
class WiFiModelPerFloor : public WiFiModel {
|
||||
|
||||
struct ModelForFloor {
|
||||
|
||||
float fromZ;
|
||||
float toZ;
|
||||
WiFiModel* mdl;
|
||||
|
||||
/** ctor */
|
||||
ModelForFloor(const float fromZ, const float toZ, WiFiModel* mdl) : fromZ(fromZ), toZ(toZ), mdl(mdl) {;}
|
||||
|
||||
/** does this entry apply to the given z-position? */
|
||||
bool matches(const float z) const {
|
||||
return (fromZ <= z && z < toZ);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Floorplan::IndoorMap* map;
|
||||
|
||||
/** all contained models [one per floor] */
|
||||
std::vector<ModelForFloor> models;
|
||||
|
||||
public:
|
||||
|
||||
WiFiModelPerFloor(Floorplan::IndoorMap* map) : map(map) {
|
||||
;
|
||||
}
|
||||
|
||||
/** dtor */
|
||||
virtual ~WiFiModelPerFloor() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** get a list of all APs known to the model */
|
||||
std::vector<AccessPoint> getAllAPs() const override {
|
||||
|
||||
// combine all submodels
|
||||
std::vector<AccessPoint> res;
|
||||
for (const ModelForFloor& sub : models) {
|
||||
for (const AccessPoint& ap : sub.mdl->getAllAPs()) {
|
||||
if (std::find(res.begin(), res.end(), ap) == res.end()) { // TODO use map instead?
|
||||
res.push_back(ap);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
void add(WiFiModel* mdl, const Floorplan::Floor* floor) {
|
||||
ModelForFloor mff(floor->atHeight, floor->atHeight+floor->height, mdl);
|
||||
models.push_back(mff);
|
||||
}
|
||||
|
||||
float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
|
||||
|
||||
for (const ModelForFloor& mff : models) {
|
||||
if (mff.matches(position_m.z)) {return mff.mdl->getRSSI(accessPoint, position_m);}
|
||||
}
|
||||
|
||||
return -120;
|
||||
|
||||
}
|
||||
|
||||
void readFromXML(XMLDoc* doc, XMLElem* src) override {
|
||||
|
||||
// check type
|
||||
if (std::string("WiFiModelPerFloor") != src->Attribute("type")) {throw Exception("invalid model type");}
|
||||
|
||||
models.clear();
|
||||
|
||||
// model factory [create models based on XMl content]
|
||||
WiFiModelFactory fac(map);
|
||||
|
||||
// parse all contained models [one per floor]
|
||||
XML_FOREACH_ELEM_NAMED("floor", xfloor, src) {
|
||||
|
||||
// floor params
|
||||
const float z1 = xfloor->FloatAttribute("z1");
|
||||
const float z2 = xfloor->FloatAttribute("z2");
|
||||
|
||||
// node for the model
|
||||
XMLElem* xmodel = xfloor->FirstChildElement("model");
|
||||
|
||||
// let the factory instantiate the model
|
||||
WiFiModel* mdl = fac.readFromXML(doc, xmodel);
|
||||
|
||||
// add
|
||||
models.push_back(ModelForFloor(z1, z2, mdl));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
|
||||
|
||||
// set my type
|
||||
dst->SetAttribute("type", "WiFiModelPerFloor");
|
||||
|
||||
for (const ModelForFloor& mff : models) {
|
||||
|
||||
XMLElem* xfloor = doc->NewElement("floor"); {
|
||||
xfloor->SetAttribute("z1", mff.fromZ);
|
||||
xfloor->SetAttribute("z2", mff.toZ);
|
||||
XMLElem* xmodel = doc->NewElement("model"); {
|
||||
mff.mdl->writeToXML(doc, xmodel);
|
||||
}; xfloor->InsertEndChild(xmodel);
|
||||
}; dst->InsertEndChild(xfloor);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // WIFIMODELPERFLOOR_H
|
||||
8
sensors/radio/model/WiFiModels.h
Normal file
8
sensors/radio/model/WiFiModels.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef WIFIMODELS_H
|
||||
#define WIFIMODELS_H
|
||||
|
||||
#include "WiFiModel.h"
|
||||
#include "WiFiModelFactory.h"
|
||||
#include "WiFiModelFactoryImpl.h"
|
||||
|
||||
#endif // WIFIMODELS_H
|
||||
@@ -8,10 +8,10 @@
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* denotes a wifi fingerprint
|
||||
* known position and several measurements conducted at this position
|
||||
* denotes a wifi fingerprint:
|
||||
* known position and 1-n measurements [wifi-scan on all channels] conducted at this position
|
||||
*
|
||||
* as several measurements were conducted, each AP is usually contained more than once!
|
||||
* if more than one measurement is conducted, each AP is contained more than once!
|
||||
*/
|
||||
struct WiFiFingerprint {
|
||||
|
||||
@@ -19,7 +19,7 @@ struct WiFiFingerprint {
|
||||
/** real-world-position that was measured */
|
||||
Point3 pos_m;
|
||||
|
||||
/** measurements (APs) at the given location */
|
||||
/** seen APs at the given location */
|
||||
WiFiMeasurements measurements;
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@ struct WiFiFingerprint {
|
||||
/** ctor */
|
||||
WiFiFingerprint(const Point3 pos_m) : pos_m(pos_m) {;}
|
||||
|
||||
/** ctor */
|
||||
WiFiFingerprint(const Point3 pos_m, const WiFiMeasurements& measurements) : pos_m(pos_m), measurements(measurements) {;}
|
||||
|
||||
|
||||
/** as each AP is contained more than once (scanned more than once), group them by MAC and use the average RSSI */
|
||||
/** as each AP might be 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)
|
||||
@@ -46,9 +48,9 @@ struct WiFiFingerprint {
|
||||
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.setRssi(avg.getRSSI() + apMeasurements.entries[i].getRSSI());
|
||||
}
|
||||
avg.rssi /= apMeasurements.entries.size();
|
||||
avg.setRssi(avg.getRSSI() / apMeasurements.entries.size());
|
||||
res.entries.push_back(avg); // add to output
|
||||
}
|
||||
|
||||
@@ -57,12 +59,21 @@ struct WiFiFingerprint {
|
||||
|
||||
}
|
||||
|
||||
/** get all measurements for the given MAC */
|
||||
std::vector<WiFiMeasurement> getAllForMAC(const MACAddress& mac) const {
|
||||
std::vector<WiFiMeasurement> res;
|
||||
for (const WiFiMeasurement& m : measurements.entries) {
|
||||
if (m.getAP().getMAC() == mac) {res.push_back(m);}
|
||||
}
|
||||
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";
|
||||
out << wm.getTimestamp().ms() << " " << wm.getAP().getMAC().asString() << " " << wm.getRSSI() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
140
sensors/radio/setup/WiFiFingerprints.h
Normal file
140
sensors/radio/setup/WiFiFingerprints.h
Normal file
@@ -0,0 +1,140 @@
|
||||
#ifndef WIFIFINGERPRINTS_H
|
||||
#define WIFIFINGERPRINTS_H
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "WiFiFingerprint.h"
|
||||
#include <iostream>
|
||||
|
||||
/**
|
||||
* helper class to load and save fingerprints.
|
||||
* fingerprints are wifi-measurements given at some known location
|
||||
* those can be used to e.g. optimize access-point models etc.
|
||||
*/
|
||||
class WiFiFingerprints {
|
||||
|
||||
private:
|
||||
|
||||
// /** the file to save the calibration model to */
|
||||
// std::string file;
|
||||
|
||||
/** all fingerprints (position -> measurements) within the model */
|
||||
std::vector<WiFiFingerprint> fingerprints;
|
||||
|
||||
public:
|
||||
|
||||
/** empty ctor */
|
||||
WiFiFingerprints() {
|
||||
|
||||
}
|
||||
|
||||
/** ctor from file */
|
||||
WiFiFingerprints(const std::string& file) {
|
||||
load(file);
|
||||
}
|
||||
|
||||
const std::vector<WiFiFingerprint>& getFingerprints() const {
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
std::vector<WiFiFingerprint>& getFingerprints() {
|
||||
return fingerprints;
|
||||
}
|
||||
|
||||
/** attach the given fingerprint */
|
||||
void add(const WiFiFingerprint& fp) {
|
||||
fingerprints.push_back(fp);
|
||||
}
|
||||
|
||||
/** get all fingerprints that measured exactly the given mac [no VAP grouping] */
|
||||
const std::vector<WiFiFingerprint> getFingerprintsFor(const MACAddress& mac) const {
|
||||
|
||||
std::vector<WiFiFingerprint> res;
|
||||
|
||||
// process each fingerprint location
|
||||
for (const WiFiFingerprint& _fp : fingerprints) {
|
||||
|
||||
// start with an empty copy
|
||||
WiFiFingerprint fp = _fp; fp.measurements.entries.clear();
|
||||
|
||||
// only add the measurements that belong to the requested mac
|
||||
for (const WiFiMeasurement& _m : _fp.measurements.entries) {
|
||||
if (_m.getAP().getMAC() == mac) {
|
||||
fp.measurements.entries.push_back(_m);
|
||||
}
|
||||
}
|
||||
if (fp.measurements.entries.size() > 0) { // got some measurements for this AP?
|
||||
res.push_back(fp);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
/** deserialize the model */
|
||||
void load(const std::string& file) {
|
||||
|
||||
// open and check
|
||||
std::ifstream inp(file.c_str());
|
||||
if (inp.bad() || inp.eof() || ! inp.is_open()) { return; }
|
||||
|
||||
// read all entries
|
||||
while (!inp.eof()) {
|
||||
|
||||
// each section starts with [fingerprint]
|
||||
std::string section;
|
||||
inp >> section;
|
||||
if (inp.eof()) {break;}
|
||||
if (section != "[fingerprint]") {throw Exception("error!");}
|
||||
|
||||
// deserialize it
|
||||
WiFiFingerprint wfp;
|
||||
wfp.read(inp);
|
||||
if (wfp.measurements.entries.empty()) {continue;}
|
||||
fingerprints.push_back(wfp);
|
||||
|
||||
}
|
||||
|
||||
inp.close();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** serialize the model */
|
||||
void save(const std::string& file) {
|
||||
|
||||
// open and check
|
||||
std::ofstream out(file.c_str());
|
||||
if (out.bad()) {throw Exception("error while opening " + file + " for write");}
|
||||
|
||||
// write all entries
|
||||
for (const WiFiFingerprint& wfp : fingerprints) {
|
||||
out << "[fingerprint]\n";
|
||||
wfp.write(out);
|
||||
}
|
||||
|
||||
// done
|
||||
out.close();
|
||||
|
||||
}
|
||||
|
||||
/** get the fingerprint for the given location. if no fingerprint exists, an empty one is created! */
|
||||
WiFiFingerprint& getFingerprint(const Point3 pos_m) {
|
||||
|
||||
// try to find an existing one
|
||||
for (WiFiFingerprint& wfp : fingerprints) {
|
||||
// get within range of floating-point rounding issues
|
||||
if (wfp.pos_m.getDistance(pos_m) < 0.01) {return wfp;}
|
||||
}
|
||||
|
||||
// create a new one and return its reference
|
||||
WiFiFingerprint wfp(pos_m);
|
||||
fingerprints.push_back(wfp);
|
||||
return fingerprints.back();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIFINGERPRINTS_H
|
||||
@@ -1,247 +1,68 @@
|
||||
#ifndef OPTIMIZER_H
|
||||
#define OPTIMIZER_H
|
||||
|
||||
#include "../../../floorplan/v2/Floorplan.h"
|
||||
#include "../../../floorplan/v2/FloorplanHelper.h"
|
||||
#ifndef WIFIOPTIMIZER_H
|
||||
#define WIFIOPTIMIZER_H
|
||||
|
||||
#include "WiFiFingerprints.h"
|
||||
#include "WiFiOptimizerStructs.h"
|
||||
#include "../VAPGrouper.h"
|
||||
#include "../../../geo/BBox3.h"
|
||||
#include "../../../misc/Debug.h"
|
||||
|
||||
#include "WiFiFingerprint.h"
|
||||
#include "../model/WiFiModel.h"
|
||||
#include "../model/WiFiModelLogDistCeiling.h"
|
||||
#include <unordered_map>
|
||||
|
||||
#include <KLib/math/optimization/NumOptAlgoDownhillSimplex.h>
|
||||
#include <KLib/math/optimization/NumOptAlgoGenetic.h>
|
||||
#include <KLib/math/optimization/NumOptAlgoRangeRandom.h>
|
||||
namespace WiFiOptimizer {
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
/** base-class for all WiFiOptimizers */
|
||||
class Base {
|
||||
|
||||
struct WiFiOptimizer {
|
||||
protected:
|
||||
|
||||
private:
|
||||
/** each MAC-Adress has several position->rssi entries */
|
||||
std::unordered_map<MACAddress, std::vector<RSSIatPosition>> apMap;
|
||||
|
||||
/** combine one RSSI measurement with the position the signal was measured at */
|
||||
struct RSSIatPosition {
|
||||
/** how to handle virtual access points [group, not-group, ..] */
|
||||
const VAPGrouper vg;
|
||||
|
||||
/** real-world position (in meter) */
|
||||
const Point3 pos_m;
|
||||
|
||||
/** measured signal strength (for one AP) */
|
||||
const float rssi;
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
RSSIatPosition(const Point3 pos_m, const float rssi) : pos_m(pos_m), rssi(rssi) {;}
|
||||
Base(const VAPGrouper vg) : vg(vg) {;}
|
||||
|
||||
};
|
||||
|
||||
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) */
|
||||
virtual std::vector<MACAddress> getAllMACs() const {
|
||||
std::vector<MACAddress> res;
|
||||
for (const auto& it : apMap) {res.push_back(it.first);}
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** 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;
|
||||
/** get all [VAPGrouped, Averaged] fingerprints for the given mac */
|
||||
virtual const std::vector<RSSIatPosition>& getFingerprintsFor(const MACAddress& mac) {
|
||||
const auto& it = apMap.find(mac);
|
||||
if (it == apMap.end()) {throw Exception("mac not found: " + mac.asString());}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
const float avgErr = errSum / getAllMACs().size();
|
||||
Log::add(name, "average AP error is: " + std::to_string(avgErr) + " dB");
|
||||
/** add a new fingerprint to the optimizer as data-source */
|
||||
virtual void addFingerprint(const WiFiFingerprint& fp) {
|
||||
|
||||
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;}
|
||||
// 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.getRSSI());
|
||||
apMap[m.getAP().getMAC()].push_back(rap);
|
||||
}
|
||||
|
||||
err /= cnt;
|
||||
err = std::sqrt(err);
|
||||
}
|
||||
|
||||
if (params->txp < -50) {err += 999999;}
|
||||
if (params->txp > -35) {err += 999999;}
|
||||
/** add new fingerprints to the optimizer as data-source */
|
||||
virtual void addFingerprints(const WiFiFingerprints& fps) {
|
||||
for (const WiFiFingerprint& fp : fps.getFingerprints()) {
|
||||
addFingerprint(fp);
|
||||
}
|
||||
}
|
||||
|
||||
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*) ¶ms);
|
||||
|
||||
// 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*) ¶ms);
|
||||
errResult = func((float*)¶ms);
|
||||
|
||||
Log::tock();
|
||||
Log::add(name, mac.asString() + ": " + params.asString() + " @ " + std::to_string(errResult) +" dB err");
|
||||
|
||||
return params;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // OPTIMIZER_H
|
||||
#endif // WIFIOPTIMIZER_H
|
||||
|
||||
345
sensors/radio/setup/WiFiOptimizerLogDistCeiling.h
Normal file
345
sensors/radio/setup/WiFiOptimizerLogDistCeiling.h
Normal file
@@ -0,0 +1,345 @@
|
||||
#ifndef WIFI_OPTIMIZER_LOG_DIST_CEILING_H
|
||||
#define WIFI_OPTIMIZER_LOG_DIST_CEILING_H
|
||||
|
||||
#include "../../../floorplan/v2/Floorplan.h"
|
||||
#include "../../../floorplan/v2/FloorplanHelper.h"
|
||||
|
||||
#include "../../../geo/BBox3.h"
|
||||
#include "../../../misc/Debug.h"
|
||||
|
||||
#include "WiFiFingerprint.h"
|
||||
#include "WiFiFingerprints.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>
|
||||
|
||||
#include "WiFiOptimizer.h"
|
||||
#include "WiFiOptimizerStructs.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace WiFiOptimizer {
|
||||
|
||||
/**
|
||||
* optimize access-point parameters,
|
||||
* given several fingerprints using the log-dist-ceiling model
|
||||
*/
|
||||
struct LogDistCeiling : public Base {
|
||||
|
||||
public:
|
||||
|
||||
enum class Mode {
|
||||
FAST,
|
||||
MEDIUM,
|
||||
QUALITY,
|
||||
};
|
||||
|
||||
/**
|
||||
* resulting optimization stats for one AP
|
||||
*/
|
||||
struct Stats {
|
||||
|
||||
/** average model<->scan error after optimzing */
|
||||
float error_db;
|
||||
|
||||
/** number of fingerprints [= locations] that were used for optimzing */
|
||||
int usedFingerprins;
|
||||
|
||||
/** resulting model<->scan error after optimzing for each individual fingerprints [= location] */
|
||||
std::vector<ErrorAtPosition> errors;
|
||||
|
||||
/** get the location where the model estimation reaches the highest negative value [model estimation too low] */
|
||||
ErrorAtPosition getEstErrorMaxNeg() const {
|
||||
auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();};
|
||||
return *std::min_element(errors.begin(), errors.end(), cmpErrAtPos);
|
||||
}
|
||||
|
||||
/** get the location where the model estimation reaches the highest positive value [model estimation too high] */
|
||||
ErrorAtPosition getEstErrorMaxPos() const {
|
||||
auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();};
|
||||
return *std::max_element(errors.begin(), errors.end(), cmpErrAtPos);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/** parameters for one AP when using the LogDistCeiling model */
|
||||
struct APParams {
|
||||
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
|
||||
float txp;
|
||||
float exp;
|
||||
float waf;
|
||||
|
||||
Point3 getPos() const {return Point3(x,y,z);}
|
||||
|
||||
/** ctor */
|
||||
APParams() {;}
|
||||
|
||||
/** ctor */
|
||||
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() const {
|
||||
std::stringstream ss;
|
||||
ss << "Pos:" << getPos().asString() << " TXP:" << txp << " EXP:" << exp << " WAF:" << waf;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/** we add some constraints to the parameter range */
|
||||
bool outOfRange() const {
|
||||
return (waf > 0) ||
|
||||
(txp < -50) ||
|
||||
(txp > -30) ||
|
||||
(exp > 4) ||
|
||||
(exp < 1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/** add MAC-info to params */
|
||||
struct APParamsMAC {
|
||||
MACAddress mac;
|
||||
APParams params;
|
||||
APParamsMAC(const MACAddress mac, const APParams& params) : mac(mac), params(params) {;}
|
||||
};
|
||||
|
||||
class APParamsList {
|
||||
|
||||
std::vector<APParamsMAC> lst;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
APParamsList(const std::vector<APParamsMAC>& lst) : lst(lst) {
|
||||
|
||||
}
|
||||
|
||||
/** get the list */
|
||||
const std::vector<APParamsMAC>& get() const {
|
||||
return lst;
|
||||
}
|
||||
|
||||
/** get params for the given mac [if known, otherwise nullptr] */
|
||||
const APParamsMAC* get (const MACAddress& mac) const {
|
||||
for (const APParamsMAC& ap : lst) {
|
||||
if (ap.mac == mac) {return ≈}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
using APFilter = std::function<bool(const int numFingerprints, const MACAddress& mac)>;
|
||||
|
||||
const APFilter NONE = [] (const int numFingerprints, const MACAddress& mac) {
|
||||
(void) numFingerprints; (void) mac;
|
||||
return false;
|
||||
};
|
||||
|
||||
const APFilter MIN_5_FPS = [] (const int numFingerprints, const MACAddress& mac) {
|
||||
(void) mac;
|
||||
return numFingerprints < 5;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::IndoorMap* map;
|
||||
|
||||
Mode mode = Mode::QUALITY;
|
||||
|
||||
const char* name = "WiFiOptLDC";
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const Mode mode = Mode::QUALITY) : Base(vg), map(map), mode(mode) {
|
||||
;
|
||||
}
|
||||
|
||||
/** ctor */
|
||||
LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const WiFiFingerprints& fps, const Mode mode = Mode::QUALITY) : Base(vg), map(map), mode(mode) {
|
||||
addFingerprints(fps);
|
||||
}
|
||||
|
||||
/** optimize all known APs */
|
||||
APParamsList optimizeAll(APFilter filter) const {
|
||||
|
||||
// sanity check
|
||||
Assert::isFalse(getAllMACs().empty(), "no APs found for optimization! call addFingerprint() first!");
|
||||
|
||||
float errSum = 0; int errCnt = 0;
|
||||
std::vector<APParamsMAC> res;
|
||||
for (const MACAddress& mac : getAllMACs()) {
|
||||
Stats stats;
|
||||
const APParams params = optimize(mac, stats);
|
||||
if (!filter(stats.usedFingerprins, mac)) {
|
||||
res.push_back(APParamsMAC(mac, params));
|
||||
errSum += stats.error_db;
|
||||
++errCnt;
|
||||
} else {
|
||||
std::cout << "ignored due to filter!" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
const float avgErr = errSum / errCnt;
|
||||
Log::add(name, "optimized APs: " + std::to_string(errCnt));
|
||||
Log::add(name, "average AP error is: " + std::to_string(avgErr) + " dB");
|
||||
|
||||
// done
|
||||
return APParamsList(res);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** optimize the given AP */
|
||||
APParams optimize(const MACAddress& mac, Stats& res) const {
|
||||
|
||||
// starting parameters do not matter for the current optimizer!
|
||||
APParams params(0,0,0, -40, 2.5, -4.0);
|
||||
|
||||
// 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;
|
||||
|
||||
// log
|
||||
Log::add(name, "optimizing parameters for AP " + mac.asString() + " by using " + std::to_string(entries.size()) + " fingerprints", false);
|
||||
Log::tick();
|
||||
|
||||
// get the map's size
|
||||
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, 4), // exp
|
||||
LeOpt::MinMax(-15, -0), // waf
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
LeOpt opt(valRegion);
|
||||
|
||||
switch(mode) {
|
||||
case Mode::FAST:
|
||||
opt.setPopulationSize(100);
|
||||
opt.setNumIerations(50);
|
||||
break;
|
||||
case Mode::MEDIUM:
|
||||
opt.setPopulationSize(200);
|
||||
opt.setNumIerations(100);
|
||||
break;
|
||||
case Mode::QUALITY:
|
||||
opt.setPopulationSize(500);
|
||||
opt.setNumIerations(150);
|
||||
break;
|
||||
}
|
||||
|
||||
// error function
|
||||
auto func = [&] (const float* params) {
|
||||
return getErrorLogDistCeiling(mac, entries, params, nullptr);
|
||||
};
|
||||
|
||||
opt.calculateOptimum(func, (float*) ¶ms);
|
||||
|
||||
// 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*) ¶ms);
|
||||
res.error_db = getErrorLogDistCeiling(mac, entries, (float*)¶ms, &res);
|
||||
res.usedFingerprins = entries.size();
|
||||
|
||||
Log::tock();
|
||||
Log::add(name, mac.asString() + ": " + params.asString() + " @ " + std::to_string(res.error_db) +" dB err");
|
||||
|
||||
return params;
|
||||
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
|
||||
float getErrorLogDistCeiling(const MACAddress& mac, const std::vector<RSSIatPosition>& entries, const float* data, Stats* stats = nullptr) const {
|
||||
|
||||
const APParams* params = (APParams*) data;
|
||||
|
||||
// some sanity checks
|
||||
if (params->outOfRange()) {return 1e10;}
|
||||
|
||||
// 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
|
||||
// signal-strength-prediction-model...
|
||||
WiFiModelLogDistCeiling model(map);
|
||||
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);
|
||||
|
||||
// add error to stats object?
|
||||
if (stats) {
|
||||
stats->errors.push_back(ErrorAtPosition(reading.pos_m, reading.rssi, rssiModel));
|
||||
}
|
||||
|
||||
// adjust the error
|
||||
err += std::pow(std::abs(diff), 2.0);
|
||||
++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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif // WIFI_OPTIMIZER_LOG_DIST_CEILING_H
|
||||
55
sensors/radio/setup/WiFiOptimizerStructs.h
Normal file
55
sensors/radio/setup/WiFiOptimizerStructs.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef WIFIOPTIMIZERSTRUCTS_H
|
||||
#define WIFIOPTIMIZERSTRUCTS_H
|
||||
|
||||
#include "../../../geo/Point3.h"
|
||||
|
||||
namespace WiFiOptimizer {
|
||||
|
||||
/**
|
||||
* one entry that is used during optimization:
|
||||
* 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) {;}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* for statistics
|
||||
* after optimiziaton
|
||||
*
|
||||
* denotes the difference [error] between one fingerprinted rssi
|
||||
* at location (x,y,z) and the model estimation for this location
|
||||
*/
|
||||
struct ErrorAtPosition {
|
||||
|
||||
/** real-world position (in meter) */
|
||||
const Point3 pos_m;
|
||||
|
||||
/** measured signal strength (for one AP) */
|
||||
const float scan_rssi;
|
||||
|
||||
/** final model's prediction */
|
||||
const float model_rssi;
|
||||
|
||||
/** ctor */
|
||||
ErrorAtPosition(const Point3 pos_m, const float scan_rssi, const float model_rssi) : pos_m(pos_m), scan_rssi(scan_rssi), model_rssi(model_rssi) {;}
|
||||
|
||||
/** get the difference [error] between model-estimated-rssi and fingerprinted-rssi */
|
||||
float getError_db() const {
|
||||
return model_rssi - scan_rssi;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // WIFIOPTIMIZERSTRUCTS_H
|
||||
Reference in New Issue
Block a user