diff --git a/doc/uwsn/aqua-sim-ng-packet-path.png b/doc/uwsn/aqua-sim-ng-packet-path.png new file mode 100644 index 00000000..f52a50fe Binary files /dev/null and b/doc/uwsn/aqua-sim-ng-packet-path.png differ diff --git a/doc/uwsn/build.sh b/doc/uwsn/build.sh new file mode 100755 index 00000000..6756b43a --- /dev/null +++ b/doc/uwsn/build.sh @@ -0,0 +1 @@ +latexmk -bibtex -pdf -pdflatex="pdflatex -halt-on-error -interaction=nonstopmode -shell-escape" -use-make channel.tex diff --git a/doc/uwsn/channel.tex b/doc/uwsn/channel.tex new file mode 100644 index 00000000..0fe43992 --- /dev/null +++ b/doc/uwsn/channel.tex @@ -0,0 +1,137 @@ +\documentclass[a4paper]{article} +\usepackage{amsmath} +\usepackage[utf8]{inputenc} +\usepackage[english]{babel} +\usepackage{cleveref} +\usepackage{textcomp} +\usepackage{graphicx} +\usepackage[style=ieee]{biblatex} +\addbibresource{channel.bib} + + +\title{Channel models for terrestrial radio and acoustic underwater links} +\author{Bálint Áron Üveges} +\date{\today} + +\begin{document} +\maketitle + +\section{Castalia's path loss model} +Castalia utilizes the Log-distance path loss model: +\begin{equation} + PL_{d} = PL_{d0} + 10.0 \cdot \gamma \cdot log_{10} \left( \frac{d}{d_0} \right) + \mathcal{N}(0,\sigma) + \frac{\mathcal{N}(0,\sigma_{b})}{2} +\label{eq:log_d_pl} +\end{equation} +In \eqref{eq:log_d_pl}, $PL_{d0}$ is the path loss at the reference distance $d_{0}$; $d$ is the length of the path; $\gamma$ is the path loss exponent; $\mathcal{N}$ is the Gaussian-distribution, $\sigma$ is the standard variance of shadow fading; whereas $\sigma_{b}$ is the standard variance of bidirectional path loss jitter. + +\section{Underwater acoustic channel path loss model} +According to \cite{Stojanovic_2007}, the narrowband + +\section{Castalia's parameters, bits and pieces} +\subsection{Parameters} +defaultChannel: +\begin{itemize} + \item \texttt{signalDeliveryThreshold} -- threshold in dBm above which, wireless channel module is delivering signal messages to radio modules of individual nodes. +\end{itemize} + +radio: +\begin{itemize} + \item \texttt{carrierFreq} -- The carrier frequency in MHz. + \item \texttt{rssiIntegrationTime} -- rssiIntegrationTime = symbolsForRSSI * RXmode\textrightarrow bitsPerSymbol / RXmode \textrightarrow datarate + \item \texttt{timeToTxPacket} -- Provided by popAndSendToWirelessChannel(), calculated as: double txTime = ((double)(end \textrightarrow getByteLength() * 8.0f)) / RXmode \textrightarrow datarate; (in seconds) + \item \texttt{parameter file} -- Name, dataRate(kbps), modulationType, bitsPerSymbol, bandwidth(MHz), noiseBandwidth(KHz), noiseFloor(dBm), sensitivity(dBm), powerConsumed(mW) + \item \texttt{SNR2BER} -- method used to calculate BER based on SNR and the actual modulation. +\end{itemize} + +\subsubsection{Packet traversal} +Castalia's packet path is relatively simple in terms of utilized objects: +MAC \textrightarrow Radio \textrightarrow Wireless Channel \textrightarrow Radio \textrightarrow MAC. + +\subsubsection{MAC} +Castalia's T-MAC implementation depends on the \texttt{TX\_TIME} function call when determining timeouts. The function is: \texttt{(phyLayerOverhead + x)*1/(1000 * phyDataRate/8.0)}, and is interpreted in seconds. + +\subsubsection{Radio} +The Radio module buffers incoming MAC packets, and schedules itself for sending. Each packet is broken into two parts: a \textit{begin} and an \textit{end} message. The begin message contains the output power in dBm, the frequency, bandwidth, modulation and encoding, whereas the end message contains the actual data and the physical frame overhead (as size?). The begin message is immediately sent to the wireless channel, whereas the end message is scheduled with \texttt{txTime} delay. The delay is calculated as \texttt{(double)(end->getByteLength() * 8.0f)) / RXmode->datarate;} + +\subsubsection{Physical Channel} +Once receiving the \texttt{WC\_SIGNAL\_START}, the physical channel module iterates over all cells, and collects those, which are affected by the sender based on the signal delivery threshold and the received signal strength attenuated by the path loss. A copy of the begin message is also sent with the calculated received signal strength. The \texttt{WC\_SIGNAL\_END} part of the handling iterates over the collected nodes, and sends the end message to each node. + +\noindent \textbf{Note:} contrary to AquaSim, in Castalia there is no delay on the wireless channel. However, a feasible solution would be to delay both the begin and the end message based on the distance between the sender and the receiver. + +\noindent \textbf{Note 2:} Castalia's major "weakness" is that the pathloss between each node is static for the simulation run. Whereas it is recommended do repeat a simulation scenario multiple times \cite{Ritter_2011}, a more "living" channel model would be more beneficial. A solution could be that the Gaussian noise and the asymmetric noise is recalculated in case of each \texttt{WC\_SIGNAL\_START} message in the physical channel. + +\subsubsection{Radio} +The Radio module calculates bit errors and iterates through all received signals since the last end signal. Each signal's bit error is additively updated based on BER. If bit errors are not exceeded based on encoding, the packet s forwarded to the MAC layer with \texttt{PROCESSING\_DELAY} set to $0.00001$. + +\section{AquaSim's parameters, bits and pieces} +\subsection{Packet traversal} + +Packets generated by an AquaSim node traverses along the data path illustrated in \cref{fig:aquasim-packet-path}. From propagation perspective, it is only relevant to analyze the traversal from the MAC layer. + +\begin{figure}[h] +\centering +\includegraphics[width=\textwidth]{aqua-sim-ng-packet-path.png} + \caption{Path of a packet traversing between different components} +\label{fig:aquasim-packet-path} +\end{figure} + +\subsubsection{\texttt{AquaSimMac::TxProcess}} +The \texttt{TxProcess} is a virtual method, provided by the \texttt{AquaSimMac} class. In case of the \texttt{AquaSimBroadcastMac} class, it performs backoff or drops the packet if the device is not in \texttt{NIDLE} state. In \texttt{NIDLE} state, the MAC and AquaSim headers are added, the transmission time determined, and the \texttt{SendDown} method from the parent class is called. + +\subsubsection{\texttt{AquaSimMac::SendDown}} +The \texttt{SendDown} method either sends the packet directly to the Radio, via \texttt{AquaSimPhy}'s \texttt{Recv} (\texttt{IDLE} state), queues the packet (\texttt{RECV} state), or drops the packet (\texttt{SLEEP} state). The method also addds the \texttt{AquaSimPacketStamp} header and schedules the \texttt{SetTransmissionStatus} method call once the transmission is finished. + +\subsubsection{\texttt{AquaSimPhy::Recv}}\label{sssec:radio_recv} +In reality, the \texttt{Recv} method is implemented in the \texttt{AquaSimPhyCmn} class, derived from the \texttt{AquaSimPhy}. Depending on the direction encoded in the packet's AquaSim header, the packet is either handed to the \texttt{PktTransmit} method (\texttt{DOWN}), or after prevalidation detailed in \cref{sssec:prevalid}, queued into the \texttt{m\_sC} (\texttt{UP}). + +\subsubsection{\texttt{AquaSimPhy::PktTransmit}} +Similarly to the \texttt{Recv} method, the \texttt{PktTransmit} is also implemented in the \texttt{AquaSimPhyCmn} class. The method performs checks with regards to failure state, \texttt{SLEEP} state, and energy level. It substracts the power used to perform Tx from the battery. Stamps TX info, that is, \texttt{m\_pT}, \texttt{m\_lambda}, \texttt{m\_freq}, and \texttt{m\_transRange}. It also adjusts \texttt{EM}'s TX power accordingly. The method also schedules a timer to return to \texttt{NIDLE} state based on the delay, calculated based on modulation and size. Finally, the method notifies the corresponding channel, indicating the channel ID to the \texttt{Recv} method. + +One interesting thing is, that the \texttt{m\_channel} is a vector of channel pointers (smart). + +\subsubsection{\texttt{AquaSimChannel::Recv}} +The \texttt{Recv} method is just simply calling the \texttt{SendUp} method. Whereas the \texttt{Recv} returns a boolean value, it is not evaluated by the callee method, \texttt{PktTransmit}. + +\subsubsection{\texttt{AquaSimChannel::SendUp}} +The \texttt{SendUp} method first collects all devices that may receive the signal. It is down by calling the \texttt{{ReceivedCopies}} method, detailed in \cref{sssec:reccop}. The result is not a simple array of devices, it is also annotated with delay and received power. + +The method iterates over all recipients, calculates delay (once again, however it is already done by \texttt{ReceivedCopies} at this stage), and retrieves the stamp from the packet. Based on the recipient's annotated data, it adds the received power to the stamp, adds noise and distance-based propagation delay. Finally, it schedules each copy to arrive at the corresponding device's Radio, with the calculated delay. + +The noise is calculated by the \texttt{AquaSimNoiseGen::Noise} method. The method utilizes the noise model described in \cite{Stojanovic_2007}, in section II.B. Whereas the model uses \textit{dB}, prior to returning with the result, the method raises the result to the power of $10$. + +The behaviour of the Radio's \texttt{Recv} module is already explained in \cref{sssec:radio_recv}. + +\subsubsection{\texttt{AquaSimPropagation::ReceivedCopies}}\label{sssec:reccop} +The \texttt{RecievedCopies} inspects the stamp header to retrieve frequency and Tx power, calculates distance of the sender and all receivers and the Rx power and delay related to the Packet as it arrives to the receiver. Interestingly, no receivers are filtered based on the transmission range. + +\subsubsection{\texttt{AuqaSimPhy::PrevalidateIncomingPkt}}\label{sssec:prevalid} +The \texttt{PrevalidateIncomingPkt} method performs frequency matching and device failure check. If the device is in \texttt{RECV} mode or the received signal strength does not exceed the RX threshold, the packet is flagged with error (\texttt{SetErrorFlag}) and the collision flag is set on Radio level. Otherwise the device is set to \texttt{RECV} state (without any prior check), and a call to \texttt{SetTransmissionStatus} is scheduled. Quite ambiguously, the state change's timer is calculated via the \texttt{CalcTxTime}, utilizing the AquaSim header's \texttt{size} property. The method also takes the preamble length into account and the modulation. The time is calculated per bit. + +The method also updates the energy consumption, but in this case with the AquaSim header's getTxTime results. The question is: why? + +Finally, if the packet is not flagged with error, it schedules the \texttt{CollisionCheck} method, but returns with a null pointer. However, if the method identifies failure, it returns a pointer to the packet. + +Note: Both the \texttt{CollisionCheck} method and the \texttt{Recv} schedules the packet to \texttt{m\_sC} variable (signal cache) with the \texttt{AddNewPacket} method. + +\subsubsection{\texttt{AquaSimSignalCache::AddNewPacket}} +The \texttt{AddNewPacket} method performs check on the AquaSim header's error flag, and casts the packet as \texttt{IncomingPacket} with adding the information. The method calls the \texttt{AddNewSubmission} method, where based on AquaSim's header size the \texttt{Expire} method id scheduled, which calls the \texttt{SubmitPkt} method. + +The \texttt{AddNewPacket} method stores the packet in a linked list, as the first element. + +The method also adjusts the power strength (\textit{this is what (s)he said}) and calls the \texttt{UpdatePacketStatus} method. The aforementioned method iterates through the linked list of packets, and calculates the noise level, as $p_{noise} = p_{total} - p_{s}$, where $p_{total}$ is set to the value of the total RX power in the \texttt{AddNewSubmission} additively (\texttt{+=} operator), and the $p_{s}$ is also set to RX power, retrieved from the energy modul (model). The method calls the Radio's \texttt{Decodable}, that is just a guard method before calling the SINR checkerwith $\frac{p_{s}}{p_{noise}}$, evaluated against \texttt{m\_decThresh}, which is set to $0$. + +\subsubsection{\texttt{AquaSimSignalCache::SubmitPkt}} +The \texttt{SubmitPkt} method calls the \texttt{DeleteIncomingPacket} method, which removes the packet from the linked list. The total energy is also decreased with the TX energy, retrieved from the EM module. If the packet is not invalidated until now by the noise or by the energy level, the \texttt{SignalCacheCallback} is called. + +\subsubsection{\texttt{AquaSimPhy::SignalCacheCallback}} +The \texttt{SignalCacheCallback} updates the AquaSim header with TX time set to $0.1$ s and calls the \texttt{SendPktUp} method. + +\subsubsection{\texttt{AquaSimPhy::SendPktUp}} +The \texttt{SendPktUp} method dispatches the packet based on the MAC header's \texttt{m\_demuxPType}. The field is set to \texttt{UWPTYPE\_OTHER} as default, and the method calls the MAC layer's \texttt{RecvProcess} method. + +\subsubsection{\texttt{AquaSimMac::RecvProcess}} +The \texttt{RecvProcess} method evaluates the AquaSim header's error flag, and either discards the packet or forwards to the upper layer (at least in case of Broadcast MAC). + + +\printbibliography +\end{document} diff --git a/src/node/communication/radio/Radio.h b/src/node/communication/radio/Radio.h index cdd9bfcc..d576ae7e 100644 --- a/src/node/communication/radio/Radio.h +++ b/src/node/communication/radio/Radio.h @@ -234,6 +234,7 @@ class Radio: public CastaliaModule { double readRSSI(); CCA_result isChannelClear(); PktBreakdown getStats() { return stats; }; + double getCarrierFrequency() { return carrierFreq;}; }; #endif //_RADIOMODULE_H_ diff --git a/src/wirelessChannel/underwaterChannel/UnderWaterChannel.cc b/src/wirelessChannel/underwaterChannel/UnderWaterChannel.cc new file mode 100644 index 00000000..781dcd72 --- /dev/null +++ b/src/wirelessChannel/underwaterChannel/UnderWaterChannel.cc @@ -0,0 +1,680 @@ +/**************************************************************************** + * Copyright: National ICT Australia, 2007 - 2010 * + * Developed at the ATP lab, Networked Systems research theme * + * Author(s): Athanassios Boulis, Yuriy Tselishchev * + * This file is distributed under the terms in the attached LICENSE file. * + * If you do not find this file, copies can be found by writing to: * + * * + * NICTA, Locked Bag 9013, Alexandria, NSW 1435, Australia * + * Attention: License Inquiry. * + * * + ****************************************************************************/ + +#include "wirelessChannel/underwaterChannel/UnderWaterChannel.h" + +Define_Module(UnderWaterChannel); + +int UnderWaterChannel::numInitStages() const +{ + return 2; +} + +void UnderWaterChannel::initialize(int stage) +{ + if (stage == 0) { + readIniFileParameters(); + return; + } + + /* variable to report initialization run time */ + clock_t startTime; + startTime = clock(); + + /**************************************************** + * To handle mobile nodes we break up the space into + * cells. All path loss calculations are now done + * between cells. First we need to find out how many + * distinct cells we have in space, based on the + * dimensions of the space and the declared cell size. + * If we only have static nodes, then we keep the + * same variables and abstractions to make the code + * more compact and easier to read, but we do not + * really need to divide the space into cells. Even + * though we keep the variable names, cells in the + * static case only correspond to node positions + * and we only have numOfNodes cells, much like + * we used to do in Castalia 1.3 + ****************************************************/ + + if (onlyStaticNodes) { + numOfSpaceCells = numOfNodes; + } else { + throw cRuntimeError("Mobility not implemented\n"); + if (xFieldSize <= 0) { + xFieldSize = 1; + xCellSize = 1; + } + if (yFieldSize <= 0) { + yFieldSize = 1; + yCellSize = 1; + } + if (zFieldSize <= 0) { + zFieldSize = 1; + zCellSize = 1; + } + if (xCellSize <= 0) + xCellSize = xFieldSize; + if (yCellSize <= 0) + yCellSize = yFieldSize; + if (zCellSize <= 0) + zCellSize = zFieldSize; + + numOfZCells = (int)ceil(zFieldSize / zCellSize); + numOfYCells = (int)ceil(yFieldSize / yCellSize); + numOfXCells = (int)ceil(xFieldSize / xCellSize); + numOfSpaceCells = numOfZCells * numOfYCells * numOfXCells; + + /*************************************************************** + * Calculate some values that help us transform a 1D index in + * [0..numOfSpaceCells -1] to a 3D index x, y, z and vice versa. + * Each variable holds index increments (in the 1D large index) + * needed to move one space cell in the z, y, and x directions + **************************************************************/ + zIndexIncrement = numOfYCells * numOfXCells; + yIndexIncrement = numOfXCells; + xIndexIncrement = 1; + } + + /*************************************************************** + * Allocate and initialize cellOccupation and nodeLocation. + * nodeLocation keeps the state about all nodes locations and + * cellOccupation is an array of lists. List at index i contains + * the node IDs that reside in cell i. We define and use these + * arrays even for the static nodes case as it makes the code + * more compact and easier to follow. + **************************************************************/ + nodeLocation = new NodeLocation_type[numOfNodes]; + if (nodeLocation == NULL) + throw cRuntimeError("Could not allocate array nodeLocation\n"); + + cellOccupation = new list[numOfSpaceCells]; + if (cellOccupation == NULL) + throw cRuntimeError("Could not allocate array cellOccupation\n"); + + cTopology *topo; // temp variable to access initial location of the nodes + topo = new cTopology("topo"); + topo->extractByNedTypeName(cStringTokenizer("node.Node").asVector()); + + + Radio *radio = check_and_cast(topo->getNode(0)->getModule()->getSubmodule("Communcation")->getSubmodule("Radio")); + carrier_frequency=radio->getCarrierFrequency(); + for (int i = 0; i < numOfNodes; i++) { + VirtualMobilityManager *nodeMobilityModule = + check_and_cast + (topo->getNode(i)->getModule()->getSubmodule("MobilityManager")); + nodeLocation[i] = nodeMobilityModule->getLocation(); + nodeLocation[i].cell = i; + + if (!onlyStaticNodes) { + throw cRuntimeError("Mobility for underwater channel is not supported\n"); + + /****************************************************************** + * Compute the cell this node is in and initialize cellOccupation. + * Cavaet in computing the XYZ indices: + * Because we allow cell resolutions that do not perfectly divide + * the field (we take the ceiling of the division when calculating + * numOfXCells) this means that the edge cells might be smaller than + * the others. So in some cases, the calculation we are doing + * below, might give the wrong cell by +1. That's why we are doing + * the test immediately after. + ******************************************************************/ + int xIndex = (int)floor(nodeLocation[i].x / xFieldSize * numOfXCells); + if (((xIndex - 1) * xCellSize) >= nodeLocation[i].x) + xIndex--; + else if (xIndex >= numOfXCells) { + xIndex = numOfXCells - 1; // the maximum possible x index + if (nodeLocation[i].x > xFieldSize) + trace() << "WARNING at initialization: node position out of bounds in X dimension!\n"; + } + + int yIndex = (int)floor(nodeLocation[i].y / yFieldSize * numOfYCells); + if (((yIndex - 1) * yCellSize) >= nodeLocation[i].y) + yIndex--; + else if (yIndex >= numOfYCells) { + yIndex = numOfYCells - 1; // the maximum possible y index + if (nodeLocation[i].y > yFieldSize) + trace() << "WARNING at initialization: node position out of bounds in Y dimension!\n"; + } + + int zIndex = (int)floor(nodeLocation[i].z / zFieldSize * numOfZCells); + if (((zIndex - 1) * zCellSize) >= nodeLocation[i].z) + zIndex--; + else if (zIndex >= numOfZCells) { + zIndex = numOfZCells - 1; // the maximum possible z index + if (nodeLocation[i].z > zFieldSize) + trace() << "WARNING at initialization: node position out of bounds in Z dimension!\n"; + } + + int cell = zIndex * zIndexIncrement + yIndex * yIndexIncrement + xIndex * xIndexIncrement; + if (cell < 0 || cell >= numOfSpaceCells) { + throw cRuntimeError("Cell out of bounds for node %i, please check your mobility module settings\n", i); + } + + nodeLocation[i].cell = cell; + } + + /************************************************* + * pushing ID i into the list cellOccupation[cell] + * (if onlyStaticNodes cell=i ) + *************************************************/ + cellOccupation[nodeLocation[i].cell].push_front(i); + } + delete(topo); + + /********************************************** + * Allocate and initialize the pathLoss array. + * This is the "propagation map" of our space. + **********************************************/ + pathLoss = new list[numOfSpaceCells]; + if (pathLoss == NULL) + throw cRuntimeError("Could not allocate array pathLoss\n"); + + int elementSize = sizeof(PathLossElement) + 3 * sizeof(PathLossElement *); + int totalElements = 0; //keep track of pathLoss size for reporting purposes + + float x1, x2, y1, y2, z1, z2, dist; + float PLd; // path loss at distance dist, in dB + float bidirectionalPathLossJitter; // variation of the pathloss in the two directions of a link, in dB + + /******************************************************* + * Calculate the distance, beyond which we cannot + * have connectivity between two nodes. This calculation is + * based on the maximum TXPower the signalDeliveryThreshold + * the pathLossExponent, the PLd0. For the random + * shadowing part we use 3*sigma to account for 99.7% + * of the cases. We use this value to considerably + * speed up the filling of the pathLoss array, + * especially for the mobile case. + *******************************************************/ + float distanceThreshold = 0; + // = d0 * +// pow(10.0,(maxTxPower - signalDeliveryThreshold - PLd0 + 3 * sigma) / +// (10.0 * pathLossExponent)); + + for (int i = 0; i < numOfSpaceCells; i++) { + if (onlyStaticNodes) { + x1 = nodeLocation[i].x; + y1 = nodeLocation[i].y; + z1 = nodeLocation[i].z; + } else { + z1 = zCellSize * (int)floor(i / zIndexIncrement); + y1 = yCellSize * (((int)floor(i / yIndexIncrement)) % zIndexIncrement); + x1 = xCellSize * (((int)floor(i / xIndexIncrement)) % yIndexIncrement); + } + + /* Path loss to yourself is 0.0 */ + pathLoss[i].push_front(new DistPathLossElement(i, 0.0, 0.0)); + totalElements++; //keep track of pathLoss size for reporting purposes + + for (int j = i + 1; j < numOfSpaceCells; j++) { + if (onlyStaticNodes) { + x2 = nodeLocation[j].x; + y2 = nodeLocation[j].y; + z2 = nodeLocation[j].z; + } else { + z2 = zCellSize * (int)(j / zIndexIncrement); + y2 = yCellSize * (((int)(j / yIndexIncrement)) % zIndexIncrement); + x2 = xCellSize * (((int)(j / xIndexIncrement)) % yIndexIncrement); + + if (fabs(x1 - x2) > distanceThreshold) + continue; + if (fabs(y1 - y2) > distanceThreshold) + continue; + if (fabs(z1 - z2) > distanceThreshold) + continue; + } + + dist = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) + (z2 - z1) * (z2 - z1)); + if (dist > distanceThreshold) + continue; + + /* if the distance is very small (arbitrarily: smaller than one tenth + * of the reference distance) then make the path loss 0dB + */ + if (dist < d0/10.0) { + PLd = 0; + bidirectionalPathLossJitter = 0; + } + else { + // + double d_km = dist / 1000; + double t1 = pow(d_km,spreading_factor); + double Af = calcThorp(carrier_frequency); + double A = pow(10.0,(Af/10.0)); + double t3 = pow(A,d_km); + + PLd = log10(t1 * t3) + calcNoise(carrier_frequency); + + + + //PLd = PLd0 + 10.0 * pathLossExponent * log10(dist / d0) + normal(0, sigma); + bidirectionalPathLossJitter = normal(0, bidirectionalSigma) / 2; + } + + if (maxTxPower - PLd - bidirectionalPathLossJitter >= signalDeliveryThreshold) { + pathLoss[i].push_front(new DistPathLossElement(j,PLd + bidirectionalPathLossJitter, dist)); + totalElements++; //keep track of pathLoss size for reporting purposes + } + + if (maxTxPower - PLd + bidirectionalPathLossJitter >= signalDeliveryThreshold) { + pathLoss[j].push_front(new DistPathLossElement(i,PLd - bidirectionalPathLossJitter, dist)); + totalElements++; //keep track of pathLoss size for reporting purposes + } + } + } + + trace() << "Number of distinct space cells: " << numOfSpaceCells; + trace() << "Each cell affects " << + (double)totalElements / numOfSpaceCells << " other cells on average"; + trace() << "The pathLoss array of lists was allocated in " << + (double)(totalElements * elementSize) / 1048576 << " MBytes"; + // The larger this number, the slower your simulation. Consider increasing the cell size, + // decreasing the field size, or if you only have static nodes, decreasing the number of nodes + + /********************************************************************* + * Allocate nodesAffectedByTransmitter even for static nodes. + * This makes the code more compact. We also have temporal variations + * so the nodes that are affected are not necessarily the same. + *********************************************************************/ + nodesAffectedByTransmitter = new list[numOfNodes]; + if (nodesAffectedByTransmitter == NULL) + throw cRuntimeError("Could not allocate array nodesAffectedByTransmitter\n"); + + /************************************************************ + * If direct assignment of link qualities is given at the + * omnetpp.ini file we parse the input and update pathLoss. + * This is only for static nodes. (onlyStaticNodes==TRUE) + ************************************************************/ + parsePathLossMap(); + + /* Create temporal model object from parameters file (if given) */ + if (strlen(temporalModelParametersFile) > 0) { + temporalModel = new channelTemporalModel(temporalModelParametersFile, getRNG(2)); + temporalModelDefined = true; + } else { + temporalModelDefined = false; + } + + declareHistogram("Fade depth distribution", -50, 15, 13); + + trace() << "Time for Wireless Channel module initialization: " << + (double)(clock() - startTime) / CLOCKS_PER_SEC << "secs"; +} + +/***************************************************************************** + * This is where the main work is done by processing all the messages received + *****************************************************************************/ +void UnderWaterChannel::handleMessage(cMessage * msg) +{ + switch (msg->getKind()) { + + case WC_NODE_MOVEMENT:{ + + WirelessChannelNodeMoveMessage *mobilityMsg = + check_and_cast (msg); + int srcAddr = mobilityMsg->getNodeID(); + + /***************************************************** + * A node notified the wireless channel that it moved + * to a new space cell. Update the nodeLocation and + * based on the new cell calculation decide if the + * cellOccupation array needs to be updated. + *****************************************************/ + + if (onlyStaticNodes) + throw cRuntimeError("Error: Rerceived WS_NODE_MOVEMENT msg, while onlyStaticNodes is TRUE"); + + int oldCell = nodeLocation[srcAddr].cell; + nodeLocation[srcAddr].x = mobilityMsg->getX(); + nodeLocation[srcAddr].y = mobilityMsg->getY(); + nodeLocation[srcAddr].z = mobilityMsg->getZ(); + nodeLocation[srcAddr].phi = mobilityMsg->getPhi(); + nodeLocation[srcAddr].theta = mobilityMsg->getTheta(); + if ((nodeLocation[srcAddr].x < 0.0) || + (nodeLocation[srcAddr].y < 0.0) || + (nodeLocation[srcAddr].z < 0.0)) + throw cRuntimeError("Wireless channel received faulty WC_NODE_MOVEMENT msg. We cannot have negative node coordinates"); + + int xIndex = (int)floor(nodeLocation[srcAddr].x / xFieldSize * numOfXCells); + if (((xIndex - 1) * xCellSize) >= nodeLocation[srcAddr].x) + xIndex--; + else if (xIndex >= numOfXCells) { + xIndex = numOfXCells - 1; // the maximum possible x index + if (nodeLocation[srcAddr].x > xFieldSize) + debug() << "WARNING at WC_NODE_MOVEMENT: node position out of bounds in X dimension!\n"; + } + + int yIndex = (int)floor(nodeLocation[srcAddr].y / yFieldSize * numOfYCells); + if (((yIndex - 1) * yCellSize) >= nodeLocation[srcAddr].y) + yIndex--; + else if (yIndex >= numOfYCells) { + yIndex = numOfYCells - 1; // the maximum possible y index + if (nodeLocation[srcAddr].y > yFieldSize) + debug() << "WARNING at WC_NODE_MOVEMENT: node position out of bounds in Y dimension!\n"; + } + + int zIndex = (int)floor(nodeLocation[srcAddr].z / zFieldSize * numOfZCells); + if (((zIndex - 1) * zCellSize) >= nodeLocation[srcAddr].z) + zIndex--; + else if (zIndex >= numOfZCells) { + zIndex = numOfZCells - 1; // the maximum possible z index + if (nodeLocation[srcAddr].z > zFieldSize) + debug() << "WARNING at WC_NODE_MOVEMENT: node position out of bounds in Z dimension!\n"; + } + + int newCell = zIndex * zIndexIncrement + yIndex * yIndexIncrement + xIndex * xIndexIncrement; + if (newCell != oldCell) { + cellOccupation[oldCell].remove(srcAddr); + cellOccupation[newCell].push_front(srcAddr); + nodeLocation[srcAddr].cell = newCell; + } + + break; + } + + case WC_SIGNAL_START:{ + + WirelessChannelSignalBegin *signalMsg = + check_and_cast (msg); + int srcAddr = signalMsg->getNodeID(); + int receptioncount = 0; + + /* Find the cell that the transmitting node resides */ + int cellTx = nodeLocation[srcAddr].cell; + + /* Iterate through the list of cells that are affected + * by cellTx and check if there are nodes there. + * Update the nodesAffectedByTransmitter array + */ +// list < PathLossElement * >::iterator it1; + for (auto it1 = pathLoss[cellTx].begin(); it1 != pathLoss[cellTx].end(); it1++) { + /* If no nodes exist in this cell, move on. */ + if (cellOccupation[(*it1)->cellID].empty()) + continue; + + /* Otherwise there are some nodes in that cell. + * Calculate the signal received by these nodes + * It is exactly the same for all of them. + * The signal may be variable in time. + */ + float currentSignalReceived = signalMsg->getPower_dBm() - (*it1)->avgPathLoss; + if (temporalModelDefined) { + simtime_t timePassed_msec = (simTime() - (*it1)->lastObservationTime) * 1000; + simtime_t timeProcessed_msec = + temporalModel->runTemporalModel(SIMTIME_DBL(timePassed_msec), + &((*it1)->lastObservedDiffFromAvgPathLoss)); + currentSignalReceived += (*it1)->lastObservedDiffFromAvgPathLoss; + collectHistogram("Fade depth distribution", + (*it1)->lastObservedDiffFromAvgPathLoss); + /* Update the observation time */ + (*it1)->lastObservationTime = simTime() - + (timePassed_msec - timeProcessed_msec) / 1000; + } + + /* If the resulting current signal received is not strong enough, + * to be delivered to the radio module, continue to the next cell. + */ + if (currentSignalReceived < signalDeliveryThreshold) + continue; + + /* Else go through all the nodes of that cell. + * Iterator it2 returns node IDs. + */ + list < int >::iterator it2; + for (it2 = cellOccupation[(*it1)->cellID].begin(); + it2 != cellOccupation[(*it1)->cellID].end(); it2++) { + if (*it2 == srcAddr) + continue; + receptioncount++; + WirelessChannelSignalBegin *signalMsgCopy = signalMsg->dup(); + signalMsgCopy->setPower_dBm(currentSignalReceived); + sendDelayed(signalMsgCopy, calcDelay((*it1)->dist), "toNode", *it2); + nodesAffectedByTransmitter[srcAddr].push_front({*it2,(*it1)->dist}); + } //for it2 + } //for it1 + + if (receptioncount > 0) + trace() << "signal from node[" << srcAddr << "] reached " << + receptioncount << " other nodes"; + break; + } + + case WC_SIGNAL_END:{ + WirelessChannelSignalEnd *signalMsg = + check_and_cast (msg); + int srcAddr = signalMsg->getNodeID(); + + /* Go through the list of nodes that were affected + * by this transmission. *it1 holds the node ID + */ + + for (auto it1 = nodesAffectedByTransmitter[srcAddr].begin(); + it1 != nodesAffectedByTransmitter[srcAddr].end(); it1++) { + WirelessChannelSignalEnd *signalMsgCopy = signalMsg->dup(); + sendDelayed(signalMsgCopy, calcDelay(it1->dist),"toNode", it1->id); + } //for it1 + + /* Now that we are done processing the msg we delete the whole list + * nodesAffectedByTransmitter[srcAddr], since srcAddr in not TXing anymore. + */ + nodesAffectedByTransmitter[srcAddr].clear(); + break; + } + + default:{ + throw cRuntimeError("ERROR: Wireless Channel received unknown message kind=%i", msg->getKind()); + break; + } + } + delete msg; +} + +void UnderWaterChannel::finishSpecific() +{ + + YAML::Emitter y_out; + y_out<cellID; + y_out<avgPathLoss; + y_out<par("debugInfoFileName").stringValue()); + + onlyStaticNodes = par("onlyStaticNodes"); + pathLossExponent = par("pathLossExponent"); + sigma = par("sigma"); + bidirectionalSigma = par("bidirectionalSigma"); + PLd0 = par("PLd0"); + d0 = par("d0"); + spreading_factor = par("spreading_factor"); + + pathLossMapFile = par("pathLossMapFile"); + temporalModelParametersFile = par("temporalModelParametersFile"); + signalDeliveryThreshold = par("signalDeliveryThreshold"); + + numOfNodes = getParentModule()->par("numNodes"); + xFieldSize = getParentModule()->par("field_x"); + yFieldSize = getParentModule()->par("field_y"); + zFieldSize = getParentModule()->par("field_z"); + xCellSize = par("xCellSize"); + yCellSize = par("yCellSize"); + zCellSize = par("zCellSize"); + + maxTxPower = 0.0; + serializePathLossData = par("serializePathLossData"); +} // readIniParameters + +/* Parsing of custom pathloss map from a file, + * filename given by the parameter pathLossMapFile + */ +void UnderWaterChannel::parsePathLossMap(void) +{ + if (strlen(pathLossMapFile) == 0) + return; + ifstream f(pathLossMapFile); + if (!f.is_open()) + throw cRuntimeError("\n[Wireless Channel]:\n Error reading from pathLossMapFile %s\n", pathLossMapFile); + + string s; + const char *ct; + int source, destination; + float pathloss_db; + + /* each line in a file is in the same format: + * Transmitter_id>Receiver_id:PathLoss_dB, ... ,Receiver_id:PathLoss_dB + * based on these values we will update the pathloss array + * (using updatePathLossElement function) + */ + while (getline(f, s)) { + ct = s.c_str(); //ct points to the beginning of a line + while (ct[0] && (ct[0] == ' ' || ct[0] == '\t')) + ct++; + if (!ct[0] || ct[0] == '#') + continue; // skip comments + if (parseInt(ct, &source)) + throw cRuntimeError("\n[Wireless Channel]:\n Bad syntax in pathLossMapFile, expecting source identifier\n"); + while (ct[0] && ct[0] != '>') + ct++; //skip untill '>' character + if (!ct[0]) + throw cRuntimeError("\n[Wireless Channel]:\n Bad syntax in pathLossMapFile, expecting comma separated list of values\n"); + cStringTokenizer t(++ct, ","); //divide the rest of the strig with comma delimiter + while ((ct = t.nextToken())) { + if (parseInt(ct, &destination)) + throw cRuntimeError("\n[Wireless Channel]:\n Bad syntax in pathLossMapFile, expecting target identifier\n"); + while (ct[0] && ct[0] != ':') + ct++; //skip untill ':' character + if (parseFloat(++ct, &pathloss_db)) + throw cRuntimeError("\n[Wireless Channel]:\n Bad syntax in pathLossMapFile, expecting dB value for path loss\n"); + updatePathLossElement(source, destination, pathloss_db); + } + } +} + +//This function will update a pathloss element for given source and destination cells with a given value of pathloss +//If this pair is already defined in pathloss array, the old value is replaced, otherwise a new pathloss element is created +void UnderWaterChannel::updatePathLossElement(int src, int dst, float pathloss_db) +{ + throw cRuntimeError("Not implemented\n"); + + if (src >= numOfSpaceCells || dst >= numOfSpaceCells) return; + list ::iterator it1; + for (it1 = pathLoss[src].begin(); it1 != pathLoss[src].end(); it1++) { + if ((*it1)->cellID == dst) { + (*it1)->avgPathLoss = pathloss_db; + return; + } + } + pathLoss[src].push_front(new DistPathLossElement(dst, pathloss_db, 0.0)); +} + +//wrapper function for atoi(...) call. returns 1 on error, 0 on success +int UnderWaterChannel::parseInt(const char *c, int *dst) +{ + while (c[0] && (c[0] == ' ' || c[0] == '\t')) + c++; + if (!c[0] || c[0] < '0' || c[0] > '9') + return 1; + *dst = atoi(c); + return 0; +} + +//wrapper function for strtof(...) call. returns 1 on error, 0 on success +int UnderWaterChannel::parseFloat(const char *c, float *dst) +{ + char *tmp; + *dst = strtof(c, &tmp); + if (c == tmp) + return 1; + return 0; +} + +double UnderWaterChannel::calcThorp(double frequency) { + return (0.11 * pow(frequency,2) / (1 + pow(frequency,2) ) + + 44 * pow(frequency,2) / (4100 + pow(frequency,2) ) + + 0.000275 * pow(frequency,2) + 0.0003 ); +} + +double UnderWaterChannel::calcNoise(double frequency) { + double turbulence, wind, ship, thermal; + double turbulenceDb, windDb, shipDb, thermalDb; + + turbulenceDb = 17.0 - 30.0 * std::log10 (frequency); + turbulence = std::pow (10.0, turbulenceDb * 0.1); + + shipDb = 40.0 + 20.0 * (shipping_noise - 0.5) + 26.0 * + std::log10 (frequency) - 60.0 * std::log10 (frequency + 0.03); + ship = std::pow (10.0, (shipDb * 0.1)); + + windDb = 50.0 + 7.5 * std::pow (wind_noise, 0.5) + 20.0 * + std::log10 (frequency) - 40.0 * std::log10 (frequency + 0.4); + wind = std::pow (10.0, windDb * 0.1); + + thermalDb = -15 + 20 * std::log10 (frequency); + thermal = std::pow (10, thermalDb * 0.1); + + return (10 * std::log10 (turbulence + ship + wind + thermal) ); +} + +double UnderWaterChannel::calcDelay(double dist) { + return dist / SOUND_SPEED_IN_WATER; +} diff --git a/src/wirelessChannel/underwaterChannel/UnderWaterChannel.h b/src/wirelessChannel/underwaterChannel/UnderWaterChannel.h new file mode 100644 index 00000000..a23a617d --- /dev/null +++ b/src/wirelessChannel/underwaterChannel/UnderWaterChannel.h @@ -0,0 +1,119 @@ + /**************************************************************************** + * Copyright: National ICT Australia, 2007 - 2010 * + * Developed at the ATP lab, Networked Systems research theme * + * Author(s): Athanassios Boulis, Yuriy Tselishchev * + * This file is distributed under the terms in the attached LICENSE file. * + * If you do not find this file, copies can be found by writing to: * + * * + * NICTA, Locked Bag 9013, Alexandria, NSW 1435, Australia * + * Attention: License Inquiry. * + * * + ****************************************************************************/ + +#ifndef _UNDERWATERCHANNEL_H +#define _UNDERWATERCHANNEL_H + +#include "wirelessChannel/WirelessChannelMessages_m.h" +#include "wirelessChannel/defaultChannel/WirelessChannelTemporal.h" +#include "wirelessChannel/defaultChannel/WirelessChannel.h" +#include "node/mobilityManager/VirtualMobilityManager.h" +#include "helpStructures/CastaliaModule.h" +#include "node/communication/radio/Radio.h" + +#include "time.h" +#include +#include + +#define SOUND_SPEED_IN_WATER 1500.0 + + using namespace std; + +class DistPathLossElement : public PathLossElement { + public: + float dist; + DistPathLossElement(int c, float PL, float dist) : PathLossElement(c,PL), dist(dist) {}; +}; + +struct AffectedNode { + int id; + double dist; +}; + +class UnderWaterChannel: public CastaliaModule { + private: + + /*--- variables corresponding to .ned file's parameters ---*/ + int numOfNodes; + + double xFieldSize; + double yFieldSize; + double zFieldSize; + double xCellSize; + double yCellSize; + double zCellSize; + + // variables corresponding to Wireless Channel module parameters + double pathLossExponent; // the path loss exponent + double noiseFloor; // in dBm + double PLd0; // Power loss at a reference distance d0 (in dBm) + double d0; // reference distance (in meters) + double sigma; // std of a zero-mean Gaussian RV + double bidirectionalSigma; // std of a zero-mean Gaussian RV + + const char *pathLossMapFile; + const char *temporalModelParametersFile; + double signalDeliveryThreshold; + bool onlyStaticNodes; + double receiverSensitivity; + double maxTxPower; // this is derived, by reading all the Tx power levels + + /*--- other class member variables ---*/ + int numOfXCells, numOfYCells, numOfZCells; + int numOfSpaceCells; + int xIndexIncrement, yIndexIncrement, zIndexIncrement; + + list *pathLoss; // an array of lists (numOfSpaceCels long) + // holding info on path loss. Element i of the + // array is a list elements that describe which + // cells are affected (and how) when a + // node in cell i transmits. + + list *nodesAffectedByTransmitter; // an array of lists (numOfNodes long). The list + // at array element i holds the node IDs that are + // affected when node i transmits. + + list *cellOccupation; // an array of lists (numOfSpaceCels long) that + // tells us which nodes are in cell i. + + NodeLocation_type *nodeLocation; // an array (numOfNodes long) that gives the + // location for each node. + + bool temporalModelDefined; + channelTemporalModel *temporalModel; + bool serializePathLossData; + double spreading_factor; + double carrier_frequency; + double shipping_noise; + double wind_noise; + + + protected: + virtual void initialize(int); + virtual void handleMessage(cMessage * msg); + virtual void finishSpecific(); + + void readIniFileParameters(void); + void parsePathLossMap(void); + int parseInt(const char *, int *); + int parseFloat(const char *, float *); + void printRxSignalTable(void); + void updatePathLossElement(int, int, float); + float calculateProb(float, int); + double calcThorp(double); + double calcNoise(double frequency); + double calcDelay(double dist); + + int numInitStages() const; +}; + +#endif //_UNDERWATERCHANNEL_H diff --git a/src/wirelessChannel/underwaterChannel/UnderWaterChannel.ned b/src/wirelessChannel/underwaterChannel/UnderWaterChannel.ned new file mode 100644 index 00000000..992baf9b --- /dev/null +++ b/src/wirelessChannel/underwaterChannel/UnderWaterChannel.ned @@ -0,0 +1,61 @@ +//******************************************************************************** +//* Copyright: National ICT Australia, 2007 - 2010 * +//* Developed at the ATP lab, Networked Systems research theme * +//* Author(s): Athanassios Boulis, Dimosthenis Pediaditakis, Yuriy Tselishchev * +//* This file is distributed under the terms in the attached LICENSE file. * +//* If you do not find this file, copies can be found by writing to: * +//* * +//* NICTA, Locked Bag 9013, Alexandria, NSW 1435, Australia * +//* Attention: License Inquiry. * +//* * +//*******************************************************************************/* + +package wirelessChannel.underwaterChannel; + +// The wireless channel module simulates the wireless medium. Nodes sent packets to it +// and according to various conditions (fading, interference etc) it is decided which +// nodes can receive this packet + +simple UnderWaterChannel like wirelessChannel.iWirelessChannel { + parameters: + bool collectTraceInfo = default (false); + bool onlyStaticNodes = default (true); // if NO mobility, set it to true for greater efficiency + + double xCellSize = default (5); // if we define cells (to handle mobility) + double yCellSize = default (5); // how big are the cells in each dimension + double zCellSize = default (1); + + double pathLossExponent = default (2.4); // how fast is the signal strength fading + double PLd0 = default (55); // path loss at reference distance d0 (in dBm) + double d0 = default (1.0); // reference distance d0 (in meters) + + double sigma = default (4.0); // how variable is the average fade for nodes at the same distance + // from eachother. std of a gaussian random variable. + + double bidirectionalSigma = default (1.0); // how variable is the average fade for link B->A if we know + // the fade of link A->B. std of a gaussian random variable + + string pathLossMapFile = default (""); // describes a map of the connectivity based on pathloss + // if defined, then the parameters above become irrelevant + + string temporalModelParametersFile = default (""); + // the filename that contains all parameters for + // the temporal channel variation model + + double signalDeliveryThreshold = default (-100); + // threshold in dBm above which, wireless channel module + // is delivering signal messages to radio modules of + // individual nodes + + bool serializePathLossData = default(false);// Serialize path loss data to yaml file + + double spreading_factor = default(2.0); // Geometry of the propagation, 1.0 - cylindrical, 2.0 - spherical, + // 1.5 - practical + double shipping_noise = default(0.0); // Shipping noise, normalized + double wind_noise = default(1.0); // Wind (actually breaking waves due to wind) noise, m/s + + gates: + output toNode[]; + input fromMobilityModule @ directIn; + input fromNode[]; +}