001package jmri.jmrix.rps.swing.debugger; 002 003import java.awt.FlowLayout; 004import java.io.FileReader; 005import java.io.IOException; 006import java.io.Reader; 007import java.text.ParseException; 008 009import javax.swing.BoxLayout; 010import javax.swing.JButton; 011import javax.swing.JComboBox; 012import javax.swing.JFileChooser; 013import javax.swing.JLabel; 014import javax.swing.JMenu; 015import javax.swing.JMenuBar; 016import javax.swing.JPanel; 017import javax.swing.JScrollPane; 018import javax.swing.JSeparator; 019import javax.swing.JTextField; 020 021import jmri.jmrix.rps.Distributor; 022import jmri.jmrix.rps.Engine; 023import jmri.jmrix.rps.Measurement; 024import jmri.jmrix.rps.MeasurementListener; 025import jmri.jmrix.rps.Reading; 026import jmri.jmrix.rps.ReadingListener; 027import jmri.jmrix.rps.RpsSystemConnectionMemo; 028import jmri.util.IntlUtilities; 029 030import org.apache.commons.csv.CSVFormat; 031import org.apache.commons.csv.CSVParser; 032import org.apache.commons.csv.CSVRecord; 033 034/** 035 * Frame for manual operation and debugging of the RPS system. 036 * 037 * @author Bob Jacobsen Copyright (C) 2008 038 */ 039public class DebuggerFrame extends jmri.util.JmriJFrame 040 implements ReadingListener, MeasurementListener { 041 042 private RpsSystemConnectionMemo memo = null; 043 044 public DebuggerFrame(RpsSystemConnectionMemo rpsMemo) { 045 super(); 046 memo = rpsMemo; 047 048 NUMSENSORS = Engine.instance().getMaxReceiverNumber(); 049 050 setTitle(title()); 051 } 052 053 protected final String title() { 054 return "RPS Debugger"; 055 } // product name, not translated 056 057 @Override 058 public void dispose() { 059 // separate from data source 060 Distributor.instance().removeReadingListener(this); 061 Distributor.instance().removeMeasurementListener(this); 062 // and unwind swing 063 super.dispose(); 064 } 065 066 private java.text.NumberFormat nf; 067 068 private JComboBox<String> mode; 069 070 private final JTextField x = new JTextField(18); 071 private final JTextField y = new JTextField(18); 072 private final JTextField z = new JTextField(18); 073 private final JLabel code = new JLabel(); 074 075 private final JTextField id = new JTextField(5); 076 077 private final DebuggerTimePane timep = new DebuggerTimePane(); 078 079 private final int NUMSENSORS; 080 081 @Override 082 public void initComponents() { 083 084 // I18N format 085 nf = java.text.NumberFormat.getInstance(); 086 nf.setMinimumFractionDigits(1); 087 nf.setMaximumFractionDigits(1); 088 nf.setGroupingUsed(false); 089 090 getContentPane().setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)); 091 092 // add panes in the middle 093 JPanel p; 094 JPanel p1; 095 096 // Time inputs 097 p = new JPanel(); 098 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); 099 100 p.add(new JLabel("Time measurements: ")); 101 102 timep.initComponents(); 103 JScrollPane sc = new JScrollPane(timep); 104 p.add(sc); 105 106 // add id field at bottom 107 JPanel p5 = new JPanel(); 108 p5.setLayout(new FlowLayout()); 109 p5.add(new JLabel("Id: ")); 110 p5.add(id); 111 p.add(p5); 112 113 getContentPane().add(p); 114 115 getContentPane().add(new JSeparator()); 116 117 // x, y, z results 118 p = new JPanel(); 119 p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS)); 120 p.add(new JLabel("Results:")); 121 p1 = new JPanel(); 122 p1.setLayout(new FlowLayout()); 123 p1.add(new JLabel("X:")); 124 p1.add(x); 125 p.add(p1); 126 p1 = new JPanel(); 127 p1.setLayout(new FlowLayout()); 128 p1.add(new JLabel("Y:")); 129 p1.add(y); 130 p.add(p1); 131 p1 = new JPanel(); 132 p1.setLayout(new FlowLayout()); 133 p1.add(new JLabel("Z:")); 134 p1.add(z); 135 p.add(p1); 136 p1 = new JPanel(); 137 p1.setLayout(new FlowLayout()); 138 p1.add(new JLabel("Code:")); 139 p1.add(code); 140 p.add(p1); 141 getContentPane().add(p); 142 143 getContentPane().add(new JSeparator()); 144 145 // add controls at bottom 146 p = new JPanel(); 147 148 mode = new JComboBox<>(new String[]{"From time fields", "from X,Y,Z fields", "from time file", "from X,Y,Z file"}); 149 p.add(mode); 150 p.setLayout(new FlowLayout()); 151 152 JButton doButton = new JButton("Do Once"); 153 doButton.addActionListener(e -> doOnce()); 154 p.add(doButton); 155 getContentPane().add(p); 156 157 // start working 158 Distributor.instance().addReadingListener(this); 159 Distributor.instance().addMeasurementListener(this); 160 161 // add file menu 162 JMenuBar menuBar = new JMenuBar(); 163 JMenu fileMenu = new JMenu("File"); 164 menuBar.add(fileMenu); 165 fileMenu.add(new jmri.jmrix.rps.swing.CsvExportAction("Export Readings as CSV...", memo)); 166 fileMenu.add(new jmri.jmrix.rps.swing.CsvExportMeasurementAction("Export Measurements as CSV...", memo)); 167 setJMenuBar(menuBar); 168 169 // add help 170 addHelpMenu("package.jmri.jmrix.rps.swing.debugger.DebuggerFrame", true); 171 172 // prepare for display 173 pack(); 174 } 175 176 /** 177 * Invoked by button to do one cycle 178 */ 179 private void doOnce() { 180 try { 181 switch (mode.getSelectedIndex()) { 182 case 0: // From time fields 183 doReadingFromTimeFields(); 184 break; 185 case 1: // From X,Y,Z fields 186 doMeasurementFromPositionFields(); 187 break; 188 case 2: // From time file 189 doLoadReadingFromFile(); 190 doReadingFromTimeFields(); 191 break; 192 case 3: // From X,Y,Z file 193 doLoadMeasurementFromFile(); 194 break; 195 default: // should not happen 196 log.error("Did not expect selected mode {}", mode.getSelectedIndex()); 197 } 198 } catch (IOException e) { 199 log.error("exception ", e); 200 } 201 } 202 203 private void doLoadReadingFromFile() throws IOException { 204 if (readingInput == null) { 205 readingInput = getParser(readingFileChooser); 206 } 207 208 // get and load a line 209 if (readingInput.getRecords().isEmpty()) { 210 // read failed, try once to get another file 211 readingInput = getParser(readingFileChooser); 212 if (readingInput.getRecords().isEmpty()) { 213 throw new IOException("no valid file"); 214 } 215 } 216 CSVRecord readingRecord = readingInput.getRecords().get(0); 217 218 // item 0 is the ID, not used right now 219 for (int i = 0; i < Math.min(NUMSENSORS, readingRecord.size() + 1); i++) { 220 timep.times[i].setText(readingRecord.get(i + 1)); 221 } 222 } 223 224 private CSVParser getParser(JFileChooser chooser) throws IOException { 225 // get file 226 CSVParser parser = null; 227 228 chooser.rescanCurrentDirectory(); 229 int retVal = chooser.showOpenDialog(this); 230 231 // handle selection or cancel 232 if (retVal == JFileChooser.APPROVE_OPTION) { 233 // create and keep reader 234 Reader reader = new FileReader(chooser.getSelectedFile()); 235 parser = new CSVParser(reader, 236 CSVFormat.Builder.create(CSVFormat.DEFAULT).setSkipHeaderRecord(true).build()); 237 } 238 return parser; 239 } 240 241 private void doLoadMeasurementFromFile() throws IOException { 242 if (measurementInput == null) { 243 measurementInput = getParser(measurementFileChooser); 244 } 245 246 // get and load a line 247 if (measurementInput.getRecords().isEmpty()) { 248 // read failed, try once to get another file 249 measurementInput = getParser(measurementFileChooser); 250 if (measurementInput.getRecords().isEmpty()) { 251 throw new IOException("no valid file"); 252 } 253 } 254 CSVRecord measurementRecord = measurementInput.getRecords().get(0); 255 256 // item 0 is the ID, not used right now 257 Measurement m = new Measurement(null, 258 Double.parseDouble(measurementRecord.get(1)), 259 Double.parseDouble(measurementRecord.get(2)), 260 Double.parseDouble(measurementRecord.get(3)), 261 Engine.instance().getVSound(), 262 0, 263 "Data File" 264 ); 265 266 Distributor.instance().submitMeasurement(m); 267 } 268 269 private Reading getReadingFromTimeFields() { 270 271 double[] values = new double[NUMSENSORS + 1]; 272 273 JTextField tmp; 274 // parse input 275 for (int i = 0; i <= NUMSENSORS; i++) { 276 values[i] = 0.; 277 tmp = timep.times[i]; 278 if ((tmp != null) && !tmp.getText().isEmpty()) { 279 try { 280 values[i] = IntlUtilities.doubleValue(tmp.getText()); 281 } catch (ParseException ex) { 282 log.error("Could not create number from {}, {}",tmp.getText(),ex.getMessage()); 283 } 284 } 285 } 286 287 // get the id number and make reading 288 return new Reading(id.getText(), values); 289 } 290 291 private void doReadingFromTimeFields() { 292 // get the reading 293 Reading r = getReadingFromTimeFields(); 294 295 // and forward 296 Distributor.instance().submitReading(r); 297 } 298 299 @Override 300 public void notify(Reading r) { 301 // This implementation creates a new Calculator 302 // each time to ensure that the most recent 303 // receiver positions are used; this should be 304 // replaced with some notification system 305 // to reduce the work used. 306 307 id.setText("" + r.getId()); 308 timep.notify(r); 309 } 310 311 private void doMeasurementFromPositionFields() { 312 // contain dummy Reading 313 Reading r = new Reading(id.getText(), new double[]{0., 0., 0., 0.}); 314 315 try { 316 Measurement m = new Measurement(r, 317 IntlUtilities.doubleValue(x.getText()), 318 IntlUtilities.doubleValue(y.getText()), 319 IntlUtilities.doubleValue(z.getText()), 320 Engine.instance().getVSound(), 321 0, 322 "Position Data" 323 ); 324 Distributor.instance().submitMeasurement(m); 325 } catch (ParseException ex) { 326 log.error("Could not translate a field to Number. {}",ex.getMessage()); 327 } 328 } 329 330 @Override 331 public void notify(Measurement m) { 332 // show result 333 x.setText(nf.format(m.getX())); 334 y.setText(nf.format(m.getY())); 335 z.setText(nf.format(m.getZ())); 336 code.setText(m.textCode()); 337 338 timep.notify(m); 339 } 340 341 // to find and remember the input files 342 private CSVParser readingInput = null; 343 final JFileChooser readingFileChooser = new jmri.util.swing.JmriJFileChooser("rps/readings.csv"); 344 345 private CSVParser measurementInput = null; 346 final JFileChooser measurementFileChooser = new jmri.util.swing.JmriJFileChooser("rps/positions.csv"); 347 348 private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DebuggerFrame.class); 349 350}