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.toString());
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.toString());
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        GridConnectReply gc = new GridConnectReply();
145        log.debug("Decoding from hardware");
146        try {
147            gc = (GridConnectReply) m;
148        } catch(java.lang.ClassCastException cce){
149            log.error("{} cannot cast to a GridConnectReply",m);
150        }
151        CanReply ret = gc.createReply();
152        return ret;
153    }
154
155    /**
156     * Encode a CanMessage for the hardware.
157     * {@inheritDoc}
158     */
159    @Override
160    public AbstractMRMessage encodeForHardware(CanMessage m) {
161        //log.debug("Encoding for hardware");
162        return new GridConnectMessage(m);
163    }
164
165    /**
166     * New reply from hardware.
167     * {@inheritDoc}
168     */
169    @Override
170    protected AbstractMRReply newReply() {
171        log.debug("New GridConnectReply created");
172        return new GridConnectReply();
173    }
174
175    /*
176     * Normal CAN-RS replies will end with ";"
177     * Bootloader will end with ETX with no preceding DLE
178     */
179    @Override
180    protected boolean endOfMessage(AbstractMRReply r) {
181        if (endNormalReply(r)) {
182            return true;
183        }
184//        if (endBootReply(r)) return true;
185        return false;
186    }
187
188    /**
189     * Detect if the reply buffer ends with ";".
190     * @param r Reply
191     * @return true if contains end, else false.
192     */
193    boolean endNormalReply(AbstractMRReply r) {
194        int num = r.getNumDataElements() - 1;
195        //log.debug("endNormalReply checking "+(num+1)+" of "+(r.getNumDataElements()));
196        if (r.getElement(num) == ';') {
197            log.debug("End of normal message detected");
198            return true;
199        }
200        return false;
201    }
202
203    /**
204     * Get characters from the input source, and file a message.
205     * <p>
206     * Returns only when the message is complete.
207     * <p>
208     * This is over-ridden from AbstractMRTrafficController so we can add
209     * suppression of the characters before ':'. We can't use
210     * waitForStartOfReply() because that strips the 1st character also.
211     * <p>
212     * Handles timeouts on read by ignoring zero-length reads.
213     *
214     * @param msg     message to fill
215     * @param istream character source.
216     * @throws java.io.IOException when presented by the input source.
217     */
218    @Override
219    protected void loadChars(AbstractMRReply msg, DataInputStream istream)
220            throws java.io.IOException {
221        int i;
222        for (i = 0; i < msg.maxSize(); i++) {
223            byte char1 = readByteProtected(istream);
224            if (i == 0) {
225                // skip until you find ':'
226                while (char1 != ':') {
227                    char1 = readByteProtected(istream);
228                }
229            }
230            //if (log.isDebugEnabled()) log.debug("char: "+(char1&0xFF)+" i: "+i);
231            // if there was a timeout, flush any char received and start over
232            if (flushReceiveChars) {
233                log.warn("timeout flushes receive buffer: {}", msg.toString());
234                msg.flush();
235                i = 0;  // restart
236                flushReceiveChars = false;
237            }
238            if (canReceive()) {
239                msg.setElement(i, char1);
240                if (endOfMessage(msg)) {
241                    break;
242                }
243            } else {
244                i--; // flush char
245                log.error("unsolicited character received: {}", Integer.toHexString(char1));
246            }
247        }
248    }
249
250    private int gcState;
251
252    private final static Logger log = LoggerFactory.getLogger(GcTrafficController.class);
253}