Files
unison/src/mobility/helper/ns2-mobility-helper.cc

859 lines
28 KiB
C++

/*
* Copyright (c) 2007 INRIA
* 2009,2010 Contributors
*
* SPDX-License-Identifier: GPL-2.0-only
*
* Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
* Contributors: Thomas Waldecker <twaldecker@rocketmail.com>
* Martín Giachino <martin.giachino@gmail.com>
*
* Brief description: Implementation of a ns2 movement trace file reader.
*
* This implementation is based on the ns2 movement documentation of ns2
* as described in http://www.isi.edu/nsnam/ns/doc/node172.html
*
* Valid trace files use the following ns2 statements:
*
* $node set X_ x1
* $node set Y_ y1
* $node set Z_ z1
* $ns at $time $node setdest x2 y2 speed
* $ns at $time $node set X_ x1
* $ns at $time $node set Y_ Y1
* $ns at $time $node set Z_ Z1
*
*/
#include "ns2-mobility-helper.h"
#include "ns3/constant-velocity-mobility-model.h"
#include "ns3/log.h"
#include "ns3/node-list.h"
#include "ns3/node.h"
#include "ns3/simulator.h"
#include <fstream>
#include <map>
#include <sstream>
namespace ns3
{
NS_LOG_COMPONENT_DEFINE("Ns2MobilityHelper");
// Constants definitions
#define NS2_AT "at"
#define NS2_X_COORD "X_"
#define NS2_Y_COORD "Y_"
#define NS2_Z_COORD "Z_"
#define NS2_SETDEST "setdest"
#define NS2_SET "set"
#define NS2_NS_SCH "$ns_"
/**
* Type to maintain line parsed and its values
*/
struct ParseResult
{
std::vector<std::string> tokens; //!< tokens from a line
std::vector<int> ivals; //!< int values for each tokens
std::vector<bool> has_ival; //!< points if a tokens has an int value
std::vector<double> dvals; //!< double values for each tokens
std::vector<bool> has_dval; //!< points if a tokens has a double value
std::vector<std::string> svals; //!< string value for each token
};
/**
* Keeps last movement schedule. If new movement occurs during
* a current one, node stopping must be cancels (stored in a proper
* event ID), actually reached point must be calculated and new
* velocity must be calculated in accordance with actually reached
* destination.
*/
struct DestinationPoint
{
Vector m_startPosition; //!< Start position of last movement
Vector m_speed; //!< Speed of the last movement (needed to derive reached destination at next
//!< schedule = start + velocity * actuallyTravelled)
Vector m_finalPosition; //!< Final destination to be reached before next schedule. Replaced with
//!< actually reached if needed.
EventId m_stopEvent; //!< Event scheduling node's stop. May be canceled if needed.
double m_travelStartTime; //!< Travel start time is needed to calculate actually traveled time
double m_targetArrivalTime; //!< When a station arrives to a destination
DestinationPoint()
: m_startPosition(Vector(0, 0, 0)),
m_speed(Vector(0, 0, 0)),
m_finalPosition(Vector(0, 0, 0)),
m_travelStartTime(0),
m_targetArrivalTime(0)
{
}
};
/**
* Parses a line of ns2 mobility
* @param str the string to parse
* @returns The parsed line
*/
static ParseResult ParseNs2Line(const std::string& str);
/**
* Put out blank spaces at the start and end of a line
* @param str input line
* @returns the line trimmed
*/
static std::string TrimNs2Line(const std::string& str);
/**
* Checks if a string represents a number or it has others characters than digits and point.
* @param s the string to check
* @returns true if the string represents a number
*/
static bool IsNumber(const std::string& s);
/**
* Check if s string represents a numeric value
* @param str string to check
* @param ret numeric value to return
* @return true if string represents a numeric value
*/
template <class T>
static bool IsVal(const std::string& str, T& ret);
/**
* Checks if the value between brackets is a correct nodeId number
* @param str string to check
* @returns true if the string represents a nodeId number
*/
static bool HasNodeIdNumber(std::string str);
/**
* Gets nodeId number in string format from the string like $node_(4)
* @param str string to de-tokenize
* @returns A string with the nodeId number
*/
static std::string GetNodeIdFromToken(std::string str);
/**
* Get node id number in int format
* @param pr the ParseResult to analyze
* @returns the node ID (as an int)
*/
static int GetNodeIdInt(ParseResult pr);
/**
* Get node id number in string format
* @param pr the ParseResult to analyze
* @returns the node ID (as a string)
*/
static std::string GetNodeIdString(ParseResult pr);
/**
* Add one coord to a vector position
* @param actPos actual position (overwritten)
* @param coord coordinate (x, y, or z)
* @param value value of the coordinate
* @return The vector of the position
*/
static Vector SetOneInitialCoord(Vector actPos, std::string& coord, double value);
/**
* Check if this corresponds to a line like this: $node_(0) set X_ 123
* @param pr the ParseResult to analyze
* @returns true if the ParseResult looks like a coordinate without a scheduled time
*/
static bool IsSetInitialPos(ParseResult pr);
/**
* Check if this corresponds to a line like this: $ns_ at 1 "$node_(0) setdest 2 3 4"
* @param pr the ParseResult to analyze
* @returns true if the ParseResult looks like a coordinate with a scheduled time and destination
*/
static bool IsSchedSetPos(ParseResult pr);
/**
* Check if this corresponds to a line like this: $ns_ at 1 "$node_(0) set X_ 2"
* @param pr the ParseResult to analyze
* @returns true if the ParseResult looks like a coordinate with a scheduled time
*/
static bool IsSchedMobilityPos(ParseResult pr);
/**
* Set waypoints and speed for movement.
* @param model mobility model
* @param lastPos last position
* @param at initial movement time
* @param xFinalPosition final position (X axis)
* @param yFinalPosition final position (Y axis)
* @param speed movement speed
* @returns A descriptor of the movement
*/
static DestinationPoint SetMovement(Ptr<ConstantVelocityMobilityModel> model,
Vector lastPos,
double at,
double xFinalPosition,
double yFinalPosition,
double speed);
/**
* Set initial position for a node
* @param model mobility model
* @param coord coordinate (x, y, or z)
* @param coordVal value of the coordinate
* @return The vector of the position
*/
static Vector SetInitialPosition(Ptr<ConstantVelocityMobilityModel> model,
std::string coord,
double coordVal);
/**
* Schedule a set of position for a node
* @param model mobility model
* @param at initial movement time
* @param coord coordinate (x, y, or z)
* @param coordVal value of the coordinate
* @return The vector of the position at the given time
*/
static Vector SetSchedPosition(Ptr<ConstantVelocityMobilityModel> model,
double at,
std::string coord,
double coordVal);
Ns2MobilityHelper::Ns2MobilityHelper(std::string filename)
: m_filename(filename)
{
std::ifstream file(m_filename, std::ios::in);
if (!(file.is_open()))
{
NS_FATAL_ERROR("Could not open trace file " << m_filename
<< " for reading, aborting here \n");
}
}
Ptr<ConstantVelocityMobilityModel>
Ns2MobilityHelper::GetMobilityModel(std::string idString, const ObjectStore& store) const
{
std::istringstream iss;
iss.str(idString);
uint32_t id(0);
iss >> id;
Ptr<Object> object = store.Get(id);
if (!object)
{
return nullptr;
}
Ptr<ConstantVelocityMobilityModel> model = object->GetObject<ConstantVelocityMobilityModel>();
if (!model)
{
model = CreateObject<ConstantVelocityMobilityModel>();
object->AggregateObject(model);
}
return model;
}
void
Ns2MobilityHelper::ConfigNodesMovements(const ObjectStore& store) const
{
std::map<int, DestinationPoint> last_pos; // Stores previous movement scheduled for each node
//*****************************************************************
// Parse the file the first time to get the initial node positions.
//*****************************************************************
// Look through the whole the file for the the initial node
// positions to make this helper robust to handle trace files with
// the initial node positions at the end.
std::ifstream file(m_filename, std::ios::in);
if (file.is_open())
{
while (!file.eof())
{
int iNodeId = 0;
std::string nodeId;
std::string line;
getline(file, line);
// ignore empty lines
if (line.empty())
{
continue;
}
ParseResult pr = ParseNs2Line(line); // Parse line and obtain tokens
// Check if the line corresponds with setting the initial
// node positions
if (pr.tokens.size() != 4)
{
continue;
}
// Get the node Id
nodeId = GetNodeIdString(pr);
iNodeId = GetNodeIdInt(pr);
if (iNodeId == -1)
{
NS_LOG_ERROR("Node number couldn't be obtained (corrupted file?): " << line
<< "\n");
continue;
}
// get mobility model of node
Ptr<ConstantVelocityMobilityModel> model = GetMobilityModel(nodeId, store);
// if model not exists, continue
if (!model)
{
NS_LOG_ERROR("Unknown node ID (corrupted file?): " << nodeId << "\n");
continue;
}
/*
* In this case a initial position is being seted
* line like $node_(0) set X_ 151.05190721688197
*/
if (IsSetInitialPos(pr))
{
DestinationPoint point;
// coord coord value
point.m_finalPosition = SetInitialPosition(model, pr.tokens[2], pr.dvals[3]);
last_pos[iNodeId] = point;
// Log new position
NS_LOG_DEBUG("Positions after parse for node "
<< iNodeId << " " << nodeId
<< " position = " << last_pos[iNodeId].m_finalPosition);
}
}
file.close();
}
//*****************************************************************
// Parse the file a second time to get the rest of its values
//*****************************************************************
// The reason the file is parsed again is to make this helper robust
// to handle trace files with the initial node positions at the end.
file.open(m_filename, std::ios::in);
if (file.is_open())
{
while (!file.eof())
{
int iNodeId = 0;
std::string nodeId;
std::string line;
getline(file, line);
// ignore empty lines
if (line.empty())
{
continue;
}
ParseResult pr = ParseNs2Line(line); // Parse line and obtain tokens
// Check if the line corresponds with one of the three types of line
if (pr.tokens.size() != 4 && pr.tokens.size() != 7 && pr.tokens.size() != 8)
{
NS_LOG_ERROR("Line has not correct number of parameters (corrupted file?): "
<< line << "\n");
continue;
}
// Get the node Id
nodeId = GetNodeIdString(pr);
iNodeId = GetNodeIdInt(pr);
if (iNodeId == -1)
{
NS_LOG_ERROR("Node number couldn't be obtained (corrupted file?): " << line
<< "\n");
continue;
}
// get mobility model of node
Ptr<ConstantVelocityMobilityModel> model = GetMobilityModel(nodeId, store);
// if model not exists, continue
if (!model)
{
NS_LOG_ERROR("Unknown node ID (corrupted file?): " << nodeId << "\n");
continue;
}
/*
* In this case a initial position is being seted
* line like $node_(0) set X_ 151.05190721688197
*/
if (IsSetInitialPos(pr))
{
// This is the second time this file has been parsed,
// and the initial node positions were already set the
// first time. So, do nothing this time with this line.
continue;
}
else // NOW EVENTS TO BE SCHEDULED
{
// This is a scheduled event, so time at should be present
double at;
if (!IsNumber(pr.tokens[2]))
{
NS_LOG_WARN("Time is not a number: " << pr.tokens[2]);
continue;
}
at = pr.dvals[2]; // set time at
if (at < 0)
{
NS_LOG_WARN("Time is less than cero: " << at);
continue;
}
/*
* In this case a new waypoint is added
* line like $ns_ at 1 "$node_(0) setdest 2 3 4"
*/
if (IsSchedMobilityPos(pr))
{
if (last_pos[iNodeId].m_targetArrivalTime > at)
{
NS_LOG_LOGIC("Did not reach a destination! stoptime = "
<< last_pos[iNodeId].m_targetArrivalTime << ", at = " << at);
double actuallytraveled = at - last_pos[iNodeId].m_travelStartTime;
Vector reached = Vector(last_pos[iNodeId].m_startPosition.x +
last_pos[iNodeId].m_speed.x * actuallytraveled,
last_pos[iNodeId].m_startPosition.y +
last_pos[iNodeId].m_speed.y * actuallytraveled,
0);
NS_LOG_LOGIC("Final point = " << last_pos[iNodeId].m_finalPosition
<< ", actually reached = " << reached);
last_pos[iNodeId].m_stopEvent.Cancel();
last_pos[iNodeId].m_finalPosition = reached;
}
// last position time X coord Y
// coord velocity
last_pos[iNodeId] = SetMovement(model,
last_pos[iNodeId].m_finalPosition,
at,
pr.dvals[5],
pr.dvals[6],
pr.dvals[7]);
// Log new position
NS_LOG_DEBUG("Positions after parse for node "
<< iNodeId << " " << nodeId
<< " position =" << last_pos[iNodeId].m_finalPosition);
}
/*
* Scheduled set position
* line like $ns_ at 4.634906291962 "$node_(0) set X_ 28.675920486450"
*/
else if (IsSchedSetPos(pr))
{
// time coordinate coord value
last_pos[iNodeId].m_finalPosition =
SetSchedPosition(model, at, pr.tokens[5], pr.dvals[6]);
if (last_pos[iNodeId].m_targetArrivalTime > at)
{
last_pos[iNodeId].m_stopEvent.Cancel();
}
last_pos[iNodeId].m_targetArrivalTime = at;
last_pos[iNodeId].m_travelStartTime = at;
// Log new position
NS_LOG_DEBUG("Positions after parse for node "
<< iNodeId << " " << nodeId
<< " position =" << last_pos[iNodeId].m_finalPosition);
}
else
{
NS_LOG_WARN("Format Line is not correct: " << line << "\n");
}
}
}
file.close();
}
}
ParseResult
ParseNs2Line(const std::string& str)
{
ParseResult ret;
std::istringstream s;
std::string line;
// ignore comments (#)
size_t pos_sharp = str.find_first_of('#');
if (pos_sharp != std::string::npos)
{
line = str.substr(0, pos_sharp);
}
else
{
line = str;
}
line = TrimNs2Line(line);
// If line hasn't a correct node Id
if (!HasNodeIdNumber(line))
{
NS_LOG_WARN("Line has no node Id: " << line);
return ret;
}
s.str(line);
while (!s.eof())
{
std::string x;
s >> x;
if (x.empty())
{
continue;
}
ret.tokens.push_back(x);
int ii(0);
double d(0);
if (HasNodeIdNumber(x))
{
x = GetNodeIdFromToken(x);
}
ret.has_ival.push_back(IsVal<int>(x, ii));
ret.ivals.push_back(ii);
ret.has_dval.push_back(IsVal<double>(x, d));
ret.dvals.push_back(d);
ret.svals.push_back(x);
}
size_t tokensLength = ret.tokens.size(); // number of tokens in line
size_t lasTokenLength = ret.tokens[tokensLength - 1].size(); // length of the last token
// if it is a scheduled set _[XYZ] or a setdest I need to remove the last "
// and re-calculate values
if ((tokensLength == 7 || tokensLength == 8) &&
(ret.tokens[tokensLength - 1][lasTokenLength - 1] == '"'))
{
// removes " from the last position
ret.tokens[tokensLength - 1] = ret.tokens[tokensLength - 1].substr(0, lasTokenLength - 1);
std::string x;
x = ret.tokens[tokensLength - 1];
if (HasNodeIdNumber(x))
{
x = GetNodeIdFromToken(x);
}
// Re calculate values
int ii(0);
double d(0);
ret.has_ival[tokensLength - 1] = IsVal<int>(x, ii);
ret.ivals[tokensLength - 1] = ii;
ret.has_dval[tokensLength - 1] = IsVal<double>(x, d);
ret.dvals[tokensLength - 1] = d;
ret.svals[tokensLength - 1] = x;
}
else if ((tokensLength == 9 && ret.tokens[tokensLength - 1] == "\"") ||
(tokensLength == 8 && ret.tokens[tokensLength - 1] == "\""))
{
// if the line has the " character in this way: $ns_ at 1 "$node_(0) setdest 2 2 1 "
// or in this: $ns_ at 4 "$node_(0) set X_ 2 " we need to ignore this last token
ret.tokens.erase(ret.tokens.begin() + tokensLength - 1);
ret.has_ival.erase(ret.has_ival.begin() + tokensLength - 1);
ret.ivals.erase(ret.ivals.begin() + tokensLength - 1);
ret.has_dval.erase(ret.has_dval.begin() + tokensLength - 1);
ret.dvals.erase(ret.dvals.begin() + tokensLength - 1);
ret.svals.erase(ret.svals.begin() + tokensLength - 1);
}
return ret;
}
std::string
TrimNs2Line(const std::string& s)
{
std::string ret = s;
while (!ret.empty() && isblank(ret[0]))
{
ret.erase(0, 1); // Removes blank spaces at the beginning of the line
}
while (!ret.empty() && (isblank(ret[ret.size() - 1]) || (ret[ret.size() - 1] == ';')))
{
ret.erase(ret.size() - 1, 1); // Removes blank spaces from at end of line
}
return ret;
}
bool
IsNumber(const std::string& s)
{
char* endp;
strtod(s.c_str(), &endp);
return endp == s.c_str() + s.size();
}
template <class T>
bool
IsVal(const std::string& str, T& ret)
{
if (str.empty())
{
return false;
}
if (IsNumber(str))
{
std::istringstream s(str);
s >> ret;
return true;
}
else
{
return false;
}
}
bool
HasNodeIdNumber(std::string str)
{
// find brackets
std::string::size_type startNodeId = str.find_first_of('('); // index of left bracket
std::string::size_type endNodeId = str.find_first_of(')'); // index of right bracket
// Get de nodeId in a string and in a int value
std::string nodeId; // node id
// if no brackets, continue!
if (startNodeId == std::string::npos || endNodeId == std::string::npos)
{
return false;
}
nodeId = str.substr(startNodeId + 1, endNodeId - (startNodeId + 1)); // set node id
// is number is integer is not negative
return IsNumber(nodeId) && nodeId.find_first_of('.') == std::string::npos && nodeId[0] != '-';
}
std::string
GetNodeIdFromToken(std::string str)
{
if (HasNodeIdNumber(str))
{
// find brackets
std::string::size_type startNodeId = str.find_first_of('('); // index of left bracket
std::string::size_type endNodeId = str.find_first_of(')'); // index of right bracket
return str.substr(startNodeId + 1, endNodeId - (startNodeId + 1)); // set node id
}
else
{
return "";
}
}
int
GetNodeIdInt(ParseResult pr)
{
int result = -1;
switch (pr.tokens.size())
{
case 4: // line like $node_(0) set X_ 11
result = pr.ivals[0];
break;
case 7: // line like $ns_ at 4 "$node_(0) set X_ 28"
case 8: // line like $ns_ at 1 "$node_(0) setdest 2 3 4"
result = pr.ivals[3];
break;
default:
result = -1;
}
return result;
}
// Get node id number in string format
std::string
GetNodeIdString(ParseResult pr)
{
switch (pr.tokens.size())
{
case 4: // line like $node_(0) set X_ 11
return pr.svals[0];
case 7: // line like $ns_ at 4 "$node_(0) set X_ 28"
case 8: // line like $ns_ at 1 "$node_(0) setdest 2 3 4"
return pr.svals[3];
default:
return "";
}
}
Vector
SetOneInitialCoord(Vector position, std::string& coord, double value)
{
// set the position for the coord.
if (coord == NS2_X_COORD)
{
position.x = value;
NS_LOG_DEBUG("X=" << value);
}
else if (coord == NS2_Y_COORD)
{
position.y = value;
NS_LOG_DEBUG("Y=" << value);
}
else if (coord == NS2_Z_COORD)
{
position.z = value;
NS_LOG_DEBUG("Z=" << value);
}
return position;
}
bool
IsSetInitialPos(ParseResult pr)
{
// number of tokens has $node_( ? has "set" has
// double for position?
return pr.tokens.size() == 4 && HasNodeIdNumber(pr.tokens[0]) && pr.tokens[1] == NS2_SET &&
pr.has_dval[3]
// coord name is X_, Y_ or Z_ ?
&& (pr.tokens[2] == NS2_X_COORD || pr.tokens[2] == NS2_Y_COORD ||
pr.tokens[2] == NS2_Z_COORD);
}
bool
IsSchedSetPos(ParseResult pr)
{
// correct number of tokens, has $ns_ and at
return pr.tokens.size() == 7 && pr.tokens[0] == NS2_NS_SCH && pr.tokens[1] == NS2_AT &&
pr.tokens[4] == NS2_SET && pr.has_dval[2] &&
pr.has_dval[3] // has set and double value for time and nodeid
&& (pr.tokens[5] == NS2_X_COORD || pr.tokens[5] == NS2_Y_COORD ||
pr.tokens[5] == NS2_Z_COORD) // has X_, Y_ or Z_?
&& pr.has_dval[2]; // time is a number
}
bool
IsSchedMobilityPos(ParseResult pr)
{
// number of tokens and has $ns_ and has at
return pr.tokens.size() == 8 && pr.tokens[0] == NS2_NS_SCH &&
pr.tokens[1] == NS2_AT
// time x coord y coord velocity are numbers?
&& pr.has_dval[2] && pr.has_dval[5] && pr.has_dval[6] && pr.has_dval[7] &&
pr.tokens[4] == NS2_SETDEST; // and has setdest
}
DestinationPoint
SetMovement(Ptr<ConstantVelocityMobilityModel> model,
Vector last_pos,
double at,
double xFinalPosition,
double yFinalPosition,
double speed)
{
DestinationPoint retval;
retval.m_startPosition = last_pos;
retval.m_finalPosition = last_pos;
retval.m_travelStartTime = at;
retval.m_targetArrivalTime = at;
if (speed == 0)
{
// We have to maintain last position, and stop the movement
retval.m_stopEvent = Simulator::Schedule(Seconds(at),
&ConstantVelocityMobilityModel::SetVelocity,
model,
Vector(0, 0, 0));
return retval;
}
if (speed > 0)
{
// first calculate the time; time = distance / speed
double time = std::sqrt(std::pow(xFinalPosition - retval.m_finalPosition.x, 2) +
std::pow(yFinalPosition - retval.m_finalPosition.y, 2)) /
speed;
NS_LOG_DEBUG("at=" << at << " time=" << time);
if (time == 0)
{
return retval;
}
// now calculate the xSpeed = distance / time
double xSpeed = (xFinalPosition - retval.m_finalPosition.x) / time;
double ySpeed = (yFinalPosition - retval.m_finalPosition.y) / time; // & same with ySpeed
retval.m_speed = Vector(xSpeed, ySpeed, 0);
// quick and dirty set zSpeed = 0
double zSpeed = 0;
NS_LOG_DEBUG("Calculated Speed: X=" << xSpeed << " Y=" << ySpeed << " Z=" << zSpeed);
// Set the Values
Simulator::Schedule(Seconds(at),
&ConstantVelocityMobilityModel::SetVelocity,
model,
Vector(xSpeed, ySpeed, zSpeed));
retval.m_stopEvent = Simulator::Schedule(Seconds(at + time),
&ConstantVelocityMobilityModel::SetVelocity,
model,
Vector(0, 0, 0));
retval.m_finalPosition.x += xSpeed * time;
retval.m_finalPosition.y += ySpeed * time;
retval.m_targetArrivalTime += time;
}
return retval;
}
Vector
SetInitialPosition(Ptr<ConstantVelocityMobilityModel> model, std::string coord, double coordVal)
{
model->SetPosition(SetOneInitialCoord(model->GetPosition(), coord, coordVal));
Vector position;
position.x = model->GetPosition().x;
position.y = model->GetPosition().y;
position.z = model->GetPosition().z;
return position;
}
// Schedule a set of position for a node
Vector
SetSchedPosition(Ptr<ConstantVelocityMobilityModel> model,
double at,
std::string coord,
double coordVal)
{
// update position
model->SetPosition(SetOneInitialCoord(model->GetPosition(), coord, coordVal));
Vector position;
position.x = model->GetPosition().x;
position.y = model->GetPosition().y;
position.z = model->GetPosition().z;
// Schedule next positions
Simulator::Schedule(Seconds(at), &ConstantVelocityMobilityModel::SetPosition, model, position);
return position;
}
void
Ns2MobilityHelper::Install() const
{
Install(NodeList::Begin(), NodeList::End());
}
} // namespace ns3