001package jmri.jmrix.nce.consist;
002
003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
004
005import java.io.BufferedReader;
006import java.io.File;
007import java.io.FileReader;
008import java.io.IOException;
009
010import javax.swing.JFileChooser;
011import javax.swing.JPanel;
012
013import jmri.jmrix.nce.NceBinaryCommand;
014import jmri.jmrix.nce.NceMessage;
015import jmri.jmrix.nce.NceReply;
016import jmri.jmrix.nce.NceTrafficController;
017import jmri.util.FileUtil;
018import jmri.util.StringUtil;
019import jmri.util.swing.JmriJOptionPane;
020import jmri.util.swing.TextFilter;
021
022/**
023 * Restores NCE consists from a text file defined by NCE.
024 * <p>
025 * NCE file format:
026 * <p>
027 * :F500 (16 bytes per line, grouped as 8 words with space delimiters) :F510 . .
028 * :FAF0 :0000
029 * <p>
030 * The restore routine checks that each line of the file begins with the
031 * appropriate consist address.
032 *
033 * @author Dan Boudreau Copyright (C) 2007
034 * @author Ken Cameron Copyright (C) 2023
035 */
036public class NceConsistRestore extends Thread implements jmri.jmrix.nce.NceListener {
037
038    private static final int CONSIST_LNTH = 16; // 16 bytes per consist line
039    private static final int REPLY_1 = 1; // reply length of 1 byte expected
040    private int replyLen = 0; // expected byte length
041    private int waiting = 0; // to catch responses not intended for this module
042    private boolean fileValid = false; // used to flag status messages
043
044    javax.swing.JLabel textConsist = new javax.swing.JLabel();
045    javax.swing.JLabel consistNumber = new javax.swing.JLabel();
046
047    private NceTrafficController tc = null;
048
049    public NceConsistRestore(NceTrafficController t) {
050        super();
051        this.tc = t;
052    }
053
054    @Override
055    public void run() {
056
057        // Get file to read from
058        JFileChooser fc = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath());
059        fc.addChoosableFileFilter(new TextFilter());
060        int retVal = fc.showOpenDialog(null);
061        if (retVal != JFileChooser.APPROVE_OPTION) {
062            return; // Canceled
063        }
064        if (fc.getSelectedFile() == null) {
065            return; // Canceled
066        }
067        File f = fc.getSelectedFile();
068        try (BufferedReader in = new BufferedReader(new FileReader(f))) {
069            // create a status frame
070            JPanel ps = new JPanel();
071            jmri.util.JmriJFrame fstatus = new jmri.util.JmriJFrame(Bundle.getMessage("NceConsistRestore"));
072            fstatus.setLocationRelativeTo(null);
073            fstatus.setSize(300, 100);
074            fstatus.getContentPane().add(ps);
075
076            ps.add(textConsist);
077            ps.add(consistNumber);
078
079            textConsist.setText(Bundle.getMessage("ConsistLineNumber"));
080            textConsist.setVisible(true);
081            consistNumber.setVisible(true);
082
083            // Now read the file and check the consist address
084            waiting = 0;
085            fileValid = false; // in case we break out early
086            int consistNum = 0; // for user status messages
087            int curConsist = tc.csm.getConsistHeadAddr(); // load the start address of the NCE consist memory
088            byte[] consistData = new byte[CONSIST_LNTH]; // NCE Consist data
089            String line;
090            int consistMemMim = tc.csm.getConsistHeadAddr();
091            int consistMemMax = consistMemMim + 0x100 + 0x100 + tc.csm.getConsistMidSize();
092            int consistMemAddr;
093
094            while (true) {
095                line = in.readLine();
096
097                consistNumber.setText(Integer.toString(consistNum++));
098
099                if (line == null) { // while loop does not break out quick enough
100                    log.error("NCE consist file terminator :0000 not found"); // NOI18N
101                    break;
102                }
103                
104                log.debug("consist {}", line);
105                
106                // check that each line contains the NCE memory address of the consist
107                String consistAddr = ":" + Integer.toHexString(curConsist);
108                String[] consistLine = line.split(" ");
109
110                // check for end of consist terminator
111                if (consistLine[0].equalsIgnoreCase(":0000")) {
112                    fileValid = true; // success!
113                    break;
114                }
115                
116                // check for consistLine our of range
117                consistMemAddr = Integer.parseUnsignedInt(consistLine[0].replace(":", ""), 16);
118                if (consistMemAddr < consistMemMim) {
119                    log.warn("consist mem file out of range, ending restore, got: {} mimimum: {} ",
120                            Integer.toHexString(consistMemAddr), Integer.toHexString(consistMemMim));
121                    fileValid = true;
122                    break;
123                }
124                if (consistMemAddr >= consistMemMax ) {
125                    log.warn("consist mem file out of range, ending restore, got: {} maximum: {} ",
126                            Integer.toHexString(consistMemAddr), Integer.toHexString(consistMemMax));
127                    fileValid = true;
128                    break;
129                }
130                if (consistMemAddr != curConsist) {
131                    log.warn("consist mem file out of range, ending restore, got: {} expected: {} ",
132                            Integer.toHexString(consistMemAddr), Integer.toHexString(curConsist));
133                    fileValid = true;
134                    break;
135                }
136
137                if (!consistAddr.equalsIgnoreCase(consistLine[0])) {
138                    log.error("Restore file selected is not a valid backup file"); // NOI18N
139                    log.error("Consist memory address in restore file should be {}, read {}",
140                            consistAddr, consistLine[0]); // NOI18N
141                    break;
142                }
143
144                // consist file found, give the user the choice to continue
145                if (curConsist == tc.csm.getConsistHeadAddr()) {
146                    if (JmriJOptionPane.showConfirmDialog(null,
147                            Bundle.getMessage("RestoreTakesAwhile"),
148                            Bundle.getMessage("NceConsistRestore"),
149                            JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) {
150                        break;
151                    }
152                }
153
154                fstatus.setVisible(true);
155
156                // now read the entire line from the file and create NCE message
157                for (int i = 0; i < 8; i++) {
158                    int j = i << 1; // i = word index, j = byte index
159
160                    byte b[] = StringUtil.bytesFromHexString(consistLine[i + 1]);
161
162                    consistData[j] = b[0];
163                    consistData[j + 1] = b[1];
164                }
165
166                NceMessage m = writeNceConsistMemory(curConsist, consistData);
167                tc.sendNceMessage(m, this);
168
169                curConsist += CONSIST_LNTH;
170
171                // wait for write to NCE CS to complete
172                if (waiting > 0) {
173                    synchronized (this) {
174                        try {
175                            wait(20000);
176                        } catch (InterruptedException e) {
177                            Thread.currentThread().interrupt(); // retain if needed later
178                        }
179                    }
180                }
181                // failed
182                if (waiting > 0) {
183                    log.error("timeout waiting for reply"); // NOI18N
184                    break;
185                }
186            }
187            in.close();
188
189            // kill status panel
190            fstatus.dispose();
191
192            if (fileValid) {
193                JmriJOptionPane.showMessageDialog(null,
194                        Bundle.getMessage("SuccessfulRestore"),
195                        Bundle.getMessage("NceConsistRestore"),
196                        JmriJOptionPane.INFORMATION_MESSAGE);
197            } else {
198                JmriJOptionPane.showMessageDialog(null,
199                        Bundle.getMessage("RestoreFailed"),
200                        Bundle.getMessage("NceConsistRestore"),
201                        JmriJOptionPane.ERROR_MESSAGE);
202            }
203        } catch (IOException e) {
204            // this is the end of the try-with-resources that opens in.
205        }
206    }
207//
208//    // USB set cab memory pointer
209//    private void setUsbCabMemoryPointer(int cab, int offset) {
210//        log.debug("Macro base address: {}, offset: {}", Integer.toHexString(cab), offset);
211//        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
212//        waiting++;
213//        byte[] bl = NceBinaryCommand.usbMemoryPointer(cab, offset);
214//        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
215//        tc.sendNceMessage(m, this);
216//    }
217//
218//    // USB Read N bytes of NCE cab memory
219//    private void readUsbMemoryN(int num) {
220//        switch (num) {
221//            case 1:
222//                replyLen = NceMessage.REPLY_1; // Expect 1 byte response
223//                break;
224//            case 2:
225//                replyLen = NceMessage.REPLY_2; // Expect 2 byte response
226//                break;
227//            case 4:
228//                replyLen = NceMessage.REPLY_4; // Expect 4 byte response
229//                break;
230//            default:
231//                log.error("Invalid usb read byte count");
232//                return;
233//        }
234//        waiting++;
235//        byte[] bl = NceBinaryCommand.usbMemoryRead((byte) num);
236//        NceMessage m = NceMessage.createBinaryMessage(tc, bl, replyLen);
237//        tc.sendNceMessage(m, this);
238//    }
239//
240//    // USB Write 1 byte of NCE memory
241//    private void writeUsbMemory1(byte value) {
242//        log.debug("Write byte: {}", String.format("%2X", value));
243//        replyLen = NceMessage.REPLY_1; // Expect 1 byte response
244//        waiting++;
245//        byte[] bl = NceBinaryCommand.usbMemoryWrite1(value);
246//        NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1);
247//        tc.sendNceMessage(m, this);
248//    }
249
250    // writes 16 bytes of NCE consist memory
251    private NceMessage writeNceConsistMemory(int curConsist, byte[] b) {
252
253        replyLen = REPLY_1; // Expect 1 byte response
254        waiting++;
255
256        byte[] bl = NceBinaryCommand.accMemoryWriteN(curConsist, b);
257        NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_1);
258        return m;
259    }
260
261    @Override
262    public void message(NceMessage m) {
263    } // ignore replies
264
265    @SuppressFBWarnings(value = "NN_NAKED_NOTIFY")
266    @Override
267    public void reply(NceReply r) {
268        log.debug("waiting for {} responses", waiting); // NOI18N
269
270        if (waiting <= 0) {
271            log.error("unexpected response"); // NOI18N
272            return;
273        }
274        waiting--;
275        if (r.getNumDataElements() != replyLen) {
276            log.error("reply length incorrect"); // NOI18N
277            return;
278        }
279        if (replyLen == REPLY_1) {
280            // Looking for proper response
281            if (r.getElement(0) != NceMessage.NCE_OKAY) {
282                log.error("reply incorrect"); // NOI18N
283            }
284        }
285
286        // wake up restore thread
287        if (waiting == 0) {
288            synchronized (this) {
289                notify();
290            }
291        }
292    }
293
294    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConsistRestore.class);
295
296}