forked from farrellf/Telemetry-Viewer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Controller.java
892 lines (655 loc) · 27.3 KB
/
Controller.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
import java.awt.Color;
import java.awt.Toolkit;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.swing.JOptionPane;
import com.fazecast.jSerialComm.SerialPort;
/**
* Handles all non-GUI logic and manages access to the Model (the data).
*/
public class Controller {
static List<GridChangedListener> gridChangedListeners = new ArrayList<GridChangedListener>();
static List<SerialPortListener> serialPortListeners = new ArrayList<SerialPortListener>();
static List<ChartListener> chartListeners = new ArrayList<ChartListener>();
static SerialPort port;
static Thread serialPortThread;
static AtomicBoolean dataStructureDefined = new AtomicBoolean(false);
/**
* @return The percentage of 100dpi that the screen uses. It's currently rounded to an integer, but future plans will make use of floats.
*/
public static float getDisplayScalingFactor() {
return (int) Math.round((double) Toolkit.getDefaultToolkit().getScreenResolution() / 100.0);
}
/**
* Registers a listener that will be notified when the ChartsRegion grid size (column or row count) changes.
*
* @param listener The listener to be notified.
*/
public static void addGridChangedListener(GridChangedListener listener) {
gridChangedListeners.add(listener);
}
/**
* Notifies all registered listeners about a new ChartsRegion grid size.
*/
private static void notifyGridChangedListeners() {
for(GridChangedListener listener : gridChangedListeners)
listener.gridChanged(Model.gridColumns, Model.gridRows);
}
/**
* Changes the ChartsRegion grid column count if it is within the allowed range and would not obscure part of an existing chart.
*
* @param value The new column count.
*/
public static void setGridColumns(int value) {
boolean chartsObscured = false;
for(PositionedChart chart : Model.charts)
if(chart.regionOccupied(value, 0, Model.gridColumns, Model.gridRows))
chartsObscured = true;
if(value >= Model.gridColumnsMinimum && value <= Model.gridColumnsMaximum && !chartsObscured)
Model.gridColumns = value;
notifyGridChangedListeners();
}
/**
* @return The current ChartsRegion grid column count.
*/
public static int getGridColumns() {
return Model.gridColumns;
}
/**
* Changes the ChartsRegion grid row count if it is within the allowed range and would not obscure part of an existing chart.
*
* @param value The new row count.
*/
public static void setGridRows(int value) {
boolean chartsObscured = false;
for(PositionedChart chart : Model.charts)
if(chart.regionOccupied(0, value, Model.gridColumns, Model.gridRows))
chartsObscured = true;
if(value >= Model.gridRowsMinimum && value <= Model.gridRowsMaximum && !chartsObscured)
Model.gridRows = value;
notifyGridChangedListeners();
}
/**
* @return The current ChartsRegion grid row count.
*/
public static int getGridRows() {
return Model.gridRows;
}
/**
* @return An array of ChartDescriptor's, one for each possible chart type.
*/
public static ChartDescriptor[] getChartDescriptors() {
return Model.chartDescriptors;
}
/**
* @return The number of CSV columns or Binary elements described in the data structure.
*/
public static int getDatasetsCount() {
return Model.datasets.size();
}
/**
* @param location CSV column number, or Binary packet byte offset. Locations may be sparse.
* @return The Dataset.
*/
public static Dataset getDatasetByLocation(int location) {
return Model.datasets.get(location);
}
/**
* @param index An index between 0 and getDatasetsCount()-1, inclusive.
* @return The Dataset.
*/
public static Dataset getDatasetByIndex(int index) {
return (Dataset) Model.datasets.values().toArray()[index];
}
/**
* Creates and stores a new Dataset. If a Dataset already exists for the same location, the new Dataset will replace it.
*
* @param location CSV column number, or Binary packet byte offset.
* @param processor BinaryProcessor for the raw samples in the Binary packet. (Ignored in CSV mode.)
* @param name Descriptive name of what the samples represent.
* @param color Color to use when visualizing the samples.
* @param unit Descriptive name of how the samples are quantified.
* @param conversionFactorA This many unprocessed LSBs...
* @param conversionFactorB ... equals this many units.
*/
public static void insertDataset(int location, BinaryProcessor processor, String name, Color color, String unit, double conversionFactorA, double conversionFactorB) {
Model.datasets.put(location, new Dataset(location, processor, name, color, unit, conversionFactorA, conversionFactorB));
}
/**
* Removes all charts and Datasets.
*/
public static void removeAllDatasets() {
Controller.removeAllPositionedCharts();
Model.datasets.clear();
}
/**
* @return The Datasets.
*/
public static Collection<Dataset> getAllDatasets() {
return Model.datasets.values();
}
/**
* Registers a listener that will be notified when the serial port status (connection made or lost) changes.
*
* @param listener The listener to be notified.
*/
public static void addSerialPortListener(SerialPortListener listener) {
serialPortListeners.add(listener);
}
private static final int SERIAL_CONNECTION_OPENED = 0;
private static final int SERIAL_CONNECTION_CLOSED = 1;
/**
* Notifies all registered listeners about a change in the serial port status.
*
* @param status Either SERIAL_CONNECTION_OPENED or SERIAL_CONNECTION_CLOSED.
*/
private static void notifySerialPortListeners(int status) {
for(SerialPortListener listener : serialPortListeners)
if(status == SERIAL_CONNECTION_OPENED)
listener.connectionOpened(Model.sampleRate, Model.packetType, Model.portName, Model.baudRate);
else if(status == SERIAL_CONNECTION_CLOSED)
listener.connectionClosed();
}
/**
* @return A String[] of names for all serial ports that were detected at the time of this function call, plus the "Test" dummy serial port.
*/
public static String[] getSerialPortNames() {
SerialPort[] ports = SerialPort.getCommPorts();
String[] names = new String[ports.length + 1];
for(int i = 0; i < ports.length; i++)
names[i] = ports[i].getSystemPortName();
names[names.length - 1] = "Test";
return names;
}
/**
* @return An int[] of supported UART baud rates.
*/
public static int[] getBaudRates() {
return new int[] {9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600, 1000000, 1500000, 2000000, 3000000};
}
/**
* @return A String[] of descriptions for supported UART packet types.
*/
public static String[] getPacketTypes() {
return new String[] {"ASCII CSVs", "Binary"};
}
/**
* Connects to a serial port and spawns a new thread to process incoming data.
*
* @param sampleRate Expected samples per second (Hz.) This is used for FFTs.
* @param packetType One of the Strings from Controller.getPacketTypes()
* @param portName One of the Strings from Controller.getSerialPortNames()
* @param baudRate One of the baud rates from Controller.getBaudRates()
*/
@SuppressWarnings("deprecation")
public static void connectToSerialPort(int sampleRate, String packetType, String portName, int baudRate) {
if(portName.equals("Test")) {
Tester.populateDataStructure();
Tester.startTransmission();
Model.sampleRate = sampleRate;
Model.packetType = packetType;
Model.portName = portName;
Model.baudRate = 9600;
notifySerialPortListeners(SERIAL_CONNECTION_OPENED);
return;
} else if(packetType.equals("ASCII CSVs")) {
port = SerialPort.getCommPort(portName);
port.setBaudRate(baudRate);
port.setComPortTimeouts(SerialPort.TIMEOUT_SCANNER, 0, 0);
if(!port.openPort()) { // try 3 times before giving up
if(!port.openPort()) {
if(!port.openPort()) {
notifySerialPortListeners(SERIAL_CONNECTION_CLOSED);
return;
}
}
}
Model.sampleRate = sampleRate;
Model.packetType = packetType;
Model.portName = portName;
Model.baudRate = baudRate;
if(serialPortThread != null && serialPortThread.isAlive())
serialPortThread.stop();
serialPortThread = new Thread(new Runnable() {
@Override public void run() {
// wait for the data structure to be defined
while(!dataStructureDefined.get())
try { Thread.sleep(10); } catch(Exception e) { }
Scanner scanner = new Scanner(port.getInputStream());
while(scanner.hasNextLine()) {
// stop receiving data if the thread has been interrupted
if(serialPortThread.isInterrupted())
break;
try {
String line = scanner.nextLine();
String[] tokens = line.split(",");
double[] samples = new double[tokens.length];
for(int i = 0; i < tokens.length; i++)
samples[i] = Double.parseDouble(tokens[i]);
Controller.insertSamples(samples);
} catch(Exception e) { }
}
scanner.close();
port.closePort();
port = null;
notifySerialPortListeners(SERIAL_CONNECTION_CLOSED);
}
});
serialPortThread.setPriority(Thread.MAX_PRIORITY);
serialPortThread.setName("Serial Port Receiver");
serialPortThread.start();
notifySerialPortListeners(SERIAL_CONNECTION_OPENED);
return;
} else if(packetType.equals("Binary")) {
port = SerialPort.getCommPort(portName);
port.setBaudRate(baudRate);
port.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING, 0, 0);
if(!port.openPort()) { // try 3 times before giving up
if(!port.openPort()) {
if(!port.openPort()) {
notifySerialPortListeners(SERIAL_CONNECTION_CLOSED);
return;
}
}
}
Model.sampleRate = sampleRate;
Model.packetType = packetType;
Model.portName = portName;
Model.baudRate = baudRate;
if(serialPortThread != null && serialPortThread.isAlive())
serialPortThread.stop();
serialPortThread = new Thread(new Runnable() {
@Override public void run() {
byte[] rx_buffer = new byte[1024];
// wait for data structure to be defined
while(!dataStructureDefined.get())
try { Thread.sleep(10); } catch(Exception e) { }
// get packet size (includes 1 byte sync word)
int packetSize = Controller.getBinaryPacketSize();
double[] samples = new double[Controller.getDatasetsCount()];
while(true) {
// stop receiving data if the thread has been interrupted
if(serialPortThread.isInterrupted()) {
port.closePort();
port = null;
notifySerialPortListeners(SERIAL_CONNECTION_CLOSED);
return;
}
// wait for sync byte of 0xAA
while(rx_buffer[0] != (byte) 0xAA) // the byte cast is required!
port.readBytes(rx_buffer, 1);
// get rest of packet after the sync word
port.readBytes(rx_buffer, packetSize - 1 + 2); // -1 for sync word, +2 for checksum
// extract all samples from the packet
for(int datasetNumber = 0; datasetNumber < samples.length; datasetNumber++) {
Dataset dataset = Controller.getDatasetByIndex(datasetNumber);
byte[] rawData = new byte[dataset.processor.getByteCount()];
int rx_buffer_index = dataset.location - 1; // -1 for the sync word
for(int i = 0; i < rawData.length; i++)
rawData[i] = rx_buffer[rx_buffer_index++];
samples[datasetNumber] = dataset.processor.extractValue(rawData);
}
// calculate the sum
int wordCount = (packetSize - 1) / 2; // -1 for sync word, /2 for 16bit words
int sum = 0;
int lsb = 0;
int msb = 0;
for(int i = 0; i < wordCount; i++) {
lsb = 0xFF & rx_buffer[i*2];
msb = 0xFF & rx_buffer[i*2 + 1];
sum += (msb << 8 | lsb);
}
// extract the checksum
lsb = 0xFF & rx_buffer[wordCount*2];
msb = 0xFF & rx_buffer[wordCount*2 + 1];
int checksum = (msb << 8 | lsb);
// add samples to the database if the checksum passed
sum %= 65535;
if(sum == checksum)
Controller.insertSamples(samples);
else
System.err.println("checksum failed");
}
}
});
serialPortThread.setPriority(Thread.MAX_PRIORITY);
serialPortThread.setName("Serial Port Receiver");
serialPortThread.start();
notifySerialPortListeners(SERIAL_CONNECTION_OPENED);
return;
}
}
/**
* Disconnects from the active serial port and stops the data processing thread.
*/
public static void disconnectFromSerialPort() {
dataStructureDefined.set(false);
if(Model.portName.equals("Test")) {
Tester.stopTransmission();
notifySerialPortListeners(SERIAL_CONNECTION_CLOSED);
} else {
if(serialPortThread != null) {
serialPortThread.interrupt();
}
}
}
/**
* Registers a listener that will be notified when a chart is added or removed.
*
* @param listener The listener to be notified.
*/
public static void addChartsListener(ChartListener listener) {
chartListeners.add(listener);
}
/**
* Notifies all registered listeners about a new chart.
*/
private static void notifyChartListenersOfAddition(PositionedChart chart) {
for(ChartListener listener : chartListeners)
listener.chartAdded(chart);
}
/**
* Notifies all registered Listeners about a removed chart.
*/
private static void notifyChartListenersOfRemoval(PositionedChart chart) {
for(ChartListener listener : chartListeners)
listener.chartRemoved(chart);
}
/**
* @param chart New chart to insert and display.
*/
public static void addPositionedChart(PositionedChart chart) {
Model.charts.add(chart);
notifyChartListenersOfAddition(chart);
}
/**
* Removes all charts.
*/
public static void removeAllPositionedCharts() {
for(PositionedChart chart : Model.charts)
notifyChartListenersOfRemoval(chart);
Model.charts.clear();
}
/**
* Checks if a region is available in the ChartsRegion.
*
* @param x1 The x-coordinate of a bounding-box corner in the ChartsRegion grid.
* @param y1 The y-coordinate of a bounding-box corner in the ChartsRegion grid.
* @param x2 The x-coordinate of the opposite bounding-box corner in the ChartsRegion grid.
* @param y2 The y-coordinate of the opposite bounding-box corner in the ChartsRegion grid.
* @return True if available, false if not.
*/
public static boolean gridRegionAvailable(int x1, int y1, int x2, int y2) {
int topLeftX = x1 < x2 ? x1 : x2;
int topLeftY = y1 < y2 ? y1 : y2;
int bottomRightX = x2 > x1 ? x2 : x1;
int bottomRightY = y2 > y1 ? y2 : y1;
for(PositionedChart chart : Model.charts)
if(chart.regionOccupied(topLeftX, topLeftY, bottomRightX, bottomRightY))
return false;
return true;
}
/**
* Repositions and resizes all charts.
*
* @param columnWidth The width of a column in the ChartsRegion grid.
* @param rowHeight The height of a row in the ChartsRegion grid.
*/
public static void repositionCharts(int columnWidth, int rowHeight) {
for(PositionedChart chart : Model.charts)
chart.reposition(columnWidth, rowHeight);
}
/**
* @return The frequency of samples, in Hz.
*/
public static int getSampleRate() {
return Model.sampleRate;
}
/**
* @param rate The frequency of samples, in Hz.
*/
public static void setSampleRate(int rate) {
Model.sampleRate = rate;
}
/**
* @return The default color to use when defining the data structure.
*/
public static Color getDefaultLineColor() {
return Model.lineColorDefault;
}
/**
* A helper function that calculates the sample count of datasets.
* Since datasets may contain different numbers of samples (due to live insertion of new samples), the smallest count is returned to ensure validity.
*
* @param dataset The Dataset[].
* @return Smallest sample count from the datasets.
*/
static int getSamplesCount(Dataset[] dataset) {
int[] count = new int[dataset.length];
for(int i = 0; i < dataset.length; i ++)
count[i] = dataset[i].size();
Arrays.sort(count);
return count[0];
}
/**
* Inserts one new sample into each of the datasets.
*
* @param newSamples A double[] containing one sample for each dataset.
*/
static void insertSamples(double[] newSamples) {
for(int i = 0; i < newSamples.length; i++)
Controller.getDatasetByIndex(i).add(newSamples[i]); // FIXME should be byLocation, but that breaks Binary mode
}
/**
* Allows reception of data from the UART. This should only be called after the data structure has been fully defined.
*/
static void startReceivingData() {
dataStructureDefined.set(true);
}
/**
* @return The number of bytes in a complete Binary data packet (including the 0xAA sync word.)
*/
static int getBinaryPacketSize() {
Dataset lastDataset = Controller.getDatasetByIndex(Controller.getDatasetsCount() - 1);
return lastDataset.location + lastDataset.processor.getByteCount();
}
/**
* @return An array of BinaryProcessor's that each describe their data type and can convert raw bytes into a number.
*/
static BinaryProcessor[] getBinaryProcessors() {
BinaryProcessor[] processor = new BinaryProcessor[2];
processor[0] = new BinaryProcessor() {
@Override public String toString() { return "uint16 LSB First"; }
@Override public int getByteCount() { return 2; }
@Override public double extractValue(byte[] rawByte) { return (double) ((0xFF & rawByte[1]) << 8 | (0xFF & rawByte[0])); }
};
processor[1] = new BinaryProcessor() {
@Override public String toString() { return "uint16 MSB First"; }
@Override public int getByteCount() { return 2; }
@Override public double extractValue(byte[] rawByte) { return (double) ((0xFF & rawByte[0]) << 8 | (0xFF & rawByte[1])); }
};
return processor;
}
/**
* Saves the current state to a file. The state consists of: grid row and column counts, serial port settings, data structure definition, and details for each chart.
*
* @param outputFilePath An absolute path to a .txt file.
*/
static void saveLayout(String outputFilePath) {
try {
PrintWriter outputFile = new PrintWriter(new File(outputFilePath), "UTF-8");
outputFile.println("Telemetry Viewer File Format v0.1");
outputFile.println("");
outputFile.println("Grid Settings:");
outputFile.println("");
outputFile.println("\tcolumn count = " + Model.gridColumns);
outputFile.println("\trow count = " + Model.gridRows);
outputFile.println("");
outputFile.println("Serial Port Settings:");
outputFile.println("");
outputFile.println("\tport = " + port.getSystemPortName());
outputFile.println("\tbaud = " + port.getBaudRate());
outputFile.println("\tpacket type = " + Model.packetType);
outputFile.println("\tsample rate = " + Model.sampleRate);
outputFile.println("");
outputFile.println(Model.datasets.size() + " Data Structure Locations:");
for(Dataset dataset : Model.datasets.values()) {
int processorIndex = 0;
BinaryProcessor[] processors = Controller.getBinaryProcessors();
for(int i = 0; i < processors.length; i++)
if(dataset.processor == processors[i])
processorIndex = i;
outputFile.println("");
outputFile.println("\tlocation = " + dataset.location);
outputFile.println("\tprocessor index = " + processorIndex);
outputFile.println("\tname = " + dataset.name);
outputFile.println("\tcolor = " + String.format("0x%02X%02X%02X", dataset.color.getRed(), dataset.color.getGreen(), dataset.color.getBlue()));
outputFile.println("\tunit = " + dataset.unit);
outputFile.println("\tconversion factor a = " + dataset.conversionFactorA);
outputFile.println("\tconversion factor b = " + dataset.conversionFactorB);
}
outputFile.println("");
outputFile.println(Model.charts.size() + " Charts:");
for(PositionedChart chart : Model.charts) {
outputFile.println("");
outputFile.println("\tchart type = " + chart.toString());
outputFile.println("\tduration = " + chart.duration);
outputFile.println("\ttop left x = " + chart.topLeftX);
outputFile.println("\ttop left y = " + chart.topLeftY);
outputFile.println("\tbottom right x = " + chart.bottomRightX);
outputFile.println("\tbottom right y = " + chart.bottomRightY);
outputFile.println("\tdatasets count = " + chart.datasets.length);
for(int i = 0; i < chart.datasets.length; i++)
outputFile.println("\t\tdataset location = " + chart.datasets[i].location);
}
outputFile.close();
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Unable to save the file.", "Error: Unable to Save the File", JOptionPane.ERROR_MESSAGE);
}
}
/**
* Opens a file and resets the current state to the state defined in that file.
* The state consists of: grid row and column counts, serial port settings, data structure definition, and details for each chart.
*
* @param inputFilePath An absolute path to a .txt file.
*/
static void openLayout(String inputFilePath) {
Controller.removeAllDatasets();
Controller.disconnectFromSerialPort();
try {
List<String> lines = Files.readAllLines(new File(inputFilePath).toPath(), StandardCharsets.UTF_8);
int n = 0;
verify(lines.get(n++).equals("Telemetry Viewer File Format v0.1"));
verify(lines.get(n++).equals(""));
verify(lines.get(n++).equals("Grid Settings:"));
verify(lines.get(n++).equals(""));
verify(lines.get(n++).startsWith("\tcolumn count = "));
int gridColumns = Integer.parseInt(lines.get(n-1).substring(16));
verify(lines.get(n++).startsWith("\trow count = "));
int gridRows = Integer.parseInt(lines.get(n-1).substring(13));
verify(lines.get(n++).equals(""));
Controller.setGridColumns(gridColumns);
Controller.setGridRows(gridRows);
verify(lines.get(n++).equals("Serial Port Settings:"));
verify(lines.get(n++).equals(""));
verify(lines.get(n++).startsWith("\tport = "));
String portName = lines.get(n-1).substring(8);
verify(lines.get(n++).startsWith("\tbaud = "));
int baudRate = Integer.parseInt(lines.get(n-1).substring(8));
verify(lines.get(n++).startsWith("\tpacket type = "));
String packetType = lines.get(n-1).substring(15);
verify(lines.get(n++).startsWith("\tsample rate = "));
int sampleRate = Integer.parseInt(lines.get(n-1).substring(15));
verify(lines.get(n++).equals(""));
Controller.connectToSerialPort(sampleRate, packetType, portName, baudRate);
verify(lines.get(n++).endsWith(" Data Structure Locations:"));
int locationsCount = Integer.parseInt(lines.get(n-1).split(" ")[0]);
for(int i = 0; i < locationsCount; i++) {
verify(lines.get(n++).equals(""));
verify(lines.get(n++).startsWith("\tlocation = "));
int location = Integer.parseInt(lines.get(n-1).substring(12));
verify(lines.get(n++).startsWith("\tprocessor index = "));
int processorIndex = Integer.parseInt(lines.get(n-1).substring(19));
BinaryProcessor processor = Controller.getBinaryProcessors()[processorIndex];
verify(lines.get(n++).startsWith("\tname = "));
String name = lines.get(n-1).substring(8);
verify(lines.get(n++).startsWith("\tcolor = 0x"));
int colorNumber = Integer.parseInt(lines.get(n-1).substring(11), 16);
Color color = new Color(colorNumber);
verify(lines.get(n++).startsWith("\tunit = "));
String unit = lines.get(n-1).substring(8);
verify(lines.get(n++).startsWith("\tconversion factor a = "));
double conversionFactorA = Double.parseDouble(lines.get(n-1).substring(23));
verify(lines.get(n++).startsWith("\tconversion factor b = "));
double conversionFactorB = Double.parseDouble(lines.get(n-1).substring(23));
Controller.insertDataset(location, processor, name, color, unit, conversionFactorA, conversionFactorB);
}
Controller.startReceivingData();
try{ Thread.sleep(3000); } catch(Exception e) { }
verify(lines.get(n++).equals(""));
verify(lines.get(n++).endsWith(" Charts:"));
int chartsCount = Integer.parseInt(lines.get(n-1).split(" ")[0]);
for(int i = 0; i < chartsCount; i++) {
verify(lines.get(n++).equals(""));
verify(lines.get(n++).startsWith("\tchart type = "));
String chartType = lines.get(n-1).substring(14);
verify(lines.get(n++).startsWith("\tduration = "));
int duration = Integer.parseInt(lines.get(n-1).substring(12));
verify(lines.get(n++).startsWith("\ttop left x = "));
int topLeftX = Integer.parseInt(lines.get(n-1).substring(14));
verify(lines.get(n++).startsWith("\ttop left y = "));
int topLeftY = Integer.parseInt(lines.get(n-1).substring(14));
verify(lines.get(n++).startsWith("\tbottom right x = "));
int bottomRightX = Integer.parseInt(lines.get(n-1).substring(18));
verify(lines.get(n++).startsWith("\tbottom right y = "));
int bottomRightY = Integer.parseInt(lines.get(n-1).substring(18));
verify(lines.get(n++).startsWith("\tdatasets count = "));
int datasetsCount = Integer.parseInt(lines.get(n-1).substring(18));
Dataset[] datasets = new Dataset[datasetsCount];
for(int j = 0; j < datasetsCount; j++) {
verify(lines.get(n++).startsWith("\t\tdataset location = "));
int location = Integer.parseInt(lines.get(n-1).substring(21));
datasets[j] = Controller.getDatasetByLocation(location);
}
for(ChartDescriptor descriptor : Controller.getChartDescriptors())
if(descriptor.toString().equals(chartType)) {
PositionedChart chart = descriptor.createChart(topLeftX, topLeftY, bottomRightX, bottomRightY, duration, datasets);
Controller.addPositionedChart(chart);
break;
}
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Unable to open the file.", "Error: Unable to Open the File", JOptionPane.ERROR_MESSAGE);
} catch(AssertionError e) {
JOptionPane.showMessageDialog(null, "Unable to parse the file.", "Error: Unable to Parse the File", JOptionPane.ERROR_MESSAGE);
}
}
/**
* A helper function that is essentially an "assert" and throws an exception if the assert fails.
*
* @param good If true nothing will happen, if false an AssertionError will be thrown.
*/
private static void verify(boolean good) {
if(!good)
throw new AssertionError();
}
/**
* This specifies the target period for how long to wait between the rendering of frames.
* This is the upper limit, if the CPU/GPU can't keep up, the rendering will of course slow down.
* 15ms will ensure smooth updates on a 60Hz monitor. Making this number bigger will reduce CPU/GPU load but make the charts appear to stutter.
*
* @return The ideal number of milliseconds between frames.
*/
public static int getTargetFramePeriod() {
return 15;
}
}