001package jmri.jmrix.secsi;
002
003import java.io.DataInputStream;
004import jmri.jmrix.AbstractMRListener;
005import jmri.jmrix.AbstractMRMessage;
006import jmri.jmrix.AbstractMRNodeTrafficController;
007import jmri.jmrix.AbstractMRReply;
008import org.slf4j.Logger;
009import org.slf4j.LoggerFactory;
010
011/**
012 * Converts Stream-based I/O to/from SECSI serial messages.
013 * <p>
014 * The "SerialInterface" side sends/receives message objects.
015 * <p>
016 * The connection to a SerialPortController is via a pair of *Streams, which
017 * then carry sequences of characters for transmission. Note that this
018 * processing is handled in an independent thread.
019 * <p>
020 * This handles the state transitions, based on the necessary state in each
021 * message.
022 * <p>
023 * Handles initialization, polling, output, and input for multiple Serial Nodes.
024 *
025 * @author Bob Jacobsen Copyright (C) 2003, 2006
026 * @author Bob Jacobsen, Dave Duchamp, multiNode extensions, 2004
027 */
028public class SerialTrafficController extends AbstractMRNodeTrafficController implements SerialInterface {
029
030    /**
031     * Create a new SerialTrafficController instance.
032     *
033     * @param adaptermemo the associated SystemConnectionMemo
034     */
035    public SerialTrafficController(SecsiSystemConnectionMemo adaptermemo) {
036        super();
037        memo = adaptermemo;
038        log.debug("creating a new SECSI SerialTrafficController object on {}", adaptermemo.getSystemPrefix());
039        // set node range
040        init(0, 255);
041
042        // entirely poll driven, so reduce interval
043        mWaitBeforePoll = 25;  // default = 25
044    }
045
046    // The methods to implement the SerialInterface
047
048    @Override
049    public synchronized void addSerialListener(SerialListener l) {
050        this.addListener(l);
051    }
052
053    @Override
054    public synchronized void removeSerialListener(SerialListener l) {
055        this.removeListener(l);
056    }
057
058    /**
059     * Set up for initialization of a Serial node.
060     * @param node node to initialize.
061     */
062    public void initializeSerialNode(SerialNode node) {
063        synchronized (this) {
064            // find the node in the registered node list
065            for (int i = 0; i < getNumNodes(); i++) {
066                if (getNode(i) == node) {
067                    // found node - set up for initialization
068                    setMustInit(i, true);
069                    return;
070                }
071            }
072        }
073    }
074
075    @Override
076    protected AbstractMRMessage enterProgMode() {
077        log.warn("enterProgMode doesn't make sense for SECSI serial");
078        return null;
079    }
080
081    @Override
082    protected AbstractMRMessage enterNormalMode() {
083        return null;
084    }
085
086    /**
087     * Forward a SerialMessage to all registered SerialInterface listeners.
088     */
089    @Override
090    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
091        ((SerialListener) client).message((SerialMessage) m);
092    }
093
094    /**
095     * Forward a SerialReply to all registered SerialInterface listeners.
096     */
097    @Override
098    protected void forwardReply(AbstractMRListener client, AbstractMRReply m) {
099        ((SerialListener) client).reply((SerialReply) m);
100    }
101
102    SerialSensorManager mSensorManager = null;
103
104    public void setSensorManager(SerialSensorManager m) {
105        mSensorManager = m;
106    }
107
108    /**
109     * Handles initialization, output and polling from within the running thread
110     */
111    @Override
112    protected synchronized AbstractMRMessage pollMessage() {
113        // ensure validity of call
114        if (getNumNodes() <= 0) {
115            return null;
116        }
117
118        // move to a new node
119        curSerialNodeIndex++;
120        if (curSerialNodeIndex >= getNumNodes()) {
121            curSerialNodeIndex = 0;
122        }
123        // ensure that each node is initialized        
124        if (getMustInit(curSerialNodeIndex)) {
125            setMustInit(curSerialNodeIndex, false);
126            AbstractMRMessage m = getNode(curSerialNodeIndex).createInitPacket();
127            if (m != null) { // SECSI boards don't need this yet, so createInitPacket() returns null
128                log.debug("send init message: {} to node {}", m.toString(), curSerialNodeIndex);
129                m.setTimeout(2000);  // wait for init to finish (milliseconds)
130                return m;
131            }   // else fall through to continue
132        }
133        // send Output packet if needed
134        if (getNode(curSerialNodeIndex).mustSend()) {
135            log.debug("request write command to send");
136            AbstractMRMessage m = getNode(curSerialNodeIndex).createOutPacket();
137            getNode(curSerialNodeIndex).resetMustSend();
138            m.setTimeout(500);
139            return m;
140        }
141        // poll for Sensor input
142        if (getNode(curSerialNodeIndex).getSensorsActive()) {
143            // Some sensors are active for this node, issue poll
144            log.debug("poll command start for {} nodes", getNumNodes());
145            SerialMessage m = SerialMessage.getPoll(
146                    getNode(curSerialNodeIndex).getNodeAddress());
147            if (curSerialNodeIndex >= getNumNodes()) {
148                curSerialNodeIndex = 0;
149            }
150            log.debug("poll command created");
151            return m;
152        } else {
153            // no Sensors (inputs) are active for this node
154            return null;
155        }
156    }
157
158    @Override
159    synchronized protected void handleTimeout(AbstractMRMessage m, AbstractMRListener l) {
160        // inform node, and if it resets then reinitialize 
161        if (getNode(curSerialNodeIndex) != null) {
162            if (getNode(curSerialNodeIndex).handleTimeout(m, l)) {
163                setMustInit(curSerialNodeIndex, true);
164            } else {
165                log.warn("Timeout can't be handled due to missing node (index {})", curSerialNodeIndex);
166            }
167        }
168    }
169
170    @Override
171    synchronized protected void resetTimeout(AbstractMRMessage m) {
172        // inform node
173        getNode(curSerialNodeIndex).resetTimeout(m);
174    }
175
176    @Override
177    protected AbstractMRListener pollReplyHandler() {
178        return mSensorManager;
179    }
180
181    /**
182     * Forward a preformatted message to the actual interface.
183     */
184    @Override
185    public void sendSerialMessage(SerialMessage m, SerialListener reply) {
186        sendMessage(m, reply);
187    }
188
189    /**
190     * Reference to the system connection memo.
191     */
192    SecsiSystemConnectionMemo memo = null;
193
194    /**
195     * Get access to the system connection memo associated with this traffic
196     * controller.
197     *
198     * @return associated systemConnectionMemo object
199     */
200    public SecsiSystemConnectionMemo getSystemConnectionMemo() {
201        return memo;
202    }
203
204    /**
205     * Set the system connection memo associated with this traffic controller.
206     *
207     * @param m associated systemConnectionMemo object
208     */
209    public void setSystemConnectionMemo(SecsiSystemConnectionMemo m) {
210        log.debug("Secsi SerialTrafficController set memo to {}", m.getUserName());
211        memo = m;
212    }
213
214    @Override
215    protected AbstractMRReply newReply() {
216        return new SerialReply();
217    }
218
219    @Override
220    protected boolean endOfMessage(AbstractMRReply msg) {
221        // our version of loadChars doesn't invoke this, so it shouldn't be called
222        log.error("Not using endOfMessage, should not be called");
223        return false;
224    }
225
226    protected int currentAddr = -1; // at startup, can't match
227    protected int incomingLength = 0;
228
229    @Override
230    protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
231        // get 1st byte, see if ending too soon
232        byte char1 = readByteProtected(istream);
233        msg.setElement(0, char1 & 0xFF);
234        if ((char1 & 0xFF) != currentAddr) {
235            // mismatch, end early
236            return;
237        }
238        if (incomingLength <= 1) {
239            return;
240        }
241        for (int i = 1; i < incomingLength; i++) {  // reading next four bytes
242            char1 = readByteProtected(istream);
243            msg.setElement(i, char1 & 0xFF);
244        }
245    }
246
247    @Override
248    protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException {
249        // does nothing
250    }
251
252    /**
253     * Add header to the outgoing byte stream.
254     *
255     * @param msg The output byte stream
256     * @return next location in the stream to fill
257     */
258    @Override
259    protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) {
260        return 0;  // Do nothing
261    }
262
263    /**
264     * Although this protocol doesn't use a trailer, we implement this method to
265     * set the expected reply length and address for this message.
266     *
267     * @param msg    The output byte stream
268     * @param offset the first byte not yet used
269     * @param m      the original message
270     */
271    @Override
272    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
273        incomingLength = ((SerialMessage) m).getResponseLength();
274        currentAddr = ((SerialMessage) m).getAddr();
275        return;
276    }
277
278    /**
279     * Determine how many bytes the entire message will take, including
280     * space for header and trailer.
281     * @see SerialMessage#getPoll(int)
282     * @see SerialNode#createOutPacket()
283     *
284     * @param m the message to be sent
285     * @return number of bytes in message
286     */
287    @Override
288    protected int lengthOfByteStream(AbstractMRMessage m) {
289        return m.getNumDataElements(); // Length varies by type. A fixed size of 5 as
290        // was copied from OakTree.SerialTrafficController#lengthOfByteStream
291        // caused an ArrayIndexOutOfBounds exception in AbstractSerialTrafficController
292        // over the 9 byte Node Reply message
293    }
294
295    private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class);
296
297}