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 091 while (true) { 092 line = in.readLine(); 093 094 consistNumber.setText(Integer.toString(consistNum++)); 095 096 if (line == null) { // while loop does not break out quick enough 097 log.error("NCE consist file terminator :0000 not found"); // NOI18N 098 break; 099 } 100 101 log.debug("consist {}", line); 102 103 // check that each line contains the NCE memory address of the consist 104 String consistAddr = ":" + Integer.toHexString(curConsist); 105 String[] consistLine = line.split(" "); 106 107 // check for end of consist terminator 108 if (consistLine[0].equalsIgnoreCase(":0000")) { 109 fileValid = true; // success! 110 break; 111 } 112 113 if (!consistAddr.equalsIgnoreCase(consistLine[0])) { 114 log.error("Restore file selected is not a valid backup file"); // NOI18N 115 log.error("Consist memory address in restore file should be {}, read {}", 116 consistAddr, consistLine[0]); // NOI18N 117 break; 118 } 119 120 // consist file found, give the user the choice to continue 121 if (curConsist == tc.csm.getConsistHeadAddr()) { 122 if (JmriJOptionPane.showConfirmDialog(null, 123 Bundle.getMessage("RestoreTakesAwhile"), 124 Bundle.getMessage("NceConsistRestore"), 125 JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) { 126 break; 127 } 128 } 129 130 fstatus.setVisible(true); 131 132 // now read the entire line from the file and create NCE message 133 for (int i = 0; i < 8; i++) { 134 int j = i << 1; // i = word index, j = byte index 135 136 byte b[] = StringUtil.bytesFromHexString(consistLine[i + 1]); 137 138 consistData[j] = b[0]; 139 consistData[j + 1] = b[1]; 140 } 141 142 NceMessage m = writeNceConsistMemory(curConsist, consistData); 143 tc.sendNceMessage(m, this); 144 145 curConsist += CONSIST_LNTH; 146 147 // wait for write to NCE CS to complete 148 if (waiting > 0) { 149 synchronized (this) { 150 try { 151 wait(20000); 152 } catch (InterruptedException e) { 153 Thread.currentThread().interrupt(); // retain if needed later 154 } 155 } 156 } 157 // failed 158 if (waiting > 0) { 159 log.error("timeout waiting for reply"); // NOI18N 160 break; 161 } 162 } 163 in.close(); 164 165 // kill status panel 166 fstatus.dispose(); 167 168 if (fileValid) { 169 JmriJOptionPane.showMessageDialog(null, 170 Bundle.getMessage("SuccessfulRestore"), 171 Bundle.getMessage("NceConsistRestore"), 172 JmriJOptionPane.INFORMATION_MESSAGE); 173 } else { 174 JmriJOptionPane.showMessageDialog(null, 175 Bundle.getMessage("RestoreFailed"), 176 Bundle.getMessage("NceConsistRestore"), 177 JmriJOptionPane.ERROR_MESSAGE); 178 } 179 } catch (IOException e) { 180 // this is the end of the try-with-resources that opens in. 181 } 182 } 183 184 // writes 16 bytes of NCE consist memory 185 private NceMessage writeNceConsistMemory(int curConsist, byte[] b) { 186 187 replyLen = REPLY_1; // Expect 1 byte response 188 waiting++; 189 190 byte[] bl = NceBinaryCommand.accMemoryWriteN(curConsist, b); 191 NceMessage m = NceMessage.createBinaryMessage(tc, bl, REPLY_1); 192 return m; 193 } 194 195 @Override 196 public void message(NceMessage m) { 197 } // ignore replies 198 199 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY") 200 @Override 201 public void reply(NceReply r) { 202 log.debug("waiting for {} responses", waiting); // NOI18N 203 204 if (waiting <= 0) { 205 log.error("unexpected response"); // NOI18N 206 return; 207 } 208 waiting--; 209 if (r.getNumDataElements() != replyLen) { 210 log.error("reply length incorrect"); // NOI18N 211 return; 212 } 213 if (replyLen == REPLY_1) { 214 // Looking for proper response 215 if (r.getElement(0) != NceMessage.NCE_OKAY) { 216 log.error("reply incorrect"); // NOI18N 217 } 218 } 219 220 // wake up restore thread 221 if (waiting == 0) { 222 synchronized (this) { 223 notify(); 224 } 225 } 226 } 227 228 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConsistRestore.class); 229 230}