added support for XML reading/writing
new serialization interfaces new helper methods new wifi models
This commit is contained in:
14
data/XMLload.h
Normal file
14
data/XMLload.h
Normal file
@@ -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
|
||||||
14
data/XMLsave.h
Normal file
14
data/XMLsave.h
Normal file
@@ -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
|
||||||
44
data/XMLserialize.h
Normal file
44
data/XMLserialize.h
Normal file
@@ -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
|
||||||
33
data/xml.h
Normal file
33
data/xml.h
Normal file
@@ -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
|
||||||
76
debug/PlotWifiMeasurements.h
Normal file
76
debug/PlotWifiMeasurements.h
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
#ifndef PLOTWIFIMEASUREMENTS_H
|
||||||
|
#define PLOTWIFIMEASUREMENTS_H
|
||||||
|
|
||||||
|
#include <KLib/misc/gnuplot/Gnuplot.h>
|
||||||
|
#include <KLib/misc/gnuplot/GnuplotPlot.h>
|
||||||
|
#include <KLib/misc/gnuplot/GnuplotPlotElementLines.h>
|
||||||
|
|
||||||
|
#include "../sensors/radio/WiFiMeasurements.h"
|
||||||
|
#include "../sensors/radio/WiFiQualityAnalyzer.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<MACAddress, K::GnuplotPlotElementLines*> 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
|
||||||
45
geo/BBoxes3.h
Normal file
45
geo/BBoxes3.h
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#ifndef BBOXES3_H
|
||||||
|
#define BBOXES3_H
|
||||||
|
|
||||||
|
#include "BBox3.h"
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class BBoxes3 {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** all contained bboxes */
|
||||||
|
std::vector<BBox3> bboxes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** empty ctor */
|
||||||
|
BBoxes3() {;}
|
||||||
|
|
||||||
|
/** ctor with entries */
|
||||||
|
BBoxes3(const std::vector<BBox3>& bboxes) : bboxes(bboxes) {;}
|
||||||
|
|
||||||
|
|
||||||
|
/** add the given bbox */
|
||||||
|
void add(const BBox3& bbox) {
|
||||||
|
bboxes.push_back(bbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** get all contained bboxes */
|
||||||
|
const std::vector<BBox3>& 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
|
||||||
49
math/MovingStdDevTS.h
Normal file
49
math/MovingStdDevTS.h
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef MOVINGSTDDEVTS_H
|
||||||
|
#define MOVINGSTDDEVTS_H
|
||||||
|
|
||||||
|
#include "MovingAverageTS.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* moving stadnard-deviation using a given time-region
|
||||||
|
*/
|
||||||
|
template <typename T> class MovingStdDevTS {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
MovingAverageTS<T> avg;
|
||||||
|
MovingAverageTS<T> 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
|
||||||
46
math/distribution/Rectangular.h
Normal file
46
math/distribution/Rectangular.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef RECTANGULAR_H
|
||||||
|
#define RECTANGULAR_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <random>
|
||||||
|
#include "../Random.h"
|
||||||
|
#include "../../Assertions.h"
|
||||||
|
#include "Normal.h"
|
||||||
|
|
||||||
|
namespace Distribution {
|
||||||
|
|
||||||
|
/** normal distribution */
|
||||||
|
template <typename T> 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<T> dist(mu, width);
|
||||||
|
return dist.getProbability(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif // RECTANGULAR_H
|
||||||
20
sensors/activity/Activity.h
Normal file
20
sensors/activity/Activity.h
Normal file
@@ -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
|
||||||
161
sensors/activity/ActivityDetector.h
Normal file
161
sensors/activity/ActivityDetector.h
Normal file
@@ -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 <KLib/misc/gnuplot/Gnuplot.h>
|
||||||
|
#include <KLib/misc/gnuplot/GnuplotSplot.h>
|
||||||
|
#include <KLib/misc/gnuplot/GnuplotSplotElementLines.h>
|
||||||
|
#include <KLib/misc/gnuplot/GnuplotPlot.h>
|
||||||
|
#include <KLib/misc/gnuplot/GnuplotPlotElementLines.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* simple step detection based on accelerometer magnitude.
|
||||||
|
* magnitude > threshold? -> step!
|
||||||
|
* block for several msec until detecting the next one
|
||||||
|
*/
|
||||||
|
class ActivityDetector {
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
MovingAverageTS<float> avgLong;
|
||||||
|
MovingAverageTS<float> avgShort;
|
||||||
|
|
||||||
|
MovingStdDevTS<float> stdDev;
|
||||||
|
MovingStdDevTS<float> stdDev2;
|
||||||
|
|
||||||
|
MovingAverageTS<float> baroAvg;
|
||||||
|
HistoryTS<float> 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
|
||||||
65
sensors/gps/GPSProbability.h
Normal file
65
sensors/gps/GPSProbability.h
Normal file
@@ -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<double>::getProbability(0, d.accuracy, dist);
|
||||||
|
const double prob = Distribution::Normal<double>::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
|
||||||
123
sensors/offline/FilePlayer.h
Normal file
123
sensors/offline/FilePlayer.h
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#ifndef FILEPLAYER_H
|
||||||
|
#define FILEPLAYER_H
|
||||||
|
|
||||||
|
#include "FileReader.h"
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
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<Entry> 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
|
||||||
32
sensors/radio/model/WiFiModelFactory.h
Normal file
32
sensors/radio/model/WiFiModelFactory.h
Normal 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
|
||||||
42
sensors/radio/model/WiFiModelFactoryImpl.h
Normal file
42
sensors/radio/model/WiFiModelFactoryImpl.h
Normal 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
|
||||||
167
sensors/radio/model/WiFiModelPerBBox.h
Normal file
167
sensors/radio/model/WiFiModelPerBBox.h
Normal 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
|
||||||
133
sensors/radio/model/WiFiModelPerFloor.h
Normal file
133
sensors/radio/model/WiFiModelPerFloor.h
Normal 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
|
||||||
8
sensors/radio/model/WiFiModels.h
Normal file
8
sensors/radio/model/WiFiModels.h
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef WIFIMODELS_H
|
||||||
|
#define WIFIMODELS_H
|
||||||
|
|
||||||
|
#include "WiFiModel.h"
|
||||||
|
#include "WiFiModelFactory.h"
|
||||||
|
#include "WiFiModelFactoryImpl.h"
|
||||||
|
|
||||||
|
#endif // WIFIMODELS_H
|
||||||
Reference in New Issue
Block a user