From 0864f55a54df624e7893b12574b127dde58100b6 Mon Sep 17 00:00:00 2001 From: kazu Date: Wed, 24 May 2017 09:32:05 +0200 Subject: [PATCH] added support for XML reading/writing new serialization interfaces new helper methods new wifi models --- data/XMLload.h | 14 ++ data/XMLsave.h | 14 ++ data/XMLserialize.h | 44 ++++++ data/xml.h | 33 ++++ debug/PlotWifiMeasurements.h | 76 ++++++++++ geo/BBoxes3.h | 45 ++++++ math/MovingStdDevTS.h | 49 ++++++ math/distribution/Rectangular.h | 46 ++++++ sensors/activity/Activity.h | 20 +++ sensors/activity/ActivityDetector.h | 161 ++++++++++++++++++++ sensors/gps/GPSProbability.h | 65 ++++++++ sensors/offline/FilePlayer.h | 123 +++++++++++++++ sensors/radio/model/WiFiModelFactory.h | 32 ++++ sensors/radio/model/WiFiModelFactoryImpl.h | 42 ++++++ sensors/radio/model/WiFiModelPerBBox.h | 167 +++++++++++++++++++++ sensors/radio/model/WiFiModelPerFloor.h | 133 ++++++++++++++++ sensors/radio/model/WiFiModels.h | 8 + 17 files changed, 1072 insertions(+) create mode 100644 data/XMLload.h create mode 100644 data/XMLsave.h create mode 100644 data/XMLserialize.h create mode 100644 data/xml.h create mode 100644 debug/PlotWifiMeasurements.h create mode 100644 geo/BBoxes3.h create mode 100644 math/MovingStdDevTS.h create mode 100644 math/distribution/Rectangular.h create mode 100644 sensors/activity/Activity.h create mode 100644 sensors/activity/ActivityDetector.h create mode 100644 sensors/gps/GPSProbability.h create mode 100644 sensors/offline/FilePlayer.h create mode 100644 sensors/radio/model/WiFiModelFactory.h create mode 100644 sensors/radio/model/WiFiModelFactoryImpl.h create mode 100644 sensors/radio/model/WiFiModelPerBBox.h create mode 100644 sensors/radio/model/WiFiModelPerFloor.h create mode 100644 sensors/radio/model/WiFiModels.h diff --git a/data/XMLload.h b/data/XMLload.h new file mode 100644 index 0000000..f9dc5e4 --- /dev/null +++ b/data/XMLload.h @@ -0,0 +1,14 @@ +#ifndef XMLLOAD_H +#define XMLLOAD_H + +#include "xml.h" + +class XMLload { + +public: + + virtual void readFromXML(XMLDoc* doc, XMLElem* src) = 0; + +}; + +#endif // XMLLOAD_H diff --git a/data/XMLsave.h b/data/XMLsave.h new file mode 100644 index 0000000..f457c40 --- /dev/null +++ b/data/XMLsave.h @@ -0,0 +1,14 @@ +#ifndef XMLSAVE_H +#define XMLSAVE_H + +#include "xml.h" + +class XMLsave { + +public: + + virtual void writeToXML(XMLDoc* doc, XMLElem* dst) = 0; + +}; + +#endif // XMLSAVE_H diff --git a/data/XMLserialize.h b/data/XMLserialize.h new file mode 100644 index 0000000..7fc4817 --- /dev/null +++ b/data/XMLserialize.h @@ -0,0 +1,44 @@ +#ifndef XMLSERIALIZE_H +#define XMLSERIALIZE_H + +#include "XMLload.h" +#include "XMLsave.h" +#include "xml.h" + +class XMLserialize : public XMLload, public XMLsave { + +public: + + void loadXML(const std::string& file) { + + XMLDoc doc; + assertOK(doc.LoadFile(file.c_str()), doc, "error while loading file"); + XMLElem* root = doc.FirstChildElement("root"); + readFromXML(&doc, root); + + } + + void saveXML(const std::string& file) { + + XMLDoc doc; + XMLElem* root = doc.NewElement("root"); + doc.InsertFirstChild(root); + writeToXML(&doc, root); + assertOK(doc.SaveFile(file.c_str()), doc, "error while saving file"); + + } + +public: + + + static void assertOK(XMLErr res, XMLDoc& doc, const std::string& txt) { + if (res != tinyxml2::XMLError::XML_SUCCESS) { + const std::string err = doc.ErrorName(); + const std::string add = doc.GetErrorStr1(); + throw Exception(txt + ": " + err + " - " + add); + } + } + +}; + +#endif // XMLSERIALIZE_H diff --git a/data/xml.h b/data/xml.h new file mode 100644 index 0000000..b222148 --- /dev/null +++ b/data/xml.h @@ -0,0 +1,33 @@ +#ifndef DATA_XML_H +#define DATA_XML_H + +#include "../lib/tinyxml/tinyxml2.h" + +using XMLDoc = tinyxml2::XMLDocument; +using XMLErr = tinyxml2::XMLError; +using XMLNode = tinyxml2::XMLNode; +using XMLElem = tinyxml2::XMLElement; +using XMLAttr = tinyxml2::XMLAttribute; +using XMLText = tinyxml2::XMLText; + +#define XML_FOREACH_ATTR(attr, root) \ +for (const XMLAttr* attr = root->FirstAttribute(); attr != nullptr; attr = attr->Next()) + +#define XML_FOREACH_NODE(sub, root) \ +for (const XMLNode* sub = root->FirstChild(); sub != nullptr; sub = sub->NextSibling()) + +#define XML_FOREACH_ELEM(sub, root) \ +for (XMLElem* sub = (XMLElem*)root->FirstChild(); sub != nullptr; sub = (XMLElem*)sub->NextSibling()) + +#define XML_FOREACH_ELEM_NAMED(name, sub, root) \ +for (XMLElem* sub = root->FirstChildElement(name); sub != nullptr; sub = sub->NextSiblingElement(name)) + +#define XML_ID(node) node->Attribute("xml:id") +#define XML_WITH_ID(node) node->Attribute("with_id") + +#define XML_ASSERT_NODE_NAME(name, node) if (std::string(name) != std::string(node->Name())) {throw Exception("expected " + std::string(name) + " got " + node->Name());} + +#define XML_MANDATORY_ATTR(node, attr) (node->Attribute(attr) ? node->Attribute(attr) : throw Exception(std::string("missing XML attribute: ") + attr)); + + +#endif // DATA_XML_H diff --git a/debug/PlotWifiMeasurements.h b/debug/PlotWifiMeasurements.h new file mode 100644 index 0000000..541312f --- /dev/null +++ b/debug/PlotWifiMeasurements.h @@ -0,0 +1,76 @@ +#ifndef PLOTWIFIMEASUREMENTS_H +#define PLOTWIFIMEASUREMENTS_H + +#include +#include +#include + +#include "../sensors/radio/WiFiMeasurements.h" +#include "../sensors/radio/WiFiQualityAnalyzer.h" + +#include + +/** + * helper-class that plots incoming wifi measurements + * one line per AP + */ +class PlotWifiMeasurements { + + K::Gnuplot gp; + K::GnuplotPlot gplot; + K::GnuplotPlotElementLines lineQuality; + std::unordered_map lineForMac; + WiFiQualityAnalyzer quality; + +public: + + PlotWifiMeasurements(const std::string& labelX = "time (sec)", const std::string& labelY = "dBm") { + + gplot.getAxisX().setLabel(labelX); + gplot.getAxisY().setLabel(labelY); + + // quality indicator using the 2nd y axis + gplot.add(&lineQuality); lineQuality.getStroke().setWidth(2); lineQuality.setUseAxis(1, 2); + gplot.getAxisY2().setTicsVisible(true); + gplot.getAxisY2().setRange(K::GnuplotAxis::Range(0, 1)); + + } + + K::Gnuplot& getGP() { + return gp; + } + + K::GnuplotPlot& getPlot() { + return gplot; + } + + void add(const WiFiMeasurements& mes) { + + const Timestamp ts = mes.entries.front().getTimestamp(); + + for (const WiFiMeasurement& m : mes.entries) { + const auto& it = lineForMac.find(m.getAP().getMAC()); + if (it == lineForMac.end()) { + K::GnuplotPlotElementLines* line = new K::GnuplotPlotElementLines(); + line->setTitle(m.getAP().getMAC().asString()); + line->getStroke().getColor().setAuto(); + lineForMac[m.getAP().getMAC()] = line; + gplot.add(line); + } + const K::GnuplotPoint2 gp2(m.getTimestamp().sec(), m.getRSSI()); + lineForMac[m.getAP().getMAC()]->add(gp2); + } + + quality.add(mes); + lineQuality.add(K::GnuplotPoint2(ts.sec(), quality.getQuality())); + + } + + void plot() { + gp.draw(gplot); + gp.flush(); + } + +}; + +#endif // PLOTWIFIMEASUREMENTS_H diff --git a/geo/BBoxes3.h b/geo/BBoxes3.h new file mode 100644 index 0000000..b870861 --- /dev/null +++ b/geo/BBoxes3.h @@ -0,0 +1,45 @@ +#ifndef BBOXES3_H +#define BBOXES3_H + +#include "BBox3.h" +#include + +class BBoxes3 { + +private: + + /** all contained bboxes */ + std::vector bboxes; + +public: + + /** empty ctor */ + BBoxes3() {;} + + /** ctor with entries */ + BBoxes3(const std::vector& bboxes) : bboxes(bboxes) {;} + + + /** add the given bbox */ + void add(const BBox3& bbox) { + bboxes.push_back(bbox); + } + + /** get all contained bboxes */ + const std::vector& get() const { + return bboxes; + } + + /** does the compound contain the given point? */ + bool contains(const Point3& p) const { + for (const BBox3& bb : bboxes) { + if (bb.contains(p)) {return true;} + } + return false; + } + + + +}; + +#endif // BBOXES3_H diff --git a/math/MovingStdDevTS.h b/math/MovingStdDevTS.h new file mode 100644 index 0000000..4cfd439 --- /dev/null +++ b/math/MovingStdDevTS.h @@ -0,0 +1,49 @@ +#ifndef MOVINGSTDDEVTS_H +#define MOVINGSTDDEVTS_H + +#include "MovingAverageTS.h" + +/** + * moving stadnard-deviation using a given time-region + */ +template class MovingStdDevTS { + +private: + + MovingAverageTS avg; + MovingAverageTS avg2; + + +public: + + + /** ctor with the window-size to use */ + MovingStdDevTS(const Timestamp window, const T zeroElement) : avg(window, zeroElement), avg2(window, zeroElement) { + ; + } + + /** add a new entry */ + void add(const Timestamp ts, const T& data) { + + // E(x) + avg.add(ts, data); + + // E(x^2) + avg2.add(ts, data*data); + + } + + /** get the current std-dev */ + T get() const { + + const T e = avg.get(); + const T e2 = avg2.get(); + const T var = e2 - e*e; + return std::sqrt(var); + + } + + +}; + +#endif // MOVINGSTDDEVTS_H diff --git a/math/distribution/Rectangular.h b/math/distribution/Rectangular.h new file mode 100644 index 0000000..3f3a5fb --- /dev/null +++ b/math/distribution/Rectangular.h @@ -0,0 +1,46 @@ +#ifndef RECTANGULAR_H +#define RECTANGULAR_H + + +#include +#include +#include "../Random.h" +#include "../../Assertions.h" +#include "Normal.h" + +namespace Distribution { + + /** normal distribution */ + template class Rectangular { + + private: + + const T mu; + const T h; + const T width; + + public: + + /** ctor */ + Rectangular(const T mu, const T width) : mu(mu), h(1.0/width), width(width) { + + } + + /** get probability for the given value */ + T getProbability(const T val) const { + const T diff = std::abs(val - mu); + return (diff < width/2) ? (h) : (0.0); + } + + /** get the probability for the given value */ + static T getProbability(const T mu, const T width, const T val) { + Rectangular dist(mu, width); + return dist.getProbability(val); + } + + }; + +} + + +#endif // RECTANGULAR_H diff --git a/sensors/activity/Activity.h b/sensors/activity/Activity.h new file mode 100644 index 0000000..bcacc55 --- /dev/null +++ b/sensors/activity/Activity.h @@ -0,0 +1,20 @@ +#ifndef ACTIVITY_H +#define ACTIVITY_H + +enum class Activity { + + STANDING, + + WALKING, + + WALKING_UP, + + WALKING_DOWN, + + ELEVATOR_UP, + + ELEVATOR_DOWN, + +}; + +#endif // ACTIVITY_H diff --git a/sensors/activity/ActivityDetector.h b/sensors/activity/ActivityDetector.h new file mode 100644 index 0000000..e816064 --- /dev/null +++ b/sensors/activity/ActivityDetector.h @@ -0,0 +1,161 @@ +#ifndef ACTIVITYDETECTOR_H +#define ACTIVITYDETECTOR_H + + +#include "../imu/AccelerometerData.h" +#include "../pressure/BarometerData.h" + +#include "../../data/Timestamp.h" + +#include "../../Assertions.h" +#include "../../math/MovingAverageTS.h" +#include "../../math/MovingStdDevTS.h" +#include "../../data/HistoryTS.h" + +#include "../activity/Activity.h" + +//#define ACT_DET_DEBUG_PLOT + +#ifdef ACT_DET_DEBUG_PLOT +#include +#include +#include +#include +#include +#endif + + + +/** + * simple step detection based on accelerometer magnitude. + * magnitude > threshold? -> step! + * block for several msec until detecting the next one + */ +class ActivityDetector { + +private: + + MovingAverageTS avgLong; + MovingAverageTS avgShort; + + MovingStdDevTS stdDev; + MovingStdDevTS stdDev2; + + MovingAverageTS baroAvg; + HistoryTS baroHistory; + + Activity current; + + + +public: + +#ifdef ACT_DET_DEBUG_PLOT + K::Gnuplot gp; + K::GnuplotPlot gplot; + K::GnuplotPlotElementLines l1; + K::GnuplotPlotElementLines l2; +#endif + + /** ctor */ + ActivityDetector() : avgLong(Timestamp::fromMS(1500), 0), avgShort(Timestamp::fromMS(500), 0), + stdDev(Timestamp::fromMS(150), 0), stdDev2(Timestamp::fromMS(2000), 0), + baroAvg(Timestamp::fromMS(500), 0), baroHistory(Timestamp::fromMS(4000)) { + ; + + #ifdef ACT_DET_DEBUG_PLOT + gplot.add(&l1); + gplot.add(&l2); l2.getStroke().getColor().setHexStr("#ff0000"); + #endif + + } + + //int xx = 0; + + /** add barometer data */ + void add(const Timestamp ts, const BarometerData& baro) { + if (baro.isValid()) { + baroAvg.add(ts, baro.hPa); + const float avg = baroAvg.get(); + baroHistory.add(ts, avg); + //l1.add(K::GnuplotPoint2(xx, avg)); + update(); + } + } + + /** get the currently detected activity */ + Activity get() const { + return current; + } + + /** does the given data indicate a step? */ + void add(const Timestamp ts, const AccelerometerData& acc) { + + // update averages + avgLong.add(ts, acc.magnitude()); + avgShort.add(ts, acc.magnitude()); + stdDev.add(ts, acc.magnitude()); + stdDev2.add(ts, acc.magnitude()); + +// const float delta = std::abs(avgLong.get() - avgShort.get()); + +// static int x = 0; ++x; + + +// if (delta < 0.3) { +// return Activity::STANDING; +// } + +// if (avgLong.get() > 9.81+0.5) { +// return Activity::WALKING_UP; +// } else if (avgLong.get() < 9.81-0.5) { +// return Activity::WALKING_DOWN; +// } + +// return Activity::WALKING; + + } + + +private: + + /** estimate the current activity based on the sensor data */ + void update() { + + // delta in acceleration + const float delta_acc = std::abs(avgLong.get() - avgShort.get()); + + if (delta_acc < 0.015) { + current = Activity::STANDING; + return; + } + + // delta in pressure + const float delta_hPa = baroHistory.getMostRecent() - baroHistory.getOldest(); + + #ifdef ACT_DET_DEBUG_PLOT + l2.add(K::GnuplotPoint2(xx, delta_hPa)); + gp.draw(gplot); + gp.flush(); + ++xx; + #endif + + if (std::abs(delta_hPa) < 0.042) { + current = Activity::WALKING; + return; + } else if (delta_hPa > 0) { + current = Activity::WALKING_DOWN; + return; + } else { + current = Activity::WALKING_UP; + return; + } + + } + + + + +}; + +#endif // ACTIVITYDETECTOR_H diff --git a/sensors/gps/GPSProbability.h b/sensors/gps/GPSProbability.h new file mode 100644 index 0000000..194cbd7 --- /dev/null +++ b/sensors/gps/GPSProbability.h @@ -0,0 +1,65 @@ +#ifndef GPSPROBABILITY_H +#define GPSPROBABILITY_H + +#include "GPSData.h" +#include "../../geo/Point3.h" +#include "../../math/distribution/Region.h" +#include "../../geo/EarthMapping.h" + +class GPSProbability { + +private: + + /** convert between map and earth */ + const EarthMapping& em; + +public: + + /** ctor with the map<->earth translator */ + GPSProbability(const EarthMapping& em) : em(em) { + ; + } + + /** get the probability for residing at pos_m [in meter, map-coordinates] given some GPS measurement */ + double getProbability(const Point3 pos_m, const GPSData& d) const { + + // pad GPS? -> no gps eval + if (isBad(d)) {return 1.0;} + + // adjust accuracy [sometimes strange values are provided here!] + float accuracy = d.accuracy; + if (accuracy < 3.0) { + std::cout << "note: adjusting gps accuracy as '" << accuracy << "'' seems invalid" << std::endl; + accuracy = 3.0; + } + + // convert GPS to map coordinats + const Point3 gpsToMap_m = em.worldToMap(d.toEarthPos()); + + // distance between given point and GPS's estimation + const float dist = pos_m.getDistance(gpsToMap_m); + + // calculate probability + //const double prob = Distribution::Region::getProbability(0, d.accuracy, dist); + const double prob = Distribution::Normal::getProbability(0, accuracy, dist); + + // sanity checks + Assert::isNot0(prob, "gps probability is 0.0"); + Assert::isNotNaN(prob, "gps probability is NaN"); + + // done + return prob; + + } + +private: + + /** returns true if the given GPS reading is bad [inaccurate, invalid, ...] */ + static inline bool isBad(const GPSData& d) { + return (!d.isValid()) || (d.accuracy == 0) || (d.accuracy > 25); + } + + +}; + +#endif // GPSPROBABILITY_H diff --git a/sensors/offline/FilePlayer.h b/sensors/offline/FilePlayer.h new file mode 100644 index 0000000..7d5c6e2 --- /dev/null +++ b/sensors/offline/FilePlayer.h @@ -0,0 +1,123 @@ +#ifndef FILEPLAYER_H +#define FILEPLAYER_H + +#include "FileReader.h" +#include + +namespace Offline { + + /** + * this class can be used to "play" previously recorded [so-called "offline"] files. + * one can attach itself as listener and is informed whenever new sensor data is available. + * one may choose whether to use full-speed playback [as many events as possible] or + * live-speed playback with the same timing the values were recorded with + */ + class FilePlayer { + + private: + + FileReader* reader; + + bool realtime = false; + bool enabled; + std::thread thread; + + /** the listener to inform */ + Listener* listener = nullptr; + + public: + + /** empty ctor */ + FilePlayer() : reader(nullptr), listener(nullptr) { + ; + } + + /** ctor */ + FilePlayer(FileReader* reader, Listener* l) : reader(reader), listener(l) { + ; + } + + /** whether to use realtime playback or "as fast as possible" */ + void setRealtime(const bool rt) { + this->realtime = rt; + } + + /** set the offline-file-reader to use as data source */ + void setReader(FileReader* r) { + this->reader = r; + } + + /** set the event listener to inform */ + void setListener(Listener* l) { + this->listener = l; + } + + /** start playback */ + void start() { + + // sanity check + Assert::isNotNull(reader, "call FilePlayer::setReader() first"); + Assert::isNotNull(listener, "call FilePlayer::setListener() first"); + Assert::isFalse(reader->getEntries().empty(), "FileReader has no loaded entries for playback within the FilePlayer!"); + + enabled = true; + thread = std::thread(&FilePlayer::loop, this); + + } + + /** stop playback */ + void stop() { + enabled = false; + } + + /** wait for termination */ + void join() { + thread.join(); + } + + private: + + /** background loop */ + void loop() { + + // get all sensor events from the offline file + const std::vector events = reader->getEntries(); + + // process every event + for (const Entry& e : events) { + + // aborted? + if (!enabled) {break;} + + // timestamp + const Timestamp ts = Timestamp::fromMS(e.ts); + + // event index + const size_t idx = e.idx; + +#warning "some sensors todo:" + switch(e.type) { + case Sensor::ACC: listener->onAccelerometer(ts, reader->getAccelerometer()[idx].data); break; + case Sensor::BARO: listener->onBarometer(ts, reader->getBarometer()[idx].data); break; + case Sensor::BEACON: break;//listener->onBe(ts, reader->getBarometer()[idx].data); break; + case Sensor::COMPASS: listener->onCompass(ts, reader->getCompass()[idx].data); break; + case Sensor::GPS: listener->onGPS(ts, reader->getGPS()[idx].data); break; + case Sensor::GRAVITY: listener->onGravity(ts, reader->getGravity()[idx].data); break; + case Sensor::GYRO: listener->onGyroscope(ts, reader->getGyroscope()[idx].data); break; + case Sensor::LIN_ACC: break;//listener->on(ts, reader->getBarometer()[idx].data); break; + case Sensor::WIFI: listener->onWiFi(ts, reader->getWiFiGroupedByTime()[idx].data); break; + default: throw Exception("code error. found not-yet-implemented sensor"); + } + + } + + // done + enabled = false; + + } + + }; + +} + +#endif // FILEPLAYER_H diff --git a/sensors/radio/model/WiFiModelFactory.h b/sensors/radio/model/WiFiModelFactory.h new file mode 100644 index 0000000..6bcf94a --- /dev/null +++ b/sensors/radio/model/WiFiModelFactory.h @@ -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 diff --git a/sensors/radio/model/WiFiModelFactoryImpl.h b/sensors/radio/model/WiFiModelFactoryImpl.h new file mode 100644 index 0000000..d318146 --- /dev/null +++ b/sensors/radio/model/WiFiModelFactoryImpl.h @@ -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 diff --git a/sensors/radio/model/WiFiModelPerBBox.h b/sensors/radio/model/WiFiModelPerBBox.h new file mode 100644 index 0000000..1ae5ffc --- /dev/null +++ b/sensors/radio/model/WiFiModelPerBBox.h @@ -0,0 +1,167 @@ +#ifndef WIFIMODELPERBBOX_H +#define WIFIMODELPERBBOX_H + + +#include "../AccessPoint.h" +#include "../../../geo/Point3.h" +#include "../../../geo/BBoxes3.h" +#include + +#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 models; + +public: + + WiFiModelPerBBox(Floorplan::IndoorMap* map) : map(map) { + ; + } + + /** dtor */ + virtual ~WiFiModelPerBBox() { + + } + + + /** get a list of all APs known to the model */ + std::vector getAllAPs() const override { + + // combine all submodels + std::vector 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 diff --git a/sensors/radio/model/WiFiModelPerFloor.h b/sensors/radio/model/WiFiModelPerFloor.h new file mode 100644 index 0000000..f14dd7e --- /dev/null +++ b/sensors/radio/model/WiFiModelPerFloor.h @@ -0,0 +1,133 @@ +#ifndef WIFIMODELPERFLOOR_H +#define WIFIMODELPERFLOOR_H + +#include "../AccessPoint.h" +#include "../../../geo/Point3.h" +#include + +#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 models; + +public: + + WiFiModelPerFloor(Floorplan::IndoorMap* map) : map(map) { + ; + } + + /** dtor */ + virtual ~WiFiModelPerFloor() { + + } + + + /** get a list of all APs known to the model */ + std::vector getAllAPs() const override { + + // combine all submodels + std::vector 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 diff --git a/sensors/radio/model/WiFiModels.h b/sensors/radio/model/WiFiModels.h new file mode 100644 index 0000000..6dd00c9 --- /dev/null +++ b/sensors/radio/model/WiFiModels.h @@ -0,0 +1,8 @@ +#ifndef WIFIMODELS_H +#define WIFIMODELS_H + +#include "WiFiModel.h" +#include "WiFiModelFactory.h" +#include "WiFiModelFactoryImpl.h" + +#endif // WIFIMODELS_H