001package jmri.jmrix.nce.consist; 002 003import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 004 005import java.io.BufferedWriter; 006import java.io.File; 007import java.io.FileWriter; 008import java.io.IOException; 009import java.io.PrintWriter; 010 011import java.text.MessageFormat; 012import javax.swing.JFileChooser; 013import javax.swing.JLabel; 014import javax.swing.JPanel; 015 016import jmri.jmrix.nce.NceBinaryCommand; 017import jmri.jmrix.nce.NceMessage; 018import jmri.jmrix.nce.NceReply; 019import jmri.jmrix.nce.NceTrafficController; 020import jmri.util.FileUtil; 021import jmri.util.StringUtil; 022import jmri.util.swing.JmriJOptionPane; 023import jmri.util.swing.TextFilter; 024 025/** 026 * Backups NCE Consists to a text file format defined by NCE. 027 * <p> 028 * NCE "Backup consists" dumps the consists into a text file. The consists data 029 * are stored in the NCE CS starting at xF500 and ending at xFAFF. 030 * <p> 031 * NCE file format: 032 * <p> 033 * :F500 (16 bytes per line, grouped as 8 words with space delimiters) :F510 . . 034 * :FAF0 :0000 035 * <p> 036 * Consist data byte: 037 * <p> 038 * bit 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 039 * <p> 040 * This backup routine uses the same consist data format as NCE. 041 * 042 * @author Dan Boudreau Copyright (C) 2007 043 * @author Ken Cameron Copyright (C) 2023 044 */ 045public class NceConsistBackup extends Thread implements jmri.jmrix.nce.NceListener { 046 047 private int consistRecLen = 16; // bytes per line 048 private int replyLen = 0; // expected byte length 049 private int waiting = 0; // to catch responses not intended for this module 050 private boolean fileValid = false; // used to flag backup status messages 051 052 private final byte[] nceConsistData = new byte[consistRecLen]; 053 054 JLabel textConsist = new JLabel(); 055 JLabel consistNumber = new JLabel(); 056 057 private NceTrafficController tc = null; 058 private int consistStartNum = -1; 059 private int consistEndNum = -1; 060 061 public NceConsistBackup(NceTrafficController t) { 062 tc = t; 063 consistStartNum = tc.csm.getConsistMin(); 064 consistEndNum = tc.csm.getConsistMax(); 065 } 066 067 @Override 068 public void run() { 069 070 // get file to write to 071 JFileChooser fc = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 072 fc.addChoosableFileFilter(new TextFilter()); 073 074 File fs = new File("NCE consist backup.txt"); // NOI18N 075 fc.setSelectedFile(fs); 076 077 int retVal = fc.showSaveDialog(null); 078 if (retVal != JFileChooser.APPROVE_OPTION) { 079 return; // Canceled 080 } 081 if (fc.getSelectedFile() == null) { 082 return; // Canceled 083 } 084 File f = fc.getSelectedFile(); 085 if (fc.getFileFilter() != fc.getAcceptAllFileFilter()) { 086 // append .txt to file name if needed 087 String fileName = f.getAbsolutePath(); 088 String fileNameLC = fileName.toLowerCase(); 089 if (!fileNameLC.endsWith(".txt")) { 090 fileName = fileName + ".txt"; 091 f = new File(fileName); 092 } 093 } 094 if (f.exists()) { 095 if (JmriJOptionPane.showConfirmDialog(null, 096 MessageFormat.format(Bundle.getMessage("FileExists"), f.getName()), 097 Bundle.getMessage("OverwriteFile"), 098 JmriJOptionPane.OK_CANCEL_OPTION) != JmriJOptionPane.OK_OPTION) { 099 return; 100 } 101 } 102 103 try (PrintWriter fileOut = new PrintWriter(new BufferedWriter(new FileWriter(f)), 104 true)) { 105 106 if (JmriJOptionPane.showConfirmDialog(null, 107 Bundle.getMessage("BackupTakesAwhile"), 108 Bundle.getMessage("NceConsistBackup"), 109 JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) { 110 fileOut.close(); 111 return; 112 } 113 114 // create a status frame 115 JPanel ps = new JPanel(); 116 jmri.util.JmriJFrame fstatus = new jmri.util.JmriJFrame(Bundle.getMessage("NceConsistBackup")); 117 fstatus.setLocationRelativeTo(null); 118 fstatus.setSize(300, 100); 119 fstatus.getContentPane().add(ps); 120 121 ps.add(textConsist); 122 ps.add(consistNumber); 123 124 textConsist.setText(Bundle.getMessage("ConsistLineNumber")); 125 textConsist.setVisible(true); 126 consistNumber.setVisible(true); 127 128 // now read NCE CS consist memory and write to file 129 waiting = 0; // reset in case there was a previous error 130 fileValid = true; // assume we're going to succeed 131 // output string to file 132 // head 2 bytes, end 2 bytes, mid num * 2 bytes 133 int consistBytesEach = (tc.csm.getConsistMidEntries() * 2) + 2 + 2; 134 int memOffsetEnd = consistBytesEach * (1 +consistEndNum - consistStartNum); 135 int consistRecNum = 0; 136 int consistNum = consistStartNum; 137 138 for (int memOffset = 0; memOffset < memOffsetEnd; memOffset += consistRecLen) { 139 140 consistNum = consistStartNum + (memOffset / consistBytesEach); 141 consistRecNum = memOffset / consistRecLen; 142 consistNumber.setText(Integer.toString(consistNum)); 143 fstatus.setVisible(true); 144 145 getNceConsist(consistRecNum); 146 147 if (!fileValid) { 148 memOffset = memOffsetEnd; // break out of for loop 149 } 150 if (fileValid) { 151 StringBuilder buf = new StringBuilder(); 152 buf.append(":").append(Integer.toHexString(tc.csm.getConsistHeadAddr() + memOffset)); 153 154 for (int i = 0; i < consistRecLen; i++) { 155 buf.append(" ").append(StringUtil.twoHexFromInt(nceConsistData[i++])); 156 buf.append(StringUtil.twoHexFromInt(nceConsistData[i])); 157 } 158 159 log.debug("consist {}", buf); 160 fileOut.println(buf.toString()); 161 } 162 } 163 164 if (fileValid) { 165 // NCE file terminator 166 String line = ":0000"; 167 fileOut.println(line); 168 } 169 170 // Write to disk and close file 171 fileOut.flush(); 172 fileOut.close(); 173 174 // kill status panel 175 fstatus.dispose(); 176 177 if (fileValid) { 178 JmriJOptionPane.showMessageDialog(null, 179 Bundle.getMessage("SuccessfulBackup"), 180 Bundle.getMessage("NceConsistBackup"), 181 JmriJOptionPane.INFORMATION_MESSAGE); 182 } else { 183 JmriJOptionPane.showMessageDialog(null, 184 Bundle.getMessage("BackupFailed"), 185 Bundle.getMessage("NceConsistBackup"), 186 JmriJOptionPane.ERROR_MESSAGE); 187 } 188 189 } catch (IOException e) { 190 // this is the end of the try-with-resources that opens fileOut. 191 } 192 193 } 194 195 // Read 16 bytes of NCE CS memory 196 private void getNceConsist(int cN) { 197 198 NceMessage m = readConsistMemory(cN); 199 tc.sendNceMessage(m, this); 200 // wait for read to complete 201 readWait(); 202 } 203 204 // wait up to 30 sec per read 205 private boolean readWait() { 206 int waitcount = 30; 207 while (waiting > 0) { 208 synchronized (this) { 209 try { 210 wait(1000); 211 } catch (InterruptedException e) { 212 Thread.currentThread().interrupt(); // retain if needed later 213 } 214 } 215 if (waitcount-- < 0) { 216 log.error("read timeout"); // NOI18N 217 fileValid = false; // need to quit 218 return false; 219 } 220 } 221 return true; 222 } 223 224 /* 225 * // USB set cab memory pointer private void setUsbCabMemoryPointer(int 226 * cab, int offset) { log.debug("Macro base address: {}, offset: {}", 227 * Integer.toHexString(cab), offset); replyLen = NceMessage.REPLY_1; // 228 * Expect 1 byte response waiting++; byte[] bl = 229 * NceBinaryCommand.usbMemoryPointer(cab, offset); NceMessage m = 230 * NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1); 231 * tc.sendNceMessage(m, this); } 232 * 233 * // USB Read N bytes of NCE cab memory private void readUsbMemoryN(int 234 * num) { switch (num) { case 1: replyLen = NceMessage.REPLY_1; // Expect 1 235 * byte response break; case 2: replyLen = NceMessage.REPLY_2; // Expect 2 236 * byte response break; case 4: replyLen = NceMessage.REPLY_4; // Expect 4 237 * byte response break; default: log.error("Invalid usb read byte count"); 238 * return; } waiting++; byte[] bl = NceBinaryCommand.usbMemoryRead((byte) 239 * num); NceMessage m = NceMessage.createBinaryMessage(tc, bl, replyLen); 240 * tc.sendNceMessage(m, this); } 241 * 242 * // USB Write 1 byte of NCE memory private void writeUsbMemory1(byte 243 * value) { log.debug("Write byte: {}", String.format("%2X", value)); 244 * replyLen = NceMessage.REPLY_1; // Expect 1 byte response waiting++; 245 * byte[] bl = NceBinaryCommand.usbMemoryWrite1(value); NceMessage m = 246 * NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_1); 247 * tc.sendNceMessage(m, this); } 248 */ 249 250 // Reads 16 bytes of NCE consist memory 251 private NceMessage readConsistMemory(int consistNum) { 252 253 int nceConsistAddr = (consistNum * consistRecLen) + tc.csm.getConsistHeadAddr(); 254 replyLen = NceMessage.REPLY_16; // Expect 16 byte response 255 waiting++; 256 byte[] bl = NceBinaryCommand.accMemoryRead(nceConsistAddr); 257 NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_16); 258 return m; 259 } 260 261 @Override 262 public void message(NceMessage m) { 263 } // ignore replies 264 265 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY") 266 // this reply always expects two consecutive reads 267 @Override 268 public void reply(NceReply r) { 269 270 if (waiting <= 0) { 271 log.error("unexpected response"); // NOI18N 272 return; 273 } 274 if (r.getNumDataElements() != replyLen) { 275 log.error("reply length incorrect"); // NOI18N 276 return; 277 } 278 279 // load data buffer 280 for (int i = 0; i < NceMessage.REPLY_16; i++) { 281 nceConsistData[i] = (byte) r.getElement(i); 282 } 283 waiting--; 284 285 // wake up backup thread 286 synchronized (this) { 287 notify(); 288 } 289 } 290 291 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConsistBackup.class); 292 293}