package com.serotonin.bacnet4j.npdu.mstp;
import gnu.io.SerialPort;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang3.StringUtils;
import com.serotonin.bacnet4j.util.ClockTimeSource;
import com.serotonin.bacnet4j.util.TimeSource;
import org.free.bacnet4j.util.StreamUtils;
import org.free.bacnet4j.util.SerialParameters;
import org.free.bacnet4j.util.SerialUtils;
import org.free.bacnet4j.util.ByteQueue;
abstract public class MstpNode implements Runnable {
private static final Logger LOG = Logger.getLogger(MstpNode.class.toString());
private static final byte PREAMBLE1 = 0x55;
private static final byte PREAMBLE2 = (byte) 0xFF;
private static final int MAX_FRAME_LENGTH = 501;
public static boolean DEBUG = false;
private enum ReadFrameState {
idle, preamble, header, headerCrc, data, dataCrc;
}
//
// Configuration
protected int inactivityDelay = 1;
/**
* The MAC address of this node. TS is generally read from a hardware DIP switch, or from nonvolatile memory. Valid
* values for TS are 0 to 254. The value 255 is used to denote broadcast when used as a destination address but is
* not allowed as a value for TS.
*/
protected final byte thisStation;
//
// Fields
private MstpNetwork network;
private SerialParameters serialParams;
private SerialPort serialPort;
private OutputStream out;
private InputStream in;
private final byte[] readArray = new byte[512];
private int readCount;
private final Frame sendFrame = new Frame();
private final HeaderCRC sendHeaderCRC = new HeaderCRC();
private final DataCRC sendDataCRC = new DataCRC();
protected TimeSource timeSource = new ClockTimeSource();
private long start;
Thread thread;
private volatile boolean running;
private ReadFrameState state;
private String lastWriteError;
public MstpNode(SerialParameters serialParams, byte thisStation) {
this.serialParams = serialParams;
this.thisStation = thisStation;
}
public MstpNode(InputStream in, OutputStream out, byte thisStation) {
this.in = in;
this.out = out;
this.thisStation = thisStation;
}
String getCommPortId() {
if (serialParams == null)
return null;
return serialParams.getCommPortId();
}
public void initialize() throws Exception {
initialize(true);
}
public void initialize(boolean runInThread) throws Exception {
if (!running) {
if (serialParams != null) {
serialPort = SerialUtils.openSerialPort(serialParams);
in = serialPort.getInputStream();
out = serialPort.getOutputStream();
}
start = timeSource.currentTimeMillis();
running = true;
lastNonSilence = timeSource.currentTimeMillis();
state = ReadFrameState.idle;
if (runInThread) {
thread = new Thread(this);
thread.start();
}
}
}
public void terminate() {
running = false;
}
public void setNetwork(MstpNetwork network) {
this.network = network;
}
/**
* @param timeSource
* the timeSource to set
*/
public void setTimeSource(TimeSource timeSource) {
this.timeSource = timeSource;
}
/**
* The flag ReceiveError is TRUE if an error is detected during the reception of an octet. Many common UARTs detect
* several types of receive errors, in particular framing errors and overrun errors. ReceiveError shall be TRUE if
* any of these errors is detected.
*/
private boolean receiveError;
/**
* Substitute for the silence timer.
*/
protected long lastNonSilence;
/**
* Used to store the data on the incoming frame.
*/
protected final Frame frame = new Frame();
/**
* The header CRC accumulator.
*/
private final HeaderCRC headerCRC = new HeaderCRC();
/**
* The data CRC accumulator.
*/
private final DataCRC dataCRC = new DataCRC();
/**
* Used as an index by the Receive State Machine, up to a maximum value of InputBufferSize.
*/
private int index;
/**
* Used to count the number of received octets or errors. This is used in the detection of link activity.
*/
int eventCount;
/**
* The number of frames sent by this node during a single token hold. When this counter reaches the value
* Nmax_info_frames, the node must pass the token.
*/
protected int frameCount;
/**
* An array of octets, used to store octets as they are received. InputBuffer is indexed from 0 to
* InputBufferSize-1. The maximum size of a frame is 501 octets. A smaller value for InputBufferSize may be used by
* some implementations.
*/
private final ByteQueue inputBuffer = new ByteQueue(MAX_FRAME_LENGTH);
/**
* ReceivedInvalidFrame A Boolean flag set to TRUE by the Receive State Machine if an error is detected during the
* reception of a frame. Set to FALSE by the main state machine.
*/
protected String receivedInvalidFrame;
/**
* A Boolean flag set to TRUE by the Receive State Machine if a valid frame is received. Set to FALSE by the main
* state machine.
*/
protected boolean receivedValidFrame;
protected boolean activity;
/**
* @return the running
*/
public boolean isRunning() {
return running;
}
/**
* @return the thisStation
*/
public byte getThisStation() {
return thisStation;
}
@Override
public final void run() {
while (running) {
activity = false;
doCycle();
if (!activity && inactivityDelay > 0) {
try {
Thread.sleep(inactivityDelay);
}
catch (InterruptedException e) {
// no op
}
}
}
if (serialPort != null)
SerialUtils.close(serialPort);
}
abstract protected void doCycle();
abstract public void setReplyFrame(FrameType type, byte destination, byte[] data);
protected void readFrame() {
readInputStream();
if (receiveError) {
// EatAnError
receiveError = false;
eventCount++;
state = ReadFrameState.idle;
activity = true;
}
if (!receivedValidFrame) {
if (state == ReadFrameState.idle)
idle();
if (state == ReadFrameState.preamble)
preamble();
if (state == ReadFrameState.header)
header();
if (state == ReadFrameState.headerCrc)
headerCrc();
if (state == ReadFrameState.data)
data();
if (state == ReadFrameState.dataCrc)
dataCrc();
}
}
protected long silence() {
return timeSource.currentTimeMillis() - lastNonSilence;
}
private void readInputStream() {
try {
if (in.available() > 0) {
readCount = in.read(readArray);
if (DEBUG)
debug("in: " + StreamUtils.dumpArrayHex(readArray, 0, readCount));
inputBuffer.push(readArray, 0, readCount);
eventCount += readCount;
noise();
}
}
catch (IOException e) {
if (StringUtils.equals(e.getMessage(), "Stream closed."))
throw new RuntimeException(e);
if (LOG.isLoggable(Level.FINE))
LOG.log(Level.FINE, "Input stream listener exception", e);
receiveError = true;
}
}
private void idle() {
byte b;
while (inputBuffer.size() > 0) {
activity = true;
b = inputBuffer.pop();
if (b == PREAMBLE1) {
// Preamble1
state = ReadFrameState.preamble;
break;
}
// else
// EatAnOctet
// ; // EatAnOctet
}
}
private void preamble() {
if (silence() > Constants.FRAME_ABORT) {
// Timeout
state = ReadFrameState.idle;
activity = true;
}
else {
byte b;
while (inputBuffer.size() > 0) {
activity = true;
b = inputBuffer.pop();
if (b == PREAMBLE1) {
// RepeatedPreamble1
// no op
}
else if (b == PREAMBLE2) {
// Preamble2
frame.reset();
headerCRC.reset();
index = 0;
state = ReadFrameState.header;
break;
}
else {
// NotPreamble
state = ReadFrameState.idle;
break;
}
}
}
}
private void header() {
if (silence() > Constants.FRAME_ABORT) {
// Timeout
receivedInvalidFrame = "Timeout reading header";
if (LOG.isLoggable(Level.FINE))
LOG.fine("Timeout reading header: index=" + index + ", frame=" + frame);
state = ReadFrameState.idle;
activity = true;
}
else {
byte b;
while (inputBuffer.size() > 0) {
activity = true;
b = inputBuffer.pop();
if (index == 0) {
// FrameType
headerCRC.accumulate(b);
frame.setFrameType(FrameType.forId(b));
if (DEBUG && frame.getFrameType() == null)
debug("Unknown frame type for value: " + b);
index = 1;
}
else if (index == 1) {
// Destination
headerCRC.accumulate(b);
frame.setDestinationAddress(b);
index = 2;
}
else if (index == 2) {
// Source
headerCRC.accumulate(b);
frame.setSourceAddress(b);
index = 3;
}
else if (index == 3) {
// Length1
headerCRC.accumulate(b);
frame.setLength((b & 0xff) << 8);
index = 4;
}
else if (index == 4) {
// Length2
headerCRC.accumulate(b);
frame.setLength(frame.getLength() | (b & 0xff));
index = 5;
}
else if (index == 5) {
// HeaderCRC
headerCRC.accumulate(b);
state = ReadFrameState.headerCrc;
break;
}
}
}
}
private void headerCrc() {
activity = true;
if (!headerCRC.isOk()) {
// BadCRC
receivedInvalidFrame = "Bad header CRC. Frame: " + frame;
state = ReadFrameState.idle;
}
else {
if (!frame.forStationOrBroadcast(thisStation))
// NotForUs
state = ReadFrameState.idle;
else if (frame.getLength() > MAX_FRAME_LENGTH) {
// FrameTooLong
receivedInvalidFrame = "Frame too long";
state = ReadFrameState.idle;
}
else if (frame.getLength() == 0) {
// NoData
receivedValidFrame = true;
if (frame.getFrameType() == null && LOG.isLoggable(Level.FINE))
LOG.fine("Received valid frame with no type (1): " + frame);
state = ReadFrameState.idle;
}
else {
// Data
index = 0;
dataCRC.reset();
state = ReadFrameState.data;
frame.setData(new byte[frame.getLength()]);
}
}
}
private void data() {
if (silence() > Constants.FRAME_ABORT) {
// Timeout
receivedInvalidFrame = "Timeout reading data";
state = ReadFrameState.idle;
activity = true;
}
else {
while (inputBuffer.size() > 0) {
activity = true;
byte b = inputBuffer.pop();
if (index < frame.getLength()) {
// DataOctet
dataCRC.accumulate(b);
frame.getData()[index] = b;
index++;
}
else if (index == frame.getLength()) {
// CRC1
dataCRC.accumulate(b);
index++;
}
else if (index == frame.getLength() + 1) {
// CRC2
dataCRC.accumulate(b);
state = ReadFrameState.dataCrc;
break;
}
}
}
}
private void dataCrc() {
activity = true;
if (!dataCRC.isOk())
// BadCRC
receivedInvalidFrame = "Bad data CRC";
else {
// GoodCRC
receivedValidFrame = true;
if (frame.getFrameType() == null && LOG.isLoggable(Level.FINE))
LOG.fine("Received valid frame with no type (2): " + frame);
}
state = ReadFrameState.idle;
}
protected void sendFrame(FrameType type, byte destinationAddress) {
sendFrame(type, destinationAddress, null);
}
protected void sendFrame(FrameType type, byte destinationAddress, byte[] data) {
sendFrame.reset();
sendFrame.setFrameType(type);
sendFrame.setDestinationAddress(destinationAddress);
sendFrame.setSourceAddress(thisStation);
sendFrame.setData(data);
sendFrame(sendFrame);
}
public void testSendFrame(Frame frame) {
sendFrame(frame);
}
protected void sendFrame(Frame frame) {
// long start = timeSource.currentTimeMillis();
// Turnaround. 40 bit times at 9600 baud is around 4ms.
// long wait = 4 - silence();
// if (wait > 0) {
// debug("Turnaround wait time: " + wait);
// try {
// Thread.sleep(wait);
// }
// catch (InterruptedException e) {
// // no op
// }
// }
try {
if (DEBUG)
debug("out: " + frame);
//LOG.fine("writing frame: " + frame);
// Preamble
out.write(0x55);
out.write(0xFF);
// Header
out.write(frame.getFrameType().id & 0xff);
out.write(frame.getDestinationAddress() & 0xff);
out.write(frame.getSourceAddress() & 0xff);
out.write((frame.getLength() >> 8) & 0xff);
out.write(frame.getLength() & 0xff);
out.write(sendHeaderCRC.getCrc(frame));
if (frame.getLength() > 0) {
// Data
out.write(frame.getData());
int crc = sendDataCRC.getCrc(frame);
out.write(crc & 0xff);
out.write((crc >> 8) & 0xff);
}
out.flush();
}
catch (IOException e) {
// Only write the same error message once. Prevents logs from getting filled up unnecessarily with repeated
// error messages.
if (!StringUtils.equals(e.getMessage(), lastWriteError)) {
// NOTE: should anything else be informed of this?
LOG.log(Level.SEVERE, "Error while sending frame", e);
lastWriteError = e.getMessage();
}
}
noise();
// debug("Write took " + (timeSource.currentTimeMillis() - start) + " ms");
// NOTE: ??
// Wait until the final stop bit of the most significant CRC octet has been transmitted
// but not more than Tpostdrive.
}
private void noise() {
lastNonSilence = timeSource.currentTimeMillis();
}
protected void debug(String msg) {
System.out.println(thisStation + "/" + (timeSource.currentTimeMillis() - start) + ": " + msg);
}
//
//
// Incoming message handling
//
protected void receivedDataNoReply(Frame frame) {
if (frame.getFrameType() == FrameType.testResponse)
LOG.info("Received test response frame");
else if (network == null)
LOG.info("Received data no reply: " + frame);
else
network.receivedFrame(frame.copy());
}
protected void receivedDataNeedingReply(Frame frame) {
if (frame.getFrameType() == FrameType.testRequest)
// Echo the data back.
sendFrame(FrameType.testResponse, frame.getSourceAddress(), frame.getData());
else if (network == null)
LOG.info("Received data needing reply: " + frame);
else
network.receivedFrame(frame.copy());
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((serialParams == null) ? 0 : serialParams.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MstpNode other = (MstpNode) obj;
if (serialParams == null) {
if (other.serialParams != null)
return false;
}
else if (!serialParams.equals(other.serialParams))
return false;
return true;
}
}