recentFilteredSamples = new ArrayList<>();
+
+ // Counter to control how often the graph updates (to avoid too many updates)
+ private int updateCount = 0;
+
+ // Threshold to limit the frequency of graph updates (update graph every 4 slider changes)
+ private final int UPDATE_THRESHOLD = 4; // Reduces lag by updating graph less frequently
+
+ /**
+ * Resets the FFT (Fast Fourier Transform) series for both the unfiltered and filtered data.
+ *
+ * This method clears the data in the FFT graphs and sets the values back to zero.
+ * It is useful when the user stops interacting with the slider or when a filter change occurs.
+ * The FFT series for both the raw (unfiltered) and filtered signals are updated here.
+ */
+ private void resetFFT() {
+ // Clear the existing FFT data for both unfiltered and filtered series
+ fftSeriesUnfiltered.getData().clear();
+ fftSeriesFiltered.getData().clear();
+
+ // Populate the FFT series with zeroes to reset the graph visually
+ for (int i = -FFT_SIZE / 2; i < FFT_SIZE / 2; i++) {
+ fftSeriesUnfiltered.getData().add(new XYChart.Data<>(i, 0)); // Reset to zero for unfiltered series
+ fftSeriesFiltered.getData().add(new XYChart.Data<>(i, 0)); // Reset to zero for filtered series
+ }
+ }
+
+ /**
+ * The entry point for the JavaFX application.
+ *
+ * This method sets up the user interface for controlling motor speed and viewing
+ * the time-domain and frequency-domain (FFT) graphs. It includes sliders, filter options,
+ * and a button to oscillate the motor speed automatically.
+ *
+ * @param primaryStage The primary stage for this JavaFX application.
+ */
+ @Override
+ public void start(Stage primaryStage) {
+ // Slider for controlling motor speed
+ Slider speedSlider = new Slider(-100, 100, 0);
+ speedSlider.setShowTickLabels(true);
+ speedSlider.setShowTickMarks(true);
+ speedSlider.setMajorTickUnit(50);
+ speedSlider.setMinorTickCount(5);
+ speedSlider.setOrientation(Orientation.VERTICAL);
+ Label speedLabel = new Label("Speed");
+
+ // Slider for adjusting beta (roll-off factor) for the raised cosine filter
+ Slider betaSlider = new Slider(0.0, 1.0, beta);
+ betaSlider.setShowTickLabels(true);
+ betaSlider.setShowTickMarks(true);
+ betaSlider.setMajorTickUnit(0.1);
+ betaSlider.setMinorTickCount(10);
+ Label betaLabel = new Label("Beta (Roll-off Factor)");
+ Label betaValueLabel = new Label("Beta Value: " + beta);
+
+ // Radio buttons for selecting between raised cosine and low-pass filters
+ RadioButton raisedCosineRadio = new RadioButton("Raised Cosine");
+ RadioButton lowPassRadio = new RadioButton("Low-Pass");
+ ToggleGroup filterGroup = new ToggleGroup();
+ raisedCosineRadio.setToggleGroup(filterGroup);
+ lowPassRadio.setToggleGroup(filterGroup);
+ raisedCosineRadio.setSelected(true);
+
+ // Update graphs and FFT when filter selection changes
+ filterGroup.selectedToggleProperty().addListener((obs, oldVal, newVal) -> {
+ if (newVal == raisedCosineRadio) {
+ useRaisedCosine = true;
+ } else {
+ useRaisedCosine = false;
+ }
+ updateGraph(speedSlider.getValue());
+ updateFFT();
+ });
+
+ // Pause transition to reset the FFT after slider movement stops
+ PauseTransition pause = new PauseTransition(Duration.seconds(0.5));
+
+ // Trigger FFT reset when slider stops moving
+ pause.setOnFinished(event -> resetFFT());
+
+ // Add listener to update the FFT when the slider changes and reset pause timer
+ speedSlider.valueProperty().addListener((observable, oldValue, newValue) -> {
+ updateCount++;
+ if (updateCount % UPDATE_THRESHOLD == 0) {
+ updateGraph(newValue.doubleValue());
+ updateFFT();
+ updateCount = 0; // Reset update counter
+ }
+ });
+
+ // Setup for time-domain graph (displays speed over time)
+ NumberAxis xAxis = new NumberAxis(0, WINDOW_SIZE * X_INTERVAL, 5);
+ NumberAxis yAxis = new NumberAxis(-100, 100, 10);
+ LineChart lineChart = new LineChart<>(xAxis, yAxis);
+ lineChart.setCreateSymbols(false);
+ series.setName("Original Speed");
+ filteredSeries.setName("Filtered Speed");
+ lineChart.getData().add(series);
+ lineChart.getData().add(filteredSeries);
+
+ // Setup for frequency-domain (FFT) graph
+ NumberAxis freqAxis = new NumberAxis(-FFT_SIZE / 2, FFT_SIZE / 2 - 1, FFT_SIZE / 16);
+ NumberAxis magAxis = new NumberAxis(0, 5000, 1000); // Constant y-axis for FFT
+ LineChart fftChart = new LineChart<>(freqAxis, magAxis);
+ fftChart.setTitle("Frequency Domain");
+ fftSeriesUnfiltered.setName("FFT (Unfiltered)");
+ fftSeriesFiltered.setName("FFT (Filtered)");
+ fftChart.getData().add(fftSeriesUnfiltered);
+ fftChart.getData().add(fftSeriesFiltered);
+ fftChart.setCreateSymbols(false);
+
+ // Pre-fill the time-domain chart with zeros
+ for (int i = 0; i < WINDOW_SIZE; i++) {
+ series.getData().add(new XYChart.Data<>(i * X_INTERVAL, 0));
+ filteredSeries.getData().add(new XYChart.Data<>(i * X_INTERVAL, 0));
+ recentSamples.add(0.0);
+ recentFilteredSamples.add(0.0); // Initialize filtered samples with zeros
+ }
+ time = WINDOW_SIZE;
+
+ // Timeline for continuously updating the time-domain graph
+ Timeline timeline = new Timeline(new KeyFrame(Duration.millis(50), e -> {
+ double speed = speedSlider.getValue();
+ beta = betaSlider.getValue();
+ betaValueLabel.setText("Beta Value: " + String.format("%.2f", beta));
+ updateGraph(speed); // Update time-domain graph only
+ }));
+ timeline.setCycleCount(Timeline.INDEFINITE);
+ timeline.play();
+
+ // Button to toggle between manual control and automatic oscillation
+ Button oscillateButton = new Button("Start Oscillation");
+
+ // Timeline for oscillating motor speed (square wave)
+ Timeline oscillationTimeline = new Timeline(new KeyFrame(Duration.millis(50), e -> {
+ double frequency = 0.5; // 0.5 Hz = 2 seconds per cycle
+ double timeInMillis = System.currentTimeMillis();
+ double period = 1000 / frequency; // Period of the square wave in milliseconds
+
+ // Alternate between 0 and 100 (square wave logic)
+ double oscillatedSpeed = (timeInMillis % period) < (period / 2) ? 0 : 100;
+ speedSlider.setValue(oscillatedSpeed); // Update slider automatically
+ updateFFT(); // Update FFT based on oscillating speed
+ }));
+ oscillationTimeline.setCycleCount(Timeline.INDEFINITE);
+
+ // Toggle between oscillation and manual control when button is pressed
+ oscillateButton.setOnAction(event -> {
+ if (oscillationTimeline.getStatus() == Animation.Status.RUNNING) {
+ oscillationTimeline.stop(); // Stop oscillation
+ oscillateButton.setText("Start Oscillation");
+ speedSlider.setDisable(false); // Re-enable manual control
+ } else {
+ oscillationTimeline.play(); // Start oscillation
+ oscillateButton.setText("Stop Oscillation");
+ speedSlider.setDisable(true); // Disable manual control
+ }
+ });
+
+ // Layout for controls and graphs
+ VBox controlLayout = new VBox(10, raisedCosineRadio, lowPassRadio, betaLabel, betaSlider, betaValueLabel, oscillateButton);
+ controlLayout.setAlignment(Pos.TOP_LEFT);
+
+ VBox speedLayout = new VBox(10, speedLabel, speedSlider);
+ speedLayout.setAlignment(Pos.CENTER);
+
+ // Arrange the layout in the main window
+ BorderPane layout = new BorderPane();
+ layout.setLeft(speedLayout);
+ layout.setRight(controlLayout);
+ layout.setCenter(lineChart);
+ layout.setBottom(fftChart); // FFT graph at the bottom
+
+ // Setup and display the scene
+ Scene scene = new Scene(layout, 900, 600);
+ primaryStage.setScene(scene);
+ primaryStage.setTitle("Speed Graph with FFT");
+ primaryStage.show();
+ }
+
+ /**
+ * Updates the time-domain graph with new data and applies the selected filter.
+ *
+ * This method adds the current speed value (from the slider or oscillation)
+ * to the graph and applies either the raised cosine or low-pass filter to
+ * the recent samples. It ensures that both the original and filtered graphs
+ * stay within the window size, removing old data as new points are added.
+ *
+ *
+ * @param speed The current speed value to be plotted on the graph.
+ */
+ private void updateGraph(double speed) {
+ // Add the raw speed data to the original series
+ series.getData().add(new XYChart.Data<>(time * X_INTERVAL, speed));
+ recentSamples.add(speed); // Keep track of recent samples
+
+ // Remove oldest sample if the list exceeds the filter length
+ if (recentSamples.size() > FILTER_LENGTH) {
+ recentSamples.remove(0);
+ }
+
+ // Apply either the raised cosine filter or the low-pass filter
+ double filteredValue;
+ if (useRaisedCosine) {
+ double[] raisedCosineCoeffs = FilterUtils.raisedCosineCoefficients(FILTER_LENGTH, beta);
+ filteredValue = FilterUtils.applyRaisedCosineFilter(raisedCosineCoeffs, recentSamples);
+ } else {
+ double[] lowPassCoeffs = FilterUtils.lowPassCoefficients(FILTER_LENGTH, cutoffFrequency);
+ filteredValue = FilterUtils.applyLowPassFilter(lowPassCoeffs, recentSamples);
+ }
+
+ // Add the filtered speed data to the filtered series
+ filteredSeries.getData().add(new XYChart.Data<>(time * X_INTERVAL, filteredValue));
+ recentFilteredSamples.add(filteredValue); // Track recent filtered samples
+
+ // Keep the filtered sample list within the filter length
+ if (recentFilteredSamples.size() > FILTER_LENGTH) {
+ recentFilteredSamples.remove(0);
+ }
+
+ // Ensure both graphs (original and filtered) stay within the window size
+ if (series.getData().size() > WINDOW_SIZE) {
+ series.getData().remove(0);
+ filteredSeries.getData().remove(0);
+ }
+
+ // Update the x-axis to scroll as new data is added
+ NumberAxis xAxis = (NumberAxis) series.getChart().getXAxis();
+ xAxis.setLowerBound((time - WINDOW_SIZE) * X_INTERVAL);
+ xAxis.setUpperBound(time * X_INTERVAL);
+
+ // Increment the time step for the next data point
+ time++;
+ }
+
+ /**
+ * Updates the frequency-domain (FFT) graph for both unfiltered and filtered data.
+ *
+ * This method calculates the FFT (Fast Fourier Transform) of the most recent
+ * samples (both original and filtered), computes the magnitudes, shifts the
+ * frequencies to center zero, and updates the FFT graph for both data sets.
+ *
+ *
+ * The FFT graph shows how the frequency content of the signal changes over time.
+ * It is recalculated and updated whenever the time-domain data is changed or filtered.
+ *
+ */
+ private void updateFFT() {
+ // Calculate the FFT for the unfiltered and filtered data, which returns complex values
+ Complex[] unfilteredFFT = FFTUtils.calculateFFT(recentSamples, FFT_SIZE);
+ Complex[] filteredFFT = FFTUtils.calculateFFT(recentFilteredSamples, FFT_SIZE);
+
+ // Convert the complex FFT values to magnitudes (absolute values)
+ double[] unfilteredMagnitudes = calculateMagnitudes(unfilteredFFT);
+ double[] filteredMagnitudes = calculateMagnitudes(filteredFFT);
+
+ // Shift the FFT output to center the zero frequency (DC component) in the middle of the graph
+ double[] shiftedUnfilteredMagnitudes = shiftFFT(unfilteredMagnitudes);
+ double[] shiftedFilteredMagnitudes = shiftFFT(filteredMagnitudes);
+
+ // Filter out any invalid values such as NaN or Infinity from the magnitude arrays
+ shiftedUnfilteredMagnitudes = filterInvalidValues(shiftedUnfilteredMagnitudes);
+ shiftedFilteredMagnitudes = filterInvalidValues(shiftedFilteredMagnitudes);
+
+ // Clear and update the FFT graph for the unfiltered signal
+ fftSeriesUnfiltered.getData().clear();
+ for (int i = 0; i < shiftedUnfilteredMagnitudes.length; i++) {
+ fftSeriesUnfiltered.getData().add(new XYChart.Data<>(i - FFT_SIZE / 2, shiftedUnfilteredMagnitudes[i]));
+ }
+
+ // Clear and update the FFT graph for the filtered signal
+ fftSeriesFiltered.getData().clear();
+ for (int i = 0; i < shiftedFilteredMagnitudes.length; i++) {
+ fftSeriesFiltered.getData().add(new XYChart.Data<>(i - FFT_SIZE / 2, shiftedFilteredMagnitudes[i]));
+ }
+ }
+
+ /**
+ * Shifts the FFT result so that zero frequency (DC component) is in the center of the graph.
+ *
+ * FFT results place the zero frequency at the start, followed by positive and negative frequencies.
+ * This method rearranges the result so that the negative frequencies appear on the left side
+ * and positive frequencies appear on the right side, with zero frequency centered.
+ *
+ *
+ * @param fftData The FFT result (magnitude or complex) to be shifted
+ * @return A shifted array with the zero frequency centered
+ */
+ private double[] shiftFFT(double[] fftData) {
+ int n = fftData.length;
+ double[] shifted = new double[n];
+ int halfSize = n / 2;
+
+ // Move negative frequencies to the beginning of the array
+ System.arraycopy(fftData, halfSize, shifted, 0, halfSize); // Negative frequencies
+
+ // Move positive frequencies to the end of the array
+ System.arraycopy(fftData, 0, shifted, halfSize, halfSize); // Positive frequencies
+
+ return shifted;
+ }
+
+ /**
+ * Calculates the magnitudes of complex FFT data.
+ *
+ * FFT results are complex numbers, which represent both amplitude and phase.
+ * The magnitude (absolute value) of a complex number represents the strength of each frequency component.
+ *
+ *
+ * @param fftData The complex FFT data
+ * @return An array of magnitudes representing the strength of each frequency component
+ */
+ private double[] calculateMagnitudes(Complex[] fftData) {
+ double[] magnitudes = new double[fftData.length];
+
+ // Loop through FFT results and calculate the magnitude (absolute value) for each complex number
+ for (int i = 0; i < fftData.length; i++) {
+ magnitudes[i] = fftData[i].abs(); // Magnitude = absolute value of the complex number
+ }
+
+ return magnitudes;
+ }
+
+ /**
+ * Filters out invalid values from a data array.
+ *
+ * Sometimes the FFT or other calculations can produce invalid results such as NaN (Not a Number)
+ * or Infinity. This method replaces such values with zero to prevent display or calculation issues.
+ *
+ *
+ * @param data The array of data to be filtered
+ * @return A cleaned array with invalid values replaced by zero
+ */
+ private double[] filterInvalidValues(double[] data) {
+ // Loop through the data array
+ for (int i = 0; i < data.length; i++) {
+ // Check if the value is NaN (Not a Number) or Infinite and replace it with zero
+ if (Double.isNaN(data[i]) || Double.isInfinite(data[i])) {
+ data[i] = 0; // Replace invalid values with 0
+ }
+ }
+
+ return data;
+ }
+
+ /**
+ * The main method to launch the JavaFX application.
+ *
+ * This method is the entry point for launching the JavaFX GUI application.
+ * It sets up the window and calls the {@code start} method to initialize the interface.
+ *
+ *
+ * @param args Command line arguments (not used)
+ */
+ public static void main(String[] args) {
+ launch(args); // Launches the JavaFX application
+ }
+}
diff --git a/src/main/java/com/waveletsolutions/robot/FFTUtils.java b/src/main/java/com/waveletsolutions/robot/FFTUtils.java
new file mode 100644
index 0000000..1014af1
--- /dev/null
+++ b/src/main/java/com/waveletsolutions/robot/FFTUtils.java
@@ -0,0 +1,70 @@
+package com.waveletsolutions.robot;
+
+import org.apache.commons.math3.complex.Complex;
+import org.apache.commons.math3.transform.DftNormalization;
+import org.apache.commons.math3.transform.FastFourierTransformer;
+import org.apache.commons.math3.transform.TransformType;
+
+import java.util.List;
+
+/**
+ * The {@code FFTUtils} class provides utility methods for performing
+ * Fast Fourier Transform (FFT) operations on time-domain signals.
+ *
+ * This class is used to convert a list of time-domain samples into their
+ * frequency-domain representation using the FFT. The primary method is
+ * designed to handle input of varying sizes by either padding or truncating
+ * the input data to fit the required FFT size.
+ *
+ *
+ * Usage:
+ *
+ * - Call the {@code calculateFFT} method with a list of time-domain samples and the desired FFT size.
+ * - The method returns an array of {@code Complex} numbers representing the frequency components of the signal.
+ *
+ *
+ * Technical Concepts:
+ *
+ * - Fast Fourier Transform (FFT): Converts time-domain signals to frequency-domain signals.
+ * - Padding: If the number of input samples is smaller than the required FFT size, the input is padded with zeros.
+ * - Truncation: If the number of input samples exceeds the FFT size, the input is truncated.
+ *
+ *
+ * @author Eric Ratliff
+ * @version 1.0.0
+ * @since 2024-09-09
+ */
+public class FFTUtils {
+ /**
+ * Calculates the Fast Fourier Transform (FFT) of a list of time-domain samples.
+ *
+ * This method takes in a list of time-domain samples, applies padding or truncation
+ * to ensure the correct FFT size, and then computes the FFT. The result is returned
+ * as an array of {@code Complex} numbers representing the frequency-domain data.
+ *
+ *
+ * @param samples the list of time-domain samples to be transformed
+ * @param fftSize the desired size of the FFT (the number of points in the frequency domain)
+ * @return an array of {@code Complex} numbers representing the frequency components of the input signal
+ *
+ * Details:
+ *
+ * - If the number of samples is less than {@code fftSize}, the input array is padded with zeros.
+ * - If the number of samples exceeds {@code fftSize}, the input array is truncated.
+ * - The FFT is computed using the Apache Commons Math {@code FastFourierTransformer} class.
+ *
+ */
+ public static Complex[] calculateFFT(List samples, int fftSize) {
+ FastFourierTransformer transformer = new FastFourierTransformer(DftNormalization.STANDARD);
+ double[] inputArray = samples.stream().mapToDouble(Double::doubleValue).toArray();
+
+ // Ensure that the input array has the right size
+ if (inputArray.length < fftSize) {
+ inputArray = java.util.Arrays.copyOf(inputArray, fftSize); // Pad with zeros if too small
+ } else if (inputArray.length > fftSize) {
+ inputArray = java.util.Arrays.copyOfRange(inputArray, 0, fftSize); // Truncate if too large
+ }
+
+ return transformer.transform(inputArray, TransformType.FORWARD);
+ }
+}
diff --git a/src/main/java/com/waveletsolutions/robot/FilterUtils.java b/src/main/java/com/waveletsolutions/robot/FilterUtils.java
new file mode 100644
index 0000000..8380c7c
--- /dev/null
+++ b/src/main/java/com/waveletsolutions/robot/FilterUtils.java
@@ -0,0 +1,137 @@
+package com.waveletsolutions.robot;
+
+import java.util.List;
+
+/**
+ * The {@code FilterUtils} class provides utility methods for creating and applying
+ * digital filters, such as raised cosine filters and low-pass filters.
+ *
+ * These filters are commonly used in signal processing to smooth or shape signals.
+ * The class offers methods to generate filter coefficients and apply them to a set
+ * of samples. The filters implemented here are designed for basic signal processing
+ * tasks.
+ *
+ *
+ * Filters Implemented:
+ *
+ * - Raised Cosine Filter: Used for shaping signals with controlled bandwidth.
+ * - Low-Pass Filter: Allows signals below a certain frequency to pass while attenuating higher frequencies.
+ *
+ *
+ * Usage:
+ *
+ * - Use the {@code raisedCosineCoefficients} or {@code lowPassCoefficients} to generate filter coefficients.
+ * - Apply the generated filter to a set of samples using {@code applyRaisedCosineFilter} or {@code applyLowPassFilter}.
+ *
+ *
+ * @author Eric Ratliff
+ * @version 1.0.0
+ * @since 2024-09-09
+ */
+public class FilterUtils {
+
+ /**
+ * Generates raised cosine filter coefficients.
+ *
+ * The raised cosine filter is used for pulse shaping in communication systems.
+ * It shapes the signal to control the bandwidth while reducing intersymbol interference.
+ * The filter is defined by a roll-off factor {@code beta}, which controls how much excess
+ * bandwidth is allowed.
+ *
+ *
+ * @param length the number of filter coefficients (the length of the filter)
+ * @param beta the roll-off factor (ranges between 0 and 1)
+ * @return an array of raised cosine filter coefficients
+ */
+ public static double[] raisedCosineCoefficients(int length, double beta) {
+ double[] coeffs = new double[length];
+ double sum = 0.0;
+
+ for (int i = 0; i < length; i++) {
+ double t = (double) i / (length - 1);
+ double cosPart = 0.5 * (1 + Math.cos(Math.PI * (2 * t - 1) * beta));
+ coeffs[i] = cosPart;
+ sum += cosPart;
+ }
+
+ // Normalize the coefficients to ensure proper filtering
+ for (int i = 0; i < length; i++) {
+ coeffs[i] /= sum;
+ }
+
+ return coeffs;
+ }
+
+ /**
+ * Applies the raised cosine filter to a list of samples.
+ *
+ * This method multiplies the given raised cosine filter coefficients with
+ * the most recent samples to smooth the signal.
+ *
+ *
+ * @param coeffs the raised cosine filter coefficients
+ * @param samples the time-domain samples to be filtered
+ * @return the filtered value of the signal at the current time step
+ */
+ public static double applyRaisedCosineFilter(double[] coeffs, List samples) {
+ double result = 0;
+ for (int i = 0; i < coeffs.length; i++) {
+ result += coeffs[i] * samples.get(samples.size() - 1 - i);
+ }
+ return result;
+ }
+
+ /**
+ * Generates low-pass filter coefficients using a Hamming window.
+ *
+ * This method creates a low-pass filter, which allows frequencies below a certain
+ * cutoff to pass through while attenuating higher frequencies. The filter is generated
+ * using a Hamming window for smooth transitions.
+ *
+ *
+ * @param length the number of filter coefficients (the length of the filter)
+ * @param cutoffFrequency the cutoff frequency as a fraction of the sampling rate (e.g., 0.1 for 10%)
+ * @return an array of low-pass filter coefficients
+ */
+ public static double[] lowPassCoefficients(int length, double cutoffFrequency) {
+ double[] coeffs = new double[length];
+ double sum = 0.0;
+
+ for (int i = 0; i < length; i++) {
+ double t = i - (length - 1) / 2.0;
+ if (t == 0.0) {
+ coeffs[i] = 2 * cutoffFrequency;
+ } else {
+ coeffs[i] = Math.sin(2 * Math.PI * cutoffFrequency * t) / (Math.PI * t);
+ }
+ coeffs[i] *= 0.54 - 0.46 * Math.cos(2 * Math.PI * i / (length - 1)); // Apply Hamming window
+ sum += coeffs[i];
+ }
+
+ // Normalize the coefficients to ensure proper filtering
+ for (int i = 0; i < length; i++) {
+ coeffs[i] /= sum;
+ }
+
+ return coeffs;
+ }
+
+ /**
+ * Applies a low-pass filter to a list of samples.
+ *
+ * This method multiplies the given low-pass filter coefficients with
+ * the most recent samples to smooth the signal and reduce high-frequency noise.
+ *
+ *
+ * @param coeffs the low-pass filter coefficients
+ * @param samples the time-domain samples to be filtered
+ * @return the filtered value of the signal at the current time step
+ */
+ public static double applyLowPassFilter(double[] coeffs, List samples) {
+ double result = 0;
+ for (int i = 0; i < coeffs.length; i++) {
+ result += coeffs[i] * samples.get(samples.size() - 1 - i);
+ }
+ return result;
+ }
+}
diff --git a/src/test/java/com/waveletsolutions/robot/AppTest.java b/src/test/java/com/waveletsolutions/robot/AppTest.java
new file mode 100644
index 0000000..5dba93c
--- /dev/null
+++ b/src/test/java/com/waveletsolutions/robot/AppTest.java
@@ -0,0 +1,38 @@
+package com.waveletsolutions.robot;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Unit test for simple App.
+ */
+public class AppTest
+ extends TestCase
+{
+ /**
+ * Create the test case
+ *
+ * @param testName name of the test case
+ */
+ public AppTest( String testName )
+ {
+ super( testName );
+ }
+
+ /**
+ * @return the suite of tests being tested
+ */
+ public static Test suite()
+ {
+ return new TestSuite( AppTest.class );
+ }
+
+ /**
+ * Rigourous Test :-)
+ */
+ public void testApp()
+ {
+ assertTrue( true );
+ }
+}