package com.serotonin.bacnet4j.npdu.mstp;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.free.bacnet4j.util.SerialParameters;
public class MasterNode extends MstpNode {
private static final Logger LOG = Logger.getLogger(MasterNode.class.toString());
private enum MasterNodeState {
idle, useToken, waitForReply, doneWithToken, passToken, noToken, pollForMaster, answerDataRequest
}
private final List<Frame> framesToSend = new ArrayList<Frame>();
/**
* The MAC address of the node to which This Station passes the token. If the Next
* Station is unknown, NS shall be equal to TS.
*/
private byte nextStation;
/**
* The MAC address of the node to which This Station last sent a Poll For Master. This is
* used during token maintenance.
*/
private byte pollStation;
/**
* A counter of transmission retries used for Token and Poll For Master transmission.
*/
private int retryCount;
/**
* A Boolean flag set to TRUE by the master machine if this node is the only known master node.
*/
private boolean soleMaster;
/**
* The number of tokens received by this node. When this counter reaches the value Npoll, the node
* polls the address range between TS and NS for additional master nodes. TokenCount is set to one at
* the end of the polling process.
*/
private int tokenCount;
private int maxInfoFrames = Constants.MAX_INFO_FRAMES;
private MasterNodeState state;
private long replyDeadline;
private Frame replyFrame;
// private long lastTokenPossession;
public MasterNode(SerialParameters serialParams, byte thisStation, int retryCount) throws IllegalArgumentException {
super(serialParams, thisStation);
validate(retryCount);
}
public MasterNode(InputStream in, OutputStream out, byte thisStation, int retryCount)
throws IllegalArgumentException {
super(in, out, thisStation);
validate(retryCount);
}
private void validate(int retryCount) {
int is = thisStation & 0xff;
if (is > 127)
throw new IllegalArgumentException("thisStation cannot be greater than 127");
this.retryCount = retryCount;
nextStation = thisStation;
pollStation = thisStation;
tokenCount = Constants.POLL;
soleMaster = false;
state = MasterNodeState.idle;
}
/**
* @param maxInfoFrames
* the maxInfoFrames to set
*/
public void setMaxInfoFrames(int maxInfoFrames) {
if (this.maxInfoFrames < 1)
throw new IllegalArgumentException("Cannot be less than 1");
this.maxInfoFrames = maxInfoFrames;
}
public void queueFrame(FrameType type, byte destination, byte[] data) {
if (!type.oneOf(FrameType.bacnetDataExpectingReply, FrameType.bacnetDataNotExpectingReply,
FrameType.testRequest))
throw new RuntimeException("Cannot send frame of type: " + type);
Frame frame = new Frame(type, destination, thisStation, data);
synchronized (framesToSend) {
framesToSend.add(frame);
}
}
@Override
public void setReplyFrame(FrameType type, byte destination, byte[] data) {
synchronized (this) {
if (state == MasterNodeState.answerDataRequest)
// If there is still time to reply immediately...
replyFrame = new Frame(type, destination, thisStation, data);
else
// The response has already exceeded the timeout, so just queue it.
queueFrame(type, destination, data);
}
}
@Override
protected void doCycle() {
readFrame();
if (state == MasterNodeState.idle)
idle();
if (state == MasterNodeState.useToken)
useToken();
if (state == MasterNodeState.waitForReply)
waitForReply();
if (state == MasterNodeState.doneWithToken)
doneWithToken();
if (state == MasterNodeState.passToken)
passToken();
if (state == MasterNodeState.noToken)
noToken();
if (state == MasterNodeState.pollForMaster)
pollForMaster();
if (state == MasterNodeState.answerDataRequest)
answerDataRequest();
}
private void idle() {
if (silence() >= Constants.NO_TOKEN) {
// LostToken
// debug("idle:LostToken");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " idle:LostToken");
state = MasterNodeState.noToken;
activity = true;
}
else if (receivedInvalidFrame != null) {
// ReceivedInvalidFrame
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " idle:Received invalid frame: " + receivedInvalidFrame);
receivedInvalidFrame = null;
activity = true;
}
else if (receivedValidFrame) {
frame();
receivedValidFrame = false;
activity = true;
}
}
private void frame() {
FrameType type = frame.getFrameType();
if (type == null) {
// ReceivedUnwantedFrame
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " idle:Unknown frame type");
}
else if (frame.broadcast()
&& type.oneOf(FrameType.token, FrameType.bacnetDataExpectingReply, FrameType.testRequest)) {
// ReceivedUnwantedFrame
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " Frame type should not be broadcast: " + type);
}
else if (frame.forStation(thisStation) && type == FrameType.token) {
// ReceivedToken
// debug("idle:ReceivedToken from " + frame.getSourceAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " idle:ReceivedToken");
frameCount = 0;
soleMaster = false;
state = MasterNodeState.useToken;
//
// long now = timeSource.currentTimeMillis();
// if (lastTokenPossession > 0)
// debug("Since last token possession: " + (now - lastTokenPossession) + " ms");
// else
// debug("Received first token possession");
// lastTokenPossession = now;
}
else if (frame.forStation(thisStation) && type == FrameType.pollForMaster) {
// ReceivedPFM
// debug("idle:ReceivedPFM from " + frame.getSourceAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " idle:ReceivedPFM");
sendFrame(FrameType.replyToPollForMaster, frame.getSourceAddress());
}
else if (frame.forStationOrBroadcast(thisStation)
&& type.oneOf(FrameType.bacnetDataNotExpectingReply, FrameType.testResponse)) {
// ReceivedDataNoReply
// debug("idle:ReceivedDataNoReply");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " idle:ReceivedDataNoReply");
receivedDataNoReply(frame);
}
else if (frame.forStation(thisStation) && type.oneOf(FrameType.bacnetDataExpectingReply, FrameType.testRequest)) {
// ReceivedDataNeedingReply
// debug("idle:ReceivedDataNeedingReply");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " idle:ReceivedDataNeedingReply");
receivedDataNeedingReply(frame);
state = MasterNodeState.answerDataRequest;
replyDeadline = lastNonSilence + Constants.REPLY_DELAY;
}
}
private void useToken() {
Frame frameToSend = null;
synchronized (framesToSend) {
if (!framesToSend.isEmpty())
frameToSend = framesToSend.remove(0);
}
if (frameToSend == null) {
// NothingToSend
// debug("useToken:NothingToSend");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " useToken:NothingToSend");
frameCount = maxInfoFrames;
state = MasterNodeState.doneWithToken;
}
else {
activity = true;
if (frameToSend.getFrameType().oneOf(FrameType.testResponse, FrameType.bacnetDataNotExpectingReply)) {
// SendNoWait
// debug("useToken:SendNoWait to " + frameToSend.frame.getDestinationAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " useToken:SendNoWait");
state = MasterNodeState.doneWithToken;
}
else if (frameToSend.getFrameType().oneOf(FrameType.testRequest, FrameType.bacnetDataExpectingReply)) {
// SendAndWait
// debug("useToken:SendAndWait to " + frameToSend.frame.getDestinationAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " useToken:SendAndWait");
state = MasterNodeState.waitForReply;
}
else
throw new RuntimeException("Unhandled frame type: " + frameToSend.getFrameType());
sendFrame(frameToSend);
frameCount++;
}
}
private void waitForReply() {
if (silence() > Constants.REPLY_TIMEOUT) {
// ReplyTimeout - assume that the request has failed
// debug("waitForReply:ReplyTimeout");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " waitForReply:ReplyTimeout");
frameCount = maxInfoFrames;
state = MasterNodeState.doneWithToken;
}
else if (receivedInvalidFrame != null) {
// InvalidFrame
// debug("waitForReply:InvalidFrame: '" + receivedInvalidFrame + "', frame=" + frame);
if (LOG.isLoggable(Level.FINE))
LOG.fine("Received invalid frame: " + receivedInvalidFrame);
receivedInvalidFrame = null;
state = MasterNodeState.doneWithToken;
activity = true;
}
else if (receivedValidFrame) {
activity = true;
FrameType type = frame.getFrameType();
if (frame.forStation(thisStation)) {
if (type.oneOf(FrameType.testResponse, FrameType.bacnetDataNotExpectingReply)) {
// ReceivedReply
//debug("waitForReply:ReceivedReply from " + frame.getSourceAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " waitForReply:ReceivedReply");
receivedDataNoReply(frame);
}
else if (type.oneOf(FrameType.replyPostponed)) {
// ReceivedPostpone
//debug("waitForReply:ReceivedPostpone from " + frame.getSourceAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " waitForReply:ReceivedPostpone");
; // the reply to the message has been postponed until a later time.
}
state = MasterNodeState.doneWithToken;
}
else if (!type.oneOf(FrameType.testResponse, FrameType.bacnetDataNotExpectingReply)) {
// ReceivedUnexpectedFrame
//debug("waitForReply:ReceivedUnexpectedFrame: " + frame);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " waitForReply:ReceivedUnexpectedFrame");
// This may indicate the presence of multiple tokens.
state = MasterNodeState.idle;
}
receivedValidFrame = false;
}
}
/**
* The DONE_WITH_TOKEN state either sends another data frame, passes the token, or initiates a Poll For Master
* cycle.
*/
private void doneWithToken() {
activity = true;
if (frameCount < maxInfoFrames) {
// SendAnotherFrame
//debug("doneWithToken:SendAnotherFrame");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " doneWithToken:SendAnotherFrame");
state = MasterNodeState.useToken;
}
else if (tokenCount < Constants.POLL - 1 && soleMaster) {
// SoleMaster
//debug("doneWithToken:SoleMaster");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " doneWithToken:SoleMaster");
frameCount = 0;
tokenCount++;
state = MasterNodeState.useToken;
}
else if ((tokenCount < Constants.POLL - 1 && !soleMaster) || nextStation == adjacentStation(thisStation)) {
// SendToken
//debug("doneWithToken:SendToken to " + nextStation);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " doneWithToken:SendToken");
tokenCount++;
sendFrame(FrameType.token, nextStation);
retryCount = 0;
eventCount = 0;
state = MasterNodeState.passToken;
}
else if (tokenCount >= Constants.POLL - 1 && adjacentStation(pollStation) != nextStation) {
// SendMaintenancePFM
//debug("doneWithToken:SendMaintenancePFM to " + adjacentStation(pollStation));
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " doneWithToken:SendMaintenancePFM");
pollStation = adjacentStation(pollStation);
sendFrame(FrameType.pollForMaster, pollStation);
retryCount = 0;
state = MasterNodeState.pollForMaster;
}
else if (tokenCount >= Constants.POLL - 1 && adjacentStation(pollStation) == nextStation && !soleMaster) {
// ResetMaintenancePFM
//debug("doneWithToken:ResetMaintenancePFM: next=" + nextStation);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " doneWithToken:ResetMaintenancePFM");
pollStation = thisStation;
sendFrame(FrameType.token, nextStation);
retryCount = 0;
eventCount = 0;
tokenCount = 1;
state = MasterNodeState.passToken;
}
// NOTE: variation from spec here ----vvv (i.e. added the "- 1")
else if (tokenCount >= Constants.POLL - 1 && adjacentStation(pollStation) == nextStation && soleMaster) {
// SoleMasterRestartMaintenancePFM
//debug("doneWithToken:SoleMasterRestartMaintenancePFM: poll=" + pollStation);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " doneWithToken:SoleMasterRestartMaintenancePFM");
pollStation = adjacentStation(nextStation);
sendFrame(FrameType.pollForMaster, pollStation);
nextStation = thisStation;
retryCount = 0;
eventCount = 0;
tokenCount = 1;
state = MasterNodeState.pollForMaster;
}
}
/**
* The PASS_TOKEN state listens for a successor to begin using the token that this node has just attempted to pass.
*/
private void passToken() {
activity = true;
if (silence() < Constants.USAGE_TIMEOUT && eventCount > Constants.MIN_OCTETS) {
// SawTokenUser
// debug("passToken:SawTokenUser");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " passToken:SawTokenUser");
state = MasterNodeState.idle;
}
else if (silence() >= Constants.USAGE_TIMEOUT && retryCount < Constants.RETRY_TOKEN) {
// RetrySendToken
// debug("passToken:RetrySendToken to " + nextStation);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " passToken:RetrySendToken");
retryCount++;
sendFrame(FrameType.token, nextStation);
eventCount = 0;
}
else if (silence() >= Constants.USAGE_TIMEOUT && retryCount >= Constants.RETRY_TOKEN) {
// FindNewSuccessor
// debug("passToken:FindNewSuccessor: trying " + adjacentStation(nextStation));
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " passToken:FindNewSuccessor");
pollStation = adjacentStation(nextStation);
sendFrame(FrameType.pollForMaster, pollStation);
nextStation = thisStation;
retryCount = 0;
tokenCount = 0;
eventCount = 0;
state = MasterNodeState.pollForMaster;
}
}
/**
* The NO_TOKEN state is entered if SilenceTimer becomes greater than Tno_token, indicating that there has been no
* network activity for that period of time. The timeout is continued to determine whether or not this node may
* create a token.
*/
private void noToken() {
long silence = silence();
long delay = Constants.NO_TOKEN + Constants.SLOT * (thisStation & 0xff);
if (silence < delay && eventCount > Constants.MIN_OCTETS) {
// SawFrame
// debug("noToken:SawFrame");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " noToken:SawFrame");
state = MasterNodeState.idle;
activity = true;
}
else if ((silence >= delay && silence < delay + Constants.SLOT) // Silence is in this master's slot.
// Silence is beyond all slots.
|| (silence > Constants.NO_TOKEN + Constants.SLOT * (Constants.MAX_MASTER + 1))) {
// GenerateToken
// debug("noToken:GenerateToken: poll=" + adjacentStation(thisStation));
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " noToken:GenerateToken");
pollStation = adjacentStation(thisStation);
sendFrame(FrameType.pollForMaster, pollStation);
nextStation = thisStation;
tokenCount = 0;
retryCount = 0;
eventCount = 0;
state = MasterNodeState.pollForMaster;
activity = true;
}
}
/**
* In the POLL_FOR_MASTER state, the node listens for a reply to a previously sent Poll For Master frame in order to
* find a successor node.
*/
private void pollForMaster() {
if (receivedValidFrame) {
if (frame.forStation(thisStation) && frame.getFrameType() == FrameType.replyToPollForMaster) {
// ReceivedReplyToPFM
// debug("pollForMaster:ReceivedReplyToPFM from " + frame.getSourceAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " pollForMaster:ReceivedReplyToPFM");
soleMaster = false;
nextStation = frame.getSourceAddress();
eventCount = 0;
sendFrame(FrameType.token, nextStation);
pollStation = thisStation;
tokenCount = 0;
retryCount = 0;
receivedValidFrame = false;
state = MasterNodeState.passToken;
}
else {
// ReceivedUnexpectedFrame
// debug("pollForMaster:ReceivedUnexpectedFrame: " + frame);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " pollForMaster:ReceivedUnexpectedFrame");
receivedValidFrame = false;
state = MasterNodeState.idle;
}
activity = true;
}
else if (soleMaster && (silence() >= Constants.USAGE_TIMEOUT || receivedInvalidFrame != null)) {
// SoleMaster
// debug("pollForMaster:SoleMaster");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " pollForMaster:SoleMaster");
frameCount = 0;
receivedInvalidFrame = null;
state = MasterNodeState.useToken;
activity = true;
}
else if (!soleMaster) {
boolean longCondition = silence() >= Constants.USAGE_TIMEOUT || receivedInvalidFrame != null;
if (nextStation != thisStation && longCondition) {
// DoneWithPFM
// debug("pollForMaster:DoneWithPFM passing token to " + nextStation);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " pollForMaster:DoneWithPFM");
eventCount = 0;
sendFrame(FrameType.token, nextStation);
retryCount = 0;
receivedInvalidFrame = null;
state = MasterNodeState.passToken;
activity = true;
}
else if (nextStation == thisStation) {
if (adjacentStation(pollStation) != thisStation && longCondition) {
// SendNextPFM
// debug("pollForMaster:SendNextPFM to " + adjacentStation(pollStation));
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " pollForMaster:SendNextPFM");
pollStation = adjacentStation(pollStation);
sendFrame(FrameType.pollForMaster, pollStation);
retryCount = 0;
receivedInvalidFrame = null;
activity = true;
}
else if (adjacentStation(pollStation) == thisStation && longCondition) {
// DeclareSoleMaster
// debug("pollForMaster:DeclareSoleMaster");
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " pollForMaster:DeclareSoleMaster");
soleMaster = true;
frameCount = 0;
receivedInvalidFrame = null;
state = MasterNodeState.useToken;
activity = true;
}
}
}
}
/**
* The ANSWER_DATA_REQUEST state is entered when a BACnet Data Expecting Reply, a Test_Request, or a proprietary
* frame that expects a reply is received.
*/
private void answerDataRequest() {
synchronized (this) {
if (replyFrame != null) {
// Reply
// debug("answerDataRequest:Reply with " + replyFrame);
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " answerDataRequest:Reply");
sendFrame(replyFrame);
replyFrame = null;
state = MasterNodeState.idle;
activity = true;
}
else if (replyDeadline >= timeSource.currentTimeMillis()) {
// DeferredReply
// debug("answerDataRequest:DeferredReply to " + frameToReply.getSourceAddress());
if (LOG.isLoggable(Level.FINE))
LOG.fine(thisStation + " answerDataRequest:DeferredReply");
sendFrame(FrameType.replyPostponed, frame.getSourceAddress());
state = MasterNodeState.idle;
activity = true;
}
}
}
private byte adjacentStation(byte station) {
int i = station & 0xff;
i = (i + 1) % Constants.MAX_MASTER;
return (byte) i;
}
}