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/beacon/BeaconMeasurementGrouper.h
mail@toni-fetzer.de 96c63ac3ec 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
2019-06-10 16:57:02 +02:00

277 lines
8.4 KiB
C++

#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