added measurement grouping for beacons

had to change the parameter boundaries of the wifi optimizer to be able to use it for bluetooth... this should be refactored to something more generic..
some minor changes in ble
This commit is contained in:
mail@toni-fetzer.de
2019-06-10 16:57:02 +02:00
parent 8d37e94647
commit 96c63ac3ec
11 changed files with 1020 additions and 661 deletions

View File

@@ -0,0 +1,276 @@
#ifndef BEACONMEASUREMENTGROUPER_H
#define BEACONMEASUREMENTGROUPER_H
#include <vector>
#include <unordered_map>
#include <cmath>
#include "../../math/Stats.h"
#include "../../misc/Debug.h"
#include "BeaconMeasurements.h"
class BeaconMeasurementGrouper {
public:
/** the mode denotes the algorithm that is used for grouping VAPs together */
enum class Mode {
/** do NOT group */
DISABLED,
/** group VAPs by setting the MAC's last digit to zero */
LAST_MAC_DIGIT_TO_ZERO,
};
/** describes how to calculate the final signal-strengh of the VAP-grouped entry */
enum class Aggregation {
/** use the average signal-strength of all grouped APs */
AVERAGE,
/** use the median signal-strength of all grouped APs */
MEDIAN,
/** use the maximum signal-strength of all grouped APs */
MAXIMUM,
/** use std-dev around the signal-strength average of all grouped APs. NOTE not directly useful but for debug! */
STD_DEV,
};
/** how to determine the grouped timestamp */
enum class TimeAggregation {
/** use the smallest timestamp among all grouped APs */
MINIMUM,
/** use the average timestamp among all grouped APs */
AVERAGE,
/** use the maximum timestamp among all grouped APs */
MAXIMUM,
};
private:
static constexpr const char* name = "BLEGrp";
/** the mode to use for grouping VAPs */
const Mode mode;
/** the signal-strength aggregation algorithm to use */
const Aggregation rssiAgg;
/** how to aggreage the grouped time */
const TimeAggregation timeAgg;
/** respect only outputs with at-least X occurences of one physical hardware [can be used to prevent issues] */
int minOccurences;
public:
/** ctor */
BeaconMeasurementGrouper(const Mode mode, const Aggregation rssiAgg, const TimeAggregation timeAgg = TimeAggregation::AVERAGE, const int minOccurences = 2) :
mode(mode), rssiAgg(rssiAgg), timeAgg(timeAgg), 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 */
BeaconMeasurements group(const BeaconMeasurements& 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<BeaconMeasurement>> grouped;
for (const BeaconMeasurement& m : original.entries) {
// the vap-base-mac this entry belongs to
const MACAddress baseMAC = getBaseMAC(m.getBeacon().getMAC());
grouped[baseMAC].push_back(m);
}
#ifdef WITH_DEBUG_LOG
std::stringstream vals;
vals << std::fixed;
vals.precision(1);
#endif
// to-be-constructed output
BeaconMeasurements result;
int skipped = 0;
// perform aggregation on each VAP-group
for (auto it : grouped) {
const MACAddress& base = it.first;
const std::vector<BeaconMeasurement>& 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 BeaconMeasurement groupedMeasurement = groupVAPs(base, vaps, rssiAgg, timeAgg);
// get corresponding std-dev for debug
#ifdef WITH_DEBUG_LOG
const BeaconMeasurement groupedStdDev = groupVAPs(base, vaps, Aggregation::STD_DEV, timeAgg);
vals << groupedMeasurement.getRSSI() << "±";
vals << groupedStdDev.getRSSI() << " ";
#endif
// add it to the result-vector
result.entries.push_back(groupedMeasurement);
}
// debug
#ifdef WITH_DEBUG_LOG
Log::add(name,
"grouped " + std::to_string(original.entries.size()) + " measurements " +
"into " + std::to_string(result.entries.size()) + " [omitted: " + std::to_string(skipped) + "]" +
" Stats:[" + vals.str() + "]",
true
);
#endif
// done
return result;
}
/** get the VAP-base-MAC-Address that is the same for all APs that belong to a VAP-Group */
MACAddress getBaseMAC(const MACAddress& mac) const {
switch(mode) {
case Mode::DISABLED: return mac;
case Mode::LAST_MAC_DIGIT_TO_ZERO: return lastMacDigitToZero(mac);
default: throw Exception("unsupported vap-grouping mode given");
}
}
private:
struct FieldRSSI {
static float get(const BeaconMeasurement& m) { return m.getRSSI(); }
};
struct FieldTS {
static Timestamp get(const BeaconMeasurement& m) { return m.getTimestamp(); }
};
/** combine all of the given VAPs into one entry using the configured aggregation method */
static BeaconMeasurement groupVAPs(const MACAddress& baseMAC, const std::vector<BeaconMeasurement>& vaps, Aggregation aggRssi, TimeAggregation aggTime) {
// the resulting entry is an AP with the base-MAC all of the given VAPs have in common
const Beacon baseAP(baseMAC);
// calculate the rssi using the configured aggregate function
float rssi = NAN;
switch(aggRssi) {
case Aggregation::AVERAGE: rssi = getAVG<float, FieldRSSI>(vaps); break;
case Aggregation::MEDIAN: rssi = getMedian(vaps); break;
case Aggregation::MAXIMUM: rssi = getMax<float, FieldRSSI>(vaps); break;
case Aggregation::STD_DEV: rssi = getStdDev<float, FieldRSSI>(vaps); break;
default: throw Exception("unsupported rssi-aggregation method");
}
// calculate the time using the configured aggregate function
Timestamp baseTS;
switch(aggTime) {
case TimeAggregation::MINIMUM: baseTS = getMin<Timestamp, FieldTS>(vaps); break;
case TimeAggregation::AVERAGE: baseTS = getAVG<Timestamp, FieldTS>(vaps); break;
case TimeAggregation::MAXIMUM: baseTS = getMax<Timestamp, FieldTS>(vaps); break;
default: throw Exception("unsupported time-aggregation method");
}
// create the result measurement
return BeaconMeasurement(baseTS, baseAP, rssi);
}
private:
/** get the average signal strength */
template <typename T, typename Field> static inline T getAVG(const std::vector<BeaconMeasurement>& vaps) {
Stats::Average<T> avg;
for (const BeaconMeasurement& vap : vaps) {
avg.add(Field::get(vap));
}
return avg.get();
}
/** get the std-dev around the average */
template <typename T, typename Field> static inline T getStdDev(const std::vector<BeaconMeasurement>& vaps) {
Stats::Variance<T> var;
for (const BeaconMeasurement& vap : vaps) {
var.add(Field::get(vap));
}
return var.getStdDev();
}
/** get the median signal strength */
static inline float getMedian(const std::vector<BeaconMeasurement>& vaps) {
Stats::Median<float> median;
for (const BeaconMeasurement& vap : vaps) {
median.add(vap.getRSSI());
}
return median.get();
}
/** get the maximum value */
template <typename T, typename Field> static inline T getMax(const std::vector<BeaconMeasurement>& vaps) {
Stats::Maximum<T> max;
for (const BeaconMeasurement& vap : vaps) {
max.add(Field::get(vap));
}
return max.get();
}
/** get the minimum value */
template <typename T, typename Field> static inline T getMin(const std::vector<BeaconMeasurement>& vaps) {
Stats::Minimum<T> min;
for (const BeaconMeasurement& vap : vaps) {
min.add(Field::get(vap));
}
return min.get();
}
private:
/** convert the MAC by setting the last (right-most) digit to zero (0) */
static inline MACAddress lastMacDigitToZero(const MACAddress& mac) {
std::string str = mac.asString();
str.back() = '0';
return MACAddress(str);
}
};
#endif // BEACONMEASUREMENTGROUPER_H

View File

@@ -22,12 +22,12 @@ struct BeaconMeasurements {
std::vector<BeaconMeasurement> entries; std::vector<BeaconMeasurement> entries;
/** remove entries older then 3000 ms*/ /** remove entries older then 1000 ms*/
void removeOld(const Timestamp latestTS) { void removeOld(const Timestamp latestTS) {
std::vector<BeaconMeasurement>::iterator it; std::vector<BeaconMeasurement>::iterator it;
for (it = entries.begin(); it != entries.end(); ++it){ for (it = entries.begin(); it != entries.end(); ++it){
if(latestTS - it->getTimestamp() > Timestamp::fromMS(1000*3)){ if(latestTS - it->getTimestamp() > Timestamp::fromMS(1000)){
entries.erase(it); entries.erase(it);
} }
} }

View File

@@ -14,7 +14,7 @@
#include "BeaconProbability.h" #include "BeaconProbability.h"
#include "BeaconMeasurements.h" #include "BeaconMeasurements.h"
#include "model/BeaconModel.h" #include "model/BeaconModel.h"
#include "../../math/Distributions.h" #include "../../math/distribution/Normal.h"
#include "../../data/Timestamp.h" #include "../../data/Timestamp.h"
#include "../../floorplan/v2/Floorplan.h" #include "../../floorplan/v2/Floorplan.h"
@@ -29,75 +29,75 @@ class BeaconObserverFree : public BeaconProbability {
private: private:
const float sigma = 8.0f; const float sigma = 10.0f;
const float sigmaPerSecond = 3.0f; const float sigmaPerSecond = 3.0f;
/** the RSSI prediction model */ /** the RSSI prediction model */
BeaconModel& model; BeaconModel& model;
/** the map's floorplan */ /** the map's floorplan */
Floorplan::IndoorMap* map; Floorplan::IndoorMap* map;
public: public:
/** ctor */ /** ctor */
BeaconObserverFree(const float sigma, BeaconModel& model) : sigma(sigma), model(model) { BeaconObserverFree(const float sigma, BeaconModel& model) : sigma(sigma), model(model) {
} }
/** provides the probability for a specific point in space */ /** provides the probability for a specific point in space */
double getProbability(const Point3& pos_m, const Timestamp curTime, const BeaconMeasurements& obs) const { double getProbability(const Point3& pos_m, const Timestamp curTime, const BeaconMeasurements& obs) const {
double prob = 1.0; double prob = 1.0;
int numMatchingBeacons = 0; int numMatchingBeacons = 0;
// process each measured AP // process each measured AP
for (const BeaconMeasurement& entry : obs.entries) { for (const BeaconMeasurement& entry : obs.entries) {
// sanity check // sanity check
Assert::isFalse(entry.getTimestamp().isZero(), "wifi measurement without timestamp. coding error?"); Assert::isFalse(entry.getTimestamp().isZero(), "wifi measurement without timestamp. coding error?");
// updating the beacons sended txp if available // updating the beacons sended txp if available
if(entry.getBeacon().getTXP() != 0.0f){ if(entry.getBeacon().getTXP() != 0.0f){
//TODO: check if this works //TODO: check if this works
model.updateBeacon(entry); //model.updateBeacon(entry);
} }
// get the model's RSSI (if possible!) // get the model's RSSI (if possible!)
const float modelRSSI = model.getRSSI(entry.getBeacon().getMAC(), pos_m); const float modelRSSI = model.getRSSI(entry.getBeacon().getMAC(), pos_m);
// NaN? -> AP not known to the model -> skip // NaN? -> AP not known to the model -> skip
if (modelRSSI != modelRSSI) { if (modelRSSI != modelRSSI) {
continue; continue;
} }
// the scan's RSSI // the scan's RSSI
const float scanRSSI = entry.getRSSI(); const float scanRSSI = entry.getRSSI();
// the measurement's age // the measurement's age
const Timestamp age = curTime - entry.getTimestamp(); 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() >= 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() <= 40000, "found a 40 second old wifi measurement. maybe there is a coding error?");
// sigma grows with measurement age // sigma grows with measurement age
const float sigma = this->sigma + this->sigmaPerSecond * age.sec(); const float sigma = this->sigma + this->sigmaPerSecond * age.sec();
// update probability // update probability
prob *= Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI); prob *= Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI);
//prob *= Distribution::Region<double>::getProbability(modelRSSI, sigma, scanRSSI); //prob *= Distribution::Region<double>::getProbability(modelRSSI, sigma, scanRSSI);
++numMatchingBeacons; ++numMatchingBeacons;
} }
// sanity check // sanity check
//Assert::isTrue(numMatchingBeacons > 0, "not a single measured Beacon was matched against known ones. coding error? model error?"); //Assert::isTrue(numMatchingBeacons > 0, "not a single measured Beacon was matched against known ones. coding error? model error?");
return prob; return prob;
} }
}; };

View File

@@ -15,6 +15,8 @@
#include "../BeaconMeasurement.h" #include "../BeaconMeasurement.h"
#include "../../../geo/Point3.h" #include "../../../geo/Point3.h"
#include "../../../data/XMLserialize.h"
#include <vector> #include <vector>
/** /**
@@ -23,14 +25,17 @@
* the model is passed a MAC-address of an AP in question, and a position. * 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. * hereafter the model returns the RSSI for this AP at the questioned location.
*/ */
class BeaconModel { class BeaconModel : public XMLserialize {
public: public:
// /** get the given access-point's RSSI at the provided location */ // /** get the given access-point's RSSI at the provided location */
// virtual float getRSSI(const LocatedAccessPoint& ap, const Point3 p) = 0; // virtual float getRSSI(const LocatedAccessPoint& ap, const Point3 p) = 0;
/** get a list of all APs known to the model */ /** dtor */
virtual ~BeaconModel() {;}
/** get a list of all APs known to the model */
virtual std::vector<Beacon> getAllBeacons() const = 0; virtual std::vector<Beacon> getAllBeacons() const = 0;
/** /**
@@ -43,13 +48,13 @@ public:
virtual void updateBeacon(const BeaconMeasurement beacon) = 0; virtual void updateBeacon(const BeaconMeasurement beacon) = 0;
/** /**
* get the RSSI expected at the given location (in meter) * get the RSSI expected at the given location (in meter)
* for an beacon identified by the given MAC. * for an beacon identified by the given MAC.
* *
* if the model can not predict the RSSI for an beacon, it returns NaN! * if the model can not predict the RSSI for an beacon, it returns NaN!
*/ */
virtual float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const = 0; virtual float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const = 0;
}; };

View File

@@ -24,17 +24,17 @@ class BeaconModelLogDist : public BeaconModel {
public: public:
/** parameters describing one beacons to the model */ /** parameters describing one beacons to the model */
struct APEntry { struct APEntry {
Point3 position_m; // the AP's position (in meter) Point3 position_m; // the AP's position (in meter)
float txp; // sending power (-40) float txp; // sending power (-40)
float exp; // path-loss-exponent (~2.0 - 4.0) float exp; // path-loss-exponent (~2.0 - 4.0)
/** ctor */ /** ctor */
APEntry(const Point3 position_m, const float txp, const float exp) : APEntry(const Point3 position_m, const float txp, const float exp) :
position_m(position_m), txp(txp), exp(exp) {;} position_m(position_m), txp(txp), exp(exp) {;}
}; };
private: private:
@@ -43,29 +43,29 @@ private:
public: public:
/** ctor */ /** ctor */
BeaconModelLogDist() { BeaconModelLogDist() {
; ;
} }
/** get a list of all beacons known to the model */ /** get a list of all beacons known to the model */
std::vector<Beacon> getAllBeacons() const { std::vector<Beacon> getAllBeacons() const {
std::vector<Beacon> aps; std::vector<Beacon> aps;
for (const auto it : beacons) {aps.push_back(Beacon(it.first));} for (const auto it : beacons) {aps.push_back(Beacon(it.first));}
return aps; return aps;
} }
/** make the given beacon (and its parameters) known to the model */ /** make the given beacon (and its parameters) known to the model */
void addAP(const MACAddress& beacon, const APEntry& params) { void addAP(const MACAddress& beacon, const APEntry& params) {
// sanity check // sanity check
Assert::isBetween(params.txp, -50.0f, -30.0f, "TXP out of bounds [-90:-30]"); Assert::isBetween(params.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]");
Assert::isBetween(params.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]"); Assert::isBetween(params.exp, 1.0f, 5.0f, "EXP out of bounds [1:5]");
// add // add
beacons.insert( std::pair<MACAddress, APEntry>(beacon, params) ); beacons.insert( std::pair<MACAddress, APEntry>(beacon, params) );
} }
void updateBeacon(const BeaconMeasurement beacon) override{ void updateBeacon(const BeaconMeasurement beacon) override{
// try to get the corresponding parameters // try to get the corresponding parameters
@@ -79,23 +79,23 @@ public:
virtual float getRSSI(const MACAddress& beacon, const Point3 position_m) const override { virtual float getRSSI(const MACAddress& beacon, const Point3 position_m) const override {
// try to get the corresponding parameters // try to get the corresponding parameters
const auto it = beacons.find(beacon); const auto it = beacons.find(beacon);
// AP unknown? -> NAN // AP unknown? -> NAN
if (it == beacons.end()) {return NAN;} if (it == beacons.end()) {return NAN;}
// the beacons' parameters // the beacons' parameters
const APEntry& params = it->second; const APEntry& params = it->second;
// free-space (line-of-sight) RSSI // free-space (line-of-sight) RSSI
const float distance_m = position_m.getDistance(params.position_m); const float distance_m = position_m.getDistance(params.position_m);
const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m);
// done // done
return rssiLOS; return rssiLOS;
} }
}; };

View File

@@ -12,12 +12,16 @@
#define BEACONMODELLOGDISTCEILING_H #define BEACONMODELLOGDISTCEILING_H
#include "../../../floorplan/v2/Floorplan.h" #include "../../../floorplan/v2/Floorplan.h"
#include "../../../floorplan/v2/FloorplanCeilings.h"
#include "../../../Assertions.h" #include "../../../Assertions.h"
#include "BeaconModel.h" #include "BeaconModel.h"
#include "../../radio/model/LogDistanceModel.h" #include "../../radio/model/LogDistanceModel.h"
#include "../BeaconMeasurement.h" #include "../BeaconMeasurement.h"
#include "../../../data/XMLserialize.h"
#include "../../../misc/Debug.h"
#include <unordered_map> #include <unordered_map>
/** /**
@@ -26,63 +30,61 @@
*/ */
class BeaconModelLogDistCeiling : public BeaconModel { class BeaconModelLogDistCeiling : public BeaconModel {
static constexpr const char* name = "BLEModelLDC";
public: public:
/** parameters describing one beacon to the model */ /** parameters describing one beacon to the model */
struct APEntry { struct APEntry {
Point3 position_m; // the beacon's position (in meter) Point3 position_m; // the beacon's position (in meter)
float txp; // sending power (-40) float txp; // sending power (-40)
float exp; // path-loss-exponent (~2.0 - 4.0) float exp; // path-loss-exponent (~2.0 - 4.0)
float waf; // attenuation per ceiling/floor (~-8.0) float waf; // attenuation per ceiling/floor (~-8.0)
/** ctor */ /** ctor */
APEntry(const Point3 position_m, const float txp, const float exp, const float waf) : APEntry(const Point3 position_m, const float txp, const float exp, const float waf) :
position_m(position_m), txp(txp), exp(exp), waf(waf) {;} position_m(position_m), txp(txp), exp(exp), waf(waf) {;}
}; };
private: private:
/** map of all beacons (and their parameters) known to the model */ /** map of all beacons (and their parameters) known to the model */
std::unordered_map<MACAddress, APEntry> beacons; std::unordered_map<MACAddress, APEntry> beacons;
/** position (height) of all ceilings (in meter) */ /** position (height) of all ceilings (in meter) */
std::vector<float> ceilingsAtHeight_m; //std::vector<float> ceilingsAtHeight_m;
Floorplan::Ceilings ceilings;
public: public:
/** ctor with floorplan (needed for ceiling position) */ /** ctor with floorplan (needed for ceiling position) */
BeaconModelLogDistCeiling(const Floorplan::IndoorMap* map) { BeaconModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(map){
// sanity checks // sanity checks
Assert::isTrue(map->floors.size() >= 1, "map has no floors?!"); 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 a list of all beacons known to the model */ /** get a list of all beacons known to the model */
std::vector<Beacon> getAllBeacons() const { std::vector<Beacon> getAllBeacons() const {
std::vector<Beacon> aps; std::vector<Beacon> aps;
for (const auto it : beacons) {aps.push_back(Beacon(it.first));} for (const auto it : beacons) {aps.push_back(Beacon(it.first));}
return aps; return aps;
} }
/** load beacon information from the floorplan. use the given fixed TXP/EXP/WAF for all APs */ /** load beacon information from the floorplan. use the given fixed TXP/EXP/WAF for all APs */
void loadBeaconsFromMap(const Floorplan::IndoorMap* map, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f) { void loadBeaconsFromMap(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::Floor* floor : map->floors) {
for (const Floorplan::Beacon* beacon : floor->beacons) { for (const Floorplan::Beacon* beacon : floor->beacons) {
APEntry ape(beacon->getPos(floor), txp, exp, waf); APEntry ape(beacon->getPos(floor), txp, exp, waf);
addBeacon(MACAddress(beacon->mac), ape); addBeacon(MACAddress(beacon->mac), ape);
} }
} }
} }
/** load beacon information from a vector. use the given fixed TXP/EXP/WAF for all APs */ /** load beacon information from a vector. use the given fixed TXP/EXP/WAF for all APs */
void loadBeaconsFromVector(const Floorplan::IndoorMap* map, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f) { void loadBeaconsFromVector(const Floorplan::IndoorMap* map, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f) {
@@ -99,17 +101,17 @@ public:
/** make the given beacon (and its parameters) known to the model */ /** make the given beacon (and its parameters) known to the model */
void addBeacon(const MACAddress& beacon, const APEntry& params) { void addBeacon(const MACAddress& beacon, const APEntry& params) {
// sanity check // sanity check
Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]"); Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]");
Assert::isBetween(params.txp, -90.0f, -30.0f, "TXP out of bounds [-50:-30]"); Assert::isBetween(params.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]");
Assert::isBetween(params.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]"); Assert::isBetween(params.exp, 1.0f, 5.0f, "EXP out of bounds [1:5]");
//Assert::equal(beacons.find(beacon), beacons.end(), "AccessPoint already present!"); //Assert::equal(beacons.find(beacon), beacons.end(), "AccessPoint already present!");
// add // add
beacons.insert( std::pair<MACAddress, APEntry>(beacon, params) ); beacons.insert( std::pair<MACAddress, APEntry>(beacon, params) );
} }
void updateBeacon(const BeaconMeasurement beacon) override { void updateBeacon(const BeaconMeasurement beacon) override {
// try to get the corresponding parameters // try to get the corresponding parameters
@@ -123,94 +125,155 @@ public:
it->second.txp = beacon.getBeacon().getTXP(); it->second.txp = beacon.getBeacon().getTXP();
} }
/** remove all added APs */ /** remove all added APs */
void clear() { void clear() {
beacons.clear(); beacons.clear();
} }
float getRSSI(const MACAddress& beacon, const Point3 position_m) const override { float getRSSI(const MACAddress& beacon, const Point3 position_m) const override {
// try to get the corresponding parameters // try to get the corresponding parameters
const auto it = beacons.find(beacon); const auto it = beacons.find(beacon);
// beacon unknown? -> NAN // beacon unknown? -> NAN
if (it == beacons.end()) {return NAN;} if (it == beacons.end()) {return NAN;}
// the access-points' parameters // the access-points' parameters
const APEntry& params = it->second; const APEntry& params = it->second;
// free-space (line-of-sight) RSSI // free-space (line-of-sight) RSSI
const float distance_m = position_m.getDistance(params.position_m); const float distance_m = position_m.getDistance(params.position_m);
const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); 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 // 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.numCeilingsBetweenFloat(position_m, params.position_m);
// combine // 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", "BeaconModelLogDistCeiling");
for (const auto& it : beacons) {
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);
}
for (const float atHeight_m : ceilings.getCeilings()) {
XMLElem* xceil = doc->NewElement("ceiling");
xceil->SetAttribute("atHeight", atHeight_m);
dst->InsertEndChild(xceil);
}
}
void readFromXML(XMLDoc* doc, XMLElem* src) override {
// check type
if (std::string("BeaconModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");}
beacons.clear();
ceilings.clear();
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")
);
beacons.insert( std::make_pair(mac, ape) );
}
XML_FOREACH_ELEM_NAMED("ceiling", xceil, src) {
const float atHeight_m = xceil->FloatAttribute("atHeight");
ceilings.addCeiling(atHeight_m);
}
Log::add(name, "loaded " + std::to_string(beacons.size()) + " APs");
}
protected: //protected:
FRIEND_TEST(LogDistanceCeilingModelBeacon, numCeilings); // FRIEND_TEST(LogDistanceCeilingModelBeacon, numCeilings);
FRIEND_TEST(LogDistanceCeilingModelBeacon, numCeilingsFloat); // FRIEND_TEST(LogDistanceCeilingModelBeacon, numCeilingsFloat);
/** get the number of ceilings between z1 and z2 */ // /** get the number of ceilings between z1 and z2 */
float numCeilingsBetweenFloat(const Point3 pos1, const Point3 pos2) const { // float numCeilingsBetweenFloat(const Point3 pos1, const Point3 pos2) const {
const float zMin = std::min(pos1.z, pos2.z); // const float zMin = std::min(pos1.z, pos2.z);
const float zMax = std::max(pos1.z, pos2.z); // const float zMax = std::max(pos1.z, pos2.z);
float cnt = 0; // float cnt = 0;
for (const float z : ceilingsAtHeight_m) { // for (const float z : ceilingsAtHeight_m) {
if (zMin < z && zMax > z) { // if (zMin < z && zMax > z) {
const float dmax = zMax - z; // const float dmax = zMax - z;
cnt += (dmax > 1) ? (1) : (dmax); // cnt += (dmax > 1) ? (1) : (dmax);
} // }
} // }
return cnt; // return cnt;
} // }
/** get the number of ceilings between z1 and z2 */ // /** get the number of ceilings between z1 and z2 */
int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const { // int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const {
int cnt = 0; // int cnt = 0;
const float zMin = std::min(pos1.z, pos2.z); // const float zMin = std::min(pos1.z, pos2.z);
const float zMax = std::max(pos1.z, pos2.z); // const float zMax = std::max(pos1.z, pos2.z);
#ifdef WITH_ASSERTIONS //#ifdef WITH_ASSERTIONS
static int numNear = 0; // static int numNear = 0;
static int numFar = 0; // static int numFar = 0;
for (const float z : ceilingsAtHeight_m) { // for (const float z : ceilingsAtHeight_m) {
const float diff = std::min( std::abs(z-zMin), std::abs(z-zMax) ); // const float diff = std::min( std::abs(z-zMin), std::abs(z-zMax) );
if (diff < 0.1) {++numNear;} else {++numFar;} // if (diff < 0.1) {++numNear;} else {++numFar;}
} // }
if ((numNear + numFar) > 150000) { // if ((numNear + numFar) > 150000) {
Assert::isTrue(numNear < numFar*0.1, // Assert::isTrue(numNear < numFar*0.1,
"many requests to the WiFiModel address nodes (very) near to a ground! \ // "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! \ // due to rounding issues, determining the number of floors between AP and point-in-question is NOT possible! \
expect very wrong outputs! \ // expect very wrong outputs! \
consider adding the person's height to the questioned positions: p += Point3(0,0,1.3) " // consider adding the person's height to the questioned positions: p += Point3(0,0,1.3) "
); // );
} // }
#endif //#endif
for (const float z : ceilingsAtHeight_m) { // for (const float z : ceilingsAtHeight_m) {
if (zMin < z && zMax > z) {++cnt;} // if (zMin < z && zMax > z) {++cnt;}
} // }
return cnt; // return cnt;
} // }
}; };

View File

@@ -7,12 +7,12 @@
#include <unordered_map> #include <unordered_map>
/** /**
* denotes a wifi fingerprint: * denotes a beacon fingerprint:
* known position and 1-n measurements [wifi-scan on all channels] conducted at this position * known position and 1-n measurements [ble-advertisment-scan on all channels] conducted at this position
* *
* if more than one measurement is conducted, each AP is contained more than once! * if more than one measurement is conducted, each AP is contained more than once!
*/ */
struct WiFiFingerprint { struct BeaconFingerprint {
/** real-world-position that was measured */ /** real-world-position that was measured */
@@ -22,13 +22,13 @@ struct WiFiFingerprint {
BeaconMeasurements measurements; BeaconMeasurements measurements;
/** ctor */ /** ctor */
WiFiFingerprint() {;} BeaconFingerprint() {;}
/** ctor */ /** ctor */
WiFiFingerprint(const Point3 pos_m) : pos_m(pos_m) {;} BeaconFingerprint(const Point3 pos_m) : pos_m(pos_m) {;}
/** ctor */ /** ctor */
WiFiFingerprint(const Point3 pos_m, const BeaconMeasurements& measurements) : pos_m(pos_m), measurements(measurements) {;} BeaconFingerprint(const Point3 pos_m, const BeaconMeasurements& measurements) : pos_m(pos_m), measurements(measurements) {;}
/** as each AP might be 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 */

View File

@@ -23,88 +23,88 @@ class WiFiModelLogDist : public WiFiModel {
public: public:
/** parameters describing one AP to the model */ /** parameters describing one AP to the model */
struct APEntry { struct APEntry {
Point3 position_m; // the AP's position (in meter) Point3 position_m; // the AP's position (in meter)
float txp; // sending power (-40) float txp; // sending power (-40)
float exp; // path-loss-exponent (~2.0 - 4.0) float exp; // path-loss-exponent (~2.0 - 4.0)
/** ctor */ /** ctor */
APEntry(const Point3 position_m, const float txp, const float exp) : APEntry(const Point3 position_m, const float txp, const float exp) :
position_m(position_m), txp(txp), exp(exp) {;} position_m(position_m), txp(txp), exp(exp) {;}
}; };
private: private:
/** map of all APs (and their parameters) known to the model */ /** map of all APs (and their parameters) known to the model */
std::unordered_map<MACAddress, APEntry> accessPoints; std::unordered_map<MACAddress, APEntry> accessPoints;
public: public:
/** ctor */ /** ctor */
WiFiModelLogDist() { WiFiModelLogDist() {
; ;
} }
/** get a list of all APs known to the model */ /** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const { std::vector<AccessPoint> getAllAPs() const {
std::vector<AccessPoint> aps; std::vector<AccessPoint> aps;
for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));} for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));}
return aps; return aps;
} }
/** make the given AP (and its parameters) known to the model */ /** make the given AP (and its parameters) known to the model */
void addAP(const MACAddress& accessPoint, const APEntry& params) { void addAP(const MACAddress& accessPoint, const APEntry& params) {
// sanity check // sanity check
Assert::isBetween(params.txp, -50.0f, -30.0f, "TXP out of bounds [-90:-30]"); Assert::isBetween(params.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]");
Assert::isBetween(params.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]"); Assert::isBetween(params.exp, 1.0f, 5.0f, "EXP out of bounds [1:5]");
// add // add
accessPoints.insert( std::pair<MACAddress, APEntry>(accessPoint, params) ); accessPoints.insert( std::pair<MACAddress, APEntry>(accessPoint, params) );
} }
/** does the model know the given AP? */ /** does the model know the given AP? */
bool knowsAP(const MACAddress& accessPoint) { bool knowsAP(const MACAddress& accessPoint) {
// try to get the corresponding parameters // try to get the corresponding parameters
const auto it = accessPoints.find(accessPoint); const auto it = accessPoints.find(accessPoint);
// AP known? // AP known?
return (it != accessPoints.end()); return (it != accessPoints.end());
} }
virtual float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override { virtual float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
// try to get the corresponding parameters // try to get the corresponding parameters
const auto it = accessPoints.find(accessPoint); const auto it = accessPoints.find(accessPoint);
// AP unknown? -> NAN // AP unknown? -> NAN
if (it == accessPoints.end()) {return NAN;} if (it == accessPoints.end()) {return NAN;}
// the access-points' parameters // the access-points' parameters
const APEntry& params = it->second; const APEntry& params = it->second;
// free-space (line-of-sight) RSSI // free-space (line-of-sight) RSSI
const float distance_m = position_m.getDistance(params.position_m); const float distance_m = position_m.getDistance(params.position_m);
const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m);
// done // done
return rssiLOS; return rssiLOS;
} }
void readFromXML(XMLDoc* doc, XMLElem* src) override { void readFromXML(XMLDoc* doc, XMLElem* src) override {
throw Exception("not yet implemented"); throw Exception("not yet implemented");
} }
void writeToXML(XMLDoc* doc, XMLElem* dst) override { void writeToXML(XMLDoc* doc, XMLElem* dst) override {
throw Exception("not yet implemented"); throw Exception("not yet implemented");
} }
}; };

View File

@@ -28,217 +28,217 @@
*/ */
class WiFiModelLogDistCeiling : public WiFiModel { class WiFiModelLogDistCeiling : public WiFiModel {
static constexpr const char* name = "WifiModelLDC"; static constexpr const char* name = "WifiModelLDC";
public: public:
/** parameters describing one AP to the model */ /** parameters describing one AP to the model */
struct APEntry { struct APEntry {
Point3 position_m; // the AP's position (in meter) Point3 position_m; // the AP's position (in meter)
float txp; // sending power (-40) float txp; // sending power (-40)
float exp; // path-loss-exponent (~2.0 - 4.0) float exp; // path-loss-exponent (~2.0 - 4.0)
float waf; // attenuation per ceiling/floor (~-8.0) float waf; // attenuation per ceiling/floor (~-8.0)
/** ctor */ /** ctor */
APEntry(const Point3 position_m, const float txp, const float exp, const float waf) : APEntry(const Point3 position_m, const float txp, const float exp, const float waf) :
position_m(position_m), txp(txp), exp(exp), waf(waf) {;} position_m(position_m), txp(txp), exp(exp), waf(waf) {;}
}; };
private: private:
/** map of all APs (and their parameters) known to the model */ /** map of all APs (and their parameters) known to the model */
std::unordered_map<MACAddress, APEntry> accessPoints; std::unordered_map<MACAddress, APEntry> accessPoints;
// /** position (height) of all ceilings (in meter) */ // /** position (height) of all ceilings (in meter) */
// std::vector<float> ceilingsAtHeight_m; // std::vector<float> ceilingsAtHeight_m;
Floorplan::Ceilings ceilings; Floorplan::Ceilings ceilings;
public: public:
/** ctor with floorplan (needed for ceiling position) */ /** ctor with floorplan (needed for ceiling position) */
WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(map) { WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(map) {
// sanity checks // sanity checks
Assert::isTrue(map->floors.size() >= 1, "map has no floors?!"); Assert::isTrue(map->floors.size() >= 1, "map has no floors?!");
} }
/** get the entry for the given mac. exception if missing */ /** get the entry for the given mac. exception if missing */
const APEntry& getAP(const MACAddress& mac) const { const APEntry& getAP(const MACAddress& mac) const {
const auto& it = accessPoints.find(mac); const auto& it = accessPoints.find(mac);
if (it == accessPoints.end()) {throw Exception("model does not contain an entry for " + mac.asString());} if (it == accessPoints.end()) {throw Exception("model does not contain an entry for " + mac.asString());}
return it->second; return it->second;
} }
/** get a list of all APs known to the model */ /** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const { std::vector<AccessPoint> getAllAPs() const {
std::vector<AccessPoint> aps; std::vector<AccessPoint> aps;
for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));} for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));}
return aps; return aps;
} }
/** load AP information from the floorplan. use the given fixed TXP/EXP/WAF for all 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, const bool assertSafe = true) { 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::Floor* floor : map->floors) {
for (const Floorplan::AccessPoint* ap : floor->accesspoints) { for (const Floorplan::AccessPoint* ap : floor->accesspoints) {
const APEntry ape(ap->getPos(floor), txp, exp, waf); const APEntry ape(ap->getPos(floor), txp, exp, waf);
addAP(MACAddress(ap->mac), ape, assertSafe); addAP(MACAddress(ap->mac), ape, assertSafe);
} }
} }
} }
/** /**
* load AP information from the floorplan. * load AP information from the floorplan.
* use the given fixed TXP/EXP/WAF for all APs. * 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! * 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) { 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::Floor* floor : map->floors) {
for (const Floorplan::AccessPoint* ap : floor->accesspoints) { for (const Floorplan::AccessPoint* ap : floor->accesspoints) {
const APEntry ape(ap->getPos(floor), txp, exp, waf); const APEntry ape(ap->getPos(floor), txp, exp, waf);
const MACAddress mac = vg.getBaseMAC(MACAddress(ap->mac)); const MACAddress mac = vg.getBaseMAC(MACAddress(ap->mac));
Log::add("WiModLDC", "AP added! given: " + ap->mac + " -> after VAP: " + mac.asString()); Log::add("WiModLDC", "AP added! given: " + ap->mac + " -> after VAP: " + mac.asString());
addAP(MACAddress(mac), ape, assertSafe); addAP(MACAddress(mac), ape, assertSafe);
} }
} }
} }
/** /**
* make the given AP (and its parameters) known to the model * 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! * 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) { void addAP(const MACAddress& accessPoint, const APEntry& params, const bool assertSafe = true) {
// sanity check // sanity check
if (assertSafe) { if (assertSafe) {
Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]"); 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.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]");
Assert::isBetween(params.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]"); Assert::isBetween(params.exp, 1.0f, 5.0f, "EXP out of bounds [1:5]");
} }
Assert::equal(accessPoints.find(accessPoint), accessPoints.end(), "AccessPoint already present! VAP-Grouping issue?"); Assert::equal(accessPoints.find(accessPoint), accessPoints.end(), "AccessPoint already present! VAP-Grouping issue?");
// add // add
accessPoints.insert( std::pair<MACAddress, APEntry>(accessPoint, params) ); accessPoints.insert( std::pair<MACAddress, APEntry>(accessPoint, params) );
} }
/** /**
* make the given AP (and its parameters) known to the model * 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! * 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) { 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); addAP(accessPoint, APEntry(pos_m, txp, exp, waf), assertSafe);
} }
/** remove all added APs */ /** remove all added APs */
void clear() { void clear() {
accessPoints.clear(); accessPoints.clear();
} }
/** does the model know the given AP? */ /** does the model know the given AP? */
bool knowsAP(const MACAddress& accessPoint) { bool knowsAP(const MACAddress& accessPoint) {
// try to get the corresponding parameters // try to get the corresponding parameters
const auto it = accessPoints.find(accessPoint); const auto it = accessPoints.find(accessPoint);
// AP known? // AP known?
return (it != accessPoints.end()); return (it != accessPoints.end());
} }
float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override { float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
// try to get the corresponding parameters // try to get the corresponding parameters
const auto it = accessPoints.find(accessPoint); const auto it = accessPoints.find(accessPoint);
// AP unknown? -> NAN // AP unknown? -> NAN
if (it == accessPoints.end()) {return NAN;} if (it == accessPoints.end()) {return NAN;}
// the access-points' parameters // the access-points' parameters
const APEntry& params = it->second; const APEntry& params = it->second;
// free-space (line-of-sight) RSSI // free-space (line-of-sight) RSSI
const float distance_m = position_m.getDistance(params.position_m); const float distance_m = position_m.getDistance(params.position_m);
const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); 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 // WAF loss (params.waf is a negative value!) -> WAF loss is also a negative value
//const float wafLoss = params.waf * ceilings.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.numCeilingsBetweenFloat(position_m, params.position_m);
//const float wafLoss = params.waf * ceilings.numCeilingsBetweenLinearInt(position_m, params.position_m); //const float wafLoss = params.waf * ceilings.numCeilingsBetweenLinearInt(position_m, params.position_m);
// combine // combine
const float res = rssiLOS + wafLoss; const float res = rssiLOS + wafLoss;
// sanity check // sanity check
Assert::isNotNaN(res, "detected NaN within WiFiModelLogDistCeiling::getRSSI()"); Assert::isNotNaN(res, "detected NaN within WiFiModelLogDistCeiling::getRSSI()");
// ok // ok
return res; return res;
} }
void writeToXML(XMLDoc* doc, XMLElem* dst) override { void writeToXML(XMLDoc* doc, XMLElem* dst) override {
// set my type // set my type
dst->SetAttribute("type", "WiFiModelLogDistCeiling"); dst->SetAttribute("type", "WiFiModelLogDistCeiling");
for (const auto& it : accessPoints) { for (const auto& it : accessPoints) {
const MACAddress& mac = it.first; const MACAddress& mac = it.first;
const APEntry& ape = it.second; const APEntry& ape = it.second;
XMLElem* xap = doc->NewElement("ap"); XMLElem* xap = doc->NewElement("ap");
xap->SetAttribute("mac", mac.asString().c_str()); xap->SetAttribute("mac", mac.asString().c_str());
xap->SetAttribute("px", ape.position_m.x); xap->SetAttribute("px", ape.position_m.x);
xap->SetAttribute("py", ape.position_m.y); xap->SetAttribute("py", ape.position_m.y);
xap->SetAttribute("pz", ape.position_m.z); xap->SetAttribute("pz", ape.position_m.z);
xap->SetAttribute("txp", ape.txp); xap->SetAttribute("txp", ape.txp);
xap->SetAttribute("exp", ape.exp); xap->SetAttribute("exp", ape.exp);
xap->SetAttribute("waf", ape.waf); xap->SetAttribute("waf", ape.waf);
dst->InsertEndChild(xap); dst->InsertEndChild(xap);
} }
for (const float atHeight_m : ceilings.getCeilings()) { for (const float atHeight_m : ceilings.getCeilings()) {
XMLElem* xceil = doc->NewElement("ceiling"); XMLElem* xceil = doc->NewElement("ceiling");
xceil->SetAttribute("atHeight", atHeight_m); xceil->SetAttribute("atHeight", atHeight_m);
dst->InsertEndChild(xceil); dst->InsertEndChild(xceil);
} }
} }
void readFromXML(XMLDoc* doc, XMLElem* src) override { void readFromXML(XMLDoc* doc, XMLElem* src) override {
// check type // check type
if (std::string("WiFiModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");} if (std::string("WiFiModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");}
accessPoints.clear(); accessPoints.clear();
ceilings.clear(); ceilings.clear();
XML_FOREACH_ELEM_NAMED("ap", xap, src) { XML_FOREACH_ELEM_NAMED("ap", xap, src) {
MACAddress mac = MACAddress(xap->Attribute("mac")); MACAddress mac = MACAddress(xap->Attribute("mac"));
APEntry ape( APEntry ape(
Point3(xap->FloatAttribute("px"), xap->FloatAttribute("py"), xap->FloatAttribute("pz")), Point3(xap->FloatAttribute("px"), xap->FloatAttribute("py"), xap->FloatAttribute("pz")),
xap->FloatAttribute("txp"), xap->FloatAttribute("txp"),
xap->FloatAttribute("exp"), xap->FloatAttribute("exp"),
xap->FloatAttribute("waf") xap->FloatAttribute("waf")
); );
accessPoints.insert( std::make_pair(mac, ape) ); accessPoints.insert( std::make_pair(mac, ape) );
} }
XML_FOREACH_ELEM_NAMED("ceiling", xceil, src) { XML_FOREACH_ELEM_NAMED("ceiling", xceil, src) {
const float atHeight_m = xceil->FloatAttribute("atHeight"); const float atHeight_m = xceil->FloatAttribute("atHeight");
ceilings.addCeiling(atHeight_m); ceilings.addCeiling(atHeight_m);
} }
Log::add(name, "loaded " + std::to_string(accessPoints.size()) + " APs"); Log::add(name, "loaded " + std::to_string(accessPoints.size()) + " APs");
} }
}; };

View File

@@ -11,6 +11,7 @@
#ifndef WIFIOPTIMIZER_H #ifndef WIFIOPTIMIZER_H
#define WIFIOPTIMIZER_H #define WIFIOPTIMIZER_H
#include "../../beacon/setup/BeaconFingerprint.h"
#include "WiFiFingerprints.h" #include "WiFiFingerprints.h"
#include "WiFiOptimizerStructs.h" #include "WiFiOptimizerStructs.h"
#include "../VAPGrouper.h" #include "../VAPGrouper.h"
@@ -19,65 +20,79 @@
namespace WiFiOptimizer { namespace WiFiOptimizer {
enum class Mode { enum class Mode {
FAST, FAST,
MEDIUM, MEDIUM,
QUALITY, QUALITY,
}; };
/** base-class for all WiFiOptimizers */ /** base-class for all WiFiOptimizers */
class Base { class Base {
protected: protected:
/** each MAC-Adress has several position->rssi entries */ /** each MAC-Adress has several position->rssi entries */
std::unordered_map<MACAddress, std::vector<RSSIatPosition>> apMap; std::unordered_map<MACAddress, std::vector<RSSIatPosition>> apMap;
/** how to handle virtual access points [group, not-group, ..] */ /** how to handle virtual access points [group, not-group, ..] */
const VAPGrouper vg; const VAPGrouper vg;
public: public:
/** ctor */ /** ctor */
Base(const VAPGrouper vg) : vg(vg) {;} Base(const VAPGrouper vg) : vg(vg) {;}
/** get a list of all to-be-optimized access-points (given by their mac-address) */ /** get a list of all to-be-optimized access-points (given by their mac-address) */
virtual std::vector<MACAddress> getAllMACs() const { virtual std::vector<MACAddress> getAllMACs() const {
std::vector<MACAddress> res; std::vector<MACAddress> res;
for (const auto& it : apMap) {res.push_back(it.first);} for (const auto& it : apMap) {res.push_back(it.first);}
return res; return res;
} }
/** get all [VAPGrouped, Averaged] fingerprints for the given mac */ /** get all [VAPGrouped, Averaged] fingerprints for the given mac */
virtual const std::vector<RSSIatPosition>& getFingerprintsFor(const MACAddress& mac) { virtual const std::vector<RSSIatPosition>& getFingerprintsFor(const MACAddress& mac) {
const auto& it = apMap.find(mac); const auto& it = apMap.find(mac);
if (it == apMap.end()) {throw Exception("mac not found: " + mac.asString());} if (it == apMap.end()) {throw Exception("mac not found: " + mac.asString());}
return it->second; return it->second;
} }
/** add a new fingerprint to the optimizer as data-source */ /** add a new fingerprint to the optimizer as data-source */
virtual void addFingerprint(const WiFiFingerprint& fp) { virtual void addFingerprint(const WiFiFingerprint& fp) {
// group the fingerprint's measurements by VAP (if configured) // group the fingerprint's measurements by VAP (if configured)
const WiFiMeasurements measurements = vg.group(fp.measurements); const WiFiMeasurements measurements = vg.group(fp.measurements);
// add each available AP to its slot (lookup map) // add each available AP to its slot (lookup map)
for (const WiFiMeasurement& m : measurements.entries) { for (const WiFiMeasurement& m : measurements.entries) {
const RSSIatPosition rap(fp.pos_m, m.getRSSI()); const RSSIatPosition rap(fp.pos_m, m.getRSSI());
apMap[m.getAP().getMAC()].push_back(rap); apMap[m.getAP().getMAC()].push_back(rap);
} }
} }
/** add new fingerprints to the optimizer as data-source */ /** add a new bluetooth fingerprint to the optimizer as data-source */
virtual void addFingerprints(const WiFiFingerprints& fps) { virtual void addFingerprint(const BeaconFingerprint& fp) {
for (const WiFiFingerprint& fp : fps.getFingerprints()) {
addFingerprint(fp);
}
}
}; // group the fingerprint's measurements by VAP (if configured)
//const BeaconMeasurements measurements = vg.group(fp.measurements);
// add each available AP to its slot (lookup map)
for (const BeaconMeasurement& m : fp.measurements.entries) {
const RSSIatPosition rap(fp.pos_m, m.getRSSI());
apMap[m.getBeacon().getMAC()].push_back(rap);
}
}
/** add new fingerprints to the optimizer as data-source */
virtual void addFingerprints(const WiFiFingerprints& fps) {
for (const WiFiFingerprint& fp : fps.getFingerprints()) {
addFingerprint(fp);
}
}
};
} }

View File

@@ -37,355 +37,355 @@
namespace WiFiOptimizer { namespace WiFiOptimizer {
/** /**
* optimize access-point parameters, * optimize access-point parameters,
* given several fingerprints using the log-dist-ceiling model * given several fingerprints using the log-dist-ceiling model
*/ */
struct LogDistCeiling : public Base { struct LogDistCeiling : public Base {
public: public:
/** /**
* resulting optimization stats for one AP * resulting optimization stats for one AP
*/ */
struct Stats { struct Stats {
/** average model<->scan error after optimzing */ /** average model<->scan error after optimzing */
float error_db; float error_db;
/** number of fingerprints [= locations] that were used for optimzing */ /** number of fingerprints [= locations] that were used for optimzing */
int usedFingerprins; int usedFingerprins;
/** resulting model<->scan error after optimzing for each individual fingerprints [= location] */ /** resulting model<->scan error after optimzing for each individual fingerprints [= location] */
std::vector<ErrorAtPosition> errors; std::vector<ErrorAtPosition> errors;
/** get the location where the model estimation reaches the highest negative value [model estimation too low] */ /** get the location where the model estimation reaches the highest negative value [model estimation too low] */
ErrorAtPosition getEstErrorMaxNeg() const { ErrorAtPosition getEstErrorMaxNeg() const {
auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();}; auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();};
return *std::min_element(errors.begin(), errors.end(), cmpErrAtPos); 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] */ /** get the location where the model estimation reaches the highest positive value [model estimation too high] */
ErrorAtPosition getEstErrorMaxPos() const { ErrorAtPosition getEstErrorMaxPos() const {
auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();}; auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();};
return *std::max_element(errors.begin(), errors.end(), cmpErrAtPos); return *std::max_element(errors.begin(), errors.end(), cmpErrAtPos);
} }
}; };
using APFilter = std::function<bool(const Stats& stats, const MACAddress& mac)>; using APFilter = std::function<bool(const Stats& stats, const MACAddress& mac)>;
static inline bool NONE(const Stats& stats, const MACAddress& mac) { static inline bool NONE(const Stats& stats, const MACAddress& mac) {
(void) stats; (void) mac; (void) stats; (void) mac;
return false; return false;
} }
static inline bool MIN_2_FPS(const Stats& stats, const MACAddress& mac) { static inline bool MIN_2_FPS(const Stats& stats, const MACAddress& mac) {
(void) mac; (void) mac;
return stats.usedFingerprins < 2; return stats.usedFingerprins < 2;
} }
static inline bool MIN_5_FPS(const Stats& stats, const MACAddress& mac) { static inline bool MIN_5_FPS(const Stats& stats, const MACAddress& mac) {
(void) mac; (void) mac;
return stats.usedFingerprins < 5; return stats.usedFingerprins < 5;
} }
static inline bool MIN_10_FPS(const Stats& stats, const MACAddress& mac) { static inline bool MIN_10_FPS(const Stats& stats, const MACAddress& mac) {
(void) mac; (void) mac;
return stats.usedFingerprins < 10; return stats.usedFingerprins < 10;
} }
/** parameters for one AP when using the LogDistCeiling model */ /** parameters for one AP when using the LogDistCeiling model */
struct APParams { struct APParams {
float x; float x;
float y; float y;
float z; float z;
float txp; float txp;
float exp; float exp;
float waf; float waf;
/** ctor */ /** ctor */
APParams() {;} APParams() {;}
/** ctor */ /** 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) {;} 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) {;}
Point3 getPos() const {return Point3(x,y,z);} Point3 getPos() const {return Point3(x,y,z);}
std::string asString() const { std::string asString() const {
std::stringstream ss; std::stringstream ss;
ss << "Pos:" << getPos().asString() << " TXP:" << txp << " EXP:" << exp << " WAF:" << waf; ss << "Pos:" << getPos().asString() << " TXP:" << txp << " EXP:" << exp << " WAF:" << waf;
return ss.str(); return ss.str();
} }
/** we add some constraints to the parameter range */ /** we add some constraints to the parameter range */
bool outOfRange() const { bool outOfRange() const {
return (waf > 0) || return (waf > 0) ||
(txp < -50) || (txp < -65) ||
(txp > -30) || (txp > -45) ||
(exp > 4) || (exp > 5) ||
(exp < 1); (exp < 1);
} }
}; };
/** add MAC-info to params */ /** add MAC-info to params */
struct APParamsMAC { struct APParamsMAC {
MACAddress mac; MACAddress mac;
APParams params; APParams params;
APParamsMAC(const MACAddress mac, const APParams& params) : mac(mac), params(params) {;} APParamsMAC(const MACAddress mac, const APParams& params) : mac(mac), params(params) {;}
}; };
struct OptResultStats { struct OptResultStats {
K::Statistics<float> avgApErrors; // contains one average-error per optimized AP K::Statistics<float> avgApErrors; // contains one average-error per optimized AP
K::Statistics<float> singleFPErrors; // contains one error for each fingerprint<->ap SIGNED K::Statistics<float> singleFPErrors; // contains one error for each fingerprint<->ap SIGNED
K::Statistics<float> singleFPErrorsAbs; // contains one error for each fingerprint<->ap ABSOLUTE K::Statistics<float> singleFPErrorsAbs; // contains one error for each fingerprint<->ap ABSOLUTE
}; };
class APParamsList { class APParamsList {
std::vector<APParamsMAC> lst; std::vector<APParamsMAC> lst;
public: public:
/** ctor */ /** ctor */
APParamsList(const std::vector<APParamsMAC>& lst) : lst(lst) { APParamsList(const std::vector<APParamsMAC>& lst) : lst(lst) {
} }
/** get the list */ /** get the list */
const std::vector<APParamsMAC>& get() const { const std::vector<APParamsMAC>& get() const {
return lst; return lst;
} }
/** get params for the given mac [if known, otherwise nullptr] */ /** get params for the given mac [if known, otherwise nullptr] */
const APParamsMAC* get (const MACAddress& mac) const { const APParamsMAC* get (const MACAddress& mac) const {
for (const APParamsMAC& ap : lst) { for (const APParamsMAC& ap : lst) {
if (ap.mac == mac) {return &ap;} if (ap.mac == mac) {return &ap;}
} }
return nullptr; return nullptr;
} }
}; };
private: private:
Floorplan::IndoorMap* map; Floorplan::IndoorMap* map;
Mode mode = Mode::QUALITY; Mode mode = Mode::QUALITY;
const char* name = "WiFiOptLDC"; const char* name = "WiFiOptLDC";
public: public:
/** ctor */ /** ctor */
LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const Mode mode = Mode::QUALITY) : Base(vg), map(map), mode(mode) { LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const Mode mode = Mode::MEDIUM) : Base(vg), map(map), mode(mode) {
; ;
} }
/** ctor */ /** ctor */
LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const WiFiFingerprints& fps, const Mode mode = Mode::QUALITY) : Base(vg), map(map), mode(mode) { LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const WiFiFingerprints& fps, const Mode mode = Mode::MEDIUM) : Base(vg), map(map), mode(mode) {
addFingerprints(fps); addFingerprints(fps);
} }
/** optimize all known APs */ /** optimize all known APs */
APParamsList optimizeAll(APFilter filter, OptResultStats* dst = nullptr) const { APParamsList optimizeAll(APFilter filter, OptResultStats* dst = nullptr) const {
// sanity check // sanity check
Assert::isFalse(getAllMACs().empty(), "no APs found for optimization! call addFingerprint() first!"); Assert::isFalse(getAllMACs().empty(), "no APs found for optimization! call addFingerprint() first!");
K::Statistics<float> avgErrors; K::Statistics<float> avgErrors;
K::Statistics<float> singleErrors; K::Statistics<float> singleErrors;
K::Statistics<float> singleErrorsAbs; K::Statistics<float> singleErrorsAbs;
std::vector<APParamsMAC> res; std::vector<APParamsMAC> res;
for (const MACAddress& mac : getAllMACs()) { for (const MACAddress& mac : getAllMACs()) {
// perform optimization, get resulting parameters and optimization stats // perform optimization, get resulting parameters and optimization stats
Stats stats; Stats stats;
const APParams params = optimize(mac, stats); const APParams params = optimize(mac, stats);
// filter based on stats (option to ignore/filter some access-points) // filter based on stats (option to ignore/filter some access-points)
if (!filter(stats, mac)) { if (!filter(stats, mac)) {
res.push_back(APParamsMAC(mac, params)); res.push_back(APParamsMAC(mac, params));
//errSum += stats.error_db; //errSum += stats.error_db;
//++errCnt; //++errCnt;
avgErrors.add(stats.error_db); avgErrors.add(stats.error_db);
for (const auto e : stats.errors) { for (const auto e : stats.errors) {
singleErrors.add(e.getError_db()); singleErrors.add(e.getError_db());
singleErrorsAbs.add(std::abs(e.getError_db())); singleErrorsAbs.add(std::abs(e.getError_db()));
} }
} else { } else {
Log::add(name, "ignoring opt-result for AP " + mac.asString() + " due to filter"); Log::add(name, "ignoring opt-result for AP " + mac.asString() + " due to filter");
//std::cout << "ignored due to filter!" << std::endl; //std::cout << "ignored due to filter!" << std::endl;
} }
} }
//const float avgErr = errSum / errCnt; //const float avgErr = errSum / errCnt;
//Log::add(name, "optimized APs: " + std::to_string(errCnt)); //Log::add(name, "optimized APs: " + std::to_string(errCnt));
//Log::add(name, "average AP error is: " + std::to_string(avgErr) + " dB"); //Log::add(name, "average AP error is: " + std::to_string(avgErr) + " dB");
Log::add(name, "optimization result: "); Log::add(name, "optimization result: ");
Log::add(name, " - AvgPerAP " + avgErrors.asString()); Log::add(name, " - AvgPerAP " + avgErrors.asString());
Log::add(name, " - Single: " + singleErrors.asString()); Log::add(name, " - Single: " + singleErrors.asString());
Log::add(name, " - SingleAbs: " + singleErrorsAbs.asString()); Log::add(name, " - SingleAbs: " + singleErrorsAbs.asString());
if (dst) { if (dst) {
dst->avgApErrors = avgErrors; dst->avgApErrors = avgErrors;
dst->singleFPErrors = singleErrors; dst->singleFPErrors = singleErrors;
dst->singleFPErrorsAbs = singleErrorsAbs; dst->singleFPErrorsAbs = singleErrorsAbs;
} }
// done // done
return APParamsList(res); return APParamsList(res);
} }
/** optimize the given AP */ /** optimize the given AP */
APParams optimize(const MACAddress& mac, Stats& res) const { APParams optimize(const MACAddress& mac, Stats& res) const {
// starting parameters do not matter for the current optimizer! // starting parameters do not matter for the current optimizer!
APParams params(0,0,0, -40, 2.5, -4.0); APParams params(0,0,0, -59, 3, -8.0);
// get all position->rssi measurements for this AP to compare them with the corresponding model estimations // 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; const std::vector<RSSIatPosition>& entries = apMap.find(mac)->second;
// log // log
Log::add(name, "optimizing parameters for AP " + mac.asString() + " by using " + std::to_string(entries.size()) + " fingerprints", false); Log::add(name, "optimizing parameters for AP " + mac.asString() + " by using " + std::to_string(entries.size()) + " fingerprints", false);
Log::tick(); Log::tick();
// get the map's size // get the map's size
const BBox3 mapBBox = FloorplanHelper::getBBox(map); const BBox3 mapBBox = FloorplanHelper::getBBox(map);
using LeOpt = K::NumOptAlgoRangeRandom<float>; using LeOpt = K::NumOptAlgoRangeRandom<float>;
const std::vector<LeOpt::MinMax> valRegion = { const std::vector<LeOpt::MinMax> valRegion = {
LeOpt::MinMax(mapBBox.getMin().x - 20, mapBBox.getMax().x + 20), // x 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().y - 20, mapBBox.getMax().y + 20), // y
LeOpt::MinMax(mapBBox.getMin().z - 6, mapBBox.getMax().z + 6), // z LeOpt::MinMax(mapBBox.getMin().z - 6, mapBBox.getMax().z + 6), // z
LeOpt::MinMax(-50, -30), // txp LeOpt::MinMax(-65, -45), // txp
LeOpt::MinMax(1, 4), // exp LeOpt::MinMax(1, 5), // exp
LeOpt::MinMax(-15, -0), // waf LeOpt::MinMax(-15, -0), // waf
}; };
LeOpt opt(valRegion); LeOpt opt(valRegion);
switch(mode) { switch(mode) {
case Mode::FAST: case Mode::FAST:
opt.setPopulationSize(100); opt.setPopulationSize(100);
opt.setNumIerations(50); opt.setNumIerations(50);
break; break;
case Mode::MEDIUM: case Mode::MEDIUM:
opt.setPopulationSize(200); opt.setPopulationSize(200);
opt.setNumIerations(100); opt.setNumIerations(100);
break; break;
case Mode::QUALITY: case Mode::QUALITY:
opt.setPopulationSize(1500); opt.setPopulationSize(1500);
opt.setNumIerations(150); opt.setNumIerations(150);
break; break;
} }
// error function // error function
auto func = [&] (const float* params) { auto func = [&] (const float* params) {
return getErrorLogDistCeiling(mac, entries, params, nullptr); return getErrorLogDistCeiling(mac, entries, params, nullptr);
}; };
opt.calculateOptimum(func, (float*) &params); opt.calculateOptimum(func, (float*) &params);
// using LeOpt = K::NumOptAlgoGenetic<float>; // using LeOpt = K::NumOptAlgoGenetic<float>;
// LeOpt opt(6); // LeOpt opt(6);
// opt.setPopulationSize(750); // opt.setPopulationSize(750);
// opt.setMaxIterations(50); // opt.setMaxIterations(50);
// opt.setElitism(0.05f); // opt.setElitism(0.05f);
// opt.setMutation(0.75f); // opt.setMutation(0.75f);
// //opt.setValRange({0.5, 0.5, 0.5, 0.1, 0.1, 0.1}); // //opt.setValRange({0.5, 0.5, 0.5, 0.1, 0.1, 0.1});
// opt.setValRegion(valRegion); // opt.setValRegion(valRegion);
// K::NumOptAlgoDownhillSimplex<float, 6> opt; // K::NumOptAlgoDownhillSimplex<float, 6> opt;
// opt.setMaxIterations(100); // opt.setMaxIterations(100);
// opt.setNumRestarts(10); // opt.setNumRestarts(10);
opt.calculateOptimum(func, (float*) &params); opt.calculateOptimum(func, (float*) &params);
res.error_db = getErrorLogDistCeiling(mac, entries, (float*)&params, &res); res.error_db = getErrorLogDistCeiling(mac, entries, (float*)&params, &res);
res.usedFingerprins = entries.size(); res.usedFingerprins = entries.size();
Log::tock(); Log::tock();
Log::add(name, mac.asString() + ": " + params.asString() + " @ " + std::to_string(res.error_db) +" dB err"); Log::add(name, mac.asString() + ": " + params.asString() + " @ " + std::to_string(res.error_db) +" dB err");
return params; return params;
} }
private: private:
float getErrorLogDistCeiling(const MACAddress& mac, const std::vector<RSSIatPosition>& entries, const float* data, Stats* stats = nullptr) const { float getErrorLogDistCeiling(const MACAddress& mac, const std::vector<RSSIatPosition>& entries, const float* data, Stats* stats = nullptr) const {
const APParams* params = (APParams*) data; const APParams* params = (APParams*) data;
// some sanity checks // some sanity checks
if (params->outOfRange()) {return 1e10;} if (params->outOfRange()) {return 1e10;}
// current position guess for the AP; // current position guess for the AP;
const Point3 apPos_m = params->getPos(); const Point3 apPos_m = params->getPos();
// add the AP [described by the current guess] to the signal-strength-prediction model // add the AP [described by the current guess] to the signal-strength-prediction model
// signal-strength-prediction-model... // signal-strength-prediction-model...
WiFiModelLogDistCeiling model(map); WiFiModelLogDistCeiling model(map);
model.addAP(mac, WiFiModelLogDistCeiling::APEntry(apPos_m, params->txp, params->exp, params->waf)); model.addAP(mac, WiFiModelLogDistCeiling::APEntry(apPos_m, params->txp, params->exp, params->waf));
float err = 0; float err = 0;
int cnt = 0; int cnt = 0;
// process each measurement // process each measurement
for (const RSSIatPosition& reading : entries) { for (const RSSIatPosition& reading : entries) {
// get the model-estimation for the fingerprint's position // get the model-estimation for the fingerprint's position
const float rssiModel = model.getRSSI(mac, reading.pos_m); const float rssiModel = model.getRSSI(mac, reading.pos_m);
// difference between estimation and measurement // difference between estimation and measurement
const float diff = std::abs(rssiModel - reading.rssi); const float diff = std::abs(rssiModel - reading.rssi);
// add error to stats object? // add error to stats object?
if (stats) { if (stats) {
stats->errors.push_back(ErrorAtPosition(reading.pos_m, reading.rssi, rssiModel)); stats->errors.push_back(ErrorAtPosition(reading.pos_m, reading.rssi, rssiModel));
} }
// adjust the error // adjust the error
err += std::pow(std::abs(diff), 2.0); err += std::pow(std::abs(diff), 2.0);
++cnt; ++cnt;
// max distance penality // max distance penality
// [unlikely to get a reading for this AP here!] // [unlikely to get a reading for this AP here!]
if (apPos_m.getDistance(reading.pos_m) > 150) {err += 999999;} if (apPos_m.getDistance(reading.pos_m) > 150) {err += 999999;}
} }
err /= cnt; err /= cnt;
err = std::sqrt(err); err = std::sqrt(err);
if (params->txp < -50) {err += 999999;} if (params->txp < -65) {err += 999999;}
if (params->txp > -35) {err += 999999;} if (params->txp > -45) {err += 999999;}
if (params->exp > 3.5) {err += 999999;} if (params->exp > 5) {err += 999999;}
if (params->exp < 1.0) {err += 999999;} if (params->exp < 1.0) {err += 999999;}
return err; return err;
} }
}; };
} }