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