From da477866c1c900d8d402b9f21f60692c902865a7 Mon Sep 17 00:00:00 2001 From: frank Date: Wed, 11 Oct 2017 14:00:24 +0200 Subject: [PATCH] worked on wifi-scanner for linux new time-grouping for vap grouper adjusted test-cases minor changes/fixes/improvements --- data/Timestamp.h | 24 +- main.cpp | 72 +++- math/stats/Average.h | 4 +- math/stats/Maximum.h | 3 +- math/stats/Minimum.h | 4 +- sensors/MACAddress.h | 12 + sensors/radio/AccessPoint.h | 7 + sensors/radio/VAPGrouper.h | 87 +++-- sensors/radio/WiFiMeasurement.h | 66 ++-- sensors/radio/scan/WiFiRAW.h | 47 +++ sensors/radio/scan/WiFiScanLinux.h | 442 ++++++++++++++++--------- tests/data/TestTimestamp.cpp | 31 ++ tests/sensors/radio/TestVAPGrouper.cpp | 73 +++- 13 files changed, 649 insertions(+), 223 deletions(-) create mode 100644 sensors/radio/scan/WiFiRAW.h diff --git a/data/Timestamp.h b/data/Timestamp.h index 877bfff..90fcf4b 100644 --- a/data/Timestamp.h +++ b/data/Timestamp.h @@ -21,6 +21,7 @@ public: /** empty ctor */ explicit Timestamp() : _ms(0) {;} + /** get timestamp from the given value which represents milliesconds */ static inline Timestamp fromMS(const int64_t ms) {return Timestamp(ms);} @@ -75,11 +76,14 @@ public: Timestamp operator - (const Timestamp& o) const {return Timestamp(_ms - o._ms);} + Timestamp& operator -= (const Timestamp& o) {_ms += o._ms; return *this;} Timestamp operator + (const Timestamp& o) const {return Timestamp(_ms + o._ms);} + Timestamp& operator += (const Timestamp& o) {_ms += o._ms; return *this;} - Timestamp operator * (const float val) const {return Timestamp(_ms * val);} + template Timestamp operator * (const T val) const {return Timestamp(_ms * val);} + template Timestamp operator / (const T val) const {return Timestamp(_ms / val);} // /** cast to float */ // operator float () const {return sec();} @@ -87,4 +91,22 @@ public: }; +namespace std { + template<> class numeric_limits { + public: + static Timestamp min() { + const int64_t minVal = std::numeric_limits::min(); + return Timestamp::fromMS(minVal); + } + static Timestamp lowest() { + const int64_t minVal = std::numeric_limits::min(); + return Timestamp::fromMS(minVal); + } + static Timestamp max() { + const int64_t maxVal = std::numeric_limits::max(); + return Timestamp::fromMS(maxVal); + } + }; +} + #endif // TIMESTAMP_H diff --git a/main.cpp b/main.cpp index 492f544..e7b4eeb 100755 --- a/main.cpp +++ b/main.cpp @@ -11,10 +11,74 @@ class Test : public GridPoint { #include "tests/Tests.h" #include "sensors/radio/scan/WiFiScanLinux.h" +#include "sensors/radio/VAPGrouper.h" + +#include +#include +#include +#include + void wifi() { - const std::string dev = "wlp0s20u2u1"; + + K::Gnuplot gp; + K::GnuplotPlot plot; + std::vector lines; lines.resize(5); + std::vector points; points.resize(5); + + for (int i = 0; i < points.size(); ++i) { + plot.add(&points[i]); + plot.add(&lines[i]); + points[i].setPointSize(1); + points[i].setPointType(7); + lines[i].getStroke().setWidth(2); + } + points[0].getColor().setHexStr("#ff6666"); lines[0].getStroke().getColor().setHexStr("#ff0000"); + points[1].getColor().setHexStr("#cccc66"); lines[1].getStroke().getColor().setHexStr("#cccc00"); + points[2].getColor().setHexStr("#66cccc"); lines[2].getStroke().getColor().setHexStr("#00cccc"); + points[3].getColor().setHexStr("#6666ff"); lines[3].getStroke().getColor().setHexStr("#0000ff"); + points[4].getColor().setHexStr("#cc66cc"); lines[4].getStroke().getColor().setHexStr("#cc00cc"); + + VAPGrouper vap(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::MEDIAN, VAPGrouper::TimeAggregation::AVERAGE, 1); + + const std::string dev = "wlp0s20u2u4"; WiFiScanLinux scanner(dev); - scanner.scan(); + + Timestamp start = Timestamp::fromUnixTime(); + + for (int i = 0; i < 500; ++i) { + + WiFiMeasurements mes = scanner.scan(); + for (const WiFiMeasurement& m : mes.entries) { + + K::GnuplotPoint2 gp((m.getTimestamp()-start).ms(), m.getRSSI()); + std::string mac = m.getAP().getMAC().asString().substr(0,14); + + if (mac == "5C:CF:7F:C3:F9") {points[0].add(gp);} + if (mac == "18:FE:34:E1:2B") {points[1].add(gp);} + if (mac == "60:01:94:03:16") {points[2].add(gp);} + + } + + WiFiMeasurements mes2 = vap.group(mes); + for (const WiFiMeasurement& m : mes2.entries) { + + K::GnuplotPoint2 gp((m.getTimestamp()-start).ms(), m.getRSSI()); + std::string mac = m.getAP().getMAC().asString().substr(0,14); + + if (mac == "5C:CF:7F:C3:F9") {lines[0].add(gp);} + if (mac == "18:FE:34:E1:2B") {lines[1].add(gp);} + if (mac == "60:01:94:03:16") {lines[2].add(gp);} + + } + + gp.draw(plot); + gp.flush(); + + } + + + + } int main(int argc, char** argv) { @@ -45,7 +109,9 @@ int main(int argc, char** argv) { //::testing::GTEST_FLAG(filter) = "*Matrix4*"; //::testing::GTEST_FLAG(filter) = "*Sphere3*"; - ::testing::GTEST_FLAG(filter) = "Geo_*"; + ::testing::GTEST_FLAG(filter) = "WiFiVAPGrouper*"; + //::testing::GTEST_FLAG(filter) = "Timestamp*"; + //::testing::GTEST_FLAG(filter) = "*RayTrace3*"; //::testing::GTEST_FLAG(filter) = "*BVH*"; diff --git a/math/stats/Average.h b/math/stats/Average.h index 5db5149..62d294a 100644 --- a/math/stats/Average.h +++ b/math/stats/Average.h @@ -15,7 +15,7 @@ namespace Stats { public: /** ctor */ - Average() : cnt(0), sum(0) { + Average() : cnt(0), sum() { ; } @@ -33,7 +33,7 @@ namespace Stats { /** get the current value */ Scalar get() const { Assert::isNot0(cnt, "add() values first!"); - return sum / (Scalar)cnt; + return sum / cnt; } }; diff --git a/math/stats/Maximum.h b/math/stats/Maximum.h index f7235e2..533af3e 100644 --- a/math/stats/Maximum.h +++ b/math/stats/Maximum.h @@ -2,6 +2,7 @@ #define STATS_MAXIMUM_H #include "../../Assertions.h" +#include namespace Stats { @@ -9,7 +10,7 @@ namespace Stats { private: - const Scalar START = -99999999; + const Scalar START = std::numeric_limits::lowest(); Scalar curMax; public: diff --git a/math/stats/Minimum.h b/math/stats/Minimum.h index 4567586..b29e426 100644 --- a/math/stats/Minimum.h +++ b/math/stats/Minimum.h @@ -1,13 +1,15 @@ #ifndef STATS_MINIMUM_H #define STATS_MINIMUM_H +#include + namespace Stats { template class Minimum { private: - const Scalar START = +999999999; + const Scalar START = std::numeric_limits::max(); Scalar curMin; public: diff --git a/sensors/MACAddress.h b/sensors/MACAddress.h index 7443aed..35ec1bb 100644 --- a/sensors/MACAddress.h +++ b/sensors/MACAddress.h @@ -71,6 +71,18 @@ public: } + uint8_t getField(const int idx) const { + switch(idx) { + case 0: return fields.h0; + case 1: return fields.h1; + case 2: return fields.h2; + case 3: return fields.h3; + case 4: return fields.h4; + case 5: return fields.h5; + } + throw Exception("field-idx out of bounds"); + } + /** convert to lower-case hex-string ("xx:xx:xx:xx:xx:xx") */ std::string asString() const { diff --git a/sensors/radio/AccessPoint.h b/sensors/radio/AccessPoint.h index 839793c..7211f6d 100644 --- a/sensors/radio/AccessPoint.h +++ b/sensors/radio/AccessPoint.h @@ -58,6 +58,13 @@ public: /** OPTIONAL: get the AP's ssid (if any) */ inline const std::string& getSSID() const {return ssid;} + /** as string for debuging */ + std::string asString() const { + std::string res = "AP(" + mac.asString(); + if (!ssid.empty()) {res += ", '" + ssid + "'";} + res += ")"; + return res; + } }; diff --git a/sensors/radio/VAPGrouper.h b/sensors/radio/VAPGrouper.h index d12cd7a..9a24805 100644 --- a/sensors/radio/VAPGrouper.h +++ b/sensors/radio/VAPGrouper.h @@ -40,6 +40,20 @@ public: }; + /** 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"; @@ -50,14 +64,17 @@ private: /** the signal-strength aggregation algorithm to use */ const Aggregation agg; + /** 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 agg, const int minOccurences = 2) : - mode(mode), agg(agg), minOccurences(minOccurences) { + VAPGrouper(const Mode mode, const Aggregation agg, const TimeAggregation timeAgg = TimeAggregation::AVERAGE, const int minOccurences = 2) : + mode(mode), agg(agg), timeAgg(timeAgg), minOccurences(minOccurences) { ; } @@ -127,6 +144,15 @@ public: 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 */ WiFiMeasurement groupVAPs(const MACAddress& baseMAC, const std::vector& vaps) const { @@ -134,17 +160,27 @@ private: const AccessPoint baseAP(baseMAC); // the resultign timestamp - const Timestamp baseTS = vaps.front().getTimestamp(); + //Timestamp baseTS = vaps.front().getTimestamp(); // calculate the rssi using the configured aggregate function float rssi = NAN; switch(agg) { - case Aggregation::AVERAGE: rssi = getAVG(vaps); break; - case Aggregation::MEDIAN: rssi = getMedian(vaps); break; - case Aggregation::MAXIMUM: rssi = getMax(vaps); break; - default: throw Exception("unsupported vap-aggregation method"); + case Aggregation::AVERAGE: rssi = getAVG(vaps); break; + case Aggregation::MEDIAN: rssi = getMedian(vaps); break; + case Aggregation::MAXIMUM: rssi = getMax(vaps); break; + default: throw Exception("unsupported rssi-aggregation method"); } + // calculate the time using the configured aggregate function + Timestamp baseTS; + switch(timeAgg) { + 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 WiFiMeasurement(baseAP, rssi, baseTS); @@ -153,13 +189,18 @@ private: private: /** get the average signal strength */ - inline float getAVG(const std::vector& vaps) const { + template inline T getAVG(const std::vector& vaps) const { - float rssi = 0; +// T field = T(); +// for (const WiFiMeasurement& vap : vaps) { +// field = field + Field::get(vap); +// } +// return field / vaps.size(); + Stats::Average avg; for (const WiFiMeasurement& vap : vaps) { - rssi += vap.getRSSI(); + avg.add(Field::get(vap)); } - return rssi / vaps.size(); + return avg.get(); } @@ -174,19 +215,25 @@ private: } - /** get the maximum signal strength */ - inline float getMax(const std::vector& vaps) const { + /** get the maximum value */ + template inline T getMax(const std::vector& vaps) const { - Stats::Maximum max; + Stats::Maximum max; for (const WiFiMeasurement& vap : vaps) { - max.add(vap.getRSSI()); + max.add(Field::get(vap)); } return max.get(); -// float max = -9999999; -// for (const WiFiMeasurement& vap : vaps) { -// if (vap.getRSSI() > max) {max = vap.getRSSI();} -// } -// return max; + + } + + /** get the minimum value */ + template inline T getMin(const std::vector& vaps) const { + + Stats::Minimum min; + for (const WiFiMeasurement& vap : vaps) { + min.add(Field::get(vap)); + } + return min.get(); } diff --git a/sensors/radio/WiFiMeasurement.h b/sensors/radio/WiFiMeasurement.h index e8d5d81..a7e39fd 100644 --- a/sensors/radio/WiFiMeasurement.h +++ b/sensors/radio/WiFiMeasurement.h @@ -19,7 +19,7 @@ private: /** the measured signal strength */ float rssi; - /** OPTIONAL. frequence the signal was received */ + /** OPTIONAL. frequence the signal was received */ float freq = NAN; /** OPTIONAL. timestamp the measurement was recorded at */ @@ -32,40 +32,56 @@ public: ; } - /** ctor with freq*/ - WiFiMeasurement(const AccessPoint& ap, const float rssi, const float freq) : ap(ap), rssi(rssi), freq(freq) { - ; - } + /** ctor with freq*/ + WiFiMeasurement(const AccessPoint& ap, const float rssi, const float freq) : ap(ap), rssi(rssi), freq(freq) { + ; + } - /** ctor with timestamp */ - WiFiMeasurement(const AccessPoint& ap, const float rssi, const Timestamp ts) : ap(ap), rssi(rssi), ts(ts) { - ; - } + /** ctor with timestamp */ + WiFiMeasurement(const AccessPoint& ap, const float rssi, const Timestamp ts) : ap(ap), rssi(rssi), freq(NAN), ts(ts) { + ; + } - /** ctor with timestamp and freq*/ - WiFiMeasurement(const AccessPoint& ap, const float rssi, const float freq, const Timestamp ts) : ap(ap), rssi(rssi), freq(freq), ts(ts) { - ; - } + /** ctor with timestamp and freq*/ + WiFiMeasurement(const AccessPoint& ap, const float rssi, const float freq, const Timestamp ts) : ap(ap), rssi(rssi), freq(freq), ts(ts) { + ; + } public: - /** get the AP we got the measurement for */ - const AccessPoint& getAP() const {return ap;} + /** get the AP we got the measurement for */ + const AccessPoint& getAP() const {return ap;} - /** get the measurement's signal strength */ - float getRSSI() const {return rssi;} + /** get the measurement's signal strength */ + float getRSSI() const {return rssi;} - /** OPTIONAL: get the measurement's timestamp (if known!) */ - const Timestamp& getTimestamp() const {return ts;} + /** OPTIONAL: get the measurement's timestamp (if known!) */ + const Timestamp& getTimestamp() const {return ts;} - /** OPTIONAL: get the measurement's frequence (if known!) */ - float getFrequency() const {return freq;} + /** timestamp known? */ + bool hasTimestamp() const {return ts == ts;} - /** set another signal strength */ - void setRssi(float value){rssi = value;} + /** OPTIONAL: get the measurement's frequency (if known!) */ + float getFrequency() const {return freq;} + + /** frequency known? */ + bool hasFrequency() const {return freq == freq;} + + /** set another signal strength */ + void setRssi(float value){rssi = value;} + + /** set the timestamp */ + void setTimestamp(const Timestamp& val){ts = val;} + + /** as string for debug printing */ + std::string asString() const { + std::string res = ap.asString(); + if (hasTimestamp()) {res += " @" + std::to_string(ts.ms());} + if (hasFrequency()) {res += " " + std::to_string((int)freq) + " MHz";} + res += " - " + std::to_string(rssi) + " dBm "; + return res; + } - /** set the timestamp */ - void setTimestamp(const Timestamp& val){ts = val;} }; diff --git a/sensors/radio/scan/WiFiRAW.h b/sensors/radio/scan/WiFiRAW.h new file mode 100644 index 0000000..fd9313d --- /dev/null +++ b/sensors/radio/scan/WiFiRAW.h @@ -0,0 +1,47 @@ +#ifndef INDOOR_WIFIRAW_H +#define INDOOR_WIFIRAW_H + +#include + +/** + * parse raw binary wifi packets as defined within the standard + */ +class WiFiRAW { + +public: + + enum Tags { + TAG_SSID = 0x00 + }; + + struct TaggedParams { + std::string ssid; + }; + + /** parsed tagged params within wifi packets: [tag][len][len-bytes][tag][len][len-bytes]... */ + static TaggedParams parseTaggedParams(const uint8_t* data, const size_t len) { + + TaggedParams res; + + int pos = 0; + while(pos < len) { + + const int tag = data[pos+0]; // the tag-ID + const int len = data[pos+1]; // the lenght of the following data + + switch(tag) { + case TAG_SSID: res.ssid = std::string( (const char*) &(data[pos+2]), len); break; + } + + // position at the start of the next tag + pos += 1 + 1 + len; + + } + + return res; + + } + +}; + +#endif // INDOOR_WIFIRAW_H diff --git a/sensors/radio/scan/WiFiScanLinux.h b/sensors/radio/scan/WiFiScanLinux.h index baab78d..0a2de1d 100644 --- a/sensors/radio/scan/WiFiScanLinux.h +++ b/sensors/radio/scan/WiFiScanLinux.h @@ -28,23 +28,28 @@ #include #include #include +#include +#include "WiFiRAW.h" #include "WiFiScan.h" class WiFiScanLinux : public WiFiScan { + struct TMP { + Timestamp tsStart; + WiFiMeasurements res; + }; + struct trigger_results { int done; int aborted; }; - struct handler_args { // For family_handler() and nl_get_multicast_id(). const char *group; int id; }; - static int error_handler(struct sockaddr_nl* nla, struct nlmsgerr* err, void* arg) { (void) nla; // Callback for errors. @@ -54,7 +59,6 @@ class WiFiScanLinux : public WiFiScan { return NL_STOP; } - static int finish_handler(struct nl_msg* msg, void* arg) { (void) msg; // Callback for NL_CB_FINISH. @@ -63,7 +67,6 @@ class WiFiScanLinux : public WiFiScan { return NL_SKIP; } - static int ack_handler(struct nl_msg* msg, void* arg) { (void) msg; // Callback for NL_CB_ACK. @@ -72,7 +75,6 @@ class WiFiScanLinux : public WiFiScan { return NL_STOP; } - static int no_seq_check(struct nl_msg* msg, void* arg) { (void) msg; (void) arg; @@ -80,7 +82,6 @@ class WiFiScanLinux : public WiFiScan { return NL_OK; } - static int family_handler(struct nl_msg* msg, void* arg) { // Callback for NL_CB_VALID within nl_get_multicast_id(). From http://sourcecodebrowser.com/iw/0.9.14/genl_8c.html. struct handler_args* grp = (struct handler_args*) arg; @@ -113,11 +114,16 @@ class WiFiScanLinux : public WiFiScan { int nl_get_multicast_id(struct nl_sock *sock, const char *family, const char *group) { + // From http://sourcecodebrowser.com/iw/0.9.14/genl_8c.html. struct nl_msg *msg; struct nl_cb *cb; int ret, ctrlid; - struct handler_args grp = { .group = group, .id = -ENOENT, }; + + //struct handler_args grp = { .group = group, .id = -ENOENT, }; + struct handler_args grp; + grp.group = group; + grp.id = -ENOENT; msg = nlmsg_alloc(); if (!msg) return -ENOMEM; @@ -157,45 +163,6 @@ class WiFiScanLinux : public WiFiScan { } - static void mac_addr_n2a(char *mac_addr, unsigned char *arg) { - // From http://git.kernel.org/cgit/linux/kernel/git/jberg/iw.git/tree/util.c. - int i, l; - - l = 0; - for (i = 0; i < 6; i++) { - if (i == 0) { - sprintf(mac_addr+l, "%02x", arg[i]); - l += 2; - } else { - sprintf(mac_addr+l, ":%02x", arg[i]); - l += 3; - } - } - } - - - static void print_ssid(unsigned char *ie, int ielen) { - uint8_t len; - uint8_t *data; - int i; - - while (ielen >= 2 && ielen >= ie[1]) { - if (ie[0] == 0 && ie[1] >= 0 && ie[1] <= 32) { - len = ie[1]; - data = ie + 2; - for (i = 0; i < len; i++) { - if (isprint(data[i]) && data[i] != ' ' && data[i] != '\\') printf("%c", data[i]); - else if (data[i] == ' ' && (i != 0 && i != len -1)) printf(" "); - else printf("\\x%.2x", data[i]); - } - break; - } - ielen -= ie[1] + 2; - ie += ie[1] + 2; - } - } - - static int callback_trigger(struct nl_msg *msg, void *arg) { // Called by the kernel when the scan is done or has been aborted. @@ -220,42 +187,30 @@ class WiFiScanLinux : public WiFiScan { } - static int callback_dump(struct nl_msg* msg, void* arg) { - (void) arg; + static int addResult(struct nl_msg* msg, void* arg) { + + TMP* tmp = (TMP*) arg; // Called by the kernel with a dump of the successful scan's data. Called for each SSID. struct genlmsghdr* gnlh = (struct genlmsghdr*) nlmsg_data(nlmsg_hdr(msg)); - char mac_addr[20]; + // char mac_addr[20]; struct nlattr *tb[NL80211_ATTR_MAX + 1]; struct nlattr *bss[NL80211_BSS_MAX + 1]; - // static struct nla_policy bss_policy[NL80211_BSS_MAX + 1] = { - // [NL80211_BSS_TSF] = { .type = NLA_U64 }, - // [NL80211_BSS_FREQUENCY] = { .type = NLA_U32 }, - // [NL80211_BSS_BSSID] = { }, - // [NL80211_BSS_BEACON_INTERVAL] = { .type = NLA_U16 }, - // [NL80211_BSS_CAPABILITY] = { .type = NLA_U16 }, - // [NL80211_BSS_INFORMATION_ELEMENTS] = { }, - // [NL80211_BSS_SIGNAL_MBM] = { .type = NLA_U32 }, - // [NL80211_BSS_SIGNAL_UNSPEC] = { .type = NLA_U8 }, - // [NL80211_BSS_STATUS] = { .type = NLA_U32 }, - // [NL80211_BSS_SEEN_MS_AGO] = { .type = NLA_U32 }, - // [NL80211_BSS_BEACON_IES] = { }, - // }; static struct nla_policy bss_policy[NL80211_BSS_MAX + 1]; memset(&bss_policy, 0, sizeof(bss_policy)); bss_policy[NL80211_BSS_TSF].type = NLA_U64; bss_policy[NL80211_BSS_FREQUENCY].type = NLA_U32; - bss_policy[NL80211_BSS_BSSID];// = { }; +// bss_policy[NL80211_BSS_BSSID] = { }; bss_policy[NL80211_BSS_BEACON_INTERVAL].type = NLA_U16; bss_policy[NL80211_BSS_CAPABILITY].type = NLA_U16; - bss_policy[NL80211_BSS_INFORMATION_ELEMENTS];// = { }; +// bss_policy[NL80211_BSS_INFORMATION_ELEMENTS] = { }; bss_policy[NL80211_BSS_SIGNAL_MBM].type = NLA_U32; bss_policy[NL80211_BSS_SIGNAL_UNSPEC].type = NLA_U8; bss_policy[NL80211_BSS_STATUS].type = NLA_U32; bss_policy[NL80211_BSS_SEEN_MS_AGO].type = NLA_U32; - bss_policy[NL80211_BSS_BEACON_IES];// = { }; +// bss_policy[NL80211_BSS_BEACON_IES] = { }; // Parse and error check. @@ -274,124 +229,212 @@ class WiFiScanLinux : public WiFiScan { const uint64_t seen_ago_ms = nla_get_u32(bss[NL80211_BSS_SEEN_MS_AGO]); const int rssi = (nla_get_s32(bss[NL80211_BSS_SIGNAL_MBM])) / 100.0f; - // Start printing. - mac_addr_n2a(mac_addr, (unsigned char*) nla_data(bss[NL80211_BSS_BSSID])); - printf("%s, ", mac_addr); - printf("%d MHz, ", nla_get_u32(bss[NL80211_BSS_FREQUENCY])); - print_ssid((unsigned char*) nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS])); - printf(" %d ms", seen_ago_ms); - printf(" %d dBm", rssi); - printf("\n"); +// // Start printing. +// mac_addr_n2a(mac_addr, (unsigned char*) nla_data(bss[NL80211_BSS_BSSID])); +// printf("%s, ", mac_addr); +// printf("%d MHz, ", nla_get_u32(bss[NL80211_BSS_FREQUENCY])); +// //print_ssid((unsigned char*) nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS])); +// printf(" %d ms", seen_ago_ms); +// printf(" %d dBm", rssi); +// printf("\n"); + + WiFiRAW::TaggedParams params = WiFiRAW::parseTaggedParams( + (const uint8_t*) nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]), + nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]) + ); + + const Timestamp ts = Timestamp::fromUnixTime() - Timestamp::fromMS(seen_ago_ms); + const int freq_MHz = nla_get_u32(bss[NL80211_BSS_FREQUENCY]); + const uint8_t* macPtr = (const uint8_t*) nla_data(bss[NL80211_BSS_BSSID]); + const uint64_t macLng = ((uint64_t)macPtr[5]<<40)|((uint64_t)macPtr[4]<<32)|((uint64_t)macPtr[3]<<24)|((uint64_t)macPtr[2]<<16)|((uint64_t)macPtr[1]<<8)|((uint64_t)macPtr[0]<<0); + const MACAddress mac(macLng); + const AccessPoint ap(mac, params.ssid); + const WiFiMeasurement mes(ap, rssi, freq_MHz, ts); + + // by default, linux also lists older scan results + // remove them here! + if (ts > tmp->tsStart) { + //std::cout << seen_ago_ms << std::endl; + tmp->res.entries.push_back(mes); + std::cout << mes.asString() << std::endl; + } + + return NL_SKIP; + } - int do_scan_trigger(struct nl_sock *socket, int if_index, int driver_id) { - // Starts the scan and waits for it to finish. Does not return until the scan is done or has been aborted. - struct trigger_results results = { .done = 0, .aborted = 0 }; - struct nl_msg *msg; - struct nl_cb *cb; - struct nl_msg *ssids_to_scan; - int err; - int ret; - int mcid = nl_get_multicast_id(socket, "nl80211", "scan"); - nl_socket_add_membership(socket, mcid); // Without this, callback_trigger() won't be called. +// int do_scan_trigger(struct nl_sock *socket, int if_index, int driver_id) { - // Allocate the messages and callback handler. - msg = nlmsg_alloc(); - if (!msg) { - printf("ERROR: Failed to allocate netlink message for msg.\n"); - return -ENOMEM; - } - ssids_to_scan = nlmsg_alloc(); - if (!ssids_to_scan) { - printf("ERROR: Failed to allocate netlink message for ssids_to_scan.\n"); - nlmsg_free(msg); - return -ENOMEM; - } - cb = nl_cb_alloc(NL_CB_DEFAULT); - if (!cb) { - printf("ERROR: Failed to allocate netlink callbacks.\n"); - nlmsg_free(msg); - nlmsg_free(ssids_to_scan); - return -ENOMEM; - } +// // Starts the scan and waits for it to finish. Does not return until the scan is done or has been aborted. +// struct trigger_results results; +// results.done = 0; +// results.aborted = 0; - // Setup the messages and callback handler. - genlmsg_put(msg, 0, 0, driver_id, 0, 0, NL80211_CMD_TRIGGER_SCAN, 0); // Setup which command to run. - nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); // Add message attribute, which interface to use. - nla_put(ssids_to_scan, 1, 0, ""); // Scan all SSIDs. - nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids_to_scan); // Add message attribute, which SSIDs to scan for. - nlmsg_free(ssids_to_scan); // Copied to `msg` above, no longer need this. - nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, callback_trigger, &results); // Add the callback. - nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); - nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); - nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); - nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); // No sequence checking for multicast messages. +// struct nl_msg *msg; +// struct nl_cb *cb; +// struct nl_msg *ssids_to_scan; +// int err; +// int ret; +// int mcid = nl_get_multicast_id(socket, "nl80211", "scan"); +// nl_socket_add_membership(socket, mcid); // Without this, callback_trigger() won't be called. - // Send NL80211_CMD_TRIGGER_SCAN to start the scan. The kernel may reply with NL80211_CMD_NEW_SCAN_RESULTS on - // success or NL80211_CMD_SCAN_ABORTED if another scan was started by another process. - err = 1; - ret = nl_send_auto(socket, msg); // Send the message. - printf("NL80211_CMD_TRIGGER_SCAN sent %d bytes to the kernel.\n", ret); - printf("Waiting for scan to complete...\n"); - while (err > 0) ret = nl_recvmsgs(socket, cb); // First wait for ack_handler(). This helps with basic errors. - if (err < 0) { - printf("WARNING: err has a value of %d.\n", err); - } - if (ret < 0) { - printf("ERROR: nl_recvmsgs() returned %d (%s).\n", ret, nl_geterror(-ret)); - return ret; - } - while (!results.done) nl_recvmsgs(socket, cb); // Now wait until the scan is done or aborted. - if (results.aborted) { - printf("ERROR: Kernel aborted scan.\n"); - return 1; - } - printf("Scan is done.\n"); +// // Allocate the messages and callback handler. +// msg = nlmsg_alloc(); +// if (!msg) {throw Exception("Failed to allocate netlink message for msg.");} - // Cleanup. - nlmsg_free(msg); - nl_cb_put(cb); - nl_socket_drop_membership(socket, mcid); // No longer need this. - return 0; - } +// ssids_to_scan = nlmsg_alloc(); +// if (!ssids_to_scan) { +// nlmsg_free(msg); +// throw Exception("Failed to allocate netlink message for ssids_to_scan."); +// } + +// cb = nl_cb_alloc(NL_CB_DEFAULT); +// if (!cb) { +// nlmsg_free(msg); +// nlmsg_free(ssids_to_scan); +// throw Exception("Failed to allocate netlink callbacks."); +// } + +// // Setup the messages and callback handler. +// genlmsg_put(msg, 0, 0, driver_id, 0, 0, NL80211_CMD_TRIGGER_SCAN, 0); // Setup which command to run. +// nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); // Add message attribute, which interface to use. +// nla_put(ssids_to_scan, 1, 0, ""); // Scan all SSIDs. +// nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids_to_scan); // Add message attribute, which SSIDs to scan for. +// nlmsg_free(ssids_to_scan); // Copied to `msg` above, no longer need this. +// nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, callback_trigger, &results); // Add the callback. +// nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); +// nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); +// nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); +// nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); // No sequence checking for multicast messages. + +// // Send NL80211_CMD_TRIGGER_SCAN to start the scan. The kernel may reply with NL80211_CMD_NEW_SCAN_RESULTS on +// // success or NL80211_CMD_SCAN_ABORTED if another scan was started by another process. +// err = 1; +// ret = nl_send_auto(socket, msg); // Send the message. +// printf("NL80211_CMD_TRIGGER_SCAN sent %d bytes to the kernel.\n", ret); +// printf("Waiting for scan to complete...\n"); +// while (err > 0) ret = nl_recvmsgs(socket, cb); // First wait for ack_handler(). This helps with basic errors. +// if (err < 0) { +// printf("WARNING: err has a value of %d.\n", err); +// } +// if (ret < 0) { +// printf("ERROR: nl_recvmsgs() returned %d (%s).\n", ret, nl_geterror(-ret)); +// return ret; +// } +// while (!results.done) nl_recvmsgs(socket, cb); // Now wait until the scan is done or aborted. +// if (results.aborted) { +// printf("ERROR: Kernel aborted scan.\n"); +// return 1; +// } +// printf("Scan is done.\n"); + +// // Cleanup. +// nlmsg_free(msg); +// nl_cb_put(cb); +// nl_socket_drop_membership(socket, mcid); // No longer need this. +// return 0; +// } int if_index; struct nl_sock* socket; int driver_id; -public: - WiFiScanLinux(const std::string& devName) { + struct nl_cb *cb; + int mcid; - if_index = if_nametoindex("wlp0s20u2u1"); // Use this wireless interface for scanning. + int err; + struct trigger_results results; - // Open socket to kernel. - socket = nl_socket_alloc(); // Allocate new netlink socket in memory. - genl_connect(socket); // Create file descriptor and bind socket. - driver_id = genl_ctrl_resolve(socket, "nl80211"); // Find the nl80211 driver ID. + /** configure all needed callback (from netlink to code) once */ + void setupOnce() { + + mcid = nl_get_multicast_id(socket, "nl80211", "scan"); + nl_socket_add_membership(socket, mcid); // Without this, callback_trigger() won't be called. + + cb = nl_cb_alloc(NL_CB_DEFAULT); + if (!cb) {throw Exception("Failed to allocate netlink callbacks.");} + + // Setup the messages and callback handler. + nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, callback_trigger, &results); // Add the callback. + nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err); + nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err); + nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, ack_handler, &err); + nl_cb_set(cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, no_seq_check, NULL); // No sequence checking for multicast messages. } - /** triger WiFiScan and fetch the result */ - WiFiMeasurements scan() { + /** triggers a new scan within the wifi hardware */ + void triggerNewScan() { - // Issue NL80211_CMD_TRIGGER_SCAN to the kernel and wait for it to finish. - int err = do_scan_trigger(socket, if_index, driver_id); - if (err != 0) { - printf("do_scan_trigger() failed with %d.\n", err); - throw "error"; + std::cout << "triggerNewScan()" << std::endl; + + struct nl_msg *ssids_to_scan; + ssids_to_scan = nlmsg_alloc(); + if (!ssids_to_scan) {throw Exception("Failed to allocate netlink message for ssids_to_scan.");} + nla_put(ssids_to_scan, 1, 0, ""); // Scan all SSIDs. + + // construct message + struct nl_msg* msg = nlmsg_alloc(); + if (!msg) {throw Exception("Failed to allocate netlink message for msg.");} + + genlmsg_put(msg, 0, 0, driver_id, 0, 0, NL80211_CMD_TRIGGER_SCAN, 0); // Setup which command to run. + nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); // Add message attribute, which interface to use. + nla_put_nested(msg, NL80211_ATTR_SCAN_SSIDS, ssids_to_scan); // Add message attribute, which SSIDs to scan for. + nlmsg_free(ssids_to_scan); // Copied to `msg` above, no longer need this. + + results.done = 0; + results.aborted = 0; + + // trigger scan by sending the constructed message + const int ret = nl_send_auto(socket, msg); // Send the message. + printf("NL80211_CMD_TRIGGER_SCAN sent %d bytes to the kernel.\n", ret); + printf("Waiting for scan to complete...\n"); + nlmsg_free(msg); + + } + + /** blocks until the scan-result is available. true if OK, false otherwise */ + bool waitForScanResult() { + + // Send NL80211_CMD_TRIGGER_SCAN to start the scan. The kernel may reply with NL80211_CMD_NEW_SCAN_RESULTS on + // success or NL80211_CMD_SCAN_ABORTED if another scan was started by another process. + err = 0; +// ret = nl_send_auto(socket, msg); // Send the message. +// printf("NL80211_CMD_TRIGGER_SCAN sent %d bytes to the kernel.\n", ret); +// printf("Waiting for scan to complete...\n"); +// while (err > 0) ret = nl_recvmsgs(socket, cb); // First wait for ack_handler(). This helps with basic errors. +// if (err < 0) { +// printf("WARNING: err has a value of %d.\n", err); +// } + + while(true) { + const int ret = nl_recvmsgs(socket, cb); + printf("-- ret: %d err: %d \n", ret, err); + if (results.done) { + return true; + } + if (ret < 0 || err < 0) { + nl_recvmsgs(socket, cb); // seems to fix issues when device is busy?! + printf("ERROR: nl_recvmsgs() returned %d (%s).\n", ret, nl_geterror(-ret)); + return false; + } } + } + + void scanResult(TMP* res) { + // Now get info for all SSIDs detected. struct nl_msg *msg = nlmsg_alloc(); // Allocate a message. genlmsg_put(msg, 0, 0, driver_id, 0, NLM_F_DUMP, NL80211_CMD_GET_SCAN, 0); // Setup which command to run. nla_put_u32(msg, NL80211_ATTR_IFINDEX, if_index); // Add message attribute, which interface to use. - nl_socket_modify_cb(socket, NL_CB_VALID, NL_CB_CUSTOM, callback_dump, NULL); // Add the callback. + nl_socket_modify_cb(socket, NL_CB_VALID, NL_CB_CUSTOM, addResult, res); // Add the callback and the measurements to fill int ret = nl_send_auto(socket, msg); // Send the message. printf("NL80211_CMD_GET_SCAN sent %d bytes to the kernel.\n", ret); ret = nl_recvmsgs_default(socket); // Retrieve the kernel's answer. callback_dump() prints SSIDs to stdout. @@ -401,9 +444,94 @@ public: throw "error"; } - // TODO - WiFiMeasurements mes; - return mes; + } + + void scanCleanup() { + + // Cleanup. + //nlmsg_free(msg); + nl_cb_put(cb); + nl_socket_drop_membership(socket, mcid); // No longer need this. + //return 0; + + } + + + + + +public: + + WiFiScanLinux(const std::string& devName) { + + // convert interface-name to interface-index + if_index = if_nametoindex(devName.c_str()); + + // Open socket to kernel. + socket = nl_socket_alloc(); // Allocate new netlink socket in memory. + genl_connect(socket); // Create file descriptor and bind socket. + driver_id = genl_ctrl_resolve(socket, "nl80211"); // Find the nl80211 driver ID. + + setupOnce(); + + } + + ~WiFiScanLinux() { + + // cleanup + nl_socket_free(socket); + + } + + + + /** triger WiFiScan and fetch the result */ + WiFiMeasurements scan() { + + + TMP res; + + + // Issue NL80211_CMD_TRIGGER_SCAN to the kernel and wait for it to finish. +// while(true) { + +// // use the current timestamp to suppress older scan results +// // which are cached by linux by default +// res.tsStart = Timestamp::fromUnixTime(); + +// // trigger a scan +// //int err = do_scan_trigger(socket, if_index, driver_id); +// int err = scanTrigger(); +//// if (err == -25) {std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue;} // currently busy. try again +//// if (err != 0) {throw Exception("do_scan_trigger() failed with code: " + std::to_string(err));} +//// break; + +// } + + again:; + + triggerNewScan(); + + std::cout << "scan triggered" << std::endl; + + if (waitForScanResult()) { + + std::cout << "scan done" << std::endl; + scanResult(&res); + + // return constructed result + return res.res; + + + } else { + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + goto again; + + } + + + } diff --git a/tests/data/TestTimestamp.cpp b/tests/data/TestTimestamp.cpp index 4552b4b..d7b723a 100644 --- a/tests/data/TestTimestamp.cpp +++ b/tests/data/TestTimestamp.cpp @@ -52,4 +52,35 @@ TEST(Timestamp, add) { } +TEST(Timestamp, div) { + + Timestamp ts1 = Timestamp::fromMS(1000); + + ASSERT_EQ(100, (ts1/(size_t)10).ms()); + +} + +TEST(Timestamp, minmax) { + + Timestamp tsLow = std::numeric_limits::lowest(); + + + Timestamp tsMin = std::numeric_limits::min(); + Timestamp tsMax = std::numeric_limits::max(); + Timestamp ts0 = Timestamp::fromMS(0); + Timestamp tsNeg = Timestamp::fromMS(-99999999999999L); + Timestamp tsPos = Timestamp::fromMS(+99999999999999L); + + ASSERT_EQ(tsMin, tsLow); + + ASSERT_TRUE(tsMin < tsMax); + ASSERT_TRUE(tsMin < ts0); + ASSERT_TRUE(tsMin < tsNeg); + + ASSERT_TRUE(tsMax > tsMin); + ASSERT_TRUE(tsMax > ts0); + ASSERT_TRUE(tsMax > tsPos); + +} + #endif diff --git a/tests/sensors/radio/TestVAPGrouper.cpp b/tests/sensors/radio/TestVAPGrouper.cpp index 94a251f..77d96f0 100644 --- a/tests/sensors/radio/TestVAPGrouper.cpp +++ b/tests/sensors/radio/TestVAPGrouper.cpp @@ -38,9 +38,9 @@ TEST(WiFiVAPGrouper, baseMAC) { TEST(WiFiVAPGrouper, aggregation) { - VAPGrouper vgAvg(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE); - VAPGrouper vgMedian(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::MEDIAN); - VAPGrouper vgMax(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::MAXIMUM); + VAPGrouper vgAvg(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE, VAPGrouper::TimeAggregation::AVERAGE); + VAPGrouper vgMedian(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::MEDIAN, VAPGrouper::TimeAggregation::MINIMUM); + VAPGrouper vgMax(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::MAXIMUM, VAPGrouper::TimeAggregation::MAXIMUM); WiFiMeasurements scan; @@ -59,7 +59,7 @@ TEST(WiFiVAPGrouper, aggregation) { scan.entries.push_back(WiFiMeasurement(vap20, -69, Timestamp::fromMS(22))); scan.entries.push_back(WiFiMeasurement(vap21, -61, Timestamp::fromMS(25))); - scan.entries.push_back(WiFiMeasurement(vap22, -62, Timestamp::fromMS(23))); + scan.entries.push_back(WiFiMeasurement(vap22, -62, Timestamp::fromMS(21))); scan.entries.push_back(WiFiMeasurement(vap23, -60, Timestamp::fromMS(20))); const WiFiMeasurements gAvg = vgAvg.group(scan); @@ -71,26 +71,73 @@ TEST(WiFiVAPGrouper, aggregation) { ASSERT_EQ(2, gMedian.entries.size()); ASSERT_EQ(2, gMax.entries.size()); - // correct average values? + // correct average rssi / average timestamp? ASSERT_EQ(-72, gAvg.entries.back().getRSSI()); ASSERT_EQ(-63, gAvg.entries.front().getRSSI()); - ASSERT_EQ(Timestamp::fromMS(11), gAvg.entries.back().getTimestamp()); - ASSERT_EQ(Timestamp::fromMS(22), gAvg.entries.front().getTimestamp()); + ASSERT_EQ(Timestamp::fromMS(12), gAvg.entries.back().getTimestamp()); // average ts + ASSERT_EQ(Timestamp::fromMS(22), gAvg.entries.front().getTimestamp()); // average ts - // correct median values? + // correct median rssi / min timestamp? ASSERT_EQ(-71, gMedian.entries.back().getRSSI()); ASSERT_EQ(-61.5, gMedian.entries.front().getRSSI()); - ASSERT_EQ(Timestamp::fromMS(11), gMedian.entries.back().getTimestamp()); - ASSERT_EQ(Timestamp::fromMS(22), gMedian.entries.front().getTimestamp()); + ASSERT_EQ(Timestamp::fromMS(11), gMedian.entries.back().getTimestamp()); // min ts + ASSERT_EQ(Timestamp::fromMS(20), gMedian.entries.front().getTimestamp()); // min ts - // correct max values? + // correct max rssi / max timestamp? ASSERT_EQ(-70, gMax.entries.back().getRSSI()); ASSERT_EQ(-60, gMax.entries.front().getRSSI()); - ASSERT_EQ(Timestamp::fromMS(11), gMax.entries.back().getTimestamp()); - ASSERT_EQ(Timestamp::fromMS(22), gMax.entries.front().getTimestamp()); + ASSERT_EQ(Timestamp::fromMS(13), gMax.entries.back().getTimestamp()); // max ts + ASSERT_EQ(Timestamp::fromMS(25), gMax.entries.front().getTimestamp()); // max ts } +TEST(WiFiVAPGrouper, aggregationTS) { + + VAPGrouper vgAvg(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::AVERAGE, VAPGrouper::TimeAggregation::AVERAGE); + VAPGrouper vgMedian(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::MEDIAN, VAPGrouper::TimeAggregation::MINIMUM); + VAPGrouper vgMax(VAPGrouper::Mode::LAST_MAC_DIGIT_TO_ZERO, VAPGrouper::Aggregation::MAXIMUM, VAPGrouper::TimeAggregation::MAXIMUM); + + WiFiMeasurements scan; + + const AccessPoint vap0("01:bb:cc:dd:11:a0"); + const AccessPoint vap1("01:bb:cc:dd:11:a1"); + const AccessPoint vap2("01:bb:cc:dd:11:a2"); + const AccessPoint vap3("01:bb:cc:dd:11:a3"); + const AccessPoint vap4("01:bb:cc:dd:11:a4"); + const AccessPoint vap5("01:bb:cc:dd:11:a5"); + const AccessPoint vap6("01:bb:cc:dd:11:a6"); + const AccessPoint vap7("01:bb:cc:dd:11:a7"); + const AccessPoint vap8("01:bb:cc:dd:11:a8"); + + Timestamp base = Timestamp::fromUnixTime(); + + scan.entries.push_back(WiFiMeasurement(vap0, -1, base+Timestamp::fromMS(1))); + scan.entries.push_back(WiFiMeasurement(vap1, -2, base+Timestamp::fromMS(2))); + scan.entries.push_back(WiFiMeasurement(vap2, -3, base+Timestamp::fromMS(3))); + scan.entries.push_back(WiFiMeasurement(vap3, -4, base+Timestamp::fromMS(4))); + scan.entries.push_back(WiFiMeasurement(vap4, -5, base+Timestamp::fromMS(5))); + scan.entries.push_back(WiFiMeasurement(vap5, -6, base+Timestamp::fromMS(6))); + scan.entries.push_back(WiFiMeasurement(vap6, -7, base+Timestamp::fromMS(7))); + scan.entries.push_back(WiFiMeasurement(vap7, -8, base+Timestamp::fromMS(8))); + scan.entries.push_back(WiFiMeasurement(vap8, -9, base+Timestamp::fromMS(9))); + + const WiFiMeasurements gAvg = vgAvg.group(scan); + const WiFiMeasurements gMedian = vgMedian.group(scan); + const WiFiMeasurements gMax = vgMax.group(scan); + + // correct average rssi / average timestamp? + ASSERT_EQ(-5, gAvg.entries.back().getRSSI()); + ASSERT_EQ(base+Timestamp::fromMS(5), gAvg.entries.front().getTimestamp()); + + // correct median rssi / min timestamp? + ASSERT_EQ(-5, gMedian.entries.back().getRSSI()); + ASSERT_EQ(base+Timestamp::fromMS(1), gMedian.entries.back().getTimestamp()); // min ts + + // correct max rssi / max timestamp? + ASSERT_EQ(-1, gMax.entries.back().getRSSI()); + ASSERT_EQ(base+Timestamp::fromMS(9), gMax.entries.back().getTimestamp()); // max ts + +} #endif