From 06e0e0a5aae6f23afbfc9b903344e7c37f0c3e97 Mon Sep 17 00:00:00 2001 From: kazu Date: Mon, 20 Mar 2017 11:19:57 +0100 Subject: [PATCH] fixed some potential issues with MAC addresses added corresponding test-cases switched to newer version of tinyxml due to some issues adjusted affected code-parts accordingly for better re-use, moved ceiling-calculation to a new class some minor fixes new helper methods worked on wifi-opt --- floorplan/v2/FloorplanCeilings.h | 112 ++++ floorplan/v2/FloorplanHelper.h | 17 + floorplan/v2/FloorplanReader.h | 27 +- geo/EarthPos.h | 2 +- geo/Point2.h | 1 + lib/tinyxml/tinyxml2.cpp | 519 ++++++++++++------ lib/tinyxml/tinyxml2.h | 359 ++++++++---- main.cpp | 2 +- sensors/MACAddress.h | 26 +- sensors/radio/VAPGrouper.h | 7 + sensors/radio/model/WiFiModelLogDistCeiling.h | 75 +-- sensors/radio/setup/WiFiFingerprint.h | 10 +- sensors/radio/setup/WiFiFingerprints.h | 125 +++++ sensors/radio/setup/WiFiOptimizer.h | 262 ++------- .../radio/setup/WiFiOptimizerLogDistCeiling.h | 341 ++++++++++++ sensors/radio/setup/WiFiOptimizerStructs.h | 55 ++ tests/floorplan/TestFloorplanCeilings.cpp | 85 +++ tests/sensors/TestMAC.cpp | 48 ++ .../radio/TestLogDistanceCeilingModel.cpp | 75 --- tests/sensors/radio/TestWiFiOptimizer.cpp | 14 +- 20 files changed, 1486 insertions(+), 676 deletions(-) create mode 100644 floorplan/v2/FloorplanCeilings.h create mode 100644 sensors/radio/setup/WiFiFingerprints.h create mode 100644 sensors/radio/setup/WiFiOptimizerLogDistCeiling.h create mode 100644 sensors/radio/setup/WiFiOptimizerStructs.h create mode 100644 tests/floorplan/TestFloorplanCeilings.cpp diff --git a/floorplan/v2/FloorplanCeilings.h b/floorplan/v2/FloorplanCeilings.h new file mode 100644 index 0000000..887ed8e --- /dev/null +++ b/floorplan/v2/FloorplanCeilings.h @@ -0,0 +1,112 @@ +#ifndef FLOORPLANCEILINGS_H +#define FLOORPLANCEILINGS_H + +#include "Floorplan.h" + +namespace Floorplan { + + /** + * helper-class for floorplan ceilings + * e.g. to determine the number of ceilings between two given positions + */ + class Ceilings { + + private: + + /** position (height) of all ceilings (in meter) */ + std::vector ceilingsAtHeight_m; + + public: + + /** empty ctor */ + Ceilings() { + ; + } + + /** ctor with the map to work with */ + Ceilings(const IndoorMap* map) { + + // sanity checks + Assert::isTrue(map->floors.size() >= 1, "map has no floors?!"); + + // get position of all ceilings + for (Floorplan::Floor* f : map->floors) { + + const float h1 = f->atHeight; + const float h2 = f->atHeight + f->height; + + if (std::find(ceilingsAtHeight_m.begin(), ceilingsAtHeight_m.end(), h1) == ceilingsAtHeight_m.end()) { + ceilingsAtHeight_m.push_back(h1); + } + if (std::find(ceilingsAtHeight_m.begin(), ceilingsAtHeight_m.end(), h2) == ceilingsAtHeight_m.end()) { + ceilingsAtHeight_m.push_back(h2); + } + + } + + } + + /** get the number of ceilings between z1 and z2 */ + float numCeilingsBetweenFloat(const Point3 pos1, const Point3 pos2) const { + + // sanity checks + Assert::isNot0(ceilingsAtHeight_m.size(), "no ceilings available for testing! incorrect map?"); + + 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 { + + // sanity checks + Assert::isNot0(ceilingsAtHeight_m.size(), "no ceilings available for testing! incorrect map?"); + + // find min and max height given the to-be-compared points + 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 Floorplan::Ceilings::numCeilingsBetween 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 + + int cnt = 0; + for (const float z : ceilingsAtHeight_m) { + if (zMin < z && zMax > z) {++cnt;} + } + + return cnt; + + } + + }; + +} + +#endif // FLOORPLANCEILINGS_H diff --git a/floorplan/v2/FloorplanHelper.h b/floorplan/v2/FloorplanHelper.h index a62daa9..473202c 100644 --- a/floorplan/v2/FloorplanHelper.h +++ b/floorplan/v2/FloorplanHelper.h @@ -7,11 +7,28 @@ #include "Floorplan.h" +#include "../../sensors/MACAddress.h" + /** * helper methods for the floorplan */ class FloorplanHelper { +public: + + + /** get the AP for the given MAC [if available] */ + static std::pair getAP(const Floorplan::IndoorMap* map, const MACAddress& mac) { + for (Floorplan::Floor* f : map->floors) { + for (Floorplan::AccessPoint* ap : f->accesspoints) { + if (MACAddress(ap->mac) == mac) { + return std::make_pair(ap, f); + } + } + } + return std::make_pair(nullptr, nullptr); + } + public: /** align all floorplan values to the given grid size. needed for the grid factory */ diff --git a/floorplan/v2/FloorplanReader.h b/floorplan/v2/FloorplanReader.h index cb29bcc..5d19af6 100644 --- a/floorplan/v2/FloorplanReader.h +++ b/floorplan/v2/FloorplanReader.h @@ -35,11 +35,12 @@ namespace Floorplan { if (res != tinyxml2::XMLError::XML_SUCCESS) { throw Exception( std::string() + "error while loading XML " + file + "\n" + - ((doc.GetErrorStr1()) ? (doc.GetErrorStr1()) : ("")) + "\n" + - ((doc.GetErrorStr2()) ? (doc.GetErrorStr2()) : ("")) + ((doc.GetErrorStr1()) ? (doc.GetErrorStr1()) : ("")) + "\n" + + ((doc.GetErrorStr2()) ? (doc.GetErrorStr2()) : ("")) ); } - return parse(doc); + IndoorMap* map = parse(doc); + return map; } /** read an IndoorMap from the given XMl-string */ @@ -51,16 +52,17 @@ namespace Floorplan { if (res != tinyxml2::XMLError::XML_SUCCESS) { throw Exception( std::string() + "error while parsing XML\n" + - ((doc.GetErrorStr1()) ? (doc.GetErrorStr1()) : ("")) + "\n" + - ((doc.GetErrorStr2()) ? (doc.GetErrorStr2()) : ("")) + ((doc.GetErrorStr1()) ? (doc.GetErrorStr1()) : ("")) + "\n" + + ((doc.GetErrorStr2()) ? (doc.GetErrorStr2()) : ("")) ); } - return parse(doc); + IndoorMap* map = parse(doc); + return map; } private: - #define FOREACH_NODE(out, in) for( const XMLElem* out = (XMLElem*) in->FirstChild(); out; out = (XMLElem*) out->NextSibling() ) + #define FOREACH_NODE(out, in) for( const XMLElem* out = in->FirstChildElement(); out; out = out->NextSiblingElement() ) static void assertNode(const std::string& node, const XMLElem* el) { std::string err = std::string("unexpected node '") + el->Name() + "' expected '" + node + "'"; @@ -69,7 +71,7 @@ namespace Floorplan { /** parse the complete document */ static IndoorMap* parse(tinyxml2::XMLDocument& doc) { - return parseMap((XMLElem*)doc.FirstChild()); + return parseMap(doc.FirstChildElement()); } /** parse the node */ @@ -266,7 +268,9 @@ namespace Floorplan { const XMLElem* sub = n->FirstChildElement(); while(sub) { // abc - elem->add(sub->Attribute("key"), sub->FirstChild()->Value()); + const std::string key = sub->Attribute("key"); + const std::string val = sub->GetText(); + elem->add(key, val); sub = sub->NextSiblingElement(); } return elem; @@ -402,7 +406,7 @@ namespace Floorplan { FloorOutline outline; FOREACH_NODE(n, el) { if (std::string("polygon") == n->Name()) { - outline.push_back(parseFloorPolygon(n)); // TODO + outline.push_back(parseFloorPolygon(n)); } } return outline; @@ -427,6 +431,9 @@ namespace Floorplan { poly.points.push_back(p2); } } + if (poly.points.size() < 4 || poly.points.size() > 1024) { + throw Exception("detected invalid outline-polygon during XML parsing"); + } return poly; } diff --git a/geo/EarthPos.h b/geo/EarthPos.h index bb6ccc0..d038fc1 100644 --- a/geo/EarthPos.h +++ b/geo/EarthPos.h @@ -12,7 +12,7 @@ struct EarthPos { float height; /** ctor with values */ - EarthPos(const double lat, const double lon, const float height) : lon(lon), lat(lat), height(height) { + EarthPos(const double lat, const double lon, const float height) : lat(lat), lon(lon), height(height) { ; } diff --git a/geo/Point2.h b/geo/Point2.h index 2d58178..3b19fd7 100644 --- a/geo/Point2.h +++ b/geo/Point2.h @@ -2,6 +2,7 @@ #define POINT2_H #include +#include /** * 2D Point diff --git a/lib/tinyxml/tinyxml2.cpp b/lib/tinyxml/tinyxml2.cpp index df49d0d..87c1a99 100644 --- a/lib/tinyxml/tinyxml2.cpp +++ b/lib/tinyxml/tinyxml2.cpp @@ -24,7 +24,7 @@ distribution. #include "tinyxml2.h" #include // yes, this one new style header, is in the Android SDK. -#if defined(ANDROID_NDK) || defined(__QNXNTO__) +#if defined(ANDROID_NDK) || defined(__BORLANDC__) || defined(__QNXNTO__) # include # include #else @@ -149,6 +149,7 @@ void StrPair::TransferTo( StrPair* other ) // This in effect implements the assignment operator by "moving" // ownership (as in auto_ptr). + TIXMLASSERT( other != 0 ); TIXMLASSERT( other->_flags == 0 ); TIXMLASSERT( other->_start == 0 ); TIXMLASSERT( other->_end == 0 ); @@ -188,9 +189,11 @@ void StrPair::SetStr( const char* str, int flags ) } -char* StrPair::ParseText( char* p, const char* endTag, int strFlags ) +char* StrPair::ParseText( char* p, const char* endTag, int strFlags, int* curLineNumPtr ) { + TIXMLASSERT( p ); TIXMLASSERT( endTag && *endTag ); + TIXMLASSERT(curLineNumPtr); char* start = p; char endChar = *endTag; @@ -201,8 +204,11 @@ char* StrPair::ParseText( char* p, const char* endTag, int strFlags ) if ( *p == endChar && strncmp( p, endTag, length ) == 0 ) { Set( start, p, strFlags ); return p + length; + } else if (*p == '\n') { + ++(*curLineNumPtr); } ++p; + TIXMLASSERT( p ); } return 0; } @@ -233,15 +239,15 @@ void StrPair::CollapseWhitespace() // Adjusting _start would cause undefined behavior on delete[] TIXMLASSERT( ( _flags & NEEDS_DELETE ) == 0 ); // Trim leading space. - _start = XMLUtil::SkipWhiteSpace( _start ); + _start = XMLUtil::SkipWhiteSpace( _start, 0 ); if ( *_start ) { - char* p = _start; // the read pointer + const char* p = _start; // the read pointer char* q = _start; // the write pointer while( *p ) { if ( XMLUtil::IsWhiteSpace( *p )) { - p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::SkipWhiteSpace( p, 0 ); if ( *p == 0 ) { break; // don't write to q; this trims the trailing space. } @@ -266,7 +272,7 @@ const char* StrPair::GetStr() _flags ^= NEEDS_FLUSH; if ( _flags ) { - char* p = _start; // the read pointer + const char* p = _start; // the read pointer char* q = _start; // the write pointer while( p < _end ) { @@ -280,7 +286,8 @@ const char* StrPair::GetStr() else { ++p; } - *q++ = LF; + *q = LF; + ++q; } else if ( (_flags & NEEDS_NEWLINE_NORMALIZATION) && *p == LF ) { if ( *(p+1) == CR ) { @@ -289,7 +296,8 @@ const char* StrPair::GetStr() else { ++p; } - *q++ = LF; + *q = LF; + ++q; } else if ( (_flags & NEEDS_ENTITY_PROCESSING) && *p == '&' ) { // Entities handled by tinyXML2: @@ -360,6 +368,19 @@ const char* StrPair::GetStr() // --------- XMLUtil ----------- // +const char* XMLUtil::writeBoolTrue = "true"; +const char* XMLUtil::writeBoolFalse = "false"; + +void XMLUtil::SetBoolSerialization(const char* writeTrue, const char* writeFalse) +{ + static const char* defTrue = "true"; + static const char* defFalse = "false"; + + writeBoolTrue = (writeTrue) ? writeTrue : defTrue; + writeBoolFalse = (writeFalse) ? writeFalse : defFalse; +} + + const char* XMLUtil::ReadBOM( const char* p, bool* bom ) { TIXMLASSERT( p ); @@ -471,7 +492,7 @@ const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) else { return 0; } - TIXMLASSERT( digit >= 0 && digit < 16); + TIXMLASSERT( digit < 16 ); TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); const unsigned int digitScaled = mult * digit; TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); @@ -501,7 +522,7 @@ const char* XMLUtil::GetCharacterRef( const char* p, char* value, int* length ) while ( *q != '#' ) { if ( *q >= '0' && *q <= '9' ) { const unsigned int digit = *q - '0'; - TIXMLASSERT( digit >= 0 && digit < 10); + TIXMLASSERT( digit < 10 ); TIXMLASSERT( digit == 0 || mult <= UINT_MAX / digit ); const unsigned int digitScaled = mult * digit; TIXMLASSERT( ucs <= ULONG_MAX - digitScaled ); @@ -537,7 +558,7 @@ void XMLUtil::ToStr( unsigned v, char* buffer, int bufferSize ) void XMLUtil::ToStr( bool v, char* buffer, int bufferSize ) { - TIXML_SNPRINTF( buffer, bufferSize, "%d", v ? 1 : 0 ); + TIXML_SNPRINTF( buffer, bufferSize, "%s", v ? writeBoolTrue : writeBoolFalse); } /* @@ -556,6 +577,13 @@ void XMLUtil::ToStr( double v, char* buffer, int bufferSize ) } +void XMLUtil::ToStr(int64_t v, char* buffer, int bufferSize) +{ + // horrible syntax trick to make the compiler happy about %lld + TIXML_SNPRINTF(buffer, bufferSize, "%lld", (long long)v); +} + + bool XMLUtil::ToInt( const char* str, int* value ) { if ( TIXML_SSCANF( str, "%d", value ) == 1 ) { @@ -599,6 +627,7 @@ bool XMLUtil::ToFloat( const char* str, float* value ) return false; } + bool XMLUtil::ToDouble( const char* str, double* value ) { if ( TIXML_SSCANF( str, "%lf", value ) == 1 ) { @@ -608,12 +637,24 @@ bool XMLUtil::ToDouble( const char* str, double* value ) } +bool XMLUtil::ToInt64(const char* str, int64_t* value) +{ + long long v = 0; // horrible syntax trick to make the compiler happy about %lld + if (TIXML_SSCANF(str, "%lld", &v) == 1) { + *value = (int64_t)v; + return true; + } + return false; +} + + char* XMLDocument::Identify( char* p, XMLNode** node ) { TIXMLASSERT( node ); TIXMLASSERT( p ); char* const start = p; - p = XMLUtil::SkipWhiteSpace( p ); + int const startLine = _parseCurLineNum; + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); if( !*p ) { *node = 0; TIXMLASSERT( p ); @@ -637,42 +678,37 @@ char* XMLDocument::Identify( char* p, XMLNode** node ) TIXMLASSERT( sizeof( XMLComment ) == sizeof( XMLDeclaration ) ); // use same memory pool XMLNode* returnNode = 0; if ( XMLUtil::StringEqual( p, xmlHeader, xmlHeaderLen ) ) { - TIXMLASSERT( sizeof( XMLDeclaration ) == _commentPool.ItemSize() ); - returnNode = new (_commentPool.Alloc()) XMLDeclaration( this ); - returnNode->_memPool = &_commentPool; + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; p += xmlHeaderLen; } else if ( XMLUtil::StringEqual( p, commentHeader, commentHeaderLen ) ) { - TIXMLASSERT( sizeof( XMLComment ) == _commentPool.ItemSize() ); - returnNode = new (_commentPool.Alloc()) XMLComment( this ); - returnNode->_memPool = &_commentPool; + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; p += commentHeaderLen; } else if ( XMLUtil::StringEqual( p, cdataHeader, cdataHeaderLen ) ) { - TIXMLASSERT( sizeof( XMLText ) == _textPool.ItemSize() ); - XMLText* text = new (_textPool.Alloc()) XMLText( this ); + XMLText* text = CreateUnlinkedNode( _textPool ); returnNode = text; - returnNode->_memPool = &_textPool; + returnNode->_parseLineNum = _parseCurLineNum; p += cdataHeaderLen; text->SetCData( true ); } else if ( XMLUtil::StringEqual( p, dtdHeader, dtdHeaderLen ) ) { - TIXMLASSERT( sizeof( XMLUnknown ) == _commentPool.ItemSize() ); - returnNode = new (_commentPool.Alloc()) XMLUnknown( this ); - returnNode->_memPool = &_commentPool; + returnNode = CreateUnlinkedNode( _commentPool ); + returnNode->_parseLineNum = _parseCurLineNum; p += dtdHeaderLen; } else if ( XMLUtil::StringEqual( p, elementHeader, elementHeaderLen ) ) { - TIXMLASSERT( sizeof( XMLElement ) == _elementPool.ItemSize() ); - returnNode = new (_elementPool.Alloc()) XMLElement( this ); - returnNode->_memPool = &_elementPool; + returnNode = CreateUnlinkedNode( _elementPool ); + returnNode->_parseLineNum = _parseCurLineNum; p += elementHeaderLen; } else { - TIXMLASSERT( sizeof( XMLText ) == _textPool.ItemSize() ); - returnNode = new (_textPool.Alloc()) XMLText( this ); - returnNode->_memPool = &_textPool; + returnNode = CreateUnlinkedNode( _textPool ); + returnNode->_parseLineNum = _parseCurLineNum; // Report line of first non-whitespace character p = start; // Back it up, all the text counts. + _parseCurLineNum = startLine; } TIXMLASSERT( returnNode ); @@ -701,8 +737,10 @@ bool XMLDocument::Accept( XMLVisitor* visitor ) const XMLNode::XMLNode( XMLDocument* doc ) : _document( doc ), _parent( 0 ), + _parseLineNum( 0 ), _firstChild( 0 ), _lastChild( 0 ), _prev( 0 ), _next( 0 ), + _userData( 0 ), _memPool( 0 ) { } @@ -718,7 +756,7 @@ XMLNode::~XMLNode() const char* XMLNode::Value() const { - // Catch an edge case: XMLDocuments don't have a a Value. Carefully return nullptr. + // Edge case: XMLDocuments don't have a Value. Return null. if ( this->ToDocument() ) return 0; return _value.GetStr(); @@ -739,11 +777,7 @@ void XMLNode::DeleteChildren() { while( _firstChild ) { TIXMLASSERT( _lastChild ); - TIXMLASSERT( _firstChild->_document == _document ); - XMLNode* node = _firstChild; - Unlink( node ); - - DeleteNode( node ); + DeleteChild( _firstChild ); } _firstChild = _lastChild = 0; } @@ -876,11 +910,9 @@ XMLNode* XMLNode::InsertAfterChild( XMLNode* afterThis, XMLNode* addThis ) const XMLElement* XMLNode::FirstChildElement( const char* name ) const { for( const XMLNode* node = _firstChild; node; node = node->_next ) { - const XMLElement* element = node->ToElement(); + const XMLElement* element = node->ToElementWithName( name ); if ( element ) { - if ( !name || XMLUtil::StringEqual( element->Name(), name ) ) { - return element; - } + return element; } } return 0; @@ -890,11 +922,9 @@ const XMLElement* XMLNode::FirstChildElement( const char* name ) const const XMLElement* XMLNode::LastChildElement( const char* name ) const { for( const XMLNode* node = _lastChild; node; node = node->_prev ) { - const XMLElement* element = node->ToElement(); + const XMLElement* element = node->ToElementWithName( name ); if ( element ) { - if ( !name || XMLUtil::StringEqual( element->Name(), name ) ) { - return element; - } + return element; } } return 0; @@ -904,9 +934,8 @@ const XMLElement* XMLNode::LastChildElement( const char* name ) const const XMLElement* XMLNode::NextSiblingElement( const char* name ) const { for( const XMLNode* node = _next; node; node = node->_next ) { - const XMLElement* element = node->ToElement(); - if ( element - && (!name || XMLUtil::StringEqual( name, element->Name() ))) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { return element; } } @@ -917,9 +946,8 @@ const XMLElement* XMLNode::NextSiblingElement( const char* name ) const const XMLElement* XMLNode::PreviousSiblingElement( const char* name ) const { for( const XMLNode* node = _prev; node; node = node->_prev ) { - const XMLElement* element = node->ToElement(); - if ( element - && (!name || XMLUtil::StringEqual( name, element->Name() ))) { + const XMLElement* element = node->ToElementWithName( name ); + if ( element ) { return element; } } @@ -927,7 +955,7 @@ const XMLElement* XMLNode::PreviousSiblingElement( const char* name ) const } -char* XMLNode::ParseDeep( char* p, StrPair* parentEnd ) +char* XMLNode::ParseDeep( char* p, StrPair* parentEnd, int* curLineNumPtr ) { // This is a recursive method, but thinking about it "at the current level" // it is a pretty simple flat list: @@ -950,29 +978,42 @@ char* XMLNode::ParseDeep( char* p, StrPair* parentEnd ) XMLNode* node = 0; p = _document->Identify( p, &node ); + TIXMLASSERT( p ); if ( node == 0 ) { break; } + int initialLineNum = node->_parseLineNum; + StrPair endTag; - p = node->ParseDeep( p, &endTag ); + p = node->ParseDeep( p, &endTag, curLineNumPtr ); if ( !p ) { DeleteNode( node ); if ( !_document->Error() ) { - _document->SetError( XML_ERROR_PARSING, 0, 0 ); + _document->SetError( XML_ERROR_PARSING, 0, 0, initialLineNum); } break; } XMLDeclaration* decl = node->ToDeclaration(); if ( decl ) { - // A declaration can only be the first child of a document. - // Set error, if document already has children. - if ( !_document->NoChildren() ) { - _document->SetError( XML_ERROR_PARSING_DECLARATION, decl->Value(), 0); - DeleteNode( decl ); + // Declarations are only allowed at document level + bool wellLocated = ( ToDocument() != 0 ); + if ( wellLocated ) { + // Multiple declarations are allowed but all declarations + // must occur before anything else + for ( const XMLNode* existingNode = _document->FirstChild(); existingNode; existingNode = existingNode->NextSibling() ) { + if ( !existingNode->ToDeclaration() ) { + wellLocated = false; break; + } } + } + if ( !wellLocated ) { + _document->SetError( XML_ERROR_PARSING_DECLARATION, decl->Value(), 0, initialLineNum); + DeleteNode( node ); + break; + } } XMLElement* ele = node->ToElement(); @@ -1004,7 +1045,7 @@ char* XMLNode::ParseDeep( char* p, StrPair* parentEnd ) } } if ( mismatch ) { - _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, ele->Name(), 0 ); + _document->SetError( XML_ERROR_MISMATCHED_ELEMENT, ele->Name(), 0, initialLineNum); DeleteNode( node ); break; } @@ -1035,14 +1076,29 @@ void XMLNode::InsertChildPreamble( XMLNode* insertThis ) const insertThis->_memPool->SetTracked(); } +const XMLElement* XMLNode::ToElementWithName( const char* name ) const +{ + const XMLElement* element = this->ToElement(); + if ( element == 0 ) { + return 0; + } + if ( name == 0 ) { + return element; + } + if ( XMLUtil::StringEqual( element->Name(), name ) ) { + return element; + } + return 0; +} + // --------- XMLText ---------- // -char* XMLText::ParseDeep( char* p, StrPair* ) +char* XMLText::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) { const char* start = p; if ( this->CData() ) { - p = _value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); + p = _value.ParseText( p, "]]>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); if ( !p ) { - _document->SetError( XML_ERROR_PARSING_CDATA, start, 0 ); + _document->SetError( XML_ERROR_PARSING_CDATA, start, 0, _parseLineNum ); } return p; } @@ -1052,12 +1108,12 @@ char* XMLText::ParseDeep( char* p, StrPair* ) flags |= StrPair::NEEDS_WHITESPACE_COLLAPSING; } - p = _value.ParseText( p, "<", flags ); + p = _value.ParseText( p, "<", flags, curLineNumPtr ); if ( p && *p ) { return p-1; } if ( !p ) { - _document->SetError( XML_ERROR_PARSING_TEXT, start, 0 ); + _document->SetError( XML_ERROR_PARSING_TEXT, start, 0, _parseLineNum ); } } return 0; @@ -1101,13 +1157,13 @@ XMLComment::~XMLComment() } -char* XMLComment::ParseDeep( char* p, StrPair* ) +char* XMLComment::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) { // Comment parses as text. const char* start = p; - p = _value.ParseText( p, "-->", StrPair::COMMENT ); + p = _value.ParseText( p, "-->", StrPair::COMMENT, curLineNumPtr ); if ( p == 0 ) { - _document->SetError( XML_ERROR_PARSING_COMMENT, start, 0 ); + _document->SetError( XML_ERROR_PARSING_COMMENT, start, 0, _parseLineNum ); } return p; } @@ -1151,13 +1207,13 @@ XMLDeclaration::~XMLDeclaration() } -char* XMLDeclaration::ParseDeep( char* p, StrPair* ) +char* XMLDeclaration::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) { // Declaration parses as text. const char* start = p; - p = _value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION ); + p = _value.ParseText( p, "?>", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); if ( p == 0 ) { - _document->SetError( XML_ERROR_PARSING_DECLARATION, start, 0 ); + _document->SetError( XML_ERROR_PARSING_DECLARATION, start, 0, _parseLineNum ); } return p; } @@ -1200,14 +1256,14 @@ XMLUnknown::~XMLUnknown() } -char* XMLUnknown::ParseDeep( char* p, StrPair* ) +char* XMLUnknown::ParseDeep( char* p, StrPair*, int* curLineNumPtr ) { // Unknown parses as text. const char* start = p; - p = _value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION ); + p = _value.ParseText( p, ">", StrPair::NEEDS_NEWLINE_NORMALIZATION, curLineNumPtr ); if ( !p ) { - _document->SetError( XML_ERROR_PARSING_UNKNOWN, start, 0 ); + _document->SetError( XML_ERROR_PARSING_UNKNOWN, start, 0, _parseLineNum ); } return p; } @@ -1249,7 +1305,7 @@ const char* XMLAttribute::Value() const return _value.GetStr(); } -char* XMLAttribute::ParseDeep( char* p, bool processEntities ) +char* XMLAttribute::ParseDeep( char* p, bool processEntities, int* curLineNumPtr ) { // Parse using the name rules: bug fix, was using ParseText before p = _name.ParseName( p ); @@ -1258,13 +1314,13 @@ char* XMLAttribute::ParseDeep( char* p, bool processEntities ) } // Skip white space before = - p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); if ( *p != '=' ) { return 0; } ++p; // move up to opening quote - p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); if ( *p != '\"' && *p != '\'' ) { return 0; } @@ -1272,7 +1328,7 @@ char* XMLAttribute::ParseDeep( char* p, bool processEntities ) char endTag[2] = { *p, 0 }; ++p; // move past opening quote - p = _value.ParseText( p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES ); + p = _value.ParseText( p, endTag, processEntities ? StrPair::ATTRIBUTE_VALUE : StrPair::ATTRIBUTE_VALUE_LEAVE_ENTITIES, curLineNumPtr ); return p; } @@ -1286,7 +1342,7 @@ void XMLAttribute::SetName( const char* n ) XMLError XMLAttribute::QueryIntValue( int* value ) const { if ( XMLUtil::ToInt( Value(), value )) { - return XML_NO_ERROR; + return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } @@ -1295,16 +1351,25 @@ XMLError XMLAttribute::QueryIntValue( int* value ) const XMLError XMLAttribute::QueryUnsignedValue( unsigned int* value ) const { if ( XMLUtil::ToUnsigned( Value(), value )) { - return XML_NO_ERROR; + return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } +XMLError XMLAttribute::QueryInt64Value(int64_t* value) const +{ + if (XMLUtil::ToInt64(Value(), value)) { + return XML_SUCCESS; + } + return XML_WRONG_ATTRIBUTE_TYPE; +} + + XMLError XMLAttribute::QueryBoolValue( bool* value ) const { if ( XMLUtil::ToBool( Value(), value )) { - return XML_NO_ERROR; + return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } @@ -1313,7 +1378,7 @@ XMLError XMLAttribute::QueryBoolValue( bool* value ) const XMLError XMLAttribute::QueryFloatValue( float* value ) const { if ( XMLUtil::ToFloat( Value(), value )) { - return XML_NO_ERROR; + return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } @@ -1322,7 +1387,7 @@ XMLError XMLAttribute::QueryFloatValue( float* value ) const XMLError XMLAttribute::QueryDoubleValue( double* value ) const { if ( XMLUtil::ToDouble( Value(), value )) { - return XML_NO_ERROR; + return XML_SUCCESS; } return XML_WRONG_ATTRIBUTE_TYPE; } @@ -1350,6 +1415,15 @@ void XMLAttribute::SetAttribute( unsigned v ) } +void XMLAttribute::SetAttribute(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + _value.SetStr(buf); +} + + + void XMLAttribute::SetAttribute( bool v ) { char buf[BUF_SIZE]; @@ -1413,6 +1487,47 @@ const char* XMLElement::Attribute( const char* name, const char* value ) const return 0; } +int XMLElement::IntAttribute(const char* name, int defaultValue) const +{ + int i = defaultValue; + QueryIntAttribute(name, &i); + return i; +} + +unsigned XMLElement::UnsignedAttribute(const char* name, unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedAttribute(name, &i); + return i; +} + +int64_t XMLElement::Int64Attribute(const char* name, int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Attribute(name, &i); + return i; +} + +bool XMLElement::BoolAttribute(const char* name, bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolAttribute(name, &b); + return b; +} + +double XMLElement::DoubleAttribute(const char* name, double defaultValue) const +{ + double d = defaultValue; + QueryDoubleAttribute(name, &d); + return d; +} + +float XMLElement::FloatAttribute(const char* name, float defaultValue) const +{ + float f = defaultValue; + QueryFloatAttribute(name, &f); + return f; +} const char* XMLElement::GetText() const { @@ -1450,7 +1565,15 @@ void XMLElement::SetText( unsigned v ) } -void XMLElement::SetText( bool v ) +void XMLElement::SetText(int64_t v) +{ + char buf[BUF_SIZE]; + XMLUtil::ToStr(v, buf, BUF_SIZE); + SetText(buf); +} + + +void XMLElement::SetText( bool v ) { char buf[BUF_SIZE]; XMLUtil::ToStr( v, buf, BUF_SIZE ); @@ -1500,6 +1623,19 @@ XMLError XMLElement::QueryUnsignedText( unsigned* uval ) const } +XMLError XMLElement::QueryInt64Text(int64_t* ival) const +{ + if (FirstChild() && FirstChild()->ToText()) { + const char* t = FirstChild()->Value(); + if (XMLUtil::ToInt64(t, ival)) { + return XML_SUCCESS; + } + return XML_CAN_NOT_CONVERT_TEXT; + } + return XML_NO_TEXT_NODE; +} + + XMLError XMLElement::QueryBoolText( bool* bval ) const { if ( FirstChild() && FirstChild()->ToText() ) { @@ -1538,6 +1674,47 @@ XMLError XMLElement::QueryFloatText( float* fval ) const return XML_NO_TEXT_NODE; } +int XMLElement::IntText(int defaultValue) const +{ + int i = defaultValue; + QueryIntText(&i); + return i; +} + +unsigned XMLElement::UnsignedText(unsigned defaultValue) const +{ + unsigned i = defaultValue; + QueryUnsignedText(&i); + return i; +} + +int64_t XMLElement::Int64Text(int64_t defaultValue) const +{ + int64_t i = defaultValue; + QueryInt64Text(&i); + return i; +} + +bool XMLElement::BoolText(bool defaultValue) const +{ + bool b = defaultValue; + QueryBoolText(&b); + return b; +} + +double XMLElement::DoubleText(double defaultValue) const +{ + double d = defaultValue; + QueryDoubleText(&d); + return d; +} + +float XMLElement::FloatText(float defaultValue) const +{ + float f = defaultValue; + QueryFloatText(&f); + return f; +} XMLAttribute* XMLElement::FindOrCreateAttribute( const char* name ) @@ -1552,17 +1729,17 @@ XMLAttribute* XMLElement::FindOrCreateAttribute( const char* name ) } } if ( !attrib ) { - TIXMLASSERT( sizeof( XMLAttribute ) == _document->_attributePool.ItemSize() ); - attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); - attrib->_memPool = &_document->_attributePool; + attrib = CreateAttribute(); + TIXMLASSERT( attrib ); if ( last ) { + TIXMLASSERT( last->_next == 0 ); last->_next = attrib; } else { + TIXMLASSERT( _rootAttribute == 0 ); _rootAttribute = attrib; } attrib->SetName( name ); - attrib->_memPool->SetTracked(); // always created and linked. } return attrib; } @@ -1587,30 +1764,31 @@ void XMLElement::DeleteAttribute( const char* name ) } -char* XMLElement::ParseAttributes( char* p ) +char* XMLElement::ParseAttributes( char* p, int* curLineNumPtr ) { const char* start = p; XMLAttribute* prevAttribute = 0; // Read the attributes. while( p ) { - p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); if ( !(*p) ) { - _document->SetError( XML_ERROR_PARSING_ELEMENT, start, Name() ); + _document->SetError( XML_ERROR_PARSING_ELEMENT, start, Name(), _parseLineNum ); return 0; } // attribute. if (XMLUtil::IsNameStartChar( *p ) ) { - TIXMLASSERT( sizeof( XMLAttribute ) == _document->_attributePool.ItemSize() ); - XMLAttribute* attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); - attrib->_memPool = &_document->_attributePool; - attrib->_memPool->SetTracked(); + XMLAttribute* attrib = CreateAttribute(); + TIXMLASSERT( attrib ); + attrib->_parseLineNum = _document->_parseCurLineNum; - p = attrib->ParseDeep( p, _document->ProcessEntities() ); + int attrLineNum = attrib->_parseLineNum; + + p = attrib->ParseDeep( p, _document->ProcessEntities(), curLineNumPtr ); if ( !p || Attribute( attrib->Name() ) ) { DeleteAttribute( attrib ); - _document->SetError( XML_ERROR_PARSING_ATTRIBUTE, start, p ); + _document->SetError( XML_ERROR_PARSING_ATTRIBUTE, start, p, attrLineNum ); return 0; } // There is a minor bug here: if the attribute in the source xml @@ -1619,9 +1797,11 @@ char* XMLElement::ParseAttributes( char* p ) // avoids re-scanning the attribute list. Preferring performance for // now, may reconsider in the future. if ( prevAttribute ) { + TIXMLASSERT( prevAttribute->_next == 0 ); prevAttribute->_next = attrib; } else { + TIXMLASSERT( _rootAttribute == 0 ); _rootAttribute = attrib; } prevAttribute = attrib; @@ -1637,7 +1817,7 @@ char* XMLElement::ParseAttributes( char* p ) return p+2; // done; sealed element. } else { - _document->SetError( XML_ERROR_PARSING_ELEMENT, start, p ); + _document->SetError( XML_ERROR_PARSING_ELEMENT, start, p, _parseLineNum ); return 0; } } @@ -1654,14 +1834,24 @@ void XMLElement::DeleteAttribute( XMLAttribute* attribute ) pool->Free( attribute ); } +XMLAttribute* XMLElement::CreateAttribute() +{ + TIXMLASSERT( sizeof( XMLAttribute ) == _document->_attributePool.ItemSize() ); + XMLAttribute* attrib = new (_document->_attributePool.Alloc() ) XMLAttribute(); + TIXMLASSERT( attrib ); + attrib->_memPool = &_document->_attributePool; + attrib->_memPool->SetTracked(); + return attrib; +} + // // // foobar // -char* XMLElement::ParseDeep( char* p, StrPair* strPair ) +char* XMLElement::ParseDeep( char* p, StrPair* strPair, int* curLineNumPtr ) { // Read the element name. - p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::SkipWhiteSpace( p, curLineNumPtr ); // The closing element is the form. It is // parsed just like a regular element then deleted from @@ -1676,12 +1866,12 @@ char* XMLElement::ParseDeep( char* p, StrPair* strPair ) return 0; } - p = ParseAttributes( p ); + p = ParseAttributes( p, curLineNumPtr ); if ( !p || !*p || _closingType ) { return p; } - p = XMLNode::ParseDeep( p, strPair ); + p = XMLNode::ParseDeep( p, strPair, curLineNumPtr ); return p; } @@ -1767,15 +1957,15 @@ const char* XMLDocument::_errorNames[XML_ERROR_COUNT] = { }; -XMLDocument::XMLDocument( bool processEntities, Whitespace whitespace ) : +XMLDocument::XMLDocument( bool processEntities, Whitespace whitespaceMode ) : XMLNode( 0 ), _writeBOM( false ), _processEntities( processEntities ), - _errorID( XML_NO_ERROR ), - _whitespace( whitespace ), - _errorStr1( 0 ), - _errorStr2( 0 ), - _charBuffer( 0 ) + _errorID(XML_SUCCESS), + _whitespaceMode( whitespaceMode ), + _errorLineNum( 0 ), + _charBuffer( 0 ), + _parseCurLineNum( 0 ) { // avoid VC++ C4355 warning about 'this' in initializer list (C4355 is off by default in VS2012+) _document = this; @@ -1795,9 +1985,7 @@ void XMLDocument::Clear() #ifdef DEBUG const bool hadError = Error(); #endif - _errorID = XML_NO_ERROR; - _errorStr1 = 0; - _errorStr2 = 0; + ClearError(); delete [] _charBuffer; _charBuffer = 0; @@ -1822,9 +2010,7 @@ void XMLDocument::Clear() XMLElement* XMLDocument::NewElement( const char* name ) { - TIXMLASSERT( sizeof( XMLElement ) == _elementPool.ItemSize() ); - XMLElement* ele = new (_elementPool.Alloc()) XMLElement( this ); - ele->_memPool = &_elementPool; + XMLElement* ele = CreateUnlinkedNode( _elementPool ); ele->SetName( name ); return ele; } @@ -1832,9 +2018,7 @@ XMLElement* XMLDocument::NewElement( const char* name ) XMLComment* XMLDocument::NewComment( const char* str ) { - TIXMLASSERT( sizeof( XMLComment ) == _commentPool.ItemSize() ); - XMLComment* comment = new (_commentPool.Alloc()) XMLComment( this ); - comment->_memPool = &_commentPool; + XMLComment* comment = CreateUnlinkedNode( _commentPool ); comment->SetValue( str ); return comment; } @@ -1842,9 +2026,7 @@ XMLComment* XMLDocument::NewComment( const char* str ) XMLText* XMLDocument::NewText( const char* str ) { - TIXMLASSERT( sizeof( XMLText ) == _textPool.ItemSize() ); - XMLText* text = new (_textPool.Alloc()) XMLText( this ); - text->_memPool = &_textPool; + XMLText* text = CreateUnlinkedNode( _textPool ); text->SetValue( str ); return text; } @@ -1852,9 +2034,7 @@ XMLText* XMLDocument::NewText( const char* str ) XMLDeclaration* XMLDocument::NewDeclaration( const char* str ) { - TIXMLASSERT( sizeof( XMLDeclaration ) == _commentPool.ItemSize() ); - XMLDeclaration* dec = new (_commentPool.Alloc()) XMLDeclaration( this ); - dec->_memPool = &_commentPool; + XMLDeclaration* dec = CreateUnlinkedNode( _commentPool ); dec->SetValue( str ? str : "xml version=\"1.0\" encoding=\"UTF-8\"" ); return dec; } @@ -1862,9 +2042,7 @@ XMLDeclaration* XMLDocument::NewDeclaration( const char* str ) XMLUnknown* XMLDocument::NewUnknown( const char* str ) { - TIXMLASSERT( sizeof( XMLUnknown ) == _commentPool.ItemSize() ); - XMLUnknown* unk = new (_commentPool.Alloc()) XMLUnknown( this ); - unk->_memPool = &_commentPool; + XMLUnknown* unk = CreateUnlinkedNode( _commentPool ); unk->SetValue( str ); return unk; } @@ -1908,7 +2086,7 @@ XMLError XMLDocument::LoadFile( const char* filename ) Clear(); FILE* fp = callfopen( filename, "rb" ); if ( !fp ) { - SetError( XML_ERROR_FILE_NOT_FOUND, filename, 0 ); + SetError( XML_ERROR_FILE_NOT_FOUND, filename, 0, 0 ); return _errorID; } LoadFile( fp ); @@ -1932,10 +2110,12 @@ struct LongFitsIntoSizeTMinusOne { }; template <> -bool LongFitsIntoSizeTMinusOne::Fits( unsigned long /*value*/ ) -{ - return true; -} +struct LongFitsIntoSizeTMinusOne { + static bool Fits( unsigned long ) + { + return true; + } +}; XMLError XMLDocument::LoadFile( FILE* fp ) { @@ -1943,7 +2123,7 @@ XMLError XMLDocument::LoadFile( FILE* fp ) fseek( fp, 0, SEEK_SET ); if ( fgetc( fp ) == EOF && ferror( fp ) != 0 ) { - SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0, 0 ); return _errorID; } @@ -1951,19 +2131,19 @@ XMLError XMLDocument::LoadFile( FILE* fp ) const long filelength = ftell( fp ); fseek( fp, 0, SEEK_SET ); if ( filelength == -1L ) { - SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0, 0 ); return _errorID; } TIXMLASSERT( filelength >= 0 ); if ( !LongFitsIntoSizeTMinusOne<>::Fits( filelength ) ) { // Cannot handle files which won't fit in buffer together with null terminator - SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0, 0 ); return _errorID; } if ( filelength == 0 ) { - SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0 ); return _errorID; } @@ -1972,7 +2152,7 @@ XMLError XMLDocument::LoadFile( FILE* fp ) _charBuffer = new char[size+1]; size_t read = fread( _charBuffer, 1, size, fp ); if ( read != size ) { - SetError( XML_ERROR_FILE_READ_ERROR, 0, 0 ); + SetError( XML_ERROR_FILE_READ_ERROR, 0, 0, 0 ); return _errorID; } @@ -1987,7 +2167,7 @@ XMLError XMLDocument::SaveFile( const char* filename, bool compact ) { FILE* fp = callfopen( filename, "w" ); if ( !fp ) { - SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, filename, 0 ); + SetError( XML_ERROR_FILE_COULD_NOT_BE_OPENED, filename, 0, 0 ); return _errorID; } SaveFile(fp, compact); @@ -2000,7 +2180,7 @@ XMLError XMLDocument::SaveFile( FILE* fp, bool compact ) { // Clear any error from the last save, otherwise it will get reported // for *this* call. - SetError( XML_NO_ERROR, 0, 0 ); + ClearError(); XMLPrinter stream( fp, compact ); Print( &stream ); return _errorID; @@ -2012,7 +2192,7 @@ XMLError XMLDocument::Parse( const char* p, size_t len ) Clear(); if ( len == 0 || !p || !*p ) { - SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0 ); return _errorID; } if ( len == (size_t)(-1) ) { @@ -2050,20 +2230,32 @@ void XMLDocument::Print( XMLPrinter* streamer ) const } -void XMLDocument::SetError( XMLError error, const char* str1, const char* str2 ) +void XMLDocument::SetError( XMLError error, const char* str1, const char* str2, int lineNum ) { TIXMLASSERT( error >= 0 && error < XML_ERROR_COUNT ); _errorID = error; - _errorStr1 = str1; - _errorStr2 = str2; + + _errorStr1.Reset(); + _errorStr2.Reset(); + _errorLineNum = lineNum; + + if (str1) + _errorStr1.SetStr(str1); + if (str2) + _errorStr2.SetStr(str2); +} + +/*static*/ const char* XMLDocument::ErrorIDToName(XMLError errorID) +{ + TIXMLASSERT( errorID >= 0 && errorID < XML_ERROR_COUNT ); + const char* errorName = _errorNames[errorID]; + TIXMLASSERT( errorName && errorName[0] ); + return errorName; } const char* XMLDocument::ErrorName() const { - TIXMLASSERT( _errorID >= 0 && _errorID < XML_ERROR_COUNT ); - const char* errorName = _errorNames[_errorID]; - TIXMLASSERT( errorName && errorName[0] ); - return errorName; + return ErrorIDToName(_errorID); } void XMLDocument::PrintError() const @@ -2073,18 +2265,18 @@ void XMLDocument::PrintError() const char buf1[LEN] = { 0 }; char buf2[LEN] = { 0 }; - if ( _errorStr1 ) { - TIXML_SNPRINTF( buf1, LEN, "%s", _errorStr1 ); + if ( !_errorStr1.Empty() ) { + TIXML_SNPRINTF( buf1, LEN, "%s", _errorStr1.GetStr() ); } - if ( _errorStr2 ) { - TIXML_SNPRINTF( buf2, LEN, "%s", _errorStr2 ); + if ( !_errorStr2.Empty() ) { + TIXML_SNPRINTF( buf2, LEN, "%s", _errorStr2.GetStr() ); } // Should check INT_MIN <= _errorID && _errorId <= INT_MAX, but that // causes a clang "always true" -Wtautological-constant-out-of-range-compare warning TIXMLASSERT( 0 <= _errorID && XML_ERROR_COUNT - 1 <= INT_MAX ); - printf( "XMLDocument error id=%d '%s' str1=%s str2=%s\n", - static_cast( _errorID ), ErrorName(), buf1, buf2 ); + printf( "XMLDocument error id=%d '%s' str1=%s str2=%s line=%d\n", + static_cast( _errorID ), ErrorName(), buf1, buf2, _errorLineNum ); } } @@ -2092,14 +2284,16 @@ void XMLDocument::Parse() { TIXMLASSERT( NoChildren() ); // Clear() must have been called previously TIXMLASSERT( _charBuffer ); + _parseCurLineNum = 1; + _parseLineNum = 1; char* p = _charBuffer; - p = XMLUtil::SkipWhiteSpace( p ); + p = XMLUtil::SkipWhiteSpace( p, &_parseCurLineNum ); p = const_cast( XMLUtil::ReadBOM( p, &_writeBOM ) ); if ( !*p ) { - SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0 ); + SetError( XML_ERROR_EMPTY_DOCUMENT, 0, 0, 0 ); return; } - ParseDeep(p, 0 ); + ParseDeep(p, 0, &_parseCurLineNum ); } XMLPrinter::XMLPrinter( FILE* file, bool compact, int depth ) : @@ -2117,7 +2311,7 @@ XMLPrinter::XMLPrinter( FILE* file, bool compact, int depth ) : } for( int i=0; i # include # include +# if defined(__PS3__) +# include +# endif #else # include # include @@ -37,6 +40,7 @@ distribution. # include # include #endif +#include /* TODO: intern strings instead of allocation. @@ -68,6 +72,8 @@ distribution. # else # define TINYXML2_LIB # endif +#elif __GNUC__ >= 4 +# define TINYXML2_LIB __attribute__((visibility("default"))) #else # define TINYXML2_LIB #endif @@ -92,9 +98,9 @@ distribution. /* Versioning, past 1.0.14: http://semver.org/ */ -static const int TIXML2_MAJOR_VERSION = 3; +static const int TIXML2_MAJOR_VERSION = 4; static const int TIXML2_MINOR_VERSION = 0; -static const int TIXML2_PATCH_VERSION = 0; +static const int TIXML2_PATCH_VERSION = 1; namespace tinyxml2 { @@ -121,18 +127,20 @@ public: NEEDS_NEWLINE_NORMALIZATION = 0x02, NEEDS_WHITESPACE_COLLAPSING = 0x04, - TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + TEXT_ELEMENT = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, TEXT_ELEMENT_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, - ATTRIBUTE_NAME = 0, - ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, - ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, - COMMENT = NEEDS_NEWLINE_NORMALIZATION + ATTRIBUTE_NAME = 0, + ATTRIBUTE_VALUE = NEEDS_ENTITY_PROCESSING | NEEDS_NEWLINE_NORMALIZATION, + ATTRIBUTE_VALUE_LEAVE_ENTITIES = NEEDS_NEWLINE_NORMALIZATION, + COMMENT = NEEDS_NEWLINE_NORMALIZATION }; StrPair() : _flags( 0 ), _start( 0 ), _end( 0 ) {} ~StrPair(); void Set( char* start, char* end, int flags ) { + TIXMLASSERT( start ); + TIXMLASSERT( end ); Reset(); _start = start; _end = end; @@ -152,13 +160,13 @@ public: void SetStr( const char* str, int flags=0 ); - char* ParseText( char* in, const char* endTag, int strFlags ); + char* ParseText( char* in, const char* endTag, int strFlags, int* curLineNumPtr ); char* ParseName( char* in ); void TransferTo( StrPair* other ); + void Reset(); private: - void Reset(); void CollapseWhitespace(); enum { @@ -203,7 +211,8 @@ public: void Push( T t ) { TIXMLASSERT( _size < INT_MAX ); EnsureCapacity( _size+1 ); - _mem[_size++] = t; + _mem[_size] = t; + ++_size; } T* PushArr( int count ) { @@ -217,7 +226,8 @@ public: T Pop() { TIXMLASSERT( _size > 0 ); - return _mem[--_size]; + --_size; + return _mem[_size]; } void PopArr( int count ) { @@ -311,7 +321,7 @@ public: /* Template child class to create pools of the correct type. */ -template< int SIZE > +template< int ITEM_SIZE > class MemPoolT : public MemPool { public: @@ -334,7 +344,7 @@ public: } virtual int ItemSize() const { - return SIZE; + return ITEM_SIZE; } int CurrentAllocs() const { return _currentAllocs; @@ -346,21 +356,23 @@ public: Block* block = new Block(); _blockPtrs.Push( block ); - for( int i=0; ichunk[i].next = &block->chunk[i+1]; + Item* blockItems = block->items; + for( int i = 0; i < ITEMS_PER_BLOCK - 1; ++i ) { + blockItems[i].next = &(blockItems[i + 1]); } - block->chunk[COUNT-1].next = 0; - _root = block->chunk; + blockItems[ITEMS_PER_BLOCK - 1].next = 0; + _root = blockItems; } - void* result = _root; + Item* const result = _root; + TIXMLASSERT( result != 0 ); _root = _root->next; ++_currentAllocs; if ( _currentAllocs > _maxAllocs ) { _maxAllocs = _currentAllocs; } - _nAllocs++; - _nUntracked++; + ++_nAllocs; + ++_nUntracked; return result; } @@ -369,20 +381,21 @@ public: return; } --_currentAllocs; - Chunk* chunk = static_cast( mem ); + Item* item = static_cast( mem ); #ifdef DEBUG - memset( chunk, 0xfe, sizeof(Chunk) ); + memset( item, 0xfe, sizeof( *item ) ); #endif - chunk->next = _root; - _root = chunk; + item->next = _root; + _root = item; } void Trace( const char* name ) { printf( "Mempool %s watermark=%d [%dk] current=%d size=%d nAlloc=%d blocks=%d\n", - name, _maxAllocs, _maxAllocs*SIZE/1024, _currentAllocs, SIZE, _nAllocs, _blockPtrs.Size() ); + name, _maxAllocs, _maxAllocs * ITEM_SIZE / 1024, _currentAllocs, + ITEM_SIZE, _nAllocs, _blockPtrs.Size() ); } void SetTracked() { - _nUntracked--; + --_nUntracked; } int Untracked() const { @@ -398,21 +411,23 @@ public: // 16k: 5200 // 32k: 4300 // 64k: 4000 21000 - enum { COUNT = (4*1024)/SIZE }; // Some compilers do not accept to use COUNT in private part if COUNT is private + // Declared public because some compilers do not accept to use ITEMS_PER_BLOCK + // in private part if ITEMS_PER_BLOCK is private + enum { ITEMS_PER_BLOCK = (4 * 1024) / ITEM_SIZE }; private: MemPoolT( const MemPoolT& ); // not supported void operator=( const MemPoolT& ); // not supported - union Chunk { - Chunk* next; - char mem[SIZE]; + union Item { + Item* next; + char itemData[ITEM_SIZE]; }; struct Block { - Chunk chunk[COUNT]; + Item items[ITEMS_PER_BLOCK]; }; DynArray< Block*, 10 > _blockPtrs; - Chunk* _root; + Item* _root; int _currentAllocs; int _nAllocs; @@ -485,7 +500,6 @@ public: // WARNING: must match XMLDocument::_errorNames[] enum XMLError { XML_SUCCESS = 0, - XML_NO_ERROR = 0, XML_NO_ATTRIBUTE, XML_WRONG_ATTRIBUTE_TYPE, XML_ERROR_FILE_NOT_FOUND, @@ -513,19 +527,23 @@ enum XMLError { /* Utility functionality. */ -class XMLUtil +class TINYXML2_LIB XMLUtil { public: - static const char* SkipWhiteSpace( const char* p ) { + static const char* SkipWhiteSpace( const char* p, int* curLineNumPtr ) { TIXMLASSERT( p ); + while( IsWhiteSpace(*p) ) { + if (curLineNumPtr && *p == '\n') { + ++(*curLineNumPtr); + } ++p; } TIXMLASSERT( p ); return p; } - static char* SkipWhiteSpace( char* p ) { - return const_cast( SkipWhiteSpace( const_cast(p) ) ); + static char* SkipWhiteSpace( char* p, int* curLineNumPtr ) { + return const_cast( SkipWhiteSpace( const_cast(p), curLineNumPtr ) ); } // Anything in the high order range of UTF-8 is assumed to not be whitespace. This isn't @@ -556,6 +574,9 @@ public: if ( p == q ) { return true; } + TIXMLASSERT( p ); + TIXMLASSERT( q ); + TIXMLASSERT( nChar >= 0 ); return strncmp( p, q, nChar ) == 0; } @@ -575,6 +596,7 @@ public: static void ToStr( bool v, char* buffer, int bufferSize ); static void ToStr( float v, char* buffer, int bufferSize ); static void ToStr( double v, char* buffer, int bufferSize ); + static void ToStr(int64_t v, char* buffer, int bufferSize); // converts strings to primitive types static bool ToInt( const char* str, int* value ); @@ -582,6 +604,18 @@ public: static bool ToBool( const char* str, bool* value ); static bool ToFloat( const char* str, float* value ); static bool ToDouble( const char* str, double* value ); + static bool ToInt64(const char* str, int64_t* value); + + // Changes what is serialized for a boolean value. + // Default to "true" and "false". Shouldn't be changed + // unless you have a special testing or compatibility need. + // Be careful: static, global, & not thread safe. + // Be sure to set static const memory as parameters. + static void SetBoolSerialization(const char* writeTrue, const char* writeFalse); + +private: + static const char* writeBoolTrue; + static const char* writeBoolFalse; }; @@ -687,6 +721,9 @@ public: */ void SetValue( const char* val, bool staticMem=false ); + /// Gets the line number the node is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + /// Get the parent of this node on the DOM. const XMLNode* Parent() const { return _parent; @@ -852,15 +889,30 @@ public: */ virtual bool Accept( XMLVisitor* visitor ) const = 0; + /** + Set user data into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void SetUserData(void* userData) { _userData = userData; } + + /** + Get user data set into the XMLNode. TinyXML-2 in + no way processes or interprets user data. + It is initially 0. + */ + void* GetUserData() const { return _userData; } + protected: XMLNode( XMLDocument* ); virtual ~XMLNode(); - virtual char* ParseDeep( char*, StrPair* ); + virtual char* ParseDeep( char*, StrPair*, int* ); XMLDocument* _document; XMLNode* _parent; mutable StrPair _value; + int _parseLineNum; XMLNode* _firstChild; XMLNode* _lastChild; @@ -868,11 +920,14 @@ protected: XMLNode* _prev; XMLNode* _next; + void* _userData; + private: MemPool* _memPool; void Unlink( XMLNode* child ); static void DeleteNode( XMLNode* node ); void InsertChildPreamble( XMLNode* insertThis ) const; + const XMLElement* ToElementWithName( const char* name ) const; XMLNode( const XMLNode& ); // not supported XMLNode& operator=( const XMLNode& ); // not supported @@ -893,7 +948,6 @@ private: */ class TINYXML2_LIB XMLText : public XMLNode { - friend class XMLBase; friend class XMLDocument; public: virtual bool Accept( XMLVisitor* visitor ) const; @@ -921,7 +975,7 @@ protected: XMLText( XMLDocument* doc ) : XMLNode( doc ), _isCData( false ) {} virtual ~XMLText() {} - char* ParseDeep( char*, StrPair* endTag ); + char* ParseDeep( char*, StrPair* endTag, int* curLineNumPtr ); private: bool _isCData; @@ -952,7 +1006,7 @@ protected: XMLComment( XMLDocument* doc ); virtual ~XMLComment(); - char* ParseDeep( char*, StrPair* endTag ); + char* ParseDeep( char*, StrPair* endTag, int* curLineNumPtr); private: XMLComment( const XMLComment& ); // not supported @@ -991,7 +1045,7 @@ protected: XMLDeclaration( XMLDocument* doc ); virtual ~XMLDeclaration(); - char* ParseDeep( char*, StrPair* endTag ); + char* ParseDeep( char*, StrPair* endTag, int* curLineNumPtr ); private: XMLDeclaration( const XMLDeclaration& ); // not supported @@ -1026,7 +1080,7 @@ protected: XMLUnknown( XMLDocument* doc ); virtual ~XMLUnknown(); - char* ParseDeep( char*, StrPair* endTag ); + char* ParseDeep( char*, StrPair* endTag, int* curLineNumPtr ); private: XMLUnknown( const XMLUnknown& ); // not supported @@ -1051,6 +1105,9 @@ public: /// The value of the attribute. const char* Value() const; + /// Gets the line number the attribute is in, if the document was parsed from a file. + int GetLineNum() const { return _parseLineNum; } + /// The next attribute in the list. const XMLAttribute* Next() const { return _next; @@ -1060,11 +1117,18 @@ public: If the value isn't an integer, 0 will be returned. There is no error checking; use QueryIntValue() if you need error checking. */ - int IntValue() const { - int i=0; - QueryIntValue( &i ); - return i; - } + int IntValue() const { + int i = 0; + QueryIntValue(&i); + return i; + } + + int64_t Int64Value() const { + int64_t i = 0; + QueryInt64Value(&i); + return i; + } + /// Query as an unsigned integer. See IntValue() unsigned UnsignedValue() const { unsigned i=0; @@ -1091,13 +1155,15 @@ public: } /** QueryIntValue interprets the attribute as an integer, and returns the value - in the provided parameter. The function will return XML_NO_ERROR on success, + in the provided parameter. The function will return XML_SUCCESS on success, and XML_WRONG_ATTRIBUTE_TYPE if the conversion is not successful. */ XMLError QueryIntValue( int* value ) const; /// See QueryIntValue XMLError QueryUnsignedValue( unsigned int* value ) const; - /// See QueryIntValue + /// See QueryIntValue + XMLError QueryInt64Value(int64_t* value) const; + /// See QueryIntValue XMLError QueryBoolValue( bool* value ) const; /// See QueryIntValue XMLError QueryDoubleValue( double* value ) const; @@ -1110,7 +1176,9 @@ public: void SetAttribute( int value ); /// Set the attribute to value. void SetAttribute( unsigned value ); - /// Set the attribute to value. + /// Set the attribute to value. + void SetAttribute(int64_t value); + /// Set the attribute to value. void SetAttribute( bool value ); /// Set the attribute to value. void SetAttribute( double value ); @@ -1120,17 +1188,18 @@ public: private: enum { BUF_SIZE = 200 }; - XMLAttribute() : _next( 0 ), _memPool( 0 ) {} + XMLAttribute() : _parseLineNum( 0 ), _next( 0 ), _memPool( 0 ) {} virtual ~XMLAttribute() {} XMLAttribute( const XMLAttribute& ); // not supported void operator=( const XMLAttribute& ); // not supported void SetName( const char* name ); - char* ParseDeep( char* p, bool processEntities ); + char* ParseDeep( char* p, bool processEntities, int* curLineNumPtr ); mutable StrPair _name; mutable StrPair _value; + int _parseLineNum; XMLAttribute* _next; MemPool* _memPool; }; @@ -1142,7 +1211,6 @@ private: */ class TINYXML2_LIB XMLElement : public XMLNode { - friend class XMLBase; friend class XMLDocument; public: /// Get the name of an element (which is the Value() of the node.) @@ -1188,42 +1256,25 @@ public: const char* Attribute( const char* name, const char* value=0 ) const; /** Given an attribute name, IntAttribute() returns the value - of the attribute interpreted as an integer. 0 will be - returned if there is an error. For a method with error - checking, see QueryIntAttribute() + of the attribute interpreted as an integer. The default + value will be returned if the attribute isn't present, + or if there is an error. (For a method with error + checking, see QueryIntAttribute()). */ - int IntAttribute( const char* name ) const { - int i=0; - QueryIntAttribute( name, &i ); - return i; - } + int IntAttribute(const char* name, int defaultValue = 0) const; /// See IntAttribute() - unsigned UnsignedAttribute( const char* name ) const { - unsigned i=0; - QueryUnsignedAttribute( name, &i ); - return i; - } + unsigned UnsignedAttribute(const char* name, unsigned defaultValue = 0) const; + /// See IntAttribute() + int64_t Int64Attribute(const char* name, int64_t defaultValue = 0) const; + /// See IntAttribute() + bool BoolAttribute(const char* name, bool defaultValue = false) const; /// See IntAttribute() - bool BoolAttribute( const char* name ) const { - bool b=false; - QueryBoolAttribute( name, &b ); - return b; - } + double DoubleAttribute(const char* name, double defaultValue = 0) const; /// See IntAttribute() - double DoubleAttribute( const char* name ) const { - double d=0; - QueryDoubleAttribute( name, &d ); - return d; - } - /// See IntAttribute() - float FloatAttribute( const char* name ) const { - float f=0; - QueryFloatAttribute( name, &f ); - return f; - } + float FloatAttribute(const char* name, float defaultValue = 0) const; /** Given an attribute name, QueryIntAttribute() returns - XML_NO_ERROR, XML_WRONG_ATTRIBUTE_TYPE if the conversion + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion can't be performed, or XML_NO_ATTRIBUTE if the attribute doesn't exist. If successful, the result of the conversion will be written to 'value'. If not successful, nothing will @@ -1242,7 +1293,8 @@ public: } return a->QueryIntValue( value ); } - /// See QueryIntAttribute() + + /// See QueryIntAttribute() XMLError QueryUnsignedAttribute( const char* name, unsigned int* value ) const { const XMLAttribute* a = FindAttribute( name ); if ( !a ) { @@ -1250,7 +1302,17 @@ public: } return a->QueryUnsignedValue( value ); } - /// See QueryIntAttribute() + + /// See QueryIntAttribute() + XMLError QueryInt64Attribute(const char* name, int64_t* value) const { + const XMLAttribute* a = FindAttribute(name); + if (!a) { + return XML_NO_ATTRIBUTE; + } + return a->QueryInt64Value(value); + } + + /// See QueryIntAttribute() XMLError QueryBoolAttribute( const char* name, bool* value ) const { const XMLAttribute* a = FindAttribute( name ); if ( !a ) { @@ -1277,7 +1339,7 @@ public: /** Given an attribute name, QueryAttribute() returns - XML_NO_ERROR, XML_WRONG_ATTRIBUTE_TYPE if the conversion + XML_SUCCESS, XML_WRONG_ATTRIBUTE_TYPE if the conversion can't be performed, or XML_NO_ATTRIBUTE if the attribute doesn't exist. It is overloaded for the primitive types, and is a generally more convenient replacement of @@ -1301,6 +1363,10 @@ public: return QueryUnsignedAttribute( name, value ); } + int QueryAttribute(const char* name, int64_t* value) const { + return QueryInt64Attribute(name, value); + } + int QueryAttribute( const char* name, bool* value ) const { return QueryBoolAttribute( name, value ); } @@ -1328,7 +1394,14 @@ public: XMLAttribute* a = FindOrCreateAttribute( name ); a->SetAttribute( value ); } - /// Sets the named attribute to value. + + /// Sets the named attribute to value. + void SetAttribute(const char* name, int64_t value) { + XMLAttribute* a = FindOrCreateAttribute(name); + a->SetAttribute(value); + } + + /// Sets the named attribute to value. void SetAttribute( const char* name, bool value ) { XMLAttribute* a = FindOrCreateAttribute( name ); a->SetAttribute( value ); @@ -1425,7 +1498,9 @@ public: void SetText( int value ); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText( unsigned value ); - /// Convenience method for setting text inside an element. See SetText() for important limitations. + /// Convenience method for setting text inside an element. See SetText() for important limitations. + void SetText(int64_t value); + /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText( bool value ); /// Convenience method for setting text inside an element. See SetText() for important limitations. void SetText( double value ); @@ -1461,13 +1536,28 @@ public: XMLError QueryIntText( int* ival ) const; /// See QueryIntText() XMLError QueryUnsignedText( unsigned* uval ) const; - /// See QueryIntText() + /// See QueryIntText() + XMLError QueryInt64Text(int64_t* uval) const; + /// See QueryIntText() XMLError QueryBoolText( bool* bval ) const; /// See QueryIntText() XMLError QueryDoubleText( double* dval ) const; /// See QueryIntText() XMLError QueryFloatText( float* fval ) const; + int IntText(int defaultValue = 0) const; + + /// See QueryIntText() + unsigned UnsignedText(unsigned defaultValue = 0) const; + /// See QueryIntText() + int64_t Int64Text(int64_t defaultValue = 0) const; + /// See QueryIntText() + bool BoolText(bool defaultValue = false) const; + /// See QueryIntText() + double DoubleText(double defaultValue = 0) const; + /// See QueryIntText() + float FloatText(float defaultValue = 0) const; + // internal: enum { OPEN, // @@ -1481,7 +1571,7 @@ public: virtual bool ShallowEqual( const XMLNode* compare ) const; protected: - char* ParseDeep( char* p, StrPair* endTag ); + char* ParseDeep( char* p, StrPair* endTag, int* curLineNumPtr ); private: XMLElement( XMLDocument* doc ); @@ -1494,8 +1584,9 @@ private: } XMLAttribute* FindOrCreateAttribute( const char* name ); //void LinkAttribute( XMLAttribute* attrib ); - char* ParseAttributes( char* p ); + char* ParseAttributes( char* p, int* curLineNumPtr ); static void DeleteAttribute( XMLAttribute* attribute ); + XMLAttribute* CreateAttribute(); enum { BUF_SIZE = 200 }; int _closingType; @@ -1522,7 +1613,7 @@ class TINYXML2_LIB XMLDocument : public XMLNode friend class XMLElement; public: /// constructor - XMLDocument( bool processEntities = true, Whitespace = PRESERVE_WHITESPACE ); + XMLDocument( bool processEntities = true, Whitespace whitespaceMode = PRESERVE_WHITESPACE ); ~XMLDocument(); virtual XMLDocument* ToDocument() { @@ -1536,7 +1627,7 @@ public: /** Parse an XML file from a character string. - Returns XML_NO_ERROR (0) on success, or + Returns XML_SUCCESS (0) on success, or an errorID. You may optionally pass in the 'nBytes', which is @@ -1548,7 +1639,7 @@ public: /** Load an XML file from disk. - Returns XML_NO_ERROR (0) on success, or + Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError LoadFile( const char* filename ); @@ -1561,14 +1652,14 @@ public: not text in order for TinyXML-2 to correctly do newline normalization. - Returns XML_NO_ERROR (0) on success, or + Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError LoadFile( FILE* ); /** Save the XML file to disk. - Returns XML_NO_ERROR (0) on success, or + Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError SaveFile( const char* filename, bool compact = false ); @@ -1577,7 +1668,7 @@ public: Save the XML file to disk. You are responsible for providing and closing the FILE*. - Returns XML_NO_ERROR (0) on success, or + Returns XML_SUCCESS (0) on success, or an errorID. */ XMLError SaveFile( FILE* fp, bool compact = false ); @@ -1586,7 +1677,7 @@ public: return _processEntities; } Whitespace WhitespaceMode() const { - return _whitespace; + return _whitespaceMode; } /** @@ -1671,25 +1762,35 @@ public: */ void DeleteNode( XMLNode* node ); - void SetError( XMLError error, const char* str1, const char* str2 ); + void SetError( XMLError error, const char* str1, const char* str2, int lineNum ); + + void ClearError() { + SetError(XML_SUCCESS, 0, 0, 0); + } /// Return true if there was an error parsing the document. bool Error() const { - return _errorID != XML_NO_ERROR; + return _errorID != XML_SUCCESS; } /// Return the errorID. XMLError ErrorID() const { return _errorID; } const char* ErrorName() const; + static const char* ErrorIDToName(XMLError errorID); /// Return a possibly helpful diagnostic location or string. const char* GetErrorStr1() const { - return _errorStr1; + return _errorStr1.GetStr(); } /// Return a possibly helpful secondary diagnostic location or string. const char* GetErrorStr2() const { - return _errorStr2; + return _errorStr2.GetStr(); + } + /// Return the line where the error occured, or zero if unknown. + int GetErrorLineNum() const + { + return _errorLineNum; } /// If there is an error, print it to stdout. void PrintError() const; @@ -1711,13 +1812,15 @@ private: XMLDocument( const XMLDocument& ); // not supported void operator=( const XMLDocument& ); // not supported - bool _writeBOM; - bool _processEntities; - XMLError _errorID; - Whitespace _whitespace; - const char* _errorStr1; - const char* _errorStr2; - char* _charBuffer; + bool _writeBOM; + bool _processEntities; + XMLError _errorID; + Whitespace _whitespaceMode; + mutable StrPair _errorStr1; + mutable StrPair _errorStr2; + int _errorLineNum; + char* _charBuffer; + int _parseCurLineNum; MemPoolT< sizeof(XMLElement) > _elementPool; MemPoolT< sizeof(XMLAttribute) > _attributePool; @@ -1727,8 +1830,21 @@ private: static const char* _errorNames[XML_ERROR_COUNT]; void Parse(); + + template + NodeType* CreateUnlinkedNode( MemPoolT& pool ); }; +template +inline NodeType* XMLDocument::CreateUnlinkedNode( MemPoolT& pool ) +{ + TIXMLASSERT( sizeof( NodeType ) == PoolElementSize ); + TIXMLASSERT( sizeof( NodeType ) == pool.ItemSize() ); + NodeType* returnNode = new (pool.Alloc()) NodeType( this ); + TIXMLASSERT( returnNode ); + returnNode->_memPool = &pool; + return returnNode; +} /** A XMLHandle is a class that wraps a node pointer with null checks; this is @@ -1845,19 +1961,19 @@ public: } /// Safe cast to XMLElement. This can return null. XMLElement* ToElement() { - return ( ( _node == 0 ) ? 0 : _node->ToElement() ); + return ( _node ? _node->ToElement() : 0 ); } /// Safe cast to XMLText. This can return null. XMLText* ToText() { - return ( ( _node == 0 ) ? 0 : _node->ToText() ); + return ( _node ? _node->ToText() : 0 ); } /// Safe cast to XMLUnknown. This can return null. XMLUnknown* ToUnknown() { - return ( ( _node == 0 ) ? 0 : _node->ToUnknown() ); + return ( _node ? _node->ToUnknown() : 0 ); } /// Safe cast to XMLDeclaration. This can return null. XMLDeclaration* ToDeclaration() { - return ( ( _node == 0 ) ? 0 : _node->ToDeclaration() ); + return ( _node ? _node->ToDeclaration() : 0 ); } private: @@ -1917,16 +2033,16 @@ public: return _node; } const XMLElement* ToElement() const { - return ( ( _node == 0 ) ? 0 : _node->ToElement() ); + return ( _node ? _node->ToElement() : 0 ); } const XMLText* ToText() const { - return ( ( _node == 0 ) ? 0 : _node->ToText() ); + return ( _node ? _node->ToText() : 0 ); } const XMLUnknown* ToUnknown() const { - return ( ( _node == 0 ) ? 0 : _node->ToUnknown() ); + return ( _node ? _node->ToUnknown() : 0 ); } const XMLDeclaration* ToDeclaration() const { - return ( ( _node == 0 ) ? 0 : _node->ToDeclaration() ); + return ( _node ? _node->ToDeclaration() : 0 ); } private: @@ -1998,7 +2114,8 @@ public: void PushAttribute( const char* name, const char* value ); void PushAttribute( const char* name, int value ); void PushAttribute( const char* name, unsigned value ); - void PushAttribute( const char* name, bool value ); + void PushAttribute(const char* name, int64_t value); + void PushAttribute( const char* name, bool value ); void PushAttribute( const char* name, double value ); /// If streaming, close the Element. virtual void CloseElement( bool compactMode=false ); @@ -2009,7 +2126,9 @@ public: void PushText( int value ); /// Add a text node from an unsigned. void PushText( unsigned value ); - /// Add a text node from a bool. + /// Add a text node from an unsigned. + void PushText(int64_t value); + /// Add a text node from a bool. void PushText( bool value ); /// Add a text node from a float. void PushText( float value ); diff --git a/main.cpp b/main.cpp index 07e9163..52a00bc 100755 --- a/main.cpp +++ b/main.cpp @@ -29,7 +29,7 @@ int main(int argc, char** argv) { //::testing::GTEST_FLAG(filter) = "*WiFiOptimizer*"; - ::testing::GTEST_FLAG(filter) = "*NormalN*"; + ::testing::GTEST_FLAG(filter) = "*FloorplanCeilings*"; //::testing::GTEST_FLAG(filter) = "*Barometer*"; //::testing::GTEST_FLAG(filter) = "*GridWalk2RelPressure*"; diff --git a/sensors/MACAddress.h b/sensors/MACAddress.h index 02aee2e..8c31237 100644 --- a/sensors/MACAddress.h +++ b/sensors/MACAddress.h @@ -24,7 +24,7 @@ private: } fields; /** store as 64-bit integer */ - uint64_t mac; + uint64_t mac = 0; public: @@ -61,8 +61,8 @@ public: fields.h5 = hexWordToInt(str[ 0], str[ 1]); fields.h4 = hexWordToInt(str[ 2], str[ 3]); fields.h3 = hexWordToInt(str[ 4], str[ 5]); - fields.h2 = hexWordToInt(str[ 6], str[7]); - fields.h1 = hexWordToInt(str[8], str[9]); + fields.h2 = hexWordToInt(str[ 6], str[ 7]); + fields.h1 = hexWordToInt(str[ 8], str[ 9]); fields.h0 = hexWordToInt(str[10], str[11]); } else{ @@ -89,7 +89,9 @@ public: /** get the mac address as a long-int value */ uint64_t asLong() const { - return mac; + // even if we initialized all 8 bytes with zero + // we mask the 2 unsued bytes to be absolutely safe + return mac & 0x0000FFFFFFFFFFFF; } /** equal? */ @@ -105,13 +107,19 @@ public: private: /** convert the given hex char [0-F] to an integer [0-15] */ - static uint8_t hexCharToInt(const char _hex) { - - // to upper case - const char hex = (_hex >= 'a') ? (_hex - ('a' - 'A')) : (_hex); + static uint8_t hexCharToInt(const char hex) { // convert - return (hex - '0' < 10) ? (hex - '0') : (hex - 'A' + 10); + if (hex >= '0' && hex <= '9') { // digits + return (hex - '0'); + } else if (hex >= 'a' && hex <= 'f') { // lower case + return (hex - 'a' + 10); + } else if (hex >= 'A' && hex <= 'F') { // upper case + return (hex - 'A' + 10); + } + + // found an invalid character + throw Exception("invalid character within MAC address"); } diff --git a/sensors/radio/VAPGrouper.h b/sensors/radio/VAPGrouper.h index bdf2292..b8a7545 100644 --- a/sensors/radio/VAPGrouper.h +++ b/sensors/radio/VAPGrouper.h @@ -7,6 +7,8 @@ #include "../../math/Stats.h" +#include "../../misc/Debug.h" + #include "WiFiMeasurements.h" class VAPGrouper { @@ -40,6 +42,8 @@ public: private: + static constexpr const char* name = "VAPGrp"; + /** the mode to use for grouping VAPs */ const Mode mode; @@ -85,6 +89,9 @@ public: } + Log::add(name, "grouped " + std::to_string(original.entries.size()) + " measurements into " + std::to_string(result.entries.size()), true); + + // done return result; diff --git a/sensors/radio/model/WiFiModelLogDistCeiling.h b/sensors/radio/model/WiFiModelLogDistCeiling.h index d9834d6..f0a7e05 100644 --- a/sensors/radio/model/WiFiModelLogDistCeiling.h +++ b/sensors/radio/model/WiFiModelLogDistCeiling.h @@ -2,6 +2,7 @@ #define WIFIMODELLOGDISTCEILING_H #include "../../../floorplan/v2/Floorplan.h" +#include "../../../floorplan/v2/FloorplanCeilings.h" #include "../../../Assertions.h" #include "WiFiModel.h" @@ -36,21 +37,18 @@ private: /** map of all APs (and their parameters) known to the model */ std::unordered_map accessPoints; - /** position (height) of all ceilings (in meter) */ - std::vector ceilingsAtHeight_m; +// /** position (height) of all ceilings (in meter) */ +// std::vector 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); - } } @@ -123,74 +121,13 @@ 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); // combine return rssiLOS + wafLoss; } - - -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 { - - int cnt = 0; - 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;} - } - - return cnt; - - } - }; diff --git a/sensors/radio/setup/WiFiFingerprint.h b/sensors/radio/setup/WiFiFingerprint.h index a92652b..940faa4 100644 --- a/sensors/radio/setup/WiFiFingerprint.h +++ b/sensors/radio/setup/WiFiFingerprint.h @@ -8,10 +8,10 @@ #include /** - * denotes a wifi fingerprint - * known position and several measurements conducted at this position + * denotes a wifi fingerprint: + * known position and 1-n measurements [wifi-scan on all channels] conducted at this position * - * as several measurements were conducted, each AP is usually contained more than once! + * if more than one measurement is conducted, each AP is contained more than once! */ struct WiFiFingerprint { @@ -19,7 +19,7 @@ struct WiFiFingerprint { /** real-world-position that was measured */ Point3 pos_m; - /** measurements (APs) at the given location */ + /** seen APs at the given location */ WiFiMeasurements measurements; @@ -31,7 +31,7 @@ struct WiFiFingerprint { - /** as each AP is contained more than once (scanned more than once), group them by MAC and use the average RSSI */ + /** as each AP might be 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) diff --git a/sensors/radio/setup/WiFiFingerprints.h b/sensors/radio/setup/WiFiFingerprints.h new file mode 100644 index 0000000..9dd1b2f --- /dev/null +++ b/sensors/radio/setup/WiFiFingerprints.h @@ -0,0 +1,125 @@ +#ifndef WIFIFINGERPRINTS_H +#define WIFIFINGERPRINTS_H + +#include + +#include "WiFiFingerprint.h" + + +/** + * helper class to load and save fingerprints. + * fingerprints are wifi-measurements given at some known location + * those can be used to e.g. optimize access-point models etc. + */ +class WiFiFingerprints { + +private: + + /** the file to save the calibration model to */ + std::string file; + + /** all fingerprints (position -> measurements) within the model */ + std::vector fingerprints; + +public: + + WiFiFingerprints(const std::string& file) : file(file) { + load(); + } + + const std::vector& getFingerprints() const { + return fingerprints; + } + + /** get all fingerprints that measured exactly the given mac [no VAP grouping] */ + const std::vector getFingerprintsFor(const MACAddress& mac) const { + + std::vector res; + + // process each fingerprint location + for (const WiFiFingerprint& _fp : fingerprints) { + + // start with an empty copy + WiFiFingerprint fp = _fp; fp.measurements.entries.clear(); + + // only add the measurements that belong to the requested mac + for (const WiFiMeasurement& _m : _fp.measurements.entries) { + if (_m.getAP().getMAC() == mac) { + fp.measurements.entries.push_back(_m); + } + } + if (fp.measurements.entries.size() > 0) { // got some measurements for this AP? + res.push_back(fp); + } + } + + return res; + + } + + /** deserialize the model */ + void load() { + + // open and check + std::ifstream inp(file.c_str()); + if (inp.bad() || inp.eof() || ! inp.is_open()) { return; } + + // read all entries + while (!inp.eof()) { + + // each section starts with [fingerprint] + std::string section; + inp >> section; + if (inp.eof()) {break;} + if (section != "[fingerprint]") {throw Exception("error!");} + + // deserialize it + WiFiFingerprint wfp; + wfp.read(inp); + if (wfp.measurements.entries.empty()) {continue;} + fingerprints.push_back(wfp); + + } + + inp.close(); + + } + + + /** serialize the model */ + void save() { + + // open and check + std::ofstream out(file.c_str()); + if (out.bad()) {throw Exception("error while opening " + file + " for write");} + + // write all entries + for (const WiFiFingerprint& wfp : fingerprints) { + out << "[fingerprint]\n"; + wfp.write(out); + } + + // done + out.close(); + + } + + /** get the fingerprint for the given location. if no fingerprint exists, an empty one is created! */ + WiFiFingerprint& getFingerprint(const Point3 pos_m) { + + // try to find an existing one + for (WiFiFingerprint& wfp : fingerprints) { + // get within range of floating-point rounding issues + if (wfp.pos_m.getDistance(pos_m) < 0.01) {return wfp;} + } + + // create a new one and return its reference + WiFiFingerprint wfp(pos_m); + fingerprints.push_back(wfp); + return fingerprints.back(); + + } + +}; + +#endif // WIFIFINGERPRINTS_H diff --git a/sensors/radio/setup/WiFiOptimizer.h b/sensors/radio/setup/WiFiOptimizer.h index 5ac22b6..c9996c9 100644 --- a/sensors/radio/setup/WiFiOptimizer.h +++ b/sensors/radio/setup/WiFiOptimizer.h @@ -1,247 +1,61 @@ -#ifndef OPTIMIZER_H -#define OPTIMIZER_H - -#include "../../../floorplan/v2/Floorplan.h" -#include "../../../floorplan/v2/FloorplanHelper.h" +#ifndef WIFIOPTIMIZER_H +#define WIFIOPTIMIZER_H +#include "WiFiFingerprints.h" +#include "WiFiOptimizerStructs.h" #include "../VAPGrouper.h" -#include "../../../geo/BBox3.h" -#include "../../../misc/Debug.h" -#include "WiFiFingerprint.h" -#include "../model/WiFiModel.h" -#include "../model/WiFiModelLogDistCeiling.h" +#include -#include -#include -#include +namespace WiFiOptimizer { -#include -#include + /** base-class for all WiFiOptimizers */ + class Base { -struct WiFiOptimizer { + protected: -private: + /** each MAC-Adress has several position->rssi entries */ + std::unordered_map> apMap; - /** combine one RSSI measurement with the position the signal was measured at */ - struct RSSIatPosition { + /** how to handle virtual access points [group, not-group, ..] */ + const VAPGrouper vg; - /** real-world position (in meter) */ - const Point3 pos_m; - - /** measured signal strength (for one AP) */ - const float rssi; + public: /** ctor */ - RSSIatPosition(const Point3 pos_m, const float rssi) : pos_m(pos_m), rssi(rssi) {;} + Base(const VAPGrouper vg) : vg(vg) {;} - }; - -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> 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.getRSSI()); - apMap[m.getAP().getMAC()].push_back(rap); + /** get a list of all to-be-optimized access-points (given by their mac-address) */ + virtual std::vector getAllMACs() const { + std::vector res; + for (const auto& it : apMap) {res.push_back(it.first);} + return res; } - } + /** add a new fingerprint to the optimizer as data-source */ + virtual void addFingerprint(const WiFiFingerprint& fp) { - /** get a list of all to-be-optimized access-points (given by their mac-address) */ - std::vector getAllMACs() const { - std::vector res; - for (const auto& it : apMap) {res.push_back(it.first);} - return res; - } - - /** optimize all known APs */ - std::vector optimizeAll() const { - - // sanity chekc - Assert::isFalse(getAllMACs().empty(), "no APs found for optimization!"); - - float errSum = 0; - std::vector 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& 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;} + // 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.getRSSI()); + apMap[m.getAP().getMAC()].push_back(rap); } - err /= cnt; - err = std::sqrt(err); + } - if (params->txp < -50) {err += 999999;} - if (params->txp > -35) {err += 999999;} + /** add new fingerprints to the optimizer as data-source */ + virtual void addFingerprints(const WiFiFingerprints& fps) { + for (const WiFiFingerprint& fp : fps.getFingerprints()) { + addFingerprint(fp); + } + } - 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; - const std::vector 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; -// 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 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 +#endif // WIFIOPTIMIZER_H diff --git a/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h b/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h new file mode 100644 index 0000000..8bd3e42 --- /dev/null +++ b/sensors/radio/setup/WiFiOptimizerLogDistCeiling.h @@ -0,0 +1,341 @@ +#ifndef WIFI_OPTIMIZER_LOG_DIST_CEILING_H +#define WIFI_OPTIMIZER_LOG_DIST_CEILING_H + +#include "../../../floorplan/v2/Floorplan.h" +#include "../../../floorplan/v2/FloorplanHelper.h" + +#include "../../../geo/BBox3.h" +#include "../../../misc/Debug.h" + +#include "WiFiFingerprint.h" +#include "WiFiFingerprints.h" + +#include "../model/WiFiModel.h" +#include "../model/WiFiModelLogDistCeiling.h" + +#include +#include +#include + +#include +#include + +#include "WiFiOptimizer.h" +#include "WiFiOptimizerStructs.h" + +#include + +namespace WiFiOptimizer { + + /** + * optimize access-point parameters, + * given several fingerprints using the log-dist-ceiling model + */ + struct LogDistCeiling : public Base { + + public: + + enum class Mode { + FAST, + MEDIUM, + QUALITY, + }; + + /** + * resulting optimization stats for one AP + */ + struct Stats { + + /** average model<->scan error after optimzing */ + float error_db; + + /** number of fingerprints [= locations] that were used for optimzing */ + int usedFingerprins; + + /** resulting model<->scan error after optimzing for each individual fingerprints [= location] */ + std::vector errors; + + /** get the location where the model estimation reaches the highest negative value [model estimation too low] */ + ErrorAtPosition getEstErrorMaxNeg() const { + auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();}; + return *std::min_element(errors.begin(), errors.end(), cmpErrAtPos); + } + + /** get the location where the model estimation reaches the highest positive value [model estimation too high] */ + ErrorAtPosition getEstErrorMaxPos() const { + auto cmpErrAtPos = [] (const ErrorAtPosition& a, const ErrorAtPosition& b) {return a.getError_db() < b.getError_db();}; + return *std::max_element(errors.begin(), errors.end(), cmpErrAtPos); + } + + }; + + /** parameters for one AP when using the LogDistCeiling model */ + struct APParams { + + float x; + float y; + float z; + + float txp; + float exp; + float waf; + + Point3 getPos() const {return Point3(x,y,z);} + + /** ctor */ + APParams() {;} + + /** ctor */ + 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() const { + 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) {;} + }; + + class APParamsList { + + std::vector lst; + + public: + + /** ctor */ + APParamsList(const std::vector& lst) : lst(lst) { + + } + + /** get the list */ + const std::vector& get() const { + return lst; + } + + /** get params for the given mac [if known, otherwise nullptr] */ + const APParamsMAC* get (const MACAddress& mac) const { + for (const APParamsMAC& ap : lst) { + if (ap.mac == mac) {return ≈} + } + return nullptr; + } + + }; + + using APFilter = std::function; + + const APFilter NONE = [] (const int numFingerprints, const MACAddress& mac) { + (void) numFingerprints; (void) mac; + return false; + }; + + const APFilter MIN_8_FPS = [] (const int numFingerprints, const MACAddress& mac) { + (void) mac; + return numFingerprints < 8; + }; + + private: + + Mode mode = Mode::QUALITY; + + Floorplan::IndoorMap* map; + + const char* name = "WiFiOptLDC"; + + public: + + /** ctor */ + LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const Mode mode = Mode::QUALITY) : Base(vg), map(map), mode(mode) { + ; + } + + /** ctor */ + LogDistCeiling(Floorplan::IndoorMap* map, const VAPGrouper& vg, const WiFiFingerprints& fps, const Mode mode = Mode::QUALITY) : Base(vg), map(map), mode(mode) { + addFingerprints(fps); + } + + /** optimize all known APs */ + APParamsList optimizeAll(APFilter filter) const { + + // sanity check + Assert::isFalse(getAllMACs().empty(), "no APs found for optimization! call addFingerprint() first!"); + + float errSum = 0; int errCnt = 0; + std::vector res; + for (const MACAddress& mac : getAllMACs()) { + Stats stats; + const APParams params = optimize(mac, stats); + if (!filter(stats.usedFingerprins, mac)) { + res.push_back(APParamsMAC(mac, params)); + errSum += stats.error_db; + ++errCnt; + } else { + std::cout << "ignored due to filter!" << std::endl; + } + } + + const float avgErr = errSum / errCnt; + Log::add(name, "average AP error is: " + std::to_string(avgErr) + " dB"); + + // done + return APParamsList(res); + + } + + /** optimize the given AP */ + APParams optimize(const MACAddress& mac, Stats& res) const { + + // starting parameters do not matter for the current optimizer! + APParams params(0,0,0, -40, 2.5, -4.0); + + // get all position->rssi measurements for this AP to compare them with the corresponding model estimations + const std::vector& entries = apMap.find(mac)->second; + + // log + Log::add(name, "optimizing parameters for AP " + mac.asString() + " by using " + std::to_string(entries.size()) + " fingerprints", false); + Log::tick(); + + // get the map's size + const BBox3 mapBBox = FloorplanHelper::getBBox(map); + + using LeOpt = K::NumOptAlgoRangeRandom; + const std::vector 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, 5), // exp + LeOpt::MinMax(-15, -0), // waf + }; + + + + + LeOpt opt(valRegion); + + switch(mode) { + case Mode::FAST: + opt.setPopulationSize(100); + opt.setNumIerations(50); + break; + case Mode::MEDIUM: + opt.setPopulationSize(200); + opt.setNumIerations(100); + break; + case Mode::QUALITY: + opt.setPopulationSize(500); + opt.setNumIerations(150); + break; + } + + // error function + auto func = [&] (const float* params) { + return getErrorLogDistCeiling(mac, entries, params, nullptr); + }; + + opt.calculateOptimum(func, (float*) ¶ms); + + // using LeOpt = K::NumOptAlgoGenetic; + // 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 opt; + // opt.setMaxIterations(100); + // opt.setNumRestarts(10); + + opt.calculateOptimum(func, (float*) ¶ms); + res.error_db = getErrorLogDistCeiling(mac, entries, (float*)¶ms, &res); + res.usedFingerprins = entries.size(); + + Log::tock(); + Log::add(name, mac.asString() + ": " + params.asString() + " @ " + std::to_string(res.error_db) +" dB err"); + + return params; + + } + + + private: + + float getErrorLogDistCeiling(const MACAddress& mac, const std::vector& entries, const float* data, Stats* stats = nullptr) const { + + constexpr float hugeError = 1e10; + 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 + // signal-strength-prediction-model... + WiFiModelLogDistCeiling model(map); + 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); + + // add error to stats object? + if (stats) { + stats->errors.push_back(ErrorAtPosition(reading.pos_m, reading.rssi, rssiModel)); + } + + // 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; + + } + + + + }; + +} + + +#endif // WIFI_OPTIMIZER_LOG_DIST_CEILING_H diff --git a/sensors/radio/setup/WiFiOptimizerStructs.h b/sensors/radio/setup/WiFiOptimizerStructs.h new file mode 100644 index 0000000..63faac3 --- /dev/null +++ b/sensors/radio/setup/WiFiOptimizerStructs.h @@ -0,0 +1,55 @@ +#ifndef WIFIOPTIMIZERSTRUCTS_H +#define WIFIOPTIMIZERSTRUCTS_H + +#include "../../../geo/Point3.h" + +namespace WiFiOptimizer { + + /** + * one entry that is used during optimization: + * 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) {;} + + }; + + /** + * for statistics + * after optimiziaton + * + * denotes the difference [error] between one fingerprinted rssi + * at location (x,y,z) and the model estimation for this location + */ + struct ErrorAtPosition { + + /** real-world position (in meter) */ + const Point3 pos_m; + + /** measured signal strength (for one AP) */ + const float scan_rssi; + + /** final model's prediction */ + const float model_rssi; + + /** ctor */ + ErrorAtPosition(const Point3 pos_m, const float scan_rssi, const float model_rssi) : pos_m(pos_m), scan_rssi(scan_rssi), model_rssi(model_rssi) {;} + + /** get the difference [error] between model-estimated-rssi and fingerprinted-rssi */ + float getError_db() const { + return model_rssi - scan_rssi; + } + + }; + +} + +#endif // WIFIOPTIMIZERSTRUCTS_H diff --git a/tests/floorplan/TestFloorplanCeilings.cpp b/tests/floorplan/TestFloorplanCeilings.cpp new file mode 100644 index 0000000..21a5457 --- /dev/null +++ b/tests/floorplan/TestFloorplanCeilings.cpp @@ -0,0 +1,85 @@ +#ifdef WITH_TESTS + +#include "../Tests.h" +#include "../../floorplan/v2/FloorplanCeilings.h" +#include + + + +TEST(FloorplanCeilings, numCeilings) { + + // dummy floorplan + Floorplan::Floor* f0 = new Floorplan::Floor(); f0->atHeight = 0; + Floorplan::Floor* f1 = new Floorplan::Floor(); f1->atHeight = 3; + Floorplan::Floor* f2 = new Floorplan::Floor(); f2->atHeight = 7; + + Floorplan::IndoorMap map; + map.floors.push_back(f0); + map.floors.push_back(f1); + map.floors.push_back(f2); + + Floorplan::Ceilings model(&map); + + ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,-1), Point3(0,0,0)) ); + ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,0), Point3(0,0,-1)) ); + + ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,0), Point3(0,0,1)) ); + ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,1), Point3(0,0,0)) ); + + ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,-0.01), Point3(0,0,+0.01)) ); + ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,+0.01), Point3(0,0,-0.01)) ); + + ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,2.99), Point3(0,0,3.01)) ); + ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,3.01), Point3(0,0,2.99)) ); + + ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,6.99), Point3(0,0,7.01)) ); + ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,7.01), Point3(0,0,6.99)) ); + + ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,7.00), Point3(0,0,99)) ); + + ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,0), Point3(0,0,7)) ); + ASSERT_EQ(3, model.numCeilingsBetween(Point3(0,0,-1), Point3(0,0,8)) ); + +} + +TEST(FloorplanCeilings, numCeilingsFloat) { + + // dummy floorplan + Floorplan::Floor* f0 = new Floorplan::Floor(); f0->atHeight = 0; + Floorplan::Floor* f1 = new Floorplan::Floor(); f1->atHeight = 3; + Floorplan::Floor* f2 = new Floorplan::Floor(); f2->atHeight = 7; + + Floorplan::IndoorMap map; + map.floors.push_back(f0); + map.floors.push_back(f1); + map.floors.push_back(f2); + + Floorplan::Ceilings model(&map); + + const float d = 0.01; + + ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,-1), Point3(0,0,0)), d ); + ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,0), Point3(0,0,-1)), d ); + + ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,0), Point3(0,0,1)), d ); + ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,1), Point3(0,0,0)), d ); + + ASSERT_NEAR(0.5, model.numCeilingsBetweenFloat(Point3(0,0,-0.01), Point3(0,0,+0.50)), d ); + ASSERT_NEAR(0.5, model.numCeilingsBetweenFloat(Point3(0,0,+0.50), Point3(0,0,-0.01)), d ); + + ASSERT_NEAR(0.2, model.numCeilingsBetweenFloat(Point3(0,0,2.99), Point3(0,0,3.20)), d ); + ASSERT_NEAR(0.2, model.numCeilingsBetweenFloat(Point3(0,0,3.20), Point3(0,0,2.99)), d ); + + ASSERT_NEAR(1.0, model.numCeilingsBetweenFloat(Point3(0,0,6.99), Point3(0,0,8.33)), d ); + ASSERT_NEAR(1.0, model.numCeilingsBetweenFloat(Point3(0,0,8.33), Point3(0,0,6.99)), d ); + ASSERT_NEAR(2.0, model.numCeilingsBetweenFloat(Point3(0,0,0.00), Point3(0,0,8.33)), d ); + ASSERT_NEAR(2.0, model.numCeilingsBetweenFloat(Point3(0,0,8.33), Point3(0,0,0.00)), d ); + + ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,7.00), Point3(0,0,99)), d ); + + ASSERT_NEAR(1, model.numCeilingsBetweenFloat(Point3(0,0,0), Point3(0,0,7)), d ); + ASSERT_NEAR(3, model.numCeilingsBetweenFloat(Point3(0,0,-1), Point3(0,0,8)), d ); + +} + +#endif diff --git a/tests/sensors/TestMAC.cpp b/tests/sensors/TestMAC.cpp index 4638bf3..60e21d8 100644 --- a/tests/sensors/TestMAC.cpp +++ b/tests/sensors/TestMAC.cpp @@ -20,11 +20,59 @@ TEST(MAC, caseInsensitive) { } +TEST(MAC, invalidLength) { + + ASSERT_THROW(MACAddress("12:34:56:78:9A:B"), Exception); + ASSERT_THROW(MACAddress("2:34:56:78:9A:BC"), Exception); + + ASSERT_THROW(MACAddress("12:34:56:78:9A:"), Exception); + ASSERT_THROW(MACAddress("12:34:56:78:9A"), Exception); + ASSERT_THROW(MACAddress("12:34:56:78:9A:11:"), Exception); + ASSERT_THROW(MACAddress("12:34:56:78:9A:11:11"), Exception); + + +} + +TEST(MAC, invalidChars) { + + ASSERT_THROW(MACAddress("g2:34:56:78:9A:BC"), Exception); + ASSERT_THROW(MACAddress("a2:34:56:78:9A:BG"), Exception); + ASSERT_THROW(MACAddress("a2:34:5!:78:9A:BC"), Exception); + ASSERT_THROW(MACAddress("a2:34:51:78:?A:BC"), Exception); + +} + +TEST(MAC, ends) { + + MACAddress mac1("12:34:56:78:9A:BC"); + MACAddress mac2("12:34:56:78:9a:bc"); + MACAddress mac3("12:34:56:78:9a:bd"); + MACAddress mac4("02:34:56:78:9a:bc"); + MACAddress mac5("13:34:56:78:9a:bc"); + MACAddress mac6("12:34:56:78:9a:cc"); + + ASSERT_EQ(mac1, mac2); ASSERT_EQ(mac1.asString(), mac2.asString()); + + ASSERT_NE(mac1, mac3); ASSERT_NE(mac1.asString(), mac3.asString()); + ASSERT_NE(mac2, mac3); ASSERT_NE(mac2.asString(), mac3.asString()); + + ASSERT_NE(mac1, mac4); ASSERT_NE(mac1.asString(), mac4.asString()); + ASSERT_NE(mac2, mac4); ASSERT_NE(mac2.asString(), mac4.asString()); + + ASSERT_NE(mac1, mac5); ASSERT_NE(mac1.asString(), mac5.asString()); + ASSERT_NE(mac2, mac5); ASSERT_NE(mac2.asString(), mac5.asString()); + + ASSERT_NE(mac1, mac6); ASSERT_NE(mac1.asString(), mac6.asString()); + ASSERT_NE(mac2, mac6); ASSERT_NE(mac2.asString(), mac6.asString()); + +} + TEST(MAC, convertLong) { MACAddress mac1("12:34:56:78:9A:BC"); MACAddress mac2 = MACAddress( mac1.asLong() ); ASSERT_EQ(mac1, mac2); + ASSERT_EQ(mac1.asString(), mac2.asString()); } diff --git a/tests/sensors/radio/TestLogDistanceCeilingModel.cpp b/tests/sensors/radio/TestLogDistanceCeilingModel.cpp index f734d44..246b255 100644 --- a/tests/sensors/radio/TestLogDistanceCeilingModel.cpp +++ b/tests/sensors/radio/TestLogDistanceCeilingModel.cpp @@ -41,81 +41,6 @@ TEST(LogDistanceCeilingModel, calc) { } -TEST(LogDistanceCeilingModel, numCeilings) { - - // dummy floorplan - Floorplan::Floor* f0 = new Floorplan::Floor(); f0->atHeight = 0; - Floorplan::Floor* f1 = new Floorplan::Floor(); f1->atHeight = 3; - Floorplan::Floor* f2 = new Floorplan::Floor(); f2->atHeight = 7; - - Floorplan::IndoorMap map; - map.floors.push_back(f0); - map.floors.push_back(f1); - map.floors.push_back(f2); - - WiFiModelLogDistCeiling model(&map); - - ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,-1), Point3(0,0,0)) ); - ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,0), Point3(0,0,-1)) ); - - ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,0), Point3(0,0,1)) ); - ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,1), Point3(0,0,0)) ); - - ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,-0.01), Point3(0,0,+0.01)) ); - ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,+0.01), Point3(0,0,-0.01)) ); - - ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,2.99), Point3(0,0,3.01)) ); - ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,3.01), Point3(0,0,2.99)) ); - - ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,6.99), Point3(0,0,7.01)) ); - ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,7.01), Point3(0,0,6.99)) ); - - ASSERT_EQ(0, model.numCeilingsBetween(Point3(0,0,7.00), Point3(0,0,99)) ); - - ASSERT_EQ(1, model.numCeilingsBetween(Point3(0,0,0), Point3(0,0,7)) ); - ASSERT_EQ(3, model.numCeilingsBetween(Point3(0,0,-1), Point3(0,0,8)) ); - -} - -TEST(LogDistanceCeilingModel, numCeilingsFloat) { - - // dummy floorplan - Floorplan::Floor* f0 = new Floorplan::Floor(); f0->atHeight = 0; - Floorplan::Floor* f1 = new Floorplan::Floor(); f1->atHeight = 3; - Floorplan::Floor* f2 = new Floorplan::Floor(); f2->atHeight = 7; - - Floorplan::IndoorMap map; - map.floors.push_back(f0); - map.floors.push_back(f1); - map.floors.push_back(f2); - - WiFiModelLogDistCeiling model(&map); - - const float d = 0.01; - - ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,-1), Point3(0,0,0)), d ); - ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,0), Point3(0,0,-1)), d ); - - ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,0), Point3(0,0,1)), d ); - ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,1), Point3(0,0,0)), d ); - - ASSERT_NEAR(0.5, model.numCeilingsBetweenFloat(Point3(0,0,-0.01), Point3(0,0,+0.50)), d ); - ASSERT_NEAR(0.5, model.numCeilingsBetweenFloat(Point3(0,0,+0.50), Point3(0,0,-0.01)), d ); - - ASSERT_NEAR(0.2, model.numCeilingsBetweenFloat(Point3(0,0,2.99), Point3(0,0,3.20)), d ); - ASSERT_NEAR(0.2, model.numCeilingsBetweenFloat(Point3(0,0,3.20), Point3(0,0,2.99)), d ); - - ASSERT_NEAR(1.0, model.numCeilingsBetweenFloat(Point3(0,0,6.99), Point3(0,0,8.33)), d ); - ASSERT_NEAR(1.0, model.numCeilingsBetweenFloat(Point3(0,0,8.33), Point3(0,0,6.99)), d ); - ASSERT_NEAR(2.0, model.numCeilingsBetweenFloat(Point3(0,0,0.00), Point3(0,0,8.33)), d ); - ASSERT_NEAR(2.0, model.numCeilingsBetweenFloat(Point3(0,0,8.33), Point3(0,0,0.00)), d ); - - ASSERT_NEAR(0, model.numCeilingsBetweenFloat(Point3(0,0,7.00), Point3(0,0,99)), d ); - - ASSERT_NEAR(1, model.numCeilingsBetweenFloat(Point3(0,0,0), Point3(0,0,7)), d ); - ASSERT_NEAR(3, model.numCeilingsBetweenFloat(Point3(0,0,-1), Point3(0,0,8)), d ); - -} #endif diff --git a/tests/sensors/radio/TestWiFiOptimizer.cpp b/tests/sensors/radio/TestWiFiOptimizer.cpp index c640650..d91bf92 100644 --- a/tests/sensors/radio/TestWiFiOptimizer.cpp +++ b/tests/sensors/radio/TestWiFiOptimizer.cpp @@ -1,7 +1,7 @@ #ifdef WITH_TESTS #include "../../Tests.h" -#include "../../../sensors/radio/setup/WiFiOptimizer.h" +#include "../../../sensors/radio/setup/WiFiOptimizerLogDistCeiling.h" #include "../../../sensors/radio/setup/WiFiFingerprint.h" #include "../../../misc/Debug.h" @@ -55,23 +55,23 @@ TEST(WiFiOptimizer, optimize) { } - WiFiOptimizer opt(&map, vg); + WiFiOptimizer::LogDistCeiling opt(&map, vg); for (const WiFiFingerprint& fp : fingerprints) { opt.addFingerprint(fp); } ASSERT_EQ(2, opt.getAllMACs().size()); - float errRes; + WiFiOptimizer::LogDistCeiling::Stats errRes; - const WiFiOptimizer::APParams params1 = opt.optimize(mac1, errRes); - ASSERT_TRUE(errRes < 0.1); + const WiFiOptimizer::LogDistCeiling::APParams params1 = opt.optimize(mac1, errRes); + ASSERT_TRUE(errRes.error_db < 0.1); ASSERT_NEAR(0, pos1.getDistance(params1.getPos()), 0.4); // apx position estimation ASSERT_NEAR(-40, params1.txp, 1.0); ASSERT_NEAR(2, params1.exp, 0.1); ASSERT_NEAR(-4, params1.waf, 0.5); - const WiFiOptimizer::APParams params2 = opt.optimize(mac2, errRes); - ASSERT_TRUE(errRes < 0.1); + const WiFiOptimizer::LogDistCeiling::APParams params2 = opt.optimize(mac2, errRes); + ASSERT_TRUE(errRes.error_db < 0.1); ASSERT_NEAR(0, pos2.getDistance(params2.getPos()), 0.4); // apx position estimation ASSERT_NEAR(-40, params1.txp, 1.0); ASSERT_NEAR(2, params2.exp, 0.1);