diff --git a/java_console/.idea/.name b/java_console/.idea/.name
new file mode 100644
index 0000000000..6d94bdd83c
--- /dev/null
+++ b/java_console/.idea/.name
@@ -0,0 +1 @@
+java_console
\ No newline at end of file
diff --git a/java_console/.idea/ant.xml b/java_console/.idea/ant.xml
new file mode 100644
index 0000000000..313b0c0cb8
--- /dev/null
+++ b/java_console/.idea/ant.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/java_console/.idea/compiler.xml b/java_console/.idea/compiler.xml
new file mode 100644
index 0000000000..b22c943f13
--- /dev/null
+++ b/java_console/.idea/compiler.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/.idea/copyright/profiles_settings.xml b/java_console/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000000..3572571ad8
--- /dev/null
+++ b/java_console/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/dictionaries/andrey.xml b/java_console/.idea/dictionaries/andrey.xml
new file mode 100644
index 0000000000..9969a687d0
--- /dev/null
+++ b/java_console/.idea/dictionaries/andrey.xml
@@ -0,0 +1,7 @@
+
+
+
+ belomutskiy
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/encodings.xml b/java_console/.idea/encodings.xml
new file mode 100644
index 0000000000..e206d70d85
--- /dev/null
+++ b/java_console/.idea/encodings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/java_console/.idea/libraries/SteelSeries.xml b/java_console/.idea/libraries/SteelSeries.xml
new file mode 100644
index 0000000000..2fd81cfd41
--- /dev/null
+++ b/java_console/.idea/libraries/SteelSeries.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/libraries/annotations.xml b/java_console/.idea/libraries/annotations.xml
new file mode 100644
index 0000000000..9375c6d1d4
--- /dev/null
+++ b/java_console/.idea/libraries/annotations.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/libraries/batik.xml b/java_console/.idea/libraries/batik.xml
new file mode 100644
index 0000000000..a19ef5db2a
--- /dev/null
+++ b/java_console/.idea/libraries/batik.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/libraries/jssc.xml b/java_console/.idea/libraries/jssc.xml
new file mode 100644
index 0000000000..bff9d4bff1
--- /dev/null
+++ b/java_console/.idea/libraries/jssc.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/libraries/junit.xml b/java_console/.idea/libraries/junit.xml
new file mode 100644
index 0000000000..004a5215b9
--- /dev/null
+++ b/java_console/.idea/libraries/junit.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/libraries/miglayout_4_0.xml b/java_console/.idea/libraries/miglayout_4_0.xml
new file mode 100644
index 0000000000..527499cd32
--- /dev/null
+++ b/java_console/.idea/libraries/miglayout_4_0.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/libraries/surfaceplotter.xml b/java_console/.idea/libraries/surfaceplotter.xml
new file mode 100644
index 0000000000..125a69ec07
--- /dev/null
+++ b/java_console/.idea/libraries/surfaceplotter.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/misc.xml b/java_console/.idea/misc.xml
new file mode 100644
index 0000000000..28737110d7
--- /dev/null
+++ b/java_console/.idea/misc.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ http://www.w3.org/1999/xhtml
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/.idea/modules.xml b/java_console/.idea/modules.xml
new file mode 100644
index 0000000000..5d231a23f9
--- /dev/null
+++ b/java_console/.idea/modules.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/.idea/runConfigurations/AutoTest.xml b/java_console/.idea/runConfigurations/AutoTest.xml
new file mode 100644
index 0000000000..1a245a65be
--- /dev/null
+++ b/java_console/.idea/runConfigurations/AutoTest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/runConfigurations/Launcher.xml b/java_console/.idea/runConfigurations/Launcher.xml
new file mode 100644
index 0000000000..7c497d7441
--- /dev/null
+++ b/java_console/.idea/runConfigurations/Launcher.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/runConfigurations/Launcher_29001.xml b/java_console/.idea/runConfigurations/Launcher_29001.xml
new file mode 100644
index 0000000000..f5085b59cd
--- /dev/null
+++ b/java_console/.idea/runConfigurations/Launcher_29001.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/runConfigurations/Launcher_COM10.xml b/java_console/.idea/runConfigurations/Launcher_COM10.xml
new file mode 100644
index 0000000000..1b6dbbcf3b
--- /dev/null
+++ b/java_console/.idea/runConfigurations/Launcher_COM10.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/runConfigurations/Launcher_COM11.xml b/java_console/.idea/runConfigurations/Launcher_COM11.xml
new file mode 100644
index 0000000000..9dcc97a035
--- /dev/null
+++ b/java_console/.idea/runConfigurations/Launcher_COM11.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/runConfigurations/Launcher_COM7.xml b/java_console/.idea/runConfigurations/Launcher_COM7.xml
new file mode 100644
index 0000000000..d4517f5de8
--- /dev/null
+++ b/java_console/.idea/runConfigurations/Launcher_COM7.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/scopes/scope_settings.xml b/java_console/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000000..922003b843
--- /dev/null
+++ b/java_console/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/.idea/uiDesigner.xml b/java_console/.idea/uiDesigner.xml
new file mode 100644
index 0000000000..3b00020308
--- /dev/null
+++ b/java_console/.idea/uiDesigner.xml
@@ -0,0 +1,125 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
+
diff --git a/java_console/.idea/vcs.xml b/java_console/.idea/vcs.xml
new file mode 100644
index 0000000000..ebabb34f16
--- /dev/null
+++ b/java_console/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/java_console/.idea/workspace.xml b/java_console/.idea/workspace.xml
new file mode 100644
index 0000000000..abbbae189a
--- /dev/null
+++ b/java_console/.idea/workspace.xml
@@ -0,0 +1,940 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ localhost
+ 5050
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ C:\Documents and Settings\andrey\Application Data\Subversion
+ false
+ 125
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1356459468906
+ 1356459468906
+
+
+ 1356461237109
+ 1356461237109
+
+
+ 1356490198156
+ 1356490198156
+
+
+ 1356664975109
+ 1356664975109
+
+
+ 1357444107186
+ 1357444107186
+
+
+ 1357445392108
+ 1357445392108
+
+
+ 1357493181093
+ 1357493181093
+
+
+ 1357574412109
+ 1357574412109
+
+
+ 1357583175109
+ 1357583175109
+
+
+ 1357786563484
+ 1357786563484
+
+
+ 1357824706109
+ 1357824706109
+
+
+ 1358190320109
+ 1358190320109
+
+
+ 1358205975109
+ 1358205975109
+
+
+ 1358212757093
+ 1358212757093
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No facets are configured
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1.7
+
+
+
+
+
+
+
+
+
+
+
+ ui
+
+
+
+
+
+
+
+
+
+
+
+ 1.7
+
+
+
+
+
+
+
+
+
+
+
+ surfaceplotter
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/autotest/autotest.iml b/java_console/autotest/autotest.iml
new file mode 100644
index 0000000000..d35f6f1e18
--- /dev/null
+++ b/java_console/autotest/autotest.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/autotest/src/com/rusefi/AutoTest.java b/java_console/autotest/src/com/rusefi/AutoTest.java
new file mode 100644
index 0000000000..be9a7c5d9c
--- /dev/null
+++ b/java_console/autotest/src/com/rusefi/AutoTest.java
@@ -0,0 +1,150 @@
+package com.rusefi;
+
+
+import com.irnems.FileLog;
+import com.irnems.core.Sensor;
+import com.irnems.core.SensorCentral;
+import com.rusefi.io.LinkManager;
+import com.rusefi.io.tcp.TcpConnector;
+import com.rusefi.waves.RevolutionLog;
+import com.rusefi.waves.WaveChart;
+import com.rusefi.waves.WaveChartParser;
+import com.rusefi.waves.WaveReport;
+
+import java.util.List;
+import java.util.concurrent.*;
+
+import static com.rusefi.IoUtil.*;
+import static com.rusefi.TestingUtils.assertCloseEnough;
+import static com.rusefi.TestingUtils.assertTrue;
+import static com.rusefi.waves.WaveReport.isCloseEnough;
+
+/**
+ * rusEfi firmware simulator functional test suite
+ *
+ * java -cp rusefi_console.jar com.rusefi.AutoTest
+ *
+ * @author Andrey Belomutskiy
+ * 3/5/14
+ */
+public class AutoTest {
+ public static void main(String[] args) throws InterruptedException {
+ FileLog.SIMULATOR_CONSOLE.start();
+ FileLog.MAIN.start();
+
+ try {
+ runTest();
+ } finally {
+ ExecHelper.destroy();
+ }
+ FileLog.MAIN.logLine("*******************************************************************************");
+ FileLog.MAIN.logLine("************************************ Looks good! *****************************");
+ FileLog.MAIN.logLine("*******************************************************************************");
+ System.exit(0);
+ }
+
+ private static void runTest() throws InterruptedException {
+ if (!TcpConnector.getAvailablePorts().isEmpty())
+ throw new IllegalStateException("Port already binded on startup?");
+
+ ExecHelper.startSimulator();
+
+
+// FileLog.rlog("Waiting for TCP port...");
+// for (int i = 0; i < 180; i++) {
+// if (!TcpConnector.getAvailablePorts().isEmpty())
+// break;
+// Thread.sleep(1000);
+// }
+// if (TcpConnector.getAvailablePorts().isEmpty())
+// throw new IllegalStateException("Did we start it?");
+// /**
+// * If we open a connection just to validate that the process has started, we are getting
+// * weird issues with the second - actual connection
+// */
+// FileLog.rlog("Time for simulator to close the port...");
+// Thread.sleep(3000);
+//
+// FileLog.rlog("Got a TCP port! Connecting...");
+ LinkManager.start("" + TcpConnector.DEFAULT_PORT);
+ /**
+ * TCP connector is blocking
+ */
+ LinkManager.open();
+
+ FileLog.rlog("Let's give it some time to start...");
+
+ final CountDownLatch startup = new CountDownLatch(1);
+ SensorCentral.AdcListener listener = new SensorCentral.AdcListener() {
+ @Override
+ public void onAdcUpdate(SensorCentral model, double value) {
+ startup.countDown();
+ }
+ };
+ long waitStart = System.currentTimeMillis();
+ SensorCentral.getInstance().addListener(Sensor.RPM, listener);
+ startup.await(5, TimeUnit.SECONDS);
+ SensorCentral.getInstance().removeListener(Sensor.RPM, listener);
+ FileLog.MAIN.logLine("Got first signal in " + (System.currentTimeMillis() - waitStart));
+
+ mainTestBody();
+ }
+
+ private static void mainTestBody() throws InterruptedException {
+ changeRpm(500);
+ changeRpm(2000);
+
+ String chartLine = getNextWaveChart();
+
+
+ WaveChart chart = WaveChartParser.unpackToMap(chartLine);
+
+ StringBuilder revolutions = chart.get(RevolutionLog.TOP_DEAD_CENTER_MESSAGE);
+ if (revolutions.length() == 0)
+ throw new IllegalStateException("Empty revolutions in " + chartLine);
+
+ RevolutionLog revolutionLog = RevolutionLog.parseRevolutions(revolutions);
+ assertWave(chart, revolutionLog, WaveChart.INJECTOR_1, 0.33, 238.75);
+ assertWave(chart, revolutionLog, WaveChart.INJECTOR_2, 0.33, 53.04);
+ assertWave(chart, revolutionLog, WaveChart.INJECTOR_3, 0.33, 417.04);
+ assertWave(chart, revolutionLog, WaveChart.INJECTOR_4, 0.33, 594.04);
+
+ assertWave(chart, revolutionLog, WaveChart.SPARK_1, 0.41, 53.05, 238.75, 417.72, 594.84);
+ }
+
+ private static void assertWave(WaveChart chart, RevolutionLog revolutionLog, String key, double width, double... expectedAngles) {
+ StringBuilder events = chart.get(key);
+ assertTrue("Events not null for " + key, events != null);
+ List wr = WaveReport.parse(events.toString());
+ assertTrue("waves for " + key, !wr.isEmpty());
+ for (WaveReport.UpDown ud : wr) {
+ double angleByTime = revolutionLog.getCrankAngleByTime(ud.upTime);
+ assertCloseEnough("angle for " + key, angleByTime, expectedAngles);
+
+ assertCloseEnough("width for " + key, ud.getDutyCycle(revolutionLog), width);
+ }
+ }
+
+ private static void changeRpm(final int rpm) throws InterruptedException {
+ sendCommand("rpm " + rpm);
+
+ final CountDownLatch rpmLatch = new CountDownLatch(1);
+ SensorCentral.AdcListener listener = new SensorCentral.AdcListener() {
+ @Override
+ public void onAdcUpdate(SensorCentral model, double value) {
+ double actualRpm = SensorCentral.getInstance().getValue(Sensor.RPM);
+ if (isCloseEnough(rpm, actualRpm))
+ rpmLatch.countDown();
+ }
+ };
+ SensorCentral.getInstance().addListener(Sensor.RPM, listener);
+ rpmLatch.await(5, TimeUnit.SECONDS);
+ SensorCentral.getInstance().removeListener(Sensor.RPM, listener);
+
+ double actualRpm = SensorCentral.getInstance().getValue(Sensor.RPM);
+
+ if (!isCloseEnough(rpm, actualRpm))
+ throw new IllegalStateException("rpm change did not happen");
+ }
+
+}
diff --git a/java_console/autotest/src/com/rusefi/ExecHelper.java b/java_console/autotest/src/com/rusefi/ExecHelper.java
new file mode 100644
index 0000000000..dcc5315904
--- /dev/null
+++ b/java_console/autotest/src/com/rusefi/ExecHelper.java
@@ -0,0 +1,81 @@
+package com.rusefi;
+
+import com.irnems.FileLog;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * 3/18/14
+ * (c) Andrey Belomutskiy
+ */
+public class ExecHelper {
+ private static final String SIMULATOR_COMMAND = "../win32_functional_tests/build/rusefi_simulator.exe";
+ static Process simulatorProcess;
+
+ private static void runSimulator() {
+ Thread.currentThread().setName("Main simulation");
+
+ try {
+ FileLog.rlog("Binary size: " + new File(SIMULATOR_COMMAND).length());
+
+ FileLog.rlog("Executing " + SIMULATOR_COMMAND);
+ ExecHelper.simulatorProcess = Runtime.getRuntime().exec(SIMULATOR_COMMAND);
+ FileLog.rlog("simulatorProcess: " + ExecHelper.simulatorProcess);
+
+ BufferedReader input =
+ new BufferedReader(new InputStreamReader(ExecHelper.simulatorProcess.getInputStream()));
+ new Thread(createErrorStreamEcho()).start();
+
+ String line;
+ while ((line = input.readLine()) != null) {
+ System.out.println("from console: " + line);
+ FileLog.SIMULATOR_CONSOLE.logLine(line);
+ }
+
+ FileLog.rlog("exitValue: " + simulatorProcess.exitValue());
+
+ System.out.println("end of console");
+ input.close();
+ } catch (Exception err) {
+ throw new IllegalStateException(err);
+ }
+ }
+
+ private static Runnable createErrorStreamEcho() {
+ return new Runnable() {
+ @Override
+ public void run() {
+ BufferedReader err =
+ new BufferedReader(new InputStreamReader(ExecHelper.simulatorProcess.getErrorStream()));
+ String errLine;
+ try {
+ while ((errLine = err.readLine()) != null) {
+ System.out.println("from err: " + errLine);
+ FileLog.SIMULATOR_CONSOLE.logLine(errLine);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ };
+ }
+
+ static void destroy() {
+ if (simulatorProcess != null) {
+ FileLog.rlog("Destroying sub-process...");
+ simulatorProcess.destroy();
+ }
+ }
+
+ public static void startSimulator() {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runSimulator();
+ }
+ }, "simulator process").start();
+ }
+}
diff --git a/java_console/autotest/src/com/rusefi/IoUtil.java b/java_console/autotest/src/com/rusefi/IoUtil.java
new file mode 100644
index 0000000000..7e499329bf
--- /dev/null
+++ b/java_console/autotest/src/com/rusefi/IoUtil.java
@@ -0,0 +1,51 @@
+package com.rusefi;
+
+import com.irnems.core.EngineState;
+import com.rusefi.io.CommandQueue;
+import com.rusefi.io.InvocationConfirmationListener;
+import com.rusefi.io.LinkManager;
+import com.rusefi.waves.WaveReport;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author Andrey Belomutskiy
+ * 3/19/14.
+ */
+public class IoUtil {
+ static void sendCommand(String command) throws InterruptedException {
+ final CountDownLatch responseLatch = new CountDownLatch(1);
+ CommandQueue.getInstance().write(command, CommandQueue.DEFAULT_TIMEOUT, new InvocationConfirmationListener() {
+ @Override
+ public void onCommandConfirmation() {
+ responseLatch.countDown();
+ }
+ });
+ responseLatch.await(20, TimeUnit.SECONDS);
+ }
+
+ static String getNextWaveChart() throws InterruptedException {
+ getWaveChart();
+ // we want to wait for the 2nd chart to see same same RPM across the whole chart
+ return getWaveChart();
+ }
+
+ private static String getWaveChart() throws InterruptedException {
+ final CountDownLatch waveChartLatch = new CountDownLatch(1);
+
+ final AtomicReference result = new AtomicReference();
+
+ LinkManager.engineState.registerStringValueAction(WaveReport.WAVE_CHART, new EngineState.ValueCallback() {
+ @Override
+ public void onUpdate(String value) {
+ waveChartLatch.countDown();
+ result.set(value);
+ }
+ });
+ waveChartLatch.await(5, TimeUnit.SECONDS);
+ LinkManager.engineState.removeAction(WaveReport.WAVE_CHART);
+ return result.get();
+ }
+}
diff --git a/java_console/autotest/src/com/rusefi/TestingUtils.java b/java_console/autotest/src/com/rusefi/TestingUtils.java
new file mode 100644
index 0000000000..bad0f0977e
--- /dev/null
+++ b/java_console/autotest/src/com/rusefi/TestingUtils.java
@@ -0,0 +1,29 @@
+package com.rusefi;
+
+import java.util.Arrays;
+
+import static com.rusefi.waves.WaveReport.isCloseEnough;
+
+/**
+ * @author Andrey Belomutskiy
+ * 3/19/14.
+ */
+public class TestingUtils {
+ static void assertTrue(String msg, boolean b) {
+ if (!b)
+ throw new IllegalStateException("Not true: " + msg);
+ }
+
+ static void assertCloseEnough(String msg, double current, double... expectations) {
+ for (double expected : expectations) {
+ if (isCloseEnough(expected, current))
+ return;
+ }
+ throw new IllegalStateException(msg + ": Got " + current + " while expecting " + Arrays.toString(expectations));
+ }
+
+ static void assertTrue(boolean b) {
+ if (!b)
+ throw new IllegalStateException("Not true");
+ }
+}
diff --git a/java_console/build.xml b/java_console/build.xml
new file mode 100644
index 0000000000..89bd64d030
--- /dev/null
+++ b/java_console/build.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/java_console/io/io.iml b/java_console/io/io.iml
new file mode 100644
index 0000000000..a498a0b204
--- /dev/null
+++ b/java_console/io/io.iml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/io/src/com/irnems/FileLog.java b/java_console/io/src/com/irnems/FileLog.java
new file mode 100644
index 0000000000..697789a13a
--- /dev/null
+++ b/java_console/io/src/com/irnems/FileLog.java
@@ -0,0 +1,85 @@
+package com.irnems;
+
+import com.rusefi.io.LinkManager;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 6/30/13
+ * (c) Andrey Belomutskiy
+ */
+public enum FileLog {
+ MAIN,
+ SIMULATOR_CONSOLE;
+
+ private static final String DIR = "out/";
+
+ @Nullable
+ private OutputStream fileLog; // null if not opened yet or already closed
+
+ private FileLog() {
+ }
+
+ public void start() {
+ try {
+ fileLog = openLog();
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private FileOutputStream openLog() throws FileNotFoundException {
+ if (LinkManager.onlyUI)
+ return null;
+ String date = getDate();
+ createFolderIfNeeded();
+ String fileName = DIR + name() + "_rfi_report_" + date + ".csv";
+ rlog("Writing to " + fileName);
+ return new FileOutputStream(fileName, true);
+ }
+
+ private static void createFolderIfNeeded() {
+ File dir = new File(DIR);
+ if (dir.exists())
+ return;
+ boolean created = dir.mkdirs();
+ if (!created)
+ throw new IllegalStateException("Failed to create " + DIR + " folder");
+ }
+
+ public static String getDate() {
+ return new SimpleDateFormat("yyyy-MM-dd HH_mm").format(new Date());
+ }
+
+ public synchronized void logLine(String fullLine) {
+ if (fileLog == null)
+ return;
+ try {
+ fileLog.write((fullLine + "\r\n").getBytes());
+ fileLog.flush();
+ System.out.println(fullLine);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ }
+
+ public synchronized void close() {
+ if (fileLog == null)
+ return; // already closed
+ try {
+ rlog("Closing file...");
+ fileLog.close();
+ fileLog = null;
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static void rlog(String msg) {
+ System.out.println("r " + msg);
+ }
+}
\ No newline at end of file
diff --git a/java_console/io/src/com/irnems/file/BaseMap.java b/java_console/io/src/com/irnems/file/BaseMap.java
new file mode 100644
index 0000000000..6a66e7f5ca
--- /dev/null
+++ b/java_console/io/src/com/irnems/file/BaseMap.java
@@ -0,0 +1,62 @@
+package com.irnems.file;
+
+import com.irnems.FileLog;
+import com.irnems.core.EngineState;
+import com.irnems.models.Point3D;
+import com.irnems.models.XYData;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 7/18/13
+ * (c) Andrey Belomutskiy
+ */
+public class BaseMap {
+ public static void main(String[] args) {
+ loadData("a.csv", "maf", "af");
+ }
+
+ public static XYData loadData(String filename, final String key, final String value) {
+ FileLog.rlog("Loading from " + filename);
+ FileLog.rlog("Loading " + key + ">" + value);
+ final XYData data = new XYData();
+
+ EngineState.EngineStateListener listener = new EngineState.EngineStateListenerImpl() {
+ Map values = new HashMap();
+
+ @Override
+ public void onKeyValue(String key, String value) {
+ values.put(key, value);
+ }
+
+ @Override
+ public void afterLine(String fullLine) {
+ if (values.containsKey("rpm") && values.containsKey(key)) {
+ process(values);
+ }
+ values.clear();
+ }
+
+ private void process(Map values) {
+ int rpm = (int) Double.parseDouble(values.get("rpm"));
+ double k = Double.parseDouble(values.get(key));
+ String valueString = values.get(value);
+ if (valueString == null)
+ throw new NullPointerException("No value for: " + value);
+ float v = Float.parseFloat(valueString);
+
+ data.addPoint(new Point3D(rpm, k, v));
+ }
+ };
+ EngineState engineState = new EngineState(listener);
+ engineState.registerStringValueAction("wave_chart", new EngineState.ValueCallback() {
+ @Override
+ public void onUpdate(String value) {
+ }
+ });
+ FileUtils.readFile2(filename, engineState);
+ //return AverageData.average(data, 8);
+ return data;
+ }
+}
diff --git a/java_console/io/src/com/irnems/file/FileUtils.java b/java_console/io/src/com/irnems/file/FileUtils.java
new file mode 100644
index 0000000000..dd57155c6f
--- /dev/null
+++ b/java_console/io/src/com/irnems/file/FileUtils.java
@@ -0,0 +1,45 @@
+package com.irnems.file;
+
+import com.irnems.core.EngineState;
+
+import java.io.*;
+import java.util.List;
+
+/**
+ * Date: 3/8/13
+ * (c) Andrey Belomutskiy
+ */
+public class FileUtils {
+ static void readFile(String filename, EngineState.EngineStateListener listener) {
+ readFile2(filename, new EngineState(listener));
+ }
+
+ public static void readFile2(String filename, EngineState engineState) {
+
+ BufferedReader reader;
+ try {
+ reader = new BufferedReader(new FileReader(filename));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ String packed = EngineState.packString(line);
+ engineState.append(packed + "\r\n");
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ public static void saveList(String filename, List values) {
+ try {
+ BufferedWriter w = new BufferedWriter(new FileWriter(filename));
+
+
+ for (Double v : values)
+ w.write(v + "\r\n");
+
+ w.close();
+ } catch (IOException e) {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ }
+}
diff --git a/java_console/io/src/com/irnems/file/TableGenerator.java b/java_console/io/src/com/irnems/file/TableGenerator.java
new file mode 100644
index 0000000000..914dc97ef6
--- /dev/null
+++ b/java_console/io/src/com/irnems/file/TableGenerator.java
@@ -0,0 +1,91 @@
+package com.irnems.file;
+
+import com.irnems.models.XYData;
+import com.irnems.models.XYDataReader;
+
+import java.io.BufferedWriter;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author Andrey Belomutskiy
+ * 3/27/13
+ */
+public class TableGenerator {
+ public static void main(String[] args) throws IOException {
+
+ XYData data = XYDataReader.readFile("in.csv");
+
+ writeAsC(data, "ad_", "advance_map.c");
+ }
+
+ public static void writeAsC(XYData data, String prefix, String fileName) {
+ try {
+ doWrite(data, prefix, fileName);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static void doWrite(XYData data, String prefix, String fileName) throws IOException {
+ List rpms = new ArrayList(data.getXSet());
+ BufferedWriter out = new BufferedWriter(new FileWriter(fileName));
+ out.write("#include \"thermistors.h\"\n\n\n");
+
+ writeArray(rpms, out, prefix + "rpm");
+
+ Set mafs = data.getYAxis(rpms.get(0)).getYs();
+ ArrayList yArray = new ArrayList(mafs);
+ writeArray(yArray, out, prefix + "maf");
+
+ out.write("static float " + prefix + "table[" + rpms.size() + "][" + mafs.size() + "] = {\n");
+
+ boolean isFirstX = true;
+ int indexX = 0;
+ for (double x : data.getXSet()) {
+ if (!isFirstX)
+ out.write(",\n");
+ isFirstX = false;
+
+ out.write("{");
+
+ int indexY = 0;
+ for (double y : mafs) {
+ if (indexY == 0)
+ out.write("/*" + indexX + " rpm=" + rpms.get(indexX) + "*/");
+
+ if (indexY > 0)
+ out.write(", ");
+
+ out.write("/*" + indexY + " " + yArray.get(indexY) + "*/" + data.getValue(x, y));
+ indexY++;
+ }
+ out.write("}");
+ indexX++;
+ }
+ out.write("\n};\n");
+
+ out.close();
+ }
+
+ private static void writeArray(List rpms, BufferedWriter out, String title) throws IOException {
+ out.write("#define " + title.toUpperCase() + "_COUNT " + rpms.size() + "\n");
+
+ outputDoubles(rpms, out, title);
+ }
+
+ private static void outputDoubles(List values, BufferedWriter out, String title) throws IOException {
+ out.write("static float " + title + "_table[] = {");
+
+ for (int i = 0; i < values.size(); i++) {
+ if (i > 0)
+ out.write(", ");
+ out.write("/*" + i + "*/ " + values.get(i));
+
+ }
+ out.write("};\n\n");
+ }
+}
diff --git a/java_console/io/src/com/rusefi/io/CommandQueue.java b/java_console/io/src/com/rusefi/io/CommandQueue.java
new file mode 100644
index 0000000000..860eac1acf
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/CommandQueue.java
@@ -0,0 +1,147 @@
+package com.rusefi.io;
+
+import com.irnems.core.MessagesCentral;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * This class keeps re-sending a command till a proper confirmation is received
+ *
+ * Date: 1/7/13
+ * (c) Andrey Belomutskiy
+ */
+@SuppressWarnings("FieldCanBeLocal")
+public class CommandQueue {
+ private static final String CONFIRMATION_PREFIX = "confirmation_";
+ public static final int DEFAULT_TIMEOUT = 300;
+ private final Object lock = new Object();
+ private String latestConfirmation;
+
+ private static final CommandQueue instance = new CommandQueue();
+ private final BlockingQueue pendingCommands = new LinkedBlockingQueue();
+
+ private final Runnable runnable = new Runnable() {
+ @SuppressWarnings("InfiniteLoopStatement")
+ @Override
+ public void run() {
+ MessagesCentral.getInstance().postMessage(CommandQueue.class, "SerialIO started");
+ while (true) {
+ try {
+ sendPendingCommand();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+ };
+
+ /**
+ * this method is always invoked on 'Commands Queue' thread {@link #runnable}
+ *
+ * @throws InterruptedException
+ */
+ private void sendPendingCommand() throws InterruptedException {
+ /**
+ * here we block in case there is no command to send
+ */
+ @NotNull
+ final MethodInvocation command = pendingCommands.take();
+ // got a command? let's send it!
+ sendCommand(command);
+ }
+
+ /**
+ * this method keeps retrying till a confirmation is received
+ */
+ private void sendCommand(final MethodInvocation pair) throws InterruptedException {
+ int counter = 0;
+ latestConfirmation = null;
+ String command = pair.getText();
+
+ while (!command.equals(latestConfirmation)) {
+ counter++;
+ LinkManager.send(command);
+ synchronized (lock) {
+ lock.wait(pair.getTimeout());
+ }
+ }
+ if (command.equals(latestConfirmation))
+ pair.listener.onCommandConfirmation();
+
+ if (counter != 1)
+ MessagesCentral.getInstance().postMessage(CommandQueue.class, "Took " + counter + " attempts");
+ }
+
+ private CommandQueue() {
+ new Thread(runnable, "Commands Queue").start();
+ final MessagesCentral mc = MessagesCentral.getInstance();
+ mc.addListener(new MessagesCentral.MessageListener() {
+ @Override
+ public void onMessage(Class clazz, String message) {
+ if (message.startsWith(CONFIRMATION_PREFIX))
+ handleConfirmationMessage(message, mc);
+ }
+ });
+ }
+
+ private void handleConfirmationMessage(String message, MessagesCentral mc) {
+ String confirmation = message.substring(CONFIRMATION_PREFIX.length());
+ int index = confirmation.indexOf(":");
+ if (index < 0) {
+ mc.postMessage(CommandQueue.class, "Broken confirmation: " + confirmation);
+ return;
+ }
+ int length = Integer.parseInt(confirmation.substring(index + 1));
+ if (length != index) {
+ mc.postMessage(CommandQueue.class, "Broken confirmation length: " + confirmation);
+ return;
+ }
+ latestConfirmation = confirmation.substring(0, length);
+ mc.postMessage(CommandQueue.class, "got valid conf! " + latestConfirmation);
+ synchronized (lock) {
+ lock.notifyAll();
+ }
+ }
+
+ public static CommandQueue getInstance() {
+ return instance;
+ }
+
+ public void write(String command) {
+ write(command, DEFAULT_TIMEOUT);
+ }
+
+ public void write(String command, int timeout) {
+ write(command, timeout, InvocationConfirmationListener.VOID);
+ }
+
+ /**
+ * Non-blocking command request
+ */
+ public void write(String command, int timeout, InvocationConfirmationListener listener) {
+ pendingCommands.add(new MethodInvocation(command, timeout, listener));
+ }
+
+ static class MethodInvocation {
+ private final String text;
+ private final int timeout;
+ private final InvocationConfirmationListener listener;
+
+ MethodInvocation(String text, int timeout, InvocationConfirmationListener listener) {
+ this.text = text;
+ this.timeout = timeout;
+ this.listener = listener;
+ }
+
+ public String getText() {
+ return text;
+ }
+
+ public int getTimeout() {
+ return timeout;
+ }
+ }
+}
diff --git a/java_console/io/src/com/rusefi/io/DataListener.java b/java_console/io/src/com/rusefi/io/DataListener.java
new file mode 100644
index 0000000000..524831c7e4
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/DataListener.java
@@ -0,0 +1,9 @@
+package com.rusefi.io;
+
+/**
+* @author Andrey Belomutskiy
+* 3/3/14
+*/
+public interface DataListener {
+ void onStringArrived(String string);
+}
diff --git a/java_console/io/src/com/rusefi/io/InvocationConfirmationListener.java b/java_console/io/src/com/rusefi/io/InvocationConfirmationListener.java
new file mode 100644
index 0000000000..3d6b35108d
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/InvocationConfirmationListener.java
@@ -0,0 +1,15 @@
+package com.rusefi.io;
+
+/**
+ * 3/8/14
+ * (c) Andrey Belomutskiy
+ */
+public interface InvocationConfirmationListener {
+ InvocationConfirmationListener VOID = new InvocationConfirmationListener() {
+ @Override
+ public void onCommandConfirmation() {
+ }
+ };
+
+ void onCommandConfirmation();
+}
diff --git a/java_console/io/src/com/rusefi/io/LinkConnector.java b/java_console/io/src/com/rusefi/io/LinkConnector.java
new file mode 100644
index 0000000000..60954c9939
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/LinkConnector.java
@@ -0,0 +1,13 @@
+package com.rusefi.io;
+
+/**
+ * @author Andrey Belomutskiy
+ * 3/3/14
+ */
+public interface LinkConnector {
+ void connect();
+
+ void send(String command) throws InterruptedException;
+
+ void restart();
+}
diff --git a/java_console/io/src/com/rusefi/io/LinkManager.java b/java_console/io/src/com/rusefi/io/LinkManager.java
new file mode 100644
index 0000000000..61c4044388
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/LinkManager.java
@@ -0,0 +1,67 @@
+package com.rusefi.io;
+
+import com.irnems.FileLog;
+import com.irnems.core.EngineState;
+import com.rusefi.io.serial.SerialConnector;
+import com.rusefi.io.tcp.TcpConnector;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+/**
+ * @author Andrey Belomutskiy
+ * 3/3/14
+ */
+public class LinkManager {
+ public final static Executor IO_EXECUTOR = Executors.newSingleThreadExecutor(new ThreadFactory() {
+ @Override
+ public Thread newThread(Runnable r) {
+ Thread t = new Thread(r);
+ t.setName("IO executor thread");
+ t.setDaemon(true); // need daemon thread so that COM thread is also daemon
+ return t;
+ }
+ });
+ public static EngineState engineState = new EngineState(new EngineState.EngineStateListenerImpl() {
+ @Override
+ public void beforeLine(String fullLine) {
+ FileLog.rlog("SerialManager.beforeLine: " + fullLine);
+ FileLog.MAIN.logLine(fullLine);
+ }
+ });
+ public static boolean onlyUI = false;
+ private static LinkConnector connector;
+
+ public static void start(String port) {
+ if (TcpConnector.isTcpPort(port)) {
+ connector = new TcpConnector(port);
+ } else {
+ connector = new SerialConnector(port);
+ }
+ }
+
+ public static void open() {
+ if (connector == null)
+ throw new NullPointerException("connector");
+ connector.connect();
+ }
+
+ public static void stop() {
+// connector.stop();
+ }
+
+ public static void send(String command) throws InterruptedException {
+ if (connector == null)
+ throw new NullPointerException("connector");
+ connector.send(encodeCommand(command));
+ }
+
+ private static String encodeCommand(String command) {
+ return "sec!" + command.length() + "!" + command;
+ }
+
+ public static void restart() {
+ connector.restart();
+ }
+}
diff --git a/java_console/io/src/com/rusefi/io/serial/PortHolder.java b/java_console/io/src/com/rusefi/io/serial/PortHolder.java
new file mode 100644
index 0000000000..dc4bd41728
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/serial/PortHolder.java
@@ -0,0 +1,119 @@
+package com.rusefi.io.serial;
+
+import com.irnems.FileLog;
+import com.irnems.core.EngineState;
+import com.irnems.core.MessagesCentral;
+import com.rusefi.io.CommandQueue;
+import com.rusefi.io.DataListener;
+import jssc.SerialPort;
+import jssc.SerialPortException;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * This class holds the reference to the actual Serial port object
+ *
+ * 7/25/13
+ * (c) Andrey Belomutskiy
+ */
+class PortHolder {
+ // private static final int BAUD_RATE = 8 * 115200;// 921600;
+// private static final int BAUD_RATE = 2 * 115200;
+ private static final int BAUD_RATE = 115200;
+ private static final int SECOND = 1000;
+ private static final int MINUTE = 60 * SECOND;
+ private static PortHolder instance = new PortHolder();
+ private final Object portLock = new Object();
+
+ public static long startedAt = System.currentTimeMillis();
+
+ private PortHolder() {
+ }
+
+ @Nullable
+ private SerialPort serialPort;
+
+ void openPort(String port, final EngineState es) {
+ MessagesCentral.getInstance().postMessage(SerialManager.class, "Opening port: " + port);
+ if (port == null)
+ return;
+ open(port, new DataListener() {
+ public void onStringArrived(String string) {
+ // jTextAreaIn.append(string);
+ es.append(string);
+ }
+ });
+ }
+
+ public boolean open(String port, DataListener listener) {
+ SerialPort serialPort = new SerialPort(port);
+ try {
+ FileLog.rlog("Opening " + port + " @ " + BAUD_RATE);
+ boolean opened = serialPort.openPort();//Open serial port
+ if (!opened)
+ FileLog.rlog("opened: " + opened);
+ serialPort.setParams(BAUD_RATE, 8, 1, 0);//Set params.
+ int mask = SerialPort.MASK_RXCHAR;
+ //Set the prepared mask
+ serialPort.setEventsMask(mask);
+ serialPort.addEventListener(new SerialPortReader(serialPort, listener));
+ } catch (SerialPortException e) {
+ FileLog.rlog("ERROR " + e.getMessage());
+ return false;
+ }
+
+ try {
+ Thread.sleep(200);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+
+ synchronized (portLock) {
+ this.serialPort = serialPort;
+ portLock.notifyAll();
+ }
+ return true;
+ }
+
+ public void close() {
+ synchronized (portLock) {
+ if (serialPort != null) {
+ try {
+ serialPort.closePort();
+ serialPort = null;
+ } catch (SerialPortException e) {
+ FileLog.rlog("Error while closing: " + e);
+ } finally {
+ portLock.notifyAll();
+ }
+ }
+ }
+ }
+
+ /**
+ * this method blocks till a connection is available
+ */
+ public void packAndSend(String command) throws InterruptedException {
+ FileLog.rlog("Sending [" + command + "]");
+ MessagesCentral.getInstance().postMessage(CommandQueue.class, "Sending [" + command + "]");
+
+ long now = System.currentTimeMillis();
+
+ synchronized (portLock) {
+ while (serialPort == null) {
+ if (System.currentTimeMillis() - now > 3 * MINUTE)
+ MessagesCentral.getInstance().postMessage(PortHolder.class, "Looks like connection is gone :(");
+ portLock.wait(MINUTE);
+ }
+ // we are here only when serialPort!=null, that means we have a connection
+ try {
+ serialPort.writeString(command + "\r\n");
+ } catch (SerialPortException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ public static PortHolder getInstance() {
+ return instance;
+ }
+}
diff --git a/java_console/io/src/com/rusefi/io/serial/SerialConnector.java b/java_console/io/src/com/rusefi/io/serial/SerialConnector.java
new file mode 100644
index 0000000000..7d5683469a
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/serial/SerialConnector.java
@@ -0,0 +1,28 @@
+package com.rusefi.io.serial;
+
+import com.rusefi.io.LinkConnector;
+
+/**
+ * @author Andrey Belomutskiy
+ * 3/3/14
+ */
+public class SerialConnector implements LinkConnector {
+ public SerialConnector(String serialPort) {
+ SerialManager.port = serialPort;
+ }
+
+ @Override
+ public void connect() {
+ SerialManager.scheduleOpening();
+ }
+
+ @Override
+ public void restart() {
+ SerialManager.restart();
+ }
+
+ @Override
+ public void send(String command) throws InterruptedException {
+ PortHolder.getInstance().packAndSend(command);
+ }
+}
diff --git a/java_console/io/src/com/rusefi/io/serial/SerialManager.java b/java_console/io/src/com/rusefi/io/serial/SerialManager.java
new file mode 100644
index 0000000000..e74c61b906
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/serial/SerialManager.java
@@ -0,0 +1,74 @@
+package com.rusefi.io.serial;
+
+import com.irnems.FileLog;
+import com.irnems.core.MessagesCentral;
+import com.rusefi.io.LinkManager;
+
+/**
+ * 7/9/13
+ * (c) Andrey Belomutskiy
+ */
+class SerialManager {
+ public static String port;
+
+ private static boolean closed;
+
+ public static void scheduleOpening() {
+ FileLog.rlog("scheduleOpening");
+ LinkManager.IO_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ FileLog.rlog("scheduleOpening>openPort");
+ PortHolder.getInstance().openPort(port, LinkManager.engineState);
+ }
+ });
+ }
+
+ public static void restart() {
+ LinkManager.IO_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ MessagesCentral.getInstance().postMessage(SerialManager.class, "Restarting serial IO");
+ if (closed)
+ return;
+ PortHolder.getInstance().close();
+ PortHolder.getInstance().openPort(port, LinkManager.engineState);
+ }
+ });
+ }
+/*
+ static String[] findSerialPorts() {
+ List result = new ArrayList();
+
+ Enumeration portEnum = CommPortIdentifier.getPortIdentifiers();
+ while (portEnum.hasMoreElements()) {
+ CommPortIdentifier portIdentifier = portEnum.nextElement();
+ System.out.println(portIdentifier.getName() + " - " + getPortTypeName(portIdentifier.getPortType()));
+ if (portIdentifier.getPortType() == CommPortIdentifier.PORT_SERIAL)
+ result.add(portIdentifier.getName());
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
+ static String getPortTypeName(int portType) {
+ switch (portType) {
+ case CommPortIdentifier.PORT_I2C:
+ return "I2C";
+ case CommPortIdentifier.PORT_PARALLEL:
+ return "Parallel";
+ case CommPortIdentifier.PORT_RAW:
+ return "Raw";
+ case CommPortIdentifier.PORT_RS485:
+ return "RS485";
+ case CommPortIdentifier.PORT_SERIAL:
+ return "Serial";
+ default:
+ return "unknown type";
+ }
+ }
+ */
+ // public static void close() {
+// closed = true;
+// SerialIO.getInstance().stop();
+// }
+}
diff --git a/java_console/io/src/com/rusefi/io/serial/SerialPortReader.java b/java_console/io/src/com/rusefi/io/serial/SerialPortReader.java
new file mode 100644
index 0000000000..a8017ea81e
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/serial/SerialPortReader.java
@@ -0,0 +1,44 @@
+package com.rusefi.io.serial;
+
+import com.irnems.FileLog;
+import com.rusefi.io.DataListener;
+import jssc.SerialPort;
+import jssc.SerialPortEvent;
+import jssc.SerialPortEventListener;
+import jssc.SerialPortException;
+
+/**
+ * Date: 12/25/12
+ * (c) Andrey Belomutskiy
+ */
+class SerialPortReader implements SerialPortEventListener {
+ private SerialPort serialPort;
+ private DataListener listener;
+
+ public SerialPortReader(SerialPort serialPort, DataListener listener) {
+ this.serialPort = serialPort;
+ this.listener = listener;
+ }
+
+ public void serialEvent(SerialPortEvent spe) {
+ if (spe.isRXCHAR() || spe.isRXFLAG()) {
+ try {
+ handleRx(spe);
+ } catch (SerialPortException e) {
+ e.printStackTrace(System.err);
+ }
+ } else {
+ FileLog.rlog("SerialPortReader serialEvent " + spe);
+ }
+ }
+
+ private void handleRx(SerialPortEvent spe) throws SerialPortException {
+ if (spe.getEventValue() > 0) {
+ byte[] buffer = serialPort.readBytes(spe.getEventValue());
+ String str = new String(buffer);
+ listener.onStringArrived(str);
+ // System.out.println("arrived [" + str + "]");
+ }
+ }
+
+}
diff --git a/java_console/io/src/com/rusefi/io/tcp/TcpConnector.java b/java_console/io/src/com/rusefi/io/tcp/TcpConnector.java
new file mode 100644
index 0000000000..fd4bea3bf7
--- /dev/null
+++ b/java_console/io/src/com/rusefi/io/tcp/TcpConnector.java
@@ -0,0 +1,102 @@
+package com.rusefi.io.tcp;
+
+import com.irnems.FileLog;
+import com.rusefi.io.LinkConnector;
+import com.rusefi.io.LinkManager;
+
+import java.io.*;
+import java.net.Socket;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * @author Andrey Belomutskiy
+ * 3/3/14
+ */
+public class TcpConnector implements LinkConnector {
+ public final static int DEFAULT_PORT = 29001;
+ public static final String LOCALHOST = "localhost";
+ private final int port;
+ private BufferedWriter writer;
+
+ public TcpConnector(String port) {
+ this.port = getTcpPort(port);
+ }
+
+ public static boolean isTcpPort(String port) {
+ try {
+ getTcpPort(port);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ public static int getTcpPort(String port) {
+ return Integer.parseInt(port);
+ }
+
+ /**
+ * this implementation is blocking
+ */
+ @Override
+ public void connect() {
+ FileLog.rlog("Connecting to " + port);
+ BufferedInputStream stream;
+ try {
+ Socket socket = new Socket(LOCALHOST, port);
+ writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
+ stream = new BufferedInputStream(socket.getInputStream());
+ } catch (IOException e) {
+ throw new IllegalStateException("Failed to connect to simulator", e);
+ }
+
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+
+ LinkManager.IO_EXECUTOR.execute(new Runnable() {
+ @Override
+ public void run() {
+ Thread.currentThread().setName("TCP connector loop");
+ FileLog.rlog("Running TCP connection loop");
+ while (true) {
+ try {
+ String line = reader.readLine();
+ LinkManager.engineState.append(line + "\r\n");
+ } catch (IOException e) {
+ System.err.println("End of connection");
+ return;
+ }
+ }
+ }
+ });
+
+ }
+
+ @Override
+ public void restart() {
+// FileLog.rlog("Restarting on " + port);
+ }
+
+ @Override
+ public void send(String command) throws InterruptedException {
+ FileLog.rlog("Writing " + command);
+ try {
+ writer.write(command + "\r\n");
+ writer.flush();
+ } catch (IOException e) {
+ System.err.println("err in send");
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ }
+
+ public static Collection extends String> getAvailablePorts() {
+ try {
+ Socket s = new Socket(LOCALHOST, DEFAULT_PORT);
+ s.close();
+ return Collections.singletonList("" + DEFAULT_PORT);
+ } catch (IOException e) {
+ System.out.println("Connection refused in getAvailablePorts(): simulator not running");
+ return Collections.emptyList();
+ }
+ }
+}
diff --git a/java_console/lib/SteelSeries-3.9.30.jar b/java_console/lib/SteelSeries-3.9.30.jar
new file mode 100644
index 0000000000..db9639043f
Binary files /dev/null and b/java_console/lib/SteelSeries-3.9.30.jar differ
diff --git a/java_console/lib/SteelSeries-Swing-master.zip b/java_console/lib/SteelSeries-Swing-master.zip
new file mode 100644
index 0000000000..fb2e8736d8
Binary files /dev/null and b/java_console/lib/SteelSeries-Swing-master.zip differ
diff --git a/java_console/lib/annotations.jar b/java_console/lib/annotations.jar
new file mode 100644
index 0000000000..aed48f45d8
Binary files /dev/null and b/java_console/lib/annotations.jar differ
diff --git a/java_console/lib/batik-src-1.7.zip b/java_console/lib/batik-src-1.7.zip
new file mode 100644
index 0000000000..2ad443d997
Binary files /dev/null and b/java_console/lib/batik-src-1.7.zip differ
diff --git a/java_console/lib/batik/LICENSE.dom-documentation.txt b/java_console/lib/batik/LICENSE.dom-documentation.txt
new file mode 100644
index 0000000000..966651b390
--- /dev/null
+++ b/java_console/lib/batik/LICENSE.dom-documentation.txt
@@ -0,0 +1,86 @@
+xml-commons/java/external/LICENSE.dom-documentation.txt $Id: LICENSE.dom-documentation.txt 201084 2002-12-09 16:15:21Z vhardy $
+
+
+This license came from: http://www.w3.org/Consortium/Legal/copyright-documents-19990405
+
+
+W3C® DOCUMENT NOTICE AND LICENSE
+Copyright © 1994-2001 World
+Wide Web Consortium, World
+Wide Web Consortium, (Massachusetts Institute of
+Technology, Institut National de
+Recherche en Informatique et en Automatique, Keio University). All Rights Reserved.
+http://www.w3.org/Consortium/Legal/
+
+Public documents on the W3C site are provided by the copyright
+holders under the following license. The software or Document Type
+Definitions (DTDs) associated with W3C specifications are governed
+by the Software Notice. By using and/or copying this document, or the
+W3C document from which this statement is linked, you (the
+licensee) agree that you have read, understood, and will comply
+with the following terms and conditions:
+
+Permission to use, copy, and distribute the contents of this
+document, or the W3C document from which this statement is linked,
+in any medium for any purpose and without fee or royalty is hereby
+granted, provided that you include the following on ALL
+copies of the document, or portions thereof, that you use:
+
+A link or URL to the original W3C document.
+
+The pre-existing copyright notice of the original author, or if
+it doesn't exist, a notice of the form: "Copyright © [$date-of-document] World Wide Web
+Consortium, (Massachusetts
+Institute of Technology, Institut National de Recherche en Informatique et en
+Automatique, Keio
+University). All Rights Reserved.
+http://www.w3.org/Consortium/Legal/" (Hypertext is preferred, but a
+textual representation is permitted.)
+
+If it exists, the STATUS of the W3C document.
+
+When space permits, inclusion of the full text of this NOTICE
+should be provided. We request that authorship
+attribution be provided in any software, documents, or other items
+or products that you create pursuant to the implementation of the
+contents of this document, or any portion thereof.
+
+No right to create modifications or derivatives of W3C documents
+is granted pursuant to this license. However, if additional
+requirements (documented in the Copyright
+FAQ) are satisfied, the right to create modifications or
+derivatives is sometimes granted by the W3C to individuals
+complying with those requirements.
+
+THIS DOCUMENT IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO
+REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT
+NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE, NON-INFRINGEMENT, OR TITLE; THAT THE CONTENTS
+OF THE DOCUMENT ARE SUITABLE FOR ANY PURPOSE; NOR THAT THE
+IMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY THIRD PARTY
+PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT,
+SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE
+DOCUMENT OR THE PERFORMANCE OR IMPLEMENTATION OF THE CONTENTS
+THEREOF.
+
+The name and trademarks of copyright holders may NOT be used in
+advertising or publicity pertaining to this document or its
+contents without specific, written prior permission. Title to
+copyright in this document will at all times remain with copyright
+holders.
+
+----------------------------------------------------------------------------
+This formulation of W3C's notice and license became active on
+April 05 1999 so as to account for the treatment of DTDs, schema's and
+bindings. See the older formulation for the policy prior to this date.
+Please see
+our Copyright FAQ for common questions
+about using materials from our site, including specific terms and
+conditions for packages like libwww, Amaya, and Jigsaw.
+Other questions about this notice can be directed to site-policy@w3.org.
+
+webmaster
+(last updated by reagle on 1999/04/99.)
\ No newline at end of file
diff --git a/java_console/lib/batik/LICENSE.dom-software.txt b/java_console/lib/batik/LICENSE.dom-software.txt
new file mode 100644
index 0000000000..2b5bfebb1f
--- /dev/null
+++ b/java_console/lib/batik/LICENSE.dom-software.txt
@@ -0,0 +1,74 @@
+xml-commons/java/external/LICENSE.dom-software.txt $Id: LICENSE.dom-software.txt 201084 2002-12-09 16:15:21Z vhardy $
+
+
+This license came from: http://www.w3.org/Consortium/Legal/copyright-software-19980720
+
+
+W3C® SOFTWARE NOTICE AND LICENSE
+Copyright © 1994-2001 World
+Wide Web Consortium, World
+Wide Web Consortium, (Massachusetts Institute of
+Technology, Institut National de
+Recherche en Informatique et en Automatique, Keio University). All Rights Reserved.
+http://www.w3.org/Consortium/Legal/
+
+This W3C work (including software, documents, or other related
+items) is being provided by the copyright holders under the
+following license. By obtaining, using and/or copying this work,
+you (the licensee) agree that you have read, understood, and will
+comply with the following terms and conditions:
+Permission to use, copy, modify, and distribute this software
+and its documentation, with or without modification, for any
+purpose and without fee or royalty is hereby granted, provided that
+you include the following on ALL copies of the software and
+documentation or portions thereof, including modifications, that
+you make:
+
+The full text of this NOTICE in a location viewable to users of
+the redistributed or derivative work.
+
+Any pre-existing intellectual property disclaimers, notices, or
+terms and conditions. If none exist, a short notice of the
+following form (hypertext is preferred, text is permitted) should
+be used within the body of any redistributed or derivative code:
+"Copyright © [$date-of-software] World Wide Web Consortium, (Massachusetts Institute of
+Technology, Institut National de
+Recherche en Informatique et en Automatique, Keio University). All Rights Reserved.
+http://www.w3.org/Consortium/Legal/"
+
+Notice of any changes or modifications to the W3C files,
+including the date changes were made. (We recommend you provide
+URIs to the location from which the code is derived.)
+
+THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND
+COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF
+MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE
+USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD
+PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.
+COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT,
+SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE
+SOFTWARE OR DOCUMENTATION.
+
+The name and trademarks of copyright holders may NOT be used in
+advertising or publicity pertaining to the software without
+specific, written prior permission. Title to copyright in this
+software and any associated documentation will at all times remain
+with copyright holders.
+____________________________________
+This formulation of W3C's notice and license became active on
+August 14 1998 so as to improve compatibility with GPL. This
+version ensures that W3C software licensing terms are no more
+restrictive than GPL and consequently W3C software may be
+distributed in GPL packages. See the older formulation for the
+policy prior to this date. Please see our Copyright FAQ for common
+questions about using materials from
+our site, including specific terms and conditions for packages like
+libwww, Amaya, and Jigsaw.
+Other questions about this notice can be
+directed to site-policy@w3.org.
+
+webmaster
+(last updated $Date: 2002-12-10 03:15:21 +1100 (Tue, 10 Dec 2002) $)
\ No newline at end of file
diff --git a/java_console/lib/batik/LICENSE.js.txt b/java_console/lib/batik/LICENSE.js.txt
new file mode 100644
index 0000000000..817c87bcb6
--- /dev/null
+++ b/java_console/lib/batik/LICENSE.js.txt
@@ -0,0 +1,890 @@
+This distribution includes a binary distribution of Mozilla Rhino 1.6 release 5
+plus one patch.
+
+You can get the unpatched 1.6R5 release of Rhino from the following URL:
+
+ ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_6R5.zip
+
+To obtain the source code for the 1.6R5 release of Rhino, issue the following
+commands:
+
+ cvs -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot \
+ co -D2006-11-20 mozilla/js/rhino
+
+The patch is available here:
+
+ https://bugzilla.mozilla.org/attachment.cgi?id=288467
+
+which is attached to this bug:
+
+ https://bugzilla.mozilla.org/show_bug.cgi?id=367627
+
+Rhino is licensed under both the MPL (Mozilla Public License) 1.1 and the
+GPL (GNU General Public License) 2.0, which are duplicated below.
+
+The Rhino jar also includes four classes:
+ org.mozilla.javascript.tools.debugger.downloaded.AbstractCellEditor.java
+ org.mozilla.javascript.tools.debugger.downloaded.JTreeTable.java
+ org.mozilla.javascript.tools.debugger.downloaded.TreeTableModel.java
+ org.mozilla.javascript.tools.debugger.downloaded.TreeTableModelAdapter.java
+Which come from:
+ http://java.sun.com/products/jfc/tsc/articles/treetable2
+
+Under the following license:
+
+Code sample
+License
+Copyright 1994-2006 Sun Microsystems, Inc. All Rights Reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+
+ * Redistribution of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistribution in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+
+Neither the name of Sun Microsystems, Inc. or the names of
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+This software is provided "AS IS," without a warranty of any kind. ALL
+EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
+INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
+MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
+ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
+DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
+ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
+DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
+DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
+ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
+SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+You acknowledge that this software is not designed, licensed or
+intended for use in the design, construction, operation or maintenance
+of any nuclear facility.
+
+
+
+==============================================================================
+
+ MOZILLA PUBLIC LICENSE
+ Version 1.1
+
+ ---------------
+
+1. Definitions.
+
+ 1.0.1. "Commercial Use" means distribution or otherwise making the
+ Covered Code available to a third party.
+
+ 1.1. "Contributor" means each entity that creates or contributes to
+ the creation of Modifications.
+
+ 1.2. "Contributor Version" means the combination of the Original
+ Code, prior Modifications used by a Contributor, and the Modifications
+ made by that particular Contributor.
+
+ 1.3. "Covered Code" means the Original Code or Modifications or the
+ combination of the Original Code and Modifications, in each case
+ including portions thereof.
+
+ 1.4. "Electronic Distribution Mechanism" means a mechanism generally
+ accepted in the software development community for the electronic
+ transfer of data.
+
+ 1.5. "Executable" means Covered Code in any form other than Source
+ Code.
+
+ 1.6. "Initial Developer" means the individual or entity identified
+ as the Initial Developer in the Source Code notice required by Exhibit
+ A.
+
+ 1.7. "Larger Work" means a work which combines Covered Code or
+ portions thereof with code not governed by the terms of this License.
+
+ 1.8. "License" means this document.
+
+ 1.8.1. "Licensable" means having the right to grant, to the maximum
+ extent possible, whether at the time of the initial grant or
+ subsequently acquired, any and all of the rights conveyed herein.
+
+ 1.9. "Modifications" means any addition to or deletion from the
+ substance or structure of either the Original Code or any previous
+ Modifications. When Covered Code is released as a series of files, a
+ Modification is:
+ A. Any addition to or deletion from the contents of a file
+ containing Original Code or previous Modifications.
+
+ B. Any new file that contains any part of the Original Code or
+ previous Modifications.
+
+ 1.10. "Original Code" means Source Code of computer software code
+ which is described in the Source Code notice required by Exhibit A as
+ Original Code, and which, at the time of its release under this
+ License is not already Covered Code governed by this License.
+
+ 1.10.1. "Patent Claims" means any patent claim(s), now owned or
+ hereafter acquired, including without limitation, method, process,
+ and apparatus claims, in any patent Licensable by grantor.
+
+ 1.11. "Source Code" means the preferred form of the Covered Code for
+ making modifications to it, including all modules it contains, plus
+ any associated interface definition files, scripts used to control
+ compilation and installation of an Executable, or source code
+ differential comparisons against either the Original Code or another
+ well known, available Covered Code of the Contributor's choice. The
+ Source Code can be in a compressed or archival form, provided the
+ appropriate decompression or de-archiving software is widely available
+ for no charge.
+
+ 1.12. "You" (or "Your") means an individual or a legal entity
+ exercising rights under, and complying with all of the terms of, this
+ License or a future version of this License issued under Section 6.1.
+ For legal entities, "You" includes any entity which controls, is
+ controlled by, or is under common control with You. For purposes of
+ this definition, "control" means (a) the power, direct or indirect,
+ to cause the direction or management of such entity, whether by
+ contract or otherwise, or (b) ownership of more than fifty percent
+ (50%) of the outstanding shares or beneficial ownership of such
+ entity.
+
+2. Source Code License.
+
+ 2.1. The Initial Developer Grant.
+ The Initial Developer hereby grants You a world-wide, royalty-free,
+ non-exclusive license, subject to third party intellectual property
+ claims:
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Initial Developer to use, reproduce,
+ modify, display, perform, sublicense and distribute the Original
+ Code (or portions thereof) with or without Modifications, and/or
+ as part of a Larger Work; and
+
+ (b) under Patents Claims infringed by the making, using or
+ selling of Original Code, to make, have made, use, practice,
+ sell, and offer for sale, and/or otherwise dispose of the
+ Original Code (or portions thereof).
+
+ (c) the licenses granted in this Section 2.1(a) and (b) are
+ effective on the date Initial Developer first distributes
+ Original Code under the terms of this License.
+
+ (d) Notwithstanding Section 2.1(b) above, no patent license is
+ granted: 1) for code that You delete from the Original Code; 2)
+ separate from the Original Code; or 3) for infringements caused
+ by: i) the modification of the Original Code or ii) the
+ combination of the Original Code with other software or devices.
+
+ 2.2. Contributor Grant.
+ Subject to third party intellectual property claims, each Contributor
+ hereby grants You a world-wide, royalty-free, non-exclusive license
+
+ (a) under intellectual property rights (other than patent or
+ trademark) Licensable by Contributor, to use, reproduce, modify,
+ display, perform, sublicense and distribute the Modifications
+ created by such Contributor (or portions thereof) either on an
+ unmodified basis, with other Modifications, as Covered Code
+ and/or as part of a Larger Work; and
+
+ (b) under Patent Claims infringed by the making, using, or
+ selling of Modifications made by that Contributor either alone
+ and/or in combination with its Contributor Version (or portions
+ of such combination), to make, use, sell, offer for sale, have
+ made, and/or otherwise dispose of: 1) Modifications made by that
+ Contributor (or portions thereof); and 2) the combination of
+ Modifications made by that Contributor with its Contributor
+ Version (or portions of such combination).
+
+ (c) the licenses granted in Sections 2.2(a) and 2.2(b) are
+ effective on the date Contributor first makes Commercial Use of
+ the Covered Code.
+
+ (d) Notwithstanding Section 2.2(b) above, no patent license is
+ granted: 1) for any code that Contributor has deleted from the
+ Contributor Version; 2) separate from the Contributor Version;
+ 3) for infringements caused by: i) third party modifications of
+ Contributor Version or ii) the combination of Modifications made
+ by that Contributor with other software (except as part of the
+ Contributor Version) or other devices; or 4) under Patent Claims
+ infringed by Covered Code in the absence of Modifications made by
+ that Contributor.
+
+3. Distribution Obligations.
+
+ 3.1. Application of License.
+ The Modifications which You create or to which You contribute are
+ governed by the terms of this License, including without limitation
+ Section 2.2. The Source Code version of Covered Code may be
+ distributed only under the terms of this License or a future version
+ of this License released under Section 6.1, and You must include a
+ copy of this License with every copy of the Source Code You
+ distribute. You may not offer or impose any terms on any Source Code
+ version that alters or restricts the applicable version of this
+ License or the recipients' rights hereunder. However, You may include
+ an additional document offering the additional rights described in
+ Section 3.5.
+
+ 3.2. Availability of Source Code.
+ Any Modification which You create or to which You contribute must be
+ made available in Source Code form under the terms of this License
+ either on the same media as an Executable version or via an accepted
+ Electronic Distribution Mechanism to anyone to whom you made an
+ Executable version available; and if made available via Electronic
+ Distribution Mechanism, must remain available for at least twelve (12)
+ months after the date it initially became available, or at least six
+ (6) months after a subsequent version of that particular Modification
+ has been made available to such recipients. You are responsible for
+ ensuring that the Source Code version remains available even if the
+ Electronic Distribution Mechanism is maintained by a third party.
+
+ 3.3. Description of Modifications.
+ You must cause all Covered Code to which You contribute to contain a
+ file documenting the changes You made to create that Covered Code and
+ the date of any change. You must include a prominent statement that
+ the Modification is derived, directly or indirectly, from Original
+ Code provided by the Initial Developer and including the name of the
+ Initial Developer in (a) the Source Code, and (b) in any notice in an
+ Executable version or related documentation in which You describe the
+ origin or ownership of the Covered Code.
+
+ 3.4. Intellectual Property Matters
+ (a) Third Party Claims.
+ If Contributor has knowledge that a license under a third party's
+ intellectual property rights is required to exercise the rights
+ granted by such Contributor under Sections 2.1 or 2.2,
+ Contributor must include a text file with the Source Code
+ distribution titled "LEGAL" which describes the claim and the
+ party making the claim in sufficient detail that a recipient will
+ know whom to contact. If Contributor obtains such knowledge after
+ the Modification is made available as described in Section 3.2,
+ Contributor shall promptly modify the LEGAL file in all copies
+ Contributor makes available thereafter and shall take other steps
+ (such as notifying appropriate mailing lists or newsgroups)
+ reasonably calculated to inform those who received the Covered
+ Code that new knowledge has been obtained.
+
+ (b) Contributor APIs.
+ If Contributor's Modifications include an application programming
+ interface and Contributor has knowledge of patent licenses which
+ are reasonably necessary to implement that API, Contributor must
+ also include this information in the LEGAL file.
+
+ (c) Representations.
+ Contributor represents that, except as disclosed pursuant to
+ Section 3.4(a) above, Contributor believes that Contributor's
+ Modifications are Contributor's original creation(s) and/or
+ Contributor has sufficient rights to grant the rights conveyed by
+ this License.
+
+ 3.5. Required Notices.
+ You must duplicate the notice in Exhibit A in each file of the Source
+ Code. If it is not possible to put such notice in a particular Source
+ Code file due to its structure, then You must include such notice in a
+ location (such as a relevant directory) where a user would be likely
+ to look for such a notice. If You created one or more Modification(s)
+ You may add your name as a Contributor to the notice described in
+ Exhibit A. You must also duplicate this License in any documentation
+ for the Source Code where You describe recipients' rights or ownership
+ rights relating to Covered Code. You may choose to offer, and to
+ charge a fee for, warranty, support, indemnity or liability
+ obligations to one or more recipients of Covered Code. However, You
+ may do so only on Your own behalf, and not on behalf of the Initial
+ Developer or any Contributor. You must make it absolutely clear than
+ any such warranty, support, indemnity or liability obligation is
+ offered by You alone, and You hereby agree to indemnify the Initial
+ Developer and every Contributor for any liability incurred by the
+ Initial Developer or such Contributor as a result of warranty,
+ support, indemnity or liability terms You offer.
+
+ 3.6. Distribution of Executable Versions.
+ You may distribute Covered Code in Executable form only if the
+ requirements of Section 3.1-3.5 have been met for that Covered Code,
+ and if You include a notice stating that the Source Code version of
+ the Covered Code is available under the terms of this License,
+ including a description of how and where You have fulfilled the
+ obligations of Section 3.2. The notice must be conspicuously included
+ in any notice in an Executable version, related documentation or
+ collateral in which You describe recipients' rights relating to the
+ Covered Code. You may distribute the Executable version of Covered
+ Code or ownership rights under a license of Your choice, which may
+ contain terms different from this License, provided that You are in
+ compliance with the terms of this License and that the license for the
+ Executable version does not attempt to limit or alter the recipient's
+ rights in the Source Code version from the rights set forth in this
+ License. If You distribute the Executable version under a different
+ license You must make it absolutely clear that any terms which differ
+ from this License are offered by You alone, not by the Initial
+ Developer or any Contributor. You hereby agree to indemnify the
+ Initial Developer and every Contributor for any liability incurred by
+ the Initial Developer or such Contributor as a result of any such
+ terms You offer.
+
+ 3.7. Larger Works.
+ You may create a Larger Work by combining Covered Code with other code
+ not governed by the terms of this License and distribute the Larger
+ Work as a single product. In such a case, You must make sure the
+ requirements of this License are fulfilled for the Covered Code.
+
+4. Inability to Comply Due to Statute or Regulation.
+
+ If it is impossible for You to comply with any of the terms of this
+ License with respect to some or all of the Covered Code due to
+ statute, judicial order, or regulation then You must: (a) comply with
+ the terms of this License to the maximum extent possible; and (b)
+ describe the limitations and the code they affect. Such description
+ must be included in the LEGAL file described in Section 3.4 and must
+ be included with all distributions of the Source Code. Except to the
+ extent prohibited by statute or regulation, such description must be
+ sufficiently detailed for a recipient of ordinary skill to be able to
+ understand it.
+
+5. Application of this License.
+
+ This License applies to code to which the Initial Developer has
+ attached the notice in Exhibit A and to related Covered Code.
+
+6. Versions of the License.
+
+ 6.1. New Versions.
+ Netscape Communications Corporation ("Netscape") may publish revised
+ and/or new versions of the License from time to time. Each version
+ will be given a distinguishing version number.
+
+ 6.2. Effect of New Versions.
+ Once Covered Code has been published under a particular version of the
+ License, You may always continue to use it under the terms of that
+ version. You may also choose to use such Covered Code under the terms
+ of any subsequent version of the License published by Netscape. No one
+ other than Netscape has the right to modify the terms applicable to
+ Covered Code created under this License.
+
+ 6.3. Derivative Works.
+ If You create or use a modified version of this License (which you may
+ only do in order to apply it to code which is not already Covered Code
+ governed by this License), You must (a) rename Your license so that
+ the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape",
+ "MPL", "NPL" or any confusingly similar phrase do not appear in your
+ license (except to note that your license differs from this License)
+ and (b) otherwise make it clear that Your version of the license
+ contains terms which differ from the Mozilla Public License and
+ Netscape Public License. (Filling in the name of the Initial
+ Developer, Original Code or Contributor in the notice described in
+ Exhibit A shall not of themselves be deemed to be modifications of
+ this License.)
+
+7. DISCLAIMER OF WARRANTY.
+
+ COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS,
+ WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING,
+ WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF
+ DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING.
+ THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE
+ IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT,
+ YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE
+ COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER
+ OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF
+ ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER.
+
+8. TERMINATION.
+
+ 8.1. This License and the rights granted hereunder will terminate
+ automatically if You fail to comply with terms herein and fail to cure
+ such breach within 30 days of becoming aware of the breach. All
+ sublicenses to the Covered Code which are properly granted shall
+ survive any termination of this License. Provisions which, by their
+ nature, must remain in effect beyond the termination of this License
+ shall survive.
+
+ 8.2. If You initiate litigation by asserting a patent infringement
+ claim (excluding declatory judgment actions) against Initial Developer
+ or a Contributor (the Initial Developer or Contributor against whom
+ You file such action is referred to as "Participant") alleging that:
+
+ (a) such Participant's Contributor Version directly or indirectly
+ infringes any patent, then any and all rights granted by such
+ Participant to You under Sections 2.1 and/or 2.2 of this License
+ shall, upon 60 days notice from Participant terminate prospectively,
+ unless if within 60 days after receipt of notice You either: (i)
+ agree in writing to pay Participant a mutually agreeable reasonable
+ royalty for Your past and future use of Modifications made by such
+ Participant, or (ii) withdraw Your litigation claim with respect to
+ the Contributor Version against such Participant. If within 60 days
+ of notice, a reasonable royalty and payment arrangement are not
+ mutually agreed upon in writing by the parties or the litigation claim
+ is not withdrawn, the rights granted by Participant to You under
+ Sections 2.1 and/or 2.2 automatically terminate at the expiration of
+ the 60 day notice period specified above.
+
+ (b) any software, hardware, or device, other than such Participant's
+ Contributor Version, directly or indirectly infringes any patent, then
+ any rights granted to You by such Participant under Sections 2.1(b)
+ and 2.2(b) are revoked effective as of the date You first made, used,
+ sold, distributed, or had made, Modifications made by that
+ Participant.
+
+ 8.3. If You assert a patent infringement claim against Participant
+ alleging that such Participant's Contributor Version directly or
+ indirectly infringes any patent where such claim is resolved (such as
+ by license or settlement) prior to the initiation of patent
+ infringement litigation, then the reasonable value of the licenses
+ granted by such Participant under Sections 2.1 or 2.2 shall be taken
+ into account in determining the amount or value of any payment or
+ license.
+
+ 8.4. In the event of termination under Sections 8.1 or 8.2 above,
+ all end user license agreements (excluding distributors and resellers)
+ which have been validly granted by You or any distributor hereunder
+ prior to termination shall survive termination.
+
+9. LIMITATION OF LIABILITY.
+
+ UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT
+ (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL
+ DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE,
+ OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR
+ ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY
+ CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL,
+ WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER
+ COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN
+ INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF
+ LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY
+ RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW
+ PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE
+ EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO
+ THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU.
+
+10. U.S. GOVERNMENT END USERS.
+
+ The Covered Code is a "commercial item," as that term is defined in
+ 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer
+ software" and "commercial computer software documentation," as such
+ terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48
+ C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995),
+ all U.S. Government End Users acquire Covered Code with only those
+ rights set forth herein.
+
+11. MISCELLANEOUS.
+
+ This License represents the complete agreement concerning subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. This License shall be governed by
+ California law provisions (except to the extent applicable law, if
+ any, provides otherwise), excluding its conflict-of-law provisions.
+ With respect to disputes in which at least one party is a citizen of,
+ or an entity chartered or registered to do business in the United
+ States of America, any litigation relating to this License shall be
+ subject to the jurisdiction of the Federal Courts of the Northern
+ District of California, with venue lying in Santa Clara County,
+ California, with the losing party responsible for costs, including
+ without limitation, court costs and reasonable attorneys' fees and
+ expenses. The application of the United Nations Convention on
+ Contracts for the International Sale of Goods is expressly excluded.
+ Any law or regulation which provides that the language of a contract
+ shall be construed against the drafter shall not apply to this
+ License.
+
+12. RESPONSIBILITY FOR CLAIMS.
+
+ As between Initial Developer and the Contributors, each party is
+ responsible for claims and damages arising, directly or indirectly,
+ out of its utilization of rights under this License and You agree to
+ work with Initial Developer and Contributors to distribute such
+ responsibility on an equitable basis. Nothing herein is intended or
+ shall be deemed to constitute any admission of liability.
+
+13. MULTIPLE-LICENSED CODE.
+
+ Initial Developer may designate portions of the Covered Code as
+ "Multiple-Licensed". "Multiple-Licensed" means that the Initial
+ Developer permits you to utilize portions of the Covered Code under
+ Your choice of the NPL or the alternative licenses, if any, specified
+ by the Initial Developer in the file described in Exhibit A.
+
+EXHIBIT A -Mozilla Public License.
+
+ ``The contents of this file are subject to the Mozilla Public License
+ Version 1.1 (the "License"); you may not use this file except in
+ compliance with the License. You may obtain a copy of the License at
+ http://www.mozilla.org/MPL/
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+ License for the specific language governing rights and limitations
+ under the License.
+
+ The Original Code is ______________________________________.
+
+ The Initial Developer of the Original Code is ________________________.
+ Portions created by ______________________ are Copyright (C) ______
+ _______________________. All Rights Reserved.
+
+ Contributor(s): ______________________________________.
+
+ Alternatively, the contents of this file may be used under the terms
+ of the _____ license (the "[___] License"), in which case the
+ provisions of [______] License are applicable instead of those
+ above. If you wish to allow use of your version of this file only
+ under the terms of the [____] License and not to allow others to use
+ your version of this file under the MPL, indicate your decision by
+ deleting the provisions above and replace them with the notice and
+ other provisions required by the [___] License. If you do not delete
+ the provisions above, a recipient may use your version of this file
+ under either the MPL or the [___] License."
+
+ [NOTE: The text of this Exhibit A may differ slightly from the text of
+ the notices in the Source Code files of the Original Code. You should
+ use the text of this Exhibit A rather than the text found in the
+ Original Code Source Code for Your Modifications.]
+
+==============================================================================
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
+
+==============================================================================
diff --git a/java_console/lib/batik/LICENSE.pdf-transcoder.txt b/java_console/lib/batik/LICENSE.pdf-transcoder.txt
new file mode 100644
index 0000000000..588d02cf10
--- /dev/null
+++ b/java_console/lib/batik/LICENSE.pdf-transcoder.txt
@@ -0,0 +1,2 @@
+The pdf-transcoder.jar file is licensed under the Apache License 2.0, which
+can be found in the distribution root directory in the LICENSE file.
diff --git a/java_console/lib/batik/LICENSE.sax.txt b/java_console/lib/batik/LICENSE.sax.txt
new file mode 100644
index 0000000000..739cb51a3b
--- /dev/null
+++ b/java_console/lib/batik/LICENSE.sax.txt
@@ -0,0 +1,23 @@
+xml-commons/java/external/LICENSE.sax.txt $Id: LICENSE.sax.txt 201084 2002-12-09 16:15:21Z vhardy $
+
+
+This license came from: http://www.megginson.com/SAX/copying.html
+ However please note future versions of SAX may be covered
+ under http://saxproject.org/?selected=pd
+
+
+This page is now out of date -- see the new SAX site at
+http://www.saxproject.org/ for more up-to-date
+releases and other information. Please change your bookmarks.
+
+
+SAX2 is Free!
+
+I hereby abandon any property rights to SAX 2.0 (the Simple API for
+XML), and release all of the SAX 2.0 source code, compiled code, and
+documentation contained in this distribution into the Public Domain.
+SAX comes with NO WARRANTY or guarantee of fitness for any
+purpose.
+
+David Megginson, david@megginson.com
+2000-05-05
\ No newline at end of file
diff --git a/java_console/lib/batik/LICENSE.xalan-2.6.0.txt b/java_console/lib/batik/LICENSE.xalan-2.6.0.txt
new file mode 100644
index 0000000000..6c5ba27951
--- /dev/null
+++ b/java_console/lib/batik/LICENSE.xalan-2.6.0.txt
@@ -0,0 +1,2 @@
+The xalan-2.6.0.jar file is licensed under the Apache License 2.0, which
+can be found in the distribution root directory in the LICENSE file.
diff --git a/java_console/lib/batik/LICENSE.xerces_2_5_0.txt b/java_console/lib/batik/LICENSE.xerces_2_5_0.txt
new file mode 100644
index 0000000000..b6d4a8bc9d
--- /dev/null
+++ b/java_console/lib/batik/LICENSE.xerces_2_5_0.txt
@@ -0,0 +1,60 @@
+The xerces_2_5_0.jar file comes from the Apache Xerces project
+(http://xmlapache.org/dist/xerces-j/), and is licensed under the
+Apache Software License, Version 1.1, which is reproduced below.
+
+/*
+ * The Apache Software License, Version 1.1
+ *
+ *
+ * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution,
+ * if any, must include the following acknowledgment:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ *
+ * 4. The names "Xerces" and "Apache Software Foundation" must
+ * not be used to endorse or promote products derived from this
+ * software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache",
+ * nor may "Apache" appear in their name, without prior written
+ * permission of the Apache Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation and was
+ * originally based on software copyright (c) 1999, International
+ * Business Machines, Inc., http://www.ibm.com. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ */
diff --git a/java_console/lib/batik/README.js.txt b/java_console/lib/batik/README.js.txt
new file mode 100644
index 0000000000..72df38f49d
--- /dev/null
+++ b/java_console/lib/batik/README.js.txt
@@ -0,0 +1,23 @@
+This distribution includes a binary distribution of Mozilla Rhino 1.6 release 5
+plus one patch.
+
+You can get the unpatched 1.6R5 release of Rhino from the following URL:
+
+ ftp://ftp.mozilla.org/pub/mozilla.org/js/rhino1_6R5.zip
+
+To obtain the source code for the 1.6R5 release of Rhino, issue the following
+commands:
+
+ cvs -d :pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot \
+ co -D2006-11-20 mozilla/js/rhino
+
+The patch is available here:
+
+ https://bugzilla.mozilla.org/attachment.cgi?id=288467
+
+which is attached to this bug:
+
+ https://bugzilla.mozilla.org/show_bug.cgi?id=367627
+
+Rhino is licensed under both the MPL (Mozilla Public License) 1.1 and the
+GPL (GNU General Public License) 2.0, which are in the LICENSE.js.txt file.
diff --git a/java_console/lib/batik/README.pdf-transcoder.txt b/java_console/lib/batik/README.pdf-transcoder.txt
new file mode 100644
index 0000000000..f30d98a00e
--- /dev/null
+++ b/java_console/lib/batik/README.pdf-transcoder.txt
@@ -0,0 +1,8 @@
+The pdf-transcoder.jar file is built from the Apache FOP project
+(http://xmlgraphics.apache.org/fop), version 0.94.
+
+This is only needed if you want to transcode to PDF, otherwise it can
+be removed.
+
+The pdf-transcoder.jar file is licensed under the Apache License 2.0, which
+can be found in the distribution root directory in the LICENSE file.
diff --git a/java_console/lib/batik/README.xalan-2.6.0.txt b/java_console/lib/batik/README.xalan-2.6.0.txt
new file mode 100644
index 0000000000..8e4a408530
--- /dev/null
+++ b/java_console/lib/batik/README.xalan-2.6.0.txt
@@ -0,0 +1,4 @@
+The xalan-2.6.0.jar file comes from the Apache Xalan project
+(http://xml.apache.org/xalan-j/), and is licensed under the
+Apache License 2.0, which can be found in the distribution root directory
+in the LICENSE file.
diff --git a/java_console/lib/batik/README.xerces_2_5_0.txt b/java_console/lib/batik/README.xerces_2_5_0.txt
new file mode 100644
index 0000000000..6090b00504
--- /dev/null
+++ b/java_console/lib/batik/README.xerces_2_5_0.txt
@@ -0,0 +1,4 @@
+The xerces_2_5_0.jar file comes from the Apache Xerces project
+(http://xml.apache.org/dist/xerces-j/), and is licensed under the
+Apache Software License, Version 1.1, which is in the
+LICENSE.xerces_2_5_0.txt file.
diff --git a/java_console/lib/batik/README.xml-apis-ext.txt b/java_console/lib/batik/README.xml-apis-ext.txt
new file mode 100644
index 0000000000..03a0a1ea4a
--- /dev/null
+++ b/java_console/lib/batik/README.xml-apis-ext.txt
@@ -0,0 +1,19 @@
+This distribution includes xml-apis-ext.jar from the XML Commons External
+1.3.04 binary distribution, which can also be obtained from:
+
+ http://xml.apache.org/mirrors.cgi
+
+Source code is available from the XML Commons web site:
+
+ http://xml.apache.org/commons/
+
+xml-apis-ext.jar contains:
+
+ - SAC 1.3
+ - SMIL Java bindings
+ - SVG 1.1 Java bindings
+
+SAC 1.3, the SMIL Java bindings and the SVG 1.1 Java bindings are licensed
+under the W3C Software License. Related documentation is licensed under the
+W3C Document License. See LICENSE.dom-software.txt and
+LICENSE.dom-documentation.txt.
diff --git a/java_console/lib/batik/README.xml-apis.txt b/java_console/lib/batik/README.xml-apis.txt
new file mode 100644
index 0000000000..dcfbcdfc32
--- /dev/null
+++ b/java_console/lib/batik/README.xml-apis.txt
@@ -0,0 +1,30 @@
+This distribution includes xml-apis.jar from the XML Commons External
+1.3.04 binary distribution, which can also be obtained from:
+
+ http://xml.apache.org/mirrors.cgi
+
+Source code is available from the XML Commons web site:
+
+ http://xml.apache.org/commons/
+
+xml-apis.jar contains:
+
+ - DOM Level 2 Events
+ - DOM Level 2 HTML
+ - DOM Level 2 Style
+ - DOM Level 2 Traversal and Range
+ - DOM Level 2 Views
+ - DOM Level 3 Core
+ - DOM Level 3 Load and Save
+ - DOM Level 3 XPath
+ - JAXP 1.3 (JSR 206)
+ - SAX
+
+All DOM code is licensed under the W3C Software License, and DOM documentation
+under the W3C Document License. See LICENSE.dom-software.txt and
+LICENSE.dom-documentation.txt.
+
+The JAXP 1.3 code is licensed under the Apache Software License 2.0, which is
+in the LICENSE in the root directory of this distribution.
+
+SAX is public domain. See LICENSE.sax.txt.
diff --git a/java_console/lib/batik/Squiggle.icns b/java_console/lib/batik/Squiggle.icns
new file mode 100644
index 0000000000..b4875d6c1f
Binary files /dev/null and b/java_console/lib/batik/Squiggle.icns differ
diff --git a/java_console/lib/batik/batik-anim.jar b/java_console/lib/batik/batik-anim.jar
new file mode 100644
index 0000000000..6913e421c9
Binary files /dev/null and b/java_console/lib/batik/batik-anim.jar differ
diff --git a/java_console/lib/batik/batik-awt-util.jar b/java_console/lib/batik/batik-awt-util.jar
new file mode 100644
index 0000000000..e64605af88
Binary files /dev/null and b/java_console/lib/batik/batik-awt-util.jar differ
diff --git a/java_console/lib/batik/batik-bridge.jar b/java_console/lib/batik/batik-bridge.jar
new file mode 100644
index 0000000000..62c10bae3d
Binary files /dev/null and b/java_console/lib/batik/batik-bridge.jar differ
diff --git a/java_console/lib/batik/batik-codec.jar b/java_console/lib/batik/batik-codec.jar
new file mode 100644
index 0000000000..0a83c6ff19
Binary files /dev/null and b/java_console/lib/batik/batik-codec.jar differ
diff --git a/java_console/lib/batik/batik-css.jar b/java_console/lib/batik/batik-css.jar
new file mode 100644
index 0000000000..c1f1c9a885
Binary files /dev/null and b/java_console/lib/batik/batik-css.jar differ
diff --git a/java_console/lib/batik/batik-dom.jar b/java_console/lib/batik/batik-dom.jar
new file mode 100644
index 0000000000..32d5b46d05
Binary files /dev/null and b/java_console/lib/batik/batik-dom.jar differ
diff --git a/java_console/lib/batik/batik-ext.jar b/java_console/lib/batik/batik-ext.jar
new file mode 100644
index 0000000000..8c904e1f2a
Binary files /dev/null and b/java_console/lib/batik/batik-ext.jar differ
diff --git a/java_console/lib/batik/batik-extension.jar b/java_console/lib/batik/batik-extension.jar
new file mode 100644
index 0000000000..106e4ac3cb
Binary files /dev/null and b/java_console/lib/batik/batik-extension.jar differ
diff --git a/java_console/lib/batik/batik-gui-util.jar b/java_console/lib/batik/batik-gui-util.jar
new file mode 100644
index 0000000000..bf8c8441af
Binary files /dev/null and b/java_console/lib/batik/batik-gui-util.jar differ
diff --git a/java_console/lib/batik/batik-gvt.jar b/java_console/lib/batik/batik-gvt.jar
new file mode 100644
index 0000000000..ee47ec8258
Binary files /dev/null and b/java_console/lib/batik/batik-gvt.jar differ
diff --git a/java_console/lib/batik/batik-parser.jar b/java_console/lib/batik/batik-parser.jar
new file mode 100644
index 0000000000..286b3799c3
Binary files /dev/null and b/java_console/lib/batik/batik-parser.jar differ
diff --git a/java_console/lib/batik/batik-script.jar b/java_console/lib/batik/batik-script.jar
new file mode 100644
index 0000000000..433f02e67c
Binary files /dev/null and b/java_console/lib/batik/batik-script.jar differ
diff --git a/java_console/lib/batik/batik-svg-dom.jar b/java_console/lib/batik/batik-svg-dom.jar
new file mode 100644
index 0000000000..b4c8a620bb
Binary files /dev/null and b/java_console/lib/batik/batik-svg-dom.jar differ
diff --git a/java_console/lib/batik/batik-svggen.jar b/java_console/lib/batik/batik-svggen.jar
new file mode 100644
index 0000000000..4d6bb14417
Binary files /dev/null and b/java_console/lib/batik/batik-svggen.jar differ
diff --git a/java_console/lib/batik/batik-swing.jar b/java_console/lib/batik/batik-swing.jar
new file mode 100644
index 0000000000..93e5d0f335
Binary files /dev/null and b/java_console/lib/batik/batik-swing.jar differ
diff --git a/java_console/lib/batik/batik-transcoder.jar b/java_console/lib/batik/batik-transcoder.jar
new file mode 100644
index 0000000000..0f2f7cd358
Binary files /dev/null and b/java_console/lib/batik/batik-transcoder.jar differ
diff --git a/java_console/lib/batik/batik-util.jar b/java_console/lib/batik/batik-util.jar
new file mode 100644
index 0000000000..86d75e70f2
Binary files /dev/null and b/java_console/lib/batik/batik-util.jar differ
diff --git a/java_console/lib/batik/batik-xml.jar b/java_console/lib/batik/batik-xml.jar
new file mode 100644
index 0000000000..d05eb25f77
Binary files /dev/null and b/java_console/lib/batik/batik-xml.jar differ
diff --git a/java_console/lib/batik/js.jar b/java_console/lib/batik/js.jar
new file mode 100644
index 0000000000..ccad3cc1bf
Binary files /dev/null and b/java_console/lib/batik/js.jar differ
diff --git a/java_console/lib/batik/make-squiggle-app.sh b/java_console/lib/batik/make-squiggle-app.sh
new file mode 100644
index 0000000000..76fe888025
--- /dev/null
+++ b/java_console/lib/batik/make-squiggle-app.sh
@@ -0,0 +1,87 @@
+#!/bin/bash
+# -----------------------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# make-squiggle-app.sh
+#
+# Creates a Mac OS X application bundle for Squiggle, then opens a Finder
+# window for the current directory so that the user can drag the icon
+# into their desired installation location (probably /Applications).
+#
+# $Id$
+# -----------------------------------------------------------------------------
+
+trap 'echo Error creating application bundle.; exit 1' ERR
+
+cd `dirname "$0"`/..
+
+APP=Squiggle.app
+CON=$APP/Contents
+MAC=$CON/MacOS
+RES=$CON/Resources
+
+[ -e $APP ] && echo $APP already exists: please move it out of the way before running this script. && exit 1
+
+echo Creating $APP in `pwd`...
+
+mkdir $APP $CON $MAC $RES
+
+cat >$CON/Info.plist <
+
+
+
+ CFBundleExecutable
+ Squiggle
+ CFBundleVersion
+ 1.7+r608262
+ CFBundleShortVersionString
+ 1.7+r608262
+ CFBundleIconFile
+ Squiggle.icns
+ CFBundleIdentifier
+ org.apache.batik
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ Squiggle
+ CFBundlePackageType
+ APPL
+ CFBundleSignature
+ ????
+ NSHumanReadableCopyright
+ Copyright © 2008 Apache Software Foundation. All Rights Reserved.
+
+
+EOF
+
+cat >$MAC/Squiggle <
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/models/src/com/irnems/Histograms.java b/java_console/models/src/com/irnems/Histograms.java
new file mode 100644
index 0000000000..806820470b
--- /dev/null
+++ b/java_console/models/src/com/irnems/Histograms.java
@@ -0,0 +1,492 @@
+package com.irnems;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.util.*;
+
+public final class Histograms {
+ private static final String SQL_STATEMENT = "SQL-statement";
+ public static final double H_ACCURACY = 0.05;
+ public static final int BOUND_LENGTH = (int) (Math.log(Long.MAX_VALUE) / Math.log(1.0 + H_ACCURACY));
+
+ public static final long LONG_MAX_INT = 0x7fffffffffffffffL;
+
+ public static final double H_CONFIDENCE = 0.8;
+ public static final int SBI_SIZE = 1000;
+
+ // ======= Initialization =======
+
+ private final HashMap total_stats = new HashMap();
+ private final long start_time = System.currentTimeMillis();
+ public final ThreadLocal local_stats = new ThreadLocal() {
+ @Override
+ protected LocalStats initialValue() {
+ return new LocalStats();
+ }
+ };
+ private final HashSet all_local_stats = new HashSet();
+
+ private long last_dump = System.currentTimeMillis();
+
+ /**
+ * this method updates totals & resets snapshot
+ * todo: get rid of TOS 'Profiler' and make this method protected?
+ *
+ * @see #getCurrentStatistics()
+ */
+ @NotNull
+ public List dumpStats() {
+ Collection values = takeAndResetSnapshot();
+ List al = new ArrayList();
+ al.addAll(values);
+ synchronized (total_stats) {
+ for (StatisticsGroup source : values) {
+ String type = source.type + ".TOTAL";
+ StatisticsGroup dest = total_stats.get(type);
+ if (dest == null)
+ total_stats.put(type, dest = new StatisticsGroup(type));
+ dest.add(source);
+ }
+ al.addAll(total_stats.values());
+ }
+ return sortAndAddTimes(al);
+ }
+
+ /**
+ * this method displays currently gathered histogram without affecting state
+ *
+ * @see #dumpStats()
+ */
+ @NotNull
+ public List getCurrentStatistics() {
+ Collection snapshot = getCurrentSnapshot();
+ return sortAndAddTimes(new ArrayList(snapshot));
+ }
+
+ private List sortAndAddTimes(List al) {
+ Collections.sort(al, new Comparator() {
+ public int compare(StatisticsGroup o1, StatisticsGroup o2) {
+ return o1.type.compareTo(o2.type);
+ }
+ });
+ long time = System.currentTimeMillis();
+ List result = new ArrayList();
+ for (StatisticsGroup sg : al)
+ result.add(toString(sg, time - (sg.type.endsWith(".TOTAL") ? start_time : last_dump)));
+ last_dump = time;
+ return result;
+ }
+
+ // ========= Internal Data Structures ========
+
+ /**
+ * Accuracy of histogram - the ratio of bar width to its value.
+ */
+
+ /**
+ * Confidence interval to be printed together with min/max interval.
+ */
+
+ /**
+ * Confidence quantity bounds for logging.
+ */
+ private final double[] confidence_bounds;
+
+ /**
+ * Confidence interval separators for logging.
+ */
+ private final String[] confidence_separators;
+
+ /**
+ * Bounds of histogram bars. Bar number 'i' covers interval
+ * from bounds[i] inclusive to bounds[i + 1] exclusive.
+ */
+ private final long[] bounds;
+
+ /**
+ * Index for direct access to intervals with small bounds.
+ */
+ private final int[] small_bounds_index;
+
+
+ /**
+ * Construct new instance of profiler with specified parameters.
+ */
+ public Histograms() {
+
+ confidence_bounds = new double[]{0.5 - H_CONFIDENCE * 0.5, 0.5, 0.5 + H_CONFIDENCE * 0.5};
+ confidence_separators = new String[]{"(", " [", "-", "-", "] ", ")"};
+
+ FileLog.rlog("BOUND_LENGTH=" + BOUND_LENGTH);
+
+ bounds = new long[BOUND_LENGTH];
+ bounds[0] = 0;
+ for (int i = 1; i < BOUND_LENGTH; i++) {
+ long prev = bounds[i - 1];
+ long next = prev + (long) ((double) prev * H_ACCURACY);
+ if (next == prev) // Ensure minimum step for small numbers.
+ next = prev + 1;
+ if (next < prev) // Overflow over Long.MAX_VALUE occurred.
+ next = LONG_MAX_INT;
+ bounds[i] = next;
+ }
+ bounds[BOUND_LENGTH - 1] = LONG_MAX_INT;
+
+ small_bounds_index = new int[SBI_SIZE];
+ for (int i = 0, j = 0; j < SBI_SIZE; i++)
+ while (j < bounds[i + 1] && j < SBI_SIZE)
+ small_bounds_index[j++] = i;
+ }
+
+ /**
+ * Returns histogram index for specified value.
+ */
+ public int getIndex(long value) {
+ if (value < 0)
+ return 0;
+ if (value < small_bounds_index.length)
+ return small_bounds_index[(int) value];
+ int l = small_bounds_index[small_bounds_index.length - 1];
+ int r = bounds.length - 1;
+ while (l < r) {
+ int m = (l + r) >> 1;
+ if (bounds[m] > value)
+ r = m - 1;
+ else if (bounds[m + 1] <= value)
+ l = m + 1;
+ else
+ return m;
+ }
+ return l;
+ }
+
+ public static final class LocalStats {
+ public HashMap stats; // Type -> StatisticsGroup
+ }
+
+ /**
+ * Adds specified samples to statistics.
+ */
+ public void addValue(ValueType t, String name, long value) {
+ String type = t.getName();
+ if (value < 0)
+ value = 0;
+ int index = getIndex(value);
+ LocalStats ls = local_stats.get();
+ boolean create;
+ create = ls.stats == null;
+ if (create)
+ ls.stats = new HashMap();
+ StatisticsGroup sg = ls.stats.get(t);
+ if (sg == null)
+ ls.stats.put(t, sg = new StatisticsGroup(type));
+ sg.add(name, index, value);
+ if (create)
+ synchronized (all_local_stats) {
+ all_local_stats.add(ls);
+ }
+ }
+
+ /**
+ * returns current statistics without resetting anything
+ */
+ private Collection getCurrentSnapshot() {
+ List lss = getLocalStats(false);
+ HashMap snapshot = new HashMap();
+ for (LocalStats ls : lss) {
+ // in case of a snapshot without reset, we have to merge under the lock
+ mergeStats(snapshot, ls.stats);
+ }
+ return snapshot.values();
+ }
+
+ /**
+ * Returns snapshot of all gathered statistics and clears them in process.
+ */
+ private Collection takeAndResetSnapshot() {
+ List lss = getLocalStats(true);
+ HashMap snapshot = new HashMap();
+ for (LocalStats ls : lss) {
+ HashMap stats;
+ stats = ls.stats;
+ /**
+ * we will re-register this LocalStats on next #add() invocation
+ * @see #add(String, String, int, long)
+ */
+ ls.stats = null;
+ mergeStats(snapshot, stats);
+ }
+ return snapshot.values();
+ }
+
+ private static void mergeStats(HashMap snapshot, HashMap stats) {
+ if (stats != null)
+ for (StatisticsGroup source : stats.values()) {
+ StatisticsGroup dest = snapshot.get(source.type);
+ if (dest == null)
+ snapshot.put(source.type, source);
+ else
+ dest.add(source);
+ }
+ }
+
+ private List getLocalStats(boolean reset) {
+ List lss;
+ synchronized (all_local_stats) {
+ lss = new ArrayList(all_local_stats);
+ if (reset)
+ all_local_stats.clear();
+ }
+ return lss;
+ }
+
+ /**
+ * Sorts specified statistics of specified statistics group for logging.
+ */
+ private static void sortStatistics(StatisticsGroup sg, Statistics[] sts) {
+ final boolean use_total = sg.type.startsWith(SQL_STATEMENT);
+ Arrays.sort(sts, new Comparator() {
+ public int compare(Statistics st1, Statistics st2) {
+ if (use_total && st1.total_value != st2.total_value)
+ return st1.total_value < st2.total_value ? -1 : 1;
+ return st1.name.compareTo(st2.name);
+ }
+ });
+ }
+
+ // ========== Formatting Methods ==========
+
+ private static final double[] multipliers = {1.0, 10.0, 100.0};
+ private final NumberFormat formatter = NumberFormat.getNumberInstance();
+ private final FieldPosition field = new FieldPosition(0);
+
+ /**
+ * Formats specified value into specified string buffer. Not thread-safe.
+ */
+ private StringBuffer format(double d, StringBuffer sb) {
+ int i = Math.abs(d) < 9.995 ? 2 : Math.abs(d) < 99.95 ? 1 : 0;
+ formatter.setMinimumFractionDigits(0);
+ formatter.setMaximumFractionDigits(i);
+ return formatter.format(Math.floor(d * multipliers[i] + 0.5) / multipliers[i], sb, field);
+ }
+
+ /**
+ * Appends header for specified statistics group for logging.
+ */
+ private void appendHeader(StringBuffer sb, StatisticsGroup sg, long duration) {
+ double time = duration;
+ String time_scale = "ms";
+ if (time >= 24 * 3600 * 1000 * 0.995) {
+ time /= 24.0 * 3600.0 * 1000.0;
+ time_scale = "days";
+ } else if (time >= 3600 * 1000 * 0.995) {
+ time /= 3600.0 * 1000.0;
+ time_scale = "hours";
+ } else if (time >= 60 * 1000 * 0.995) {
+ time /= 60.0 * 1000.0;
+ time_scale = "min";
+ } else if (time >= 1000 * 0.995) {
+ time /= 1000.0;
+ time_scale = "sec";
+ }
+
+ sb.append(sg.type).append(" statistics for ");
+ format(time, sb).append(time_scale).append(" with ");
+ format(H_ACCURACY * 100.0, sb).append("% accuracy and ");
+ format(H_CONFIDENCE * 100.0, sb).append("% confidence:");
+ }
+
+ /**
+ * Appends specified statistics (main part) for logging.
+ */
+ public void appendStatistics(StringBuffer sb, Statistics st, List report) {
+ format(st.total_value, sb).append(" / ").append(st.total_count).append(" = ");
+ format((double) st.total_value / (double) st.total_count, sb);
+
+ if (st.total_count < 0) {
+ sb.append("Total count is less then ZERO!\n");
+ return;
+ }
+
+ if (st.total_count <= 5) {
+ // There are too little samples for statistics.
+ sb.append(" are");
+ for (int j = 0; j < st.histogram.length; j++)
+ for (int k = 0; k < st.histogram[j]; k++) {
+ sb.append(' ');
+ format((bounds[j] + bounds[j + 1]) / 2, sb);
+ }
+ return;
+ }
+
+ // Print full min/confidence/max statistics.
+ sb.append(" in ").append(confidence_separators[0]);
+ int min = 0;
+ while (st.histogram[min] == 0)
+ min++;
+ // 'min' is index of interval with min sample.
+ format(bounds[min], sb).append(confidence_separators[1]);
+ report.add(bounds[min]);
+
+
+ long acc = 0;
+ // 'acc' is accumulated number of samples in [0, min - 1].
+ for (int j = 0; j < confidence_bounds.length; j++) {
+ long k = (long) Math.floor((double) st.total_count * confidence_bounds[j]);
+ // Always drop at least 1 'non-confident' sample...
+ if (k == 0)
+ k = 1;
+ if (k == st.total_count)
+ k = st.total_count - 1;
+ // 'k' is desired number of samples.
+ while (acc + st.histogram[min] < k)
+ acc += st.histogram[min++];
+ if (k < st.total_count / 2) // Converge to median (from left).
+ while (acc + st.histogram[min] <= k)
+ acc += st.histogram[min++];
+ // Now: acc <= k <= acc + st.histogram[min]
+ // And desired number of samples is within [min, min + 1)
+ double d = bounds[min];
+ if (acc != k)
+ d += (double) (bounds[min + 1] - 1 - bounds[min]) *
+ (double) (k - acc) /
+ (double) st.histogram[min];
+ format(d, sb).append(confidence_separators[j + 2]);
+ report.add((long)d);
+ }
+
+ int max = st.histogram.length - 1;
+ while (st.histogram[max] == 0)
+ max--;
+ // 'max' is index of interval with max sample.
+ long maxValue = bounds[max + 1] - 1;
+ format(maxValue, sb).append(confidence_separators[5]);
+ report.add(maxValue);
+ }
+
+ /**
+ * Formats specified statistics group for logging.
+ */
+ private String toString(StatisticsGroup sg, long duration) {
+ StringBuffer sb = new StringBuffer(100 + sg.data.size() * 100);
+ appendHeader(sb, sg, duration);
+ Statistics[] sts = sg.data.values().toArray(new Statistics[sg.data.size()]);
+ sortStatistics(sg, sts);
+ for (Statistics st : sts) {
+ appendStatistics(sb, st, new ArrayList());
+ }
+ return sb.toString();
+ }
+
+ // ========== Internal Classes ==========
+
+ /**
+ * Performance statistics for single operation.
+ */
+ public static class Statistics {
+ private static final long[] EMPTY_HISTOGRAM = new long[0];
+
+ public final String name;
+
+ public long total_value;
+ public long total_count;
+ public long[] histogram;
+
+ private Statistics(String name) {
+ this.name = name;
+ this.histogram = EMPTY_HISTOGRAM;
+ }
+
+ private Statistics(Statistics st) {
+ this.name = st.name;
+ this.total_value = st.total_value;
+ this.total_count = st.total_count;
+ this.histogram = st.histogram.length == 0 ? EMPTY_HISTOGRAM : st.histogram.clone();
+ }
+
+ public void add(int index, int count, long value) {
+ total_value += value;
+ total_count += count;
+ if (index >= histogram.length) {
+ int new_length = Math.max(histogram.length, 10);
+ while (index >= new_length)
+ new_length = new_length << 1;
+ long[] new_histogram = new long[new_length];
+ System.arraycopy(histogram, 0, new_histogram, 0, histogram.length);
+ histogram = new_histogram;
+ }
+ histogram[index] += count;
+ }
+
+ public void add(Statistics st) {
+ total_value += st.total_value;
+ total_count += st.total_count;
+ long[] hist = st.histogram;
+ if (histogram.length < st.histogram.length) {
+ hist = histogram;
+ histogram = st.histogram.clone();
+ }
+ for (int i = hist.length; --i >= 0; )
+ histogram[i] += hist[i];
+ }
+ }
+
+ /**
+ * Group of statistics of the same type.
+ */
+ @SuppressWarnings("ForLoopReplaceableByForEach")
+ public static class StatisticsGroup {
+ public final String type;
+ public final HashMap data; // Maps statistics name (String) to Statistics.
+
+ private StatisticsGroup(String type) {
+ this.type = type;
+ this.data = new HashMap();
+ }
+
+ public void add(String name, int index, long value) {
+ Statistics st = data.get(name);
+ if (st == null)
+ data.put(name, st = new Statistics(name));
+ st.add(index, 1, value);
+ }
+
+ public void add(StatisticsGroup sg) {
+ for (Iterator it = sg.data.values().iterator(); it.hasNext(); ) {
+ Statistics source = it.next();
+ Statistics dest = data.get(source.name);
+ if (dest == null)
+ data.put(source.name, new Statistics(source));
+ else
+ dest.add(source);
+ }
+ }
+ }
+
+ /**
+ * The type of a value.
+ */
+ public enum ValueType {
+ INVOCATION("Invocation");
+ /**
+ * Value type that is used to mean a method call.
+ */
+
+ private final String name;
+
+ /**
+ * Gets the name of this {@link ValueType}.
+ *
+ * @return the name.
+ */
+ public String getName() {
+ return name;
+ }
+
+ ValueType(String name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/java_console/models/src/com/irnems/ReportLine.java b/java_console/models/src/com/irnems/ReportLine.java
new file mode 100644
index 0000000000..6dfd4a7a95
--- /dev/null
+++ b/java_console/models/src/com/irnems/ReportLine.java
@@ -0,0 +1,48 @@
+package com.irnems;
+
+import com.irnems.models.MafValue;
+import com.irnems.models.RpmValue;
+
+/**
+ * @author Andrey Belomutskiy
+ * 1/29/13
+ */
+public class ReportLine {
+ private final int time;
+ private final MafValue maf;
+ private final RpmValue rpm;
+ private final int wave;
+
+ public ReportLine(int time, MafValue maf, RpmValue rpm, int wave) {
+ this.time = time;
+ this.maf = maf;
+ this.rpm = rpm;
+ this.wave = wave;
+ }
+
+ public int getTime() {
+ return time;
+ }
+
+ public MafValue getMaf() {
+ return maf;
+ }
+
+ public RpmValue getRpm() {
+ return rpm;
+ }
+
+ public int getWave() {
+ return wave;
+ }
+
+ @Override
+ public String toString() {
+ return "ReportLine{" +
+ "time=" + time +
+ ", maf=" + maf +
+ ", rpm=" + rpm +
+ ", wave=" + wave +
+ '}';
+ }
+}
diff --git a/java_console/models/src/com/irnems/ReportReader.java b/java_console/models/src/com/irnems/ReportReader.java
new file mode 100644
index 0000000000..33507bcace
--- /dev/null
+++ b/java_console/models/src/com/irnems/ReportReader.java
@@ -0,0 +1,157 @@
+package com.irnems;
+
+import com.irnems.models.Factory;
+import com.irnems.models.MafValue;
+import com.irnems.models.RpmValue;
+import com.irnems.models.Utils;
+
+import java.io.*;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Date: 1/29/13
+ * (c) Andrey Belomutskiy
+ */
+public class ReportReader {
+ private static final Pattern LINE_PATTERN = Pattern.compile("\\D*(\\d*);a0;(\\d*);a1;-1;rpm;(\\d*);wave;(\\d*).*");
+ private static final int INVALID_RPM_DIFF = 200;
+
+ public static void main(String[] args) {
+ //List lines = read("unfiltered.txt");
+ readMap("200ohm.txt");
+
+ }
+
+ public static TreeMap> readMap(String filename) {
+ if (!new File(filename).exists()) {
+ FileLog.rlog("Error: not found " + filename);
+ return new TreeMap>();
+ }
+ List lines = read(filename);
+ FileLog.rlog("Got " + lines.size() + " lines");
+
+ lines = filter(Collections.unmodifiableList(lines));
+
+ findMinMax(lines);
+
+ return asMap(lines);
+ }
+
+ private static TreeMap> asMap(List lines) {
+ /**
+ * map of maps by RPM. inner map is by MAF
+ */
+ TreeMap> rpm2mapByMaf = new TreeMap>();
+ for (ReportLine cur : lines) {
+ int rpmKey = cur.getRpm().getValue();
+
+ // round to 100
+ rpmKey -= (rpmKey % 100);
+
+ TreeMap maf2line = Utils.getOrCreate(rpm2mapByMaf, rpmKey, new Factory>() {
+ public TreeMap create(Integer key) {
+ return new TreeMap();
+ }
+ });
+ maf2line.put(cur.getMaf().getValue(), cur);
+ }
+ return rpm2mapByMaf;
+ }
+
+ private static void findMinMax(List lines) {
+ int minMaf = 100000;
+ int maxMaf = 0;
+
+ int minRpm = 100000;
+ int maxRpm = 0;
+ for (ReportLine cur : lines) {
+ minMaf = Math.min(minMaf, cur.getMaf().getValue());
+ maxMaf = Math.max(maxMaf, cur.getMaf().getValue());
+
+ minRpm = Math.min(minRpm, cur.getRpm().getValue());
+ maxRpm = Math.max(maxRpm, cur.getRpm().getValue());
+ }
+ FileLog.rlog("MAF range from " + minMaf + " to " + maxMaf);
+ FileLog.rlog("RPM range from " + minRpm + " to " + maxRpm);
+ }
+
+ private static List filter(List lines) {
+ List result = new ArrayList();
+ int maxValidDiff = 0;
+ int originalCount = lines.size();
+ int removedCount = 0;
+
+ result.add(lines.get(0));
+
+ for (int i = 1; i < lines.size(); i++) {
+ ReportLine prev = result.get(result.size() - 1);
+ ReportLine cur = lines.get(i);
+
+ int rpmDiff = cur.getRpm().getValue() - prev.getRpm().getValue();
+ int timeDiff = cur.getTime() - prev.getTime();
+
+ if (Math.abs(rpmDiff) > INVALID_RPM_DIFF) {
+ FileLog.rlog("Invalid diff: " + cur);
+ removedCount++;
+ continue;
+ }
+ result.add(cur);
+ // maximum valid diff
+ maxValidDiff = Math.max(maxValidDiff, Math.abs(rpmDiff));
+// System.out.println("current rpm: " + cur + ", rpm diff=" + rpmDiff + " td=" + timeDiff);
+// System.out.println("value," + cur.getRpm().getValue() + "," + cur.getMaf().getValue() + "," + cur.getWave());
+ }
+ double percent = 100.0 * removedCount / originalCount;
+ FileLog.rlog(removedCount + " out of " + originalCount + " record(s) removed. " + percent + "%");
+ FileLog.rlog("Max valid diff: " + maxValidDiff);
+ return result;
+ }
+
+ private static List read(String filename) {
+ List result = new LinkedList();
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(filename));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ //process each line in some way
+ ReportLine rl = handleOneLine(line);
+ if (rl != null)
+ result.add(rl);
+ }
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ return result;
+ }
+
+ private static int prevMaf = -1;
+ private static int prevRpm = -1;
+ private static int prevWave = -1;
+
+ private static ReportLine handleOneLine(String line) {
+// System.out.println(line);
+ Matcher m = LINE_PATTERN.matcher(line);
+ if (m.matches()) {
+ int time = Integer.valueOf(m.group(1));
+ MafValue maf = MafValue.valueOf(m.group(2));
+ RpmValue rpm = RpmValue.valueOf(m.group(3));
+ int wave = Integer.valueOf(m.group(4));
+
+ if (prevMaf == maf.getValue() && prevRpm == rpm.getValue() && prevWave == wave) {
+// System.out.println("All the same...");
+ return null;
+ }
+
+ //System.out.println(time + " m=" + maf + " r=" + rpm + " w=" + wave);
+ prevMaf = maf.getValue();
+ prevRpm = rpm.getValue();
+ prevWave = wave;
+ return new ReportLine(time, maf, rpm, wave);
+ }
+ return null;
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/EngineState.java b/java_console/models/src/com/irnems/core/EngineState.java
new file mode 100644
index 0000000000..e76b653795
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/EngineState.java
@@ -0,0 +1,348 @@
+package com.irnems.core;
+
+import com.irnems.FileLog;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Date: 12/25/12
+ * (c) Andrey Belomutskiy
+ *
+ * @see #registerStringValueAction
+ */
+public class EngineState {
+ public static final String RPM_KEY = "rpm";
+ public static final String SEPARATOR = ",";
+ public static final int SNIFFED_ADC_COUNT = 16;
+ public static final ValueCallback NOTHING = new ValueCallback() {
+ @Override
+ public void onUpdate(String value) {
+ }
+ };
+ public static final String PACKING_DELIMITER = ":";
+ private final Object lock = new Object();
+
+ static class StringActionPair extends Pair> {
+ public final String prefix;
+
+ StringActionPair(String key, ValueCallback second) {
+ super(key, second);
+ prefix = key.toLowerCase() + SEPARATOR;
+ }
+
+ @Override
+ public String toString() {
+ return "Pair(" + first + "," + second + "}";
+ }
+ }
+
+ public List timeListeners = new CopyOnWriteArrayList();
+
+ private final ResponseBuffer buffer;
+ private final List actions = new ArrayList();
+ private final Set keys = new TreeSet(String.CASE_INSENSITIVE_ORDER);
+
+ public EngineState(@NotNull final EngineStateListener listener) {
+ buffer = new ResponseBuffer(new ResponseBuffer.ResponseListener() {
+ public void onResponse(String message) {
+ String response = unpackString(message);
+ if (response != null) {
+ String copy = response;
+ listener.beforeLine(response);
+ while (!response.isEmpty())
+ response = handleResponse(response, listener);
+ listener.afterLine(copy);
+ }
+ }
+ }
+ );
+
+
+ SensorStats.start(Sensor.COOLANT, Sensor.COOLANT_WIDTH);
+ SensorStats.start(Sensor.INTAKE_AIR, Sensor.INTAKE_AIR_WIDTH);
+ SensorStats.start(Sensor.VREF, Sensor.VREF_WIDTH);
+
+ addDoubleSensor(RPM_KEY, Sensor.RPM);
+ addDoubleSensor("mat", Sensor.INTAKE_AIR);
+ addDoubleSensor("map", Sensor.MAP);
+ addDoubleSensor("map_r", Sensor.MAP_RAW);
+ addDoubleSensor("clt", Sensor.COOLANT);
+ addDoubleSensor("tp", Sensor.THROTTLE);
+ addDoubleSensor("dwell0", Sensor.DWELL0);
+ addDoubleSensor("dwell1", Sensor.DWELL1);
+ addDoubleSensor("tch", Sensor.T_CHARGE);
+ addDoubleSensor("afr", Sensor.AFR);
+ addDoubleSensor("d_fuel", Sensor.DEFAULT_FUEL);
+ addDoubleSensor("fuel", Sensor.FUEL);
+ addDoubleSensor("fuel_base", Sensor.FUEL_BASE);
+ addDoubleSensor("fuel_lag", Sensor.FUEL_LAG);
+ addDoubleSensor("fuel_clt", Sensor.FUEL_CLT);
+ addDoubleSensor("fuel_iat", Sensor.FUEL_IAT);
+ addDoubleSensor("table_spark", Sensor.TABLE_SPARK);
+ addDoubleSensor("advance0", Sensor.ADVANCE0);
+ addDoubleSensor("advance1", Sensor.ADVANCE1);
+ addDoubleSensor("vref", Sensor.VREF);
+ addDoubleSensor("vbatt", Sensor.VBATT);
+ addDoubleSensor("maf", Sensor.MAF);
+ addDoubleSensor("period0", Sensor.PERIOD0);
+ addDoubleSensor("period1", Sensor.PERIOD0);
+ addDoubleSensor("duty0", Sensor.DUTY0);
+ addDoubleSensor("duty1", Sensor.DUTY1);
+ addDoubleSensor("timing", Sensor.TIMING);
+
+ addDoubleSensor("idl", Sensor.IDLE_SWITCH);
+
+ addDoubleSensor("chart", Sensor.CHART_STATUS, true);
+ addDoubleSensor("chartsize", Sensor.CHARTSIZE, true);
+ addDoubleSensor("adcDebug", Sensor.ADC_STATUS, true);
+ addDoubleSensor("adcfast", Sensor.ADC_FAST);
+ addDoubleSensor("adcfastavg", Sensor.ADC_FAST_AVG);
+ registerStringValueAction("adcfast_co", NOTHING);
+ registerStringValueAction("adcfast_max", NOTHING);
+ registerStringValueAction("adcfast_min", NOTHING);
+ registerStringValueAction("key", NOTHING);
+ registerStringValueAction("value", NOTHING);
+// addDoubleSensor("adcfast_co", Sensor.ADC_FAST_AVG);
+
+ addDoubleSensor("injector0", Sensor.INJECTOR_0_STATUS, true);
+ addDoubleSensor("injector1", Sensor.INJECTOR_1_STATUS, true);
+ addDoubleSensor("injector2", Sensor.INJECTOR_2_STATUS, true);
+ addDoubleSensor("injector3", Sensor.INJECTOR_3_STATUS, true);
+
+ registerStringValueAction("msg", new ValueCallback() {
+ @Override
+ public void onUpdate(String value) {
+ MessagesCentral.getInstance().postMessage(EngineState.class, value);
+ }
+ });
+
+ registerStringValueAction("advance", NOTHING);
+ registerStringValueAction("dwell", NOTHING);
+
+ registerStringValueAction("offset0", NOTHING);
+ registerStringValueAction("offset1", NOTHING);
+ registerStringValueAction("crank_period", NOTHING);
+ registerStringValueAction("ckp_c", NOTHING);
+ registerStringValueAction("ev0", NOTHING);
+ registerStringValueAction("ev1", NOTHING);
+ registerStringValueAction("adc10", NOTHING);
+
+ registerStringValueAction("isC", NOTHING);
+ registerStringValueAction("p_d", NOTHING);
+ registerStringValueAction("s_d", NOTHING);
+ registerStringValueAction("pEC", NOTHING);
+ registerStringValueAction("sEC", NOTHING);
+ registerStringValueAction("wEC", NOTHING);
+ registerStringValueAction("wWi", NOTHING);
+ registerStringValueAction("wWi2", NOTHING);
+
+ registerStringValueAction("cid", NOTHING);
+ registerStringValueAction("i_d", NOTHING);
+ registerStringValueAction("i_p", NOTHING);
+ registerStringValueAction("a_time", NOTHING);
+
+
+ registerStringValueAction("time", new ValueCallback() {
+ public void onUpdate(String value) {
+ double time;
+ try {
+ time = Double.parseDouble(value);
+ } catch (NumberFormatException e) {
+ return;
+ }
+ listener.onTime(time);
+ for (EngineTimeListener l : timeListeners)
+ l.onTime(time);
+ }
+ });
+ }
+
+ private void addDoubleSensor(String key, final Sensor sensor) {
+ addDoubleSensor(key, sensor, false);
+ }
+
+ private void addDoubleSensor(final String key, final Sensor sensor, final boolean verbose) {
+ registerStringValueAction(key, new ValueCallback() {
+ @Override
+ public void onUpdate(String stringValue) {
+ double value;
+ try {
+ value = Double.parseDouble(stringValue);
+ } catch (NumberFormatException e) {
+ // serial protocol is not safe
+ return;
+ }
+ SensorCentral.getInstance().setValue(value, sensor);
+ if (verbose)
+ MessagesCentral.getInstance().postMessage(EngineState.class, key + "=" + value);
+ }
+ });
+ }
+
+ /**
+ * @see #unpackString(String)
+ */
+ public static String packString(String a) {
+ return "line" + PACKING_DELIMITER + a.length() + PACKING_DELIMITER + a;
+ }
+
+ /**
+ * serial protocol is not error-prone, so our simple approach is to validate the length of incoming strings
+ *
+ * @see #packString(String)
+ */
+ public static String unpackString(String message) {
+ String prefix = "line" + PACKING_DELIMITER;
+ if (!message.startsWith(prefix)) {
+ FileLog.rlog("EngineState: unexpected header: " + message);
+ return null;
+ }
+ message = message.substring(prefix.length());
+ int delimiterIndex = message.indexOf(PACKING_DELIMITER);
+ if (delimiterIndex == -1) {
+ FileLog.rlog("Delimiter not found in: " + message);
+ return null;
+ }
+ String lengthToken = message.substring(0, delimiterIndex);
+ int expectedLen;
+ try {
+ expectedLen = Integer.parseInt(lengthToken);
+ } catch (NumberFormatException e) {
+ FileLog.rlog("invalid len: " + lengthToken);
+ return null;
+ }
+
+ String response = message.substring(delimiterIndex + 1);
+ if (response.length() != expectedLen) {
+ FileLog.rlog("message len does not match header: " + message);
+ response = null;
+ }
+ return response;
+ }
+
+ /**
+ * @param response input string
+ * @param listener obviously
+ * @return unused part of the response
+ */
+ private String handleResponse(String response, EngineStateListener listener) {
+ String originalResponse = response;
+ synchronized (lock) {
+ for (StringActionPair pair : actions)
+ response = handleStringActionPair(response, pair, listener);
+ }
+ if (originalResponse.length() == response.length()) {
+ FileLog.rlog("EngineState.unknown: " + response);
+ // discarding invalid line
+ return "";
+ }
+ return response;
+ }
+
+ private String handleStringActionPair(String response, StringActionPair pair, EngineStateListener listener) {
+ if (startWithIgnoreCase(response, pair.prefix)) {
+ String key = pair.first;
+ int beginIndex = key.length() + 1;
+ int endIndex = response.indexOf(SEPARATOR, beginIndex);
+ if (endIndex == -1)
+ endIndex = response.length();
+
+ String strValue = response.substring(beginIndex, endIndex);
+ pair.second.onUpdate(strValue);
+ listener.onKeyValue(key, strValue);
+
+ response = response.substring(endIndex);
+ if (!response.isEmpty())
+ response = response.substring(1); // skipping the separator
+ return response;
+ }
+ return response;
+ }
+
+ public static boolean startWithIgnoreCase(String line, String prefix) {
+ /**
+ * this implementation is faster than 'line.toLowerCase().startsWith(prefix)', that's important if processing
+ * huge log files
+ */
+ int pLen = prefix.length();
+ if (line.length() < pLen)
+ return false;
+ for (int i = 0; i < pLen; i++) {
+ char lineChar = Character.toLowerCase(line.charAt(i));
+ char prefixChar = prefix.charAt(i);
+ if (Character.isLetter(prefixChar) && !Character.isLowerCase(prefixChar))
+ throw new IllegalStateException("Not lower: " + prefix);
+ if (lineChar != prefixChar)
+ return false;
+ }
+ return true;
+
+ }
+
+// private char hexChar(int value) {
+// value = value & 0xF;
+// if (value < 10)
+// return (char) ('0' + value);
+// return (char) ('A' - 10 + value);
+// }
+//
+// private int parseChar(char c) {
+// if (c <= '9')
+// return c - '0';
+// return Character.toLowerCase(c) - 'a' + 10;
+// }
+
+ public void registerStringValueAction(String key, ValueCallback callback) {
+ synchronized (lock) {
+ if (keys.contains(key))
+ throw new IllegalStateException("Already registered: " + key);
+ keys.add(key);
+ actions.add(new StringActionPair(key, callback));
+ }
+ }
+
+ public void removeAction(String key) {
+ synchronized (lock) {
+ keys.remove(key);
+ for (Iterator it = actions.iterator(); it.hasNext(); ) {
+ if (it.next().first.equals(key)) {
+ it.remove();
+ break;
+ }
+ }
+ }
+ }
+
+ public void append(String append) {
+ buffer.append(append);
+ }
+
+ public interface ValueCallback {
+ void onUpdate(V value);
+ }
+
+ public interface EngineStateListener extends EngineTimeListener {
+ void beforeLine(String fullLine);
+
+ void onKeyValue(String key, String value);
+
+ void afterLine(String fullLine);
+ }
+
+ public static class EngineStateListenerImpl implements EngineStateListener {
+ public void beforeLine(String fullLine) {
+ }
+
+ @Override
+ public void onKeyValue(String key, String value) {
+ }
+
+ public void afterLine(String fullLine) {
+ }
+
+ public void onTime(double time) {
+ }
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/EngineTimeListener.java b/java_console/models/src/com/irnems/core/EngineTimeListener.java
new file mode 100644
index 0000000000..d7cb4dd098
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/EngineTimeListener.java
@@ -0,0 +1,9 @@
+package com.irnems.core;
+
+/**
+ * Date: 3/26/13
+ * (c) Andrey Belomutskiy
+ */
+public interface EngineTimeListener {
+ void onTime(double time);
+}
diff --git a/java_console/models/src/com/irnems/core/MessagesCentral.java b/java_console/models/src/com/irnems/core/MessagesCentral.java
new file mode 100644
index 0000000000..a29b3f0419
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/MessagesCentral.java
@@ -0,0 +1,45 @@
+package com.irnems.core;
+
+import com.irnems.FileLog;
+
+import javax.swing.*;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Date: 4/27/13
+ * (c) Andrey Belomutskiy
+ */
+public class MessagesCentral {
+ private static final MessagesCentral INSTANCE = new MessagesCentral();
+ private final List listeners = new CopyOnWriteArrayList();
+
+ private MessagesCentral() {
+ }
+
+ public static MessagesCentral getInstance() {
+ return INSTANCE;
+ }
+
+ public void addListener(MessageListener listener) {
+ listeners.add(listener);
+ }
+
+ public void postMessage(final Class clazz, final String message) {
+ FileLog.rlog("postMessage " + clazz.getSimpleName() + ": " + message);
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ for (MessageListener listener : listeners)
+ listener.onMessage(clazz, message);
+ }
+ });
+ }
+
+ public interface MessageListener {
+ /**
+ * all listeners are invoked on AWT thread
+ */
+ void onMessage(Class clazz, String message);
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/Pair.java b/java_console/models/src/com/irnems/core/Pair.java
new file mode 100644
index 0000000000..f21c18b0a0
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/Pair.java
@@ -0,0 +1,23 @@
+package com.irnems.core;
+
+/**
+ * @author Andrey Belomutskiy
+ * 12/26/12
+ */
+public class Pair {
+ public final K first;
+ public final V second;
+
+ public Pair(K first, V second) {
+ this.first = first;
+ this.second = second;
+ }
+
+ @Override
+ public String toString() {
+ return "Pair{" +
+ "first=" + first +
+ ", second=" + second +
+ '}';
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/ResponseBuffer.java b/java_console/models/src/com/irnems/core/ResponseBuffer.java
new file mode 100644
index 0000000000..3be30fe8ad
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/ResponseBuffer.java
@@ -0,0 +1,61 @@
+package com.irnems.core;
+
+import com.irnems.core.test.ResponseBufferTest;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * this class takes buffers input characters and separates them into full response lines
+ *
+ * Date: 12/25/12
+ * (c) Andrey Belomutskiy
+ *
+ * @see ResponseBufferTest
+ */
+public class ResponseBuffer {
+ private final ResponseListener listener;
+ private final StringBuffer pending = new StringBuffer();
+
+ public ResponseBuffer(@NotNull ResponseListener listener) {
+ this.listener = listener;
+ }
+
+ public void append(String append) {
+ pending.append(append);
+
+ /**
+ * we might have anything between one unterminated line and numerous terminated lines
+ */
+ while (hasCRLF(pending)) {
+ int cr = pending.indexOf("\r");
+ int lf = pending.indexOf("\n");
+
+ if (cr != -1 && lf == -1)
+ lf = cr;
+
+ if (cr == -1 && lf != -1)
+ cr = lf;
+
+ if (cr != -1) {
+ int endOfLine = Math.min(cr, lf);
+ if (endOfLine > 0) {
+ String fullLine = pending.substring(0, endOfLine);
+ listener.onResponse(fullLine);
+ }
+ while (pending.length() > endOfLine && (pending.charAt(endOfLine) == '\r' || pending.charAt(endOfLine) == '\n'))
+ endOfLine++;
+
+ pending.delete(0, endOfLine);
+ }
+ }
+ }
+
+ private static boolean hasCRLF(StringBuffer str) {
+ int cr = str.indexOf("\r");
+ int lf = str.indexOf("\n");
+ return cr != -1 || lf != -1;
+ }
+
+ public interface ResponseListener {
+ void onResponse(String response);
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/Sensor.java b/java_console/models/src/com/irnems/core/Sensor.java
new file mode 100644
index 0000000000..645f372d3e
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/Sensor.java
@@ -0,0 +1,104 @@
+package com.irnems.core;
+
+import eu.hansolo.steelseries.tools.BackgroundColor;
+
+/**
+ * @author Andrey Belomutskiy
+ * 2/11/13
+ */
+public enum Sensor {
+ RPM("RPM"),
+ MAP("MAP"),
+ MAP_RAW("MAP_RAW"),
+ TIMING("Timing"),
+ THROTTLE("throttle", "%", 100),
+ COOLANT("coolant", "F", 300),
+ COOLANT_WIDTH("c w", "", 30),
+
+ INTAKE_AIR("air temp", "F", 150),
+ INTAKE_AIR_WIDTH("air w", "", 30),
+
+ TABLE_SPARK("table spark", "ms", -40, 40),
+ VREF("VRef", "Volts", 6),
+ VBATT("VBatt", "Volts", 18),
+ VREF_WIDTH("VRef w", "", 1),
+ MAF("MAF", "Volts", 4),
+ DWELL0("Dwell0", "ms", 0, 30, BackgroundColor.BEIGE),
+ DWELL1("Dwell1", "ms", 0, 30, BackgroundColor.BEIGE),
+ ADVANCE0("Advance0", "dg", -40, 40, BackgroundColor.BROWN),
+ ADVANCE1("Advance1", "dg", -40, 40, BackgroundColor.BROWN),
+ PERIOD0("Period", "dg", 0, 400),
+ DUTY0("Duty0", "%", 0, 100, BackgroundColor.RED),
+ DUTY1("Duty1", "%", 0, 100, BackgroundColor.RED),
+ FUEL("Fuel", "ms", 0, 30),
+ FUEL_BASE("Fuel Base", "ms", 0, 30),
+ FUEL_IAT("F IAT", "", 0, 10),
+ FUEL_CLT("F CLT", "", 0, 10),
+ FUEL_LAG("F Lag", "", 0, 30),
+
+ IDLE_SWITCH("idle switch"),
+
+ DEFAULT_FUEL("map fuel", "ms", 0, 40),
+ T_CHARGE("T Charge", "f", 0, 200),
+ AFR("A/F ratio", "", 0, 20),
+
+ CHARTSIZE("CHARTSIZE"),
+ CHART_STATUS("CHART_STATUS"),
+ ADC_STATUS("ADC_STATUS"),
+
+ INJECTOR_0_STATUS("INJECTOR_0_STATUS"),
+ INJECTOR_1_STATUS("INJECTOR_1_STATUS"),
+ INJECTOR_2_STATUS("INJECTOR_2_STATUS"),
+ INJECTOR_3_STATUS("INJECTOR_3_STATUS"),
+
+ ADC_FAST("ADC_FAST", "b", 4000),
+ ADC_FAST_AVG("ADC_FAST_AVG", "b", 4000);
+
+ private final String name;
+ private final String units;
+ private final double minValue;
+ private final double maxValue;
+ private final BackgroundColor color;
+
+
+
+ Sensor(String name) {
+ this(name, "", 255);
+ }
+
+ Sensor(String name, String units, double maxValue) {
+ this(name, units, 0, maxValue);
+ }
+
+ Sensor(String name, String units, double minValue, double maxValue) {
+ this(name, units, minValue, maxValue, BackgroundColor.LIGHT_GRAY);
+ }
+
+ Sensor(String name, String units, double minValue, double maxValue, BackgroundColor color) {
+ this.name = name;
+ this.units = units;
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.color = color;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getUnits() {
+ return units;
+ }
+
+ public double getMinValue() {
+ return minValue;
+ }
+
+ public double getMaxValue() {
+ return maxValue;
+ }
+
+ public BackgroundColor getColor() {
+ return color;
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/SensorCentral.java b/java_console/models/src/com/irnems/core/SensorCentral.java
new file mode 100644
index 0000000000..29eebcf21f
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/SensorCentral.java
@@ -0,0 +1,88 @@
+package com.irnems.core;
+
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Date: 1/6/13
+ * (c) Andrey Belomutskiy
+ */
+public class SensorCentral {
+ private static final SensorCentral INSTANCE = new SensorCentral();
+
+ private final Map values = new EnumMap(Sensor.class);
+
+ private final Map> allListeners = new EnumMap>(Sensor.class);
+
+
+// private final List listeners = new CopyOnWriteArrayList();
+
+ public static SensorCentral getInstance() {
+ return INSTANCE;
+ }
+
+ private SensorCentral() {
+ }
+
+// public static String getTitle(int i) {
+// Sensor sensor = Sensor.findByMazdaIndex(i);
+// return "adc " + i + sensor.getName();
+// }
+
+// public String getSniffedAdcRepresentation(Sensor channel) {
+// double value = getValue(channel);
+//
+// double volts = 5 * value / 255;
+// return "" + value + " (" + volts + ")";
+// }
+
+ public double getValue(Sensor sensor) {
+ Double value = values.get(sensor);
+ if (value == null)
+ throw new NullPointerException("No value for sensor: " + sensor);
+ return value;
+ }
+
+ public void setValue(double value, Sensor sensor) {
+ values.put(sensor, value);
+ List listeners;
+ synchronized (allListeners) {
+ listeners = allListeners.get(sensor);
+ }
+ if (listeners == null)
+ return;
+ for (AdcListener listener : listeners)
+ listener.onAdcUpdate(this, value);
+ }
+
+ public static String getInternalAdcRepresentation(double value) {
+ double volts = value * 3.3 / 4096;
+ return String.format("%.2f (%.2fv)", value, volts);
+ }
+
+ public void addListener(Sensor sensor, AdcListener listener) {
+ List listeners;
+ synchronized (allListeners) {
+ listeners = allListeners.get(sensor);
+ if (listeners == null)
+ listeners = new CopyOnWriteArrayList();
+ allListeners.put(sensor, listeners);
+ }
+ listeners.add(listener);
+ }
+
+ public void removeListener(Sensor sensor, AdcListener listener) {
+ List listeners;
+ synchronized (allListeners) {
+ listeners = allListeners.get(sensor);
+ }
+ if (listeners != null)
+ listeners.remove(listener);
+ }
+
+ public interface AdcListener {
+ void onAdcUpdate(SensorCentral model, double value);
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/SensorStats.java b/java_console/models/src/com/irnems/core/SensorStats.java
new file mode 100644
index 0000000000..533247f0ac
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/SensorStats.java
@@ -0,0 +1,38 @@
+package com.irnems.core;
+
+/**
+ * 7/26/13
+ * (c) Andrey Belomutskiy
+ */
+public class SensorStats {
+ public static void start(final Sensor source, final Sensor destination) {
+
+ SensorCentral.getInstance().addListener(source, new SensorCentral.AdcListener() {
+
+ int counter;
+ double min = Double.MAX_VALUE;
+ double max = Double.MIN_VALUE;
+
+ @Override
+ public void onAdcUpdate(SensorCentral model, double value) {
+ counter++;
+
+ min = Math.min(value, min);
+ max = Math.max(value, max);
+
+ if (counter == 10) {
+ counter = 0;
+ double width = max - min;
+
+ SensorCentral.getInstance().setValue(width, destination);
+
+ min = Double.MAX_VALUE;
+ max = Double.MIN_VALUE;
+ }
+ }
+ });
+
+
+ }
+
+}
diff --git a/java_console/models/src/com/irnems/core/test/EngineStateTest.java b/java_console/models/src/com/irnems/core/test/EngineStateTest.java
new file mode 100644
index 0000000000..a2e41a564f
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/test/EngineStateTest.java
@@ -0,0 +1,52 @@
+package com.irnems.core.test;
+
+import com.irnems.core.SensorCentral;
+import com.irnems.core.EngineState;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.irnems.core.EngineState.SEPARATOR;
+import static junit.framework.Assert.*;
+
+/**
+ * @author Andrey Belomutskiy
+ * 12/26/12
+ */
+public class EngineStateTest {
+ @Test
+ public void packUnpack() {
+ String a = "rpm,100,";
+ String packed = EngineState.packString(a);
+ assertEquals("line:8:rpm,100,", packed);
+ assertEquals(a, EngineState.unpackString(packed));
+ }
+
+ @Test
+ public void startsWithIgnoreCase() {
+ assertTrue(EngineState.startWithIgnoreCase("HELLO", "he"));
+ assertFalse(EngineState.startWithIgnoreCase("HELLO", "hellllll"));
+ assertFalse(EngineState.startWithIgnoreCase("HELLO", "ha"));
+ }
+
+ @Test
+ public void testRpm() {
+ final AtomicInteger rpmResult = new AtomicInteger();
+ EngineState es = new EngineState(new EngineState.EngineStateListenerImpl() {
+ public void onKeyValue(String key, String value) {
+ if ("rpm".equals(key))
+ rpmResult.set(Integer.parseInt(value));
+ }
+ });
+ es.append("line:7:");
+ es.append(EngineState.RPM_KEY + SEPARATOR);
+ assertEquals(0, rpmResult.get());
+ es.append("600\r");
+ assertEquals(600, rpmResult.get());
+ }
+
+ @Test
+ public void testAdcRepresentation() {
+ assertEquals("1025.00 (0.83v)", SensorCentral.getInternalAdcRepresentation(1025));
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/test/ResponseBufferTest.java b/java_console/models/src/com/irnems/core/test/ResponseBufferTest.java
new file mode 100644
index 0000000000..2febc24d9d
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/test/ResponseBufferTest.java
@@ -0,0 +1,36 @@
+package com.irnems.core.test;
+
+import com.irnems.core.ResponseBuffer;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author Andrey Belomutskiy
+ * 12/26/12
+ * @see ResponseBuffer
+ */
+public class ResponseBufferTest {
+ @Test
+ public void testSingleLine() {
+ final AtomicReference currentReference = new AtomicReference();
+
+ ResponseBuffer rb = new ResponseBuffer(new ResponseBuffer.ResponseListener() {
+ public void onResponse(String response) {
+ currentReference.set(response);
+ }
+ });
+ rb.append("\r");
+ assertNull(currentReference.get());
+ rb.append("\n");
+ assertNull(currentReference.get());
+
+ rb.append("hi\n");
+ assertEquals("hi", currentReference.get());
+
+ rb.append("\r\n\r\n\r\nhi2\n\n\n");
+ assertEquals("hi2", currentReference.get());
+ }
+}
diff --git a/java_console/models/src/com/irnems/core/test/WaveReportTest.java b/java_console/models/src/com/irnems/core/test/WaveReportTest.java
new file mode 100644
index 0000000000..8e995d35d6
--- /dev/null
+++ b/java_console/models/src/com/irnems/core/test/WaveReportTest.java
@@ -0,0 +1,26 @@
+package com.irnems.core.test;
+
+import com.rusefi.waves.WaveReport;
+import com.irnems.waves.ZoomProvider;
+import org.junit.Test;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * Date: 6/23/13
+ * (c) Andrey Belomutskiy
+ */
+public class WaveReportTest {
+ public static final String report = "up!14679!down!15991!up!16823!down!18134!up!18965!down!20278!up!21108!down!22420!up!23251!down!24563!up!25394!down!26706!up!27536!down!28850!up!29678!down!30991!up!31822!down!33134!up!33965!down!35277!up!36108!down!37420!up!38251!down!39563!up!40394!down!41706!up!42537!down!43849!";
+
+ @Test
+ public void testParse() {
+ WaveReport wr = new WaveReport(report);
+ assertEquals(14, wr.getList().size());
+
+ assertEquals(14679, wr.getMinTime());
+ assertEquals(43849, wr.getMaxTime());
+
+ assertEquals(59, wr.timeToScreen(18134, 500, ZoomProvider.DEFAULT));
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/AverageData.java b/java_console/models/src/com/irnems/models/AverageData.java
new file mode 100644
index 0000000000..332c387b64
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/AverageData.java
@@ -0,0 +1,65 @@
+package com.irnems.models;
+
+import com.irnems.FileLog;
+
+import java.util.Set;
+
+/**
+ * 7/18/13
+ * (c) Andrey Belomutskiy
+ */
+public class AverageData {
+ public static XYData average(XYData data, int divider) {
+
+ double minX = data.getMinXValue();
+ double xWidth = data.getMaxXValue() - minX;
+
+ FileLog.rlog("From x" + minX + " w=" + xWidth);
+
+ XYData result = new XYData();
+
+ for (int i = 0; i < divider; i++) {
+ double fromX_ = minX + (xWidth * i) / divider;
+ double toX_ = minX + (xWidth * (i + 1)) / divider;
+
+ FileLog.rlog("from " + fromX_ + " to " + toX_);
+
+// double fromX = data.findXfromSet(fromX_);
+// double toX = data.findXfromSet(toX_);
+// System.out.println("from internal " + fromX + " to internal " + toX);
+
+ average(data, result, fromX_, toX_, divider);
+ }
+ return result;
+ }
+
+ private static void average(XYData data, XYData result, double fromX, double toX, int divider) {
+ double minY = data.getMinYValue();
+ double yWidth = data.getMaxYValue() - minY;
+
+ for (int i = 0; i < divider; i++) {
+ double fromY_ = minY + (yWidth * i) / divider;
+ double toY_ = minY + (yWidth * (i + 1)) / divider;
+
+ Set xRange = data.getXSet().tailSet(fromX).headSet(toX);
+
+ int counter = 0;
+ double acc = 0;
+ for (Double x : xRange) {
+ YAxisData yData = data.getYAxis(x);
+
+ Set yRange = yData.getYs().tailSet(fromY_).headSet(toY_);
+
+ for (double y : yRange) {
+ counter++;
+ acc += yData.getValue(y);
+ }
+ }
+
+ if (counter == 0)
+ result.addPoint(new Point3D(fromX, fromY_, Float.NaN));
+ else
+ result.addPoint(new Point3D(fromX, fromY_, (float) (acc / counter)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/java_console/models/src/com/irnems/models/Factory.java b/java_console/models/src/com/irnems/models/Factory.java
new file mode 100644
index 0000000000..0284a5fab2
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/Factory.java
@@ -0,0 +1,9 @@
+package com.irnems.models;
+
+/**
+ * @author Andrey Belomutskiy
+ * 1/29/13
+ */
+public interface Factory {
+ V create(K key);
+}
diff --git a/java_console/models/src/com/irnems/models/MafValue.java b/java_console/models/src/com/irnems/models/MafValue.java
new file mode 100644
index 0000000000..3d52b63a9d
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/MafValue.java
@@ -0,0 +1,29 @@
+package com.irnems.models;
+
+/**
+ * @author Andrey Belomutskiy
+ * 1/29/13
+ */
+public class MafValue {
+
+ private final int value;
+
+ public MafValue(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static MafValue valueOf(String value) {
+ return new MafValue(Integer.parseInt(value));
+ }
+
+ @Override
+ public String toString() {
+ return "Maf{" +
+ value +
+ '}';
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/Point3D.java b/java_console/models/src/com/irnems/models/Point3D.java
new file mode 100644
index 0000000000..64a040b188
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/Point3D.java
@@ -0,0 +1,50 @@
+package com.irnems.models;
+
+/**
+ * Date: 3/24/13
+ * (c) Andrey Belomutskiy
+ */
+public class Point3D {
+ private final double x;
+ private final double y;
+ private final float z;
+
+ public Point3D(int rpm, double key, float value) {
+ this((double) rpm, key, value);
+ }
+
+ public Point3D(double x, double y, float z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public double getY() {
+ return y;
+ }
+
+ public float getZ() {
+ return z;
+ }
+
+ @Override
+ public String toString() {
+ return "Point3D{" +
+ "x=" + x +
+ ", y=" + y +
+ ", z=" + z +
+ '}';
+ }
+
+ public static Point3D parseLine(String line) {
+ String[] tokens = line.split(",");
+ double x = Double.parseDouble(tokens[1]);
+ double y = Double.parseDouble(tokens[3]);
+ float z = Float.parseFloat(tokens[5]);
+ return new Point3D(x, y, z);
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/Range.java b/java_console/models/src/com/irnems/models/Range.java
new file mode 100644
index 0000000000..855feb952a
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/Range.java
@@ -0,0 +1,27 @@
+package com.irnems.models;
+
+/**
+ * Date: 3/24/13
+ * (c) Andrey Belomutskiy
+ */
+public class Range {
+ private final float min;
+ private final float max;
+
+ public Range(float min, float max) {
+ this.min = min;
+ this.max = max;
+ }
+
+ public float getMin() {
+ return min;
+ }
+
+ public float getMax() {
+ return max;
+ }
+
+ public float getWidth() {
+ return max - min;
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/RpmValue.java b/java_console/models/src/com/irnems/models/RpmValue.java
new file mode 100644
index 0000000000..5124a33330
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/RpmValue.java
@@ -0,0 +1,28 @@
+package com.irnems.models;
+
+/**
+ * @author Andrey Belomutskiy
+ * 1/29/13
+ */
+public class RpmValue {
+ private final int value;
+
+ public RpmValue(int value) {
+ this.value = value;
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public static RpmValue valueOf(String value) {
+ return new RpmValue(Integer.valueOf(value));
+ }
+
+ @Override
+ public String toString() {
+ return "Rpm{"
+ + value +
+ '}';
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/Utils.java b/java_console/models/src/com/irnems/models/Utils.java
new file mode 100644
index 0000000000..18012e5033
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/Utils.java
@@ -0,0 +1,43 @@
+package com.irnems.models;
+
+import java.util.Map;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+/**
+ * @author Andrey Belomutskiy
+ * 1/29/13
+ */
+public class Utils {
+ // i do not want to deal with overflow so no MAX_VALUE for me
+ private static final int LARGE_VALUE = 10000000;
+
+ /**
+ * finds a key in the map which is closest to the specified element
+ */
+ public static int findClosest(TreeMap map, int value) {
+ SortedMap head = map.headMap(value, true);
+ SortedMap tail = map.tailMap(value, true);
+ if (head.isEmpty() && tail.isEmpty())
+ throw new IllegalStateException("Empty map? " + value);
+
+ int fromHead = head.isEmpty() ? LARGE_VALUE : head.lastKey();
+ int fromTail = tail.isEmpty() ? LARGE_VALUE : tail.firstKey();
+
+ int headDiff = Math.abs(value - fromHead);
+ int tailDiff = Math.abs(value - fromTail);
+ return headDiff < tailDiff ? fromHead : fromTail;
+ }
+
+ /**
+ * gets an element from the map. if no such element - new one is created using the factory
+ */
+ public static V getOrCreate(Map map, K key, Factory factory) {
+ V result = map.get(key);
+ if (result != null)
+ return result;
+ result = factory.create(key);
+ map.put(key, result);
+ return result;
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/XYData.java b/java_console/models/src/com/irnems/models/XYData.java
new file mode 100644
index 0000000000..0c79fe8f91
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/XYData.java
@@ -0,0 +1,157 @@
+package com.irnems.models;
+
+import com.irnems.FileLog;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeMap;
+
+/**
+ * Date: 3/24/13
+ * (c) Andrey Belomutskiy
+ *
+ * @see TableGenerator
+ */
+public class XYData {
+ @NotNull
+ private final TreeMap yDatas = new TreeMap();
+ private double maxXValue;
+ private double minXValue;
+
+ private double maxYValue;
+ private double minYValue;
+
+ String date = FileLog.getDate();
+
+
+ public XYData() {
+ clear();
+ }
+
+ public float getValue(double x, double y) {
+ YAxisData yAxis = findYAxis(x);
+ if (yAxis == null)
+ return Float.NaN; // empty map?
+ return yAxis.findZ(y);
+ }
+
+ public double getMaxXValue() {
+ return maxXValue;
+ }
+
+ public double getMinXValue() {
+ return minXValue;
+ }
+
+ public double getMaxYValue() {
+ return maxYValue;
+ }
+
+ public double getMinYValue() {
+ return minYValue;
+ }
+
+ public void addPoint(int rpm, double key, float value) {
+ addPoint(new Point3D(rpm, key, value));
+ }
+
+ public void addPoint(Point3D xyz) {
+ YAxisData yAxis = getYdata(xyz);
+ yAxis.addValue(xyz.getY(), xyz.getZ());
+ }
+
+ public void setPoint(Point3D xyz) {
+ YAxisData yAxis = getYdata(xyz);
+ yAxis.setValue(xyz.getY(), xyz.getZ());
+ }
+
+ private YAxisData getYdata(Point3D xyz) {
+ minYValue = Math.min(minYValue, xyz.getY());
+ maxYValue = Math.max(maxYValue, xyz.getY());
+ return getYAxis(xyz.getX());
+ }
+
+ @NotNull
+ public NavigableSet getXSet() {
+ return yDatas.navigableKeySet();
+ }
+
+ @NotNull
+ public YAxisData getYAxis(double x) {
+ YAxisData result = yDatas.get(x);
+ if (result == null) {
+ result = new YAxisData(x);
+ maxXValue = Math.max(maxXValue, x);
+ minXValue = Math.min(minXValue, x);
+ yDatas.put(x, result);
+ }
+ return result;
+ }
+
+ @Nullable
+ public YAxisData findYAxis(double x) {
+ double xfromSet = findXfromSet(x);
+ if (Double.isNaN(xfromSet))
+ return null;
+ return yDatas.get(xfromSet);
+ }
+
+ public double findXfromSet(double x) {
+ Map.Entry floorEntry = yDatas.floorEntry(x);
+ if (floorEntry != null)
+ return floorEntry.getKey();
+ Map.Entry ceilingEntry = yDatas.ceilingEntry(x);
+ if (ceilingEntry == null)
+ return Double.NaN;
+ return ceilingEntry.getKey();
+ }
+
+ public void clear() {
+ maxXValue = Double.MIN_VALUE;
+ minXValue = Double.MAX_VALUE;
+ maxYValue = Double.MIN_VALUE;
+ minYValue = Double.MAX_VALUE;
+ yDatas.clear();
+ }
+
+ @Override
+ public String toString() {
+ return "XYData{" +
+ "yDatas.size()=" + yDatas.size() +
+ '}';
+ }
+
+ public void saveToFile(String filename) {
+ try {
+ String name = date + filename;
+ FileLog.rlog("Writing data to " + name);
+ Writer w = new FileWriter(name);
+ for (YAxisData yAxisData : yDatas.values())
+ yAxisData.write(w);
+
+ w.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void fill(Range rpmRange, Range keyRange, int count, float value) {
+ clear();
+ for (int i = 0; i < count; i++)
+ for (int j = 0; j < count; j++) {
+
+ float rpm = rpmRange.getMin() + (rpmRange.getWidth() * i / count);
+ float key = keyRange.getMin() + (keyRange.getWidth() * j / count);
+
+
+ addPoint(new Point3D(rpm, key, value));
+
+
+ }
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/XYDataReader.java b/java_console/models/src/com/irnems/models/XYDataReader.java
new file mode 100644
index 0000000000..1204af6419
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/XYDataReader.java
@@ -0,0 +1,37 @@
+package com.irnems.models;
+
+import com.irnems.FileLog;
+
+import java.io.*;
+
+/**
+ * 6/30/13
+ * (c) Andrey Belomutskiy
+ */
+public class XYDataReader {
+ public static XYData readFile(String fileName) {
+ if (!new File(fileName).exists())
+ throw new IllegalArgumentException("No file: " + fileName);
+ try {
+ return doReadFile(fileName);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static XYData doReadFile(String fileName) throws IOException {
+ BufferedReader reader = new BufferedReader(new FileReader(fileName));
+ String line;
+ XYData data = new XYData();
+ while ((line = reader.readLine()) != null) {
+ //process each line in some way
+ Point3D xyz = Point3D.parseLine(line);
+ data.addPoint(xyz);
+ }
+ FileLog.rlog("x range: " + data.getMinXValue() + " to " + data.getMaxXValue());
+ FileLog.rlog("y range: " + data.getMinYValue() + " to " + data.getMaxYValue());
+ return data;
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/YAxisData.java b/java_console/models/src/com/irnems/models/YAxisData.java
new file mode 100644
index 0000000000..5896a3055b
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/YAxisData.java
@@ -0,0 +1,109 @@
+package com.irnems.models;
+
+import com.irnems.FileLog;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeMap;
+
+/**
+ * Y>Z mapping for the same X
+ *
+ * Date: 3/24/13
+ * (c) Andrey Belomutskiy
+ */
+public class YAxisData {
+ private final TreeMap values = new TreeMap();
+ private double maxYValue = Double.MIN_VALUE;
+ private double minYValue = Double.MAX_VALUE;
+ private final double x;
+
+ public YAxisData(double x) {
+ this.x = x;
+ }
+
+ public double getX() {
+ return x;
+ }
+
+ public NavigableSet getYs() {
+ return values.navigableKeySet();
+ }
+
+ public float findZ(double y) {
+ Map.Entry entry = values.floorEntry(y);
+ if (entry != null)
+ return entry.getValue().get();
+ return values.ceilingEntry(y).getValue().get();
+ }
+
+ public void setValue(double y, float value) {
+ ValuesHolder holder = getHolder(y);
+ holder.set(value);
+ }
+
+ public void addValue(double y, float value) {
+ ValuesHolder holder = getHolder(y);
+
+ holder.add(value);
+
+ float newAvg = holder.get();
+ if (newAvg != value && !Float.isNaN(newAvg)) {
+ FileLog.rlog("new " + value + " avg " + newAvg + " for x=" + x + "/y=" + y);
+ }
+ }
+
+ private ValuesHolder getHolder(double y) {
+ minYValue = Math.min(minYValue, y);
+ maxYValue = Math.max(maxYValue, y);
+
+ ValuesHolder holder = values.get(y);
+ if (holder == null)
+ values.put(y, holder = new ValuesHolder());
+ return holder;
+ }
+
+ public double getValue(double y) {
+ return values.get(y).get();
+ }
+
+ @Override
+ public String toString() {
+ return "YAxisData{" +
+ "size=" + values.size() +
+ ", maxYValue=" + maxYValue +
+ ", minYValue=" + minYValue +
+ ", x=" + x +
+ '}';
+ }
+
+ public void write(Writer w) throws IOException {
+ for (Map.Entry e : values.entrySet())
+ w.write("rpm," + x + ",key," + e.getKey() + ",value," + e.getValue().get() + "\r\n");
+ }
+
+ private static class ValuesHolder {
+
+ private float total;
+ private int count;
+
+ private ValuesHolder() {
+ }
+
+ public float get() {
+ return total / count;
+ }
+
+ public void add(float value) {
+ total += value;
+ count++;
+ }
+
+ public void set(float value) {
+ total = value;
+ count = 1;
+ }
+ }
+}
diff --git a/java_console/models/src/com/irnems/models/test/UtilTest.java b/java_console/models/src/com/irnems/models/test/UtilTest.java
new file mode 100644
index 0000000000..479413a901
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/test/UtilTest.java
@@ -0,0 +1,30 @@
+package com.irnems.models.test;
+
+import com.irnems.models.Utils;
+import org.junit.Test;
+
+import java.util.TreeMap;
+
+import static com.irnems.models.Utils.findClosest;
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * @author Andrey Belomutskiy
+ * 1/29/13
+ */
+public class UtilTest {
+ @Test
+ public void testClosest() {
+ TreeMap map = new TreeMap();
+ map.put(0, "0");
+ map.put(1, "1");
+ map.put(10, "10");
+ map.put(11, "11");
+
+ assertEquals(0, findClosest(map, -1));
+ assertEquals(0, findClosest(map, 0));
+ assertEquals(1, findClosest(map, 1));
+ assertEquals(1, findClosest(map, 3));
+ assertEquals(10, findClosest(map, 10)); }
+
+}
diff --git a/java_console/models/src/com/irnems/models/test/XYDataSandbox.java b/java_console/models/src/com/irnems/models/test/XYDataSandbox.java
new file mode 100644
index 0000000000..114b017f9a
--- /dev/null
+++ b/java_console/models/src/com/irnems/models/test/XYDataSandbox.java
@@ -0,0 +1,20 @@
+package com.irnems.models.test;
+
+import com.irnems.models.XYData;
+
+/**
+ * 7/24/13
+ * (c) Andrey Belomutskiy
+ */
+public class XYDataSandbox {
+ public static void main(String[] args) {
+ XYData d = new XYData();
+
+ d.addPoint(600, 3, 11);
+ d.addPoint(600, 3.1, 11);
+
+ d.addPoint(1600, 3.1, 11);
+
+ d.saveToFile("_mult.dat");
+ }
+}
diff --git a/java_console/models/src/com/irnems/test/HistogramsTest.java b/java_console/models/src/com/irnems/test/HistogramsTest.java
new file mode 100644
index 0000000000..6c2c959e18
--- /dev/null
+++ b/java_console/models/src/com/irnems/test/HistogramsTest.java
@@ -0,0 +1,51 @@
+package com.irnems.test;
+
+import com.irnems.Histograms;
+import org.junit.Test;
+
+import java.util.Random;
+
+import static junit.framework.Assert.assertEquals;
+
+/**
+ * Created by pc on 12/18/13.
+ */
+public class HistogramsTest {
+
+ @Test
+ public void testHistogram() {
+ Histograms h = new Histograms();
+
+
+ String key = "hello";
+ Histograms.ValueType type = Histograms.ValueType.INVOCATION;
+ h.addValue(type, key, 30);
+
+
+ Histograms.LocalStats ls = h.local_stats.get();
+
+ Histograms.StatisticsGroup sg = ls.stats.get(type);
+
+ Histograms.Statistics data = sg.data.get(key);
+
+ assertEquals(40, data.histogram.length);
+
+
+ assertEquals(80, h.getIndex(239));
+ assertEquals(223, h.getIndex(239239));
+ assertEquals(364, h.getIndex(239239239));
+
+
+ Random r = new Random();
+ for (int i = 0; i < 2342334; i++)
+ h.addValue(type, key, r.nextInt());
+
+
+ assertEquals(640, data.histogram.length);
+
+
+ System.out.println(h.dumpStats());
+
+ }
+
+}
diff --git a/java_console/models/src/com/irnems/waves/TimeAxisTranslator.java b/java_console/models/src/com/irnems/waves/TimeAxisTranslator.java
new file mode 100644
index 0000000000..5501c003d2
--- /dev/null
+++ b/java_console/models/src/com/irnems/waves/TimeAxisTranslator.java
@@ -0,0 +1,15 @@
+package com.irnems.waves;
+
+/**
+ * Date: 6/25/13
+ * (c) Andrey Belomutskiy
+ */
+public interface TimeAxisTranslator {
+ int timeToScreen(int time, int width, ZoomProvider zoomProvider);
+
+ double screenToTime(int screen, int width, ZoomProvider zoomProvider);
+
+ int getMaxTime();
+
+ int getMinTime();
+}
diff --git a/java_console/models/src/com/irnems/waves/ZoomProvider.java b/java_console/models/src/com/irnems/waves/ZoomProvider.java
new file mode 100644
index 0000000000..17ecf5b31b
--- /dev/null
+++ b/java_console/models/src/com/irnems/waves/ZoomProvider.java
@@ -0,0 +1,16 @@
+package com.irnems.waves;
+
+/**
+ * 7/7/13
+ * (c) Andrey Belomutskiy
+ */
+public interface ZoomProvider {
+ ZoomProvider DEFAULT = new ZoomProvider() {
+ @Override
+ public double getZoomValue() {
+ return 1;
+ }
+ };
+
+ double getZoomValue();
+}
diff --git a/java_console/models/src/com/rusefi/waves/RevolutionLog.java b/java_console/models/src/com/rusefi/waves/RevolutionLog.java
new file mode 100644
index 0000000000..142fc61022
--- /dev/null
+++ b/java_console/models/src/com/rusefi/waves/RevolutionLog.java
@@ -0,0 +1,71 @@
+package com.rusefi.waves;
+
+import java.util.*;
+
+/**
+ * 1/11/14.
+ * (c) Andrey Belomutskiy
+ */
+public class RevolutionLog {
+ public static final String TOP_DEAD_CENTER_MESSAGE = "r";
+ private final TreeMap time2rpm;
+
+ public RevolutionLog(TreeMap time2rpm) {
+ this.time2rpm = time2rpm;
+ }
+
+ public static RevolutionLog parseRevolutions(CharSequence revolutions) {
+ TreeMap time2rpm = new TreeMap();
+ if (revolutions == null)
+ return new RevolutionLog(time2rpm);
+
+ String[] r = revolutions.toString().split("!");
+ for (int i = 0; i < r.length - 1; i += 2) {
+ int rpm = Integer.parseInt(r[i]);
+ int time = Integer.parseInt(r[i + 1]);
+ time2rpm.put(time, rpm);
+ }
+ return new RevolutionLog(time2rpm);
+ }
+
+ public String getCrankAngleByTimeString(double time) {
+ double result = getCrankAngleByTime(time);
+ return Double.isNaN(result) ? "n/a" : String.format("%.2f", result);
+ }
+
+ public double getCrankAngleByTime(double time) {
+ return doGetAngle(time, true);
+ }
+
+ private double doGetAngle(double time, boolean tryNextRevolution) {
+ Map.Entry entry = getTimeAndRpm(time);
+ if (entry == null) {
+ if (tryNextRevolution && time2rpm.size() >= 2) {
+ // we are here if the value is below the first revolution point
+ List> element = new ArrayList>(time2rpm.entrySet());
+ Map.Entry first = element.get(0);
+ Map.Entry second = element.get(1);
+
+ int oneRevolutionDuration = second.getKey() - first.getKey();
+ return doGetAngle(time + oneRevolutionDuration, false);
+ } else {
+ return Double.NaN;
+ }
+ } else {
+ double diff = time - entry.getKey();
+
+ Integer rpm = entry.getValue();
+ double timeForRevolution = 60000 * WaveReport.SYS_TICKS_PER_MS / rpm;
+
+ return 360.0 * diff / timeForRevolution;
+ }
+ }
+
+ public Map.Entry getTimeAndRpm(double time) {
+ return time2rpm.floorEntry((int) time);
+ }
+
+ public Set keySet() {
+ return time2rpm.keySet();
+ }
+}
diff --git a/java_console/models/src/com/rusefi/waves/WaveChart.java b/java_console/models/src/com/rusefi/waves/WaveChart.java
new file mode 100644
index 0000000000..0dea8b1415
--- /dev/null
+++ b/java_console/models/src/com/rusefi/waves/WaveChart.java
@@ -0,0 +1,34 @@
+package com.rusefi.waves;
+
+import java.util.Map;
+
+/**
+ * A collection of {@link WaveReport}
+ *
+ *
+ *
+ * @see WaveChartParser
+ *
+ * Date: 3/17/14
+ * (c) Andrey Belomutskiy
+ */
+public class WaveChart {
+ public static final String INJECTOR_1 = "Injector 1";
+ public static final String INJECTOR_2 = "Injector 2";
+ public static final String INJECTOR_3 = "Injector 3";
+ public static final String INJECTOR_4 = "Injector 4";
+ public static final String SPARK_1 = "Spark 1";
+ public static final String SPARK_2 = "Spark 2";
+ public static final String SPARK_3 = "Spark 3";
+ public static final String SPARK_4 = "Spark 4";
+
+ public final Map map;
+
+ public WaveChart(Map map) {
+ this.map = map;
+ }
+
+ public StringBuilder get(String key) {
+ return map.get(key);
+ }
+}
diff --git a/java_console/models/src/com/rusefi/waves/WaveChartParser.java b/java_console/models/src/com/rusefi/waves/WaveChartParser.java
new file mode 100644
index 0000000000..3011eaf41a
--- /dev/null
+++ b/java_console/models/src/com/rusefi/waves/WaveChartParser.java
@@ -0,0 +1,43 @@
+package com.rusefi.waves;
+
+import com.irnems.FileLog;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 1/26/14
+ * Andrey Belomutskiy (c) 2012-2014
+ */
+public class WaveChartParser {
+ private static final String DELI = "!";
+
+ /**
+ * This method unpacks a mixed-key message into a Map of messages by key
+ */
+ public static WaveChart unpackToMap(String value) {
+ FileLog.rlog(": " + value);
+
+ String[] array = value.split(DELI);
+
+ Map map = new HashMap();
+
+ int index = 0;
+ while (index + 2 < array.length) {
+ String name = array[index];
+
+ StringBuilder sb = map.get(name);
+ if (sb == null) {
+ sb = new StringBuilder();
+ map.put(name, sb);
+ }
+
+ String signal = array[index + 1];
+ String val = array[index + 2];
+
+ sb.append(signal).append(DELI).append(val).append(DELI);
+ index += 3;
+ }
+ return new WaveChart(map);
+ }
+}
diff --git a/java_console/models/src/com/rusefi/waves/WaveReport.java b/java_console/models/src/com/rusefi/waves/WaveReport.java
new file mode 100644
index 0000000000..ecab46ce4a
--- /dev/null
+++ b/java_console/models/src/com/rusefi/waves/WaveReport.java
@@ -0,0 +1,166 @@
+package com.rusefi.waves;
+
+import com.irnems.waves.TimeAxisTranslator;
+import com.irnems.waves.ZoomProvider;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Date: 6/23/13
+ * (c) Andrey Belomutskiy
+ */
+public class WaveReport implements TimeAxisTranslator {
+ public static final String WAVE_CHART = "wave_chart";
+ public static final WaveReport MOCK = new WaveReport(Collections.singletonList(new UpDown(0, -1, 1, -1)));
+ /**
+ * number of ChibiOS systicks per ms
+ */
+ public static final double SYS_TICKS_PER_MS = 100;
+ public static final int mult = (int) (100 * SYS_TICKS_PER_MS); // 100ms
+
+ List list;
+ private int maxTime;
+ /**
+ * min timestamp on this chart, in systicks
+ */
+ private int minTime;
+
+ public WaveReport(String report) {
+ this(parse(report));
+ }
+
+ public WaveReport(List list) {
+ if (list.isEmpty())
+ throw new IllegalStateException("empty");
+ this.list = list;
+ minTime = list.get(0).upTime;
+ maxTime = list.get(list.size() - 1).downTime;
+ }
+
+ public static boolean isCloseEnough(double v1, double v2) {
+ if (v2 == 0)
+ return v1 == 0;
+ double ratio = v1 / v2;
+ return Math.abs(1 - ratio) < 0.05;
+ }
+
+ public List getList() {
+ return list;
+ }
+
+ @Override
+ public int getMaxTime() {
+ return maxTime;
+ }
+
+ @Override
+ public int getMinTime() {
+ return minTime;
+ }
+
+ public static List parse(String report) {
+ String[] array = report.split("!");
+// if (array.length % 4 != 0)
+// throw new IllegalArgumentException("Unexpected length " + array.length);
+
+ List times = new ArrayList();
+
+ int index = 0;
+ if (array[0].equals("down"))
+ index += 2;
+
+ while (index + 3 < array.length) {
+ if (!array[index].equals("up")) {
+ index += 2;
+ continue;
+ }
+ if (!array[index + 2].equals("down")) {
+ index += 2;
+ continue;
+ }
+
+ String upString[] = array[index + 1].split("_");
+ String downString[] = array[index + 3].split("_");
+ try {
+ int upTime = Integer.parseInt(upString[0]);
+ int downTime = Integer.parseInt(downString[0]);
+
+ int upIndex = upString.length > 1 ? Integer.parseInt(upString[1]) : -1;
+ int downIndex = downString.length > 1 ? Integer.parseInt(downString[1]) : -1;
+
+
+ times.add(new UpDown(upTime, upIndex, downTime, downIndex));
+ } catch (NumberFormatException e) {
+ System.err.println("Invalid? [" + upString + "][" + downString + "]");
+ }
+
+ index += 4;
+ }
+ return times;
+ }
+
+ @Override
+ public int timeToScreen(int time, int width, ZoomProvider zoomProvider) {
+ double translated = (time - minTime) * zoomProvider.getZoomValue() / getDuration();
+ return (int) (width * translated);
+ }
+
+ @Override
+ public double screenToTime(int screen, int width, ZoomProvider zoomProvider) {
+ // / SYS_TICKS_PER_MS / 1000
+ double time = 1.0 * screen * getDuration() / width / zoomProvider.getZoomValue() + minTime;
+ int x2 = timeToScreen((int) time, width, zoomProvider);
+// FileLog.rlog("screenToTime " + (screen - x2));
+ return (int) time;
+ }
+
+ /**
+ * @return Length of this chart ini systicks
+ */
+ public int getDuration() {
+ return maxTime - minTime;
+ }
+
+ @Override
+ public String toString() {
+ return "WaveReport{" +
+ "size=" + list.size() +
+ ", maxTime=" + maxTime +
+ ", minTime=" + minTime +
+ '}';
+ }
+
+ public static class UpDown {
+ // times in sys ticks
+ public final int upTime;
+ public final int upIndex;
+ public final int downTime;
+ public final int downIndex;
+
+ UpDown(int upTime, int upIndex, int downTime, int downIndex) {
+ this.upTime = upTime;
+ this.upIndex = upIndex;
+ this.downTime = downTime;
+ this.downIndex = downIndex;
+ }
+
+ public int getDuration() {
+ return downTime - upTime;
+ }
+
+ @Override
+ public String toString() {
+ return "UpDown{" +
+ "upTime=" + upTime +
+ ", downTime=" + downTime +
+ '}';
+ }
+
+ public double getDutyCycle(RevolutionLog rl) {
+ double angleDuration = (rl.getCrankAngleByTime(downTime) + 360 - rl.getCrankAngleByTime(upTime)) % 260;
+ return angleDuration / 360;
+ }
+ }
+}
diff --git a/java_console/models/src/com/rusefi/waves/test/RevolutionLogTest.java b/java_console/models/src/com/rusefi/waves/test/RevolutionLogTest.java
new file mode 100644
index 0000000000..9cbfa14cc3
--- /dev/null
+++ b/java_console/models/src/com/rusefi/waves/test/RevolutionLogTest.java
@@ -0,0 +1,21 @@
+package com.rusefi.waves.test;
+
+import com.rusefi.waves.RevolutionLog;
+import junit.framework.Assert;
+import org.junit.Test;
+
+/**
+ * Andrey Belomutskiy (c) 2012-2014
+ * 3/19/14
+ */
+public class RevolutionLogTest {
+ @Test
+ public void backTime() {
+ RevolutionLog r = RevolutionLog.parseRevolutions("2000!148958!2000!154958!2000!160958!2000!166958!");
+
+ Assert.assertEquals(594.84, r.getCrankAngleByTime(147915));
+
+ // too back into the past
+ Assert.assertEquals(Double.NaN, r.getCrankAngleByTime(140915));
+ }
+}
diff --git a/java_console/models/src/com/rusefi/waves/test/WaveChartParserTest.java b/java_console/models/src/com/rusefi/waves/test/WaveChartParserTest.java
new file mode 100644
index 0000000000..f5d1c0d6c4
--- /dev/null
+++ b/java_console/models/src/com/rusefi/waves/test/WaveChartParserTest.java
@@ -0,0 +1,64 @@
+package com.rusefi.waves.test;
+
+import com.rusefi.waves.RevolutionLog;
+import com.rusefi.waves.WaveChart;
+import com.rusefi.waves.WaveChartParser;
+import com.rusefi.waves.WaveReport;
+import org.junit.Test;
+
+import java.util.List;
+
+import static com.rusefi.waves.WaveReport.isCloseEnough;
+import static junit.framework.Assert.*;
+
+/**
+ * 1/26/14
+ * Andrey Belomutskiy (c) 2012-2014
+ */
+public class WaveChartParserTest {
+ @Test
+ public void testMultiParser() {
+ WaveChart result = WaveChartParser.unpackToMap("");
+ assertTrue(result.map.isEmpty());
+
+ result = WaveChartParser.unpackToMap("wave_chart,input1 A8!up!185080!r!0!2670996!crank!up!2670996_2!crank2!up!2674512!Injector 4!up!2674512!Spark 1!up!2674512!Injector 4!down!2674665!Spark 1!down!2674912!crank2!down!2680055!crank2!up!2687963!Injector 2!up!2687963!Spark 1!up!2687963!Injector 2!down!2688116!Spark 1!down!2688363!crank2!down!2693093!crank!down!2697428!crank2!up!2700454!Injector 1!up!2700454!Spark 1!up!2700454!Injector 1!down!2700607!Spark 1!down!2700854!crank2!down!2705329!crank2!up!2712449!Injector 3!up!2712449!Spark 1!up!2712449!Injector 3!down!2712681!Spark 1!down!2712849!crank2!down!2717385!r!0!2721629!crank!up!2721629!crank2!up!2724641!Injector 1!up!2724641!Injector 2!up!2724641!Injector 3!up!2724641!Injector 4!up!2724641!Spark 1!up!2724641!Injector 4!down!2726241!Injector 3!down!2726241!Injector 2!down!2726241!Injector 1!down!2726241!crank2!down!2729677!Spark 1!down!2730008!crank2!up!2736851!Injector 1!up!2736851!Injector 2!up!2736851!Injector 3!up!2736851!Injector 4!up!2736851!Spark 1!up!2736851!Injector 4!down!2738451!Injector 3!down!2738451!Injector 2!down!2738451!Injector 1!down!2738451!crank2!down!2741922!Spark 1!down!2742218!crank!down!2746104!crank2!up!2749010!Injector 1!up!2749010!Injector 2!up!2749010!Injector 3!up!2749010!Injector 4!up!2749010!Spark 1!up!2749010!Injector 4!down!2750601!Injector 3!down!2750601!Injector 2!down!2750601!Injector 1!down!2750601!crank2!down!2753919!Spark 1!down!2754377!crank2!up!2760922!Injector 1!up!2760922!Injector 2!up!2760922!Injector 3!up!2760922!Injector 4!up!2760922!Spark 1!up!2760922!Injector 4!down!2762522!Injector 3!down!2762522!Injector 2!down!2762522!Injector 1!down!2762522!crank2!down!2765882!Spark 1!down!2766289!r!236!2769990!crank!up!2769990!crank2!up!2773003!Injector 1!up!2773003!Injector 2!up!2773003!Injector 3!up!2773003!Injector 4!up!2773003!Spark 1!up!2773003!Injector 4!down!2774603!Injector 3!down!2774603!Injector 2!down!2774603!Injector 1!down!2774603!Spark 1!down!2778110!crank2!down!2778143!crank2!up!2785215!Injector 1!up!2785215!Injector 2!up!2785215!Injector 3!up!2785215!Injector 4!up!2785215!Spark 1!up!2785215!Injector 4!down!2786815!,");
+ assertEquals(9, result.map.size());
+
+ String crankReport = result.get("crank").toString();
+
+ List list = WaveReport.parse(crankReport);
+ assertEquals(2, list.size());
+
+ WaveReport.UpDown upDown = list.get(0);
+ assertEquals(2670996, upDown.upTime);
+ assertEquals(2, upDown.upIndex);
+
+ assertEquals(2697428, upDown.downTime);
+ assertEquals(-1, upDown.downIndex);
+ }
+
+ @Test
+ public void testDutyCycle() {
+ WaveChart result = WaveChartParser.unpackToMap("r!1199!64224414!crank2!up!64225149_3!Injector 2!up!64225149!Spark 1!up!64225249!Injector 2!down!64225303!Spark 1!down!64225649!crank2!down!64226105_4!crank!down!64226980_5!crank2!up!64227730_6!Injector 1!up!64227730!Spark 1!up!64227830!Injector 1!down!64227884!Spark 1!down!64228230!crank2!down!64228678_7!crank2!up!64230212_8!Injector 3!up!64230212!Spark 1!up!64230312!Injector 3!down!64230366!Spark 1!down!64230712!crank2!down!64231156_9!crank!up!64231982_0!crank2!up!64232672_1!Injector 4!up!64232672!Spark 1!up!64232772!Injector 4!down!64232826!Spark 1!down!64233172!crank2!down!64233626_2!r!1200!64234412!crank2!up!64235150_3!Injector 2!up!64235150!Spark 1!up!64235250!Injector 2!down!64235304!Spark 1!down!64235650!crank2!down!64236106_4!crank!down!64236981_5!crank2!up!64237730_6!Injector 1!up!64237730!Spark 1!up!64237830!Injector 1!down!64237884!Spark 1!down!64238230!crank2!down!64238677_7!crank2!up!64240213_8!Injector 3!up!64240213!Spark 1!up!64240313!Injector 3!down!64240367!Spark 1!down!64240713!crank2!down!64241158_9!crank!up!64241982_0!crank2!up!64242674_1!Injector 4!up!64242674!Spark 1!up!64242774!Injector 4!down!64242828!Spark 1!down!64243174!crank2!down!64243625_2!r!1200!64244412!crank2!up!64245149_3!Injector 2!up!64245149!Spark 1!up!64245249!Injector 2!down!64245303!Spark 1!down!64245649!crank2!down!64246106_4!crank!down!64246980_5!crank2!up!64247728_6!Injector 1!up!64247728!Spark 1!up!64247828!Injector 1!down!64247882!Spark 1!down!64248228!crank2!down!64248679_7!crank2!up!64250212_8!Injector 3!up!64250212!Spark 1!up!64250312!Injector 3!down!64250366!Spark 1!down!64250712!crank2!down!64251158_9!crank!up!64251982_0!crank2!up!64252674_1!Injector 4!up!64252674!Spark 1!up!64252774!Injector 4!down!64252828!Spark 1!down!64253174!crank2!down!64253625_2!r!1200!64254412!crank2!up!64255150_3!Injector 2!up!64255150!Spark 1!up!64255250!Injector 2!down!64255304!Spark 1!down!64255650!crank2!down!64256106_4!crank!down!64256982_5!crank2!up!64257728_6!Injector 1!up!64257728!Spark 1!up!64257828!Injector 1!down!64257882!Spark 1!down!64258228!crank2!down!64258678_7!crank2!up!64260214_8!Injector 3!up!64260214!Spark 1!up!64260314!Injector 3!down!64260368!Spark 1!down!64260714!,");
+ assertFalse(result.map.isEmpty());
+
+ StringBuilder revolutions = result.get(RevolutionLog.TOP_DEAD_CENTER_MESSAGE);
+
+ RevolutionLog rl = RevolutionLog.parseRevolutions(revolutions);
+
+ StringBuilder inj1 = result.get(WaveChart.INJECTOR_1);
+ WaveReport wr = new WaveReport(inj1.toString());
+
+ for (WaveReport.UpDown ud : wr.getList()) {
+ assertTrue(isCloseEnough(238.75, rl.getCrankAngleByTime(ud.upTime)));
+
+ assertTrue(isCloseEnough(0.308, ud.getDutyCycle(rl)));
+ }
+ }
+
+ @Test
+ public void testUpOnly() {
+ List list = WaveReport.parse("up!15500!up!25500!up!35500!up!45500!up!55500!up!65500!up!75500!up!85500!");
+ assertEquals(0, list.size());
+ }
+}
diff --git a/java_console/tools/src/com/irnems/AdcFilter.java b/java_console/tools/src/com/irnems/AdcFilter.java
new file mode 100644
index 0000000000..12bb08b9ea
--- /dev/null
+++ b/java_console/tools/src/com/irnems/AdcFilter.java
@@ -0,0 +1,60 @@
+package com.irnems;
+
+import com.irnems.core.EngineState;
+import com.irnems.file.FileUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * @author Andrey Belomutskiy
+ * 8/5/13
+ */
+public class AdcFilter {
+ public static void main(String[] args) {
+ String filename = "a.csv";
+
+ final Map map = new TreeMap();
+
+ final List values = new ArrayList();
+
+ EngineState.EngineStateListener listener = new EngineState.EngineStateListenerImpl() {
+ @Override
+ public void onKeyValue(String key, String value) {
+ map.put(key, value);
+
+// boolean isOurValue = key.equals("clt");
+ boolean isOurValue = key.equals("mat");
+ if (isOurValue) {
+ double newValue = Double.valueOf(value);
+ if (values.isEmpty() || values.get(values.size() - 1) != newValue)
+ values.add(newValue);
+ }
+ }
+
+ @Override
+ public void afterLine(String fullLine) {
+ map.clear();
+ }
+ };
+
+ EngineState engineState = new EngineState(listener);
+
+ FileUtils.readFile2(filename, engineState);
+
+ System.out.println("Got " + values.size() + " values");
+
+ FileUtils.saveList("v2.csv", values);
+
+
+ final List medianValues = MedialFilter.filter(values, 5);
+ FileUtils.saveList("v_med.csv", medianValues);
+
+
+ final List filtered = SimpleFilter.filter(medianValues, 100);
+ FileUtils.saveList("v_fil.csv", filtered);
+
+ }
+}
diff --git a/java_console/tools/src/com/irnems/MedialFilter.java b/java_console/tools/src/com/irnems/MedialFilter.java
new file mode 100644
index 0000000000..fd259d4c50
--- /dev/null
+++ b/java_console/tools/src/com/irnems/MedialFilter.java
@@ -0,0 +1,48 @@
+package com.irnems;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author Andrey Belomutskiy
+ * 8/5/13
+ */
+public class MedialFilter {
+
+
+ private List values;
+ private int size;
+
+
+
+
+ public MedialFilter(List values, int size) {
+ this.values = values;
+ this.size = size;
+ }
+
+ public static List filter(List values, int size) {
+ return new MedialFilter(values, size).filter();
+ }
+
+ private List filter() {
+
+ List result = new ArrayList();
+
+ for(int i=0;i copy = new ArrayList(values.subList(fromIndex, i+1));
+
+ Collections.sort(copy);
+
+ result.add(copy.get(copy.size() / 2));
+
+ }
+
+
+ return result;
+ }
+}
diff --git a/java_console/tools/src/com/irnems/SimpleFilter.java b/java_console/tools/src/com/irnems/SimpleFilter.java
new file mode 100644
index 0000000000..ab0921c24c
--- /dev/null
+++ b/java_console/tools/src/com/irnems/SimpleFilter.java
@@ -0,0 +1,33 @@
+package com.irnems;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Andrey Belomutskiy
+ * 8/5/13
+ */
+public class SimpleFilter {
+ public static List filter(List medianValues, int K) {
+ List result = new ArrayList();
+
+ Double firstValue = medianValues.get(0);
+
+ double Dacc = firstValue * K;
+ double Dout = firstValue;
+
+
+// for (int i = 0; i < 3 * K; i++) {
+// Dacc = Dacc + firstValue - Dout;
+// Dout = Dacc / K;
+// }
+
+ for (double Din : medianValues) {
+ Dacc = Dacc + Din - Dout;
+ Dout = Dacc / K;
+ result.add(Dout);
+ }
+
+ return result;
+ }
+}
diff --git a/java_console/tools/src/com/irnems/Test.java b/java_console/tools/src/com/irnems/Test.java
new file mode 100644
index 0000000000..2b9b98ed66
--- /dev/null
+++ b/java_console/tools/src/com/irnems/Test.java
@@ -0,0 +1,19 @@
+package com.irnems;
+
+import jssc.SerialPortList;
+
+/**
+ * Date: 12/25/12
+ * (c) Andrey Belomutskiy
+ */
+public class Test {
+
+ public static void main(String[] args) {
+ String[] portNames = SerialPortList.getPortNames();
+ System.out.println("Total " + portNames.length + " serial port(s)");
+ for (String portName : portNames) {
+ System.out.println(portName);
+ }
+ System.exit(0);
+ }
+}
diff --git a/java_console/tools/src/com/irnems/file/FrequencyDivider.java b/java_console/tools/src/com/irnems/file/FrequencyDivider.java
new file mode 100644
index 0000000000..6102d0aab9
--- /dev/null
+++ b/java_console/tools/src/com/irnems/file/FrequencyDivider.java
@@ -0,0 +1,43 @@
+package com.irnems.file;
+
+import com.irnems.core.EngineState;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Date: 3/8/13
+ * (c) Andrey Belomutskiy
+ */
+public class FrequencyDivider {
+ public static void main(String[] args) throws IOException {
+ String filename = args.length > 1 ? args[0] : "a.csv";
+ System.out.println("Reading from " + filename);
+
+ final int divider = 10;
+
+ String output = divider + "_" + filename;
+ System.out.println("Writing to " + output);
+ final FileOutputStream fos = new FileOutputStream(output);
+
+
+ EngineState.EngineStateListener listener = new EngineState.EngineStateListenerImpl() {
+ int lineCounter = 0;
+
+ @Override
+ public void beforeLine(String fullLine) {
+ if (lineCounter < 5 || lineCounter % divider == 0) {
+ String w = fullLine + "\r\n";
+ try {
+ fos.write(w.getBytes());
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ lineCounter++;
+ }
+ };
+ FileUtils.readFile(filename, listener);
+ fos.close();
+ }
+}
diff --git a/java_console/tools/tools.iml b/java_console/tools/tools.iml
new file mode 100644
index 0000000000..7d2eb6cc61
--- /dev/null
+++ b/java_console/tools/tools.iml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/java_console/ui/src/com/irnems/ChartRepository.java b/java_console/ui/src/com/irnems/ChartRepository.java
new file mode 100644
index 0000000000..1adb0507e1
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ChartRepository.java
@@ -0,0 +1,93 @@
+package com.irnems;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * 7/27/13
+ * (c) Andrey Belomutskiy
+ */
+public class ChartRepository {
+ private static final ChartRepository instance = new ChartRepository();
+
+ private final List charts = new ArrayList();
+
+ private ChartRepository() {
+ }
+
+ public static ChartRepository getInstance() {
+ return instance;
+ }
+
+ public String getChart(int index) {
+ return charts.get(index);
+ }
+
+ public int getSize() {
+ return charts.size();
+ }
+
+ public void addChart(String value) {
+ charts.add(value);
+ }
+
+ public void clear() {
+ charts.clear();
+ }
+
+ public interface CRListener {
+ public void onDigitalChart(String chart);
+ }
+
+ public JComponent createControls(final CRListener listener) {
+
+ final AtomicInteger index = new AtomicInteger();
+
+ JPanel result = new JPanel(new FlowLayout(FlowLayout.RIGHT, 5, 0));
+ result.setBorder(BorderFactory.createLineBorder(Color.red));
+
+ final JLabel info = new JLabel();
+
+ setInfoText(index, info);
+
+ JButton prev = new JButton("<");
+ prev.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (index.intValue() > 0) {
+ index.decrementAndGet();
+ listener.onDigitalChart(charts.get(index.get()));
+ setInfoText(index, info);
+ }
+ }
+ });
+
+
+ JButton next = new JButton(">");
+ next.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (index.intValue() < getSize() - 1) {
+ index.incrementAndGet();
+ listener.onDigitalChart(charts.get(index.get()));
+ setInfoText(index, info);
+ }
+ }
+ });
+
+ result.add(prev);
+ result.add(info);
+ result.add(next);
+
+ return result;
+ }
+
+ private void setInfoText(AtomicInteger index, JLabel info) {
+ info.setText(index.get() + "/" + getSize());
+ }
+}
diff --git a/java_console/ui/src/com/irnems/EcuStimulator.java b/java_console/ui/src/com/irnems/EcuStimulator.java
new file mode 100644
index 0000000000..0c5c51cc63
--- /dev/null
+++ b/java_console/ui/src/com/irnems/EcuStimulator.java
@@ -0,0 +1,226 @@
+package com.irnems;
+
+import com.irnems.core.MessagesCentral;
+import com.irnems.core.Sensor;
+import com.irnems.core.EngineTimeListener;
+import com.irnems.core.SensorCentral;
+import com.irnems.file.TableGenerator;
+import com.irnems.models.Point3D;
+import com.irnems.models.Range;
+import com.irnems.models.XYData;
+import com.irnems.ui.ChartHelper;
+import com.irnems.ui.RpmModel;
+import com.irnems.ui.widgets.PotCommand;
+import com.irnems.ui.widgets.RpmCommand;
+import com.rusefi.io.LinkManager;
+import net.ericaro.surfaceplotter.DefaultSurfaceModel;
+
+import javax.swing.*;
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Date: 3/24/13
+ * (c) Andrey Belomutskiy
+ */
+public class EcuStimulator {
+ // private static final String TITLE = "Spark Advance";
+ private static final String TITLE = "Fuel Table";
+
+ private static final String D = ",";
+ private static final long SLEEP_TIME = 300;
+
+ private static final float MAF_MIN = 1.2f;
+ private static final float MAF_MAX = 4.5f;
+ private static final double MAF_INCREMENT = 0.1;
+
+ private static final int RPM_MIN = 400;
+ private static final int RPM_MAX = 6000;
+ private static final int RPM_INCREMENT = 250;
+ private static final Sensor DWELL_SENSOR = Sensor.DWELL1;
+
+ private static final String TABLE_FILE_NAME = "table" + RPM_INCREMENT + "_" + MAF_INCREMENT + ".csv";
+
+ private static final int MEASURES = 7;
+ // private static final String C_FILE_NAME = "advance_map.c";
+// private static final String C_PREFIX = "ad_";
+ private static final String C_FILE_NAME = "fuel_map.c";
+ private static final String C_PREFIX = "fuel_";
+
+ public static Range RPM_RANGE = new Range(0, RPM_MAX); // x-coord
+ static Range mafRange = new Range(MAF_MIN, MAF_MAX); // y-coord
+ private static XYData data = new XYData();
+ private static DefaultSurfaceModel model = ChartHelper.createDefaultSurfaceModel(data, RPM_RANGE, mafRange);
+ public static JPanel panel = ChartHelper.create3DControl(data, model, TITLE);
+
+ public static void buildTable() {
+ data.clear();
+
+// setPotVoltage(2.2, Sensor.MAF);
+// if (1 == 1)
+// return;
+
+ final BufferedWriter bw;
+ try {
+ bw = new BufferedWriter(new FileWriter(TABLE_FILE_NAME));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ ResultListener listener = new ResultListener() {
+ @Override
+ public void onResult(int rpm, double maf, float advance, double dwell) {
+ data.addPoint(new Point3D(rpm, maf, (float) dwell));
+ model.plot().execute();
+
+ String msg = putValue("rpm", rpm) +
+ putValue("maf", maf) +
+ putValue("advance", advance) +
+ putValue("dwell", dwell);
+ MessagesCentral.getInstance().postMessage(EcuStimulator.class, msg);
+
+ try {
+ bw.write(msg + "\r\n");
+ bw.flush();
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ };
+
+ buildTable(bw, listener, DWELL_SENSOR);
+
+ try {
+ bw.close();
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ TableGenerator.writeAsC(data, C_PREFIX, C_FILE_NAME);
+ }
+
+ private static void buildTable(BufferedWriter bw, ResultListener listener, Sensor dwellSensor) {
+ for (int rpm = RPM_MIN; rpm <= RPM_MAX; rpm += RPM_INCREMENT)
+ runSimulation(rpm, listener, dwellSensor);
+ }
+
+ private static void runSimulation(int rpm, ResultListener resultListener, final Sensor dwellSensor) {
+ for (double maf = MAF_MIN; maf <= MAF_MAX; maf += MAF_INCREMENT) {
+ setPotVoltage(maf, Sensor.MAF);
+ setRpm(rpm);
+ sleepRuntime(SLEEP_TIME);
+
+ final List dwells = new ArrayList(MEASURES);
+ final List advances = new ArrayList(MEASURES);
+
+ final CountDownLatch latch = new CountDownLatch(MEASURES);
+
+ EngineTimeListener listener = new EngineTimeListener() {
+ @Override
+ public void onTime(double time) {
+ if (latch.getCount() == 0)
+ return;
+ double dwell0 = getValue(dwellSensor);
+ double advance = 0;//getValue(Sensor.ADVANCE0);
+ dwells.add(dwell0);
+ advances.add(advance);
+ latch.countDown();
+ }
+ };
+ LinkManager.engineState.timeListeners.add(listener);
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ LinkManager.engineState.timeListeners.remove(listener);
+
+ // sorting measurements, taking middle value
+ Collections.sort(dwells);
+ Collections.sort(advances);
+
+ double dwellDiff = Math.abs(dwells.get(0) - dwells.get(MEASURES - 1));
+ if (dwellDiff > 1)
+ System.out.println("dwells " + dwells);
+
+
+ double dwell = dwells.get(MEASURES / 2);
+ double advance = advances.get(MEASURES / 2);
+
+ if (dwell > 40) {
+ throw new UnsupportedOperationException();
+ }
+
+ log("Stimulator result: " + rpm + "@" + maf + ": " + dwell);
+
+// double dwell = Launcher.getAdcModel().getValue(Sensor.DWELL0);
+// double advance = Launcher.getAdcModel().getValue(Sensor.ADVANCE);
+
+ resultListener.onResult(rpm, maf, (float) advance, dwell);
+ }
+ }
+
+ private static void sleepRuntime(long sleepTime) {
+ try {
+ Thread.sleep(sleepTime);
+ } catch (InterruptedException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static double getValue(Sensor sensor) {
+ return SensorCentral.getInstance().getValue(sensor);
+ }
+
+ private static void setRpm(int rpm) {
+ int actual;
+ int attempt = 0;
+ do {
+ RpmCommand.requestRpmChange(rpm);
+ sleepRuntime(50);
+ actual = RpmModel.getInstance().getValue();
+ } while (attempt++ < 10 && Math.abs(rpm - actual) >= 100);
+ log("Result: " + actual + " while setting " + rpm);
+ }
+
+ public static void setPotVoltage(double voltage, Sensor sensor) {
+ log("Current voltage: " + getValue(sensor) + ", setting " + voltage);
+ int attempt = 0;
+ int resistance = PotCommand.getPotResistance(voltage);
+ if (resistance <= 0) {
+ log("Invalid resistance " + resistance + ". Invalid voltage " + voltage + "?");
+ return;
+ }
+
+ double actual;
+ do {
+ PotCommand.requestPotChange(1, resistance);
+ sleepRuntime(50);
+ actual = getValue(sensor);
+ log("Got: " + actual + " on attempt=" + attempt + " while setting " + voltage + " for " + sensor);
+ } while (attempt++ < 10 && Math.abs(voltage - actual) > 0.2);
+ log("Result: " + actual + " while setting " + voltage);
+ }
+
+ private static void log(String message) {
+ MessagesCentral.getInstance().postMessage(EcuStimulator.class, message);
+ FileLog.MAIN.logLine(message);
+ }
+
+ private static String putValue(String msg, double value) {
+ return msg + D + value + D;
+ }
+
+ private static String putValue(String msg, int value) {
+ return msg + D + value + D;
+ }
+
+ interface ResultListener {
+ void onResult(int rpm, double maf, float advance, double dwell);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/Launcher.java b/java_console/ui/src/com/irnems/Launcher.java
new file mode 100644
index 0000000000..0c5aa6105b
--- /dev/null
+++ b/java_console/ui/src/com/irnems/Launcher.java
@@ -0,0 +1,105 @@
+package com.irnems;
+
+import com.irnems.core.EngineState;
+import com.irnems.core.MessagesCentral;
+import com.irnems.ui.*;
+import com.rusefi.AnalogChartPanel;
+import com.rusefi.PortLookupFrame;
+import com.rusefi.io.LinkManager;
+import jssc.SerialPortList;
+
+import javax.swing.*;
+
+/**
+ * this is the main entry point of rusEfi ECU console
+ *
+ *
+ * Date: 12/25/12
+ * (c) Andrey Belomutskiy
+ *
+ * @see WavePanel
+ */
+public class Launcher extends FrameHelper {
+ private static final Object CONSOLE_VERSION = "20140304";
+
+ public Launcher(String port) {
+ FileLog.MAIN.start();
+
+ LinkManager.start(port);
+
+ JTabbedPane tabbedPane = new JTabbedPane();
+
+ RpmPanel rpmPanel = new RpmPanel();
+ tabbedPane.addTab("Main", rpmPanel.createRpmPanel());
+ tabbedPane.addTab("Gauges", new GaugePanel());
+ tabbedPane.addTab("Digital Sniffer", WavePanel.getInstance());
+ tabbedPane.addTab("Analog Sniffer", new AnalogChartPanel());
+
+// tabbedPane.addTab("ADC", new AdcPanel(new BooleanInputsModel()).createAdcPanel());
+// tabbedPane.add("Emulation Map", EcuStimulator.panel);
+// tabbedPane.addTab("live map adjustment", new Live3DReport().getControl());
+ tabbedPane.add("MessagesCentral", new MsgPanel(true));
+
+// tabbedPane.add("Log Viewer", new LogViewer());
+
+ tabbedPane.setSelectedIndex(2);
+// tabbedPane.setSelectedIndex(5);
+
+ for (String p : SerialPortList.getPortNames())
+ MessagesCentral.getInstance().postMessage(Launcher.class, "Available port: " + p);
+
+ showFrame(tabbedPane);
+ }
+
+ @Override
+ protected void onWindowOpened() {
+ super.onWindowOpened();
+ setTitle("N/A");
+
+ LinkManager.open();
+
+ LinkManager.engineState.registerStringValueAction("rusEfiVersion", new EngineState.ValueCallback() {
+ @Override
+ public void onUpdate(String value) {
+ setTitle(value);
+ }
+ });
+ }
+
+ private void setTitle(String value) {
+ frame.setTitle("Console " + CONSOLE_VERSION + "; firmware=" + value);
+ }
+
+ @Override
+ protected void onWindowClosed() {
+ super.onWindowClosed();
+ /**
+ * looks like reconnectTimer in {@link RpmPanel} keeps AWT alive. Simplest solution would be to 'exit'
+ */
+ System.exit(0);
+ }
+
+ public static void main(final String[] args) throws Exception {
+ Thread.setDefaultUncaughtExceptionHandler(new DefaultExceptionHandler());
+ SwingUtilities.invokeAndWait(new Runnable() {
+ public void run() {
+ awtCode(args);
+ }
+ });
+ }
+
+ private static void awtCode(String[] args) {
+ try {
+
+ boolean isPortDefined = args.length > 0;
+ if (isPortDefined) {
+ new Launcher(args[0]);
+ } else {
+ PortLookupFrame.chooseSerialPort();
+ }
+
+ } catch (Throwable e) {
+ throw new IllegalStateException(e);
+ }
+ }
+}
diff --git a/java_console/ui/src/com/irnems/LogViewer.java b/java_console/ui/src/com/irnems/LogViewer.java
new file mode 100644
index 0000000000..97aebe6171
--- /dev/null
+++ b/java_console/ui/src/com/irnems/LogViewer.java
@@ -0,0 +1,150 @@
+package com.irnems;
+
+import com.irnems.core.EngineState;
+import com.irnems.file.FileUtils;
+import com.irnems.ui.WavePanel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileFilter;
+
+/**
+ * 7/27/13
+ * (c) Andrey Belomutskiy
+ */
+public class LogViewer extends JPanel {
+ public static final FileFilter FILE_FILTER = new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.getName().startsWith("rfi_report");
+ }
+ };
+ private final JLabel folderLabel = new JLabel();
+ private final DefaultListModel fileListModel = new DefaultListModel();
+ private final JList fileList = new JList(fileListModel);
+ private String currentFolder;
+
+
+// int currentChartIndex = 0;
+
+ public LogViewer() {
+ super(new BorderLayout());
+
+ setBackground(Color.green);
+
+ // todo: this is not perfect
+ openFolder("out");
+
+ JPanel folderPanel = new JPanel(new FlowLayout());
+
+ JButton folderButton = new JButton("Open folder");
+ folderButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ JFileChooser chooser = new JFileChooser(currentFolder);
+ chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ int result = chooser.showSaveDialog(LogViewer.this);
+ if (result == JFileChooser.APPROVE_OPTION)
+ openFolder(chooser.getSelectedFile().getAbsolutePath());
+ }
+ });
+
+ folderPanel.add(folderButton);
+ folderPanel.add(folderLabel);
+
+ folderPanel.setBackground(Color.red);
+
+ add(folderPanel, BorderLayout.NORTH);
+
+
+ JPanel descPanel = new JPanel();
+ descPanel.add(new JLabel("Total digital charts: "));
+ descPanel.add(new JLabel("" + ChartRepository.getInstance().getSize()));
+
+
+ JPanel boxPanel = new JPanel();
+ boxPanel.setLayout(new BoxLayout(boxPanel, BoxLayout.X_AXIS));
+ boxPanel.setBorder(BorderFactory.createLineBorder(Color.cyan));
+
+ boxPanel.add(fileList);
+ boxPanel.add(descPanel);
+
+
+ add(boxPanel);
+ }
+
+ private void openFolder(String folderName) {
+ folderLabel.setText(folderName);
+ currentFolder = folderName;
+
+ File folder = new File(folderName);
+ if (!folder.isDirectory())
+ throw new IllegalStateException("Not directory: " + folder);
+
+ File[] files = folder.listFiles(FILE_FILTER);
+ fileList.removeAll();
+ for (File file : files)
+ fileListModel.addElement(getFileDesc(file));
+
+ if (files.length > 0)
+ openFile(files[0]);
+
+ }
+
+ private String getFileDesc(File file) {
+ return file.getName() + " " + file.getUsableSpace();
+ }
+
+
+// JPanel upperPanel = new JPanel(new FlowLayout());
+//
+//
+// JButton next = new JButton("next");
+// next.addActionListener(new ActionListener() {
+// @Override
+// public void actionPerformed(ActionEvent e) {
+// currentChartIndex++;
+// refreshChart();
+//
+// }
+// });
+//
+// upperPanel.add(next);
+//
+//
+// refreshChart();
+
+
+ // private void refreshChart() {
+// String chart = ChartRepository.getInstance().getChart(currentChartIndex);
+// }
+//
+// public static void main(String[] args) {
+// String filename = "a.csv";
+//
+//
+ private void openFile(File file) {
+ String filename = file.getAbsolutePath();
+ EngineState.EngineStateListener listener = new EngineState.EngineStateListenerImpl() {
+ };
+
+ ChartRepository.getInstance().clear();
+ EngineState engineState = new EngineState(listener);
+ engineState.registerStringValueAction("wave_chart", new EngineState.ValueCallback() {
+ @Override
+ public void onUpdate(String value) {
+ FileLog.rlog("chart " + value);
+
+ ChartRepository.getInstance().addChart(value);
+
+ }
+ });
+ FileUtils.readFile2(filename, engineState);
+
+ if (ChartRepository.getInstance().getSize() > 0)
+ WavePanel.getInstance().displayChart(ChartRepository.getInstance().getChart(0));
+ }
+}
\ No newline at end of file
diff --git a/java_console/ui/src/com/irnems/ShowMap.java b/java_console/ui/src/com/irnems/ShowMap.java
new file mode 100644
index 0000000000..ff91f636b7
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ShowMap.java
@@ -0,0 +1,26 @@
+package com.irnems;
+
+import com.irnems.file.BaseMap;
+import com.irnems.models.XYData;
+import com.irnems.ui.FrameHelper;
+import com.irnems.ui.ChartHelper;
+
+import javax.swing.*;
+
+/**
+ * 7/18/13
+ * (c) Andrey Belomutskiy
+ */
+public class ShowMap extends FrameHelper {
+ public static void main(String[] args) {
+// XYData data = BaseMap.loadData("a.csv", "maf", "af");
+// XYData data2 = BaseMap.loadData("a.csv", "maf", "table_fuel");
+
+ XYData data = BaseMap.loadData("200.csv", "maf", "dwell");
+ XYData data2 = null;
+
+ JPanel jsp = ChartHelper.create3DControl(data, ChartHelper.createDefaultSurfaceModel(data, data2), "MAF<>Fuel Map");
+
+ new ShowMap().showFrame(jsp);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/AdcPanel.java b/java_console/ui/src/com/irnems/ui/AdcPanel.java
new file mode 100644
index 0000000000..dae2dc002d
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/AdcPanel.java
@@ -0,0 +1,88 @@
+package com.irnems.ui;
+
+import com.irnems.core.SensorCentral;
+import com.irnems.core.Pair;
+import net.miginfocom.swing.MigLayout;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.irnems.core.EngineState.SNIFFED_ADC_COUNT;
+
+/**
+ * Date: 1/7/13
+ * (c) Andrey Belomutskiy
+ */
+public class AdcPanel {
+ protected final Map sniffedAdcLabels = new HashMap();
+ public final Map inputLabels = new HashMap();
+ public final Map internalAdcLabels = new HashMap();
+
+ public AdcPanel(BooleanInputsModel inputs) {
+ }
+
+ public JComponent createAdcPanel() {
+ JComponent container = new JPanel(new MigLayout());
+ JButton saveAdc = new JButton("save ADC");
+ saveAdc.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+// model.saveToFile();
+ }
+ });
+ container.add(saveAdc, "grow, wrap");
+
+ for (int channel1 = 0; channel1 < SNIFFED_ADC_COUNT / 2; channel1++) {
+// {
+// Pair pair1 = createAdcLabel(channel1);
+// // 2nd column of channels
+// int channel2 = channel1 + SNIFFED_ADC_COUNT / 2;
+// Pair pair2 = createAdcLabel(channel2);
+// sniffedAdcLabels.put(channel1, pair1.second);
+// sniffedAdcLabels.put(channel2, pair2.second);
+//
+// container.add(pair1.first);
+// container.add(pair2.first);
+// }
+
+ {
+ Pair inputPair = createLabelWithCaption(BooleanInputsModel.getTitle(channel1));
+ inputLabels.put(channel1, inputPair.second);
+ container.add(inputPair.first);
+ }
+ {
+ Pair pair = createLabelWithCaption("adc " + channel1);
+ internalAdcLabels.put(channel1, pair.second);
+ container.add(pair.first, "grow, wrap");
+ }
+
+ }
+ return container;
+ }
+
+// private Pair createAdcLabel(final int channel) {
+// final Pair result = createLabelWithCaption(SensorCentral.getTitle(channel));
+// model.addListener(new SensorCentral.AdcListener() {
+// public void onAdcUpdate(SensorCentral model, Sensor sensor, double value) {
+// if (sensor.getMazdaIndex() != channel)
+// return;
+// JLabel label = result.second;
+// String representation = model.getSniffedAdcRepresentation(sensor);
+// label.setText(representation);
+// }
+// });
+// return result;
+// }
+
+ private Pair createLabelWithCaption(String title) {
+ JPanel panel = new JPanel(new FlowLayout());
+ panel.add(new JLabel(title));
+ JLabel value = new JLabel("");
+ panel.add(value);
+ return new Pair(panel, value);
+ }
+
+}
diff --git a/java_console/ui/src/com/irnems/ui/BooleanInputsModel.java b/java_console/ui/src/com/irnems/ui/BooleanInputsModel.java
new file mode 100644
index 0000000000..409aee1332
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/BooleanInputsModel.java
@@ -0,0 +1,41 @@
+package com.irnems.ui;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Date: 1/14/13
+ * (c) Andrey Belomutskiy
+ */
+public class BooleanInputsModel {
+ private static final Map inputTitle = new HashMap();
+ private static final Map trueValueText = new HashMap();
+ private static final Map falseValueText = new HashMap();
+ private final Map values = new TreeMap();
+
+ static {
+ inputTitle.put(0, "cranking");
+ int idle = 1;
+ inputTitle.put(idle, "idle switch");
+ trueValueText.put(idle, "idle");
+ falseValueText.put(idle, "not idle");
+ }
+
+ static String getTitle(int channel) {
+ String title = inputTitle.containsKey(channel) ? (": " + inputTitle.get(channel)) : "";
+ return "adc " + channel + title;
+ }
+
+ public void setValue(int channel, boolean value) {
+ values.put(channel, value);
+ }
+
+ public static String getValueLabelText(int channel, boolean value) {
+ if (value && trueValueText.containsKey(channel))
+ return trueValueText.get(channel);
+ if (!value && falseValueText.containsKey(channel))
+ return falseValueText.get(channel);
+ return Boolean.toString(value);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/ChartHelper.java b/java_console/ui/src/com/irnems/ui/ChartHelper.java
new file mode 100644
index 0000000000..d986eea674
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/ChartHelper.java
@@ -0,0 +1,134 @@
+package com.irnems.ui;
+
+import com.irnems.FileLog;
+import com.irnems.models.Range;
+import com.irnems.models.XYData;
+import com.irnems.ui.widgets.JTextFieldWithWidth;
+import net.ericaro.surfaceplotter.DefaultSurfaceModel;
+import net.ericaro.surfaceplotter.JSurfacePanel;
+import net.ericaro.surfaceplotter.Mapper;
+import net.ericaro.surfaceplotter.surface.SurfaceModel;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Date: 1/22/13
+ * (c) Andrey Belomutskiy
+ */
+public class ChartHelper {
+ private ChartHelper() {
+ }
+
+ public static JPanel create3DControl(final XYData data, SurfaceModel surfaceModel, String title) {
+ JPanel result = new JPanel(new BorderLayout());
+
+ final JSurfacePanel jsp = new JSurfacePanel(surfaceModel);
+ jsp.setTitleText(title);
+ jsp.setConfigurationVisible(true);
+ jsp.getSurface().setXLabel("RPM");
+ jsp.getSurface().setYLabel("MAF voltage");
+ result.add(BorderLayout.CENTER, jsp);
+
+ JButton saveImageButton = new JButton("save image");
+ saveImageButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ UiUtils.saveImage(FileLog.getDate() + "_3d.png", jsp);
+ }
+ });
+
+ JPanel upperPanel = new JPanel(new FlowLayout());
+ upperPanel.add(saveImageButton);
+ result.add(upperPanel, BorderLayout.NORTH);
+
+ JPanel bottomPanel = new JPanel(new FlowLayout());
+ bottomPanel.add(new JLabel("RPM: "));
+
+ final JTextField xField = new JTextFieldWithWidth("1200", 150);
+ final JTextField yField = new JTextFieldWithWidth("50", 150);
+ bottomPanel.add(xField);
+ bottomPanel.add(yField);
+
+ final JLabel currentValue = new JLabel();
+ bottomPanel.add(currentValue);
+
+ result.add(bottomPanel, BorderLayout.SOUTH);
+
+ ActionListener updateValue = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int rpm = Integer.parseInt(xField.getText());
+
+ double y = Double.parseDouble(yField.getText());
+
+ currentValue.setText("" + data.getValue(rpm, y));
+ }
+ };
+ xField.addActionListener(updateValue);
+ yField.addActionListener(updateValue);
+
+ updateValue.actionPerformed(null);
+
+ return result;
+ }
+
+ public static DefaultSurfaceModel createDefaultSurfaceModel(final XYData data) {
+ return createDefaultSurfaceModel(data, null);
+ }
+
+ public static DefaultSurfaceModel createDefaultSurfaceModel(final XYData data, final XYData data2) {
+ Range xRange = new Range((float) data.getMinXValue(), (float) data.getMaxXValue());
+ Range yRange = new Range((float) data.getMinYValue(), (float) data.getMaxYValue());
+
+ return createDefaultSurfaceModel(data, xRange, yRange, data2);
+ }
+
+ public static DefaultSurfaceModel createDefaultSurfaceModel(final XYData data, Range xRange, Range yRange) {
+ return createDefaultSurfaceModel(data, xRange, yRange, null);
+ }
+
+ public static DefaultSurfaceModel createDefaultSurfaceModel(final XYData data, Range xRange, Range yRange, final XYData data2) {
+ final DefaultSurfaceModel sm = new DefaultSurfaceModel();
+
+ sm.setPlotFunction2(false);
+
+ sm.setCalcDivisions(50);
+ sm.setDispDivisions(50);
+ sm.setContourLines(10);
+
+ sm.setXMin(xRange.getMin());
+ sm.setXMax(xRange.getMax());
+ sm.setYMin(yRange.getMin());
+ sm.setYMax(yRange.getMax());
+
+ sm.setBoxed(true);
+ sm.setDisplayXY(true);
+ sm.setExpectDelay(false);
+ sm.setAutoScaleZ(true);
+ sm.setDisplayZ(true);
+ sm.setMesh(true);
+ sm.setPlotType(SurfaceModel.PlotType.SURFACE);
+ if (data2 == null)
+ sm.setFirstFunctionOnly(true);
+ else
+ sm.setBothFunction(true);
+
+ sm.setPlotColor(data2 != null ? SurfaceModel.PlotColor.FOG : SurfaceModel.PlotColor.SPECTRUM);
+ sm.setMapper(new Mapper() {
+ public float f1(float x, float y) {
+ return data.getValue(x, y);
+ }
+
+ public float f2(float x, float y) {
+ if (data2 == null)
+ return 0;
+ return data2.getValue(x, y);
+ }
+ });
+ sm.plot().execute();
+ return sm;
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/ChartStatusPanel.java b/java_console/ui/src/com/irnems/ui/ChartStatusPanel.java
new file mode 100644
index 0000000000..b732094f36
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/ChartStatusPanel.java
@@ -0,0 +1,77 @@
+package com.irnems.ui;
+
+import com.irnems.waves.TimeAxisTranslator;
+import com.rusefi.waves.WaveReport;
+import com.irnems.waves.ZoomProvider;
+import com.rusefi.waves.RevolutionLog;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.util.Map;
+
+/**
+ * Status bar at the bottom of Digital Sniffer - {@link WavePanel}
+ *
+ *
+ * Date: 12/26/13
+ * Andrey Belomutskiy (c) 2012-2013
+ */
+public class ChartStatusPanel {
+ public final JPanel infoPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
+ private final JLabel xLabel = new JLabel();
+ private final JLabel timeLabel = new JLabel();
+ private final JLabel angleLabel = new JLabel();
+ private final JLabel rpmLabel = new JLabel();
+ private TimeAxisTranslator translator = WaveReport.MOCK;
+
+ private RevolutionLog time2rpm = RevolutionLog.parseRevolutions(null);
+
+ final MouseMotionAdapter motionAdapter = new MouseMotionAdapter() {
+ @Override
+ public void mouseMoved(MouseEvent event) {
+ int x = event.getX();
+ xLabel.setText("" + x);
+
+ /**
+ * Time which corresponds to the mouse cursor screen location
+ */
+ double time = translator.screenToTime(x, infoPanel.getWidth(), zoomProvider);
+ timeLabel.setText("" + String.format("%.5f sec", time));
+
+ String text = time2rpm == null ? "n/a" : time2rpm.getCrankAngleByTimeString(time);
+ angleLabel.setText(text);
+
+ Map.Entry e = time2rpm.getTimeAndRpm(time);
+ if (e == null)
+ rpmLabel.setText("n/a");
+ else
+ rpmLabel.setText("" + e.getValue());
+ }
+ };
+
+ private ZoomProvider zoomProvider;
+
+ public ChartStatusPanel(ZoomProvider zoomProvider) {
+ this.zoomProvider = zoomProvider;
+ infoPanel.add(new JLabel("X: "));
+ infoPanel.add(xLabel);
+ infoPanel.add(new JLabel(" time: "));
+ infoPanel.add(timeLabel);
+
+ infoPanel.add(new JLabel(" angle: "));
+ infoPanel.add(angleLabel);
+
+ infoPanel.add(new JLabel(" RPM: "));
+ infoPanel.add(rpmLabel);
+ }
+
+ public void setWaveReport(TimeAxisTranslator translator) {
+ this.translator = translator;
+ }
+
+ public void setRevolutions(StringBuilder revolutions) {
+ time2rpm = RevolutionLog.parseRevolutions(revolutions);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/DefaultExceptionHandler.java b/java_console/ui/src/com/irnems/ui/DefaultExceptionHandler.java
new file mode 100644
index 0000000000..0f155fe750
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/DefaultExceptionHandler.java
@@ -0,0 +1,31 @@
+package com.irnems.ui;
+
+import javax.swing.*;
+import java.awt.*;
+
+import static javax.swing.JOptionPane.OK_OPTION;
+
+/**
+ * 6/30/13
+ * (c) Andrey Belomutskiy
+ */
+public class DefaultExceptionHandler implements Thread.UncaughtExceptionHandler {
+ public void uncaughtException(Thread t, Throwable e) {
+ handleException(e);
+ }
+
+ public static void handleException(Throwable e) {
+ // Here you should have a more robust, permanent record of problems
+ JOptionPane.showMessageDialog(findActiveFrame(), e.toString(), "Exception Occurred", OK_OPTION);
+ e.printStackTrace();
+ }
+
+ private static Frame findActiveFrame() {
+ Frame[] frames = JFrame.getFrames();
+ for (Frame frame : frames) {
+ if (frame.isVisible())
+ return frame;
+ }
+ return null;
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/FrameHelper.java b/java_console/ui/src/com/irnems/ui/FrameHelper.java
new file mode 100644
index 0000000000..be704c3c46
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/FrameHelper.java
@@ -0,0 +1,43 @@
+package com.irnems.ui;
+
+import com.irnems.FileLog;
+
+import javax.swing.*;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+
+/**
+ * Date: 3/24/13
+ * (c) Andrey Belomutskiy
+ */
+public class FrameHelper {
+ protected final JFrame frame = new JFrame();
+
+ protected void showFrame(JComponent container) {
+ frame.setSize(800, 500);
+ frame.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ frame.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowOpened(WindowEvent e) {
+ onWindowOpened();
+ frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
+ }
+
+ @Override
+ public void windowClosed(WindowEvent ev) {
+ onWindowClosed();
+ }
+ });
+ frame.add(container);
+ frame.setVisible(true);
+ }
+
+ protected void onWindowOpened() {
+ FileLog.rlog("onWindowOpened");
+ }
+
+ protected void onWindowClosed() {
+ FileLog.rlog("onWindowClosed");
+ FileLog.MAIN.close();
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/GaugePanel.java b/java_console/ui/src/com/irnems/ui/GaugePanel.java
new file mode 100644
index 0000000000..257de9c06c
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/GaugePanel.java
@@ -0,0 +1,133 @@
+package com.irnems.ui;
+
+import com.irnems.core.Sensor;
+import com.irnems.core.SensorCentral;
+import com.irnems.ui.widgets.MafCommand;
+import com.irnems.ui.widgets.PotCommand;
+import com.irnems.ui.widgets.RpmCommand;
+import eu.hansolo.steelseries.gauges.Radial;
+import eu.hansolo.steelseries.tools.ColorDef;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Date: 2/5/13
+ * (c) Andrey Belomutskiy
+ */
+public class GaugePanel extends JComponent {
+ private static final int ADC_MAX_VALUE = 255; // mazda ECU
+// private static final int ADC_MAX_VALUE = 4095; // discovery board
+
+ public GaugePanel() {
+ setLayout(new GridLayout(1, 3));
+
+
+// Radial radial2 = createRadial("title");
+
+ JPanel box2 = new JPanel(new GridLayout(3, 5));
+
+
+ box2.add(createControls());
+ box2.add(createGauge(Sensor.T_CHARGE));
+ box2.add(createGauge(Sensor.DWELL1));
+ box2.add(createGauge(Sensor.DWELL0));
+ box2.add(createGauge(Sensor.DUTY0));
+ box2.add(createGauge(Sensor.ADVANCE0));
+ box2.add(createGauge(Sensor.MAF));
+ box2.add(createGauge(Sensor.FUEL));
+ box2.add(createGauge(Sensor.FUEL_BASE));
+ box2.add(createGauge(Sensor.FUEL_CLT));
+ box2.add(createGauge(Sensor.FUEL_IAT));
+ box2.add(createGauge(Sensor.FUEL_LAG));
+
+// box2.add(createGauge(Sensor.TABLE_SPARK));
+
+
+// box2.add(createGauge(Sensor.DUTY1));
+// box2.add(createGauge(Sensor.ADVANCE1));
+ box2.add(createGauge(Sensor.INTAKE_AIR));
+ //box2.add(createGauge(Sensor.INTAKE_AIR_WIDTH));
+ box2.add(createGauge(Sensor.COOLANT));
+// box2.add(createGauge(Sensor.COOLANT_WIDTH));
+
+ box2.add(createGauge(Sensor.MAP));
+ box2.add(createGauge(Sensor.MAP_RAW));
+ box2.add(createGauge(Sensor.THROTTLE));
+// box2.add(createGauge(Sensor.VREF, PotCommand.VOLTAGE_CORRECTION));
+// box2.add(createGauge(Sensor.VREF_WIDTH));
+
+// box2.add(createGauge(Sensor.ADC_FAST));
+// box2.add(createGauge(Sensor.ADC_FAST_AVG));
+
+
+ box2.add(createGauge(Sensor.AFR));
+ box2.add(createGauge(Sensor.DEFAULT_FUEL));
+
+ box2.add(createGauge(Sensor.TIMING));
+
+ box2.add(createRpmGauge());
+
+ //add(rpmGauge);
+ add(box2);
+// add(new JLabel("fd"), BorderLayout.EAST);
+ }
+
+ private Component createControls() {
+ JPanel controls = new JPanel(new GridLayout(2, 1));
+ controls.add(new RpmCommand());
+ controls.add(new MafCommand());
+ return controls;
+ }
+
+ private Radial createRpmGauge() {
+ final Radial rpmGauge = createRadial("RPM", "", 8000, 0);
+ RpmModel.getInstance().addListener(new RpmModel.RpmListener() {
+ public void onRpmChange(RpmModel rpm) {
+ rpmGauge.setValue(rpm.getValue());
+ }
+ });
+ rpmGauge.setMaxMeasuredValueVisible(true);
+ return rpmGauge;
+ }
+
+ public static Component createGauge(final Sensor sensor) {
+ return createGauge(sensor, 1);
+ }
+
+ public static Component createGauge(final Sensor sensor, final double correction) {
+ final Radial gauge = createRadial(sensor.getName(), sensor.getUnits(), sensor.getMaxValue(), sensor.getMinValue());
+
+ gauge.setBackgroundColor(sensor.getColor());
+
+ SensorCentral.getInstance().addListener(sensor, new SensorCentral.AdcListener() {
+ public void onAdcUpdate(SensorCentral model, double value) {
+ gauge.setValue(value * correction);
+ }
+ });
+ gauge.setLcdDecimals(2);
+ return gauge;
+ }
+
+ private static Radial createRadial(String title, String units, double maxValue, double minValue) {
+// final Section[] SECTIONS =
+// {
+// new Section(0, to, Color.red)
+// };
+
+ Radial radial1 = new Radial();
+// radial1.setSections(SECTIONS);
+ radial1.setTitle(title);
+ radial1.setUnitString(units);
+
+ //radial1.setTrackStop(to);
+
+ radial1.setMinValue(minValue);
+ radial1.setMaxValue(maxValue);
+ radial1.setThresholdVisible(false);
+ radial1.setPointerColor(ColorDef.RED);
+
+ radial1.setValue(0);
+ return radial1;
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/Live3DReport.java b/java_console/ui/src/com/irnems/ui/Live3DReport.java
new file mode 100644
index 0000000000..25dd6f2837
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/Live3DReport.java
@@ -0,0 +1,64 @@
+package com.irnems.ui;
+
+import com.irnems.EcuStimulator;
+import com.irnems.core.MessagesCentral;
+import com.irnems.models.Point3D;
+import com.irnems.models.Range;
+import com.irnems.models.XYData;
+import net.ericaro.surfaceplotter.DefaultSurfaceModel;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * 7/22/13
+ * (c) Andrey Belomutskiy
+ */
+public class Live3DReport {
+ public static final Range KEY_RANGE = new Range(1.5f, 4.0f);
+ private final XYData primary = new XYData();
+ private final XYData secondary = null;//new XYData();
+ private final JPanel control;
+
+ private static final String KEY = "map_adjusted: ";
+
+ public Live3DReport() {
+ final DefaultSurfaceModel model = ChartHelper.createDefaultSurfaceModel(primary, EcuStimulator.RPM_RANGE, KEY_RANGE, secondary);
+
+// primary.fill(EcuStimulator.RPM_RANGE, KEY_RANGE, 16, 1);
+
+ control = ChartHelper.create3DControl(primary, model, "Live Data");
+
+// addPoint("1000 3 0.9", model);
+// addPoint("1000 320 90", model);
+// addPoint("1000 340 90", model);
+
+ MessagesCentral.getInstance().addListener(new MessagesCentral.MessageListener() {
+ @Override
+ public void onMessage(Class clazz, String message) {
+ if (!message.startsWith(KEY))
+ return;
+ message = message.substring(KEY.length());
+ addPoint(message, model);
+ }
+ });
+ }
+
+ private void addPoint(String message, DefaultSurfaceModel model) {
+ String[] v = message.split(" ");
+ if (v.length != 3)
+ return;
+
+ int rpm = Integer.valueOf(v[0]);
+ float key = Integer.valueOf(v[1]) / 100.0f;
+ float value = Integer.valueOf(v[2]) / 100.0f;
+
+ primary.setPoint(new Point3D(rpm, key, value));
+ primary.saveToFile("_mult.csv");
+ model.plot().execute();
+ }
+
+ public Component getControl() {
+ return control;
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/MsgPanel.java b/java_console/ui/src/com/irnems/ui/MsgPanel.java
new file mode 100644
index 0000000000..8eff095d00
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/MsgPanel.java
@@ -0,0 +1,104 @@
+package com.irnems.ui;
+
+import com.irnems.core.MessagesCentral;
+import com.irnems.ui.widgets.AnyCommand;
+import com.irnems.ui.widgets.IdleLabel;
+
+import javax.swing.*;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * This panel displays plain-text 'msg' plain-text debug messages
+ *
+ *
+ * Date: 4/27/13
+ * (c) Andrey Belomutskiy
+ *
+ * @see AnyCommand
+ */
+public class MsgPanel extends JPanel {
+ private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH_mm");
+
+ private final JTextPane msg = new JTextPane();
+ private boolean isPaused;
+
+ public MsgPanel(boolean needsRpmControl) {
+ super(new BorderLayout());
+ setBorder(BorderFactory.createLineBorder(Color.green));
+ JScrollPane pane = new JScrollPane(msg, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+ add(pane, BorderLayout.CENTER);
+ MessagesCentral.getInstance().addListener(new MessagesCentral.MessageListener() {
+ @Override
+ public void onMessage(Class clazz, String message) {
+ final String date = DATE_FORMAT.format(new Date());
+ if (!isPaused)
+ append(date + ": " + clazz.getSimpleName() + ": " + message);
+ }
+ });
+
+ JButton resetButton = new JButton("clear");
+ resetButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ Document d = msg.getDocument();
+ clearMessages(d);
+ }
+ });
+
+ final JButton pauseButton = new JButton("pause");
+ pauseButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ isPaused = !isPaused;
+ UiUtils.setPauseButtonText(pauseButton, isPaused);
+ }
+ });
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
+ buttonPanel.add(resetButton);
+ buttonPanel.add(pauseButton);
+ buttonPanel.add(new AnyCommand());
+ if (needsRpmControl)
+ buttonPanel.add(new RpmControl().getContent());
+ add(buttonPanel, BorderLayout.NORTH);
+
+ JPanel statsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+
+ statsPanel.add(new RpmControl().getContent());
+ statsPanel.add(new IdleLabel());
+
+ add(statsPanel, BorderLayout.SOUTH);
+ }
+
+ private void clearMessages(Document d) {
+ try {
+ d.remove(0, d.getLength());
+ } catch (BadLocationException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private void append(String line) {
+ Document d = msg.getDocument();
+ if (d.getLength() > 10000)
+ clearMessages(d);
+ try {
+ d.insertString(d.getLength(), line + "\r\n", null);
+ msg.select(d.getLength(), d.getLength());
+ } catch (BadLocationException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ return new Dimension(250, size.height);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/RpmControl.java b/java_console/ui/src/com/irnems/ui/RpmControl.java
new file mode 100644
index 0000000000..d1075c9d45
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/RpmControl.java
@@ -0,0 +1,79 @@
+package com.irnems.ui;
+
+import com.rusefi.io.LinkManager;
+import com.irnems.core.EngineTimeListener;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Digital RPM gauge which stays green while rusEfi is connected
+ *
+ * 9/17/13
+ * (c) Andrey Belomutskiy
+ */
+public class RpmControl {
+ private static final String NO_CONNECTION = "N/C";
+ private final JPanel content = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
+
+ private final JLabel rpmValue = new JLabel(NO_CONNECTION);
+ private final JLabel rpmCaption = new JLabel("RPM:");
+
+ public RpmControl() {
+ rpmCaption.setBorder(BorderFactory.createLineBorder(Color.white));
+ rpmValue.setForeground(Color.red);
+
+ content.setBorder(BorderFactory.createLineBorder(Color.white));
+ content.add(rpmCaption);
+ content.add(rpmValue, "grow, wrap");
+
+ RpmModel.getInstance().addListener(new RpmModel.RpmListener() {
+ public void onRpmChange(RpmModel rpm) {
+ int value = rpm.getSmoothedValue();
+ if (value == -1)
+ rpmValue.setText("Noise");
+ else
+ rpmValue.setText(value + "");
+ }
+ });
+
+ final Timer timer1 = new Timer(2000, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ rpmValue.setText(NO_CONNECTION);
+ rpmValue.setForeground(Color.red);
+ }
+ });
+
+
+ LinkManager.engineState.timeListeners.add(new EngineTimeListener() {
+ @Override
+ public void onTime(double time) {
+ rpmValue.setForeground(Color.green);
+ /**
+ * this timer will catch engine inactivity and display a warning
+ */
+ timer1.restart();
+ }
+ });
+ }
+
+ public JPanel getContent() {
+ return content;
+ }
+
+ public RpmControl setSize(int size) {
+ Font f = rpmCaption.getFont();
+ int fontSize = size * f.getSize();
+ Font font = new Font(f.getName(), f.getStyle(), fontSize);
+ setFont(font);
+ return this;
+ }
+
+ private void setFont(Font font) {
+ rpmCaption.setFont(font);
+ rpmValue.setFont(font);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/RpmModel.java b/java_console/ui/src/com/irnems/ui/RpmModel.java
new file mode 100644
index 0000000000..f125850897
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/RpmModel.java
@@ -0,0 +1,61 @@
+package com.irnems.ui;
+
+import com.irnems.core.Sensor;
+import com.irnems.core.SensorCentral;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Model for RPM reading with a feature of smoothing the displayed value: new value is not displayed if updated
+ * value is within 5% range around currently displayed value. Here we rely on the fact that RPM values are coming in
+ * constantly
+ *
+ * Date: 12/27/12
+ * (c) Andrey Belomutskiy
+ */
+public class RpmModel {
+ private static final RpmModel INSTANCE = new RpmModel();
+ private static final double SMOOTHING_RATIO = 0.05;
+ private int displayedValue;
+ private int value;
+ private final List listeners = new ArrayList();
+
+ public static RpmModel getInstance() {
+ return INSTANCE;
+ }
+
+ private RpmModel() {
+ SensorCentral.getInstance().addListener(Sensor.RPM, new SensorCentral.AdcListener() {
+ @Override
+ public void onAdcUpdate(SensorCentral model, double value) {
+ setValue((int) value);
+ }
+ });
+ }
+
+ public void setValue(int rpm) {
+ value = rpm;
+ for (RpmListener listener : listeners)
+ listener.onRpmChange(this);
+ }
+
+ public int getValue() {
+ return value;
+ }
+
+ public int getSmoothedValue() {
+ int diff = Math.abs(displayedValue - value);
+ if (diff > value * SMOOTHING_RATIO)
+ displayedValue = value;
+ return displayedValue;
+ }
+
+ public void addListener(RpmListener listener) {
+ listeners.add(listener);
+ }
+
+ interface RpmListener {
+ void onRpmChange(RpmModel rpm);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/RpmPanel.java b/java_console/ui/src/com/irnems/ui/RpmPanel.java
new file mode 100644
index 0000000000..7e750de511
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/RpmPanel.java
@@ -0,0 +1,153 @@
+package com.irnems.ui;
+
+import com.irnems.EcuStimulator;
+import com.rusefi.io.LinkManager;
+import com.irnems.core.EngineTimeListener;
+import com.irnems.core.Sensor;
+import com.irnems.ui.widgets.*;
+import net.miginfocom.swing.MigLayout;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Date: 1/7/13
+ * (c) Andrey Belomutskiy
+ */
+public class RpmPanel {
+ private RpmControl rpmControl = new RpmControl();
+ // this label displays real RPM received from ECU
+ // that's for CKP signal emulation
+ public final WaveInfoPanel wave0 = new WaveInfoPanel(0);
+ public final WaveInfoPanel wave1 = new WaveInfoPanel(1);
+ public final WaveInfoPanel wave2 = new WaveInfoPanel(2);
+
+ public RpmPanel() {
+ rpmControl.setSize(15);
+ }
+
+ private WaveInfoPanel findWavePanel(int index) {
+ WaveInfoPanel wave;
+ if (index == 0)
+ wave = wave0;
+ else if (index == 1)
+ wave = wave1;
+ else
+ throw new IllegalStateException("unexpected index " + index);
+ return wave;
+ }
+
+
+ public JComponent createRpmPanel() {
+ JPanel controls = createControls();
+
+ JPanel gauges = new JPanel(new GridLayout(2, 3));
+ gauges.setBorder(BorderFactory.createLineBorder(Color.black));
+// gauges.add(GaugePanel.createCoolantGauge());
+ gauges.add(GaugePanel.createGauge(Sensor.DWELL0));
+ gauges.add(GaugePanel.createGauge(Sensor.DUTY0));
+ gauges.add(GaugePanel.createGauge(Sensor.FUEL));
+ //gauges.add(GaugePanel.createGauge(Sensor.ADVANCE0));
+
+ gauges.add(GaugePanel.createGauge(Sensor.VREF, PotCommand.VOLTAGE_CORRECTION));
+ gauges.add(GaugePanel.createGauge(Sensor.MAF));
+ gauges.add(GaugePanel.createGauge(Sensor.DWELL1));
+// gauges.add(GaugePanel.createGauge(Sensor.ADVANCE1));
+// gauges.add(GaugePanel.createGauge(Sensor.MAF));
+
+
+ final Timer reconnectTimer = new Timer(10000, new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ LinkManager.restart();
+ }
+ });
+ reconnectTimer.restart();
+
+ LinkManager.engineState.timeListeners.add(new EngineTimeListener() {
+ @Override
+ public void onTime(double time) {
+ /**
+ * this timer will reconnect
+ */
+ postponeReconnecting(reconnectTimer);
+
+ }
+ });
+
+ JComponent rpmPanel = new JPanel(new BorderLayout());
+ rpmPanel.setBorder(BorderFactory.createLineBorder(Color.white));
+
+ rpmPanel.add(rpmControl.getContent(), BorderLayout.NORTH);
+ rpmPanel.add(controls, BorderLayout.WEST);
+ rpmPanel.add(gauges, BorderLayout.CENTER);
+ MsgPanel msgPanel = new MsgPanel(false);
+ rpmPanel.add(msgPanel, BorderLayout.EAST);
+
+ return rpmPanel;
+ }
+
+ private void postponeReconnecting(Timer timer2) {
+ timer2.restart();
+ }
+
+ private JPanel createControls() {
+ JPanel controls = new JPanel(new MigLayout());
+ controls.setBorder(BorderFactory.createLineBorder(Color.red));
+ JButton button = createButton();
+// controls.add(button, "grow, wrap");
+
+ controls.add(new RpmCommand(), "grow, wrap");
+// controls.add(new PotCommand(0).panel, "grow, wrap");
+// controls.add(new PotCommand(1).panel, "grow, wrap");
+ controls.add(new AnyCommand(), "grow, wrap");
+
+ controls.add(new MafCommand(), "grow, wrap");
+
+ controls.add(wave0.getControl(), "grow, wrap");
+ controls.add(wave1.getControl(), "grow, wrap");
+ controls.add(wave2.getControl(), "grow, wrap");
+
+
+ controls.add(new AdcDebugControl().getControl(), "grow, wrap");
+
+ controls.add(new InjectorControl(0, Sensor.INJECTOR_0_STATUS).getControl(), "grow, wrap");
+ controls.add(new InjectorControl(1, Sensor.INJECTOR_1_STATUS).getControl(), "grow, wrap");
+ controls.add(new InjectorControl(2, Sensor.INJECTOR_2_STATUS).getControl(), "grow, wrap");
+ controls.add(new InjectorControl(3, Sensor.INJECTOR_3_STATUS).getControl(), "grow, wrap");
+
+ controls.add(new LogModeWidget().getPanel(), "grow, wrap");
+
+ return controls;
+ }
+
+ private JButton createButton() {
+ final JButton button = new JButton("run ECU stimulation");
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ EcuStimulator.buildTable();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ System.exit(-20);
+ }
+ SwingUtilities.invokeLater(new Runnable() {
+ @Override
+ public void run() {
+ button.setText("Done");
+ }
+ });
+ }
+ }, "Ecu Stimulator").start();
+ }
+ });
+ return button;
+ }
+}
+
diff --git a/java_console/ui/src/com/irnems/ui/UiUtils.java b/java_console/ui/src/com/irnems/ui/UiUtils.java
new file mode 100644
index 0000000000..6c73d5de46
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/UiUtils.java
@@ -0,0 +1,36 @@
+package com.irnems.ui;
+
+import javax.imageio.ImageIO;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 7/7/13
+ * (c) Andrey Belomutskiy
+ */
+public class UiUtils {
+ public static void saveImage(String fileName, Component component) {
+ BufferedImage img = getScreenShot(component);
+ try {
+ ImageIO.write(img, "png", new File(fileName));
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private static BufferedImage getScreenShot(Component component) {
+ // http://stackoverflow.com/questions/5853879/swing-obtain-image-of-jframe/5853992
+ BufferedImage image = new BufferedImage(component.getWidth(), component.getHeight(), BufferedImage.TYPE_INT_RGB);
+ // call the Component's paint method, using
+ // the Graphics object of the image.
+ component.paint(image.getGraphics());
+ return image;
+ }
+
+ public static void setPauseButtonText(JButton pauseButton, boolean isPaused) {
+ pauseButton.setText(isPaused ? "resume" : "pause");
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/WavePanel.java b/java_console/ui/src/com/irnems/ui/WavePanel.java
new file mode 100644
index 0000000000..5729f4f525
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/WavePanel.java
@@ -0,0 +1,184 @@
+package com.irnems.ui;
+
+import com.irnems.ChartRepository;
+import com.irnems.FileLog;
+import com.irnems.core.EngineState;
+import com.irnems.core.Sensor;
+import com.irnems.core.SensorCentral;
+import com.irnems.ui.widgets.AnyCommand;
+import com.irnems.ui.widgets.UpDownImage;
+import com.rusefi.io.LinkManager;
+import com.rusefi.waves.RevolutionLog;
+import com.rusefi.waves.WaveChart;
+import com.rusefi.waves.WaveChartParser;
+import com.rusefi.waves.WaveReport;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Digital Sniffer control consists of a set of {@link UpDownImage}
+ *
+ *
+ * Date: 6/23/13
+ * Andrey Belomutskiy (c) 2012-2013
+ *
+ * @see ChartStatusPanel status bar
+ */
+public class WavePanel extends JPanel {
+ private static final int EFI_DEFAULT_CHART_SIZE = 180;
+
+ private final Map images = new LinkedHashMap();
+ private final JPanel imagePanel = new JPanel();
+ private final ZoomControl zoomControl = new ZoomControl();
+ private final ChartStatusPanel statusPanel = new ChartStatusPanel(zoomControl);
+ private final UpDownImage crank = register("crank");
+
+ private boolean isPaused;
+
+ private static WavePanel instance = new WavePanel();
+
+ private WavePanel() {
+ setLayout(new BorderLayout());
+
+ statusPanel.setWaveReport(crank.createTranslator());
+
+ JButton resetButton = new JButton("reset");
+ resetButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ for (UpDownImage image : images.values())
+ image.setWaveReport(WaveReport.MOCK, null);
+ }
+ });
+
+ JButton saveImageButton = new JButton("save image");
+ saveImageButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ saveImage();
+ }
+ });
+
+ final JButton pauseButton = new JButton("pause");
+ pauseButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ isPaused = !isPaused;
+ UiUtils.setPauseButtonText(pauseButton, isPaused);
+ }
+ });
+
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));
+ buttonPanel.add(resetButton);
+ buttonPanel.add(saveImageButton);
+ buttonPanel.add(pauseButton);
+ buttonPanel.add(new RpmControl().setSize(2).getContent());
+
+ JTextField command = AnyCommand.createCommandControl();
+ command.setText("chartsize " + EFI_DEFAULT_CHART_SIZE);
+ buttonPanel.add(command);
+
+ buttonPanel.add(zoomControl);
+
+ buttonPanel.add(ChartRepository.getInstance().createControls(new ChartRepository.CRListener() {
+ @Override
+ public void onDigitalChart(String chart) {
+ displayChart(chart);
+ }
+ }));
+
+ add(buttonPanel, BorderLayout.NORTH);
+ add(imagePanel, BorderLayout.CENTER);
+ add(statusPanel.infoPanel, BorderLayout.SOUTH);
+
+ zoomControl.listener = new ZoomControl.ZoomControlListener() {
+ @Override
+ public void onZoomChange() {
+ UpDownImage.trueRepaint(imagePanel);
+ }
+ };
+
+ crank.setZoomProvider(zoomControl);
+ imagePanel.add(crank);
+ createSecondaryImage("crank2");
+ createSecondaryImage("input1 A8");
+ createSecondaryImage("input2 E5");
+
+ createSecondaryImage(WaveChart.SPARK_1);
+ createSecondaryImage(WaveChart.SPARK_2);
+ createSecondaryImage(WaveChart.SPARK_3);
+ createSecondaryImage(WaveChart.SPARK_4);
+
+ createSecondaryImage(WaveChart.INJECTOR_1);
+ createSecondaryImage(WaveChart.INJECTOR_2);
+ createSecondaryImage(WaveChart.INJECTOR_3);
+ createSecondaryImage(WaveChart.INJECTOR_4);
+
+ LinkManager.engineState.registerStringValueAction(WaveReport.WAVE_CHART, new EngineState.ValueCallback() {
+ @Override
+ public void onUpdate(String value) {
+ if (isPaused)
+ return;
+ displayChart(value);
+ }
+ });
+
+// displayChart("wave_chart,crank2!down!192811978!crank2!up!192813389!crank2!down!192813749!crank2!up!192815156!crank2!down!192815512!crank!up!192820764!crank2!up!192825818!crank2!down!192826182!crank2!up!192827610!crank2!down!192827975!crank2!up!192829399!crank2!down!192829757!crank2!up!192831154!crank2!down!192831507!r!187!192834224!crank!down!192834224!crank2!up!192836757!crank2!down!192841994!crank2!up!192843561!crank2!down!192843925!crank2!up!192845334!crank2!down!192845693!crank2!up!192847086!crank2!down!192847439!crank!up!192853135!crank2!up!192857701!crank2!down!192858065!crank2!up!192859491!crank2!down!192859858!crank2!up!192861269!crank2!down!192861626!crank2!up!192863025!crank2!down!192863382!crank2!up!192868647!crank!down!192871268!crank2!down!192872804!crank2!up!192872804!crank!down!192872804!crank!up!192872804!crank2!down!192873898!crank2!up!192875508!crank2!down!192875887!crank2!up!192877357!crank2!down!192877732!crank2!up!192879192!crank2!down!192879565!crank!up!192886293!r!0!194982088!crank!down!194982088!crank2!up!194984699!crank2!down!194990112!crank2!up!194991715!crank2!down!194992085!crank2!up!194993530!crank2!down!194993884!crank2!up!194995292!crank2!down!194995645!crank!up!195001475!crank2!up!195006153!crank2!down!195006515!crank2!up!195007968!crank2!down!195008325!crank2!up!195009773!crank2!down!195010134!crank2!up!195011549!crank2!down!195011901!crank2!up!195017256!crank!down!195019915!crank2!down!195022597!crank2!up!195024189!crank2!down!195024554!crank2!up!195025980!crank2!down!195026329!crank2!up!195027744!crank2!down!195028103!crank!up!195033418!crank2!up!195038542!crank2!down!195038911!crank2!up!195040351!crank2!down!195040722!crank2!up!195042167!crank2!down!195042529!crank2!up!195043934!crank2!down!195044294!r!187!195047060!crank!down!195047060!crank2!up!195049619!crank2!down!195054954!crank2!up!195056549!crank2!down!195056920!crank2!up!195058345!crank2!down!195058703!crank2!up!195060114!crank2!down!195060464!crank!up!195066245!crank2!up!195070882!crank2!down!195071250!crank2!up!195072689!crank2!down!195073054!crank2!up!195074479!,");
+ }
+
+ public static WavePanel getInstance() {
+ return instance;
+ }
+
+ public void displayChart(String value) {
+ WaveChart map = WaveChartParser.unpackToMap(value);
+
+ StringBuilder revolutions = map.get(RevolutionLog.TOP_DEAD_CENTER_MESSAGE);
+
+ statusPanel.setRevolutions(revolutions);
+
+ for (Map.Entry e : map.map.entrySet()) {
+ String imageName = e.getKey();
+ String report = e.getValue().toString();
+
+ UpDownImage image = images.get(imageName);
+ if (image == null)
+ continue;
+ image.setRevolutions(revolutions);
+ List list = WaveReport.parse(report);
+ if (list.isEmpty()) {
+ image.onUpdate(); // this would reset empty image
+ continue;
+ }
+ WaveReport wr = new WaveReport(list);
+ image.setWaveReport(wr, revolutions);
+ }
+ }
+
+ private void createSecondaryImage(String name) {
+ UpDownImage image = register(name).setTranslator(crank.createTranslator());
+ image.setZoomProvider(zoomControl);
+ imagePanel.add(image);
+ imagePanel.setLayout(new GridLayout(images.size(), 1));
+ }
+
+ private void saveImage() {
+ int rpm = RpmModel.getInstance().getValue();
+ double maf = SensorCentral.getInstance().getValue(Sensor.MAF);
+ String fileName = FileLog.getDate() + "rpm_" + rpm + "_maf_" + maf + ".png";
+ UiUtils.saveImage(fileName, imagePanel);
+ }
+
+ private UpDownImage register(String name) {
+ UpDownImage image = new UpDownImage(name);
+ image.addMouseMotionListener(statusPanel.motionAdapter);
+ images.put(name, image);
+ return image;
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/ZoomControl.java b/java_console/ui/src/com/irnems/ui/ZoomControl.java
new file mode 100644
index 0000000000..5203a3f79a
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/ZoomControl.java
@@ -0,0 +1,67 @@
+package com.irnems.ui;
+
+import com.irnems.waves.ZoomProvider;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * 7/7/13
+ * (c) Andrey Belomutskiy
+ */
+public class ZoomControl extends JPanel implements ZoomProvider {
+ private final JLabel currentValue = new JLabel();
+ private double value;
+ public ZoomControlListener listener = null;
+
+ public ZoomControl() {
+ super(new FlowLayout());
+ setValue(1);
+
+// final JTextField text = new JTextField() {
+// @Override
+// public Dimension getPreferredSize() {
+// Dimension size = super.getPreferredSize();
+// return new Dimension(200, size.height);
+// }
+// };
+
+ add(currentValue);
+
+ JButton plus = new JButton("+");
+ plus.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setValue(value * 1.1);
+ }
+ });
+ add(plus);
+
+ JButton minus = new JButton("-");
+ minus.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ setValue(value / 1.1);
+ }
+ });
+ add(minus);
+ }
+
+ private void setValue(double value) {
+ this.value = value;
+ currentValue.setText(String.format(" %.4fms", value));
+ if (listener != null)
+ listener.onZoomChange();
+ }
+
+ @Override
+ public double getZoomValue() {
+ return value;
+ }
+
+ interface ZoomControlListener {
+ void onZoomChange();
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/test/RpmModelTest.java b/java_console/ui/src/com/irnems/ui/test/RpmModelTest.java
new file mode 100644
index 0000000000..6e9ca48cdf
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/test/RpmModelTest.java
@@ -0,0 +1,28 @@
+package com.irnems.ui.test;
+
+import com.irnems.ui.RpmModel;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Date: 12/27/12
+ * (c) Andrey Belomutskiy
+ */
+public class RpmModelTest {
+ @Test
+ public void testRpmSmoothing() {
+ RpmModel r = RpmModel.getInstance();
+ r.setValue(100);
+
+ Assert.assertEquals(100, r.getSmoothedValue());
+
+ r.setValue(104);
+ Assert.assertEquals(100, r.getSmoothedValue());
+
+ r.setValue(96);
+ Assert.assertEquals(100, r.getSmoothedValue());
+
+ r.setValue(200);
+ Assert.assertEquals(200, r.getSmoothedValue());
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/test/UpDownSandbox.java b/java_console/ui/src/com/irnems/ui/test/UpDownSandbox.java
new file mode 100644
index 0000000000..110fb3c472
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/test/UpDownSandbox.java
@@ -0,0 +1,33 @@
+package com.irnems.ui.test;
+
+import com.rusefi.waves.WaveReport;
+import com.irnems.core.test.WaveReportTest;
+import com.irnems.ui.FrameHelper;
+import com.irnems.ui.widgets.UpDownImage;
+
+import javax.swing.*;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * Date: 6/23/13
+ * (c) Andrey Belomutskiy
+ */
+public class UpDownSandbox extends FrameHelper {
+
+ public UpDownSandbox() {
+
+ WaveReport wr = new WaveReport(WaveReportTest.report);
+
+ showFrame(new UpDownImage(wr, "test"));
+
+ }
+
+ public static void main(String[] args) throws InvocationTargetException, InterruptedException {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ new UpDownSandbox();
+ }
+ });
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/test/WavePanelSandbox.java b/java_console/ui/src/com/irnems/ui/test/WavePanelSandbox.java
new file mode 100644
index 0000000000..f67ac9cbfb
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/test/WavePanelSandbox.java
@@ -0,0 +1,33 @@
+package com.irnems.ui.test;
+
+import com.irnems.ui.FrameHelper;
+import com.irnems.ui.WavePanel;
+
+import javax.swing.*;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ * 7/25/13
+ * (c) Andrey Belomutskiy
+ */
+public class WavePanelSandbox extends FrameHelper {
+
+ public WavePanelSandbox() {
+
+
+ WavePanel wp = WavePanel.getInstance();
+
+ wp.displayChart("Injector 1!down!55013038!crank!up!55013444!crank!down!55013781!Injector 3!up!55013789!Injector 3!down!55013969!crank2!up!55014065!crank!up!55014358!crank!down!55014706!input1 A8!up!55014730!Injector 4!up!55014734!Injector 4!down!55014933!input1 A8!down!55014941!crank!up!55015298!crank!down!55015638!Injector 2!up!55015644!Injector 2!down!55015823!crank2!down!55015932!crank!up!55016223!crank!down!55016566!Injector 1!up!55016574!Injector 1!down!55016753!crank!up!55017148!crank!down!55017494!Injector 3!up!55017499!Injector 3!down!55017679!crank2!up!55017777!crank!up!55018070!crank!down!55018406!Injector 4!input1 A8!up!55018414!up!55018416!input1 A8!down!55018439!input1 A8!up!55018439!Injector 4!down!55018593!input1 A8!down!55018600!crank!up!55018989!crank!down!55019330!Injector 2!up!55019338!Injector 2!down!55019517!crank2!down!55019618!crank!up!55019909!crank!down!55020246!Injector 1!up!55020251!Injector 1!down!55020430!crank!up!55020827!crank!down!55021171!Injector 3!up!55021179!Injector 3!down!55021358!crank2!up!55021453!crank!up!55021747!crank!down!55022084!Injector 4input1 A8!up!55022089!!up!55022093!Injector 4!down!55022270!input1 A8!down!55022276!input1 A8!up!55022276!input1 A8!down!55022276!crank!up!55022666!crank!down!55023006!Injector 2!up!55023011!Injector 2!down!55023191!crank2!down!55023294!crank!up!55023584!crank!down!55023928!Injector 1!up!55023932!Injector 1!down!55024109!crank!up!55024507!crank!down!55024846!Injector 3!up!55024871!Injector 3!down!55025072!crank2!up!55025138!crank!up!55025440!crank!down!55025780!Injector 4input1 A8!up!55025785!!up!55025791!input1 A8!down!55025923!input1 A8!up!55025923!Injector 4!down!55025970!input1 A8!down!55025976!input1 A8!up!55025976!crank!up!55026355!crank!down!55026690!Injector 2!up!55026696!Injector 2!down!55026874!crank2!down!55026979!crank!up!55027268!crank!down!55027611!Injector 1!up!55027619!Injector 1!down!55027800!crank!up!55028186!crank!down!55028520!,");
+
+ showFrame(wp);
+ }
+
+ public static void main(String[] args) throws InvocationTargetException, InterruptedException {
+ SwingUtilities.invokeAndWait(new Runnable() {
+ @Override
+ public void run() {
+ new WavePanelSandbox();
+ }
+ });
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/AdcDebugControl.java b/java_console/ui/src/com/irnems/ui/widgets/AdcDebugControl.java
new file mode 100644
index 0000000000..2b03a06980
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/AdcDebugControl.java
@@ -0,0 +1,15 @@
+package com.irnems.ui.widgets;
+
+import com.irnems.core.Sensor;
+
+/**
+ * 7/11/13
+ * (c) Andrey Belomutskiy
+ */
+public class AdcDebugControl extends BooleanFlagControlPanel {
+ public AdcDebugControl() {
+ super("Adc Debug", "");
+ installStatusReader(Sensor.ADC_STATUS);
+ installCommand("adcDebug ");
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/AnyCommand.java b/java_console/ui/src/com/irnems/ui/widgets/AnyCommand.java
new file mode 100644
index 0000000000..c5239d6c77
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/AnyCommand.java
@@ -0,0 +1,47 @@
+package com.irnems.ui.widgets;
+
+import com.rusefi.io.CommandQueue;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Date: 3/20/13
+ * (c) Andrey Belomutskiy
+ */
+public class AnyCommand extends JPanel {
+ public AnyCommand() {
+ setBorder(BorderFactory.createLineBorder(Color.PINK));
+ setLayout(new FlowLayout(FlowLayout.LEFT));
+ add(new JLabel("Command: "));
+ final JTextField text = createCommandControl();
+ add(text);
+ }
+
+ public static JTextField createCommandControl() {
+ final JTextField text = new JTextField() {
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ return new Dimension(200, size.height);
+ }
+ };
+ text.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ String cmd = text.getText();
+ int timeout = isSlowCommand(cmd) ? 5000 : 300;
+ CommandQueue.getInstance().write(cmd, timeout);
+ }
+ });
+ // todo: limit the length of text in the text field
+ return text;
+ }
+
+ private static boolean isSlowCommand(String cmd) {
+ String lc = cmd.toLowerCase();
+ return lc.startsWith("set_engine_type") || lc.startsWith("writeconfig") || lc.startsWith("showconfig");
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/BooleanFlagControlPanel.java b/java_console/ui/src/com/irnems/ui/widgets/BooleanFlagControlPanel.java
new file mode 100644
index 0000000000..a46f867eab
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/BooleanFlagControlPanel.java
@@ -0,0 +1,50 @@
+package com.irnems.ui.widgets;
+
+import com.irnems.core.Sensor;
+import com.irnems.core.SensorCentral;
+import com.rusefi.io.CommandQueue;
+import net.miginfocom.swing.MigLayout;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * This panel turns ON/OFF some rusefi configuration property
+ *
+ * 7/11/13
+ * (c) Andrey Belomutskiy
+ */
+public class BooleanFlagControlPanel {
+ private final JPanel content = new JPanel(new MigLayout());
+ protected final JCheckBox checkBox;
+
+ public BooleanFlagControlPanel(String labelCaption, String checkboxCaption) {
+ content.add(new JLabel(labelCaption));
+ checkBox = new JCheckBox(checkboxCaption);
+ content.add(checkBox);
+ }
+
+ public JComponent getControl() {
+ return content;
+ }
+
+ protected void installCommand(final String command) {
+ checkBox.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int mode = checkBox.isSelected() ? 1 : 0;
+ CommandQueue.getInstance().write(command + mode);
+ }
+ });
+ }
+
+ protected void installStatusReader(final Sensor statusSensor) {
+ SensorCentral.getInstance().addListener(statusSensor, new SensorCentral.AdcListener() {
+ @Override
+ public void onAdcUpdate(SensorCentral model, double value) {
+ checkBox.setSelected(value > 0);
+ }
+ });
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/IdleLabel.java b/java_console/ui/src/com/irnems/ui/widgets/IdleLabel.java
new file mode 100644
index 0000000000..4e424b4cb2
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/IdleLabel.java
@@ -0,0 +1,21 @@
+package com.irnems.ui.widgets;
+
+import com.irnems.core.Sensor;
+import com.irnems.core.SensorCentral;
+
+import javax.swing.*;
+
+/**
+ * 8/2/13
+ * (c) Andrey Belomutskiy
+ */
+public class IdleLabel extends JLabel {
+ public IdleLabel() {
+ SensorCentral.getInstance().addListener(Sensor.IDLE_SWITCH, new SensorCentral.AdcListener() {
+ @Override
+ public void onAdcUpdate(SensorCentral model, double value) {
+ IdleLabel.this.setText("Idle: " + (value == 0));
+ }
+ });
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/InjectorControl.java b/java_console/ui/src/com/irnems/ui/widgets/InjectorControl.java
new file mode 100644
index 0000000000..31ac982d9c
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/InjectorControl.java
@@ -0,0 +1,15 @@
+package com.irnems.ui.widgets;
+
+import com.irnems.core.Sensor;
+
+/**
+ * 7/13/13
+ * (c) Andrey Belomutskiy
+ */
+public class InjectorControl extends BooleanFlagControlPanel {
+ public InjectorControl(int id, Sensor sensor) {
+ super("Injector " + id, "");
+ installStatusReader(sensor);
+ installCommand("injector " + id + " ");
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/JTextFieldWithWidth.java b/java_console/ui/src/com/irnems/ui/widgets/JTextFieldWithWidth.java
new file mode 100644
index 0000000000..4bf6b80b27
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/JTextFieldWithWidth.java
@@ -0,0 +1,31 @@
+package com.irnems.ui.widgets;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * 7/18/13
+ * (c) Andrey Belomutskiy
+ */
+public class JTextFieldWithWidth extends JTextField {
+ private int width;
+
+ public JTextFieldWithWidth() {
+ this("", 200);
+ }
+
+ public JTextFieldWithWidth(String text) {
+ this(text, 200);
+ }
+
+ public JTextFieldWithWidth(String text, int width) {
+ super(text);
+ this.width = width;
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ return new Dimension(width, size.height);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/LogModeWidget.java b/java_console/ui/src/com/irnems/ui/widgets/LogModeWidget.java
new file mode 100644
index 0000000000..43a582865c
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/LogModeWidget.java
@@ -0,0 +1,35 @@
+package com.irnems.ui.widgets;
+
+import com.rusefi.io.CommandQueue;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+/**
+ * Date: 3/29/13
+ * (c) Andrey Belomutskiy
+ */
+public class LogModeWidget {
+ private final JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
+
+ private final JCheckBox mode = new JCheckBox("full logging");
+
+ public LogModeWidget() {
+ panel.setBorder(BorderFactory.createLineBorder(Color.black));
+ panel.add(mode);
+
+ mode.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int code = mode.isSelected() ? 1 : 0;
+ CommandQueue.getInstance().write("fl " + code);
+ }
+ });
+ }
+
+ public JPanel getPanel() {
+ return panel;
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/MafCommand.java b/java_console/ui/src/com/irnems/ui/widgets/MafCommand.java
new file mode 100644
index 0000000000..ffeda2ff35
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/MafCommand.java
@@ -0,0 +1,42 @@
+package com.irnems.ui.widgets;
+
+import com.irnems.EcuStimulator;
+import com.irnems.core.Sensor;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+
+/**
+ * 6/30/13
+ * (c) Andrey Belomutskiy
+ */
+public class MafCommand extends JPanel {
+ public MafCommand() {
+ setBorder(BorderFactory.createLineBorder(Color.BLACK));
+ setLayout(new FlowLayout(FlowLayout.LEFT));
+ add(new JLabel("MAF: "));
+ final JSpinner maf = new JSpinner(new SpinnerNumberModel(Double.valueOf(1.5), null, null, Double.valueOf(0.11))) {
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ return new Dimension(100, size.height);
+ }
+ };
+ final SpinnerNumberModel m = (SpinnerNumberModel) maf.getModel();
+// m.setStepSize(0.1);
+// maf.setValue(3);
+ maf.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ Double value = (Double) m.getValue();
+
+ EcuStimulator.setPotVoltage(value, Sensor.MAF);
+ }
+ });
+
+
+ add(maf);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/PotCommand.java b/java_console/ui/src/com/irnems/ui/widgets/PotCommand.java
new file mode 100644
index 0000000000..724ffcfc10
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/PotCommand.java
@@ -0,0 +1,109 @@
+package com.irnems.ui.widgets;
+
+import com.irnems.core.MessagesCentral;
+import com.irnems.core.Sensor;
+import com.irnems.core.SensorCentral;
+import com.rusefi.io.CommandQueue;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+
+/**
+ * Date: 3/17/13
+ * (c) Andrey Belomutskiy
+ */
+public class PotCommand {
+ public static final double VOLTAGE_CORRECTION = 2.9 / 3;
+ private static final int MAF_CHANNEL_ECU_INTERNAL_RESISTANCE = 1000; // 1KOhm internal resistor?
+ public final JPanel panel;
+ final JSpinner potSpinner;
+
+ public PotCommand(final int channel) {
+ final JLabel rValue = new JLabel();
+
+ final JSpinner voltageSpinner = new JSpinner(new SpinnerNumberModel(0.0, 0, 5, 0.1)) {
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ return new Dimension(100, size.height);
+ }
+ };
+ ((SpinnerNumberModel) voltageSpinner.getModel()).setStepSize(0.1);
+ voltageSpinner.setValue(1.0);
+ voltageSpinner.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ Double Vout = (Double) voltageSpinner.getValue();
+ int d = getPotResistance(Vout);
+ potSpinner.setValue(d);
+ }
+ });
+
+
+ potSpinner = new JSpinner() {
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ return new Dimension(100, size.height);
+ }
+ };
+ potSpinner.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ Integer value = (Integer) potSpinner.getValue();
+ try {
+ requestPotChange(channel, value);
+ } catch (IllegalArgumentException ignore) {
+ return;
+ }
+ int r = getRbyD(value);
+ rValue.setText("R=" + r);
+ }
+ });
+ potSpinner.setValue(10);
+
+
+ JPanel upper = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ upper.add(new JLabel("set pot" + channel + ": "));
+ upper.add(potSpinner);
+ upper.add(rValue);
+
+ JPanel center = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ center.add(new JLabel("volts: "));
+ center.add(voltageSpinner);
+
+ panel = new JPanel(new BorderLayout());
+ panel.setBorder(BorderFactory.createLineBorder(Color.CYAN));
+ panel.add(upper, BorderLayout.NORTH);
+ panel.add(center, BorderLayout.CENTER);
+ }
+
+ public static void requestPotChange(int channel, int resistance) {
+ if (resistance < 0 || resistance > 10000)
+ throw new IllegalArgumentException("resistance: " + resistance);
+ CommandQueue.getInstance().write("pot" + channel + " " + resistance);
+ }
+
+ public static int getPotResistance(Double vout) {
+ double vRef = SensorCentral.getInstance().getValue(Sensor.VREF) * VOLTAGE_CORRECTION;
+ double r = getR1InVoltageDividor3(vout, vRef, MAF_CHANNEL_ECU_INTERNAL_RESISTANCE);
+ MessagesCentral.getInstance().postMessage(PotCommand.class, "VRef=" + vRef + ", needed resistance: " + r);
+ // pot command accept resistance and does the conversion itself
+ return (int) r;
+ }
+
+ private static int getRbyD(Integer value) {
+ return (int) (10000.0 * (256 - value) / 256) + 52;
+ }
+
+// private static int getDbyR(double Rwa) {
+// return (int) (256 - (Rwa - 52) * 256 / 10000);
+// }
+
+ private static double getR1InVoltageDividor3(double Vout, double Vin, double r2) {
+ return r2 * Vin / Vout - r2;
+ }
+
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/RpmCommand.java b/java_console/ui/src/com/irnems/ui/widgets/RpmCommand.java
new file mode 100644
index 0000000000..453cbb8778
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/RpmCommand.java
@@ -0,0 +1,43 @@
+package com.irnems.ui.widgets;
+
+import com.rusefi.io.CommandQueue;
+
+import javax.swing.*;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import java.awt.*;
+
+/**
+ * Date: 3/17/13
+ * (c) Andrey Belomutskiy
+ */
+public class RpmCommand extends JPanel {
+ public RpmCommand() {
+ setBorder(BorderFactory.createLineBorder(Color.ORANGE));
+ setLayout(new FlowLayout(FlowLayout.LEFT));
+ add(new JLabel("set RPM: "));
+
+ final JSpinner spinner = new JSpinner() {
+ @Override
+ public Dimension getPreferredSize() {
+ Dimension size = super.getPreferredSize();
+ return new Dimension(100, size.height);
+ }
+ };
+ SpinnerNumberModel m = (SpinnerNumberModel) spinner.getModel();
+ m.setStepSize(100);
+ spinner.setValue(600);
+
+ spinner.addChangeListener(new ChangeListener() {
+ @Override
+ public void stateChanged(ChangeEvent e) {
+ requestRpmChange((Integer) spinner.getValue());
+ }
+ });
+ add(spinner);
+ }
+
+ public static void requestRpmChange(int rpm) {
+ CommandQueue.getInstance().write("rpm " + rpm);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/UpDownImage.java b/java_console/ui/src/com/irnems/ui/widgets/UpDownImage.java
new file mode 100644
index 0000000000..7a0d111bcb
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/UpDownImage.java
@@ -0,0 +1,194 @@
+package com.irnems.ui.widgets;
+
+import com.irnems.waves.TimeAxisTranslator;
+import com.rusefi.waves.WaveReport;
+import com.irnems.waves.ZoomProvider;
+import com.rusefi.waves.RevolutionLog;
+
+import javax.swing.*;
+import java.awt.*;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * This is a renderer of {@link WaveReport} - this makes a simple Logical Analyzer
+ *
+ *
+ * Date: 6/23/13
+ * (c) Andrey Belomutskiy
+ */
+public class UpDownImage extends JPanel {
+ private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
+ private static final int LINE_SIZE = 20;
+
+ private long lastUpdateTime;
+ private ZoomProvider zoomProvider = ZoomProvider.DEFAULT;
+ private WaveReport wr;
+ private StringBuilder revolutions;
+ private final String name;
+ private TimeAxisTranslator translator;
+ private RevolutionLog time2rpm = RevolutionLog.parseRevolutions(null);
+
+ public UpDownImage(final String name) {
+ this(WaveReport.MOCK, name);
+ }
+
+ public void setZoomProvider(ZoomProvider zoomProvider) {
+ this.zoomProvider = zoomProvider;
+ }
+
+ public void onUpdate() {
+ trueRepaint(this);
+ }
+
+ public static void trueRepaint(JComponent control) {
+ control.invalidate();
+ control.repaint();
+ }
+
+ public UpDownImage(WaveReport wr, String name) {
+ this.name = name;
+ setWaveReport(wr, null);
+ setOpaque(true);
+ translator = createTranslator();
+ }
+
+ public UpDownImage setTranslator(TimeAxisTranslator translator) {
+ this.translator = translator;
+ return this;
+ }
+
+ public TimeAxisTranslator createTranslator() {
+ return new TimeAxisTranslator() {
+ @Override
+ public int timeToScreen(int time, int width, ZoomProvider zoomProvider) {
+ return UpDownImage.this.wr.timeToScreen(time, width, zoomProvider);
+ }
+
+ @Override
+ public double screenToTime(int screen, int width, ZoomProvider zoomProvider) {
+ return UpDownImage.this.wr.screenToTime(screen, width, zoomProvider);
+ }
+
+ @Override
+ public int getMaxTime() {
+ return UpDownImage.this.wr.getMaxTime();
+ }
+
+ @Override
+ public int getMinTime() {
+ return UpDownImage.this.wr.getMinTime();
+ }
+ };
+ }
+
+ public void setWaveReport(WaveReport wr, StringBuilder revolutions) {
+ this.wr = wr;
+ this.revolutions = revolutions;
+ lastUpdateTime = System.currentTimeMillis();
+ onUpdate();
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ Graphics2D g2 = (Graphics2D) g;
+
+ Dimension d = getSize();
+ g.setColor(getBackground());
+ g.fillRect(0, 0, d.width, d.height);
+
+ for (WaveReport.UpDown upDown : wr.getList())
+ paintUpDown(d, upDown, g);
+
+ paintScaleLines(g2, d);
+
+ int duration = wr.getDuration();
+ g2.setColor(Color.black);
+
+ int line = 0;
+ g.drawString(name, 5, ++line * LINE_SIZE);
+ g.drawString("Tick length: " + duration + "; count=" + wr.getList().size(), 5, ++line * LINE_SIZE);
+ g.drawString("Total seconds: " + (duration / WaveReport.SYS_TICKS_PER_MS / 000.0), 5, ++line * LINE_SIZE);
+ g.drawString(FORMAT.format(new Date(lastUpdateTime)), 5, ++line * LINE_SIZE);
+
+ drawStartOfRevolution(g2, d);
+ }
+
+ private void drawStartOfRevolution(Graphics2D g2, Dimension d) {
+ if (revolutions == null)
+ return;
+
+ RevolutionLog time2rpm = RevolutionLog.parseRevolutions(revolutions);
+
+ g2.setStroke(new BasicStroke());
+ for (int time : time2rpm.keySet()) {
+ int x = translator.timeToScreen(time, d.width, zoomProvider);
+ g2.setColor(Color.green);
+ g2.drawLine(x, 0, x, d.height);
+ }
+ }
+
+ private static final BasicStroke LONG_STROKE = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f,
+ new float[]{21.0f, 7.0f}, 0.0f);
+
+ /**
+ * This method draws a vertical line every millisecond
+ */
+ private void paintScaleLines(Graphics2D g2, Dimension d) {
+ int fromMs = translator.getMinTime() / WaveReport.mult;
+ g2.setStroke(LONG_STROKE);
+ g2.setColor(Color.red);
+
+ int toMs = translator.getMaxTime() / WaveReport.mult;
+ for (int ms = fromMs; ms <= toMs; ms++) {
+ int tick = ms * WaveReport.mult;
+ int x = translator.timeToScreen(tick, d.width, zoomProvider);
+ g2.drawLine(x, 0, x, d.height);
+ }
+ }
+
+ private void paintUpDown(Dimension d, WaveReport.UpDown upDown, Graphics g) {
+
+ int x1 = translator.timeToScreen(upDown.upTime, d.width, zoomProvider);
+ int x2 = translator.timeToScreen(upDown.downTime, d.width, zoomProvider);
+
+ int y = (int) (0.2 * d.height);
+
+// g.setColor(Color.cyan);
+// g.fillRect(x1, y, x2 - x1, d.height);
+
+ g.setColor(Color.lightGray);
+ g.fillRect(x1, y, x2 - x1, d.height - y);
+
+
+ g.setColor(Color.blue);
+ g.drawLine(x1, y, x2, y);
+ g.drawLine(x1, y, x1, d.height);
+ g.drawLine(x2, y, x2, d.height);
+
+ g.setColor(Color.red);
+ String durationString = String.format(" %.2fms", upDown.getDuration() / WaveReport.SYS_TICKS_PER_MS);
+
+ g.drawString(durationString, x1, (int) (0.5 * d.height));
+
+ g.setColor(Color.darkGray);
+ if (upDown.upIndex != -1)
+ g.drawString("" + upDown.upIndex, x1, (int) (0.25 * d.height));
+ if (upDown.downIndex != -1)
+ g.drawString("" + upDown.downIndex, x2, (int) (0.25 * d.height));
+
+ int offset = 3;
+ g.setColor(Color.black);
+ String fromAngle = time2rpm.getCrankAngleByTimeString(upDown.upTime);
+ g.drawString(fromAngle, x1 + offset, (int) (0.75 * d.height));
+
+ g.setColor(Color.green);
+ String toAngle = time2rpm.getCrankAngleByTimeString(upDown.downTime);
+ g.drawString(toAngle, x1 + offset, (int) (1.0 * d.height));
+ }
+
+ public void setRevolutions(StringBuilder revolutions) {
+ time2rpm = RevolutionLog.parseRevolutions(revolutions);
+ }
+}
diff --git a/java_console/ui/src/com/irnems/ui/widgets/WaveInfoPanel.java b/java_console/ui/src/com/irnems/ui/widgets/WaveInfoPanel.java
new file mode 100644
index 0000000000..9e724df135
--- /dev/null
+++ b/java_console/ui/src/com/irnems/ui/widgets/WaveInfoPanel.java
@@ -0,0 +1,12 @@
+package com.irnems.ui.widgets;
+
+/**
+ * Date: 1/14/13
+ * (c) Andrey Belomutskiy
+ */
+public class WaveInfoPanel extends BooleanFlagControlPanel {
+ public WaveInfoPanel(final int index) {
+ super("wave" + index, "active on low");
+ installCommand("wm " + index + " ");
+ }
+}
diff --git a/java_console/ui/src/com/rusefi/AnalogChartPanel.java b/java_console/ui/src/com/rusefi/AnalogChartPanel.java
new file mode 100644
index 0000000000..377a902f34
--- /dev/null
+++ b/java_console/ui/src/com/rusefi/AnalogChartPanel.java
@@ -0,0 +1,131 @@
+package com.rusefi;
+
+import com.irnems.FileLog;
+import com.irnems.core.MessagesCentral;
+import com.irnems.ui.RpmModel;
+import com.irnems.ui.UiUtils;
+import com.irnems.ui.widgets.UpDownImage;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.*;
+import java.util.List;
+
+/**
+ * Date: 12/21/13
+ * Andrey Belomutskiy (c) 2012-2013
+ */
+public class AnalogChartPanel extends JPanel {
+ private static final String KEY = "analog_chart";
+
+ private final TreeMap values = new TreeMap();
+ private final AnalogChart analogChart = new AnalogChart();
+
+ private double minX;
+ private double maxX;
+ private double minY;
+ private double maxY;
+
+ private boolean paused = false;
+
+ public AnalogChartPanel() {
+ super(new BorderLayout());
+
+ MessagesCentral.getInstance().addListener(new MessagesCentral.MessageListener() {
+ @Override
+ public void onMessage(Class clazz, String message) {
+ if (paused || !message.startsWith(KEY))
+ return;
+ unpackValues(values, message);
+
+// MessagesCentral.getInstance().postMessage(AnalogChartPanel.class, "chart arrived, len=" + message.length());
+
+ processValues();
+ UpDownImage.trueRepaint(analogChart);
+
+ }
+ });
+
+ JPanel upperPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
+
+ JButton imageButton = new JButton("save image");
+ upperPanel.add(imageButton);
+ imageButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ int rpm = RpmModel.getInstance().getValue();
+ String fileName = FileLog.getDate() + "rpm_" + rpm + "_analog" + ".png";
+ UiUtils.saveImage(fileName, analogChart);
+ }
+ });
+
+ final JButton pauseButton = new JButton("Pause");
+ upperPanel.add(pauseButton);
+ pauseButton.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ paused = !paused;
+ pauseButton.setText(paused ? "Resume" : "Pause");
+ }
+ });
+
+ add(upperPanel, BorderLayout.NORTH);
+ add(analogChart, BorderLayout.CENTER);
+ }
+
+ private void processValues() {
+ List keys = new ArrayList(values.keySet());
+ minX = keys.get(0);
+ maxX = keys.get(keys.size() - 1);
+ FileLog.rlog("Analog chart from " + minX + " to " + maxX);
+
+ TreeSet sortedValues = new TreeSet();
+ sortedValues.addAll(values.values());
+ List values = new ArrayList(sortedValues);
+
+ minY = values.get(0);
+ maxY = values.get(values.size() - 1);
+ }
+
+ private class AnalogChart extends JComponent {
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ //Graphics2D g2 = (Graphics2D) g;
+
+ Dimension size = getSize();
+
+ g.drawString("X range from " + minX + " to " + maxX, 4, 20);
+ g.drawString("Y range from " + minY + " to " + maxY, 4, 40);
+
+ int prevX = 0;
+ int prevY = size.height;
+
+ double bX = size.width / (maxX - minX);
+ double bY = size.height / (maxY - minY);
+
+ for (Map.Entry e : values.entrySet()) {
+ int x = (int) ((e.getKey() - minX) * bX);
+ int y = size.height - (int) ((e.getValue() - minY) * bY);
+
+ g.drawLine(prevX, prevY, x, y);
+ prevX = x;
+ prevY = y;
+ }
+ }
+ }
+
+ private void unpackValues(TreeMap values, String chart) {
+ values.clear();
+
+ String[] tokens = chart.split("\\|");
+ for (int i = 1; i < tokens.length - 1; ) {
+ String key = tokens[i++];
+ String value = tokens[i++];
+
+ values.put(Double.parseDouble(key), Double.parseDouble(value));
+ }
+ }
+}
\ No newline at end of file
diff --git a/java_console/ui/src/com/rusefi/PortLookupFrame.java b/java_console/ui/src/com/rusefi/PortLookupFrame.java
new file mode 100644
index 0000000000..e239d9ddb8
--- /dev/null
+++ b/java_console/ui/src/com/rusefi/PortLookupFrame.java
@@ -0,0 +1,54 @@
+package com.rusefi;
+
+import com.irnems.Launcher;
+import com.rusefi.io.tcp.TcpConnector;
+import jssc.SerialPortList;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * This frame is used on startup to select the port we would be using
+ *
+ * @author Andrey Belomutskiy
+ * 2/14/14
+ */
+public class PortLookupFrame {
+ public static void chooseSerialPort() {
+ java.util.List ports = new ArrayList();
+ ports.addAll(Arrays.asList(SerialPortList.getPortNames()));
+ ports.addAll(TcpConnector.getAvailablePorts());
+
+
+ if (ports.size() == 0) {
+ JOptionPane.showMessageDialog(null, "No suitable ports found");
+ System.exit(-1);
+ }
+
+ final JFrame frame = new JFrame("Serial port selection");
+
+ JPanel panel = new JPanel(new FlowLayout());
+
+ for (final String port : ports) {
+ JButton button = new JButton("Use " + port);
+
+ button.addActionListener(new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ frame.dispose();
+ new Launcher(port);
+ }
+ });
+
+ panel.add(button);
+ }
+
+ frame.add(panel);
+ frame.pack();
+ frame.setVisible(true);
+ }
+}
diff --git a/java_console/ui/ui.iml b/java_console/ui/ui.iml
new file mode 100644
index 0000000000..756ce4e759
--- /dev/null
+++ b/java_console/ui/ui.iml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unit_tests/.cproject b/unit_tests/.cproject
new file mode 100644
index 0000000000..71e7c9e65e
--- /dev/null
+++ b/unit_tests/.cproject
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/unit_tests/.project b/unit_tests/.project
new file mode 100644
index 0000000000..3238f268dc
--- /dev/null
+++ b/unit_tests/.project
@@ -0,0 +1,27 @@
+
+
+ unit_tests
+
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.genmakebuilder
+ clean,full,incremental,
+
+
+
+
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
+ full,incremental,
+
+
+
+
+
+ org.eclipse.cdt.core.cnature
+ org.eclipse.cdt.core.ccnature
+ org.eclipse.cdt.managedbuilder.core.managedBuildNature
+ org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
+
+
diff --git a/unit_tests/Makefile b/unit_tests/Makefile
new file mode 100644
index 0000000000..1eb54405fd
--- /dev/null
+++ b/unit_tests/Makefile
@@ -0,0 +1,221 @@
+##############################################################################
+# Build global options
+# NOTE: Can be overridden externally.
+#
+
+PROJECT_DIR = ../firmware
+#CHIBIOS = $(PROJECT_DIR)/chibios
+
+# Compiler options here.
+ifeq ($(USE_OPT),)
+# -O2 is needed for mingw, without it there is a linking issue to isnanf?!?!
+ #USE_OPT = $(RFLAGS) -O2 -fgnu89-inline -ggdb -fomit-frame-pointer -falign-functions=16 -std=gnu99 -Werror-implicit-function-declaration -Werror -Wno-error=pointer-sign -Wno-error=unused-function -Wno-error=unused-variable -Wno-error=sign-compare -Wno-error=unused-parameter -Wno-error=missing-field-initializers
+ USE_OPT = -c -Wall -O2
+endif
+
+# C specific options here (added to USE_OPT).
+ifeq ($(USE_COPT),)
+ USE_COPT = -std=gnu99 -fgnu89-inline
+endif
+
+# C++ specific options here (added to USE_OPT).
+ifeq ($(USE_CPPOPT),)
+ USE_CPPOPT = -fno-rtti -fpermissive -fno-exceptions -fno-use-cxa-atexit
+endif
+
+# Enable this if you want the linker to remove unused code and data
+ifeq ($(USE_LINK_GC),)
+ USE_LINK_GC = yes
+endif
+
+# If enabled, this option allows to compile the application in THUMB mode.
+ifeq ($(USE_THUMB),)
+ USE_THUMB = no
+endif
+
+# Enable this if you want to see the full log while compiling.
+ifeq ($(USE_VERBOSE_COMPILE),)
+ USE_VERBOSE_COMPILE = no
+endif
+
+#
+# Build global options
+##############################################################################
+
+##############################################################################
+# Architecture or project specific options
+#
+
+
+# List all default C defines here, like -D_DEBUG=1
+DDEFS =
+
+#
+# Architecture or project specific options
+##############################################################################
+
+##############################################################################
+# Project, sources and paths
+#
+
+# Define project name here
+PROJECT = rusefi_test
+
+#PROJECT_BOARD = OLIMEX_STM32_E407
+#ifneq ($(PROJECT_BOARD),OLIMEX_STM32_E407)
+# PROJECT_BOARD = ST_STM32F4_DISCOVERY
+#endif
+#DDEFS += -D$(PROJECT_BOARD)
+
+# Imported source files and paths
+include $(PROJECT_DIR)/util/util.mk
+include $(PROJECT_DIR)/config/engines/engines.mk
+include $(PROJECT_DIR)/controllers/algo/algo.mk
+include $(PROJECT_DIR)/controllers/math/math.mk
+include $(PROJECT_DIR)/controllers/sensors/sensors.mk
+include test.mk
+
+# Define linker script file here
+#LDSCRIPT= config/system/STM32F407xG.ld
+#LDSCRIPT= $(PORTLD)/STM32F407xG_CCM.ld
+
+# C sources that can be compiled in ARM or THUMB mode depending on the global
+# setting.
+CSRC = $(UTILSRC) \
+ $(CONTROLLERS_ALGO_SRC) \
+ $(CONTROLLERS_MATH_SRC) \
+ $(CONTROLLERS_SENSORS_SRC) \
+ $(ENGINES_SRC) \
+ $(TEST_SRC_C)
+
+# C++ sources that can be compiled in ARM or THUMB mode depending on the global
+# setting.
+CPPSRC = $(UTILSRC_CPP) \
+ $(CONTROLLERS_ALGO_SRC_CPP) \
+ $(ENGINES_SRC_CPP) \
+ $(TEST_SRC_CPP) \
+ main.cpp
+
+# C sources to be compiled in ARM mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+ACSRC =
+
+# C++ sources to be compiled in ARM mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+ACPPSRC =
+
+# C sources to be compiled in THUMB mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+TCSRC =
+
+# C sources to be compiled in THUMB mode regardless of the global setting.
+# NOTE: Mixing ARM and THUMB mode enables the -mthumb-interwork compiler
+# option that results in lower performance and larger code size.
+TCPPSRC =
+
+# List ASM source files here
+ASMSRC = $(PORTASM)
+
+INCDIR = . \
+ $(PROJECT_DIR)/util \
+ $(PROJECT_DIR)/config/engines \
+ $(PROJECT_DIR)/controllers/sensors \
+ $(PROJECT_DIR)/controllers/algo \
+ $(PROJECT_DIR)/controllers/math \
+ $(PROJECT_DIR)/ext_algo \
+ test_data_structures \
+ test_basic_math
+
+
+#
+# Project, sources and paths
+##############################################################################
+
+##############################################################################
+# Compiler settings
+#
+
+#MCU = cortex-m4
+
+ifeq ($(OS),Windows_NT)
+ TRGT = i686-pc-mingw32-
+else
+ TRGT =
+endif
+
+CC = $(TRGT)gcc
+CPPC = $(TRGT)g++
+# Enable loading with g++ only if you need C++ runtime support.
+# NOTE: You can use C++ even without C++ support if you are careful. C++
+# runtime support makes code size explode.
+#LD = $(TRGT)gcc
+LD = $(TRGT)g++
+CP = $(TRGT)objcopy
+AS = $(TRGT)gcc -x assembler-with-cpp
+OD = $(TRGT)objdump
+HEX = $(CP) -O ihex
+BIN = $(CP) -O binary
+
+# ARM-specific options here
+AOPT =
+
+# THUMB-specific options here
+TOPT = -mthumb -DTHUMB
+
+# Define C warning options here
+CWARN = -Wall -Wextra -Wstrict-prototypes
+
+# Define C++ warning options here
+CPPWARN = -Wall -Wextra
+
+#
+# Compiler settings
+##############################################################################
+
+##############################################################################
+# Start of default section
+#
+
+# List all default ASM defines here, like -D_DEBUG=1
+DADEFS =
+
+# List all default directories to look for include files here
+DINCDIR =
+
+# List the default directory to look for the libraries here
+DLIBDIR =
+
+# List all default libraries here
+DLIBS = -static-libgcc -static-libstdc++
+
+#
+# End of default section
+##############################################################################
+
+##############################################################################
+# Start of user section
+#
+
+# List all user C define here, like -D_DEBUG=1
+UDEFS =
+
+# Define ASM defines here
+UADEFS =
+
+# List all user directories here
+UINCDIR =
+
+# List the user directory to look for the libraries here
+ULIBDIR =
+
+# List all user libraries here
+ULIBS = -lm
+
+#
+# End of user defines
+##############################################################################
+
+include rules.mk
diff --git a/unit_tests/adc_inputs.h b/unit_tests/adc_inputs.h
new file mode 100644
index 0000000000..5c526173a7
--- /dev/null
+++ b/unit_tests/adc_inputs.h
@@ -0,0 +1,11 @@
+/**
+ * @file adc_inputs.h
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef ADC_INPUTS_H_
+#define ADC_INPUTS_H_
+
+#endif /* ADC_INPUTS_H_ */
diff --git a/unit_tests/boards.c b/unit_tests/boards.c
new file mode 100644
index 0000000000..5755c03f80
--- /dev/null
+++ b/unit_tests/boards.c
@@ -0,0 +1,23 @@
+/**
+ * @file board.c
+ *
+ * @date Nov 15, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "boards.h"
+
+float getVoltageDivided(int channel) {
+ return 0;
+}
+
+float getVoltage(int channel) {
+ return 0;
+}
+
+
+int getAdcValue(int channel) {
+ return 0;
+}
+
+
diff --git a/unit_tests/boards.h b/unit_tests/boards.h
new file mode 100644
index 0000000000..bbcf9f6256
--- /dev/null
+++ b/unit_tests/boards.h
@@ -0,0 +1,24 @@
+/*
+ * boards.h
+ *
+ * Created on: Nov 15, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef BOARDS_H_
+#define BOARDS_H_
+
+#define ADC_LOGIC_TPS 0
+#define ADC_LOGIC_AFR 0
+#define ADC_LOGIC_MAF 0
+#define ADC_LOGIC_MAP 0
+#define ADC_CHANNEL_VREF 0
+#define ADC_CHANNEL_VBATT 0
+#define ADC_LOGIC_INTAKE_AIR 0
+#define ADC_LOGIC_COOLANT 0
+
+float getVoltageDivided(int);
+float getVoltage(int channel);
+int getAdcValue(int channel);
+
+#endif /* BOARDS_H_ */
diff --git a/unit_tests/compile.bat b/unit_tests/compile.bat
new file mode 100644
index 0000000000..1c9d2c2e35
--- /dev/null
+++ b/unit_tests/compile.bat
@@ -0,0 +1,3 @@
+rm -rf .dep/
+rm -rf build/
+make
diff --git a/unit_tests/efifeatures.h b/unit_tests/efifeatures.h
new file mode 100644
index 0000000000..8845e51ac7
--- /dev/null
+++ b/unit_tests/efifeatures.h
@@ -0,0 +1,13 @@
+/*
+ * efifeatures.h
+ *
+ * Created on: Mar 7, 2014
+ * Author: Andrey
+ */
+
+#ifndef EFIFEATURES_H_
+#define EFIFEATURES_H_
+
+#define EFI_CLI_SUPPORT FALSE
+
+#endif /* EFIFEATURES_H_ */
diff --git a/unit_tests/global.h b/unit_tests/global.h
new file mode 100644
index 0000000000..fd7f9e766f
--- /dev/null
+++ b/unit_tests/global.h
@@ -0,0 +1,23 @@
+/*
+ * @file global.h
+ *
+ * @date Nov 28, 2013
+ * @author pc
+ */
+
+#ifndef GLOBAL_H_
+#define GLOBAL_H_
+
+#include
+#include
+
+#define EFI_SUPPORT_FORD_ASPIRE TRUE
+#define EFI_SUPPORT_DODGE_NEON TRUE
+#define EFI_SUPPORT_1995_FORD_INLINE_6 TRUE
+#define EFI_SUPPORT_FORD_FIESTA TRUE
+#define EFI_SUPPORT_NISSAN_PRIMERA TRUE
+
+#define TRUE 1
+#define FALSE 0
+
+#endif /* GLOBAL_H_ */
diff --git a/unit_tests/jenkins.sh b/unit_tests/jenkins.sh
new file mode 100644
index 0000000000..67df86f044
--- /dev/null
+++ b/unit_tests/jenkins.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+ echo "The PATH is ${PATH}"
+ cd "${WORKSPACE}"
+ echo "start in workspace ${PWD}"
+
+ cd firmware
+ echo "CD to ${PWD}"
+
+ rm -fR .dep
+ rm -fR build
+ make
+
+ if [ ! -f build/rusefi.hex ]; then
+ echo "Firmware compilation failed"
+ exit -1
+ fi
+
+ cd "${WORKSPACE}/win32_algo_tests"
+ echo "CD to ${PWD}"
+
+ rm -fR .dep
+ rm -fR build
+ make
+ if [ ! -f build/rusefi_test ]; then
+ echo "test compilation failed"
+ exit -1
+ fi
+
+# we want to terminate if test fails
+set -e
+
+ # invoke the tests - hopefully error code would be propagated?
+ build/rusefi_test
+
+cd "${WORKSPACE}/java_console"
+echo "CD to ${PWD}"
+
+#JAVA_HOME=/usr/lib/jvm/java-6-openjdk-amd64
+#ant
+
diff --git a/unit_tests/junction.exe b/unit_tests/junction.exe
new file mode 100644
index 0000000000..77054219b3
Binary files /dev/null and b/unit_tests/junction.exe differ
diff --git a/unit_tests/main.cpp b/unit_tests/main.cpp
new file mode 100644
index 0000000000..3335c4cd69
--- /dev/null
+++ b/unit_tests/main.cpp
@@ -0,0 +1,145 @@
+/*
+ ============================================================================
+ Name : main.c
+ Author : Andrey Belomutskiy
+ Copyright : (c) 2012-2013
+ Description : First step towards unit-testing rusEfi algorithms on win32
+ ============================================================================
+ */
+
+#include
+#include
+#include
+#include
+
+#include "main.h"
+
+extern "C"
+{
+
+#include "map_resize.h"
+#include "test_idle_controller.h"
+#include "test_interpolation_3d.h"
+#include "test_find_index.h"
+#include "test_fuel_map.h"
+#include "test_engine_math.h"
+#include "test_event_registry.h"
+#include "test_sensors.h"
+#include "test_signal_executor.h"
+#include "test_util.h"
+#include "engine_configuration.h"
+#include "test_trigger_decoder.h"
+
+}
+
+
+static engine_configuration_s ec;
+engine_configuration_s *engineConfiguration = &ec;
+
+static float absF(float value) {
+ return value > 0 ? value : -value;
+}
+
+void assertEqualsM(char *msg, float expected, float actual) {
+ if (isnan(actual) && !isnan(expected)) {
+ printf("Unexpected: %s %.4f while expected %.4f\r\n", msg, actual, expected);
+ exit(-1);
+ }
+
+ float delta = absF(actual - expected);
+ if (delta > 0.0001) {
+ printf("delta: %.7f\r\n", delta);
+ printf("Unexpected: %s %.4f while expected %.4f\r\n", msg, actual, expected);
+ exit(-1);
+ }
+ printf("Validated %s: %f\r\n", msg, expected);
+}
+
+void assertEquals(float expected, float actual) {
+ assertEqualsM("", expected, actual);
+}
+
+void assertTrueM(char *msg, float actual) {
+ assertEqualsM(msg, TRUE, actual);
+}
+
+void assertTrue(float actual) {
+ assertTrueM("", actual);
+}
+
+void assertFalseM(char *msg, float actual) {
+ assertEqualsM(msg, FALSE, actual);
+}
+
+void assertFalse(float actual) {
+ assertFalseM("", actual);
+}
+
+void chDbgAssert(int c, char *msg, void *arg) {
+ if (!c) {
+ printf("assert failed: %s\r\n", msg);
+ exit(-1);
+ }
+}
+
+static engine_configuration2_s ec2;
+engine_configuration2_s *engineConfiguration2 = &ec2;
+
+int main(void) {
+ testInterpolate3d();
+ testFindIndex();
+ testInterpolate2d();
+ testGpsParser();
+ testFuelMap();
+ testEngineMath();
+ testEventRegistry();
+ testSensors();
+ testCyclicBuffer();
+
+ testSignalExecutor();
+
+ testHistogram();
+
+ testTriggerDecoder();
+
+ testMalfunctionCentral();
+
+ testConsoleLogic();
+
+ testAngleResolver();
+
+ testPinHelper();
+ testSetTableValue();
+
+ printf("Success 20130319\r\n");
+
+// resizeMap();
+
+ return EXIT_SUCCESS;
+}
+
+void warning(char *msg, float value) {
+ printf("Warning: %s %f\r\n", msg, value);
+}
+
+void firmwareError(const char *fmt, ...) {
+ printf(fmt);
+ exit(-1);
+}
+
+void print(const char *format, ...) {
+ va_list ap;
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+}
+
+void fatal3(char *msg, char *file, int line) {
+ printf(msg);
+ exit(-1);
+}
+
+int warning(const char *fmt, ...) {
+ printf(fmt);
+ exit(-1);
+}
diff --git a/unit_tests/main.h b/unit_tests/main.h
new file mode 100644
index 0000000000..23f1765c86
--- /dev/null
+++ b/unit_tests/main.h
@@ -0,0 +1,58 @@
+/**
+ * @file main.h
+ * @brief Test version of main.h
+ *
+ * Created on: Oct 17, 2013
+ * Author: Andrey Belomutskiy (C) 2012-2013
+ */
+
+#ifndef MAIN_H_
+#define MAIN_H_
+
+#include
+#include
+#include "error_handling.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "efilib.h"
+
+#include "global.h"
+#include "boards.h"
+#include "engines.h"
+
+typedef int bool_t;
+
+void chDbgAssert(int c, char *msg, void *arg);
+
+void print(const char *fmt, ...);
+
+#define TICKS_IN_MS 100
+
+#define DEBUG_INTERPOLATION 1
+
+#define chDbgCheck(x, y) chDbgAssert(x, y, NULL)
+
+void assertEqualsM(char *msg, float expected, float actual);
+void assertEquals(float expected, float actual);
+void assertTrue(float actual);
+void assertTrueM(char *msg, float actual);
+void assertFalse(float actual);
+void assertFalseM(char *msg, float actual);
+
+float getIntakeAirTemperature(void);
+float getCoolantTemperature(void);
+float getVBatt(void);
+float getMaf(void);
+
+#define systicks2ms(x) (0)
+
+#ifdef __cplusplus
+}
+#endif
+
+
+#endif /* MAIN_H_ */
diff --git a/unit_tests/makelinks.bat b/unit_tests/makelinks.bat
new file mode 100644
index 0000000000..a700673682
--- /dev/null
+++ b/unit_tests/makelinks.bat
@@ -0,0 +1,8 @@
+
+junction controllers_algo ..\firmware\controllers\algo
+junction controllers_math ..\firmware\controllers\math
+junction engines ..\firmware\config\engines
+junction controllers_sensors ..\firmware\controllers\sensors
+junction util ..\firmware\util
+junction ext_algo ..\firmware\ext_algo
+
diff --git a/unit_tests/map_resize.c b/unit_tests/map_resize.c
new file mode 100644
index 0000000000..44b7f5a6e7
--- /dev/null
+++ b/unit_tests/map_resize.c
@@ -0,0 +1,176 @@
+/**
+ * @file map_resize.c
+ *
+ * @date Jan 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include
+#include "fuel_math.h"
+#include "interpolation.h"
+#include "engines.h"
+#include "engine_configuration.h"
+#include "main.h"
+#include "idle_controller.h"
+
+#if 1
+
+#define AD_LOAD_COUNT 16
+#define AD_RPM_COUNT 16
+
+
+static float ad_rpm_table[] = {/*0*/ 800.000000,
+ /*1*/ 1213.333374,
+ /*2*/ 1626.666748,
+ /*3*/ 2040.000000,
+ /*4*/ 2453.333496,
+ /*5*/ 2866.666748,
+ /*6*/ 3280.000000,
+ /*7*/ 3693.333496,
+ /*8*/ 4106.666992,
+ /*9*/ 4520.000000,
+ /*10*/ 4933.333496,
+ /*11*/ 5346.666992,
+ /*12*/ 5760.000000,
+ /*13*/ 6173.333496,
+ /*14*/ 6586.666992,
+ /*15*/ 7000.000000,
+ };;
+
+static float ad_maf_table[] =
+{/*0*/ 1.200000,
+/*1*/ 1.413333,
+/*2*/ 1.626667,
+/*3*/ 1.840000,
+/*4*/ 2.053333,
+/*5*/ 2.266667,
+/*6*/ 2.480000,
+/*7*/ 2.693333,
+/*8*/ 2.906667,
+/*9*/ 3.120000,
+/*10*/ 3.333333,
+/*11*/ 3.546667,
+/*12*/ 3.760000,
+/*13*/ 3.973333,
+/*14*/ 4.186667,
+/*15*/ 4.400000,
+};
+
+static float ad_table[AD_LOAD_COUNT][AD_RPM_COUNT] = {
+ /* RPM 800.000000 1213.333374 1626.666748 2040.000000 2453.333496 2866.666748 3280.000000 3693.333496 4106.666992 4520.000000 4933.333496 5346.666992 5760.000000 6173.333496 6586.666992 7000.000000*/
+ /* Load 1.200000 */{ 0.662000, -7.730000, -16.722000, -23.139999, -29.398001, -31.268000, -32.108002, -30.436001, -30.896000, -26.656000, -24.704000, -25.108000, -25.132000, -25.459999, -25.459999, -25.459999},
+ /* Load 1.413333 */{ 0.546000, -7.662000, -16.882000, -23.482000, -29.520000, -31.323999, -32.108002, -30.656000, -30.468000, -26.879999, -24.746000, -24.742001, -29.032000, -25.562000, -25.562000, -25.562000},
+ /* Load 1.626667 */{ 0.584000, -7.870000, -16.714001, -23.025999, -29.542000, -31.166000, -32.175999, -30.540001, -30.268000, -26.416000, -24.134001, -25.007999, -24.698000, -26.167999, -26.167999, -26.167999},
+ /* Load 1.840000 */{ 0.584000, -7.658000, -16.714001, -23.254000, -29.351999, -30.978001, -32.141998, -30.874001, -30.896000, -26.507999, -24.558001, -24.389999, -25.761999, -35.492001, -35.492001, -35.492001},
+ /* Load 2.053333 */{ 0.584000, -7.862000, -16.538000, -23.254000, -29.232000, -31.296000, -32.520000, -30.142000, -30.388000, -25.903999, -24.370001, -24.082001, -24.792000, -24.351999, -24.351999, -24.351999},
+ /* Load 2.266667 */{ -1.364000, -7.726000, -16.806000, -23.254000, -29.639999, -31.006001, -32.298000, -30.912001, -29.882000, -26.392000, -24.664000, -27.233999, -25.374001, -25.417999, -25.417999, -25.417999},
+ /* Load 2.480000 */{ 1.364000, -10.490000, -16.705999, -22.441999, -28.101999, -30.238001, -32.363998, -30.719999, -30.896000, -26.608000, -24.664000, -24.431999, -24.500000, -25.510000, -25.510000, -25.510000},
+ /* Load 2.693333 */{ 9.864000, -10.416000, -11.680000, -19.150000, -25.754000, -27.936001, -32.554001, -30.656000, -30.153999, -27.184000, -25.252001, -22.812000, -24.452000, -25.219999, -25.219999, -25.219999},
+ /* Load 2.906667 */{ 9.866000, 5.452000, 2.854000, -17.212000, -17.552000, -20.688000, -25.660000, -27.809999, -27.691999, -27.224001, -25.882000, -25.360001, -26.100000, -27.992001, -27.992001, -27.992001},
+ /* Load 3.120000 */{ 9.864000, 5.452000, 2.854000, -0.342000, -12.526000, -16.218000, -21.364000, -27.590000, -25.780001, -24.170000, -24.664000, -25.584000, -26.490000, -31.968000, -31.968000, -31.968000},
+ /* Load 3.333333 */{ 9.864000, 5.516000, 2.854000, -0.226000, -2.738000, -3.816000, -11.924000, -18.808001, -21.038000, -21.538000, -21.209999, -22.228001, -25.046000, -25.156000, -25.156000, -25.156000},
+ /* Load 3.546667 */{ 9.866000, 5.518000, 2.854000, 0.000000, -3.022000, -3.816000, -6.428000, -7.788000, -19.426001, -20.860001, -19.966000, -21.030001, -21.396000, -21.570000, -21.570000, -21.570000},
+ /* Load 3.760000 */{ 9.864000, 5.516000, 2.772000, -0.226000, -2.732000, -3.500000, -6.798000, -8.102000, -8.660000, -9.500000, -11.788000, -20.132000, -20.072001, -20.510000, -20.510000, -20.510000},
+ /* Load 3.973333 */{ 9.864000, 5.518000, 2.854000, 0.000000, -2.880000, -3.816000, -6.420000, -8.320000, -8.426000, -8.532000, -11.470000, -11.442000, -13.610000, -12.022000, -12.022000, -12.022000},
+ /* Load 4.186667 */{ 9.750000, 5.518000, 2.604000, 0.000000, -2.880000, -3.654000, -6.050000, -6.888000, -8.372000, -9.364000, -11.764000, -11.732000, -11.864000, -12.376000, -12.376000, -12.376000},
+ /* Load 4.400000 */{ 0.350000, 5.590000, 0.502000, 0.910000, 0.864000, 0.954000, 1.324000, -7.436000, 1.170000, 1.054000, 2.058000, 2.098000, 2.636000, -12.352000, -12.352000, -12.352000}
+ };
+
+//float getBaseAdvance(int rpm, float key) {
+// // todo: use interpolation
+// int rpm_index = findIndex(ad_rpm_table, AD_RPM_COUNT, rpm);
+// rpm_index = max(rpm_index, 0);
+// int maf_index = findIndex(ad_maf_table, AD_LOAD_COUNT, key);
+// maf_index = max(maf_index, 0);
+//
+// return ad_table[rpm_index][maf_index];
+//}
+
+
+
+#define newRpmSize 16
+#define newKeySize 16
+
+static float newRpmBin[newRpmSize];
+static float newKeyBin[newKeySize];
+
+//static float *fuel_ptrs[FUEL_LOAD_COUNT];
+
+//EngineConfiguration *engineConfiguration;
+
+extern int needInterpolationLogging;
+
+void resizeMap(void) {
+// float keyMin = 1.2;
+// float keyMax = 4.4;
+//
+// float rpmMin = 800;
+// float rpmMax = 7000;
+
+// for (int k = 0; k < FUEL_LOAD_COUNT; k++)
+// fuel_ptrs[k] = engineConfiguration->fuelTable[k];
+
+// for (int i = 0; i < FUEL_MAF_COUNT; i++)
+// engineConfiguration->fuelKeyBins[i] = default_fuel_maf_bins[i];
+// for (int i = 0; i < FUEL_RPM_COUNT; i++)
+// engineConfiguration->fuelRpmBins[i] = default_fuel_rpm_bins[i];
+// for (int k = 0; k < FUEL_MAF_COUNT; k++) {
+// for (int r = 0; r < FUEL_RPM_COUNT; r++) {
+// // todo: this is BAD, this needs to be fixed - TS table indexes are different from default indexes
+// engineConfiguration->fuelTable[k][r] = default_fuel_table[r][k];
+// }
+// }
+
+// assertEquals(15, interpolate3d(1.2, engineConfiguration->fuelKeyBins, FUEL_MAF_COUNT, 8000,
+// engineConfiguration->fuelRpmBins,
+// FUEL_RPM_COUNT, fuel_ptrs));
+
+ needInterpolationLogging = 0;
+
+// printf("static float ad_maf_table[AD_LOAD_COUNT] = {");
+// for (int i = 0; i < newKeySize; i++) {
+// newKeyBin[i] = interpolate(0, keyMin, newKeySize - 1, keyMax, i);
+// printf("/*%d*/ %f,\r\n", i, newKeyBin[i]);
+// }
+// printf("};\r\n");
+//
+// printf("static float ad_rpm_table[AD_RPM_COUNT] = {");
+// for (int i = 0; i < newRpmSize; i++) {
+// newRpmBin[i] = interpolate(0, rpmMin, newRpmSize - 1, rpmMax, i);
+// printf("/*%d*/ %f,\r\n", i, newRpmBin[i]);
+// }
+// printf("};\r\n");
+
+ printf("static float ad_table[AD_RPM_COUNT][AD_LOAD_COUNT] = {\r\n");
+
+ printf("/* RPM\t\t");
+ for (int r = 0; r < newRpmSize; r++) {
+ float rpm = newRpmBin[r];
+ printf("\t%f", rpm);
+ }
+ printf("*/\r\n");
+
+ for (int k = 0; k < newKeySize; k++) {
+ float load = newKeyBin[k];
+ printf("/* Load %f */{", load);
+
+ for (int r = 0; r < newRpmSize; r++) {
+ float rpm = newRpmBin[r];
+
+ float v = ad_table[k][r];
+
+ printf("\t%f", v);
+ if (r != newRpmSize - 1)
+ printf(",");
+
+ }
+ printf("}");
+ if (k != newKeySize - 1)
+ printf(",");
+ printf("\r\n");
+ }
+ printf("};\r\n");
+
+}
+
+#endif
diff --git a/unit_tests/map_resize.h b/unit_tests/map_resize.h
new file mode 100644
index 0000000000..3d105a434e
--- /dev/null
+++ b/unit_tests/map_resize.h
@@ -0,0 +1,13 @@
+/**
+ * @file map_resize.h
+ *
+ * @date Jan 12, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef MAP_RESIZE_H_
+#define MAP_RESIZE_H_
+
+void resizeMap(void);
+
+#endif /* MAP_RESIZE_H_ */
diff --git a/unit_tests/readme.txt b/unit_tests/readme.txt
new file mode 100644
index 0000000000..621959dacd
--- /dev/null
+++ b/unit_tests/readme.txt
@@ -0,0 +1,6 @@
+In this folder we have a naive test set. I am not sure what is the best approach to tests in C (someone can educate me), but my approach is:
+
+1) with a symlink linking some (only some, not all) folders of the firmware implementation, we can compile some files of the firmware as win32 code.
+Please execute the 'makelinks.bat' file to get the links. On XP you might need to execute 'junction.exe' before exuting the .bat file.
+
+2) we then compile & run the .exe, which is expected to say SUCCESS and not fail :)
\ No newline at end of file
diff --git a/unit_tests/rules.mk b/unit_tests/rules.mk
new file mode 100644
index 0000000000..5e93b3e311
--- /dev/null
+++ b/unit_tests/rules.mk
@@ -0,0 +1,168 @@
+# ARM Cortex-Mx common makefile scripts and rules.
+
+# Output directory and files
+ifeq ($(BUILDDIR),)
+ BUILDDIR = build
+endif
+ifeq ($(BUILDDIR),.)
+ BUILDDIR = build
+endif
+OUTFILES = $(BUILDDIR)/$(PROJECT)
+
+# Automatic compiler options
+OPT = $(USE_OPT)
+COPT = $(USE_COPT)
+CPPOPT = $(USE_CPPOPT)
+ifeq ($(USE_LINK_GC),yes)
+ OPT += -ffunction-sections -fdata-sections -fno-common
+endif
+
+# Source files groups and paths
+ifeq ($(USE_THUMB),yes)
+ TCSRC += $(CSRC)
+ TCPPSRC += $(CPPSRC)
+else
+ ACSRC += $(CSRC)
+ ACPPSRC += $(CPPSRC)
+endif
+ASRC = $(ACSRC)$(ACPPSRC)
+TSRC = $(TCSRC)$(TCPPSRC)
+SRCPATHS = $(sort $(dir $(ASMXSRC)) $(dir $(ASMSRC)) $(dir $(ASRC)) $(dir $(TSRC)))
+
+# Various directories
+OBJDIR = $(BUILDDIR)/obj
+LSTDIR = $(BUILDDIR)/lst
+
+# Object files groups
+ACOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ACSRC:.c=.o)))
+ACPPOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ACPPSRC:.cpp=.o)))
+TCOBJS = $(addprefix $(OBJDIR)/, $(notdir $(TCSRC:.c=.o)))
+TCPPOBJS = $(addprefix $(OBJDIR)/, $(notdir $(TCPPSRC:.cpp=.o)))
+ASMOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ASMSRC:.s=.o)))
+ASMXOBJS = $(addprefix $(OBJDIR)/, $(notdir $(ASMXSRC:.S=.o)))
+OBJS = $(ASMXOBJS) $(ASMOBJS) $(ACOBJS) $(TCOBJS) $(ACPPOBJS) $(TCPPOBJS)
+
+# Paths
+IINCDIR = $(patsubst %,-I%,$(INCDIR) $(DINCDIR) $(UINCDIR))
+LLIBDIR = $(patsubst %,-L%,$(DLIBDIR) $(ULIBDIR))
+
+# Macros
+DEFS = $(DDEFS) $(UDEFS)
+ADEFS = $(DADEFS) $(UADEFS)
+
+# Libs
+LIBS = $(DLIBS) $(ULIBS)
+
+# Various settings
+#MCFLAGS = -mcpu=$(MCU)
+ODFLAGS = -x --syms
+ASFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.s=.lst)) $(ADEFS)
+ASXFLAGS = $(MCFLAGS) -Wa,-amhls=$(LSTDIR)/$(notdir $(<:.S=.lst)) $(ADEFS)
+CFLAGS = $(MCFLAGS) $(OPT) $(COPT) $(CWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.c=.lst)) $(DEFS)
+CPPFLAGS = $(MCFLAGS) $(OPT) $(CPPOPT) $(CPPWARN) -Wa,-alms=$(LSTDIR)/$(notdir $(<:.cpp=.lst)) $(DEFS)
+ifeq ($(USE_LINK_GC),yes)
+ LDFLAGS = $(MCFLAGS) -Wl,-Map=$(BUILDDIR)/$(PROJECT).map,--cref,--no-warn-mismatch,--gc-sections $(LLIBDIR)
+else
+ LDFLAGS = $(MCFLAGS) -Wl,-Map=$(BUILDDIR)/$(PROJECT).map,--cref,--no-warn-mismatch $(LLIBDIR)
+endif
+
+# Generate dependency information
+CFLAGS += -MD -MP -MF .dep/$(@F).d
+CPPFLAGS += -MD -MP -MF .dep/$(@F).d
+
+# Paths where to search for sources
+VPATH = $(SRCPATHS)
+
+#
+# Makefile rules
+#
+
+all: $(OBJS) $(OUTFILES) MAKE_ALL_RULE_HOOK
+
+MAKE_ALL_RULE_HOOK:
+
+$(OBJS): | $(BUILDDIR)
+
+$(BUILDDIR) $(OBJDIR) $(LSTDIR):
+ifneq ($(USE_VERBOSE_COMPILE),yes)
+ @echo Compiler Options
+ @echo $(CPPC) -c $(CPPFLAGS) -I. $(IINCDIR) main.cpp -o main.o
+ @echo
+endif
+ mkdir -p $(OBJDIR)
+ mkdir -p $(LSTDIR)
+
+$(ACPPOBJS) : $(OBJDIR)/%.o : %.cpp Makefile
+ifeq ($(USE_VERBOSE_COMPILE),yes)
+ @echo
+ $(CPPC) -c $(CPPFLAGS) $(AOPT) -I. $(IINCDIR) $< -o $@
+else
+ @echo Compiling $(/dev/null) $(wildcard .dep/*)
+
+# *** EOF ***
diff --git a/unit_tests/settings.h b/unit_tests/settings.h
new file mode 100644
index 0000000000..31327e150e
--- /dev/null
+++ b/unit_tests/settings.h
@@ -0,0 +1,13 @@
+/**
+ * @file settings.h
+ *
+ * @date Feb 13, 2014
+ * @author Andrey Belomutskiy, (c) 2012-2014
+ */
+
+#ifndef SETTINGS_H_
+#define SETTINGS_H_
+
+
+
+#endif /* SETTINGS_H_ */
diff --git a/unit_tests/test.mk b/unit_tests/test.mk
new file mode 100644
index 0000000000..8929ffc1f6
--- /dev/null
+++ b/unit_tests/test.mk
@@ -0,0 +1,14 @@
+TEST_SRC_C = boards.c \
+ test_data_structures/test_engine_math.c \
+ test_event_registry.c \
+ test_basic_math/test_find_index.c \
+ test_basic_math/test_interpolation_3d.c \
+ test_fuel_map.c \
+ test_idle_controller.c \
+ test_trigger_decoder.c \
+ test_sensors.c \
+ test_signal_executor.c
+
+TEST_SRC_CPP = test_util.cpp
+
+
diff --git a/unit_tests/test_basic_math/test_find_index.c b/unit_tests/test_basic_math/test_find_index.c
new file mode 100644
index 0000000000..47d34d851a
--- /dev/null
+++ b/unit_tests/test_basic_math/test_find_index.c
@@ -0,0 +1,120 @@
+/*
+ * test_find_index.c
+ *
+ * Created on: Oct 30, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "engine_math.h"
+#include "main.h"
+#include "interpolation.h"
+#include
+#include "engine_configuration.h"
+
+void testFindIndex(void) {
+ printf("*************************************************** testFindIndex\r\n");
+
+ float array[] = { 1, 2, 3, 4, 5 };
+ int size = 4;
+ int result;
+
+ printf("To the left\r\n");
+ result = findIndex(array, size, -1.0);
+ assertEquals(-1, result);
+
+ printf("To the right4\r\n");
+ result = findIndex(array, size, 10.0);
+ assertEquals(3, result);
+
+ printf("To the right5\r\n");
+ result = findIndex(array, 5, 10.0);
+ assertEquals(4, result);
+
+ printf("On the edge\r\n");
+ result = findIndex(array, size, 4.0);
+ assertEquals(3, result);
+
+ printf("Another1\r\n");
+ result = findIndex(array, size, 3.9);
+ assertEquals(2, result);
+
+ printf("Another2\r\n");
+ result = findIndex(array, size, 4.1);
+ assertEquals(3, result);
+
+ printf("Another3\r\n");
+ result = findIndex(array, size, 2);
+ assertEquals(1, result);
+
+ printf("Left edge1\r\n");
+ result = findIndex(array, size, 1);
+ assertEquals(0, result);
+
+ printf("Left edge2\r\n");
+ result = findIndex(array, size, 1.1);
+ assertEquals(0, result);
+
+ printf("Middle\r\n");
+ result = findIndex(array, size, 3);
+ assertEquals(2, result);
+
+ size = 5; // now test with off array size
+
+ printf("Middle2\r\n");
+ result = findIndex(array, size, 4);
+ assertEquals(3, result);
+
+ printf("Middle2\r\n");
+ result = findIndex(array, size, 3.1);
+ assertEquals(2, result);
+}
+
+//static float getValue2(float key, float maf) {
+//
+//}
+
+void testInterpolate2d(void) {
+ printf("*************************************************** testInterpolate2d\r\n");
+
+ float bins4[] = { 1, 2, 3, 4 };
+ float values4[] = { 1, 20, 30, 400 };
+ int size = 4;
+
+ int result;
+
+ printf("Left size\r\n");
+ result = interpolate2d(0, bins4, values4, size);
+ assertEquals(1, result);
+
+ printf("Right size\r\n");
+ result = interpolate2d(10, bins4, values4, size);
+ assertEquals(400, result);
+
+ printf("Middle1\r\n");
+ result = interpolate2d(3, bins4, values4, size);
+ assertEquals(30, result);
+
+ printf("Middle1\r\n");
+ result = interpolate2d(3.5, bins4, values4, size);
+ assertEquals(215, result);
+}
+
+static engine_configuration_s engineConfiguration;
+
+void testSetTableValue(void) {
+ printf("*************************************************** testSetTableValue\r\n");
+
+ for (int i = 0; i < CLT_CURVE_SIZE; i++) {
+ engineConfiguration.cltFuelCorrBins[i] = -40 + i * 10;
+ engineConfiguration.cltFuelCorr[i] = 1;
+ }
+
+ assertEquals(1, engineConfiguration.cltFuelCorr[0]);
+
+ setTableValue(engineConfiguration.cltFuelCorrBins, engineConfiguration.cltFuelCorr, CLT_CURVE_SIZE, -40, 1.5);
+ assertEquals(1.5, engineConfiguration.cltFuelCorr[0]);
+
+ setTableValue(engineConfiguration.cltFuelCorrBins, engineConfiguration.cltFuelCorr, CLT_CURVE_SIZE, -50, 1.4);
+ assertEquals(1.4, engineConfiguration.cltFuelCorr[0]);
+
+}
diff --git a/unit_tests/test_basic_math/test_find_index.h b/unit_tests/test_basic_math/test_find_index.h
new file mode 100644
index 0000000000..3a611751c1
--- /dev/null
+++ b/unit_tests/test_basic_math/test_find_index.h
@@ -0,0 +1,15 @@
+/*
+ * test_find_index.h
+ *
+ * Created on: Oct 30, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_FIND_INDEX_H_
+#define TEST_FIND_INDEX_H_
+
+void testFindIndex(void);
+void testInterpolate2d(void);
+void testSetTableValue(void);
+
+#endif /* TEST_FIND_INDEX_H_ */
diff --git a/unit_tests/test_basic_math/test_interpolation_3d.c b/unit_tests/test_basic_math/test_interpolation_3d.c
new file mode 100644
index 0000000000..6fa875b94c
--- /dev/null
+++ b/unit_tests/test_basic_math/test_interpolation_3d.c
@@ -0,0 +1,70 @@
+/*
+ * Created on: Oct 17, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+/**
+ * @file test_interpolation_3d.c
+ */
+
+#include "test_interpolation_3d.h"
+#include
+#include
+
+#include "interpolation.h"
+#include "main.h"
+
+float rpmBins[5] = { 100, 200, 300, 400, 500 };
+float mafBins[4] = { 1, 2, 3, 4 };
+
+float map0[4] = { 1, 2, 3, 4 };
+float map1[4] = { 2, 3, 4, 5 };
+float map2[4] = { 3, 4, 200, 300 };
+float map3[4] = { 4, 200, 500, 600 };
+float map4[4] = { 4, 200, 500, 600 };
+
+float *map[5] = { map0, map1, map2, map3, map4 };
+
+
+static float getValue(float rpm, float maf) {
+ return interpolate3d(rpm, rpmBins, 5, maf, mafBins, 4, map);
+}
+
+void testInterpolate3d(void) {
+ printf("*************************************************** testInterpolate3d\r\n");
+ float dwell;
+ printf("*** no interpolation here 1\r\n");
+ dwell = getValue(100, 2);
+ assertEquals(2, dwell);
+
+ printf("*** no interpolation here 2\r\n");
+ dwell = getValue(200, 4);
+ assertEquals(5, dwell);
+
+ printf("*** rpm interpolated value expected1\r\n");
+ dwell = getValue(150, 2);
+ assertEquals(2.5, dwell);
+
+ printf("*** rpm interpolated value expected2\r\n");
+ dwell = getValue(250, 3);
+ assertEquals(102, dwell);
+
+ printf("*** both rpm and maf interpolated value expected\r\n");
+ dwell = getValue(335.3, 3.551);
+ assertEquals(361, dwell);
+
+ printf("*** both rpm and maf interpolated value expected 2\r\n");
+ dwell = getValue(410.01, 2.012);
+ assertEquals(203.6, dwell);
+
+ printf("*** both rpm and maf interpolated value expected 3\r\n");
+ dwell = getValue(1000000, 1000);
+ assertEquals(600, dwell);
+
+ printf("*** both rpm and maf interpolated value expected 4\r\n");
+ dwell = getValue(410.01, -1);
+ assertEquals(4, dwell);
+
+ dwell = getValue(-1, -1);
+ assertEquals(1, dwell);
+}
diff --git a/unit_tests/test_basic_math/test_interpolation_3d.h b/unit_tests/test_basic_math/test_interpolation_3d.h
new file mode 100644
index 0000000000..684c26ba74
--- /dev/null
+++ b/unit_tests/test_basic_math/test_interpolation_3d.h
@@ -0,0 +1,16 @@
+/*
+ * Created on: Oct 17, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+/**
+ * @file test_interpolation_3d.h
+ */
+
+
+#ifndef TEST_INTERPOLATION_3D_H_
+#define TEST_INTERPOLATION_3D_H_
+
+void testInterpolate3d(void);
+
+#endif /* TEST_INTERPOLATION_3D_H_ */
diff --git a/unit_tests/test_data_structures/test_event_registry.c b/unit_tests/test_data_structures/test_event_registry.c
new file mode 100644
index 0000000000..e7d2bd025a
--- /dev/null
+++ b/unit_tests/test_data_structures/test_event_registry.c
@@ -0,0 +1,56 @@
+/*
+ * test_event_registry.c
+ *
+ * Created on: Nov 27, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "event_registry.h"
+#include "test_event_registry.h"
+#include "main.h"
+
+static ActuatorEventList eventList;
+static ActuatorEventList result;
+
+int pinDefaultState[IO_PIN_COUNT];
+
+void initOutputSignal(OutputSignal *signal, io_pin_e ioPin) {
+ signal->io_pin = ioPin;
+}
+
+extern int outputSignalCount;
+
+void testEventRegistry(void) {
+ printf("*************************************** testEventRegistry\r\n");
+
+ printf("resetting\r\n");
+ resetEventList(&eventList);
+ resetOutputSignals();
+ printf("registering 0\r\n");
+
+ registerActuatorEvent(&eventList, 0, addOutputSignal(10), 0);
+ registerActuatorEvent(&eventList, 0, addOutputSignal(20), 10);
+ assertEquals(2, eventList.size);
+
+ printf("registering 1\r\n");
+ registerActuatorEvent(&eventList, 1, addOutputSignal(30), 0);
+ registerActuatorEvent(&eventList, 1, addOutputSignal(40), 10);
+ assertEquals(4, eventList.size);
+
+ printf("Looking for 0\r\n");
+ findEvents(0, &eventList, &result);
+ assertEquals(2, result.size);
+ assertEquals(4, eventList.size);
+
+ printf("Validating pins\r\n");
+ assertEquals(10, result.events[0].actuator->io_pin);
+ assertEquals(20, result.events[1].actuator->io_pin);
+
+ printf("Looking for 1\r\n");
+ findEvents(1, &eventList, &result);
+ assertEquals(2, result.size);
+ assertEquals(4, eventList.size);
+
+ assertEquals(30, result.events[0].actuator->io_pin);
+ assertEquals(40, result.events[1].actuator->io_pin);
+}
diff --git a/unit_tests/test_data_structures/test_event_registry.h b/unit_tests/test_data_structures/test_event_registry.h
new file mode 100644
index 0000000000..593a291844
--- /dev/null
+++ b/unit_tests/test_data_structures/test_event_registry.h
@@ -0,0 +1,13 @@
+/*
+ * test_event_registry.h
+ *
+ * Created on: Nov 27, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_EVENT_REGISTRY_H_
+#define TEST_EVENT_REGISTRY_H_
+
+void testEventRegistry(void);
+
+#endif /* TEST_EVENT_REGISTRY_H_ */
diff --git a/unit_tests/test_engine_math.c b/unit_tests/test_engine_math.c
new file mode 100644
index 0000000000..b2898892c7
--- /dev/null
+++ b/unit_tests/test_engine_math.c
@@ -0,0 +1,27 @@
+/*
+ * @file test_engine_math.c
+ *
+ * Created on: Nov 14, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "test_engine_math.h"
+#include "main.h"
+#include "engine_math.h"
+#include "engine_configuration.h"
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+void testEngineMath(void) {
+ printf("*************************************************** testEngineMath\r\n");
+
+ engineConfiguration->rpmMultiplier = 0.5;
+
+ assertEqualsM("600 RPM", 5000, getOneDegreeTime(600) * 180);
+ assertEqualsM("6000 RPM", 500, getOneDegreeTime(6000) * 180);
+}
+
+float getMap(void) {
+ return 0;
+}
diff --git a/unit_tests/test_engine_math.h b/unit_tests/test_engine_math.h
new file mode 100644
index 0000000000..8f80cc7f41
--- /dev/null
+++ b/unit_tests/test_engine_math.h
@@ -0,0 +1,13 @@
+/*
+ * @file test_engine_math.h
+ *
+ * Created on: Nov 14, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_ENGINE_MATH_H_
+#define TEST_ENGINE_MATH_H_
+
+void testEngineMath(void);
+
+#endif /* TEST_ENGINE_MATH_H_ */
diff --git a/unit_tests/test_fuel_map.c b/unit_tests/test_fuel_map.c
new file mode 100644
index 0000000000..337dc6a561
--- /dev/null
+++ b/unit_tests/test_fuel_map.c
@@ -0,0 +1,150 @@
+/**
+ * @file test_fuel_map.c
+ *
+ * Created on: Nov 6, 2013
+ * Author: Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "main.h"
+#include "engine_configuration.h"
+#include "fuel_math.h"
+#include "trigger_structure.h"
+#include "allsensors.h"
+#include "engine_math.h"
+
+extern engine_configuration_s *engineConfiguration;
+extern engine_configuration2_s *engineConfiguration2;
+
+void testFuelMap(void) {
+ chDbgCheck(engineConfiguration!=NULL, "engineConfiguration");
+
+ printf("*************************************************** testFuelMap\r\n");
+
+ for (int k = 0; k < FUEL_LOAD_COUNT; k++) {
+ for (int r = 0; r < FUEL_RPM_COUNT; r++) {
+ engineConfiguration->fuelTable[k][r] = k * 200 + r;
+ }
+ }
+ printf("*************************************************** initThermistors\r\n");
+
+ initThermistors();
+
+ printf("*** getInjectorLag\r\n");
+ assertEquals(0, getInjectorLag(12));
+
+ for (int i = 0; i < FUEL_LOAD_COUNT; i++)
+ engineConfiguration->fuelLoadBins[i] = i;
+ for (int i = 0; i < FUEL_RPM_COUNT; i++)
+ engineConfiguration->fuelRpmBins[i] = i;
+
+ printf("*************************************************** prepareFuelMap\r\n");
+ prepareFuelMap();
+ assertEquals(1005, getBaseFuel(5, 5));
+
+ engineConfiguration->injectorLag = 0.5;
+
+ for (int i = 0; i < VBAT_INJECTOR_CURVE_SIZE; i++) {
+ engineConfiguration->battInjectorLagCorrBins[i] = i;
+ engineConfiguration->battInjectorLagCorr[i] = 2 * i;
+ }
+
+ // because all the correction tables are zero
+ printf("*************************************************** getRunningFuel\r\n");
+ assertEquals(1005.5, getRunningFuel(5, 5));
+
+ printf("*************************************************** setting IAT table\r\n");
+ for (int i = 0; i < IAT_CURVE_SIZE; i++) {
+ engineConfiguration->iatFuelCorrBins[i] = i;
+ engineConfiguration->iatFuelCorr[i] = 2 * i;
+ }
+ engineConfiguration->iatFuelCorr[0] = 2;
+
+ printf("*************************************************** setting CLT table\r\n");
+ for (int i = 0; i < CLT_CURVE_SIZE; i++) {
+ engineConfiguration->cltFuelCorrBins[i] = i;
+ engineConfiguration->cltFuelCorr[i] = 1;
+ }
+ engineConfiguration->injectorLag = 0;
+
+ assertEquals(NAN, getIntakeAirTemperature());
+ float iatCorrection = getIatCorrection(-KELV);
+ assertEqualsM("IAT", 2, iatCorrection);
+ float cltCorrection = getCltCorrection(getCoolantTemperature());
+ assertEqualsM("CLT", 1, cltCorrection);
+ float injectorLag = getInjectorLag(getVBatt());
+ assertEquals(0, injectorLag);
+
+
+ // 1005 * 2 for IAT correction
+ printf("*************************************************** getRunningFuel\r\n");
+ assertEquals(1005, getRunningFuel(5, 5));
+
+ engineConfiguration->crankingSettings.coolantTempMaxC = 65; // 8ms at 65C
+ engineConfiguration->crankingSettings.fuelAtMaxTempMs = 8;
+
+ engineConfiguration->crankingSettings.coolantTempMinC = 0; // 20ms at 0C
+ engineConfiguration->crankingSettings.fuelAtMinTempMs = 20;
+
+ printf("*************************************************** getStartingFuel\r\n");
+ // NAN in case we have issues with the CLT sensor
+// assertEquals(16, getStartingFuel(NAN));
+ assertEquals(20, getStartingFuel(0));
+ assertEquals(18.5231, getStartingFuel(8));
+ assertEquals(8, getStartingFuel(70));
+}
+
+static void confgiureFordAspireTriggerShape(trigger_shape_s * s) {
+ triggerShapeInit(s);
+
+ triggerAddEvent(s, 53.747, T_SECONDARY, 1);
+ triggerAddEvent(s, 121.90, T_SECONDARY, 0);
+ triggerAddEvent(s, 232.76, T_SECONDARY, 1);
+ triggerAddEvent(s, 300.54, T_SECONDARY, 0);
+ triggerAddEvent(s, 360, T_PRIMARY, 1);
+
+ triggerAddEvent(s, 409.8412, T_SECONDARY, 1);
+ triggerAddEvent(s, 478.6505, T_SECONDARY, 0);
+ triggerAddEvent(s, 588.045, T_SECONDARY, 1);
+ triggerAddEvent(s, 657.03, T_SECONDARY, 0);
+ triggerAddEvent(s, 720, T_PRIMARY, 0);
+}
+
+
+static ActuatorEventList ae;
+
+extern int outputSignalCount;
+
+void testAngleResolver(void) {
+ printf("*************************************************** testAngleResolver\r\n");
+
+ engineConfiguration->globalTriggerAngleOffset = 175;
+ trigger_shape_s * ts = &engineConfiguration2->triggerShape;
+
+ confgiureFordAspireTriggerShape(ts);
+ assertEqualsM("shape size", 10, ts->size);
+
+ resetOutputSignals();
+
+ resetEventList(&ae);
+ printf("*************************************************** testAngleResolver 0\r\n");
+ registerActuatorEventExt(engineConfiguration, &engineConfiguration2->triggerShape, &ae, addOutputSignal(INJECTOR_1_OUTPUT), 53 - 175);
+ assertEquals(1, ae.size);
+ assertEquals(1, outputSignalCount);
+ assertEquals(0, ae.events[0].eventIndex);
+ assertEquals(53, ae.events[0].angleOffset);
+
+ printf("*************************************************** testAngleResolver 2\r\n");
+ resetEventList(&ae);
+ registerActuatorEventExt(engineConfiguration, &engineConfiguration2->triggerShape, &ae, addOutputSignal(INJECTOR_1_OUTPUT), 51 + 180 - 175);
+ assertEquals(2, ae.events[0].eventIndex);
+ assertEquals(51.9870, ae.events[0].angleOffset);
+}
+
+void testPinHelper(void) {
+ printf("*************************************************** testPinHelper\r\n");
+ assertEquals(0, getElectricalValue(0, OM_DEFAULT));
+ assertEquals(1, getElectricalValue(1, OM_DEFAULT));
+
+ assertEquals(0, getElectricalValue(1, OM_INVERTED));
+ assertEquals(1, getElectricalValue(0, OM_INVERTED));
+}
diff --git a/unit_tests/test_fuel_map.h b/unit_tests/test_fuel_map.h
new file mode 100644
index 0000000000..4f68e7daf5
--- /dev/null
+++ b/unit_tests/test_fuel_map.h
@@ -0,0 +1,15 @@
+/*
+ * test_fuel_map.h
+ *
+ * Created on: Nov 6, 2013
+ * Author: pc
+ */
+
+#ifndef TEST_FUEL_MAP_H_
+#define TEST_FUEL_MAP_H_
+
+void testFuelMap(void);
+void testAngleResolver(void);
+void testPinHelper(void);
+
+#endif /* TEST_FUEL_MAP_H_ */
diff --git a/unit_tests/test_idle_controller.c b/unit_tests/test_idle_controller.c
new file mode 100644
index 0000000000..8150a638fe
--- /dev/null
+++ b/unit_tests/test_idle_controller.c
@@ -0,0 +1,17 @@
+/*
+ * test_idle_controller.c
+ *
+ * Created on: Oct 17, 2013
+ * Author: Andrey
+ */
+
+#include
+
+
+void idleDebug(char *msg, int value) {
+ printf("%s\r\n", msg);
+}
+
+void isCranking(void) {
+ return;
+}
diff --git a/unit_tests/test_idle_controller.h b/unit_tests/test_idle_controller.h
new file mode 100644
index 0000000000..ff3aa90ac1
--- /dev/null
+++ b/unit_tests/test_idle_controller.h
@@ -0,0 +1,7 @@
+#ifndef TEST_IDLE_CONTROLLER_H
+#define TEST_IDLE_CONTROLLER_H
+
+void idleDebug(char *msg, int value);
+void isCranking(void);
+
+#endif
diff --git a/unit_tests/test_sensors.c b/unit_tests/test_sensors.c
new file mode 100644
index 0000000000..cf4a72c7df
--- /dev/null
+++ b/unit_tests/test_sensors.c
@@ -0,0 +1,35 @@
+/**
+ * @file test_sensors.c
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "main.h"
+#include "thermistors.h"
+#include "map.h"
+
+static ThermistorConf tc;
+
+static void testMapDecoding() {
+ assertEqualsM("denso 0 volts", -6.64, getMAPValueHonda_Denso183(0));
+ assertEquals(31.244, getMAPValueHonda_Denso183(1));
+
+ assertEqualsM("MPX_4250 0 volts", 8, getMAPValueMPX_4250(0));
+ assertEquals(58.4, getMAPValueMPX_4250(1));
+}
+
+void testSensors(void) {
+ print("************************************************** testSensors\r\n");
+ testMapDecoding();
+ setThermistorConfiguration(&tc, 32, 9500, 75, 2100, 120, 1000);
+
+ prepareThermistorCurve(&tc);
+
+ assertEquals(-0.003, tc.s_h_a);
+ assertEquals(0.001, tc.s_h_b);
+ assertEquals(0.0, tc.s_h_c);
+
+ float t = convertResistanceToKelvinTemperature(2100, &tc);
+ assertEquals(75 + KELV, t);
+}
diff --git a/unit_tests/test_sensors.h b/unit_tests/test_sensors.h
new file mode 100644
index 0000000000..b59fd418fd
--- /dev/null
+++ b/unit_tests/test_sensors.h
@@ -0,0 +1,13 @@
+/**
+ * @file test_sensors.h
+ *
+ * @date Dec 7, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_SENSORS_H_
+#define TEST_SENSORS_H_
+
+void testSensors(void);
+
+#endif /* TEST_SENSORS_H_ */
diff --git a/unit_tests/test_signal_executor.c b/unit_tests/test_signal_executor.c
new file mode 100644
index 0000000000..ab066951d0
--- /dev/null
+++ b/unit_tests/test_signal_executor.c
@@ -0,0 +1,69 @@
+/**
+ * @file test_signal_executor.c
+ *
+ * @date Nov 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include
+#include "main.h"
+
+#include "signal_executor.h"
+#include "signal_executor_single_timer_algo.h"
+#include "test_signal_executor.h"
+#include "io_pins.h"
+#include "utlist.h"
+
+extern OutputSignal *st_output_list;
+
+static io_pin_e testLastToggledPin;
+static int testToggleCounter;
+
+void setOutputPinValue(io_pin_e pin, int value) {
+ // this is a test implementation of the method - we use it to see what's going on
+ testLastToggledPin = pin;
+ testToggleCounter++;
+}
+
+void scheduleTask(scheduling_s *scheduling, int delay, schfunc_t callback, void *param) {
+
+}
+
+void testSignalExecutor() {
+ print("*************************************** testSignalExecutor\r\n");
+
+// OutputSignal s1;
+// OutputSignal s2;
+//
+// registerSignal(&s1);
+// registerSignal(&s2);
+//
+// OutputSignal *out;
+// int count;
+// LL_COUNT(st_output_list, out, count);
+// assertEquals(2, count);
+//
+// s1.io_pin = 0;
+// initOutputSignalBase(&s1);
+// assertEqualsM("status", IDLE, s1.status);
+// scheduleOutputBase(&s1, 10, 100);
+//
+// long now = 1;
+// print("now = 1\r\n");
+// testToggleCounter = 0;
+// assertEqualsM("duration", 10, GET_DURATION(&s1));
+// assertEquals(9, toggleSignalIfNeeded(&s1, now));
+// assertEquals(0, testToggleCounter);
+//
+// now = 100;
+// print("now = 100\r\n");
+// testToggleCounter = 0;
+// assertEquals(100, toggleSignalIfNeeded(&s1, now));
+// assertEquals(1, testToggleCounter);
+//
+// print("now = 300\r\n");
+// now = 300; // let's see what happens if the handler is late
+// testToggleCounter = 0;
+// assertEquals(10, toggleSignalIfNeeded(&s1, now));
+// assertEquals(1, testToggleCounter);
+}
diff --git a/unit_tests/test_signal_executor.h b/unit_tests/test_signal_executor.h
new file mode 100644
index 0000000000..e61fcf3b38
--- /dev/null
+++ b/unit_tests/test_signal_executor.h
@@ -0,0 +1,13 @@
+/**
+ * @file test_signal_executor.h
+ *
+ * @date Nov 28, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_SIGNAL_EXECUTOR_H_
+#define TEST_SIGNAL_EXECUTOR_H_
+
+void testSignalExecutor(void);
+
+#endif /* TEST_SIGNAL_EXECUTOR_H_ */
diff --git a/unit_tests/test_trigger_decoder.c b/unit_tests/test_trigger_decoder.c
new file mode 100644
index 0000000000..08082b4c3f
--- /dev/null
+++ b/unit_tests/test_trigger_decoder.c
@@ -0,0 +1,245 @@
+/**
+ * @file test_trigger_decoder.c
+ *
+ * @date Dec 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include "main.h"
+#include "test_trigger_decoder.h"
+#include "trigger_decoder.h"
+#include "engine_math.h"
+
+#include "ford_aspire.h"
+#include "dodge_neon.h"
+#include "ford_1995_inline_6.h"
+#include "mazda_323.h"
+
+void sendOutConfirmation(char *value, int i) {
+ // test implementation
+}
+
+static void testDodgeNeonDecoder(void) {
+ printf("*************************************************** testDodgeNeonDecoder\r\n");
+ initTriggerDecoder();
+
+ engine_configuration_s ec;
+ engine_configuration2_s ec2;
+
+ resetConfigurationExt(DODGE_NEON_1995, &ec, &ec2);
+
+ trigger_shape_s * shape = &ec2.triggerShape;
+ trigger_state_s state;
+ clearTriggerState(&state);
+
+
+
+
+ assertFalseM("1 shaft_is_synchronized", state.shaft_is_synchronized);
+
+
+ int r = 0;
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r + 60);
+ assertFalseM("2 shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r + 210);
+ assertFalseM("3 shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r + 420);
+ assertFalseM("4 shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r + 630);
+ assertFalse(state.shaft_is_synchronized); // still no synchronization
+
+ printf("2nd camshaft revolution\r\n");
+ r = 720;
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r + 60);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r + 210);
+ assertTrue(state.shaft_is_synchronized);
+ assertEquals(0, state.current_index);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r + 420);
+ assertEquals(1, state.current_index);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r + 630);
+ assertEquals(2, state.current_index);
+
+ printf("3rd camshaft revolution\r\n");
+ r = 2 * 720;
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r + 60);
+ assertEqualsM("current index", 3, state.current_index);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r + 210);
+ assertTrue(state.shaft_is_synchronized);
+ assertEqualsM("current index", 0, state.current_index);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r + 420);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r + 630);
+}
+
+static void test1995FordInline6TriggerDecoder(void) {
+ printf("*************************************************** test1995FordInline6TriggerDecoder\r\n");
+ initTriggerDecoder();
+ resetOutputSignals();
+
+ engine_configuration_s ec;
+ engine_configuration2_s ec2;
+
+ resetConfigurationExt(FORD_INLINE_6_1995, &ec, &ec2);
+
+ ActuatorEventList *ecl = &ec2.engineEventConfiguration.ignitionEvents;
+ assertEqualsM("ignition events size", 6, ecl->size);
+ assertEqualsM("event index", 0, ecl->events[0].eventIndex);
+ assertEquals(13, ecl->events[0].angleOffset);
+
+ assertEqualsM("event index", 10, ecl->events[5].eventIndex);
+ assertEquals(13, ecl->events[5].angleOffset);
+
+ trigger_state_s state;
+ clearTriggerState(&state);
+
+ trigger_shape_s * shape = &ec2.triggerShape;
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized);
+ int r = 0;
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r + 10);
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized); // still no synchronization
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r + 11);
+ assertTrue(state.shaft_is_synchronized); // first signal rise synchronize
+ assertEquals(0, state.current_index);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r++);
+ assertEquals(1, state.current_index);
+
+ for (int i = 2; i < 10;) {
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r++);
+ assertEqualsM("even", i++, state.current_index);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r++);
+ assertEqualsM("odd", i++, state.current_index);
+ }
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r++);
+ assertEquals(10, state.current_index);
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, r++);
+ assertEquals(11, state.current_index);
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, r++);
+ assertEquals(0, state.current_index); // new revolution
+
+ assertEqualsM("running dwell", 0.5, getSparkDwellMsT(&ec, 2000));
+}
+
+void testFordAspire(void) {
+ printf("*************************************************** testTriggerDecoder\r\n");
+
+ engine_configuration_s ec;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(FORD_ASPIRE_1996, &ec, &ec2);
+
+ assertEqualsM("cranking dwell", 54.166670, getSparkDwellMsT(&ec, 200));
+ assertEqualsM("running dwell", 4, getSparkDwellMsT(&ec, 2000));
+
+ assertEqualsM("higher rpm dwell", 3.25, getSparkDwellMsT(&ec, 6000));
+}
+
+void testMazda323(void) {
+ printf("*************************************************** testMazda323\r\n");
+
+ engine_configuration_s ec;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(MAZDA_323, &ec, &ec2);
+
+}
+
+void testMazdaMianaNbDecoder(void) {
+ printf("*************************************************** testMazdaMianaNbDecoder\r\n");
+
+ engine_configuration_s ec;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(MAZDA_MIATA_NB, &ec, &ec2);
+
+ trigger_state_s state;
+ clearTriggerState(&state);
+ trigger_shape_s * shape = &ec2.triggerShape;
+
+ int a = 0;
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, a + 20);
+ assertFalseM("0a shaft_is_synchronized", state.shaft_is_synchronized);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, a + 340);
+ assertFalseM("0b shaft_is_synchronized", state.shaft_is_synchronized);
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, a + 360);
+ assertFalseM("0c shaft_is_synchronized", state.shaft_is_synchronized);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, a + 380);
+ assertFalseM("0d shaft_is_synchronized", state.shaft_is_synchronized);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, a + 400);
+ assertTrueM("0e shaft_is_synchronized", state.shaft_is_synchronized);
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, a + 720);
+ assertTrueM("0f shaft_is_synchronized", state.shaft_is_synchronized);
+
+ a = 720;
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, a + 20);
+ assertTrueM("1a shaft_is_synchronized", state.shaft_is_synchronized);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, a + 340);
+ assertTrueM("1b shaft_is_synchronized", state.shaft_is_synchronized);
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, a + 360);
+ assertTrueM("1c shaft_is_synchronized", state.shaft_is_synchronized);
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, a + 380);
+ assertTrueM("1d shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(5, state.current_index);
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, a + 400);
+ assertTrueM("1e shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(0, state.current_index);
+
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, a + 720);
+ assertTrueM("1f shaft_is_synchronized", state.shaft_is_synchronized);
+
+
+}
+
+void testGY6_139QMB(void) {
+ printf("*************************************************** testGY6_139QMB\r\n");
+
+ engine_configuration_s ec;
+ engine_configuration2_s ec2;
+ resetConfigurationExt(GY6_139QMB, &ec, &ec2);
+
+ trigger_state_s state;
+ clearTriggerState(&state);
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized);
+
+ trigger_shape_s * shape = &ec2.triggerShape;
+
+ assertFalseM("shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(0, state.current_index);
+
+ int now = 0;
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_UP, now++);
+ assertTrueM("shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(0, state.current_index);
+
+ processTriggerEvent(&state, shape, &ec.triggerConfig, SHAFT_PRIMARY_DOWN, now++);
+ assertTrueM("shaft_is_synchronized", state.shaft_is_synchronized);
+ assertEquals(1, state.current_index);
+}
+
+void testTriggerDecoder(void) {
+ printf("*************************************************** testTriggerDecoder\r\n");
+
+ engine_configuration2_s ec2;
+
+ initializeSkippedToothTriggerShapeExt(&ec2, 2, 0);
+ assertEquals(ec2.triggerShape.size, 4);
+ assertEquals(ec2.triggerShape.wave.switchTimes[0], 0.25);
+ assertEquals(ec2.triggerShape.wave.switchTimes[1], 0.5);
+ assertEquals(ec2.triggerShape.wave.switchTimes[2], 0.75);
+ assertEquals(ec2.triggerShape.wave.switchTimes[3], 1);
+
+ testDodgeNeonDecoder();
+ testFordAspire();
+ test1995FordInline6TriggerDecoder();
+ testMazdaMianaNbDecoder();
+ testGY6_139QMB();
+
+ testMazda323();
+}
+
diff --git a/unit_tests/test_trigger_decoder.h b/unit_tests/test_trigger_decoder.h
new file mode 100644
index 0000000000..916227865c
--- /dev/null
+++ b/unit_tests/test_trigger_decoder.h
@@ -0,0 +1,13 @@
+/**
+ * @file test_trigger_decoder.h
+ *
+ * @date Dec 24, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_TRIGGER_DECODER_H_
+#define TEST_TRIGGER_DECODER_H_
+
+void testTriggerDecoder(void);
+
+#endif /* TEST_TRIGGER_DECODER_H_ */
diff --git a/unit_tests/test_util.cpp b/unit_tests/test_util.cpp
new file mode 100644
index 0000000000..c04bfb6374
--- /dev/null
+++ b/unit_tests/test_util.cpp
@@ -0,0 +1,278 @@
+/**
+ * @file test_util.c
+ *
+ * @date Dec 8, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#include
+
+#include "test_util.h"
+#include "cyclic_buffer.h"
+#include "main.h"
+#include "histogram.h"
+
+#include "malfunction_central.h"
+#include "eficonsole_logic.h"
+
+#include "nmea.h"
+
+static cyclic_buffer sb;
+
+
+void testCyclicBuffer(void) {
+ print("*************************************** testCyclicBuffer\r\n");
+
+ sb.add(10);
+
+ assertEquals(10, sb.sum(3));
+
+ sb.add(2);
+ assertEquals(12, sb.sum(2));
+}
+
+void testHistogram(void) {
+ print("******************************************* testHistogram\r\n");
+
+ initHistogramsModule();
+
+ assertEquals(80, histogramGetIndex(239));
+ assertEquals(223, histogramGetIndex(239239));
+ assertEquals(364, histogramGetIndex(239239239));
+
+ histogram_s h;
+
+ initHistogram(&h, "test");
+
+ int result[5];
+ assertEquals(0, hsReport(&h, result));
+
+ hsAdd(&h, 10);
+ assertEquals(1, hsReport(&h, result));
+ assertEquals(10, result[0]);
+
+ // let's add same value one more time
+ hsAdd(&h, 10);
+ assertEquals(2, hsReport(&h, result));
+ assertEquals(10, result[0]);
+ assertEquals(10, result[1]);
+
+ hsAdd(&h, 10);
+ hsAdd(&h, 10);
+ hsAdd(&h, 10);
+
+ hsAdd(&h, 1000);
+ hsAdd(&h, 100);
+
+ assertEquals(5, hsReport(&h, result));
+
+ assertEquals(5, result[0]);
+ assertEquals(10, result[1]);
+ assertEquals(10, result[2]);
+ assertEquals(100, result[3]);
+ // values are not expected to be exactly the same, it's the shape what matters
+ assertEquals(1011, result[4]);
+}
+
+static void testMalfunctionCentralRemoveNonExistent() {
+ print("******************************************* testMalfunctionCentralRemoveNonExistent\r\n");
+ initMalfunctionCentral();
+
+ // this should not crash
+ removeError(OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+}
+
+static void testMalfunctionCentralSameElementAgain() {
+ initMalfunctionCentral();
+ print("******************************************* testMalfunctionCentralSameElementAgain\r\n");
+ error_codes_set_s localCopy;
+
+ addError(OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+ addError(OBD_Engine_Coolant_Temperature_Circuit_Malfunction);
+ getErrorCodes(&localCopy);
+ assertEquals(1, localCopy.count);
+}
+
+static void testMalfunctionCentralRemoveFirstElement() {
+ initMalfunctionCentral();
+ print("******************************************* testMalfunctionCentralRemoveFirstElement\r\n");
+ error_codes_set_s localCopy;
+
+ obd_code_e firstElement = OBD_Engine_Coolant_Temperature_Circuit_Malfunction;
+ addError(firstElement);
+
+ obd_code_e secondElement = OBD_Intake_Air_Temperature_Circuit_Malfunction;
+ addError(secondElement);
+ getErrorCodes(&localCopy);
+ assertEquals(2, localCopy.count);
+
+ // let's remove first element - code
+ removeError(firstElement);
+
+ getErrorCodes(&localCopy);
+ assertEquals(1, localCopy.count);
+ assertEquals(secondElement, localCopy.error_codes[0]);
+}
+
+void testMalfunctionCentral(void) {
+ testMalfunctionCentralRemoveNonExistent();
+ testMalfunctionCentralSameElementAgain();
+ testMalfunctionCentralRemoveFirstElement();
+
+ print("******************************************* testMalfunctionCentral\r\n");
+ initMalfunctionCentral();
+
+ error_codes_set_s localCopy;
+
+ // on start-up error storage should be empty
+ getErrorCodes(&localCopy);
+ assertEquals(0, localCopy.count);
+
+ obd_code_e code = OBD_Engine_Coolant_Temperature_Circuit_Malfunction;
+ // let's add one error and validate
+ addError(code);
+
+ getErrorCodes(&localCopy);
+ assertEqualsM("count #1", 1, localCopy.count);
+ assertEquals(code, localCopy.error_codes[0]);
+
+ // let's remove value which is not in the collection
+ removeError((obd_code_e)22);
+ // element not present - nothing to removed
+ assertEquals(1, localCopy.count);
+ assertEquals(code, localCopy.error_codes[0]);
+
+ code = OBD_Intake_Air_Temperature_Circuit_Malfunction;
+ addError(code);
+ getErrorCodes(&localCopy);
+ // todo: assertEquals(2, localCopy.count);
+
+ for (int code = 0; code < 100; code++) {
+ addError((obd_code_e) code);
+ }
+ getErrorCodes(&localCopy);
+ assertEquals(MAX_ERROR_CODES_COUNT, localCopy.count);
+
+ // now we have full array and code below present
+ removeError(code);
+ getErrorCodes(&localCopy);
+ assertEquals(MAX_ERROR_CODES_COUNT - 1, localCopy.count);
+}
+
+static int lastInteger = -1;
+static int lastInteger2 = -1;
+
+static void testEchoI(int param) {
+ lastInteger = param;
+}
+
+static void testEchoII(int param, int param2) {
+ lastInteger = param;
+ lastInteger2 = param2;
+}
+
+static char *lastFirst = NULL;
+static char *lastThird = NULL;
+
+static void testEchoSSS(char *first, char *second, char *third) {
+ lastFirst = first;
+ lastThird = third;
+}
+
+#define UNKNOWN_COMMAND "dfadasdasd"
+
+static loc_t GPSdata;
+
+static char nmeaMessage[1000];
+
+void testGpsParser(void) {
+ print("******************************************* testGpsParser\r\n");
+
+ strcpy(nmeaMessage, "");
+ gps_location(&GPSdata, nmeaMessage);
+
+ // we need to pass a mutable string, not a constant because the parser would be modifying the string
+ strcpy(nmeaMessage, "$GPRMC,173843,A,3349.896,N,11808.521,W,000.0,360.0,230108,013.4,E*69");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("1 valid", 4, GPSdata.quality);
+ assertEqualsM("1 latitude", 3349.896, GPSdata.latitude);
+ assertEqualsM("1 longitude", 11808.521, GPSdata.longitude);
+ assertEqualsM("1 speed", 0, GPSdata.speed);
+// assertEqualsM("1 altitude", 0, GPSdata.altitude); // GPRMC not overwrite altitude
+ assertEqualsM("1 course", 360, GPSdata.course);
+
+ strcpy(nmeaMessage, "$GPGGA,111609.14,5001.27,N,3613.06,E,3,08,0.0,10.2,M,0.0,M,0.0,0000*70");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("2 valid", 3, GPSdata.quality); // see field details
+ assertEqualsM("2 latitude", 50.0212, GPSdata.latitude);
+ assertEqualsM("2 longitude", 36.2177, GPSdata.longitude);
+ assertEqualsM("2 speed", 0, GPSdata.speed);
+ assertEqualsM("2 altitude", 10.2, GPSdata.altitude);
+// assertEqualsM("2 course", 0, GPSdata.course); // GPGGA not overwrite course
+
+ strcpy(nmeaMessage, "$GPRMC,111609.14,A,5001.27,N,3613.06,E,11.2,0.0,261206,0.0,E*50");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("3 valid", 4, GPSdata.quality);
+ assertEqualsM("3 latitude", 5001.27, GPSdata.latitude);
+ assertEqualsM("3 longitude", 3613.06, GPSdata.longitude);
+ assertEqualsM("3 speed", 11.2, GPSdata.speed);
+// assertEqualsM("3 altitude", 0, GPSdata.altitude); // GPRMC not overwrite altitude
+ assertEqualsM("3 course", 0, GPSdata.course);
+ assertEqualsM("3 GPS yy",2006, GPSdata.GPStm.tm_year+1900);
+ assertEqualsM("3 GPS mm",12, GPSdata.GPStm.tm_mon);
+ assertEqualsM("3 GPS yy",26, GPSdata.GPStm.tm_mday);
+ assertEqualsM("3 GPS hh",11, GPSdata.GPStm.tm_hour);
+ assertEqualsM("3 GPS mm",16, GPSdata.GPStm.tm_min);
+ assertEqualsM("3 GPS ss",9, GPSdata.GPStm.tm_sec);
+
+ // check again first one
+ // we need to pass a mutable string, not a constant because the parser would be modifying the string
+ strcpy(nmeaMessage, "$GPRMC,173843,A,3349.896,N,11808.521,W,000.0,360.0,230108,013.4,E*69");
+ gps_location(&GPSdata, nmeaMessage);
+ assertEqualsM("4 valid", 4, GPSdata.quality);
+ assertEqualsM("4 latitude", 3349.896, GPSdata.latitude);
+ assertEqualsM("4 longitude", 11808.521, GPSdata.longitude);
+ assertEqualsM("4 speed", 0, GPSdata.speed);
+ assertEqualsM("4 course", 360, GPSdata.course);
+}
+
+// this buffer is needed because on Unix you would not be able to change static char constants
+static char buffer[300];
+
+void testConsoleLogic(void) {
+ print("******************************************* testConsoleLogic\r\n");
+ resetConsoleActions();
+
+ helpCommand();
+
+ char *ptr = validateSecureLine(UNKNOWN_COMMAND);
+ assertEquals(0, strcmp(UNKNOWN_COMMAND, ptr));
+ assertEquals(10, tokenLength(UNKNOWN_COMMAND));
+
+ // handling invalid token should work
+ strcpy(buffer, "sdasdafasd asd");
+ handleConsoleLine(buffer);
+
+ print("\r\naddConsoleActionI\r\n");
+ addConsoleActionI("echoi", testEchoI);
+ strcpy(buffer, "echoi 239");
+ handleConsoleLine(buffer);
+ assertEquals(239, lastInteger);
+
+ print("\r\naddConsoleActionII\r\n");
+ addConsoleActionII("echoii", testEchoII);
+ strcpy(buffer, "echoii 22 239");
+ handleConsoleLine(buffer);
+ assertEquals(22, lastInteger);
+ assertEquals(239, lastInteger2);
+
+ print("\r\addConsoleActionSSS\r\n");
+ addConsoleActionSSS("echosss", testEchoSSS);
+ strcpy(buffer, "echosss 111 222 333");
+ handleConsoleLine(buffer);
+ assertEquals(111, atoi(lastFirst));
+ assertEquals(333, atoi(lastThird));
+
+ //addConsoleActionSSS("GPS", testGpsParser);
+}
+
diff --git a/unit_tests/test_util.h b/unit_tests/test_util.h
new file mode 100644
index 0000000000..e77940bcc7
--- /dev/null
+++ b/unit_tests/test_util.h
@@ -0,0 +1,30 @@
+/**
+ * @file test_cyclic_buffer.h
+ *
+ * @date Dec 8, 2013
+ * @author Andrey Belomutskiy, (c) 2012-2013
+ */
+
+#ifndef TEST_CYCLIC_BUFFER_H_
+#define TEST_CYCLIC_BUFFER_H_
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif /* __cplusplus */
+
+
+
+void testCyclicBuffer(void);
+void testHistogram(void);
+void testMalfunctionCentral(void);
+void testConsoleLogic(void);
+void testGpsParser(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+
+#endif /* TEST_CYCLIC_BUFFER_H_ */
diff --git a/win32_functional_tests/.cproject b/win32_functional_tests/.cproject
new file mode 100644
index 0000000000..2480081e74
--- /dev/null
+++ b/win32_functional_tests/.cproject
@@ -0,0 +1,42 @@
+
+
+
+