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        if (r != null) {
052            ((CanListener) client).reply((CanReply) r);
053        } else {
054            // null shouldn't happen, but we've seen it; try to track if down
055            log.error("Found unexpected null message", new Exception("traceback") );
056        }
057    }
058
059    // Current state
060    public static final int NORMAL = 0;
061    public static final int BOOTMODE = 1;
062
063    /**
064     * Get the GridConnect State.
065     * @return NORMAL or BOOTMODE
066     */
067    public int getgcState() {
068        return gcState;
069    }
070
071    /**
072     * Set the GridConnect State.
073     * @param state NORMAL or BOOTMODE
074     */
075    public void setgcState(int state) {
076        gcState = state;
077        log.debug("Setting gcState {}", state);
078    }
079
080    /**
081     * Get if GcTC is in Boot Mode.
082     * @return true if in Boot Mode, else False.
083     */
084    public boolean isBootMode() {
085        return gcState == BOOTMODE;
086    }
087
088    /**
089     * Forward a preformatted message to the actual interface.
090     * {@inheritDoc}
091     */
092    @Override
093    public void sendCanMessage(CanMessage m, CanListener reply) {
094        log.debug("GcTrafficController sendCanMessage() {}", m);
095        sendMessage(m, reply);
096    }
097
098    /**
099     * Forward a preformatted reply to the actual interface.
100     * {@inheritDoc}
101     */
102    @Override
103    public void sendCanReply(CanReply r, CanListener reply) {
104        log.debug("TrafficController sendCanReply() {}", r);
105        notifyReply(r, reply);
106    }
107
108    /**
109     * Does nothing.
110     * {@inheritDoc}
111     */
112    @Override
113    protected void addTrailerToOutput(byte[] msg, int offset, AbstractMRMessage m) {
114    }
115
116    /**
117     * Determine how much many bytes the entire message will take,
118     * including space for header and trailer.
119     *
120     * @param m The message to be sent
121     * @return Number of bytes
122     */
123    @Override
124    protected int lengthOfByteStream(AbstractMRMessage m) {
125        return m.getNumDataElements();
126    }
127
128    /**
129     * Get new message for hardware protocol.
130     * @return New GridConnect Message.
131     */
132    @Override
133    protected AbstractMRMessage newMessage() {
134        log.debug("New GridConnectMessage created");
135        return new GridConnectMessage();
136    }
137
138    /**
139     * Make a CanReply from a GridConnect reply.
140     * {@inheritDoc}
141     */
142    @Override
143    public CanReply decodeFromHardware(AbstractMRReply m) {
144        log.debug("Decoding from hardware");
145        // no throwaway GridConnectReply is allocated for every incoming frame
146        GridConnectReply gc;
147        if (m instanceof GridConnectReply) {
148            gc = (GridConnectReply) m;
149        } else {
150            log.error("{} cannot cast to a GridConnectReply",m);
151            gc = new GridConnectReply();
152        }
153        return gc.createReply();
154    }
155
156    /**
157     * Encode a CanMessage for the hardware.
158     * {@inheritDoc}
159     */
160    @Override
161    public AbstractMRMessage encodeForHardware(CanMessage m) {
162        //log.debug("Encoding for hardware");
163        return new GridConnectMessage(m);
164    }
165
166    /**
167     * New reply from hardware.
168     * {@inheritDoc}
169     */
170    @Override
171    protected AbstractMRReply newReply() {
172        log.debug("New GridConnectReply created");
173        return new GridConnectReply();
174    }
175
176    /*
177     * Normal CAN-RS replies will end with ";"
178     * Bootloader will end with ETX with no preceding DLE
179     */
180    @Override
181    protected boolean endOfMessage(AbstractMRReply r) {
182        if (endNormalReply(r)) {
183            return true;
184        }
185//        if (endBootReply(r)) return true;
186        return false;
187    }
188
189    /**
190     * Detect if the reply buffer ends with ";".
191     * @param r Reply
192     * @return true if contains end, else false.
193     */
194    boolean endNormalReply(AbstractMRReply r) {
195        int num = r.getNumDataElements() - 1;
196        //log.debug("endNormalReply checking "+(num+1)+" of "+(r.getNumDataElements()));
197        if (r.getElement(num) == ';') {
198            log.debug("End of normal message detected");
199            return true;
200        }
201        return false;
202    }
203
204    /**
205     * Get characters from the input source, and file a message.
206     * <p>
207     * Returns only when the message is complete.
208     * <p>
209     * This is over-ridden from AbstractMRTrafficController so we can add
210     * suppression of the characters before ':'. We can't use
211     * waitForStartOfReply() because that strips the 1st character also.
212     * <p>
213     * Handles timeouts on read by ignoring zero-length reads.
214     *
215     * @param msg     message to fill
216     * @param istream character source.
217     * @throws java.io.IOException when presented by the input source.
218     */
219    @Override
220    protected void loadChars(AbstractMRReply msg, DataInputStream istream)
221            throws java.io.IOException {
222        int i;
223        for (i = 0; i < msg.maxSize(); i++) {
224            byte char1 = readByteProtected(istream);
225            if (i == 0) {
226                // skip until you find ':'
227                while (char1 != ':') {
228                    char1 = readByteProtected(istream);
229                }
230            } else if (char1 == ':') {
231                // Start of a new frame before the end of the current one,
232                // the end of the current frame ( ';' ) has been lost, eg. a serial overrun.
233                // Discard the partial frame and restart with this ':' so that the
234                // two frames are not merged into one corrupt frame.
235                log.warn("Incomplete frame discarded: {}", msg);
236                msg.flush();
237                i = 0;
238            }
239            //if (log.isDebugEnabled()) log.debug("char: "+(char1&0xFF)+" i: "+i);
240            // if there was a timeout, flush any char received and start over
241            if (flushReceiveChars) {
242                log.warn("timeout flushes receive buffer: {}", msg.toString());
243                msg.flush();
244                i = 0;  // restart
245                flushReceiveChars = false;
246            }
247            if (canReceive()) {
248                msg.setElement(i, char1);
249                if (endOfMessage(msg)) {
250                    break;
251                }
252            } else {
253                i--; // flush char
254                log.error("unsolicited character received: {}", Integer.toHexString(char1));
255            }
256        }
257    }
258
259    private int gcState;
260
261    private static final Logger log = LoggerFactory.getLogger(GcTrafficController.class);
262}