001package jmri.jmrix.ecos;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004import java.util.List;
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 ECOS messages. The "EcosInterface" side
012 * sends/receives message objects.
013 * <p>
014 * The connection to a EcosPortController is via a pair of *Streams, which then
015 * carry sequences of characters for transmission. Note that this processing is
016 * handled in an independent thread.
017 * <p>
018 * This handles the state transitions, based on the necessary state in each
019 * message.
020 *
021 * @author Bob Jacobsen Copyright (C) 2001
022 */
023public class EcosTrafficController extends AbstractMRTrafficController implements EcosInterface {
024
025    /**
026     * Create a new EcosTrafficController instance.
027     */
028    public EcosTrafficController() {
029        super();
030        log.debug("creating a new EcosTrafficController object");
031        // set as command station too
032        this.setAllowUnexpectedReply(true);
033        this.setSynchronizeRx(false);
034    }
035
036    public void setAdapterMemo(EcosSystemConnectionMemo memo) {
037        adaptermemo = memo;
038    }
039
040    EcosSystemConnectionMemo adaptermemo;
041
042    /** {@inheritDoc} */
043    @Override
044    public synchronized void addEcosListener(EcosListener l) {
045        this.addListener(l);
046    }
047
048    /** {@inheritDoc} */
049    @Override
050    public synchronized void removeEcosListener(EcosListener l) {
051        this.removeListener(l);
052    }
053
054    /** {@inheritDoc} */
055    @Override
056    protected int enterProgModeDelayTime() {
057        // we should to wait at least a second after enabling the programming track
058        return 1000;
059    }
060
061    /**
062     * {@inheritDoc}
063     * Forward an EcosMessage to all registered EcosInterface listeners.
064     */
065    @Override
066    protected void forwardMessage(AbstractMRListener client, AbstractMRMessage m) {
067        ((EcosListener) client).message((EcosMessage) m);
068    }
069
070    /**
071     * {@inheritDoc}
072     * Forward a EcosReply to all registered EcosInterface listeners.
073     */
074    @Override
075    protected void forwardReply(AbstractMRListener client, AbstractMRReply r) {
076        ((EcosListener) client).reply((EcosReply) r);
077    }
078
079    /** {@inheritDoc} */
080    @Override
081    protected AbstractMRMessage pollMessage() {
082        return null;
083    }
084
085    /** {@inheritDoc} */
086    @Override
087    protected AbstractMRListener pollReplyHandler() {
088        return null;
089    }
090
091    /** {@inheritDoc} */
092    @Override
093    public void sendEcosMessage(EcosMessage m, EcosListener reply) {
094        sendMessage(m, reply);
095    }
096
097    /** {@inheritDoc} */
098    @Override
099    protected void forwardToPort(AbstractMRMessage m, AbstractMRListener reply) {
100        super.forwardToPort(m, reply);
101    }
102
103    protected boolean unsolicitedSensorMessageSeen = false;
104
105    /**
106     *  ECoS doesn't support this function.
107     */
108    @Override
109    protected AbstractMRMessage enterProgMode() {
110        return EcosMessage.getProgMode();
111    }
112
113    /**
114     *  ECoS doesn't support this function.
115     */
116    @Override
117    protected AbstractMRMessage enterNormalMode() {
118        return EcosMessage.getExitProgMode();
119    }
120
121    @SuppressFBWarnings(value = "MS_PKGPROTECT")
122    // SpotBugs wants this package protected, but we're removing it when multi-connection
123    // migration is complete
124    final static protected EcosTrafficController self = null;
125
126    /** {@inheritDoc} */
127    @Override
128    protected AbstractMRReply newReply() {
129        EcosReply reply = new EcosReply();
130        return reply;
131    }
132
133    /**
134     * {@inheritDoc}
135     * @return for now, receive always OK
136     */
137    @Override
138    protected boolean canReceive() {
139        return true;
140    }
141
142    /** {@inheritDoc} */
143    @Override
144    protected boolean endOfMessage(AbstractMRReply msg) {
145        // detect that the reply buffer ends with "COMMAND: " (note ending
146        // space)
147        int num = msg.getNumDataElements();
148        // ptr is offset of last element in EcosReply
149        int ptr = num - 1;
150
151        if ((num >= 2)
152                && // check NL at end of buffer
153                (msg.getElement(ptr) == 0x0A)
154                && (msg.getElement(ptr - 1) == 0x0D)
155                && (msg.getElement(ptr - 2) == '>')) {
156
157            // this might be end of element, check for "<END "
158            return ((EcosReply) msg).containsEnd();
159        }
160
161        // otherwise, it's not the end
162        return false;
163    }
164
165    public boolean sendWaitMessage(EcosMessage m, AbstractMRListener reply) {
166        if (log.isDebugEnabled()) {
167            log.debug("Send a message and wait for the response");
168        }
169        if (ostream == null) {
170            return false;
171        }
172        m.setTimeout(500);
173        m.setRetries(10);
174        synchronized (this) {
175            forwardToPort(m, reply);
176            // wait for reply
177            try {
178                if (xmtRunnable != null) {
179                    synchronized (xmtRunnable) {
180                        xmtRunnable.wait(m.getTimeout());
181                    }
182                }
183            } catch (InterruptedException e) {
184                Thread.currentThread().interrupt(); // retain if needed later
185                log.error("transmit interrupted");
186                return false;
187            }
188        }
189        return true;
190    }
191
192    /** {@inheritDoc} */
193    @Override
194    protected void terminate() {
195        if (log.isDebugEnabled()) {
196            log.debug("Cleanup Starts");
197        }
198        if (ostream == null) {
199            return;    // no connection established
200        }
201        EcosPreferences p = adaptermemo.getPreferenceManager();
202        if (p.getAdhocLocoFromEcos() == 0x01) {
203            return; //Just a double check that we can delete locos
204        }        //AbstractMRMessage modeMsg=enterNormalMode();
205        AbstractMRMessage modeMsg;
206        List<String> en;
207        String ecosObject;
208
209        modeMsg = new EcosMessage("release(10, view)");
210        modeMsg.setTimeout(50);
211        modeMsg.setRetries(10);
212        synchronized (this) {
213            forwardToPort(modeMsg, null);
214            // wait for reply
215            try {
216                if (xmtRunnable != null) {
217                    synchronized (xmtRunnable) {
218                        xmtRunnable.wait(modeMsg.getTimeout());
219                    }
220                }
221            } catch (InterruptedException e) {
222                Thread.currentThread().interrupt(); // retain if needed later
223                log.error("transmit interrupted");
224            }
225        }
226
227        EcosTurnoutManager objEcosTurnManager = adaptermemo.getTurnoutManager();
228        en = objEcosTurnManager.getEcosObjectList();
229        for (int i = 0; i < en.size(); i++) {
230            ecosObject = en.get(i);
231            modeMsg = new EcosMessage("release(" + ecosObject + ", view)");
232            modeMsg.setTimeout(50);
233            modeMsg.setRetries(10);
234            synchronized (this) {
235                forwardToPort(modeMsg, null);
236                // wait for reply
237                try {
238                    if (xmtRunnable != null) {
239                        synchronized (xmtRunnable) {
240                            xmtRunnable.wait(modeMsg.getTimeout());
241                        }
242                    }
243                } catch (InterruptedException e) {
244                    Thread.currentThread().interrupt(); // retain if needed later
245                    log.error("transmit interrupted");
246                }
247            }
248        }
249
250        EcosLocoAddressManager objEcosLocoManager = adaptermemo.getLocoAddressManager();
251        en = objEcosLocoManager.getEcosObjectList();
252        for (int i = 0; i < en.size(); i++) {
253            ecosObject = en.get(i);
254            //we only delete locos if they were a temp entry.
255            if (objEcosLocoManager.getByEcosObject(ecosObject).getEcosTempEntry()) {
256                /*The ecos can be funny in not providing control on the first request, plus we have no way to determine if we have
257                 therefore we send the request twice and hope we have control, failure not to have control isn't a problem as the loco
258                 will simply be left on the ecos.*/
259                for (int x = 0; x < 4; x++) {
260                    switch (x) {
261                        case 3:
262                            modeMsg = new EcosMessage("delete(" + ecosObject + ")");
263                            break;
264                        case 2:
265                            modeMsg = new EcosMessage("set(" + ecosObject + ", stop)");
266                            break;
267                        default:
268                            modeMsg = new EcosMessage("request(" + ecosObject + ", control)");
269                            break;
270                    }
271                    modeMsg.setTimeout(50);
272                    modeMsg.setRetries(10);
273                    synchronized (this) {
274                        forwardToPort(modeMsg, null);
275                        // wait for reply
276                        try {
277                            if (xmtRunnable != null) {
278                                synchronized (xmtRunnable) {
279                                    xmtRunnable.wait(modeMsg.getTimeout());
280                                }
281                            }
282                        } catch (InterruptedException e) {
283                            Thread.currentThread().interrupt(); // retain if needed later
284                            log.error("transmit interrupted");
285                        }
286                    }
287                }
288            }
289
290        }
291    }
292
293    private final static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(EcosTrafficController.class);
294
295}