287 lines
7.6 KiB
C++
287 lines
7.6 KiB
C++
/*
|
||
* © Copyright 2014 – Urheberrechtshinweis
|
||
* Alle Rechte vorbehalten / All Rights Reserved
|
||
*
|
||
* Programmcode ist urheberrechtlich geschuetzt.
|
||
* Das Urheberrecht liegt, soweit nicht ausdruecklich anders gekennzeichnet, bei Frank Ebner.
|
||
* Keine Verwendung ohne explizite Genehmigung.
|
||
* (vgl. § 106 ff UrhG / § 97 UrhG)
|
||
*/
|
||
|
||
#ifndef VAPGROUPER_H
|
||
#define VAPGROUPER_H
|
||
|
||
#include <vector>
|
||
#include <unordered_map>
|
||
#include <cmath>
|
||
|
||
#include "../../math/Stats.h"
|
||
|
||
#include "../../misc/Debug.h"
|
||
|
||
#include "WiFiMeasurements.h"
|
||
|
||
class VAPGrouper {
|
||
|
||
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 = "VAPGrp";
|
||
|
||
/** 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 */
|
||
VAPGrouper(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 */
|
||
WiFiMeasurements group(const WiFiMeasurements& original) const {
|
||
|
||
// first, group all VAPs into a vector [one vector per VAP-group]
|
||
// by using the modified MAC address that all VAPs have in common
|
||
std::unordered_map<MACAddress, std::vector<WiFiMeasurement>> grouped;
|
||
|
||
for (const WiFiMeasurement& m : original.entries) {
|
||
|
||
// the vap-base-mac this entry belongs to
|
||
const MACAddress baseMAC = getBaseMAC(m.getAP().getMAC());
|
||
grouped[baseMAC].push_back(m);
|
||
|
||
}
|
||
|
||
#ifdef WITH_DEBUG_LOG
|
||
std::stringstream vals;
|
||
vals << std::fixed;
|
||
vals.precision(1);
|
||
#endif
|
||
|
||
// to-be-constructed output
|
||
WiFiMeasurements result;
|
||
int skipped = 0;
|
||
|
||
// perform aggregation on each VAP-group
|
||
for (auto it : grouped) {
|
||
|
||
const MACAddress& base = it.first;
|
||
const std::vector<WiFiMeasurement>& vaps = it.second;
|
||
|
||
// remove entries that do not satify the min-occurences metric
|
||
if ((int)vaps.size() < minOccurences) {++skipped; continue;}
|
||
|
||
// group all VAPs into one measurement
|
||
const WiFiMeasurement groupedMeasurement = groupVAPs(base, vaps, rssiAgg, timeAgg);
|
||
|
||
// get corresponding std-dev for debug
|
||
#ifdef WITH_DEBUG_LOG
|
||
const WiFiMeasurement 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 WiFiMeasurement& m) { return m.getRSSI(); }
|
||
};
|
||
struct FieldTS {
|
||
static Timestamp get(const WiFiMeasurement& m) { return m.getTimestamp(); }
|
||
};
|
||
|
||
|
||
|
||
/** combine all of the given VAPs into one entry using the configured aggregation method */
|
||
static WiFiMeasurement groupVAPs(const MACAddress& baseMAC, const std::vector<WiFiMeasurement>& vaps, Aggregation aggRssi, TimeAggregation aggTime) {
|
||
|
||
// the resulting entry is an AP with the base-MAC all of the given VAPs have in common
|
||
const AccessPoint 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 WiFiMeasurement(baseAP, rssi, baseTS);
|
||
|
||
}
|
||
|
||
private:
|
||
|
||
/** get the average signal strength */
|
||
template <typename T, typename Field> static inline T getAVG(const std::vector<WiFiMeasurement>& vaps) {
|
||
|
||
Stats::Average<T> avg;
|
||
for (const WiFiMeasurement& 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<WiFiMeasurement>& vaps) {
|
||
|
||
Stats::Variance<T> var;
|
||
for (const WiFiMeasurement& vap : vaps) {
|
||
var.add(Field::get(vap));
|
||
}
|
||
return var.getStdDev();
|
||
|
||
}
|
||
|
||
/** get the median signal strength */
|
||
static inline float getMedian(const std::vector<WiFiMeasurement>& vaps) {
|
||
|
||
Stats::Median<float> median;
|
||
for (const WiFiMeasurement& 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<WiFiMeasurement>& vaps) {
|
||
|
||
Stats::Maximum<T> max;
|
||
for (const WiFiMeasurement& 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<WiFiMeasurement>& vaps) {
|
||
|
||
Stats::Minimum<T> min;
|
||
for (const WiFiMeasurement& 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 // VAPGROUPER_H
|