001package jmri.jmrit.operations.rollingstock.engines.tools;
002
003import java.util.ArrayList;
004import java.util.List;
005
006import javax.swing.JPanel;
007
008import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
009import jmri.InstanceManager;
010import jmri.jmrit.operations.rollingstock.engines.*;
011import jmri.jmrix.nce.*;
012import jmri.util.swing.JmriJOptionPane;
013
014/**
015 * Routine to synchronize operation's engines with NCE consist memory.
016 *
017 * NCE Consists are stored in Command Station (CS) memory starting at address
018 * xF500 and ending xFAFF. NCE supports up to 127 consists, numbered 1 to 127.
019 * They track the lead loco, rear loco, and four mid locos in the consist file.
020 * Consist lead locos are stored in memory locations xF500 through xF5FF.
021 * Consist rear locos are stored in memory locations xF600 through xF6FF. Mid
022 * consist locos (four max) are stored in memory locations xF700 through xFAFF.
023 * If a long address is in use, bits 6 and 7 of the high byte are set. Example:
024 * Long address 3 = 0xc0 0x03 Short address 3 = 0x00 0x03
025 * <p>
026 * NCE file format:
027 * <p>
028 * :F500 (con 0 lead loco) (con 1 lead loco) ....... (con 7 lead loco) :F510
029 * (con 8 lead loco) ........ (con 15 lead loco) . . :F5F0 (con 120 lead loco)
030 * ..... (con 127 lead loco)
031 * <p>
032 * :F600 (con 0 rear loco) (con 1 rear loco) ....... (con 7 rear loco) . . :F6F0
033 * (con 120 rear loco) ..... (con 127 rear loco)
034 * <p>
035 * :F700 (con 0 mid loco1) (con 0 mid loco2) (con 0 mid loco3) (con 0 mid loco4)
036 * . . :FAF0 (con 126 mid loco1) .. (con 126 mid loco4)(con 127 mid loco1) ..
037 * (con 127 mid loco4) :0000
038 *
039 * @author Dan Boudreau Copyright (C) 2008, 2015
040 */
041public class NceConsistEngines extends Thread implements jmri.jmrix.nce.NceListener {
042
043    private boolean syncOK = true; // used to flag status messages
044    EngineManager engineManager = InstanceManager.getDefault(EngineManager.class);
045    List<Engine> engineList;
046    List<String> consists;
047
048    javax.swing.JLabel textConsist = new javax.swing.JLabel();
049    javax.swing.JLabel indexNumber = new javax.swing.JLabel();
050
051    private static final int CS_CONSIST_MEM = 0xF500; // start of NCE CS Consist memory
052    private static final int CS_CON_MEM_REAR = 0x100; // array offset rear consist locos
053    private static final int CS_CON_MEM_MID = 0x200; // array offset mid consist locos
054
055    private static final int REPLY_16 = 16; // reply length of 16 byte expected
056    private int replyLen = 0; // expected byte length
057    private int waiting = 0; // to catch responses not intended for this module
058    private int index = 0; // byte index when reading NCE consist memory
059    private static final int CONSIST_LNTH = 128 * 6 * 2; // 128 consists x 6 engines per consists x 2 bytes
060    private static final int NUM_CONSIST_READS = CONSIST_LNTH / REPLY_16; // read 16 bytes each time from NCE memory
061
062    private static final String NCE = "nce_"; // NOI18N
063//    private static final int LEAD_BLOCK_NUMBER = 0; // mid locos blocking 2 through 5
064//    private static final int REAR_BLOCK_NUMBER = 8; // rear blocking needs to be greater than 5
065
066    private static byte[] nceConsistData = new byte[CONSIST_LNTH];
067
068    NceTrafficController tc;
069
070    public NceConsistEngines(NceTrafficController tc) {
071        super();
072        this.tc = tc;
073    }
074
075    @Override
076    // we use a thread so the status frame will work!
077    public void run() {
078        if (tc == null) {
079            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("NceSynchronizationFailed"), Bundle
080                    .getMessage("NceConsist"), JmriJOptionPane.ERROR_MESSAGE);
081            return;
082        }
083        if (JmriJOptionPane.showConfirmDialog(null, Bundle.getMessage("SynchronizeWithNce"), Bundle
084                .getMessage("NceConsist"), JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
085            return;
086        }
087        // reset
088        index = 0;
089        waiting = 0;
090        syncOK = true;
091
092        // create a status frame
093        JPanel ps = new JPanel();
094        jmri.util.JmriJFrame fstatus = new jmri.util.JmriJFrame(Bundle.getMessage("ReadingNceConsistMemory"));
095        fstatus.setLocationRelativeTo(null);
096        fstatus.setSize(300, 100);
097
098        ps.add(textConsist);
099        ps.add(indexNumber);
100        fstatus.getContentPane().add(ps);
101        textConsist.setText(Bundle.getMessage("ReadNumber"));
102        textConsist.setVisible(true);
103        indexNumber.setVisible(true);
104        fstatus.setVisible(true);
105
106        // now copy NCE memory into array
107        for (int readIndex = 0; readIndex < NUM_CONSIST_READS; readIndex++) {
108
109            indexNumber.setText(Integer.toString(readIndex));
110            fstatus.setVisible(true);
111
112            getNceConsist(readIndex);
113
114            if (!syncOK) {
115                break;
116            }
117        }
118        // kill status panel
119        fstatus.dispose();
120
121        if (syncOK) {
122            // now check each engine in the operations to see if there are any matches
123            engineList = engineManager.getByNumberList();
124            consists = new ArrayList<>();
125
126            // look for lead engines
127            for (int consistNum = 1; consistNum < 128; consistNum++) {
128                InstanceManager.getDefault(ConsistManager.class).deleteConsist(NCE + consistNum);
129                int engNum = getEngineNumberFromArray(consistNum, 0, 2);
130                if (engNum != 0) {
131                    log.debug("NCE consist {} has lead engine {}", consistNum, engNum);
132                    boolean engMatch = false;
133                    for (Engine engine : engineList) {
134                        if (engine.getNumber().equals(Integer.toString(engNum))) {
135                            log.debug("found lead engine match {}", engine.getNumber());
136                            Consist engConsist = InstanceManager.getDefault(ConsistManager.class).newConsist(NCE + consistNum);
137                            engConsist.setConsistNumber(consistNum); // load the consist number
138                            engine.setConsist(engConsist);
139                            engine.setBlocking(Engine.DEFAULT_BLOCKING_ORDER);
140                            engMatch = true;
141                            consists.add(Integer.toString(consistNum));
142                            break;
143                        }
144                    }
145                    if (!engMatch) {
146                        log.info("Lead engine {} not found in operations for NCE consist {}", engNum, consistNum); // NOI18N
147                    }
148                }
149            }
150            // look for rear engines
151            syncEngines(CS_CON_MEM_REAR, 2);
152            // look for mid engines
153            syncEngines(CS_CON_MEM_MID, 8);
154            syncEngines(CS_CON_MEM_MID + 2, 8);
155            syncEngines(CS_CON_MEM_MID + 4, 8);
156            syncEngines(CS_CON_MEM_MID + 6, 8);
157        }
158
159        if (syncOK) {
160            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("SuccessfulSynchronization"), Bundle
161                    .getMessage("NceConsist"), JmriJOptionPane.INFORMATION_MESSAGE);
162        } else {
163            JmriJOptionPane.showMessageDialog(null, Bundle.getMessage("SynchronizationFailed"), Bundle
164                    .getMessage("NceConsist"), JmriJOptionPane.ERROR_MESSAGE);
165        }
166    }
167
168    private void syncEngines(int offset, int step) {
169        for (int consistNum = 1; consistNum < 128; consistNum++) {
170            int engNum = getEngineNumberFromArray(consistNum, offset, step);
171            if (engNum != 0) {
172                log.debug("NCE consist {} has engine {}", consistNum, engNum);
173                boolean engMatch = false;
174                for (Engine engine : engineList) {
175                    if (engine.getNumber().equals(Integer.toString(engNum))) {
176                        log.debug("found engine match {}", engine.getNumber());
177                        engMatch = true;
178                        Consist engConsist = InstanceManager.getDefault(ConsistManager.class).getConsistByName(NCE + consistNum);
179                        if (engConsist != null) {
180                            engine.setConsist(engConsist);
181                            if (offset == CS_CON_MEM_REAR) {
182                                engine.setBlocking(Engine.NCE_REAR_BLOCK_NUMBER); // place rear loco at end of consist
183                            } else {
184                                engine.setBlocking(engConsist.getSize()); // mid block numbers 2 through 5
185                            }
186                            break;
187                        }
188                        log.warn("Engine ({}) needs lead engine {} for consist {}", engNum, getEngineNumberFromArray(
189                                consistNum, 0, 2), consistNum);
190                        JmriJOptionPane.showMessageDialog(null, Bundle
191                                .getMessage("NceConsistNeedsLeadEngine", engNum,
192                                    getEngineNumberFromArray(consistNum, 0, 2), consistNum), Bundle
193                                .getMessage("NceConsist"), JmriJOptionPane.ERROR_MESSAGE);
194                        syncOK = false;
195                    }
196                }
197                if (!engMatch) {
198                    log.warn("Engine {} not found in operations for NCE consist {}", engNum, consistNum);
199                    if (consists.contains(Integer.toString(consistNum))) {
200                        JmriJOptionPane.showMessageDialog(null, Bundle
201                                .getMessage("NceConsistMissingEngineNumber", engNum, consistNum),
202                                Bundle.getMessage("NceConsist"), JmriJOptionPane.ERROR_MESSAGE);
203                        syncOK = false;
204                    }
205                }
206            }
207        }
208    }
209
210    private int getEngineNumberFromArray(int consistNumber, int offset, int step) {
211        int engH = ((nceConsistData[consistNumber * step + offset] << 8) & 0x3FFF);
212        int engL = nceConsistData[(consistNumber * step) + offset + 1] & 0xFF;
213        return engH + engL;
214    }
215
216    // Read 16 bytes of NCE CS memory
217    private void getNceConsist(int cR) {
218
219        NceMessage m = readConsistMemory(cR);
220        tc.sendNceMessage(m, this);
221        // wait for read to complete
222        readWait();
223    }
224
225    // wait up to 10 seconds per read
226    private synchronized boolean readWait() {
227        int waitcount = 10;
228        while (waiting > 0) {
229            try {
230                wait(1000); // 10 x 1000mSec = 10 seconds.
231            } catch (InterruptedException e) {
232                Thread.currentThread().interrupt(); // retain if needed later
233            }
234            if (waitcount-- < 0) {
235                log.error("read timeout");
236                syncOK = false; // need to quit
237                return false;
238            }
239        }
240        return true;
241    }
242
243    // Reads 16 bytes of NCE consist memory
244    private NceMessage readConsistMemory(int num) {
245
246        int nceConsistAddr = (num * REPLY_16) + CS_CONSIST_MEM;
247        replyLen = REPLY_16; // Expect 16 byte response
248        waiting++;
249
250        byte[] bl = NceBinaryCommand.accMemoryRead(nceConsistAddr);
251        NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_16);
252        return m;
253    }
254
255    @Override
256    public void message(NceMessage m) {
257    } // ignore replies
258
259    @Override
260    @SuppressFBWarnings(value = {"NN_NAKED_NOTIFY", "NO_NOTIFY_NOT_NOTIFYALL"}, justification = "Only want to notify this thread" )
261    public void reply(NceReply r) {
262
263        if (waiting <= 0) {
264            log.error("unexpected response");
265            return;
266        }
267        if (r.getNumDataElements() != replyLen) {
268            log.error("reply length incorrect");
269            return;
270        }
271
272        // load data buffer
273        for (int i = 0; i < REPLY_16; i++) {
274            nceConsistData[index++] = (byte) r.getElement(i);
275        }
276        waiting--;
277
278        // wake up thread
279        synchronized (this) {
280            notify();
281        }
282    }
283
284    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConsistEngines.class);
285}