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}