some fixes [multithreading,..]
needed interface changes [new options] logger for android wifi-ap-optimization new test-cases
This commit is contained in:
@@ -18,23 +18,23 @@ class WiFiGridEstimator {
|
||||
public:
|
||||
|
||||
|
||||
/**
|
||||
* convenience method
|
||||
*/
|
||||
template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const Floorplan::IndoorMap* im) {
|
||||
// /**
|
||||
// * convenience method
|
||||
// */
|
||||
// template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const Floorplan::IndoorMap* im) {
|
||||
|
||||
// list of all APs
|
||||
std::vector<LocatedAccessPoint> aps;
|
||||
for (const Floorplan::Floor* f : im->floors) {
|
||||
for (const Floorplan::AccessPoint* ap : f->accesspoints) {
|
||||
aps.push_back(LocatedAccessPoint(*ap));
|
||||
}
|
||||
}
|
||||
// // list of all APs
|
||||
// std::vector<LocatedAccessPoint> aps;
|
||||
// for (const Floorplan::Floor* f : im->floors) {
|
||||
// for (const Floorplan::AccessPoint* ap : f->accesspoints) {
|
||||
// aps.push_back(LocatedAccessPoint(*ap));
|
||||
// }
|
||||
// }
|
||||
|
||||
// perform estimation
|
||||
estimate(grid, mdl, aps);
|
||||
// // perform estimation
|
||||
// estimate(grid, mdl, aps);
|
||||
|
||||
}
|
||||
// }
|
||||
|
||||
/**
|
||||
* perform a signal-strength estimation for all of the given access points
|
||||
@@ -42,39 +42,50 @@ public:
|
||||
* store the estimated strength onto each node.
|
||||
* as nodes only provide a limited number of rssi-entries,
|
||||
* store only the strongest ones.
|
||||
*
|
||||
* as the smartphone is held above the ground, we do NOT want to estimate
|
||||
* the signal strength for the nodes (on the ground) but for the nodes
|
||||
* + the height the smartphone is held at
|
||||
*
|
||||
*/
|
||||
template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const std::vector<AccessPoint> aps) {
|
||||
template <typename Node> static void estimate(Grid<Node>& grid, WiFiModel& mdl, const float smartphoneAtHeight) {
|
||||
|
||||
// sanity checks
|
||||
Assert::isTrue(Node::getMapAPs().empty(), "there are already some processed APs available!");
|
||||
Assert::isTrue(Node::getMapAPs().empty(), "there are already APs stored on the grid nodes!");
|
||||
|
||||
// attach the access-points to the shared node-vector
|
||||
// all APs known to the model
|
||||
std::vector<AccessPoint> aps = mdl.getAllAPs();
|
||||
|
||||
// attach each access-points to a vector shared for all grid-nodes
|
||||
for (const AccessPoint& ap : aps) {
|
||||
Node::getMapAPs().push_back(ap);
|
||||
}
|
||||
|
||||
// smartphone offset (meter above ground)
|
||||
const Point3 smartphoneOffset(0,0,smartphoneAtHeight);
|
||||
|
||||
// process each node
|
||||
for (Node& n : grid) {
|
||||
|
||||
// keep the strongest APs to attach to this node
|
||||
std::vector<WiFiGridNodeAP> nodeAPs;
|
||||
|
||||
// process each AP
|
||||
// process each AP known to the model
|
||||
for (int apIdx = 0; apIdx < (int) aps.size(); ++apIdx) {
|
||||
|
||||
// estimate the signal-strength
|
||||
const float rssi = mdl.getRSSI(aps[apIdx].getMAC(), n.inMeter());
|
||||
const float rssi = mdl.getRSSI(aps[apIdx].getMAC(), n.inMeter() + smartphoneOffset);
|
||||
|
||||
// keep it
|
||||
// (temporarily) keep it
|
||||
nodeAPs.push_back(WiFiGridNodeAP(apIdx, rssi));
|
||||
|
||||
}
|
||||
|
||||
// sort all APs by signal strength
|
||||
// now sort all the visible APs by signal strength
|
||||
auto comp = [] (const WiFiGridNodeAP& ap1, const WiFiGridNodeAP& ap2) {return ap1.getRSSI() > ap2.getRSSI();};
|
||||
std::sort(nodeAPs.begin(), nodeAPs.end(), comp);
|
||||
|
||||
// attach the strongest X to the node
|
||||
// and finally attach the strongest X to the node
|
||||
const int cnt = std::min( n.getMaxAPs(), (int) nodeAPs.size() );
|
||||
for (int i = 0; i < cnt; ++i) {
|
||||
n.strongestAPs[i] = nodeAPs[i];
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include "AccessPoint.h"
|
||||
#include "../../misc/Debug.h"
|
||||
|
||||
/**
|
||||
* rssi model-estimation for one AP, denoted by its index [among all APs present within the map]
|
||||
@@ -100,9 +101,11 @@ template <int maxAccessPoints> struct WiFiGridNode {
|
||||
* returns 0 if unknown
|
||||
*/
|
||||
float getRSSI(const MACAddress mac) const {
|
||||
for (const WiFiGridNodeAP ap : strongestAPs) {
|
||||
if (!ap.isValid()) {break;} // reached the end
|
||||
if (getMapAPs()[ap.getAPIdx()].getMAC() == mac) {return ap.getRSSI();}
|
||||
for (const WiFiGridNodeAP& ap : strongestAPs) {
|
||||
//std::cout << getMapAPs()[ap.getAPIdx()].getMAC().asString() << std::endl;
|
||||
//std::cout << mac.asString() << std::endl;
|
||||
if (!ap.isValid()) {break;} // reached the end of all APs visible on this node
|
||||
if (getMapAPs()[ap.getAPIdx()].getMAC() == mac) {return ap.getRSSI();} // does this APs MAC match with the requested MAC? -> found!
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -124,6 +127,8 @@ template <int maxAccessPoints> struct WiFiGridNode {
|
||||
|
||||
protected:
|
||||
|
||||
static constexpr const char* name = "WiFiGridNode";
|
||||
|
||||
/** serialize static members */
|
||||
static void staticSerialize(std::ostream& out) {
|
||||
|
||||
@@ -134,6 +139,8 @@ protected:
|
||||
out.write((const char*) &numAPs, sizeof(numAPs));
|
||||
out.write((const char*) getMapAPs().data(), sizeof(getMapAPs()[0])*numAPs);
|
||||
|
||||
Log::add(name, "serialized " + std::to_string(numAPs) + " APs");
|
||||
|
||||
}
|
||||
|
||||
/** deserialize static members */
|
||||
@@ -149,6 +156,10 @@ protected:
|
||||
// deserialize APs within map
|
||||
inp.read((char*) getMapAPs().data(), sizeof(getMapAPs()[0])*numAPs);
|
||||
|
||||
Log::add(name, "de-serialized " + std::to_string(numAPs) + " APs");
|
||||
std::string aps; for (const AccessPoint& ap : getMapAPs()) {aps += ap.getMAC().asString() + " ";}
|
||||
Log::add(name, aps);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
*/
|
||||
class WiFiMeasurement {
|
||||
|
||||
private:
|
||||
public:
|
||||
|
||||
friend class VAPGrouper;
|
||||
|
||||
|
||||
@@ -13,6 +13,15 @@ struct WiFiMeasurements {
|
||||
/** all contained measurements */
|
||||
std::vector<WiFiMeasurement> entries;
|
||||
|
||||
/** convert to string */
|
||||
std::string asString() const {
|
||||
std::string res;
|
||||
for (const WiFiMeasurement& m : entries) {
|
||||
res += m.getAP().getMAC().asString() + ": " + std::to_string(m.getRSSI()) + "\n";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIMEASUREMENTS_H
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "WiFiProbability.h"
|
||||
#include "model/WiFiModel.h"
|
||||
#include "../../math/Distributions.h"
|
||||
#include "VAPGrouper.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -17,7 +18,7 @@ private:
|
||||
|
||||
const float sigma = 8.0f;
|
||||
|
||||
const float sigmaPerSecond = 1.5f;
|
||||
const float sigmaPerSecond = 3.0f;
|
||||
|
||||
/** the RSSI prediction model */
|
||||
WiFiModel& model;
|
||||
@@ -25,23 +26,25 @@ private:
|
||||
/** the map's floorplan */
|
||||
Floorplan::IndoorMap* map;
|
||||
|
||||
|
||||
public:
|
||||
|
||||
WiFiObserverFree(const float sigma, WiFiModel& model) : sigma(sigma), model(model) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
double getProbability(const Point3& pos, const Timestamp curTime, const WiFiMeasurements& obs) const {
|
||||
double getProbability(const Point3& pos_m, const Timestamp curTime, const WiFiMeasurements& obs) const {
|
||||
|
||||
double prob = 1.0;
|
||||
int numMatchingAPs = 0;
|
||||
|
||||
// process each measured AP
|
||||
for (const WiFiMeasurement& entry : obs.entries) {
|
||||
|
||||
// sanity check
|
||||
Assert::isFalse(entry.getTimestamp().isZero(), "wifi measurement without timestamp. coding error?");
|
||||
|
||||
// get the model's RSSI (if possible!)
|
||||
const float modelRSSI = model.getRSSI(entry.getAP().getMAC(), pos);
|
||||
const float modelRSSI = model.getRSSI(entry.getAP().getMAC(), pos_m);
|
||||
|
||||
// NaN? -> AP not known to the model -> skip
|
||||
if (modelRSSI != modelRSSI) {continue;}
|
||||
@@ -53,21 +56,29 @@ public:
|
||||
// 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?");
|
||||
|
||||
// sigma grows with measurement age
|
||||
const float sigma = this->sigma + this->sigmaPerSecond * age.sec();
|
||||
|
||||
// update probability
|
||||
prob *= Distribution::Normal<double>::getProbability(modelRSSI, sigma, scanRSSI);
|
||||
//prob *= Distribution::Region<double>::getProbability(modelRSSI, sigma, scanRSSI);
|
||||
|
||||
++numMatchingAPs;
|
||||
|
||||
}
|
||||
|
||||
// sanity check
|
||||
Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
|
||||
|
||||
return prob;
|
||||
|
||||
}
|
||||
|
||||
template <typename Node> double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs, const int age_ms = 0) const {
|
||||
throw "todo??";
|
||||
throw Exception("todo??");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
#include "../../math/Distributions.h"
|
||||
#include "../../data/Timestamp.h"
|
||||
|
||||
#include "WiFiGridNode.h"
|
||||
#include "WiFiProbability.h"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
/**
|
||||
* probability is calculated by comparing pre-calculated wifi-signal-strengths
|
||||
* attached to each grid-node with a given WiFiMeasurements data structure
|
||||
*/
|
||||
class WiFiObserverGrid : public WiFiProbability {
|
||||
template <typename Node> class WiFiObserverGrid : public WiFiProbability {
|
||||
|
||||
|
||||
private:
|
||||
|
||||
@@ -21,14 +24,23 @@ private:
|
||||
float sigma = 8.0f;
|
||||
|
||||
/** additional sigma-per-second (measurement age) to*/
|
||||
float sigmaPerSecond = 1.5;
|
||||
float sigmaPerSecond = 3;
|
||||
|
||||
std::unordered_set<MACAddress> knownAPs;
|
||||
|
||||
public:
|
||||
|
||||
/** ctor with uncertainty */
|
||||
WiFiObserverGrid(const float sigma) : sigma(sigma) {
|
||||
;
|
||||
|
||||
//StaticAssert::AinheritsB<Node, WiFiGridNode>();
|
||||
|
||||
for (const AccessPoint& ap : Node::getMapAPs()) {
|
||||
knownAPs.insert(ap.getMAC());
|
||||
}
|
||||
|
||||
Assert::isFalse(knownAPs.empty(), "no APs known to the grid nodes?!");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -36,43 +48,66 @@ public:
|
||||
* compares the predicted signal-strengths stored on the given node
|
||||
* with the provided WiFi measurements
|
||||
*/
|
||||
template <typename Node> double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs) const {
|
||||
double getProbability(const Node& n, const Timestamp curTime, const WiFiMeasurements& obs) const {
|
||||
|
||||
double prob = 0;
|
||||
// compile-time sanity check. Node must be a subclass off WiFiGridNode
|
||||
//StaticAssert::AinheritsB<Node, WiFiGridNode>();
|
||||
|
||||
double prob = 1;
|
||||
int numMatchingAPs = 0;
|
||||
|
||||
// process each observed measurement
|
||||
for (const WiFiMeasurement& measurement : obs.entries) {
|
||||
|
||||
// if an AP is not known to any of the nodes, just skip it
|
||||
if (knownAPs.find(measurement.getAP().getMAC()) == knownAPs.end()) {
|
||||
continue;}
|
||||
|
||||
// determine the age for this measurement
|
||||
const Timestamp age = curTime - measurement.getTimestamp();
|
||||
|
||||
// sigma grows with measurement age
|
||||
const float sigma = this->sigma + this->sigmaPerSecond * age.sec();
|
||||
float sigma = this->sigma + this->sigmaPerSecond * age.sec();
|
||||
|
||||
// the RSSI from the scan
|
||||
const float measuredRSSI = measurement.getRSSI();
|
||||
|
||||
// the RSSI from the model (if available!)
|
||||
const float modelRSSI = n.getRSSI(measurement.getAP().getMAC());
|
||||
float modelRSSI = n.getRSSI(measurement.getAP().getMAC());
|
||||
|
||||
// no model RSSI available?
|
||||
if (modelRSSI == 0) {continue;}
|
||||
// if no model RSSI is available, that means,
|
||||
// the AP in question is not / only barely visible at this location
|
||||
// assume a very low signal-strength and increase the sigma
|
||||
if (modelRSSI == 0) {
|
||||
modelRSSI = -100;
|
||||
sigma *= 2;
|
||||
}
|
||||
|
||||
// compare both
|
||||
const double p = Distribution::Normal<double>::getProbability(measuredRSSI, sigma, modelRSSI);
|
||||
//const double p = Distribution::Region<double>::getProbability(measuredRSSI, sigma, modelRSSI);
|
||||
|
||||
// adjust using log
|
||||
prob += std::log(p);
|
||||
//prob += std::log(p);
|
||||
prob *= p;
|
||||
|
||||
++numMatchingAPs;
|
||||
|
||||
}
|
||||
|
||||
//return std::pow(std::exp(prob), 0.1);
|
||||
return std::exp(prob);
|
||||
// sanity check
|
||||
// Assert::isTrue(numMatchingAPs > 0, "not a single measured AP was matched against known ones. coding error? model error?");
|
||||
// if (numMatchingAPs == 0) {return 0;}
|
||||
|
||||
// as not every node has the same number of visible/matching APs
|
||||
// we MUST return something like the average probability
|
||||
return prob;
|
||||
//return std::pow(prob, 1.0/3.0);
|
||||
|
||||
}
|
||||
|
||||
/** gnuplot debug dump */
|
||||
template <typename Node> void dump(Grid<Node>& grid, const Timestamp curTime, const WiFiMeasurements& obs, const std::string& fileName) {
|
||||
void dump(Grid<Node>& grid, const Timestamp curTime, const WiFiMeasurements& obs, const std::string& fileName) {
|
||||
|
||||
std::ofstream out(fileName);
|
||||
out << "splot '-' with points palette\n";
|
||||
|
||||
@@ -16,6 +16,9 @@ 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 */
|
||||
virtual std::vector<AccessPoint> getAllAPs() const = 0;
|
||||
|
||||
/**
|
||||
* get the RSSI expected at the given location (in meter)
|
||||
* for an AP identified by the given MAC.
|
||||
|
||||
@@ -36,6 +36,13 @@ public:
|
||||
;
|
||||
}
|
||||
|
||||
/** get a list of all APs known to the model */
|
||||
std::vector<AccessPoint> getAllAPs() const {
|
||||
std::vector<AccessPoint> 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) {
|
||||
|
||||
|
||||
@@ -39,9 +39,12 @@ private:
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
/** ctor with floorplan (needed for ceiling position) */
|
||||
WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) {
|
||||
|
||||
// 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);
|
||||
@@ -49,6 +52,25 @@ public:
|
||||
|
||||
}
|
||||
|
||||
/** get a list of all APs known to the model */
|
||||
std::vector<AccessPoint> getAllAPs() const {
|
||||
std::vector<AccessPoint> 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) {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** make the given AP (and its parameters) known to the model */
|
||||
void addAP(const MACAddress& accessPoint, const APEntry& params) {
|
||||
|
||||
@@ -57,11 +79,18 @@ public:
|
||||
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]");
|
||||
|
||||
Assert::equal(accessPoints.find(accessPoint), accessPoints.end(), "AccessPoint already present!");
|
||||
|
||||
// add
|
||||
accessPoints.insert( std::pair<MACAddress, APEntry>(accessPoint, params) );
|
||||
|
||||
}
|
||||
|
||||
/** remove all added APs */
|
||||
void clear() {
|
||||
accessPoints.clear();
|
||||
}
|
||||
|
||||
float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
|
||||
|
||||
// try to get the corresponding parameters
|
||||
@@ -90,6 +119,28 @@ public:
|
||||
protected:
|
||||
|
||||
FRIEND_TEST(LogDistanceCeilingModel, numCeilings);
|
||||
FRIEND_TEST(LogDistanceCeilingModel, numCeilingsFloat);
|
||||
|
||||
|
||||
/** 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);
|
||||
|
||||
float cnt = 0;
|
||||
|
||||
for (const float z : ceilingsAtHeight_m) {
|
||||
if (zMin < z && zMax > z) {
|
||||
const float dmax = zMax - z;
|
||||
cnt += (dmax > 1) ? (1) : (dmax);
|
||||
}
|
||||
}
|
||||
|
||||
return cnt;
|
||||
|
||||
}
|
||||
|
||||
/** get the number of ceilings between z1 and z2 */
|
||||
int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const {
|
||||
@@ -98,6 +149,24 @@ protected:
|
||||
const float zMin = std::min(pos1.z, pos2.z);
|
||||
const float zMax = std::max(pos1.z, pos2.z);
|
||||
|
||||
#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
|
||||
|
||||
for (const float z : ceilingsAtHeight_m) {
|
||||
if (zMin < z && zMax > z) {++cnt;}
|
||||
}
|
||||
|
||||
94
sensors/radio/setup/WiFiFingerprint.h
Normal file
94
sensors/radio/setup/WiFiFingerprint.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#ifndef WIFIFINGERPRINT_H
|
||||
#define WIFIFINGERPRINT_H
|
||||
|
||||
|
||||
#include "../../../geo/Point3.h"
|
||||
#include "../WiFiMeasurements.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* denotes a wifi fingerprint
|
||||
* known position and several measurements conducted at this position
|
||||
*
|
||||
* as several measurements were conducted, each AP is usually contained more than once!
|
||||
*/
|
||||
struct WiFiFingerprint {
|
||||
|
||||
|
||||
/** real-world-position that was measured */
|
||||
Point3 pos_m;
|
||||
|
||||
/** measurements (APs) at the given location */
|
||||
WiFiMeasurements measurements;
|
||||
|
||||
|
||||
/** ctor */
|
||||
WiFiFingerprint() {;}
|
||||
|
||||
/** ctor */
|
||||
WiFiFingerprint(const Point3 pos_m) : pos_m(pos_m) {;}
|
||||
|
||||
|
||||
|
||||
/** as each AP is contained more than once (scanned more than once), group them by MAC and use the average RSSI */
|
||||
WiFiMeasurements average() {
|
||||
|
||||
// group scans by MAC (all measurements for one AP)
|
||||
std::unordered_map<MACAddress, WiFiMeasurements> group;
|
||||
for (WiFiMeasurement& m : measurements.entries) {
|
||||
group[m.getAP().getMAC()].entries.push_back(m);
|
||||
}
|
||||
|
||||
// create the output that contains the AP's average
|
||||
WiFiMeasurements res;
|
||||
for (auto& it : group) {
|
||||
const WiFiMeasurements& apMeasurements = it.second;
|
||||
WiFiMeasurement avg = apMeasurements.entries.front(); // average starts with a copy of the first entry (to get all data-fields beside the rssi)
|
||||
for (int i = 1; i < (int)apMeasurements.entries.size(); ++i) { // sum up all other entries [1:end]
|
||||
avg.rssi += apMeasurements.entries[i].rssi;
|
||||
}
|
||||
avg.rssi /= apMeasurements.entries.size();
|
||||
res.entries.push_back(avg); // add to output
|
||||
}
|
||||
|
||||
// done
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
/** serialize */
|
||||
void write(std::ostream& out) const {
|
||||
out << "pos: " << pos_m.x << " " << pos_m.y << " " << pos_m.z << "\n";
|
||||
out << "num: " << measurements.entries.size() << "\n";
|
||||
for (const WiFiMeasurement& wm : measurements.entries) {
|
||||
out << wm.getTimestamp().ms() << " " << wm.ap.getMAC().asString() << " " << wm.getRSSI() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
/** deserialize */
|
||||
void read(std::istream& inp) {
|
||||
std::string tmp;
|
||||
|
||||
// read the position
|
||||
inp >> tmp; if ("pos:" != tmp) {throw "error";}
|
||||
inp >> pos_m.x >> pos_m.y >> pos_m.z;
|
||||
|
||||
// number of entries
|
||||
inp >> tmp; if ("num:" != tmp) {throw "error";}
|
||||
int numEntries; inp >> numEntries;
|
||||
|
||||
// read the entries
|
||||
for (int i = 0; i < numEntries; ++i) {
|
||||
uint64_t ms; inp >> ms;
|
||||
std::string mac; inp >> mac;
|
||||
float rssi; inp >> rssi;
|
||||
WiFiMeasurement wm(AccessPoint(MACAddress(mac)), rssi, Timestamp::fromMS(ms));
|
||||
measurements.entries.push_back(wm);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // WIFIFINGERPRINT_H
|
||||
247
sensors/radio/setup/WiFiOptimizer.h
Normal file
247
sensors/radio/setup/WiFiOptimizer.h
Normal file
@@ -0,0 +1,247 @@
|
||||
#ifndef OPTIMIZER_H
|
||||
#define OPTIMIZER_H
|
||||
|
||||
#include "../../../floorplan/v2/Floorplan.h"
|
||||
#include "../../../floorplan/v2/FloorplanHelper.h"
|
||||
|
||||
#include "../VAPGrouper.h"
|
||||
#include "../../../geo/BBox3.h"
|
||||
#include "../../../misc/Debug.h"
|
||||
|
||||
#include "WiFiFingerprint.h"
|
||||
#include "../model/WiFiModel.h"
|
||||
#include "../model/WiFiModelLogDistCeiling.h"
|
||||
|
||||
#include <KLib/math/optimization/NumOptAlgoDownhillSimplex.h>
|
||||
#include <KLib/math/optimization/NumOptAlgoGenetic.h>
|
||||
#include <KLib/math/optimization/NumOptAlgoRangeRandom.h>
|
||||
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
|
||||
struct WiFiOptimizer {
|
||||
|
||||
private:
|
||||
|
||||
/** combine one RSSI measurement with the position the signal was measured at */
|
||||
struct RSSIatPosition {
|
||||
|
||||
/** real-world position (in meter) */
|
||||
const Point3 pos_m;
|
||||
|
||||
/** measured signal strength (for one AP) */
|
||||
const float rssi;
|
||||
|
||||
/** ctor */
|
||||
RSSIatPosition(const Point3 pos_m, const float rssi) : pos_m(pos_m), rssi(rssi) {;}
|
||||
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
struct APParams {
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float txp;
|
||||
float exp;
|
||||
float waf;
|
||||
Point3 getPos() const {return Point3(x,y,z);}
|
||||
APParams() {;}
|
||||
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) {;}
|
||||
std::string asString() {
|
||||
std::stringstream ss;
|
||||
ss << "Pos:" << getPos().asString() << " TXP:" << txp << " EXP:" << exp << " WAF:" << waf;
|
||||
return ss.str();
|
||||
}
|
||||
};
|
||||
|
||||
/** add MAC-info to params */
|
||||
struct APParamsMAC {
|
||||
MACAddress mac;
|
||||
APParams params;
|
||||
APParamsMAC(const MACAddress mac, const APParams& params) : mac(mac), params(params) {;}
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
Floorplan::IndoorMap* map;
|
||||
const VAPGrouper vg;
|
||||
|
||||
/** each MAC-Adress has several position->rssi entries */
|
||||
std::unordered_map<MACAddress, std::vector<RSSIatPosition>> apMap;
|
||||
|
||||
const char* name = "WiFiOptimizer";
|
||||
|
||||
public:
|
||||
|
||||
/** ctor */
|
||||
WiFiOptimizer(Floorplan::IndoorMap* map, const VAPGrouper& vg) : map(map), vg(vg) {
|
||||
;
|
||||
}
|
||||
|
||||
|
||||
/** add a new fingerprint to the optimizers data-source */
|
||||
void addFingerprint(const WiFiFingerprint& fp) {
|
||||
|
||||
// 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.rssi);
|
||||
apMap[m.getAP().getMAC()].push_back(rap);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** get a list of all to-be-optimized access-points (given by their mac-address) */
|
||||
std::vector<MACAddress> getAllMACs() const {
|
||||
std::vector<MACAddress> res;
|
||||
for (const auto& it : apMap) {res.push_back(it.first);}
|
||||
return res;
|
||||
}
|
||||
|
||||
/** optimize all known APs */
|
||||
std::vector<APParamsMAC> optimizeAll() const {
|
||||
|
||||
// sanity chekc
|
||||
Assert::isFalse(getAllMACs().empty(), "no APs found for optimization!");
|
||||
|
||||
float errSum = 0;
|
||||
std::vector<APParamsMAC> res;
|
||||
for (const MACAddress& mac : getAllMACs()) {
|
||||
float err;
|
||||
const APParams params = optimize(mac, err);
|
||||
res.push_back(APParamsMAC(mac, params));
|
||||
errSum += err;
|
||||
}
|
||||
|
||||
const float avgErr = errSum / getAllMACs().size();
|
||||
Log::add(name, "average AP error is: " + std::to_string(avgErr) + " dB");
|
||||
|
||||
return res;
|
||||
|
||||
}
|
||||
|
||||
/** optimize the given AP */
|
||||
APParams optimize(const MACAddress& mac, float& errResult) const {
|
||||
|
||||
// starting parameters do not matter for the current optimizer!
|
||||
APParams params(0,0,0, -40, 2.5, -4.0);
|
||||
constexpr float hugeError = 1e10;
|
||||
|
||||
// get all position->rssi measurements for this AP to compare them with the corresponding model estimations
|
||||
const std::vector<RSSIatPosition>& entries = apMap.find(mac)->second;
|
||||
|
||||
|
||||
// signal-strength-prediction-model...
|
||||
WiFiModelLogDistCeiling model(map);
|
||||
|
||||
auto func = [&] (const float* data) {
|
||||
|
||||
const APParams* params = (APParams*) data;
|
||||
|
||||
// some sanity checks
|
||||
if (params->waf > 0) {return hugeError;}
|
||||
|
||||
if (params->txp < -50) {return hugeError;}
|
||||
if (params->txp > -30) {return hugeError;}
|
||||
|
||||
if (params->exp > 4) {return hugeError;}
|
||||
if (params->exp < 1) {return hugeError;}
|
||||
|
||||
// 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
|
||||
model.clear();
|
||||
model.addAP(mac, WiFiModelLogDistCeiling::APEntry(apPos_m, params->txp, params->exp, params->waf));
|
||||
|
||||
float err = 0;
|
||||
int cnt = 0;
|
||||
|
||||
// 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);
|
||||
|
||||
// difference between estimation and measurement
|
||||
const float diff = std::abs(rssiModel - reading.rssi);
|
||||
|
||||
// adjust the error
|
||||
err += diff*diff;
|
||||
++cnt;
|
||||
|
||||
// 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);
|
||||
|
||||
if (params->txp < -50) {err += 999999;}
|
||||
if (params->txp > -35) {err += 999999;}
|
||||
|
||||
if (params->exp > 3.5) {err += 999999;}
|
||||
if (params->exp < 1.0) {err += 999999;}
|
||||
|
||||
return err;
|
||||
|
||||
};
|
||||
|
||||
|
||||
//
|
||||
const BBox3 mapBBox = FloorplanHelper::getBBox(map);
|
||||
|
||||
using LeOpt = K::NumOptAlgoRangeRandom<float>;
|
||||
const std::vector<LeOpt::MinMax> 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 - 5, mapBBox.getMax().z + 5), // z
|
||||
LeOpt::MinMax(-50,-30), // txp
|
||||
LeOpt::MinMax(1,3), // exp
|
||||
LeOpt::MinMax(-10,-4), // waf
|
||||
};
|
||||
|
||||
|
||||
// log
|
||||
Log::add(name, "optimizing parameters for AP " + mac.asString() + " by using " + std::to_string(entries.size()) + " fingerprints", false);
|
||||
Log::tick();
|
||||
|
||||
LeOpt opt(valRegion);
|
||||
opt.setPopulationSize(500); // USE MORE FOR PRODUCTION
|
||||
opt.setNumIerations(150);
|
||||
opt.calculateOptimum(func, (float*) ¶ms);
|
||||
|
||||
// using LeOpt = K::NumOptAlgoGenetic<float>;
|
||||
// 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<float, 6> opt;
|
||||
// opt.setMaxIterations(100);
|
||||
// opt.setNumRestarts(10);
|
||||
|
||||
opt.calculateOptimum(func, (float*) ¶ms);
|
||||
errResult = func((float*)¶ms);
|
||||
|
||||
Log::tock();
|
||||
Log::add(name, mac.asString() + ": " + params.asString() + " @ " + std::to_string(errResult) +" dB err");
|
||||
|
||||
return params;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
#endif // OPTIMIZER_H
|
||||
Reference in New Issue
Block a user