/* Implements a physical layer (PHY) Copyright (c) 2004-2005 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above= copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.domains.wireless.lib.network; import java.util.Iterator; import ptolemy.actor.IOPort; import ptolemy.actor.TypedIOPort; import ptolemy.actor.util.Time; import ptolemy.data.DoubleToken; import ptolemy.data.IntToken; import ptolemy.data.RecordToken; import ptolemy.data.Token; import ptolemy.data.UnionToken; import ptolemy.data.expr.Parameter; import ptolemy.data.expr.Variable; import ptolemy.data.type.BaseType; import ptolemy.data.type.RecordType; import ptolemy.data.type.Type; import ptolemy.data.type.UnionType; import ptolemy.domains.wireless.kernel.WirelessIOPort; import ptolemy.kernel.CompositeEntity; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.NameDuplicationException; ////////////////////////////////////////////////////////////////////////= // //// PHY /** The PHY class implements a physical layer which does the following: 1) collision detection; 2) carrier sense; 3) send TxStartConfirm to the MAC when TxStart is received; send TxEnd to the MAC when transmission is completed; send RxStart to the MAC when reception starts; send RxData and RxEnd to the MAC when reception ends. Things that can be added: 1) not send channel status in the transmit state; 2) The first received signal is above the sensitivity, so the PHY decides to receive it. If the second signal is much stronger, the PHY can abort the reception of the 1st one and receive the 2nd one instead. @author Charlie Zhong @version $Id$ @since Ptolemy II 4.0 @Pt.ProposedRating Red (czhong) @Pt.AcceptedRating Red (reviewmoderator) */ public class PHY extends NetworkActorBase { /** Construct an actor with the specified name and container. * The container argument must not be null, or a * NullPointerException will be thrown. * If the name argument is null, then the name is set to the empty * string. * This constructor write-synchronizes on the workspace. * @param container The container. * @param name The name of the actor. * @exception IllegalActionException If the container is incompatible * with this actor. * @exception NameDuplicationException If the name coincides with * an actor already in the container. */ public PHY(CompositeEntity container, String name) throws IllegalActionException, NameDuplicationException { super(container, name); // Configure PHY layer parameters. aPreambleLength = new Parameter(this, "aPreambleLength"); aPreambleLength.setTypeEquals(BaseType.INT); aPreambleLength.setExpression("20"); aPlcpHeaderLength = new Parameter(this, "aPlcpHeaderLength"); aPlcpHeaderLength.setTypeEquals(BaseType.INT); aPlcpHeaderLength.setExpression("4"); // Configure parameters. SNRThresholdInDB = new Parameter(this, "SNRThresholdInDB"); SNRThresholdInDB.setTypeEquals(BaseType.DOUBLE); SNRThresholdInDB.setExpression("-20"); sensitivity = new Parameter(this, "sensitivity"); sensitivity.setTypeEquals(BaseType.DOUBLE); sensitivity.setExpression("0.0"); // create ports fromMAC = new TypedIOPort(this, "fromMAC", true, false); fromMAC.setTypeEquals(BaseType.GENERAL); fromChannel = new TypedIOPort(this, "fromChannel", true, false); fromChannel.setTypeEquals(BaseType.GENERAL); toMAC = new TypedIOPort(this, "toMAC", false, true); //toMAC.setTypeEquals(BaseType.GENERAL); channelStatus = new TypedIOPort(this, "channelStatus", false, true); channelStatus.setTypeEquals(BaseType.GENERAL); PHYConfirm = new TypedIOPort(this, "PHYConfirm", false, true); PHYConfirm.setTypeEquals(BaseType.GENERAL); toChannel = new TypedIOPort(this, "toChannel", false, true); toChannel.setTypeEquals(BaseType.GENERAL); _setUnionType(); } /////////////////////////////////////////////////////////////////// //// parameters //// // all time are in the unit of microseconds /** The size of the Preamble header in a frame. The Preamble * header contains synchronization information and timing * information for the frame. */ public Parameter aPreambleLength; /** The size of the PLCP header in a frame. The PLCP header * contains logical information for the physical layer to * decode the frame. */ public Parameter aPlcpHeaderLength; /** The threshold for the signal to be recognized from interference. * It is specified in decibels (10 * log<sub>10</sub>(<i>r</i>), * where <i>r</i> is the power ratio. This is a double that * defaults to Infinity, which indicates that all overlapping * messages are lost to collisions. */ public Parameter SNRThresholdInDB; /** The power threshold above which the signal can be * detected at the receiver. Any message with a received power * below this number is ignored. This has type double * and defaults to 0.0, which indicates that all messages * (with nonzero power) will be received. */ public Parameter sensitivity; /////////////////////////////////////////////////////////////////// //// public variables //// /** Port receiving messages from the MAC */ public TypedIOPort fromMAC; /** Port receiving messages from the channel */ public TypedIOPort fromChannel; /** Port sending messages to the MAC */ public TypedIOPort toMAC; /** Port sending channel status to the MAC */ public TypedIOPort channelStatus; /** Port sending transmit confirmation to the MAC */ public TypedIOPort PHYConfirm; /** Port sending messages to the channel */ public TypedIOPort toChannel; /////////////////////////////////////////////////////////////////// //// public methods //// public void fire() throws IllegalActionException { super.fire(); int oldnum = _numBusyTimers; int kind = whoTimeout2(); // check if a timer times out and which Time currentTime = getDirector().getModelTime(); if ((oldnum > 0) && (_numBusyTimers == 0)) { // update channel status RecordToken ChannelStatusMsg = new RecordToken(SignalMsgFields, new Token[] { new IntToken(Idle) }); channelStatus.send(0, ChannelStatusMsg); } double power = 0; double duration = -1; RecordToken msg; switch (_currentState) { case PHY_Idle: if (fromChannel.hasToken(0)) { _data = (RecordToken) fromChannel.get(0); if (_debugging) { _debug(getFullName() + "Receiving a message." + _data.toString()); } // the input port may not be a WirelessIOPort, but the port it is // connected to is Iterator connectedPorts = fromChannel.sourcePortList() .iterator(); while (connectedPorts.hasNext()) { IOPort port = (IOPort) connectedPorts.next(); if (port.isInput() && port instanceof WirelessIOPort) { // Found the port. RecordToken properties = (RecordToken) ((WirelessIOPort) port) .getProperties(0); power = ((DoubleToken) properties.get("power")) .doubleValue(); duration = ((DoubleToken) properties.get("duration")) .doubleValue(); break; } } // let us be a little picky about receiving a message if ((power > _sensitivity) && ((_interference == 0.0) || ((power / _interference) > _SNRThresholdInDB))) { if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "Receiving a message with power: " + power); } // The PHY will receive this message setTimer2(RxDone, currentTime.add(duration), power); // update channel status _numBusyTimers++; if (_numBusyTimers == 1) { RecordToken ChannelStatusMsg = new RecordToken( SignalMsgFields, new Token[] { new IntToken( Busy) }); channelStatus.send(0, ChannelStatusMsg); } // send RxStart to the MAC Token[] RxStartValues = { new IntToken(RxStart), _data.get("rate") }; RecordToken RxStartMsg = new RecordToken(RxStartMsgFields, RxStartValues); UnionToken t = new UnionToken("RxStart", RxStartMsg); toMAC.send(0, t); // remember the power of the received message _receivedPower = power; _currentState = Receive; } else { if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "Receiving an interfrence message with power: " + power); } // this is also an interference // add every conversation in the network to this giant table setTimer2(InterferenceDone, currentTime.add(duration), power); // update interference _interference = _interference + power; } } else if (fromMAC.hasToken(0)) { msg = (RecordToken) fromMAC.get(0); if (((IntToken) msg.get("kind")).intValue() == TxStart) { _startTransmission(msg); } } break; case Receive: if (kind == RxDone) // MUST check the timer first { if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "about to send RxData to MAC."); } // send RxData to the MAC RecordToken data = (RecordToken) _data.get("data"); Token[] RxDataValues = { new IntToken(RxData), data }; RecordToken RxDataMsg = new RecordToken(RxDataMsgFields, RxDataValues); IntToken subType = (IntToken) data.get("Subtype"); if (subType.intValue() == Data) { //dataPacket toMAC.send(0, new UnionToken("RxData", RxDataMsg)); } else if (subType.intValue() == Rts) { toMAC.send(0, new UnionToken("RxRts", RxDataMsg)); } else if ((subType.intValue() == Cts) || (subType.intValue() == Ack)) { toMAC.send(0, new UnionToken("CsRts", RxDataMsg)); } else { throw new IllegalActionException(this, "trying to send a message with" + "not compatable type : " + _data.toString()); } if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "have sent RxData to MAC."); } // send RxEnd to the MAC Token[] RxEndValues = { new IntToken(RxEnd), new IntToken(_rxStatus) }; RecordToken RxEndMsg = new RecordToken(RxEndMsgFields, RxEndValues); toMAC.send(0, new UnionToken("RxEnd", RxEndMsg)); _currentState = PHY_Idle; } else if (fromChannel.hasToken(0)) { // This message is an interference _handleInterference(); // check collision if ((_receivedPower / _interference) <= _SNRThresholdInDB) { _rxStatus = Error; } } else if (fromMAC.hasToken(0)) { msg = (RecordToken) fromMAC.get(0); if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "about to about the current recption."); } if (((IntToken) msg.get("kind")).intValue() == TxStart) { _startTransmission(msg); // abort the current reception and send RxEnd to the MAC Token[] RxEndValues = { new IntToken(RxEnd), new IntToken(Error) }; // set the status to Error RecordToken RxEndMsg = new RecordToken(RxEndMsgFields, RxEndValues); UnionToken t = new UnionToken("RxEnd", RxEndMsg); toMAC.send(0, t); } } break; case Transmit: if (kind == TxDone) // MUST check the timer first { if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "about to send phy confirm to MAC."); } // send TxEnd to the MAC RecordToken TxEndMsg = new RecordToken(SignalMsgFields, new Token[] { new IntToken(TxEnd) }); PHYConfirm.send(0, TxEndMsg); _currentState = PHY_Idle; } else if (fromChannel.hasToken(0)) { // This message is an interference if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "about to handle interference."); } _handleInterference(); } else if (fromMAC.hasToken(0)) { msg = (RecordToken) fromMAC.get(0); if (((IntToken) msg.get("kind")).intValue() == TxData) { // send the data to the channel Token[] ChMsgValues = { new IntToken(_txRate), msg.get("pdu") }; RecordToken ChMsg = new RecordToken(ChMsgFields, ChMsgValues); if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "about to transmit msg: " + ChMsg.toString()); } toChannel.send(0, ChMsg); // update the parameter: duration _duration = (Variable) getContainer().getContainer() .getAttribute("duration"); _duration.setToken(new DoubleToken(_txDuration)); setTimer2(TxDone, currentTime.add(_txDuration), 0.0); } } break; } } /** Initialize the private variables. * @exception IllegalActionException If thrown by the base class. */ public void initialize() throws IllegalActionException { super.initialize(); _currentState = PHY_Idle; _interference = 0.0; _numBusyTimers = 0; // initialize the channel status in the MAC RecordToken ChannelStatusMsg = new RecordToken(SignalMsgFields, new Token[] { new IntToken(Idle) }); channelStatus.send(0, ChannelStatusMsg); } /** If the specified attribute is changed, * check that a positive number is given. Otherwise, * defer to the base class. * @param attribute The attribute that changed. * @exception IllegalActionException If the change is not acceptable * to this container. */ public void attributeChanged(Attribute attribute) throws IllegalActionException { int temp = 0; if (attribute == aPreambleLength) { temp = ((IntToken) aPreambleLength.getToken()).intValue(); if (temp < 0) { throw new IllegalActionException(this, "preamble Length is required to be nonnegative. " + "Attempt to set it to: " + temp); } else { _aPreambleLength = temp; } } else if (attribute == aPlcpHeaderLength) { temp = ((IntToken) aPlcpHeaderLength.getToken()).intValue(); if (temp < 0) { throw new IllegalActionException(this, "PLCPHeader Length is required to be nonnegative. " + "Attempt to set it to: " + temp); } else { _aPlcpHeaderLength = temp; } } else if (attribute == SNRThresholdInDB) { double SNRThresholdInDBValue = ((DoubleToken) SNRThresholdInDB .getToken()).doubleValue(); // Convert to linear scale. _SNRThresholdInDB = Math.pow(10, SNRThresholdInDBValue / 10); } else if (attribute == sensitivity) { _sensitivity = ((DoubleToken) sensitivity.getToken()).doubleValue(); if (_sensitivity < 0.0) { throw new IllegalActionException(this, "sensitivity is required to be nonnegative. " + "Attempt to set it to: " + _sensitivity); } } else { super.attributeChanged(attribute); } } /** Override the base class to declare that the <i>fromMAC</i> * output port does not * depend on the <i>toMAC</i>, <i>channelStatus</i> * and <i>PHYConfirm</i> ports in a firing. */ public void preinitialize() throws IllegalActionException { super.preinitialize(); // Declare that output does not immediately depend on the input, // though there is no lower bound on the time delay. declareDelayDependency(fromMAC, toMAC, 0.0); declareDelayDependency(fromMAC, channelStatus, 0.0); declareDelayDependency(fromMAC, PHYConfirm, 0.0); } /////////////////////////////////////////////////////////////////// //// protected methods //// protected ExtendedTimer setTimer2(int kind, Time expirationTime, double power) throws IllegalActionException { ExtendedTimer timer = new ExtendedTimer(); timer.kind = kind; timer.expirationTime = expirationTime; timer.power = power; // put all timers of this object into a queue _timersSet.add(timer); _fireAt(expirationTime); return timer; } /** Remove the timer that matches with the <i>timerToCancel<i> argument * from the timers set. If no match is found, do nothing. */ protected void cancelTimer2(ExtendedTimer timerToCancel) throws IllegalActionException { Iterator timers = _timersSet.iterator(); // iterate through the queue to find the timer to be canceled while (timers.hasNext()) { ExtendedTimer timer = (ExtendedTimer) timers.next(); if (timer == timerToCancel) { _timersSet.remove(timer); break; } } } /** Get the timer with expiration time that matches the current time. * Remove the timer from the timers set and return the <i>kind<i> * parameter of the timer to the caller method. If there are multiple * timers with expiration time matching the current time, return the * first one from the iterator list. * @return return the i>kind<i> parameter of the timeout timer. * @exception IllegalActionException If thrown by * getDirector().getCurrentTime(). */ protected int whoTimeout2() throws IllegalActionException { // find the 1st timer expired Iterator timers = _timersSet.iterator(); while (timers.hasNext()) { ExtendedTimer timer = (ExtendedTimer) timers.next(); if (timer.expirationTime.equals(getDirector().getModelTime())) { // update interference if (timer.kind == InterferenceDone) { _interference = _interference - timer.power; // Quantization errors may make this negative. Do not allow. if (_interference < 0.0) { _interference = 0.0; } } if (((timer.kind == InterferenceDone) || (timer.kind == RxDone)) && (timer.power > _sensitivity)) { _numBusyTimers--; } // remove it from the set no matter that // it will be processed or ignored timers.remove(); return timer.kind; } } return UNKNOWN; } /////////////////////////////////////////////////////////////////// //// private methods //// private void _handleInterference() throws IllegalActionException { double power = 0.5; double duration = -1; Token t = fromChannel.get(0); // consume the token if (_debugging) { //Token dbg = new DoubleToken(power / _interference); _debug(getFullName() + "gets a collison with message: " + t.toString()); } Iterator connectedPorts = fromChannel.sourcePortList().iterator(); while (connectedPorts.hasNext()) { IOPort port = (IOPort) connectedPorts.next(); if (port.isInput() && port instanceof WirelessIOPort) { // Found the port. RecordToken properties = (RecordToken) ((WirelessIOPort) port) .getProperties(0); power = ((DoubleToken) properties.get("power")).doubleValue(); duration = ((DoubleToken) properties.get("duration")) .doubleValue(); break; } } Time currentTime = getDirector().getModelTime(); // add every conversation in the network to this giant table setTimer2(InterferenceDone, currentTime.add(duration), power); // update interference _interference = _interference + power; // update channel status if (power > _sensitivity) { _numBusyTimers++; } if (_numBusyTimers == 1) { Token[] value = { new IntToken(Busy) }; RecordToken ChannelStatusMsg = new RecordToken(SignalMsgFields, value); channelStatus.send(0, ChannelStatusMsg); } } /** Return the type constraints of this actor. The type constraint is * that the output type is the union of the types of input ports. * @return a list of Inequality. */ private void _setUnionType() { String[] labels = { "RxStart", "RxData", "RxEnd", "RxRts", "CsRts" }; RecordType[] types = new RecordType[5]; Type[] controlType = { BaseType.INT, BaseType.INT }; types[0] = new RecordType(RxStartMsgFields, controlType); types[2] = new RecordType(RxEndMsgFields, controlType); Type[] pduType = new Type[DataPacket.length]; for (int i = 0; i < DataPacket.length; i++) { pduType[i] = BaseType.INT; } //FIXME: currently, assume the payload is a the following Record type. // it should be infered from the application layer... String[] payload = { "Length", "kind", "payload", "toMACAddr" }; Type[] payloadType = { BaseType.INT, BaseType.INT, BaseType.STRING, BaseType.INT }; //If the labels of the DataPacket changes and the "payload" is //not the second last one any more, then we need to change the //code here. pduType[DataPacket.length - 2] = new RecordType(payload, payloadType); Type[] dataType = { BaseType.INT, new RecordType(DataPacket, pduType) }; types[1] = new RecordType(RxDataMsgFields, dataType); Type[] rtsduType = new Type[RtsPacket.length]; for (int i = 0; i < RtsPacket.length; i++) { rtsduType[i] = BaseType.INT; } Type[] rtsType = { BaseType.INT, new RecordType(RtsPacket, rtsduType) }; types[3] = new RecordType(RxDataMsgFields, rtsType); Type[] csduType = new Type[AckPacket.length]; for (int i = 0; i < AckPacket.length; i++) { csduType[i] = BaseType.INT; } Type[] csType = { BaseType.INT, new RecordType(AckPacket, csduType) }; types[4] = new RecordType(RxDataMsgFields, csType); /*Type[] RxType = new Type[RxPacket.length]; for (int i = 0; i < RxPacket.length; i++) { RxType[i] = BaseType.INT; } Type[] dataType = {BaseType.INT, new RecordType(RxPacket, RxType)}; types[1] = new RecordType(RxDataMsgFields, dataType);*/ UnionType declaredType = new UnionType(labels, types); toMAC.setTypeEquals(declaredType); } private void _startTransmission(RecordToken msg) throws IllegalActionException { _txRate = ((IntToken) msg.get("rate")).intValue(); int length = ((IntToken) msg.get("length")).intValue(); // compute the duration of this packet ( with the PHY overhead added) _txDuration = ((double) length / _txRate) + ((_aPreambleLength + _aPlcpHeaderLength) * 1e-6); // send TxStartConfirm to the MAC RecordToken TxStartConfirmMsg = new RecordToken(SignalMsgFields, new Token[] { new IntToken(TxStartConfirm) }); PHYConfirm.send(0, TxStartConfirmMsg); _currentState = Transmit; } /////////////////////////////////////////////////////////////////// //// inner classes //// /** Extend the default timer to link the additional info * (e.g. power) to a timer. */ protected static class ExtendedTimer { public int kind; public Time expirationTime; public double power; } /////////////////////////////////////////////////////////////////// //// protected variables //// //the local varibles for the parameters of this actor. protected int _aPreambleLength; protected int _aPlcpHeaderLength; protected double _sensitivity; protected double _SNRThresholdInDB; // message formats protected static final String[] RxStartMsgFields = { "kind", "rxRate" }; protected static final String[] RxEndMsgFields = { "kind", "status" }; protected static final String[] RxDataMsgFields = { "kind", "pdu" }; protected static final String[] SignalMsgFields = { "kind" }; protected static final String[] ChMsgFields = { "rate", "data" }; // time that a packet uses the channel protected Variable _duration = null; /////////////////////////////////////////////////////////////////// //// private variables //// private RecordToken _data; private double _txDuration; private double _receivedPower; private int _rxStatus; private int _txRate; private double _interference; private int _numBusyTimers; //private RecordToken _txMsg; // define states in FSM private static final int PHY_Idle = 0; // not use Idle as state name private static final int Receive = 1; private static final int Transmit = 2; private int _currentState = PHY_Idle; // timer types private static final int RxDone = 1; private static final int InterferenceDone = 2; private static final int TxDone = 3; }