This commit is contained in:
2017-06-06 15:03:49 +02:00
122 changed files with 7794 additions and 1045 deletions

View File

@@ -1,7 +1,13 @@
#ifndef WIFIMODEL_H
#define WIFIMODEL_H
#include "../LocatedAccessPoint.h"
#include "../AccessPoint.h"
#include "../../../geo/Point3.h"
#include <vector>
#include "../../../data/XMLserialize.h"
/**
* interface for signal-strength prediction models.
@@ -9,10 +15,13 @@
* the model is passed a MAC-address of an AP in question, and a position.
* hereafter the model returns the RSSI for this AP at the questioned location.
*/
class WiFiModel {
class WiFiModel : public XMLserialize {
public:
/** dtor */
virtual ~WiFiModel() {;}
// /** get the given access-point's RSSI at the provided location */
// virtual float getRSSI(const LocatedAccessPoint& ap, const Point3 p) = 0;
@@ -27,6 +36,7 @@ public:
*/
virtual float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const = 0;
};
#endif // WIFIMODEL_H

View File

@@ -0,0 +1,32 @@
#ifndef WIFIMODELFACTORY_H
#define WIFIMODELFACTORY_H
#include "WiFiModel.h"
#include "../../../floorplan/v2/Floorplan.h"
/**
* this class can instantiate WiFiModels based on serialized XML files
*/
class WiFiModelFactory {
private:
Floorplan::IndoorMap* map;
public:
/** ctor. needs the floorplan for some models */
WiFiModelFactory(Floorplan::IndoorMap* map) : map(map) {
;
}
/** parse model from XML file */
WiFiModel* loadXML(const std::string& file);
/** parse model from XML node */
WiFiModel* readFromXML(XMLDoc* doc, XMLElem* src);
};
#endif // WIFIMODELFACTORY_H

View File

@@ -0,0 +1,42 @@
#ifndef WIFIMODELFACTORYIMPL_H
#define WIFIMODELFACTORYIMPL_H
#include "WiFiModelFactory.h"
#include "WiFiModelLogDist.h"
#include "WiFiModelLogDistCeiling.h"
#include "WiFiModelPerFloor.h"
#include "WiFiModelPerBBox.h"
WiFiModel* WiFiModelFactory::loadXML(const std::string& file) {
XMLDoc doc;
XMLserialize::assertOK(doc.LoadFile(file.c_str()), doc, "error while loading file");
XMLElem* root = doc.FirstChildElement("root");
return readFromXML(&doc, root);
}
WiFiModel* WiFiModelFactory::readFromXML(XMLDoc* doc, XMLElem* src) {
// each model attaches its "type" during serialization
const std::string type = src->Attribute("type");
WiFiModel* mdl = nullptr;
// create an instance for the model
if (type == "WiFiModelLogDist") {mdl = new WiFiModelLogDist();}
else if (type == "WiFiModelLogDistCeiling") {mdl = new WiFiModelLogDistCeiling(map);}
else if (type == "WiFiModelPerFloor") {mdl = new WiFiModelPerFloor(map);}
else if (type == "WiFiModelPerBBox") {mdl = new WiFiModelPerBBox(map);}
else {throw Exception("invalid model type given: " + type);}
// load the model from XML
mdl->readFromXML(doc, src);
// done
return mdl;
}
#endif // WIFIMODELFACTORYIMPL_H

View File

@@ -4,6 +4,8 @@
#include "WiFiModel.h"
#include "LogDistanceModel.h"
#include <unordered_map>
/**
* signal-strength estimation using log-distance model
*/
@@ -86,6 +88,14 @@ public:
}
void readFromXML(XMLDoc* doc, XMLElem* src) override {
throw Exception("not yet implemented");
}
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
throw Exception("not yet implemented");
}
};
#endif // WIFIMODELLOGDIST_H

View File

@@ -2,6 +2,7 @@
#define WIFIMODELLOGDISTCEILING_H
#include "../../../floorplan/v2/Floorplan.h"
#include "../../../floorplan/v2/FloorplanCeilings.h"
#include "../../../Assertions.h"
#include "WiFiModel.h"
@@ -9,6 +10,8 @@
#include "../VAPGrouper.h"
#include "../../../misc/Debug.h"
#include "../../../data/XMLserialize.h"
/**
* signal-strength estimation using log-distance model
* including ceilings between AP and position
@@ -36,24 +39,28 @@ private:
/** map of all APs (and their parameters) known to the model */
std::unordered_map<MACAddress, APEntry> accessPoints;
/** position (height) of all ceilings (in meter) */
std::vector<float> ceilingsAtHeight_m;
// /** position (height) of all ceilings (in meter) */
// std::vector<float> ceilingsAtHeight_m;
Floorplan::Ceilings ceilings;
public:
/** ctor with floorplan (needed for ceiling position) */
WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) {
WiFiModelLogDistCeiling(const Floorplan::IndoorMap* map) : ceilings(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);
}
}
/** get the entry for the given mac. exception if missing */
const APEntry& getAP(const MACAddress& mac) const {
const auto& it = accessPoints.find(mac);
if (it == accessPoints.end()) {throw Exception("model does not contain an entry for " + mac.asString());}
return it->second;
}
/** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const {
std::vector<AccessPoint> aps;
@@ -62,38 +69,47 @@ public:
}
/** 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) {
void loadAPs(const Floorplan::IndoorMap* map, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f, const bool assertSafe = true) {
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);
addAP(MACAddress(ap->mac), ape, assertSafe);
}
}
}
/** load AP information from the floorplan. use the given fixed TXP/EXP/WAF for all APs */
void loadAPs(const Floorplan::IndoorMap* map, const VAPGrouper& vg, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f) {
/**
* load AP information from the floorplan.
* use the given fixed TXP/EXP/WAF for all APs.
* usually txp,exp,waf are checked for a sane range. if you know what you are doing, set assertSafe to false!
*/
void loadAPs(const Floorplan::IndoorMap* map, const VAPGrouper& vg, const float txp = -40.0f, const float exp = 2.5f, const float waf = -8.0f, const bool assertSafe = true) {
for (const Floorplan::Floor* floor : map->floors) {
for (const Floorplan::AccessPoint* ap : floor->accesspoints) {
const APEntry ape(ap->getPos(floor), txp, exp, waf);
const MACAddress mac = vg.getBaseMAC(MACAddress(ap->mac));
Log::add("WiModLDC", "AP: " + ap->mac + " -> " + mac.asString());
addAP(MACAddress(mac), ape);
Log::add("WiModLDC", "AP added! given: " + ap->mac + " -> after VAP: " + mac.asString());
addAP(MACAddress(mac), ape, assertSafe);
}
}
}
/** make the given AP (and its parameters) known to the model */
void addAP(const MACAddress& accessPoint, const APEntry& params) {
/**
* make the given AP (and its parameters) known to the model
* usually txp,exp,waf are checked for a sane range. if you know what you are doing, set assertSafe to false!
*/
void addAP(const MACAddress& accessPoint, const APEntry& params, const bool assertSafe = true) {
// sanity check
Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]");
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]");
if (assertSafe) {
Assert::isBetween(params.waf, -99.0f, 0.0f, "WAF out of bounds [-99:0]");
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! VAP-Grouping issue?");
@@ -102,6 +118,14 @@ public:
}
/**
* make the given AP (and its parameters) known to the model
* usually txp,exp,waf are checked for a sane range. if you know what you are doing, set assertSafe to false!
*/
void addAP(const MACAddress& accessPoint, const Point3 pos_m, const float txp, const float exp, const float waf, const bool assertSafe = true) {
addAP(accessPoint, APEntry(pos_m, txp, exp, waf), assertSafe);
}
/** remove all added APs */
void clear() {
accessPoints.clear();
@@ -134,71 +158,71 @@ public:
const float rssiLOS = LogDistanceModel::distanceToRssi(params.txp, params.exp, distance_m);
// WAF loss (params.waf is a negative value!) -> WAF loss is also a negative value
const float wafLoss = params.waf * numCeilingsBetween(position_m, params.position_m);
//const float wafLoss = params.waf * ceilings.numCeilingsBetween(position_m, params.position_m);
const float wafLoss = params.waf * ceilings.numCeilingsBetweenFloat(position_m, params.position_m);
//const float wafLoss = params.waf * ceilings.numCeilingsBetweenLinearInt(position_m, params.position_m);
// combine
return rssiLOS + wafLoss;
const float res = rssiLOS + wafLoss;
// sanity check
Assert::isNotNaN(res, "detected NaN within WiFiModelLogDistCeiling::getRSSI()");
// ok
return res;
}
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
// set my type
dst->SetAttribute("type", "WiFiModelLogDistCeiling");
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);
}
for (const auto& it : accessPoints) {
const MACAddress& mac = it.first;
const APEntry& ape = it.second;
XMLElem* xap = doc->NewElement("ap");
xap->SetAttribute("mac", mac.asString().c_str());
xap->SetAttribute("px", ape.position_m.x);
xap->SetAttribute("py", ape.position_m.y);
xap->SetAttribute("pz", ape.position_m.z);
xap->SetAttribute("txp", ape.txp);
xap->SetAttribute("exp", ape.exp);
xap->SetAttribute("waf", ape.waf);
dst->InsertEndChild(xap);
}
return cnt;
for (const float atHeight_m : ceilings.getCeilings()) {
XMLElem* xceil = doc->NewElement("ceiling");
xceil->SetAttribute("atHeight", atHeight_m);
dst->InsertEndChild(xceil);
}
}
/** get the number of ceilings between z1 and z2 */
int numCeilingsBetween(const Point3 pos1, const Point3 pos2) const {
void readFromXML(XMLDoc* doc, XMLElem* src) override {
int cnt = 0;
const float zMin = std::min(pos1.z, pos2.z);
const float zMax = std::max(pos1.z, pos2.z);
// check type
if (std::string("WiFiModelLogDistCeiling") != src->Attribute("type")) {throw Exception("invalid model type");}
#ifdef WITH_ASSERTIONS
accessPoints.clear();
ceilings.clear();
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) "
XML_FOREACH_ELEM_NAMED("ap", xap, src) {
MACAddress mac = MACAddress(xap->Attribute("mac"));
APEntry ape(
Point3(xap->FloatAttribute("px"), xap->FloatAttribute("py"), xap->FloatAttribute("pz")),
xap->FloatAttribute("txp"),
xap->FloatAttribute("exp"),
xap->FloatAttribute("waf")
);
}
#endif
for (const float z : ceilingsAtHeight_m) {
if (zMin < z && zMax > z) {++cnt;}
accessPoints.insert( std::make_pair(mac, ape) );
}
return cnt;
XML_FOREACH_ELEM_NAMED("ceiling", xceil, src) {
const float atHeight_m = xceil->FloatAttribute("atHeight");
ceilings.addCeiling(atHeight_m);
}
}

View File

@@ -0,0 +1,167 @@
#ifndef WIFIMODELPERBBOX_H
#define WIFIMODELPERBBOX_H
#include "../AccessPoint.h"
#include "../../../geo/Point3.h"
#include "../../../geo/BBoxes3.h"
#include <vector>
#include "WiFiModelFactory.h"
/**
* FOR TESTING
*
* this model allows using a different sub-models
* identified by a bound-box to reduce the error
*/
class WiFiModelPerBBox : public WiFiModel {
struct ModelForBBoxes {
WiFiModel* mdl;
BBoxes3 bboxes;
/** ctor */
ModelForBBoxes(WiFiModel* mdl, BBoxes3 bboxes) : mdl(mdl), bboxes(bboxes) {;}
/** does this entry apply to the given position? */
bool matches(const Point3 pt) const {
if (bboxes.get().empty()) {throw Exception("no bbox(es) given for model!");}
return bboxes.contains(pt);
}
};
Floorplan::IndoorMap* map;
/** all contained models [one per bbox] */
std::vector<ModelForBBoxes> models;
public:
WiFiModelPerBBox(Floorplan::IndoorMap* map) : map(map) {
;
}
/** dtor */
virtual ~WiFiModelPerBBox() {
}
/** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const override {
// combine all submodels
std::vector<AccessPoint> res;
for (const ModelForBBoxes& sub : models) {
for (const AccessPoint& ap : sub.mdl->getAllAPs()) {
if (std::find(res.begin(), res.end(), ap) == res.end()) { // TODO use map instead?
res.push_back(ap);
}
}
}
return res;
}
void add(WiFiModel* mdl, const BBoxes3 bboxes) {
if (bboxes.get().empty()) {throw Exception("no bbox(es) given for model!");}
ModelForBBoxes mfb(mdl, bboxes);
models.push_back(mfb);
}
float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
for (const ModelForBBoxes& mfb : models) {
if (mfb.matches(position_m)) {return mfb.mdl->getRSSI(accessPoint, position_m);}
}
return -120;
}
void readFromXML(XMLDoc* doc, XMLElem* src) override {
// check type
if (std::string("WiFiModelPerBBox") != src->Attribute("type")) {throw Exception("invalid model type");}
models.clear();
// model factory [create models based on XMl content]
WiFiModelFactory fac(map);
// parse all contained models [one per floor]
XML_FOREACH_ELEM_NAMED("entry", xentry, src) {
// all bboxes
BBoxes3 bboxes;
XML_FOREACH_ELEM_NAMED("bbox", xbbox, xentry) {
const float x1 = xbbox->FloatAttribute("x1");
const float y1 = xbbox->FloatAttribute("y1");
const float z1 = xbbox->FloatAttribute("z1");
const float x2 = xbbox->FloatAttribute("x2");
const float y2 = xbbox->FloatAttribute("y2");
const float z2 = xbbox->FloatAttribute("z2");
const BBox3 bbox(Point3(x1,y1,z1), Point3(x2,y2,z2));
bboxes.add(bbox);
}
// node for the model
XMLElem* xmodel = xentry->FirstChildElement("model");
// let the factory instantiate the model
WiFiModel* mdl = fac.readFromXML(doc, xmodel);
// add
models.push_back(ModelForBBoxes(mdl, bboxes));
}
}
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
// set my type
dst->SetAttribute("type", "WiFiModelPerBBox");
for (const ModelForBBoxes& mfb : models) {
// all models
XMLElem* xentry = doc->NewElement("entry"); {
// each bbox
for (const BBox3& bbox : mfb.bboxes.get()) {
XMLElem* xbbox = doc->NewElement("bbox"); {
xbbox->SetAttribute("x1", bbox.getMin().x);
xbbox->SetAttribute("y1", bbox.getMin().y);
xbbox->SetAttribute("z1", bbox.getMin().z);
xbbox->SetAttribute("x2", bbox.getMax().x);
xbbox->SetAttribute("y2", bbox.getMax().y);
xbbox->SetAttribute("z2", bbox.getMax().z);
}; xentry->InsertFirstChild(xbbox);
}
// the corresponding model
XMLElem* xmodel = doc->NewElement("model"); {
mfb.mdl->writeToXML(doc, xmodel);
}; xentry->InsertEndChild(xmodel);
}; dst->InsertEndChild(xentry);
}
}
};
#endif // WIFIMODELPERBBOX_H

View File

@@ -0,0 +1,133 @@
#ifndef WIFIMODELPERFLOOR_H
#define WIFIMODELPERFLOOR_H
#include "../AccessPoint.h"
#include "../../../geo/Point3.h"
#include <vector>
#include "WiFiModelFactory.h"
/**
* FOR TESTING
*
* this model allows using a different sub-models for each floor to reduce the error
*/
class WiFiModelPerFloor : public WiFiModel {
struct ModelForFloor {
float fromZ;
float toZ;
WiFiModel* mdl;
/** ctor */
ModelForFloor(const float fromZ, const float toZ, WiFiModel* mdl) : fromZ(fromZ), toZ(toZ), mdl(mdl) {;}
/** does this entry apply to the given z-position? */
bool matches(const float z) const {
return (fromZ <= z && z < toZ);
}
};
Floorplan::IndoorMap* map;
/** all contained models [one per floor] */
std::vector<ModelForFloor> models;
public:
WiFiModelPerFloor(Floorplan::IndoorMap* map) : map(map) {
;
}
/** dtor */
virtual ~WiFiModelPerFloor() {
}
/** get a list of all APs known to the model */
std::vector<AccessPoint> getAllAPs() const override {
// combine all submodels
std::vector<AccessPoint> res;
for (const ModelForFloor& sub : models) {
for (const AccessPoint& ap : sub.mdl->getAllAPs()) {
if (std::find(res.begin(), res.end(), ap) == res.end()) { // TODO use map instead?
res.push_back(ap);
}
}
}
return res;
}
void add(WiFiModel* mdl, const Floorplan::Floor* floor) {
ModelForFloor mff(floor->atHeight, floor->atHeight+floor->height, mdl);
models.push_back(mff);
}
float getRSSI(const MACAddress& accessPoint, const Point3 position_m) const override {
for (const ModelForFloor& mff : models) {
if (mff.matches(position_m.z)) {return mff.mdl->getRSSI(accessPoint, position_m);}
}
return -120;
}
void readFromXML(XMLDoc* doc, XMLElem* src) override {
// check type
if (std::string("WiFiModelPerFloor") != src->Attribute("type")) {throw Exception("invalid model type");}
models.clear();
// model factory [create models based on XMl content]
WiFiModelFactory fac(map);
// parse all contained models [one per floor]
XML_FOREACH_ELEM_NAMED("floor", xfloor, src) {
// floor params
const float z1 = xfloor->FloatAttribute("z1");
const float z2 = xfloor->FloatAttribute("z2");
// node for the model
XMLElem* xmodel = xfloor->FirstChildElement("model");
// let the factory instantiate the model
WiFiModel* mdl = fac.readFromXML(doc, xmodel);
// add
models.push_back(ModelForFloor(z1, z2, mdl));
}
}
void writeToXML(XMLDoc* doc, XMLElem* dst) override {
// set my type
dst->SetAttribute("type", "WiFiModelPerFloor");
for (const ModelForFloor& mff : models) {
XMLElem* xfloor = doc->NewElement("floor"); {
xfloor->SetAttribute("z1", mff.fromZ);
xfloor->SetAttribute("z2", mff.toZ);
XMLElem* xmodel = doc->NewElement("model"); {
mff.mdl->writeToXML(doc, xmodel);
}; xfloor->InsertEndChild(xmodel);
}; dst->InsertEndChild(xfloor);
}
}
};
#endif // WIFIMODELPERFLOOR_H

View File

@@ -0,0 +1,8 @@
#ifndef WIFIMODELS_H
#define WIFIMODELS_H
#include "WiFiModel.h"
#include "WiFiModelFactory.h"
#include "WiFiModelFactoryImpl.h"
#endif // WIFIMODELS_H