001package jmri.jmrix.can.adapters.gridconnect;
002
003import java.io.DataInputStream;
004import jmri.jmrix.AbstractMRListener;
005import jmri.jmrix.AbstractMRMessage;
006import jmri.jmrix.AbstractMRReply;
007import jmri.jmrix.can.CanListener;
008import jmri.jmrix.can.CanMessage;
009import jmri.jmrix.can.CanReply;
010import jmri.jmrix.can.TrafficController;
011import org.slf4j.Logger;
012import org.slf4j.LoggerFactory;
013
014/**
015 * Traffic controller for the GridConnect protocol.
016 * <p>
017 * GridConnect uses messages transmitted as an ASCII string of up to 24
018 * characters of the form: :ShhhhNd0d1d2d3d4d5d6d7; 
019 * <p>
020 * The S indicates a standard
021 * CAN frame hhhh is the two byte header (11 useful bits) N or R indicates a
022 * normal or remote frame d0 - d7 are the (up to) 8 data bytes
023 *
024 * @author Andrew Crosland Copyright (C) 2008
025 */
026public class GcTrafficController extends TrafficController {
027
028    /**
029     * Create new GridConnect Traffic Controller.
030     */
031    public GcTrafficController() {
032        super();
033        this.setSynchronizeRx(false);
034    }
035
036    /**
037     * Forward a CanMessage to all registered CanInterface listeners.
038     * {@inheritDoc}
039     */
040    @Override
041    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
042        ((CanListener) client).message((CanMessage) m);
043    }
044
045    /**
046     * Forward a CanReply to all registered CanInterface listeners.
047     * {@inheritDoc}
048     */
049    @Override
050    protected void forwardReply(AbstractMRListener client, AbstractMRReply r) {
051        ((CanListener) client).reply((CanReply) r);
052    }
053
054    // Current state
055    public static final int NORMAL = 0;
056    public static final int BOOTMODE = 1;
057
058    /**
059     * Get the GridConnect State.
060     * @return NORMAL or BOOTMODE
061     */
062    public int getgcState() {
063        return gcState;
064    }
065
066    /**
067     * Set the GridConnect State.
068     * @param state NORMAL or BOOTMODE
069     */
070    public void setgcState(int state) {
071        gcState = state;
072        log.debug("Setting gcState {}", state);
073    }
074
075    /**
076     * Get if GcTC is in Boot Mode.
077     * @return true if in Boot Mode, else False.
078     */
079    public boolean isBootMode() {
080        return gcState == BOOTMODE;
081    }
082
083    /**
084     * Forward a preformatted message to the actual interface.
085     * {@inheritDoc}
086     */
087    @Override
088    public void sendCanMessage(CanMessage m, CanListener reply) {
089        log.debug("GcTrafficController sendCanMessage() {}", m.toString());
090        sendMessage(m, reply);
091    }
092
093    /**
094     * Forward a preformatted reply to the actual interface.
095     * {@inheritDoc}
096     */
097    @Override
098    public void sendCanReply(CanReply r, CanListener reply) {
099        log.debug("TrafficController sendCanReply() {}", r.toString());
100        notifyReply(r, reply);
101    }
102
103    /**
104     * Does nothing.
105     * {@inheritDoc}
106     */
107    @Override
108    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
109    }
110
111    /**
112     * Determine how much many bytes the entire message will take, 
113     * including space for header and trailer.
114     *
115     * @param m The message to be sent
116     * @return Number of bytes
117     */
118    @Override
119    protected int lengthOfByteStream(AbstractMRMessage m) {
120        return m.getNumDataElements();
121    }
122
123    /**
124     * Get new message for hardware protocol.
125     * @return New GridConnect Message.
126     */
127    @Override
128    protected AbstractMRMessage newMessage() {
129        log.debug("New GridConnectMessage created");
130        GridConnectMessage msg = new GridConnectMessage();
131        return msg;
132    }
133
134    /**
135     * Make a CanReply from a GridConnect reply.
136     * {@inheritDoc}
137     */
138    @Override
139    public CanReply decodeFromHardware(AbstractMRReply m) {
140        GridConnectReply gc = new GridConnectReply();
141        log.debug("Decoding from hardware");
142        try {
143            gc = (GridConnectReply) m;
144        } catch(java.lang.ClassCastException cce){
145            log.error("{} cannot cast to a GridConnectReply",m);
146        }
147        CanReply ret = gc.createReply();
148        return ret;
149    }
150
151    /**
152     * Encode a CanMessage for the hardware.
153     * {@inheritDoc}
154     */
155    @Override
156    public AbstractMRMessage encodeForHardware(CanMessage m) {
157        //log.debug("Encoding for hardware");
158        GridConnectMessage ret = new GridConnectMessage(m);
159
160        return ret;
161    }
162
163    /**
164     * New reply from hardware.
165     * {@inheritDoc}
166     */
167    @Override
168    protected AbstractMRReply newReply() {
169        log.debug("New GridConnectReply created");
170        GridConnectReply reply = new GridConnectReply();
171        return reply;
172    }
173
174    /*
175     * Normal CAN-RS replies will end with ";"
176     * Bootloader will end with ETX with no preceding DLE
177     */
178    @Override
179    protected boolean endOfMessage(AbstractMRReply r) {
180        if (endNormalReply(r)) {
181            return true;
182        }
183//        if (endBootReply(r)) return true;
184        return false;
185    }
186
187    /**
188     * Detect if the reply buffer ends with ";".
189     * @param r Reply
190     * @return true if contais end, else false.
191     */
192    boolean endNormalReply(AbstractMRReply r) {
193        int num = r.getNumDataElements() - 1;
194        //log.debug("endNormalReply checking "+(num+1)+" of "+(r.getNumDataElements()));
195        if (r.getElement(num) == ';') {
196            log.debug("End of normal message detected");
197            return true;
198        }
199        return false;
200    }
201
202    /**
203     * Get characters from the input source, and file a message.
204     * <p>
205     * Returns only when the message is complete.
206     * <p>
207     * This is over-ridden from AbstractMRTrafficController so we can add
208     * suppression of the characters before ':'. We can't use
209     * waitForStartOfReply() because that strips the 1st character also.
210     * <p>
211     * Handles timeouts on read by ignoring zero-length reads.
212     *
213     * @param msg     message to fill
214     * @param istream character source.
215     * @throws java.io.IOException when presented by the input source.
216     */
217    @Override
218    protected void loadChars(AbstractMRReply msg, DataInputStream istream)
219            throws java.io.IOException {
220        int i;
221        for (i = 0; i < msg.maxSize(); i++) {
222            byte char1 = readByteProtected(istream);
223            if (i == 0) {
224                // skip until you find ':'
225                while (char1 != ':') {
226                    char1 = readByteProtected(istream);
227                }
228            }
229            //if (log.isDebugEnabled()) log.debug("char: "+(char1&0xFF)+" i: "+i);
230            // if there was a timeout, flush any char received and start over
231            if (flushReceiveChars) {
232                log.warn("timeout flushes receive buffer: {}", msg.toString());
233                msg.flush();
234                i = 0;  // restart
235                flushReceiveChars = false;
236            }
237            if (canReceive()) {
238                msg.setElement(i, char1);
239                if (endOfMessage(msg)) {
240                    break;
241                }
242            } else {
243                i--; // flush char
244                log.error("unsolicited character received: {}", Integer.toHexString(char1));
245            }
246        }
247    }
248
249    private int gcState;
250
251    private final static Logger log = LoggerFactory.getLogger(GcTrafficController.class);
252}