diff --git a/sensors/beacon/BeaconMeasurementGrouper.h b/sensors/beacon/BeaconMeasurementGrouper.h new file mode 100644 index 0000000..e3b3f7e --- /dev/null +++ b/sensors/beacon/BeaconMeasurementGrouper.h @@ -0,0 +1,276 @@ +#ifndef BEACONMEASUREMENTGROUPER_H +#define BEACONMEASUREMENTGROUPER_H + +#include +#include +#include + +#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> 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& 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& 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(vaps); break; + case Aggregation::MEDIAN: rssi = getMedian(vaps); break; + case Aggregation::MAXIMUM: rssi = getMax(vaps); break; + case Aggregation::STD_DEV: rssi = getStdDev(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(vaps); break; + case TimeAggregation::AVERAGE: baseTS = getAVG(vaps); break; + case TimeAggregation::MAXIMUM: baseTS = getMax(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 static inline T getAVG(const std::vector& vaps) { + + Stats::Average avg; + for (const BeaconMeasurement& vap : vaps) { + avg.add(Field::get(vap)); + } + return avg.get(); + + } + + /** get the std-dev around the average */ + template static inline T getStdDev(const std::vector& vaps) { + + Stats::Variance 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& vaps) { + + Stats::Median median; + for (const BeaconMeasurement& vap : vaps) { + median.add(vap.getRSSI()); + } + return median.get(); + + } + + /** get the maximum value */ + template static inline T getMax(const std::vector& vaps) { + + Stats::Maximum max; + for (const BeaconMeasurement& vap : vaps) { + max.add(Field::get(vap)); + } + return max.get(); + + } + + /** get the minimum value */ + template static inline T getMin(const std::vector& vaps) { + + Stats::Minimum 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 diff --git a/sensors/beacon/BeaconMeasurements.h b/sensors/beacon/BeaconMeasurements.h index d25a7b8..6cad0fa 100644 --- a/sensors/beacon/BeaconMeasurements.h +++ b/sensors/beacon/BeaconMeasurements.h @@ -22,12 +22,12 @@ struct BeaconMeasurements { std::vector entries; - /** remove entries older then 3000 ms*/ + /** remove entries older then 1000 ms*/ void removeOld(const Timestamp latestTS) { std::vector::iterator 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); } } diff --git a/sensors/beacon/BeaconProbabilityFree.h b/sensors/beacon/BeaconProbabilityFree.h index 91f805f..633e030 100644 --- a/sensors/beacon/BeaconProbabilityFree.h +++ b/sensors/beacon/BeaconProbabilityFree.h @@ -14,7 +14,7 @@ #include "BeaconProbability.h" #include "BeaconMeasurements.h" #include "model/BeaconModel.h" -#include "../../math/Distributions.h" +#include "../../math/distribution/Normal.h" #include "../../data/Timestamp.h" #include "../../floorplan/v2/Floorplan.h" @@ -29,75 +29,75 @@ class BeaconObserverFree : public BeaconProbability { private: - const float sigma = 8.0f; - const float sigmaPerSecond = 3.0f; + const float sigma = 10.0f; + const float sigmaPerSecond = 3.0f; - /** the RSSI prediction model */ + /** the RSSI prediction model */ BeaconModel& model; - /** the map's floorplan */ - Floorplan::IndoorMap* map; + /** the map's floorplan */ + Floorplan::IndoorMap* map; public: /** ctor */ BeaconObserverFree(const float sigma, BeaconModel& model) : sigma(sigma), model(model) { - } + } /** provides the probability for a specific point in space */ double getProbability(const Point3& pos_m, const Timestamp curTime, const BeaconMeasurements& obs) const { - double prob = 1.0; + double prob = 1.0; int numMatchingBeacons = 0; - // process each measured AP + // process each measured AP for (const BeaconMeasurement& entry : obs.entries) { - // sanity check - Assert::isFalse(entry.getTimestamp().isZero(), "wifi measurement without timestamp. coding error?"); + // sanity check + Assert::isFalse(entry.getTimestamp().isZero(), "wifi measurement without timestamp. coding error?"); // updating the beacons sended txp if available if(entry.getBeacon().getTXP() != 0.0f){ //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); - // NaN? -> AP not known to the model -> skip + // NaN? -> AP not known to the model -> skip if (modelRSSI != modelRSSI) { continue; } - // the scan's RSSI - const float scanRSSI = entry.getRSSI(); + // the scan's RSSI + const float scanRSSI = entry.getRSSI(); - // the measurement's age - const Timestamp age = curTime - entry.getTimestamp(); + // the measurement's age + const Timestamp age = curTime - entry.getTimestamp(); - Assert::isTrue(age.ms() >= 0, "found a negative wifi measurement age. this does not make sense"); - Assert::isTrue(age.ms() <= 40000, "found a 40 second old wifi measurement. maybe there is a coding error?"); + Assert::isTrue(age.ms() >= 0, "found a negative wifi measurement age. this does not make sense"); + Assert::isTrue(age.ms() <= 40000, "found a 40 second old wifi measurement. maybe there is a coding error?"); - // sigma grows with measurement age - const float sigma = this->sigma + this->sigmaPerSecond * age.sec(); + // sigma grows with measurement age + const float sigma = this->sigma + this->sigmaPerSecond * age.sec(); - // update probability - prob *= Distribution::Normal::getProbability(modelRSSI, sigma, scanRSSI); - //prob *= Distribution::Region::getProbability(modelRSSI, sigma, scanRSSI); + // update probability + prob *= Distribution::Normal::getProbability(modelRSSI, sigma, scanRSSI); + //prob *= Distribution::Region::getProbability(modelRSSI, sigma, scanRSSI); ++numMatchingBeacons; - } + } - // sanity check + // sanity check //Assert::isTrue(numMatchingBeacons > 0, "not a single measured Beacon was matched against known ones. coding error? model error?"); - return prob; + return prob; - } + } }; diff --git a/sensors/beacon/model/BeaconModel.h b/sensors/beacon/model/BeaconModel.h index c20c616..e705c04 100644 --- a/sensors/beacon/model/BeaconModel.h +++ b/sensors/beacon/model/BeaconModel.h @@ -15,6 +15,8 @@ #include "../BeaconMeasurement.h" #include "../../../geo/Point3.h" +#include "../../../data/XMLserialize.h" + #include /** @@ -23,14 +25,17 @@ * 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 BeaconModel { +class BeaconModel : public XMLserialize { public: // /** get the given access-point's RSSI at the provided location */ // virtual float getRSSI(const LocatedAccessPoint& ap, const Point3 p) = 0; - /** get a list of all APs known to the model */ + /** dtor */ + virtual ~BeaconModel() {;} + + /** get a list of all APs known to the model */ virtual std::vector getAllBeacons() const = 0; /** @@ -43,13 +48,13 @@ public: 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. - * + * * 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; }; diff --git a/sensors/beacon/model/BeaconModelLogDist.h b/sensors/beacon/model/BeaconModelLogDist.h index 6f09749..25f090b 100644 --- a/sensors/beacon/model/BeaconModelLogDist.h +++ b/sensors/beacon/model/BeaconModelLogDist.h @@ -24,17 +24,17 @@ class BeaconModelLogDist : public BeaconModel { public: /** parameters describing one beacons to the model */ - struct APEntry { + struct APEntry { - Point3 position_m; // the AP's position (in meter) - float txp; // sending power (-40) - float exp; // path-loss-exponent (~2.0 - 4.0) + Point3 position_m; // the AP's position (in meter) + float txp; // sending power (-40) + float exp; // path-loss-exponent (~2.0 - 4.0) - /** ctor */ - APEntry(const Point3 position_m, const float txp, const float exp) : - position_m(position_m), txp(txp), exp(exp) {;} + /** ctor */ + APEntry(const Point3 position_m, const float txp, const float exp) : + position_m(position_m), txp(txp), exp(exp) {;} - }; + }; private: @@ -43,29 +43,29 @@ private: public: - /** ctor */ + /** ctor */ BeaconModelLogDist() { - ; - } + ; + } /** get a list of all beacons known to the model */ std::vector getAllBeacons() const { std::vector aps; 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 */ void addAP(const MACAddress& beacon, const APEntry& params) { - // sanity check - Assert::isBetween(params.txp, -50.0f, -30.0f, "TXP out of bounds [-90:-30]"); - Assert::isBetween(params.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]"); + // sanity check + Assert::isBetween(params.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]"); + Assert::isBetween(params.exp, 1.0f, 5.0f, "EXP out of bounds [1:5]"); - // add + // add beacons.insert( std::pair(beacon, params) ); - } + } void updateBeacon(const BeaconMeasurement beacon) override{ // try to get the corresponding parameters @@ -79,23 +79,23 @@ public: 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); - // AP unknown? -> NAN + // AP unknown? -> NAN if (it == beacons.end()) {return NAN;} // the beacons' parameters - const APEntry& params = it->second; + const APEntry& params = it->second; - // free-space (line-of-sight) RSSI - const float distance_m = position_m.getDistance(params.position_m); - const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); + // free-space (line-of-sight) RSSI + const float distance_m = position_m.getDistance(params.position_m); + const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); - // done - return rssiLOS; + // done + return rssiLOS; - } + } }; diff --git a/sensors/beacon/model/BeaconModelLogDistCeiling.h b/sensors/beacon/model/BeaconModelLogDistCeiling.h index f8b9e6e..a90e851 100644 --- a/sensors/beacon/model/BeaconModelLogDistCeiling.h +++ b/sensors/beacon/model/BeaconModelLogDistCeiling.h @@ -12,12 +12,16 @@ #define BEACONMODELLOGDISTCEILING_H #include "../../../floorplan/v2/Floorplan.h" +#include "../../../floorplan/v2/FloorplanCeilings.h" #include "../../../Assertions.h" #include "BeaconModel.h" #include "../../radio/model/LogDistanceModel.h" #include "../BeaconMeasurement.h" +#include "../../../data/XMLserialize.h" +#include "../../../misc/Debug.h" + #include /** @@ -26,63 +30,61 @@ */ class BeaconModelLogDistCeiling : public BeaconModel { + static constexpr const char* name = "BLEModelLDC"; + public: /** parameters describing one beacon to the model */ - struct APEntry { + struct APEntry { Point3 position_m; // the beacon's position (in meter) - float txp; // sending power (-40) - float exp; // path-loss-exponent (~2.0 - 4.0) - float waf; // attenuation per ceiling/floor (~-8.0) + float txp; // sending power (-40) + float exp; // path-loss-exponent (~2.0 - 4.0) + float waf; // attenuation per ceiling/floor (~-8.0) - /** ctor */ - APEntry(const Point3 position_m, const float txp, const float exp, const float waf) : - position_m(position_m), txp(txp), exp(exp), waf(waf) {;} + /** ctor */ + APEntry(const Point3 position_m, const float txp, const float exp, const float waf) : + position_m(position_m), txp(txp), exp(exp), waf(waf) {;} - }; + }; private: /** map of all beacons (and their parameters) known to the model */ std::unordered_map beacons; - /** position (height) of all ceilings (in meter) */ - std::vector ceilingsAtHeight_m; + /** position (height) of all ceilings (in meter) */ + //std::vector ceilingsAtHeight_m; + Floorplan::Ceilings ceilings; public: - /** ctor with floorplan (needed for ceiling position) */ - BeaconModelLogDistCeiling(const Floorplan::IndoorMap* map) { + /** ctor with floorplan (needed for ceiling position) */ + BeaconModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(map){ - // sanity checks - Assert::isTrue(map->floors.size() >= 1, "map has no floors?!"); + // 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 a list of all beacons known to the model */ std::vector getAllBeacons() const { std::vector aps; 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 */ 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) { APEntry ape(beacon->getPos(floor), txp, exp, waf); addBeacon(MACAddress(beacon->mac), ape); - } - } + } + } - } + } /** 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) { @@ -99,17 +101,17 @@ public: /** make the given beacon (and its parameters) known to the model */ void addBeacon(const MACAddress& beacon, const APEntry& params) { - // sanity check - 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.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]"); + // sanity check + Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]"); + Assert::isBetween(params.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]"); + Assert::isBetween(params.exp, 1.0f, 5.0f, "EXP out of bounds [1:5]"); //Assert::equal(beacons.find(beacon), beacons.end(), "AccessPoint already present!"); - // add + // add beacons.insert( std::pair(beacon, params) ); - } + } void updateBeacon(const BeaconMeasurement beacon) override { // try to get the corresponding parameters @@ -123,94 +125,155 @@ public: it->second.txp = beacon.getBeacon().getTXP(); } - /** remove all added APs */ - void clear() { + /** remove all added APs */ + void clear() { beacons.clear(); - } + } 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); // beacon unknown? -> NAN if (it == beacons.end()) {return NAN;} - // the access-points' parameters - const APEntry& params = it->second; + // the access-points' parameters + const APEntry& params = it->second; - // free-space (line-of-sight) RSSI - const float distance_m = position_m.getDistance(params.position_m); + // free-space (line-of-sight) RSSI + const float distance_m = position_m.getDistance(params.position_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 - const float wafLoss = params.waf * numCeilingsBetween(position_m, params.position_m); + // WAF loss (params.waf is a negative value!) -> WAF loss is also a negative value + const float wafLoss = params.waf * ceilings.numCeilingsBetweenFloat(position_m, params.position_m); - // combine - return rssiLOS + wafLoss; + // combine + 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, numCeilingsFloat); +// FRIEND_TEST(LogDistanceCeilingModelBeacon, numCeilings); +// FRIEND_TEST(LogDistanceCeilingModelBeacon, numCeilingsFloat); - /** get the number of ceilings between z1 and z2 */ - float numCeilingsBetweenFloat(const Point3 pos1, const Point3 pos2) const { +// /** 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); +// const float zMin = std::min(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) { - if (zMin < z && zMax > z) { - const float dmax = zMax - z; - cnt += (dmax > 1) ? (1) : (dmax); - } - } +// for (const float z : ceilingsAtHeight_m) { +// if (zMin < z && zMax > z) { +// const float dmax = zMax - z; +// cnt += (dmax > 1) ? (1) : (dmax); +// } +// } - return cnt; +// return cnt; - } +// } - /** get the number of ceilings between z1 and z2 */ - int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const { +// /** get the number of ceilings between z1 and z2 */ +// int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const { - int cnt = 0; - const float zMin = std::min(pos1.z, pos2.z); - const float zMax = std::max(pos1.z, pos2.z); +// int cnt = 0; +// const float zMin = std::min(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 numFar = 0; - for (const float z : ceilingsAtHeight_m) { - const float diff = std::min( std::abs(z-zMin), std::abs(z-zMax) ); - if (diff < 0.1) {++numNear;} else {++numFar;} - } - if ((numNear + numFar) > 150000) { - Assert::isTrue(numNear < numFar*0.1, - "many requests to the WiFiModel address nodes (very) near to a ground! \ - due to rounding issues, determining the number of floors between AP and point-in-question is NOT possible! \ - expect very wrong outputs! \ - consider adding the person's height to the questioned positions: p += Point3(0,0,1.3) " - ); - } -#endif +// static int numNear = 0; +// static int numFar = 0; +// for (const float z : ceilingsAtHeight_m) { +// const float diff = std::min( std::abs(z-zMin), std::abs(z-zMax) ); +// if (diff < 0.1) {++numNear;} else {++numFar;} +// } +// if ((numNear + numFar) > 150000) { +// Assert::isTrue(numNear < numFar*0.1, +// "many requests to the WiFiModel address nodes (very) near to a ground! \ +// due to rounding issues, determining the number of floors between AP and point-in-question is NOT possible! \ +// expect very wrong outputs! \ +// consider adding the person's height to the questioned positions: p += Point3(0,0,1.3) " +// ); +// } +//#endif - for (const float z : ceilingsAtHeight_m) { - if (zMin < z && zMax > z) {++cnt;} - } +// for (const float z : ceilingsAtHeight_m) { +// if (zMin < z && zMax > z) {++cnt;} +// } - return cnt; +// return cnt; - } +// } }; diff --git a/sensors/beacon/setup/BeaconFingerprint.h b/sensors/beacon/setup/BeaconFingerprint.h index 22188d0..b622e60 100644 --- a/sensors/beacon/setup/BeaconFingerprint.h +++ b/sensors/beacon/setup/BeaconFingerprint.h @@ -7,12 +7,12 @@ #include /** - * denotes a wifi fingerprint: - * known position and 1-n measurements [wifi-scan on all channels] conducted at this position + * denotes a beacon fingerprint: + * 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! */ -struct WiFiFingerprint { +struct BeaconFingerprint { /** real-world-position that was measured */ @@ -22,13 +22,13 @@ struct WiFiFingerprint { BeaconMeasurements measurements; /** ctor */ - WiFiFingerprint() {;} + BeaconFingerprint() {;} /** ctor */ - WiFiFingerprint(const Point3 pos_m) : pos_m(pos_m) {;} + BeaconFingerprint(const Point3 pos_m) : pos_m(pos_m) {;} /** 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 */ diff --git a/sensors/radio/model/WiFiModelLogDist.h b/sensors/radio/model/WiFiModelLogDist.h index 38a8a71..88ddd3e 100644 --- a/sensors/radio/model/WiFiModelLogDist.h +++ b/sensors/radio/model/WiFiModelLogDist.h @@ -23,88 +23,88 @@ class WiFiModelLogDist : public WiFiModel { public: - /** parameters describing one AP to the model */ - struct APEntry { + /** parameters describing one AP to the model */ + struct APEntry { - Point3 position_m; // the AP's position (in meter) - float txp; // sending power (-40) - float exp; // path-loss-exponent (~2.0 - 4.0) + Point3 position_m; // the AP's position (in meter) + float txp; // sending power (-40) + float exp; // path-loss-exponent (~2.0 - 4.0) - /** ctor */ - APEntry(const Point3 position_m, const float txp, const float exp) : - position_m(position_m), txp(txp), exp(exp) {;} + /** ctor */ + APEntry(const Point3 position_m, const float txp, const float exp) : + position_m(position_m), txp(txp), exp(exp) {;} - }; + }; private: - /** map of all APs (and their parameters) known to the model */ - std::unordered_map accessPoints; + /** map of all APs (and their parameters) known to the model */ + std::unordered_map accessPoints; public: - /** ctor */ - WiFiModelLogDist() { - ; - } + /** ctor */ + WiFiModelLogDist() { + ; + } - /** get a list of all APs known to the model */ - std::vector getAllAPs() const { - std::vector aps; - for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));} - return aps; - } + /** get a list of all APs known to the model */ + std::vector getAllAPs() const { + std::vector aps; + for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));} + return aps; + } - /** make the given AP (and its parameters) known to the model */ - void addAP(const MACAddress& accessPoint, const APEntry& params) { + /** make the given AP (and its parameters) known to the model */ + void addAP(const MACAddress& accessPoint, const APEntry& params) { - // sanity check - Assert::isBetween(params.txp, -50.0f, -30.0f, "TXP out of bounds [-90:-30]"); - Assert::isBetween(params.exp, 1.0f, 4.0f, "EXP out of bounds [1:4]"); + // sanity check + Assert::isBetween(params.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]"); + Assert::isBetween(params.exp, 1.0f, 5.0f, "EXP out of bounds [1:5]"); - // add - accessPoints.insert( std::pair(accessPoint, params) ); + // add + accessPoints.insert( std::pair(accessPoint, params) ); - } + } - /** does the model know the given AP? */ - bool knowsAP(const MACAddress& accessPoint) { + /** does the model know the given AP? */ + bool knowsAP(const MACAddress& accessPoint) { - // try to get the corresponding parameters - const auto it = accessPoints.find(accessPoint); + // try to get the corresponding parameters + const auto it = accessPoints.find(accessPoint); - // AP known? - return (it != accessPoints.end()); + // AP known? + 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 - const auto it = accessPoints.find(accessPoint); + // try to get the corresponding parameters + const auto it = accessPoints.find(accessPoint); - // AP unknown? -> NAN - if (it == accessPoints.end()) {return NAN;} + // AP unknown? -> NAN + if (it == accessPoints.end()) {return NAN;} - // the access-points' parameters - const APEntry& params = it->second; + // the access-points' parameters + const APEntry& params = it->second; - // free-space (line-of-sight) RSSI - const float distance_m = position_m.getDistance(params.position_m); - const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); + // free-space (line-of-sight) RSSI + const float distance_m = position_m.getDistance(params.position_m); + const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); - // done - return rssiLOS; + // done + return rssiLOS; - } + } - void readFromXML(XMLDoc* doc, XMLElem* src) override { - throw Exception("not yet implemented"); - } + void readFromXML(XMLDoc* doc, XMLElem* src) override { + throw Exception("not yet implemented"); + } - void writeToXML(XMLDoc* doc, XMLElem* dst) override { - throw Exception("not yet implemented"); - } + void writeToXML(XMLDoc* doc, XMLElem* dst) override { + throw Exception("not yet implemented"); + } }; diff --git a/sensors/radio/model/WiFiModelLogDistCeiling.h b/sensors/radio/model/WiFiModelLogDistCeiling.h index d9b1742..34c8016 100644 --- a/sensors/radio/model/WiFiModelLogDistCeiling.h +++ b/sensors/radio/model/WiFiModelLogDistCeiling.h @@ -28,217 +28,217 @@ */ class WiFiModelLogDistCeiling : public WiFiModel { - static constexpr const char* name = "WifiModelLDC"; + static constexpr const char* name = "WifiModelLDC"; public: - /** parameters describing one AP to the model */ - struct APEntry { + /** parameters describing one AP to the model */ + struct APEntry { - Point3 position_m; // the AP's position (in meter) - float txp; // sending power (-40) - float exp; // path-loss-exponent (~2.0 - 4.0) - float waf; // attenuation per ceiling/floor (~-8.0) + Point3 position_m; // the AP's position (in meter) + float txp; // sending power (-40) + float exp; // path-loss-exponent (~2.0 - 4.0) + float waf; // attenuation per ceiling/floor (~-8.0) - /** ctor */ - APEntry(const Point3 position_m, const float txp, const float exp, const float waf) : - position_m(position_m), txp(txp), exp(exp), waf(waf) {;} + /** ctor */ + APEntry(const Point3 position_m, const float txp, const float exp, const float waf) : + position_m(position_m), txp(txp), exp(exp), waf(waf) {;} - }; + }; private: - /** map of all APs (and their parameters) known to the model */ - std::unordered_map accessPoints; + /** map of all APs (and their parameters) known to the model */ + std::unordered_map accessPoints; // /** position (height) of all ceilings (in meter) */ // std::vector ceilingsAtHeight_m; - Floorplan::Ceilings ceilings; + Floorplan::Ceilings ceilings; public: - /** ctor with floorplan (needed for ceiling position) */ - WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(map) { + /** ctor with floorplan (needed for ceiling position) */ + WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(map) { - // sanity checks - Assert::isTrue(map->floors.size() >= 1, "map has no floors?!"); + // sanity checks + Assert::isTrue(map->floors.size() >= 1, "map has no floors?!"); - } + } - /** 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 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 getAllAPs() const { - std::vector aps; - for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));} - return aps; - } + /** get a list of all APs known to the model */ + std::vector getAllAPs() const { + std::vector aps; + for (const auto it : accessPoints) {aps.push_back(AccessPoint(it.first));} + return aps; + } - /** load AP information from the floorplan. use the given fixed TXP/EXP/WAF for all APs */ - void loadAPs(const Floorplan::IndoorMap* map, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f, const bool assertSafe = true) { + /** 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) { - 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, assertSafe); - } - } + 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, assertSafe); + } + } - } + } - /** - * 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) { + /** + * 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 added! given: " + ap->mac + " -> after VAP: " + mac.asString()); - addAP(MACAddress(mac), ape, assertSafe); - } - } + 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 added! given: " + ap->mac + " -> after VAP: " + mac.asString()); + addAP(MACAddress(mac), ape, assertSafe); + } + } - } + } - /** - * 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) { + /** + * 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 - 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]"); - } + // sanity check + if (assertSafe) { + Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]"); + Assert::isBetween(params.txp, -65.0f, -30.0f, "TXP out of bounds [-65:-30]"); + 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 - accessPoints.insert( std::pair(accessPoint, params) ); + // add + accessPoints.insert( std::pair(accessPoint, 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 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); - } + /** + * 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(); - } + /** remove all added APs */ + void clear() { + accessPoints.clear(); + } - /** does the model know the given AP? */ - bool knowsAP(const MACAddress& accessPoint) { + /** does the model know the given AP? */ + bool knowsAP(const MACAddress& accessPoint) { - // try to get the corresponding parameters - const auto it = accessPoints.find(accessPoint); + // try to get the corresponding parameters + const auto it = accessPoints.find(accessPoint); - // AP known? - return (it != accessPoints.end()); + // AP known? + 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 - const auto it = accessPoints.find(accessPoint); + // try to get the corresponding parameters + const auto it = accessPoints.find(accessPoint); - // AP unknown? -> NAN - if (it == accessPoints.end()) {return NAN;} + // AP unknown? -> NAN + if (it == accessPoints.end()) {return NAN;} - // the access-points' parameters - const APEntry& params = it->second; + // the access-points' parameters + const APEntry& params = it->second; - // free-space (line-of-sight) RSSI - const float distance_m = position_m.getDistance(params.position_m); - const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m); + // free-space (line-of-sight) RSSI + const float distance_m = position_m.getDistance(params.position_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 - //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); + // 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.numCeilingsBetweenFloat(position_m, params.position_m); + //const float wafLoss = params.waf * ceilings.numCeilingsBetweenLinearInt(position_m, params.position_m); - // combine - const float res = rssiLOS + wafLoss; + // combine + const float res = rssiLOS + wafLoss; - // sanity check - Assert::isNotNaN(res, "detected NaN within WiFiModelLogDistCeiling::getRSSI()"); + // sanity check + Assert::isNotNaN(res, "detected NaN within WiFiModelLogDistCeiling::getRSSI()"); - // ok - return res; + // ok + return res; - } + } - void writeToXML(XMLDoc* doc, XMLElem* dst) override { + void writeToXML(XMLDoc* doc, XMLElem* dst) override { - // set my type - dst->SetAttribute("type", "WiFiModelLogDistCeiling"); + // set my type + dst->SetAttribute("type", "WiFiModelLogDistCeiling"); - 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); - } + 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); + } - for (const float atHeight_m : ceilings.getCeilings()) { - XMLElem* xceil = doc->NewElement("ceiling"); - xceil->SetAttribute("atHeight", atHeight_m); - dst->InsertEndChild(xceil); - } + 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 { + void readFromXML(XMLDoc* doc, XMLElem* src) override { - // check type - if (std::string("WiFiModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");} + // check type + if (std::string("WiFiModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");} - accessPoints.clear(); - ceilings.clear(); + accessPoints.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") - ); - accessPoints.insert( std::make_pair(mac, ape) ); - } + 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") + ); + accessPoints.insert( std::make_pair(mac, ape) ); + } - XML_FOREACH_ELEM_NAMED("ceiling", xceil, src) { - const float atHeight_m = xceil->FloatAttribute("atHeight"); - ceilings.addCeiling(atHeight_m); - } + 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(accessPoints.size()) + " APs"); + Log::add(name, "loaded " + std::to_string(accessPoints.size()) + " APs"); - } + } }; diff --git a/sensors/radio/setup/WiFiOptimizer.h b/sensors/radio/setup/WiFiOptimizer.h index 0ccddc5..8e7981e 100644 --- a/sensors/radio/setup/WiFiOptimizer.h +++ b/sensors/radio/setup/WiFiOptimizer.h @@ -11,6 +11,7 @@ #ifndef WIFIOPTIMIZER_H #define WIFIOPTIMIZER_H +#include "../../beacon/setup/BeaconFingerprint.h" #include "WiFiFingerprints.h" #include "WiFiOptimizerStructs.h" #include "../VAPGrouper.h" @@ -19,65 +20,79 @@ namespace WiFiOptimizer { - enum class Mode { - FAST, - MEDIUM, - QUALITY, - }; + enum class Mode { + FAST, + MEDIUM, + QUALITY, + }; - /** base-class for all WiFiOptimizers */ - class Base { + /** base-class for all WiFiOptimizers */ + class Base { - protected: + protected: - /** each MAC-Adress has several position->rssi entries */ - std::unordered_map> apMap; + /** each MAC-Adress has several position->rssi entries */ + std::unordered_map> apMap; - /** how to handle virtual access points [group, not-group, ..] */ - const VAPGrouper vg; + /** how to handle virtual access points [group, not-group, ..] */ + const VAPGrouper vg; - public: + public: - /** ctor */ - Base(const VAPGrouper vg) : vg(vg) {;} + /** ctor */ + Base(const VAPGrouper vg) : vg(vg) {;} - /** get a list of all to-be-optimized access-points (given by their mac-address) */ - virtual std::vector getAllMACs() const { - std::vector 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) */ + virtual std::vector getAllMACs() const { + std::vector res; + for (const auto& it : apMap) {res.push_back(it.first);} + return res; + } - /** get all [VAPGrouped, Averaged] fingerprints for the given mac */ - virtual const std::vector& getFingerprintsFor(const MACAddress& mac) { - const auto& it = apMap.find(mac); - if (it == apMap.end()) {throw Exception("mac not found: " + mac.asString());} - return it->second; - } + /** get all [VAPGrouped, Averaged] fingerprints for the given mac */ + virtual const std::vector& getFingerprintsFor(const MACAddress& mac) { + const auto& it = apMap.find(mac); + if (it == apMap.end()) {throw Exception("mac not found: " + mac.asString());} + return it->second; + } - /** add a new fingerprint to the optimizer as data-source */ - virtual void addFingerprint(const WiFiFingerprint& fp) { + /** add a new fingerprint to the optimizer as data-source */ + virtual void addFingerprint(const WiFiFingerprint& fp) { - // group the fingerprint's measurements by VAP (if configured) - const WiFiMeasurements measurements = vg.group(fp.measurements); + // 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); - } + // 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); + } - } + } - /** add new fingerprints to the optimizer as data-source */ - virtual void addFingerprints(const WiFiFingerprints& fps) { - for (const WiFiFingerprint& fp : fps.getFingerprints()) { - addFingerprint(fp); - } - } + /** add a new bluetooth fingerprint to the optimizer as data-source */ + virtual void addFingerprint(const BeaconFingerprint& 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); + } + } + + }; } diff --git a/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h b/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h index c4aa812..716b93f 100644 --- a/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h +++ b/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h @@ -37,355 +37,355 @@ namespace WiFiOptimizer { - /** - * optimize access-point parameters, - * given several fingerprints using the log-dist-ceiling model - */ - struct LogDistCeiling : public Base { + /** + * optimize access-point parameters, + * given several fingerprints using the log-dist-ceiling model + */ + struct LogDistCeiling : public Base { - public: + public: - /** - * resulting optimization stats for one AP - */ - struct Stats { + /** + * resulting optimization stats for one AP + */ + struct Stats { - /** average model<->scan error after optimzing */ - float error_db; + /** average model<->scan error after optimzing */ + float error_db; - /** number of fingerprints [= locations] that were used for optimzing */ - int usedFingerprins; + /** number of fingerprints [= locations] that were used for optimzing */ + int usedFingerprins; - /** resulting model<->scan error after optimzing for each individual fingerprints [= location] */ - std::vector errors; + /** resulting model<->scan error after optimzing for each individual fingerprints [= location] */ + std::vector 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 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); - } + /** 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); + } - }; + }; - using APFilter = std::function; + using APFilter = std::function; - static inline bool NONE(const Stats& stats, const MACAddress& mac) { - (void) stats; (void) mac; - return false; - } + static inline bool NONE(const Stats& stats, const MACAddress& mac) { + (void) stats; (void) mac; + return false; + } - static inline bool MIN_2_FPS(const Stats& stats, const MACAddress& mac) { - (void) mac; - return stats.usedFingerprins < 2; - } + static inline bool MIN_2_FPS(const Stats& stats, const MACAddress& mac) { + (void) mac; + return stats.usedFingerprins < 2; + } - static inline bool MIN_5_FPS(const Stats& stats, const MACAddress& mac) { - (void) mac; - return stats.usedFingerprins < 5; - } + static inline bool MIN_5_FPS(const Stats& stats, const MACAddress& mac) { + (void) mac; + return stats.usedFingerprins < 5; + } - static inline bool MIN_10_FPS(const Stats& stats, const MACAddress& mac) { - (void) mac; - return stats.usedFingerprins < 10; - } + static inline bool MIN_10_FPS(const Stats& stats, const MACAddress& mac) { + (void) mac; + return stats.usedFingerprins < 10; + } - /** parameters for one AP when using the LogDistCeiling model */ - struct APParams { + /** parameters for one AP when using the LogDistCeiling model */ + struct APParams { - float x; - float y; - float z; + float x; + float y; + float z; - float txp; - float exp; - float waf; + float txp; + float exp; + float waf; - /** ctor */ - APParams() {;} + /** 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) {;} + /** 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) {;} - Point3 getPos() const {return Point3(x,y,z);} + Point3 getPos() const {return Point3(x,y,z);} - std::string asString() const { - std::stringstream ss; - ss << "Pos:" << getPos().asString() << " TXP:" << txp << " EXP:" << exp << " WAF:" << waf; - return ss.str(); - } + 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); - } + /** we add some constraints to the parameter range */ + bool outOfRange() const { + return (waf > 0) || + (txp < -65) || + (txp > -45) || + (exp > 5) || + (exp < 1); + } - }; + }; - /** add MAC-info to params */ - struct APParamsMAC { - MACAddress mac; - APParams params; - APParamsMAC(const MACAddress mac, const APParams& params) : mac(mac), params(params) {;} - }; + /** add MAC-info to params */ + struct APParamsMAC { + MACAddress mac; + APParams params; + APParamsMAC(const MACAddress mac, const APParams& params) : mac(mac), params(params) {;} + }; - struct OptResultStats { - K::Statistics avgApErrors; // contains one average-error per optimized AP - K::Statistics singleFPErrors; // contains one error for each fingerprint<->ap SIGNED - K::Statistics singleFPErrorsAbs; // contains one error for each fingerprint<->ap ABSOLUTE + struct OptResultStats { + K::Statistics avgApErrors; // contains one average-error per optimized AP + K::Statistics singleFPErrors; // contains one error for each fingerprint<->ap SIGNED + K::Statistics singleFPErrorsAbs; // contains one error for each fingerprint<->ap ABSOLUTE - }; + }; - class APParamsList { + class APParamsList { - std::vector lst; + std::vector lst; - public: + public: - /** ctor */ - APParamsList(const std::vector& lst) : lst(lst) { + /** ctor */ + APParamsList(const std::vector& lst) : lst(lst) { - } + } - /** get the list */ - const std::vector& get() const { - return lst; - } + /** get the list */ + const std::vector& 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; - } + /** 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; + } - }; + }; - 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 */ - 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 Mode mode = Mode::MEDIUM) : 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); - } + /** ctor */ + LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const WiFiFingerprints& fps, const Mode mode = Mode::MEDIUM) : Base(vg), map(map), mode(mode) { + addFingerprints(fps); + } - /** optimize all known APs */ - APParamsList optimizeAll(APFilter filter, OptResultStats* dst = nullptr) const { + /** optimize all known APs */ + APParamsList optimizeAll(APFilter filter, OptResultStats* dst = nullptr) const { - // sanity check - Assert::isFalse(getAllMACs().empty(), "no APs found for optimization! call addFingerprint() first!"); + // sanity check + Assert::isFalse(getAllMACs().empty(), "no APs found for optimization! call addFingerprint() first!"); - K::Statistics avgErrors; - K::Statistics singleErrors; - K::Statistics singleErrorsAbs; + K::Statistics avgErrors; + K::Statistics singleErrors; + K::Statistics singleErrorsAbs; - std::vector res; - for (const MACAddress& mac : getAllMACs()) { + std::vector res; + for (const MACAddress& mac : getAllMACs()) { - // perform optimization, get resulting parameters and optimization stats - Stats stats; - const APParams params = optimize(mac, stats); + // perform optimization, get resulting parameters and optimization stats + Stats stats; + const APParams params = optimize(mac, stats); - // filter based on stats (option to ignore/filter some access-points) - if (!filter(stats, mac)) { - res.push_back(APParamsMAC(mac, params)); - //errSum += stats.error_db; - //++errCnt; - avgErrors.add(stats.error_db); - for (const auto e : stats.errors) { - singleErrors.add(e.getError_db()); - singleErrorsAbs.add(std::abs(e.getError_db())); - } - } else { - Log::add(name, "ignoring opt-result for AP " + mac.asString() + " due to filter"); - //std::cout << "ignored due to filter!" << std::endl; - } + // filter based on stats (option to ignore/filter some access-points) + if (!filter(stats, mac)) { + res.push_back(APParamsMAC(mac, params)); + //errSum += stats.error_db; + //++errCnt; + avgErrors.add(stats.error_db); + for (const auto e : stats.errors) { + singleErrors.add(e.getError_db()); + singleErrorsAbs.add(std::abs(e.getError_db())); + } + } else { + Log::add(name, "ignoring opt-result for AP " + mac.asString() + " due to filter"); + //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"); - Log::add(name, "optimization result: "); - Log::add(name, " - AvgPerAP " + avgErrors.asString()); - Log::add(name, " - Single: " + singleErrors.asString()); - Log::add(name, " - SingleAbs: " + singleErrorsAbs.asString()); + //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"); + Log::add(name, "optimization result: "); + Log::add(name, " - AvgPerAP " + avgErrors.asString()); + Log::add(name, " - Single: " + singleErrors.asString()); + Log::add(name, " - SingleAbs: " + singleErrorsAbs.asString()); - if (dst) { - dst->avgApErrors = avgErrors; - dst->singleFPErrors = singleErrors; - dst->singleFPErrorsAbs = singleErrorsAbs; - } + if (dst) { + dst->avgApErrors = avgErrors; + dst->singleFPErrors = singleErrors; + dst->singleFPErrorsAbs = singleErrorsAbs; + } - // done - return APParamsList(res); + // done + return APParamsList(res); - } + } - /** optimize the given AP */ - APParams optimize(const MACAddress& mac, Stats& res) const { + /** 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); + // starting parameters do not matter for the current optimizer! + 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 - const std::vector& entries = apMap.find(mac)->second; + // get all position->rssi measurements for this AP to compare them with the corresponding model estimations + const std::vector& 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(); + // 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); + // get the map's size + const BBox3 mapBBox = FloorplanHelper::getBBox(map); - using LeOpt = K::NumOptAlgoRangeRandom; - const std::vector 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 - 6, mapBBox.getMax().z + 6), // z - LeOpt::MinMax(-50, -30), // txp - LeOpt::MinMax(1, 4), // exp - LeOpt::MinMax(-15, -0), // waf - }; + using LeOpt = K::NumOptAlgoRangeRandom; + const std::vector 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 - 6, mapBBox.getMax().z + 6), // z + LeOpt::MinMax(-65, -45), // txp + LeOpt::MinMax(1, 5), // exp + LeOpt::MinMax(-15, -0), // waf + }; - LeOpt opt(valRegion); + 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(1500); - opt.setNumIerations(150); - break; - } + 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(1500); + opt.setNumIerations(150); + break; + } - // error function - auto func = [&] (const float* params) { - return getErrorLogDistCeiling(mac, entries, params, nullptr); - }; + // error function + auto func = [&] (const float* params) { + return getErrorLogDistCeiling(mac, entries, params, nullptr); + }; - opt.calculateOptimum(func, (float*) ¶ms); + opt.calculateOptimum(func, (float*) ¶ms); - // using LeOpt = K::NumOptAlgoGenetic; - // 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); + // using LeOpt = K::NumOptAlgoGenetic; + // 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 opt; - // opt.setMaxIterations(100); - // opt.setNumRestarts(10); + // K::NumOptAlgoDownhillSimplex 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(); + 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"); + Log::tock(); + 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& entries, const float* data, Stats* stats = nullptr) const { + float getErrorLogDistCeiling(const MACAddress& mac, const std::vector& entries, const float* data, Stats* stats = nullptr) const { - const APParams* params = (APParams*) data; + const APParams* params = (APParams*) data; - // some sanity checks - if (params->outOfRange()) {return 1e10;} + // some sanity checks + if (params->outOfRange()) {return 1e10;} - // current position guess for the AP; - const Point3 apPos_m = params->getPos(); + // 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)); + // 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; + float err = 0; + int cnt = 0; - // process each measurement - for (const RSSIatPosition& reading : entries) { + // 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); + // 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); + // 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)); - } + // 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; + // 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;} + // 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); + err /= cnt; + err = std::sqrt(err); - if (params->txp < -50) {err += 999999;} - if (params->txp > -35) {err += 999999;} + if (params->txp < -65) {err += 999999;} + if (params->txp > -45) {err += 999999;} - if (params->exp > 3.5) {err += 999999;} - if (params->exp < 1.0) {err += 999999;} + if (params->exp > 5) {err += 999999;} + if (params->exp < 1.0) {err += 999999;} - return err; + return err; - } + } - }; + }; }