This repository has been archived on 2020-04-08. You can view files and clone it, but cannot push or open issues or pull requests.
Files
Indoor/sensors/radio/VAPGrouper.h
2018-10-25 11:50:12 +02:00

287 lines
7.6 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* © 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