001package jmri.jmrix.oaktree;
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 * Convert Stream-based I/O to/from Oak Tree 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 Oaktree SerialTrafficController instance. Simple implementation.
032     *
033     * @param adaptermemo the associated SystemConnectionMemo
034     */
035    public SerialTrafficController(OakTreeSystemConnectionMemo adaptermemo) {
036        super();
037        memo = adaptermemo;
038        log.debug("creating a new GrapevineTrafficController 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        // clear the array of SerialNodes
046    }
047
048    // The methods to implement the SerialInterface
049    @Override
050    public synchronized void addSerialListener(SerialListener l) {
051        this.addListener(l);
052    }
053
054    @Override
055    public synchronized void removeSerialListener(SerialListener l) {
056        this.removeListener(l);
057    }
058
059    /**
060     * Set up for initialization of a Serial node.
061     * @param node node to initialize.
062     */
063    public void initializeSerialNode(SerialNode node) {
064        synchronized (this) {
065            // find the node in the registered node list
066            for (int i = 0; i < getNumNodes(); i++) {
067                if (getNode(i) == node) {
068                    // found node - set up for initialization
069                    setMustInit(i, true);
070                    return;
071                }
072            }
073        }
074    }
075
076    @Override
077    protected AbstractMRMessage enterProgMode() {
078        log.warn("enterProgMode doesn't make sense for Oak Tree serial");
079        return null;
080    }
081
082    @Override
083    protected AbstractMRMessage enterNormalMode() {
084        return null;
085    }
086
087    /**
088     * Forward a SerialMessage to all registered SerialInterface listeners.
089     */
090    @Override
091    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
092        ((SerialListener) client).message((SerialMessage) m);
093    }
094
095    /**
096     * Forward a SerialReply to all registered SerialInterface listeners.
097     */
098    @Override
099    protected void forwardReply(AbstractMRListener client, AbstractMRReply m) {
100        ((SerialListener) client).reply((SerialReply) m);
101    }
102
103    SerialSensorManager mSensorManager = null;
104
105    public void setSensorManager(SerialSensorManager m) {
106        mSensorManager = m;
107    }
108
109    /**
110     * Handles initialization, output and polling for Oak Tree from within the
111     * running thread
112     */
113    @Override
114    protected synchronized AbstractMRMessage pollMessage() {
115        // ensure validity of call
116        if (getNumNodes() <= 0) {
117            return null;
118        }
119
120        // move to a new node
121        curSerialNodeIndex++;
122        if (curSerialNodeIndex >= getNumNodes()) {
123            curSerialNodeIndex = 0;
124        }
125        // ensure that each node is initialized        
126        if (getMustInit(curSerialNodeIndex)) {
127            setMustInit(curSerialNodeIndex, false);
128            AbstractMRMessage m = getNode(curSerialNodeIndex).createInitPacket();
129            if (m != null) { // Oak Tree boards don't need this yet
130                log.debug("send init message: {}", m.toString());
131                m.setTimeout(2000);  // wait for init to finish (milliseconds)
132                return m;
133            }   // else fall through to continue
134        }
135        // send Output packet if needed
136        if (getNode(curSerialNodeIndex).mustSend()) {
137            log.debug("request write command to send");
138            AbstractMRMessage m = getNode(curSerialNodeIndex).createOutPacket();
139            getNode(curSerialNodeIndex).resetMustSend();
140            m.setTimeout(500);
141            return m;
142        }
143        // poll for Sensor input
144        if (getNode(curSerialNodeIndex).getSensorsActive()) {
145            // Some sensors are active for this node, issue poll
146            SerialMessage m = SerialMessage.getPoll(
147                    getNode(curSerialNodeIndex).getNodeAddress());
148            if (curSerialNodeIndex >= getNumNodes()) {
149                curSerialNodeIndex = 0;
150            }
151            return m;
152        } else {
153            // no Sensors (inputs) are active for this node
154            return null;
155        }
156    }
157
158    @Override
159    protected synchronized 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    protected synchronized void resetTimeout(AbstractMRMessage m) {
172        // inform node
173        getNode(curSerialNodeIndex).resetTimeout(m);
174
175    }
176
177    @Override
178    protected AbstractMRListener pollReplyHandler() {
179        return mSensorManager;
180    }
181
182    /**
183     * Forward a preformatted message to the actual interface.
184     */
185    @Override
186    public void sendSerialMessage(SerialMessage m, SerialListener reply) {
187        sendMessage(m, reply);
188    }
189
190    /**
191     * Reference to the system connection memo.
192     */
193    OakTreeSystemConnectionMemo memo = null;
194
195    /**
196     * Get access to the system connection memo associated with this traffic
197     * controller.
198     *
199     * @return associated systemConnectionMemo object
200     */
201    public OakTreeSystemConnectionMemo getSystemConnectionMemo() {
202        return memo;
203    }
204
205    /**
206     * Set the system connection memo associated with this traffic controller.
207     *
208     * @param m associated systemConnectionMemo object
209     */
210    public void setSystemConnectionMemo(OakTreeSystemConnectionMemo m) {
211        log.debug("OakTree SerialTrafficController set memo to {}", m.getUserName());
212        memo = m;
213    }
214
215    @Override
216    protected AbstractMRReply newReply() {
217        return new SerialReply();
218    }
219
220    @Override
221    protected boolean endOfMessage(AbstractMRReply msg) {
222        // our version of loadChars doesn't invoke this, so it shouldn't be called
223        log.error("Not using endOfMessage, should not be called");
224        return false;
225    }
226
227    protected int currentAddr = -1; // at startup, can't match
228    protected int incomingLength = 0;
229
230    @Override
231    protected void loadChars(AbstractMRReply msg, DataInputStream istream) throws java.io.IOException {
232        // get 1st byte, see if ending too soon
233        byte char1 = readByteProtected(istream);
234        msg.setElement(0, char1 & 0xFF);
235        if ((char1 & 0xFF) != currentAddr) {
236            // mismatch, end early
237            return;
238        }
239        if (incomingLength <= 1) {
240            return;
241        }
242        for (int i = 1; i < incomingLength; i++) {  // reading next four bytes
243            char1 = readByteProtected(istream);
244            msg.setElement(i, char1 & 0xFF);
245        }
246    }
247
248    @Override
249    protected void waitForStartOfReply(DataInputStream istream) throws java.io.IOException {
250        // does nothing
251    }
252
253    /**
254     * Add header to the outgoing byte stream.
255     *
256     * @param msg The output byte stream
257     * @return next location in the stream to fill
258     */
259    @Override
260    protected int addHeaderToOutput(byte[] msg, AbstractMRMessage m) {
261        return 0;  // Do nothing
262    }
263
264    /**
265     * Although this protocol doesn't use a trailer, we implement this method to
266     * set the expected reply length and address for this message.
267     *
268     * @param msg    The output byte stream
269     * @param offset the first byte not yet used
270     * @param m      the original message
271     */
272    @Override
273    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
274        incomingLength = ((SerialMessage) m).getResponseLength();
275        currentAddr = ((SerialMessage) m).getAddr();
276        return;
277    }
278
279    /**
280     * Determine how much many bytes the entire message will take, including
281     * space for header and trailer.
282     *
283     * @param m The message to be sent
284     * @return Number of bytes
285     */
286    @Override
287    protected int lengthOfByteStream(AbstractMRMessage m) {
288        return 5; // All are 5 bytes long
289    }
290
291    private final static Logger log = LoggerFactory.getLogger(SerialTrafficController.class);
292
293}