001package jmri.jmrix.marklin;
002
003import java.util.concurrent.ConcurrentLinkedQueue;
004import jmri.CommandStation;
005import jmri.jmrix.AbstractMRListener;
006import jmri.jmrix.AbstractMRMessage;
007import jmri.jmrix.AbstractMRReply;
008import jmri.jmrix.AbstractMRTrafficController;
009import org.slf4j.Logger;
010import org.slf4j.LoggerFactory;
011
012/**
013 * Converts Stream-based I/O to/from Marklin CS2 messages. The
014 * "MarklinInterface" side sends/receives message objects.
015 * <p>
016 * The connection to a MarklinPortController is via a pair of UDP 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 *
023 * Based on work by Bob Jacobsen
024 *
025 * @author Kevin Dickerson Copyright (C) 2012
026 */
027public class MarklinTrafficController extends AbstractMRTrafficController implements MarklinInterface, CommandStation {
028
029    /**
030     * Create a new MarklinTrafficController instance.
031     */
032    public MarklinTrafficController() {
033        super();
034        log.debug("creating a new MarklinTrafficController object");
035        // set as command station too
036        jmri.InstanceManager.store(this, jmri.CommandStation.class);
037        this.setAllowUnexpectedReply(true);
038    }
039
040    public void setAdapterMemo(MarklinSystemConnectionMemo memo) {
041        adaptermemo = memo;
042    }
043
044    MarklinSystemConnectionMemo adaptermemo;
045
046    // The methods to implement the MarklinInterface
047    @Override
048    public synchronized void addMarklinListener(MarklinListener l) {
049        this.addListener(l);
050    }
051
052    @Override
053    public synchronized void removeMarklinListener(MarklinListener l) {
054        this.removeListener(l);
055    }
056
057    @Override
058    protected int enterProgModeDelayTime() {
059        // we should to wait at least a second after enabling the programming track
060        return 1000;
061    }
062
063    /**
064     * CommandStation implementation, not yet supported.
065     */
066    @Override
067    public boolean sendPacket(byte[] packet, int count) {
068
069        return true;
070    }
071
072    /**
073     * Forward a MarklinMessage to all registered MarklinInterface listeners.
074     */
075    @Override
076    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
077        ((MarklinListener) client).message((MarklinMessage) m);
078    }
079
080    /**
081     * Forward a MarklinReply to all registered MarklinInterface listeners.
082     */
083    @Override
084    protected void forwardReply(AbstractMRListener client, AbstractMRReply r) {
085        ((MarklinListener) client).reply((MarklinReply) r);
086    }
087
088    /**
089     * Forward a preformatted message to the actual interface.
090     */
091    @Override
092    public void sendMarklinMessage(MarklinMessage m, MarklinListener reply) {
093        sendMessage(m, reply);
094    }
095
096    /*@Override
097     protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
098     super.forwardToPort(m, reply);
099     }*/
100    //Marklin doesn't support this function.
101    @Override
102    protected AbstractMRMessage enterProgMode() {
103        return MarklinMessage.getProgMode();
104    }
105
106    //Marklin doesn't support this function!
107    @Override
108    protected AbstractMRMessage enterNormalMode() {
109        return MarklinMessage.getExitProgMode();
110    }
111
112    @Override
113    protected AbstractMRReply newReply() {
114        MarklinReply reply = new MarklinReply();
115        return reply;
116    }
117
118    // for now, receive always OK
119    @Override
120    protected boolean canReceive() {
121        return true;
122    }
123
124    //In theory the replies should only be 13bytes long, so the EOM is completed when the reply can take no more data
125    @Override
126    protected boolean endOfMessage(AbstractMRReply msg) {
127        return false;
128    }
129
130    static class PollMessage {
131
132        MarklinListener ml;
133        MarklinMessage mm;
134
135        PollMessage(MarklinMessage mm, MarklinListener ml) {
136            this.mm = mm;
137            this.ml = ml;
138        }
139
140        MarklinListener getListener() {
141            return ml;
142        }
143
144        MarklinMessage getMessage() {
145            return mm;
146        }
147    }
148
149    ConcurrentLinkedQueue<PollMessage> pollQueue = new ConcurrentLinkedQueue<PollMessage>();
150
151    boolean disablePoll = false;
152
153    public boolean getPollQueueDisabled() {
154        return disablePoll;
155    }
156
157    public void setPollQueueDisabled(boolean poll) {
158        disablePoll = poll;
159    }
160
161    /**
162     * As we have to poll the system to get updates we put request into a
163     * queue and allow the abstrct traffic controller to handle them when it
164     * is free.
165     * @param mm marklin message to add.
166     * @param ml marklin listener.
167     */
168    public void addPollMessage(MarklinMessage mm, MarklinListener ml) {
169        mm.setTimeout(500);
170        for (PollMessage pm : pollQueue) {
171            if (pm.getListener() == ml && pm.getMessage().toString().equals(mm.toString())) {
172                log.debug("Message is already in the poll queue so will not add");
173                return;
174            }
175        }
176        PollMessage pm = new PollMessage(mm, ml);
177        pollQueue.offer(pm);
178    }
179
180    /**
181     * Removes a message that is used for polling from the queue.
182     * @param mm marklin message to remove.
183     * @param ml marklin listener.
184     */
185    public void removePollMessage(MarklinMessage mm, MarklinListener ml) {
186        for (PollMessage pm : pollQueue) {
187            if (pm.getListener() == ml && pm.getMessage().toString().equals(mm.toString())) {
188                pollQueue.remove(pm);
189            }
190        }
191    }
192
193    /**
194     * Check Tams MC for updates.
195     */
196    @Override
197    protected AbstractMRMessage pollMessage() {
198        if (disablePoll) {
199            return null;
200        }
201        if (!pollQueue.isEmpty()) {
202            PollMessage pm = pollQueue.peek();
203            if (pm != null) {
204                return pm.getMessage();
205            }
206        }
207        return null;
208    }
209
210    @Override
211    protected AbstractMRListener pollReplyHandler() {
212        if (disablePoll) {
213            return null;
214        }
215        if (!pollQueue.isEmpty()) {
216            PollMessage pm = pollQueue.poll();
217            if (pm != null) {
218                pollQueue.offer(pm);
219                return pm.getListener();
220            }
221        }
222        return null;
223    }
224
225    @Override
226    public String getUserName() {
227        if (adaptermemo == null) {
228            return "Marklin-CS2";
229        }
230        return adaptermemo.getUserName();
231    }
232
233    @Override
234    public String getSystemPrefix() {
235        if (adaptermemo == null) {
236            return "M";
237        }
238        return adaptermemo.getSystemPrefix();
239    }
240
241    private final static Logger log = LoggerFactory.getLogger(MarklinTrafficController.class);
242
243}