/* This actor implements a Network Bus.
@Copyright (c) 2010-2014 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.actor.lib.aspect;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import ptolemy.actor.Actor;
import ptolemy.actor.CommunicationAspect;
import ptolemy.actor.CommunicationAspectAttributes;
import ptolemy.actor.CommunicationAspectListener.EventType;
import ptolemy.actor.IOPort;
import ptolemy.actor.IntermediateReceiver;
import ptolemy.actor.Receiver;
import ptolemy.actor.util.Time;
import ptolemy.data.DoubleToken;
import ptolemy.data.IntToken;
import ptolemy.data.StringToken;
import ptolemy.data.Token;
import ptolemy.data.expr.Parameter;
import ptolemy.data.type.BaseType;
import ptolemy.kernel.CompositeEntity;
import ptolemy.kernel.Port;
import ptolemy.kernel.util.Attribute;
import ptolemy.kernel.util.Decorator;
import ptolemy.kernel.util.DecoratorAttributes;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.InternalErrorException;
import ptolemy.kernel.util.KernelException;
import ptolemy.kernel.util.NameDuplicationException;
import ptolemy.kernel.util.NamedObj;
import ptolemy.kernel.util.Workspace;
/** This actor is an {@link CommunicationAspect} that simulates a CAN bus network
* When its {@link #sendToken(Receiver, Receiver, Token)} method is called,
* the delivery of the specified token to the specified receiver is delayed according to the CAN protocol.
*
* The CAN bus is a serial communication protocol that supports real-time systems with high reliability.
* Its main features are: priority-based bus access and non destructive content-based arbitration.
* If two or more nodes attempt to transmit a message on the idle bus, the access conflicts are resolved
* by performing a bitwise arbitration (non destructive) according to a priority (called here CanPriority).
* Our {@link CommunicationAspect} simulates such content-based arbitration.
* A node attempting to transmit a message when the bus is busy must try again when the bus will be free (in fact, there is a
* queue with messages that did not win the bus during arbitration or arrived when the bus is busy).
*
*
* In order to perform such an arbitration, it is needed to set a parameter called CanPriority to each receiving switch port.
* CanPriority is a positive integer. The higher is CanPriority the lower is the priority.
* (note that in the reality the arbitration is done bit to bit. The higher is the identifier the higher is the priority)
* It is just needed to set this parameter, using the Parameter dialogs offered by the Decorator mechanism, to the port(s) we want to connect to the bus.
* The CanPriority parameter is already added and is visible on Parameter dialogs when the CanBus QM is deployed in a model (enhancing visibility).
*
*
* Messages sent on the Bus are stored and delivered on due time.
* Since the CAN protocol cover the second layer of the OSI model, messages sent on the bus are encapsulated
* in frames according to the CAN protocol.
* We consider that messages sent by actors correspond to exactly one frame.
* Also, two formats of frames are provided by the CAN standard: the base frame and the extended frame.
* We can choose the standard according to which the simulation will be performed.
*
*
* Of course, the bit rate of the bus is also a parameter that can be modified.
* Typical bit rates for the CAN bus range from 125 Kbits/second to 1 Mbits/second.
*
*
* Future work: implementing an application layer of the OSI model based on CAN, taking errors into account
* by delaying the deliveries of messages, finest management of time by dividing the time continuum in periods of 1/bitRate,
* bit stuffing...
*
*
* For more information please refer to: CAN bus simulator using a communication aspect.
*
* @author D. Marciano, G. Lasnier, P. Derler
* @version $Id: CanBus.java 70402 2014-10-23 00:52:20Z cxh $
* @since Ptolemy II 10.0
* @Pt.ProposedRating Yellow (glasnier)
* @Pt.AcceptedRating Yellow (glasnier)
*/
public class CanBus extends AtomicCommunicationAspect {
/** Construct a CanBus with a name and a container.
* The container argument must not be null, or a
* NullPointerException will be thrown. This actor will use the
* workspace of the container for synchronization and version counts.
* If the name argument is null, then the name is set to the empty string.
* Increment the version of the workspace.
* @param container The container.
* @param name The name of this 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 CanBus(CompositeEntity container, String name)
throws IllegalActionException, NameDuplicationException {
super(container, name);
canFormatOfFrame = new Parameter(this, "canFormatOfFrame");
canFormatOfFrame.setTypeEquals(BaseType.STRING);
canFormatOfFrame.addChoice("\"Standard frame\"");
canFormatOfFrame.addChoice("\"Extended frame\"");
canFormatOfFrame.setExpression("\"Standard frame\"");
_frameSize = 108;
bitRate = new Parameter(this, "bitRate");
bitRate.setDisplayName("bitRate (kbit/s)");
bitRate.setExpression("125");
bitRate.setTypeEquals(BaseType.DOUBLE);
_bitRate = 125;
canFramePolicy = new Parameter(this, "canFramePolicy");
canFramePolicy.setTypeEquals(BaseType.STRING);
canFramePolicy.addChoice("\"Send all frames\"");
canFramePolicy.addChoice("\"Send only most recent frame\"");
canFramePolicy.setExpression("\"Send all frames\"");
_mostRecentFrame = false;
_tokenTree = new TreeMap>();
_multiCast = new HashMap();
_ioPortToCanPriority = new HashMap();
_tokenCount = 0;
}
///////////////////////////////////////////////////////////////////
// public variables //
/** The bit rate of the bus. This is a double with default value to 125 (Kbit/second).
* It is required to be positive.
*/
public Parameter bitRate;
/** The format of frame. This is a string with default value to "Standard frame".
* It is required to be either "Standard frame" or "Extended frame".
*/
public Parameter canFormatOfFrame;
/**
* The selected policy for the frame queue behavior. This is a string with
* the default value "Send All Frames".
*/
public Parameter canFramePolicy;
///////////////////////////////////////////////////////////////////
//// public methods ////
/** If the attribute is bitRate, then ensure that the value
* is non-negative.
* @param attribute The attribute that changed.
* @exception IllegalActionException If the service time is negative.
*/
@Override
public void attributeChanged(Attribute attribute)
throws IllegalActionException {
if (attribute == bitRate) {
double value = ((DoubleToken) bitRate.getToken()).doubleValue();
if (value <= 0.0) {
throw new IllegalActionException(this,
"Cannot have negative or zero bitRate: " + value);
}
_bitRate = value;
} else if (attribute == canFormatOfFrame) {
String value = ((StringToken) canFormatOfFrame.getToken())
.stringValue();
if (value.equalsIgnoreCase("Standard frame")) {
_frameSize = 108;
} else if (value.equalsIgnoreCase("Extended frame")) {
_frameSize = 128;
}
} else if (attribute == canFramePolicy) {
String value = ((StringToken) canFramePolicy.getToken())
.stringValue();
if (value.equalsIgnoreCase("Send all frames")) {
_mostRecentFrame = false;
} else if (value.equalsIgnoreCase("Send only most recent frame")) {
_mostRecentFrame = true;
}
}
super.attributeChanged(attribute);
}
/** Clone this actor into the specified workspace. The new actor is
* not added to the directory of that workspace (you must do this
* yourself if you want it there).
* The result is a new actor with the same ports as the original, but
* no connections and no container. A container must be set before
* much can be done with this actor.
*
* @param workspace The workspace for the cloned object.
* @exception CloneNotSupportedException If cloned ports cannot have
* as their container the cloned entity (this should not occur), or
* if one of the attributes cannot be cloned.
* @return A new CanBus.
*/
@Override
public Object clone(Workspace workspace) throws CloneNotSupportedException {
CanBus newObject = (CanBus) super.clone(workspace);
newObject._ioPortToCanPriority = new HashMap();
newObject._tokenTree = new TreeMap>();
newObject._multiCast = new HashMap();
newObject._frameSize = _frameSize;
newObject._nextTokenSize = _nextTokenSize;
newObject._nextTokenFiringTime = null;
newObject._startingTime = null;
newObject._channelUsed = _channelUsed;
newObject._bitRate = _bitRate;
newObject._mostRecentFrame = _mostRecentFrame;
return newObject;
}
/** Return the decorated attributes for the target NamedObj.
* If the specified target is not an Actor, return null.
* @param target The NamedObj that will be decorated.
* @return The decorated attributes for the target NamedObj, or
* null if the specified target is not an Actor.
*/
@Override
public DecoratorAttributes createDecoratorAttributes(NamedObj target) {
if (target instanceof IOPort) {
try {
return new CanBusAttributes(target, this);
} catch (KernelException ex) {
// This should not occur.
throw new InternalErrorException(ex);
}
} else {
return null;
}
}
/** Fire the actor.
* Typically, the fire() method performs the computation associated
* with an actor.
* Here, it delivers (if required) the intended token to the intended receiver(s).
*
* @exception IllegalActionException If firing is not permitted.
*/
@Override
public void fire() throws IllegalActionException {
super.fire();
Time currentTime = getDirector().getModelTime();
// 'If' statement that allows to construct the 'multiCast' Map
if (!_multiCast.containsKey(_channelUsed)) {
HashSet receiverSet = new HashSet();
ListIterator