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