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 static final int CONSIST_LNTH = 16; // 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[CONSIST_LNTH]; 053 054 JLabel textConsist = new JLabel(); 055 JLabel consistNumber = new JLabel(); 056 057 private NceTrafficController tc = null; 058 private int workingNumConsists = -1; 059 060 public NceConsistBackup(NceTrafficController t) { 061 tc = t; 062 workingNumConsists = tc.csm.getConsistMax(); 063 } 064 065 @Override 066 public void run() { 067 068 // get file to write to 069 JFileChooser fc = new jmri.util.swing.JmriJFileChooser(FileUtil.getUserFilesPath()); 070 fc.addChoosableFileFilter(new TextFilter()); 071 072 File fs = new File("NCE consist backup.txt"); // NOI18N 073 fc.setSelectedFile(fs); 074 075 int retVal = fc.showSaveDialog(null); 076 if (retVal != JFileChooser.APPROVE_OPTION) { 077 return; // Canceled 078 } 079 if (fc.getSelectedFile() == null) { 080 return; // Canceled 081 } 082 File f = fc.getSelectedFile(); 083 if (fc.getFileFilter() != fc.getAcceptAllFileFilter()) { 084 // append .txt to file name if needed 085 String fileName = f.getAbsolutePath(); 086 String fileNameLC = fileName.toLowerCase(); 087 if (!fileNameLC.endsWith(".txt")) { 088 fileName = fileName + ".txt"; 089 f = new File(fileName); 090 } 091 } 092 if (f.exists()) { 093 if (JmriJOptionPane.showConfirmDialog(null, 094 MessageFormat.format(Bundle.getMessage("FileExists"), f.getName()), 095 Bundle.getMessage("OverwriteFile"), 096 JmriJOptionPane.OK_CANCEL_OPTION) != JmriJOptionPane.OK_OPTION) { 097 return; 098 } 099 } 100 101 try (PrintWriter fileOut = new PrintWriter(new BufferedWriter(new FileWriter(f)), 102 true)) { 103 104 if (JmriJOptionPane.showConfirmDialog(null, 105 Bundle.getMessage("BackupTakesAwhile"), 106 Bundle.getMessage("NceConsistBackup"), 107 JmriJOptionPane.YES_NO_OPTION) != JmriJOptionPane.YES_OPTION) { 108 fileOut.close(); 109 return; 110 } 111 112 // create a status frame 113 JPanel ps = new JPanel(); 114 jmri.util.JmriJFrame fstatus = new jmri.util.JmriJFrame(Bundle.getMessage("NceConsistBackup")); 115 fstatus.setLocationRelativeTo(null); 116 fstatus.setSize(300, 100); 117 fstatus.getContentPane().add(ps); 118 119 ps.add(textConsist); 120 ps.add(consistNumber); 121 122 textConsist.setText(Bundle.getMessage("ConsistLineNumber")); 123 textConsist.setVisible(true); 124 consistNumber.setVisible(true); 125 126 // now read NCE CS consist memory and write to file 127 waiting = 0; // reset in case there was a previous error 128 fileValid = true; // assume we're going to succeed 129 // output string to file 130 131 for (int consistNum = 0; consistNum < workingNumConsists; consistNum++) { 132 133 consistNumber.setText(Integer.toString(consistNum)); 134 fstatus.setVisible(true); 135 136 getNceConsist(consistNum); 137 138 if (!fileValid) { 139 consistNum = workingNumConsists; // break out of for loop 140 } 141 if (fileValid) { 142 StringBuilder buf = new StringBuilder(); 143 buf.append(":").append(Integer.toHexString( 144 tc.csm.getConsistHeadAddr() + (consistNum * CONSIST_LNTH))); 145 146 for (int i = 0; i < CONSIST_LNTH; i++) { 147 buf.append(" ").append(StringUtil.twoHexFromInt(nceConsistData[i++])); 148 buf.append(StringUtil.twoHexFromInt(nceConsistData[i])); 149 } 150 151 log.debug("consist {}", buf); 152 fileOut.println(buf.toString()); 153 } 154 } 155 156 if (fileValid) { 157 // NCE file terminator 158 String line = ":0000"; 159 fileOut.println(line); 160 } 161 162 // Write to disk and close file 163 fileOut.flush(); 164 fileOut.close(); 165 166 // kill status panel 167 fstatus.dispose(); 168 169 if (fileValid) { 170 JmriJOptionPane.showMessageDialog(null, 171 Bundle.getMessage("SuccessfulBackup"), 172 Bundle.getMessage("NceConsistBackup"), 173 JmriJOptionPane.INFORMATION_MESSAGE); 174 } else { 175 JmriJOptionPane.showMessageDialog(null, 176 Bundle.getMessage("BackupFailed"), 177 Bundle.getMessage("NceConsistBackup"), 178 JmriJOptionPane.ERROR_MESSAGE); 179 } 180 181 } catch (IOException e) { 182 // this is the end of the try-with-resources that opens fileOut. 183 } 184 185 } 186 187 // Read 16 bytes of NCE CS memory 188 private void getNceConsist(int cN) { 189 190 NceMessage m = readConsistMemory(cN); 191 tc.sendNceMessage(m, this); 192 // wait for read to complete 193 readWait(); 194 } 195 196 // wait up to 30 sec per read 197 private boolean readWait() { 198 int waitcount = 30; 199 while (waiting > 0) { 200 synchronized (this) { 201 try { 202 wait(1000); 203 } catch (InterruptedException e) { 204 Thread.currentThread().interrupt(); // retain if needed later 205 } 206 } 207 if (waitcount-- < 0) { 208 log.error("read timeout"); // NOI18N 209 fileValid = false; // need to quit 210 return false; 211 } 212 } 213 return true; 214 } 215 216 // Reads 16 bytes of NCE consist memory 217 private NceMessage readConsistMemory(int consistNum) { 218 219 int nceConsistAddr = (consistNum * CONSIST_LNTH) + tc.csm.getConsistHeadAddr(); 220 replyLen = NceMessage.REPLY_16; // Expect 16 byte response 221 waiting++; 222 byte[] bl = NceBinaryCommand.accMemoryRead(nceConsistAddr); 223 NceMessage m = NceMessage.createBinaryMessage(tc, bl, NceMessage.REPLY_16); 224 return m; 225 } 226 227 @Override 228 public void message(NceMessage m) { 229 } // ignore replies 230 231 @SuppressFBWarnings(value = "NN_NAKED_NOTIFY") 232 // this reply always expects two consecutive reads 233 @Override 234 public void reply(NceReply r) { 235 236 if (waiting <= 0) { 237 log.error("unexpected response"); // NOI18N 238 return; 239 } 240 if (r.getNumDataElements() != replyLen) { 241 log.error("reply length incorrect"); // NOI18N 242 return; 243 } 244 245 // load data buffer 246 for (int i = 0; i < NceMessage.REPLY_16; i++) { 247 nceConsistData[i] = (byte) r.getElement(i); 248 } 249 waiting--; 250 251 // wake up backup thread 252 synchronized (this) { 253 notify(); 254 } 255 } 256 257 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NceConsistBackup.class); 258 259}