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}