ts plugin

This commit is contained in:
Matthew Kennedy 2023-02-20 21:02:11 -08:00
parent 40a0523fc3
commit 17492fcf93
27 changed files with 0 additions and 2090 deletions

View File

@ -1,33 +0,0 @@
name: TS Plugin
on: [push,pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: '8'
- name: Test Compiler
run: javac -version
- name: Install Tools
working-directory: ./.github/workflows/
run: |
sudo ./add-ubuntu-latest-apt-mirrors.sh
sudo apt-get install sshpass
- name: Build TS plugin body
working-directory: ./java_tools/ts_plugin
run: ant
# - name: Upload plugin body
# working-directory: .
# run: java_console/upload_file.sh ${{ secrets.RUSEFI_SSH_USER }} ${{ secrets.RUSEFI_SSH_PASS }} ${{ secrets.RUSEFI_SSH_SERVER }} build_server/autoupdate java_tools/ts_plugin/build/jar/rusefi_plugin_body.jar

View File

@ -1,17 +0,0 @@
plugins {
id 'java-library'
id 'com.github.johnrengelman.shadow' version '6.1.0'
}
apply from: '../../android/dependencies.gradle'
dependencies {
api project(':core_ui')
api project(':shared_ui')
api project(':ecu_io')
api project(':inifile')
api project(':ts_plugin_launcher')
testImplementation global_libs.mockito
testImplementation testFixtures( project(':ecu_io'))
implementation files('../ts_plugin_launcher/lib/TunerStudioPluginAPI.jar')
}

View File

@ -1,77 +0,0 @@
<project default="jar">
<property name="jar_file_folder" value="build/jar"/>
<property name="jar_file" value="${jar_file_folder}/rusefi_plugin_body.jar"/>
<property name="console_path" value="../../java_console"/>
<property name="launcher_path" value="../ts_plugin_launcher"/>
<target name="clean">
<delete dir="build"/>
</target>
<target name="compile">
<mkdir dir="build/classes"/>
<resources id="libs">
<string>lib/mockito-all-1.10.19.jar</string>
<string>${console_path}/../java_tools/configuration_definition/lib/snakeyaml.jar</string>
<string>${console_path}/lib/javacan-core.jar</string>
</resources>
<pathconvert property="lib_list" refid="libs" pathsep=":" />
<javac debug="yes"
destdir="build/classes"
classpath="${lib_list}:${console_path}/lib/log4j-api-2.13.3.jar:${console_path}/lib/log4j-core-2.13.3.jar:${console_path}/lib/jsr305-2.0.1.jar:${console_path}/lib/jcip-annotations-1.0.jar:${console_path}/lib/annotations.jar:${console_path}/lib/jSerialComm.jar:${console_path}/lib/junit.jar:${console_path}/lib/json-simple-1.1.1.jar:${console_path}/lib/annotations.jar:${launcher_path}/lib/TunerStudioPluginAPI.jar:${console_path}/lib/httpclient.jar:${console_path}/lib/httpmime.jar:${console_path}/lib/httpcore.jar"
>
<src path="${console_path}/shared_ui/src"/>
<src path="${console_path}/core_ui/src/main/java"/>
<src path="${console_path}/autoupdate/src/main/java"/>
<src path="${console_path}/shared_io/src/main/java"/>
<src path="${console_path}/inifile/src"/>
<src path="${console_path}/logging/src"/>
<src path="${console_path}/io/src/main/java"/>
<src path="${console_path}/logging-api/src/main/java"/>
<src path="${console_path}/models/src"/>
<src path="${launcher_path}/src"/>
<src path="src/main/java"/>
<src path="src/test/java"/>
<exclude name="**/*Sandbox.java"/>
</javac>
</target>
<target name="jar" depends="compile">
<mkdir dir="${jar_file_folder}"/>
<delete file="${jar_file}"/>
<copy todir="build/classes">
<fileset dir="../ts_plugin_launcher/src/main/resources" includes="**/*.png"/>
<fileset dir="${console_path}/shared_ui/resources" includes="**/*.png"/>
</copy>
<tstamp>
<format property="TODAY" pattern="yyyy-MM-dd HH:mm:ss"/>
<format property="TODAY_DATE" pattern="yyyy-MM-dd"/>
</tstamp>
<jar destfile="${jar_file}" basedir="build/classes">
<manifest>
<attribute name="Built-Date" value="${TODAY_DATE}"/>
<attribute name="Built-Timestamp" value="${TODAY}"/>
<attribute name="Signature-Vendor" value="rusEFI LLC"/>
</manifest>
<zipfileset src="../../java_console/lib/httpclient.jar" includes="**/*.class"/>
<zipfileset src="../../java_console/lib/httpcore.jar" includes="**/*.class"/>
<zipfileset src="../../java_console/lib/httpmime.jar" includes="**/*.class"/>
<zipfileset src="../../java_console/lib/javacan-core.jar" includes="**/*.class"/>
<zipfileset src="../../java_console/lib/json-simple-1.1.1.jar" includes="**/*.class"/>
<zipfileset src="../../java_console/lib/jSerialComm.jar" includes="**/*.class **/*.so **/*.dll **/*.jnilib"/>
<!-- <zipfileset src="lib/commons-logging.jar" includes="**/*.class"/>-->
</jar>
</target>
<target name="body_local_install" depends="jar">
<copy file="${jar_file}" todir="${user.home}/.rusEFI/"/>
</target>
</project>

View File

@ -1,59 +0,0 @@
package com.rusefi;
import com.rusefi.tune.xml.Msq;
import com.rusefi.xml.XmlUtil;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class TsTuneReader {
private static final String TS_USER_FILE = System.getProperty("user.home") + File.separator + ".efiAnalytics" + File.separator + "tsUser.properties";
public static void main(String[] args) throws Exception {
String ecuName = "dev";
Msq tune = readTsTune(ecuName);
System.out.println(tune);
}
public static Msq readTsTune(String ecuName) throws Exception {
String fileName = getTsTuneFileName(ecuName);
return XmlUtil.readModel(Msq.class, fileName);
}
@NotNull
public static String getTsTuneFileName(String ecuName) {
String projectsDir = getProjectsDir();
return projectsDir + File.separator + ecuName + File.separator + "CurrentTune.msq";
}
public static String getProjectsDir() {
try {
Properties tsUser = new Properties();
tsUser.load(new FileInputStream(TS_USER_FILE));
// reading TS properties
return tsUser.getProperty("projectsDir");
} catch (IOException e) {
JFileChooser fr = new JFileChooser();
FileSystemView fw = fr.getFileSystemView();
File defaultDirectory = fw.getDefaultDirectory();
// fallback mechanism just in case
return defaultDirectory + File.separator + "TunerStudioProjects";
}
}
@NotNull
public static String getProjectModeFileName(String projectName) {
return getProjectsDir() +
File.separator + projectName +
File.separator + "projectCfg" +
File.separator + "mainController.ini";
}
}

View File

@ -1,127 +0,0 @@
package com.rusefi.ts_plugin;
import com.rusefi.auth.AuthTokenUtil;
import com.rusefi.autodetect.PortDetector;
import com.rusefi.core.ui.AutoupdateUtil;
import com.rusefi.proxy.NetworkConnector;
import com.rusefi.proxy.NetworkConnectorContext;
import com.rusefi.tools.VehicleToken;
import com.rusefi.ui.AuthTokenPanel;
import com.rusefi.ui.util.URLLabel;
import org.putgemin.VerticalFlowLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
* @see PluginEntry
*/
public class BroadcastTab {
private final JComponent content = new JPanel(new VerticalFlowLayout());
private final JLabel status = new JLabel();
private final JButton disconnect = new JButton("Stop Broadcasting");
private NetworkConnector networkConnector;
public BroadcastTab() {
JButton broadcast = new JButton("Broadcast");
disconnect.setEnabled(false);
broadcast.addActionListener(e -> {
String authToken = AuthTokenPanel.getAuthToken();
if (!AuthTokenUtil.isToken(authToken)) {
status.setText("Auth token is required to broadcast ECU");
return;
}
new Thread(() -> {
String autoDetectedPort = PortDetector.autoDetectSerial(null).getSerialPort();
SwingUtilities.invokeLater(() -> {
startBroadcasting(authToken, autoDetectedPort);
});
}).start();
});
disconnect.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
networkConnector.close();
disconnect.setEnabled(false);
}
});
content.add(createVehicleTokenPanel());
content.add(broadcast);
content.add(status);
content.add(disconnect);
content.add(new URLLabel(RemoteTab.HOWTO_REMOTE_TUNING));
content.add(new JLabel(PluginEntry.LOGO));
AutoupdateUtil.trueLayout(content);
}
private Component createVehicleTokenPanel() {
JLabel label = new JLabel();
updateVehicleTokenLabel(label);
JButton copy = new JButton("Copy to clipboard");
copy.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
StringSelection stringSelection = new StringSelection("" + VehicleToken.getOrCreate());
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(stringSelection, null);
}
});
JButton reset = new JButton("Reset");
reset.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
VehicleToken.refresh();
updateVehicleTokenLabel(label);
}
});
JPanel panel = new JPanel(new FlowLayout());
panel.add(label);
panel.add(copy);
panel.add(reset);
return panel;
}
private void updateVehicleTokenLabel(JLabel label) {
label.setText("Vehicle access token: " + VehicleToken.getOrCreate());
}
private void startBroadcasting(String authToken, String autoDetectedPort) {
if (autoDetectedPort == null) {
status.setText("<html>rusEFI ECU not detected.<br/>Please make sure that TunerStudio is currently not connected to ECU.</html>");
} else {
status.setText("rusEFI detected at " + autoDetectedPort);
disconnect.setEnabled(true);
NetworkConnectorContext connectorContext = new NetworkConnectorContext();
new Thread(() -> {
networkConnector = new NetworkConnector();
NetworkConnector.NetworkConnectorResult networkConnectorResult = networkConnector.start(NetworkConnector.Implementation.Plugin, authToken, autoDetectedPort, connectorContext);
SwingUtilities.invokeLater(() -> status.setText("One time password to connect to this ECU: " + networkConnectorResult.getOneTimeToken()));
}).start();
}
AutoupdateUtil.trueLayout(content);
}
public JComponent getContent() {
return content;
}
}

View File

@ -1,140 +0,0 @@
package com.rusefi.ts_plugin;
import com.devexperts.logging.Logging;
import com.rusefi.autodetect.PortDetector;
import com.rusefi.io.ConnectionStateListener;
import com.rusefi.io.LinkManager;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static com.devexperts.logging.Logging.getLogging;
/**
* todo: move IO away from AWT thread
*/
public class ConnectPanel {
private static final Logging log = getLogging(ConnectPanel.class);
static final Executor IO_THREAD = Executors.newSingleThreadExecutor();
private final JPanel content = new JPanel(new BorderLayout());
private final JLabel status = new JLabel();
private LinkManager controllerConnector;
private final JButton connect = new JButton("Connect");
private final JButton disconnect = new JButton("Disconnect");
private boolean isFirstAttempt = true;
public ConnectPanel(final ConnectionStateListener connectionStateListener) {
JPanel flow = new JPanel(new FlowLayout());
disconnect.setEnabled(false);
disconnect.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
controllerConnector.close();
status.setText("Disconnected");
disconnect.setEnabled(false);
connect.setEnabled(true);
}
});
connect.addActionListener(e -> {
if (isFirstAttempt) {
isFirstAttempt = false;
Window topFrame = SwingUtilities.getWindowAncestor(connect);
log.info("Adding Window Listener to " + topFrame);
topFrame.addWindowListener(new WindowAdapter() {
@Override
public void windowDeactivated(WindowEvent e) {
log.info("topFrame windowDeactivated " + topFrame);
}
@Override
public void windowClosing(WindowEvent e) {
log.info("windowClosing " + topFrame);
if (controllerConnector != null)
controllerConnector.close();
// I am super confused about the life cycle of parent Window
connect.setEnabled(true);
disconnect.setEnabled(false);
}
@Override
public void windowClosed(WindowEvent e) {
log.info("windowClosed " + topFrame);
}
});
topFrame.addComponentListener(new ComponentAdapter() {
@Override
public void componentHidden(ComponentEvent e) {
log.info("componentHidden " + topFrame);
}
});
}
connect.setEnabled(false);
status.setText("Looking for rusEFI...");
IO_THREAD.execute(() -> {
controllerConnector = new LinkManager()
.setCompositeLogicEnabled(false)
.setNeedPullData(false);
try {
tryToConnect(connectionStateListener);
} catch (Throwable er) {
log.error("Error connecting", er);
SwingUtilities.invokeLater(() -> {
status.setText("Some error, see logs.");
connect.setEnabled(true);
});
}
});
});
flow.add(connect);
flow.add(disconnect);
content.add(flow, BorderLayout.NORTH);
content.add(status, BorderLayout.SOUTH);
}
private void tryToConnect(ConnectionStateListener connectionStateListener) {
String autoDetectedPort = PortDetector.autoDetectSerial(null).getSerialPort();
if (autoDetectedPort == null) {
status.setText("rusEFI not found");
connect.setEnabled(true);
} else {
controllerConnector.startAndConnect(autoDetectedPort, new ConnectionStateListener() {
public void onConnectionEstablished() {
SwingUtilities.invokeLater(() -> {
status.setText("Connected to rusEFI " + autoDetectedPort);
disconnect.setEnabled(true);
connectionStateListener.onConnectionEstablished();
});
}
public void onConnectionFailed(String message) {
}
});
}
}
public LinkManager getControllerConnector() {
return controllerConnector;
}
public static String getLastFour(String fileName) {
int dotIndex = fileName.indexOf(".");
fileName = fileName.substring(0, dotIndex);
if (fileName.length() < 5)
return fileName;
return fileName.substring(fileName.length() - 4);
}
public JComponent getContent() {
return content;
}
}

View File

@ -1,69 +0,0 @@
package com.rusefi.ts_plugin;
import javax.swing.*;
import javax.swing.text.*;
public class IntegerDocumentFilter extends DocumentFilter {
public static void install(JTextField jTextField) {
PlainDocument doc = (PlainDocument) jTextField.getDocument();
doc.setDocumentFilter(new IntegerDocumentFilter());
}
@Override
public void insertString(FilterBypass fb, int offset, String string,
AttributeSet attr) throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.insert(offset, string);
if (test(sb.toString())) {
super.insertString(fb, offset, string, attr);
} else {
// warn the user and don't allow the insert
}
}
private boolean test(String text) {
try {
Integer.parseInt(text);
return true;
} catch (NumberFormatException e) {
return false;
}
}
@Override
public void replace(FilterBypass fb, int offset, int length, String text,
AttributeSet attrs) throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.replace(offset, offset + length, text);
if (test(sb.toString())) {
super.replace(fb, offset, length, text, attrs);
} else {
// warn the user and don't allow the insert
}
}
@Override
public void remove(FilterBypass fb, int offset, int length)
throws BadLocationException {
Document doc = fb.getDocument();
StringBuilder sb = new StringBuilder();
sb.append(doc.getText(0, doc.getLength()));
sb.delete(offset, offset + length);
if (test(sb.toString())) {
super.remove(fb, offset, length);
} else {
// warn the user and don't allow the insert
}
}
}

View File

@ -1,124 +0,0 @@
package com.rusefi.ts_plugin;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.rusefi.TsTuneReader;
import com.rusefi.core.ui.AutoupdateUtil;
import com.rusefi.tools.online.Online;
import com.rusefi.tools.online.UploadResult;
import org.apache.http.concurrent.FutureCallback;
import org.jetbrains.annotations.NotNull;
import org.putgemin.VerticalFlowLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.Objects;
import java.util.function.Supplier;
public class LogUploadSelector {
private final JPanel content = new JPanel(new BorderLayout());
private final JLabel uploadState = new JLabel();
private final JPanel fileList = new JPanel(new VerticalFlowLayout());
private final Supplier<ControllerAccess> controllerAccessSupplier;
public LogUploadSelector(Supplier<ControllerAccess> controllerAccessSupplier) {
this.controllerAccessSupplier = controllerAccessSupplier;
JButton refresh = new JButton("Refresh");
refresh.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
refresh();
}
});
JPanel topPanel = new JPanel(new FlowLayout());
topPanel.add(refresh);
JPanel filePanel = new JPanel(new BorderLayout());
filePanel.add(fileList, BorderLayout.CENTER);
JScrollPane fileScroll = new JScrollPane(filePanel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
content.add(topPanel, BorderLayout.NORTH);
content.add(fileScroll, BorderLayout.CENTER);
content.add(uploadState, BorderLayout.SOUTH);
refresh();
}
private void refresh() {
fileList.removeAll();
String[] ecuConfigurationNames = controllerAccessSupplier.get().getEcuConfigurationNames();
if (ecuConfigurationNames != null && ecuConfigurationNames.length > 0) {
String folder = getLogsFolderDir(ecuConfigurationNames[0]);
processFolder(folder);
}
AutoupdateUtil.trueLayout(content);
}
private void processFolder(String folder) {
for (String fileName : Objects.requireNonNull(new File(folder).list((dir, name) -> name.endsWith(".mlg")))) {
JPanel panel = new JPanel(new FlowLayout());
JButton delete = new JButton("Delete");
JButton upload = new JButton("Upload");
final String fullFileName = folder + File.separator + fileName;
panel.add(delete);
panel.add(upload);
delete.addActionListener(new AbstractAction() {
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public void actionPerformed(ActionEvent e) {
int result = JOptionPane.showConfirmDialog(null, "Are you sure you want to remove " + fileName,
"rusEfi", JOptionPane.YES_NO_OPTION);
if (result == JOptionPane.YES_OPTION) {
new File(fullFileName).delete();
}
}
});
upload.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
Online.uploadFile(content, new FutureCallback<UploadResult>() {
@Override
public void completed(UploadResult uploadResult) {
SwingUtilities.invokeLater(() -> UploadView.setResult(uploadResult, uploadState));
}
@Override
public void failed(Exception e) {
}
@Override
public void cancelled() {
}
}, fullFileName);
}
});
panel.add(new JLabel(fileName));
fileList.add(panel);
}
}
@NotNull
public static String getLogsFolderDir(String projectName) {
return TsTuneReader.getProjectsDir() + File.separator + projectName + File.separator + "DataLogs";
}
public JComponent getContent() {
return content;
}
}

View File

@ -1,36 +0,0 @@
package com.rusefi.ts_plugin;
import com.opensr5.ini.IniFileMetaInfo;
import com.opensr5.ini.RawIniFile;
import com.rusefi.TsTuneReader;
import org.jetbrains.annotations.Nullable;
import java.io.FileNotFoundException;
public class MetaDataCache {
private static String cachedProjectName;
private static IniFileMetaInfo cache;
@Nullable
public synchronized static IniFileMetaInfo getModel(String projectName) {
if (projectName == null)
return null;
if (!projectName.equals(cachedProjectName)) {
cache = null;
}
if (cache == null) {
System.out.println("Reading meta " + projectName);
String modeFileName = TsTuneReader.getProjectModeFileName(projectName);
try {
cache = new IniFileMetaInfo(RawIniFile.read(modeFileName));
} catch (FileNotFoundException e) {
System.out.println("No luck reading " + modeFileName + ": " + e);
return null;
}
cachedProjectName = projectName;
}
return cache;
}
}

View File

@ -1,111 +0,0 @@
package com.rusefi.ts_plugin;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.rusefi.core.ui.AutoupdateUtil;
import com.rusefi.ts_plugin.auth.InstanceAuthContext;
import com.rusefi.ts_plugin.util.ManifestHelper;
import com.rusefi.tune.xml.Constant;
import javax.swing.*;
import java.awt.*;
import java.lang.reflect.Field;
import java.util.function.Supplier;
/**
* {@link TsPluginLauncher} creates an instance of this class via reflection.
*
* @see TuneUploadTab upload tune & TODO upload logs
* @see RemoteTab remote ECU access & control
* @see BroadcastTab offer your ECU for remove access & control
* @see PluginBodySandbox
*/
public class PluginEntry implements TsPluginBody {
private final JPanel content = new JPanel(new BorderLayout());
static final ImageIcon LOGO = AutoupdateUtil.loadIcon("/rusefi_online_color_300.png");
private final JTabbedPane tabbedPane = new JTabbedPane();
/**
* the real constructor - this one is invoked via reflection
*/
@SuppressWarnings("unused")
public PluginEntry() {
this(ControllerAccess::getInstance);
}
public PluginEntry(Supplier<ControllerAccess> controllerAccessSupplier) {
System.out.println("PluginEntry init " + this);
if (isLauncherTooOld()) {
content.add(new JLabel("<html>Please manually install latest plugin version<br/>Usually we can update to latest version but this time there was a major change.<br/>" +
"Please use TunerStudio controls to update to plugin from recent rusEFI bundle."));
return;
}
TuneUploadTab tuneUploadTab = new TuneUploadTab(controllerAccessSupplier);
LogUploadSelector logUploadTab = new LogUploadSelector(controllerAccessSupplier);
BroadcastTab broadcastTab = new BroadcastTab();
RemoteTab remoteTab = new RemoteTab();
tabbedPane.addTab("Tune Upload", tuneUploadTab.getContent());
tabbedPane.addTab("Log Upload", logUploadTab.getContent());
tabbedPane.addTab("Broadcast", broadcastTab.getContent());
tabbedPane.addTab("Remote ECU", remoteTab.getContent());
this.content.add(tabbedPane);
InstanceAuthContext.startup();
}
public static String getNonDaemonThreads() {
StringBuilder sb = new StringBuilder();
for (Thread thread : Thread.getAllStackTraces().keySet()) {
// Daemon thread will not prevent the JVM from exiting
if (!thread.isDaemon())
sb.append(thread.getName() + "\n");
}
return sb.toString();
}
private boolean isLauncherTooOld() {
try {
// at some point we did not have this field so using reflection for the next couple of months
Field field = TsPluginLauncher.class.getField("BUILD_VERSION");
int launcherVersion = (int) field.get(null);
System.out.println("Launcher version " + launcherVersion + " detected");
return false;
} catch (NoSuchFieldException | IllegalAccessException e) {
return true;
}
}
public static boolean isEmpty(Constant constant) {
if (constant == null)
return true;
return isEmpty(constant.getValue());
}
private static boolean isEmpty(String value) {
return value == null || value.trim().length() == 0;
}
@Override
public JComponent getContent() {
return content;
}
/*
public void close() {
PersistentConfiguration.getConfig().save();
}
*/
/**
* this method is invoked by refection
*
* @see TsPluginBody#GET_VERSION
*/
@SuppressWarnings("unused")
public static String getVersion() {
return ManifestHelper.getVersion();
}
}

View File

@ -1,358 +0,0 @@
package com.rusefi.ts_plugin;
import com.rusefi.NamedThreadFactory;
import com.rusefi.core.SignatureHelper;
import com.rusefi.Timeouts;
import com.rusefi.core.ui.AutoupdateUtil;
import com.rusefi.core.Pair;
import com.rusefi.io.serial.StreamStatistics;
import com.rusefi.io.tcp.ServerSocketReference;
import com.rusefi.io.tcp.TcpIoStream;
import com.rusefi.proxy.NetworkConnector;
import com.rusefi.proxy.client.LocalApplicationProxy;
import com.rusefi.proxy.client.LocalApplicationProxyContextImpl;
import com.rusefi.proxy.client.UpdateType;
import com.rusefi.core.rusEFIVersion;
import com.rusefi.server.ApplicationRequest;
import com.rusefi.server.ControllerInfo;
import com.rusefi.server.SessionDetails;
import com.rusefi.server.UserDetails;
import com.rusefi.tools.online.HttpUtil;
import com.rusefi.tools.online.ProxyClient;
import com.rusefi.tools.online.PublicSession;
import com.rusefi.ts_plugin.auth.InstanceAuthContext;
import com.rusefi.ts_plugin.auth.SelfInfo;
import com.rusefi.ui.AuthTokenPanel;
import com.rusefi.ui.util.URLLabel;
import org.jetbrains.annotations.NotNull;
import org.putgemin.VerticalFlowLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import static com.rusefi.core.preferences.storage.PersistentConfiguration.getConfig;
/**
* remote ECU access & control
*
* @see RemoteTabSandbox
* @see PluginEntry
*/
public class RemoteTab {
private static final String APPLICATION_PORT = "application_port";
public static final String HOWTO_REMOTE_TUNING = "https://github.com/rusefi/rusefi/wiki/HOWTO-Remote-Tuning";
private final JComponent content = new JPanel(new BorderLayout());
private final JScrollPane scroll = new JScrollPane(content, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
private final JPanel list = new JPanel(new VerticalFlowLayout());
private final JTextField oneTimePasswordControl = new JTextField("0") {
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
// todo: dynamic calculation of desired with based on String width?
return new Dimension(100, size.height);
}
};
private StreamStatusControl streamStatusControl = null;
private final JButton disconnect = new JButton("Disconnect");
private final Executor listDownloadExecutor = Executors.newSingleThreadExecutor(new NamedThreadFactory("online list downloader", true));
public RemoteTab() {
JButton refresh = new JButton("Refresh Remote Controllers List");
refresh.addActionListener(e -> requestControllersList());
disconnect.addActionListener(e -> {
LocalApplicationProxy localApplicationProxy = RemoteTabController.INSTANCE.getLocalApplicationProxy();
if (localApplicationProxy != null)
localApplicationProxy.close();
RemoteTabController.INSTANCE.setState(RemoteTabController.State.NOT_CONNECTED);
requestControllersList();
});
Timer timer = new Timer(Timeouts.SECOND, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (streamStatusControl != null)
streamStatusControl.update();
}
});
timer.start();
JTextField applicationPort = new JTextField() {
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
// todo: dynamic calculation of desired with based on String width?
return new Dimension(100, size.height);
}
};
IntegerDocumentFilter.install(applicationPort);
IntegerDocumentFilter.install(oneTimePasswordControl);
String portProperty = getLocalPort();
applicationPort.setText(portProperty);
JPanel topLines = new JPanel(new VerticalFlowLayout());
topLines.add(new URLLabel(HOWTO_REMOTE_TUNING));
topLines.add(new SelfInfo().getContent());
topLines.add(refresh);
topLines.add(new JLabel("Local Port for tuning software"));
topLines.add(applicationPort);
topLines.add(new JLabel("One time password:"));
topLines.add(oneTimePasswordControl);
content.add(topLines, BorderLayout.NORTH);
content.add(list, BorderLayout.CENTER);
list.add(new JLabel("Requesting list of ECUs"));
InstanceAuthContext.listeners.add(userDetails -> requestControllersList());
LocalApplicationProxy currentState = RemoteTabController.INSTANCE.getLocalApplicationProxy();
if (currentState == null) {
requestControllersList();
} else {
setConnectedStatus(currentState.getApplicationRequest().getVehicleOwner(), null,
currentState.getApplicationRequest().getSessionDetails().getControllerInfo());
}
}
private String getLocalPort() {
return getConfig().getRoot().getProperty(APPLICATION_PORT, "29001");
}
private void requestControllersList() {
listDownloadExecutor.execute(() -> {
try {
List<PublicSession> userDetails = ProxyClient.getOnlineApplications(HttpUtil.PROXY_JSON_API_HTTP_PORT);
SwingUtilities.invokeLater(() -> showList(userDetails));
} catch (IOException e) {
e.printStackTrace();
}
});
}
private void showList(List<PublicSession> userDetails) {
if (RemoteTabController.INSTANCE.getState() != RemoteTabController.State.NOT_CONNECTED)
return;
list.removeAll();
if (userDetails.isEmpty()) {
list.add(new JLabel("No ECUs are broadcasting at the moment :("));
} else {
JPanel verticalPanel = new JPanel(new VerticalFlowLayout());
list.add(verticalPanel);
for (PublicSession user : userDetails) {
verticalPanel.add(createControllerRow(user));
}
}
AutoupdateUtil.trueLayout(list);
}
private JComponent createControllerRow(PublicSession publicSession) {
ControllerInfo controllerInfo = publicSession.getControllerInfo();
JComponent topLine = new JPanel(new FlowLayout());
topLine.add(new JLabel(publicSession.getVehicleOwner().getUserName()));
topLine.add(new JLabel(controllerInfo.getVehicleName() + " " + controllerInfo.getEngineMake() + " " + controllerInfo.getEngineCode()));
JPanel bottomPanel = new JPanel(new VerticalFlowLayout());
if (publicSession.isUsed()) {
bottomPanel.add(new JLabel(" Used by " + publicSession.getTunerName()));
} else {
JButton connect = new JButton("Connect to " + publicSession.getVehicleOwner().getUserName() + " ECU");
connect.addActionListener(event -> connectToProxy(publicSession));
bottomPanel.add(connect);
if (InstanceAuthContext.isOurController(publicSession.getVehicleOwner().getUserId())) {
if (publicSession.getImplementation().equals(NetworkConnector.Implementation.SBC.name())) {
JPanel updateSoftwarePanel = new JPanel(new FlowLayout());
JButton updateSoftwareLatest = new JButton("Update Connector to Latest");
updateSoftwareLatest.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
requestUpdate(publicSession, updateSoftwareLatest, UpdateType.CONTROLLER);
}
});
JButton updateSoftwareRelease = new JButton("Update Connector to Release");
updateSoftwareRelease.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
requestUpdate(publicSession, updateSoftwareRelease, UpdateType.CONTROLLER_RELEASE);
}
});
updateSoftwarePanel.add(updateSoftwareLatest);
updateSoftwarePanel.add(updateSoftwareRelease);
bottomPanel.add(updateSoftwarePanel);
}
JPanel updateFirmwarePanel = createUpdateFirmwarePanel(publicSession);
bottomPanel.add(updateFirmwarePanel);
}
}
JPanel userPanel = new JPanel(new BorderLayout());
JPanel infoLine = new JPanel(new FlowLayout());
infoLine.add(new JLabel("Age " + publicSession.getAge()));
infoLine.add(getSignatureDownload(controllerInfo));
userPanel.add(topLine, BorderLayout.NORTH);
userPanel.add(infoLine, BorderLayout.CENTER);
userPanel.add(bottomPanel, BorderLayout.SOUTH);
userPanel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
return userPanel;
}
@NotNull
private JPanel createUpdateFirmwarePanel(PublicSession publicSession) {
JButton updateFirmwareLatest = new JButton("Update ECU to latest");
updateFirmwareLatest.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
requestUpdate(publicSession, updateFirmwareLatest, UpdateType.FIRMWARE);
}
});
JButton updateFirmwareRelease = new JButton("Update ECU to release");
updateFirmwareRelease.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
requestUpdate(publicSession, updateFirmwareRelease, UpdateType.FIRMWARE_RELEASE);
}
});
JPanel updateFirmwarePanel = new JPanel(new FlowLayout());
updateFirmwarePanel.add(updateFirmwareLatest);
updateFirmwarePanel.add(updateFirmwareRelease);
return updateFirmwarePanel;
}
private void requestUpdate(PublicSession publicSession, JButton updateSoftware, UpdateType type) {
try {
LocalApplicationProxy.requestSoftwareUpdate(HttpUtil.PROXY_JSON_API_HTTP_PORT,
getApplicationRequest(publicSession), type);
updateSoftware.setText("Update requested");
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
@NotNull
private URLLabel getSignatureDownload(ControllerInfo controllerInfo) {
Pair<String, String> url = SignatureHelper.getUrl(controllerInfo.getSignature());
return new URLLabel(url.second, url.first);
}
private void connectToProxy(PublicSession publicSession) {
RemoteTabController.INSTANCE.setState(RemoteTabController.State.CONNECTING);
setStatus("Connecting to " + publicSession.getVehicleOwner().getUserName());
LocalApplicationProxy.ConnectionListener connectionListener = (localApplicationProxy, authenticatorToProxyStream) -> {
RemoteTabController.INSTANCE.setConnected(localApplicationProxy);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setConnectedStatus(publicSession.getVehicleOwner(), authenticatorToProxyStream, publicSession.getControllerInfo());
}
});
};
new Thread(() -> {
runAuthenticator(publicSession, connectionListener);
}, "Authenticator").start();
}
private void setConnectedStatus(UserDetails userDetails, StreamStatistics authenticatorToProxyStream, ControllerInfo controllerInfo) {
if (authenticatorToProxyStream != null) {
streamStatusControl = new StreamStatusControl(authenticatorToProxyStream);
}
setStatus("Connected to " + userDetails.getUserName(),
new JLabel("You can now connect your TunerStudio to IP address localhost and port " + getLocalPort()),
new URLLabel(SignatureHelper.getUrl(controllerInfo.getSignature()).first),
disconnect, streamStatusControl == null ? null : streamStatusControl.getContent());
}
private void setStatus(String text, JComponent... extra) {
list.removeAll();
list.add(new JLabel(text));
for (JComponent component : extra) {
if (component != null) {
list.add(component);
}
}
AutoupdateUtil.trueLayout(list);
}
private void runAuthenticator(PublicSession publicSession, LocalApplicationProxy.ConnectionListener connectionListener) {
ApplicationRequest applicationRequest = getApplicationRequest(publicSession);
try {
AtomicReference<ServerSocketReference> serverHolderAtomicReference = new AtomicReference<>();
TcpIoStream.DisconnectListener disconnectListener = message -> SwingUtilities.invokeLater(() -> {
System.out.println("Disconnected " + message);
setStatus("Disconnected: " + message);
RemoteTabController.INSTANCE.setState(RemoteTabController.State.NOT_CONNECTED);
ServerSocketReference serverHolder = serverHolderAtomicReference.get();
if (serverHolder != null)
serverHolder.close();
});
LocalApplicationProxyContextImpl context = new LocalApplicationProxyContextImpl() {
@Override
public int authenticatorPort() {
return Integer.parseInt(getLocalPort());
}
};
ServerSocketReference serverHolder = LocalApplicationProxy.startAndRun(
context,
applicationRequest,
HttpUtil.PROXY_JSON_API_HTTP_PORT, disconnectListener, connectionListener);
serverHolderAtomicReference.set(serverHolder);
} catch (IOException e) {
setStatus("IO error: " + e);
}
}
@NotNull
private ApplicationRequest getApplicationRequest(PublicSession publicSession) {
SessionDetails sessionDetails = new SessionDetails(NetworkConnector.Implementation.Plugin,
publicSession.getControllerInfo(), AuthTokenPanel.getAuthToken(),
Integer.parseInt(oneTimePasswordControl.getText()), rusEFIVersion.CONSOLE_VERSION);
return new ApplicationRequest(sessionDetails, publicSession.getVehicleOwner());
}
public JComponent getContent() {
return scroll;
}
interface Listener {
void onConnected();
}
}

View File

@ -1,49 +0,0 @@
package com.rusefi.ts_plugin;
import com.rusefi.proxy.client.LocalApplicationProxy;
import java.util.concurrent.CopyOnWriteArrayList;
public enum RemoteTabController {
/**
* TunerStudio likes to close plugin panel, we need a singleton to preserve the state
*/
INSTANCE;
private State state = State.NOT_CONNECTED;
private LocalApplicationProxy localApplicationProxy;
public final CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
public synchronized void setState(State state) {
localApplicationProxy = null;
if (state != this.state) {
this.state = state;
for (Listener listener : listeners)
listener.onChange(state);
}
}
public synchronized State getState() {
return state;
}
public synchronized void setConnected(LocalApplicationProxy localApplicationProxy) {
setState(State.CONNECTED);
this.localApplicationProxy = localApplicationProxy;
}
public LocalApplicationProxy getLocalApplicationProxy() {
return localApplicationProxy;
}
public enum State {
NOT_CONNECTED,
CONNECTING,
CONNECTED
}
interface Listener {
void onChange(State state);
}
}

View File

@ -1,23 +0,0 @@
package com.rusefi.ts_plugin;
import com.rusefi.io.serial.StreamStatistics;
import javax.swing.*;
public class StreamStatusControl {
private final StreamStatistics authenticatorToProxyStream;
private final JLabel content = new JLabel();
public StreamStatusControl(StreamStatistics statistics) {
this.authenticatorToProxyStream = statistics;
}
public void update() {
content.setText("In " + authenticatorToProxyStream.getBytesIn() + " Out " + authenticatorToProxyStream.getBytesOut());
}
public JComponent getContent() {
return content;
}
}

View File

@ -1,210 +0,0 @@
package com.rusefi.ts_plugin;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.efiAnalytics.plugin.ecu.ControllerException;
import com.efiAnalytics.plugin.ecu.ControllerParameterChangeListener;
import com.opensr5.ini.IniFileModel;
import com.opensr5.ini.field.IniField;
import com.rusefi.NamedThreadFactory;
import com.rusefi.TsTuneReader;
import com.rusefi.config.generated.Fields;
import com.rusefi.tools.online.Online;
import com.rusefi.tools.online.UploadResult;
import com.rusefi.ts_plugin.util.ManifestHelper;
import com.rusefi.tune.xml.Msq;
import com.rusefi.ui.AuthTokenPanel;
import com.rusefi.ui.util.URLLabel;
import org.apache.http.concurrent.FutureCallback;
import org.putgemin.VerticalFlowLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ThreadFactory;
import java.util.function.Supplier;
/**
* @see PluginBodySandbox
*/
public class TuneUploadTab {
private final JComponent content = new JPanel(new VerticalFlowLayout());
// 2 seconds aggregation by default
private static final int AUTO_UPDATE_AGGREGATION = Integer.parseInt(System.getProperty("autoupload.aggregation", "2000"));
private static final ThreadFactory THREAD_FACTORY = new NamedThreadFactory("Tune Upload", true);
private static final String REO_URL = "https://rusefi.com/online/";
private final AuthTokenPanel tokenPanel = new AuthTokenPanel();
private final Supplier<ControllerAccess> controllerAccessSupplier;
private final UploadView uploadView = new UploadView();
private final JButton upload = new JButton("Upload Current Tune");
private String projectName;
private final UploaderStatus uploaderStatus = new UploaderStatus();
private final Timer timer = new Timer(AUTO_UPDATE_AGGREGATION, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
// System.out.println("Timer! " + System.currentTimeMillis() + " " + timer + " " + e);
if (UploadView.isAutoUpload()) {
System.out.println(new Date() + ": enqueue tune");
UploadQueue.enqueue(controllerAccessSupplier.get(), projectName);
}
}
});
private final ControllerParameterChangeListener listener;
public TuneUploadTab(Supplier<ControllerAccess> controllerAccessSupplier) {
this.controllerAccessSupplier = controllerAccessSupplier;
timer.stop();
timer.setRepeats(false);
UploadQueue.start();
listener = parameterName -> {
// System.out.println("Parameter value changed " + parameterName);
timer.restart();
};
upload.setBackground(new Color(0x90EE90));
Thread t = THREAD_FACTORY.newThread(new Runnable() {
@Override
public void run() {
while (true) {
String configurationName = getConfigurationName();
if ((projectName == null && configurationName != null)
|| !projectName.equals(configurationName)) {
handleConfigurationChange(configurationName, controllerAccessSupplier.get());
}
boolean isProjectActive = configurationName != null;
uploaderStatus.updateProjectStatus(configurationName, isProjectActive);
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
updateUploadEnabled();
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t.start();
upload.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if (!tokenPanel.hasToken()) {
tokenPanel.showError(content);
return;
}
String configurationName = getConfigurationName();
if (configurationName == null) {
JOptionPane.showMessageDialog(content, "No project opened");
return;
}
uploadView.uploadState.setVisible(true);
uploadView.uploadState.setText("Uploading...");
Msq tune = TuneUploder.writeCurrentTune(controllerAccessSupplier.get(), configurationName);
Online.uploadTune(tune, tokenPanel, content, new FutureCallback<UploadResult>() {
@Override
public void completed(UploadResult array) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
UploadView.setResult(array, uploadView.uploadState);
}
});
}
@Override
public void failed(Exception e) {
}
@Override
public void cancelled() {
}
});
}
});
content.add(new JLabel("Version " + ManifestHelper.getBuildTimestamp()));
// content.add(new JLabel("Active project: " + getConfigurationName()));
content.add(uploadView.getContent());
content.add(upload);
content.add(new JLabel(PluginEntry.LOGO));
content.add(tokenPanel.getContent());
content.add(new URLLabel(REO_URL));
}
/**
* This method is invoked every time we defect a switch between projects
*/
private void handleConfigurationChange(String projectName, ControllerAccess controllerAccess) {
uploaderStatus.readTuneState(projectName);
if (projectName != null) {
subscribeToUpdates(projectName, controllerAccess);
}
updateUploadEnabled();
this.projectName = projectName;
}
private void updateUploadEnabled() {
uploadView.update(uploaderStatus);
upload.setEnabled(uploaderStatus.isTuneOk() && uploaderStatus.isProjectIsOk());
}
private void subscribeToUpdates(String configurationName, ControllerAccess controllerAccess) {
IniFileModel model = new IniFileModel().readIniFile(TsTuneReader.getProjectModeFileName(configurationName));
Map<String, IniField> allIniFields = model.allIniFields;
if (model.allIniFields == null)
return;
for (Map.Entry<String, IniField> field : allIniFields.entrySet()) {
boolean isOnlineTuneField = field.getValue().getOffset() >= Fields.engine_configuration_s_size;
if (!isOnlineTuneField) {
try {
controllerAccess.getControllerParameterServer().subscribe(configurationName, field.getKey(), listener);
} catch (ControllerException e) {
throw new IllegalStateException(e);
}
}
}
}
private String getConfigurationName() {
ControllerAccess controllerAccess = controllerAccessSupplier.get();
if (controllerAccess == null) {
System.out.println("No ControllerAccess");
return null;
}
String[] configurationNames = controllerAccess.getEcuConfigurationNames();
if (configurationNames.length == 0)
return null;
return configurationNames[0];
}
public JComponent getContent() {
return content;
}
}

View File

@ -1,140 +0,0 @@
package com.rusefi.ts_plugin;
import com.devexperts.logging.Logging;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.efiAnalytics.plugin.ecu.ControllerException;
import com.efiAnalytics.plugin.ecu.ControllerParameter;
import com.efiAnalytics.plugin.ecu.servers.ControllerParameterServer;
import com.opensr5.ini.IniFileMetaInfo;
import com.rusefi.TsTuneReader;
import com.rusefi.tools.online.Online;
import com.rusefi.tune.xml.Constant;
import com.rusefi.tune.xml.Msq;
import com.rusefi.tune.xml.Page;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.xml.bind.JAXBException;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
public class TuneUploder {
private final static Logging log = Logging.getLogging(TuneUploder.class);
static Msq writeCurrentTune(ControllerAccess controllerAccess, String configurationName) {
Msq msq = grabTune(controllerAccess, configurationName);
if (msq == null)
return null;
try {
String fileName = Online.outputXmlFileName;
msq.writeXmlFile(fileName);
return msq;
} catch (JAXBException | IOException e) {
System.out.println("Error writing XML: " + e);
return null;
}
}
@Nullable
public static Msq grabTune(ControllerAccess controllerAccess, String configurationName) {
Objects.requireNonNull(controllerAccess, "controllerAccess");
IniFileMetaInfo meta = MetaDataCache.getModel(configurationName);
if (meta == null)
return null;
Msq msq = Msq.create(meta.getTotalSize(), meta.getSignature());
ControllerParameterServer controllerParameterServer = controllerAccess.getControllerParameterServer();
Objects.requireNonNull(controllerParameterServer, "controllerParameterServer");
Map<String, Constant> fileSystemValues = getFileSystemValues(configurationName);
try {
String[] parameterNames = controllerParameterServer.getParameterNames(configurationName);
for (String parameterName : parameterNames) {
if (!fileSystemValues.containsKey(parameterName)) {
System.out.println("Skipping " + parameterName + " since not in model, maybe pcVariable?");
continue;
}
applyParameterValue(configurationName, msq, controllerParameterServer, fileSystemValues, parameterName);
}
} catch (ControllerException e) {
System.out.println("Error saving configuration: " + e);
return null;
}
return msq;
}
@NotNull
static Map<String, Constant> getFileSystemValues(String configurationName) {
if (configurationName == null)
return Collections.emptyMap();
Msq tsTune;
try {
tsTune = TsTuneReader.readTsTune(configurationName);
} catch (Exception e) {
return Collections.emptyMap();
}
Map<String, Constant> byName = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
for (Constant c : tsTune.findPage().constant) {
byName.put(c.getName(), c);
}
return byName;
}
private static void applyParameterValue(String configurationName, Msq msq, ControllerParameterServer controllerParameterServer, Map<String, Constant> byName, String parameterName) throws ControllerException {
ControllerParameter cp = controllerParameterServer.getControllerParameter(configurationName, parameterName);
Objects.requireNonNull(cp, "ControllerParameter");
String type = cp.getParamClass();
String value;
if (ControllerParameter.PARAM_CLASS_BITS.equals(type)) {
value = cp.getStringValue();
log.info("bits " + parameterName + ": " + value);
} else if (ControllerParameter.PARAM_CLASS_SCALAR.equals(type)) {
value = toString(cp.getScalarValue(), cp.getDecimalPlaces());
System.out.println("TsPlugin scalar " + parameterName + ": " + cp.getScalarValue() + "/" + cp.getStringValue());
} else if (ControllerParameter.PARAM_CLASS_ARRAY.equals(type)) {
value = getArrayValue(cp.getArrayValues());
} else if ("string".equals(type)) {
//value = cp.getStringValue();
// WOW hack
// TS does not provide values for string parameters?! so we read the file directly
Constant constant = byName.get(parameterName);
if (constant == null) {
System.out.println("Not found in TS tune " + parameterName);
value = null;
} else {
value = constant.getValue();
System.out.println("TsPlugin name=" + parameterName + " string=" + cp.getStringValue() + "/h=" + value);
}
} else {
System.out.println("TsPlugin name=" + parameterName + " unexpected type " + type + "/" + cp.getStringValue());
value = cp.getStringValue();
}
Page page = msq.findPage();
page.constant.add(new Constant(parameterName, cp.getUnits(), value, Integer.toString(cp.getDecimalPlaces())));
}
private static String getArrayValue(double[][] arrayValues) {
StringBuilder sb = new StringBuilder();
for (int rowIndex = 0; rowIndex < arrayValues.length; rowIndex++) {
double[] array = arrayValues[rowIndex];
sb.append("\n\t");
for (int colIndex = 0; colIndex < array.length; colIndex++) {
double value = array[colIndex];
sb.append(' ');
sb.append(value);
}
}
sb.append("\n");
return sb.toString();
}
private static String toString(double scalarValue, int decimalPlaces) {
// todo: start using decimalPlaces parameter!
return Double.toString(scalarValue);
}
}

View File

@ -1,140 +0,0 @@
package com.rusefi.ts_plugin;
import com.devexperts.logging.Logging;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.rusefi.core.FileUtil;
import com.rusefi.tools.online.Online;
import com.rusefi.tools.online.UploadResult;
import com.rusefi.tune.xml.Msq;
import com.rusefi.ui.AuthTokenPanel;
import javax.xml.bind.JAXBException;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.LinkedBlockingDeque;
public class UploadQueue {
private final static Logging log = Logging.getLogging(UploadQueue.class);
public static final String OUTBOX_FOLDER = FileUtil.RUSEFI_SETTINGS_FOLDER + File.separator + "outbox";
private static final LinkedBlockingDeque<FileAndFolder> queue = new LinkedBlockingDeque<>(128);
private static boolean isStarted;
public synchronized static void start() {
if (isStarted)
return;
isStarted = true;
readOutbox();
Thread t = new Thread(() -> {
try {
uploadLoop();
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}, "Posting Thread");
t.setDaemon(true);
t.start();
}
private static void readOutbox() {
File folder = new File(OUTBOX_FOLDER);
if (!folder.exists())
return;
String[] files = folder.list((dir, name) -> name.endsWith(".msq"));
if (files == null)
return;
for (String file : files) {
if (queue.size() > 90)
return;
System.out.println(UploadQueue.class.getSimpleName() + " readOutbox " + file);
try {
queue.put(new FileAndFolder(OUTBOX_FOLDER, file));
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(UploadQueue.class.getSimpleName() + " readOutbox got " + queue.size());
}
@SuppressWarnings("InfiniteLoopStatement")
private static void uploadLoop() throws InterruptedException {
while (true) {
FileAndFolder file = queue.take();
UploadResult result = Online.upload(new File(file.getFullName()), AuthTokenPanel.getAuthToken());
System.out.println("isError " + result.isError());
System.out.println("first " + result.getFirstMessage());
if (result.isError() && result.getFirstMessage().contains("This file already exists")) {
System.out.println(UploadQueue.class.getSimpleName() + " No need to re-try this one");
file.postUpload();
// do not retry this error
continue;
}
if (result.isError()) {
System.out.println(UploadQueue.class.getSimpleName() + " Re-queueing " + file.getFullName());
queue.put(file);
continue;
}
file.postUpload();
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private static void delete(String fileName) {
System.out.println(UploadQueue.class.getSimpleName() + " Deleting " + fileName);
new File(fileName).delete();
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void enqueue(ControllerAccess controllerAccess, String configurationName) {
start();
if (queue.size() > 100) {
// too much pending drama
return;
}
Msq msq = TuneUploder.grabTune(controllerAccess, configurationName);
if (msq == null) {
log.error("Error saving tune");
return;
}
msq.bibliography.setTuneComment("Auto-saved");
try {
new File(OUTBOX_FOLDER).mkdirs();
String fileName = System.currentTimeMillis() + ".msq";
String fullFileName = OUTBOX_FOLDER + File.separator + fileName;
msq.writeXmlFile(fullFileName);
queue.put(new FileAndFolder(OUTBOX_FOLDER, fileName));
} catch (InterruptedException | JAXBException | IOException e) {
throw new IllegalStateException(e);
}
}
static class FileAndFolder {
private static final boolean DEBUG_SAVE_UPLOADED = false;
private final String folder;
private final String file;
public FileAndFolder(String folder, String file) {
this.folder = folder;
this.file = file;
}
public String getFullName() {
return folder + File.separator + file;
}
@SuppressWarnings("ResultOfMethodCallIgnored")
public void postUpload() {
if (DEBUG_SAVE_UPLOADED) {
log.info("Renaming file " + file);
String uploadedDir = folder + File.separator + "uploaded";
new File(uploadedDir).mkdirs();
new File(getFullName()).renameTo(new File(uploadedDir + File.separator + file));
} else {
delete(getFullName());
}
}
}
}

View File

@ -1,67 +0,0 @@
package com.rusefi.ts_plugin;
import com.rusefi.tools.online.UploadResult;
import com.rusefi.core.preferences.storage.PersistentConfiguration;
import org.putgemin.VerticalFlowLayout;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
public class UploadView {
private final JComponent content = new JPanel(new VerticalFlowLayout());
private static final String AUTO_UPLOAD = "AUTO_UPLOAD";
public final JLabel uploadState = new JLabel();
private final JLabel projectWarning = new JLabel(UploaderStatus.NO_PROJECT);
private final JLabel tuneInfo = new JLabel();
private final JCheckBox autoUpload = new JCheckBox("Continuous auto-upload");
public UploadView() {
content.add(projectWarning);
content.add(tuneInfo);
content.add(uploadState);
content.add(autoUpload);
autoUpload.setSelected(isAutoUpload());
autoUpload.addActionListener(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
PersistentConfiguration.getConfig().getRoot().setProperty(AUTO_UPLOAD, autoUpload.isSelected());
PersistentConfiguration.getConfig().save();
}
});
uploadState.setVisible(false);
}
public static boolean isAutoUpload() {
return PersistentConfiguration.getConfig().getRoot().getBoolProperty(AUTO_UPLOAD, false);
}
public static void setResult(UploadResult result, JLabel uploadState) {
uploadState.setText(result.getFirstMessage());
uploadState.setVisible(true);
}
public JComponent getContent() {
return content;
}
public void update(UploaderStatus uploaderStatus) {
if (uploaderStatus.isTuneOk()) {
tuneInfo.setText(uploaderStatus.tuneInfo);
tuneInfo.setForeground(Color.black);
} else {
tuneInfo.setText(uploaderStatus.tuneWarning);
tuneInfo.setForeground(Color.red);
}
if (uploaderStatus.isProjectIsOk()) {
projectWarning.setVisible(false);
} else {
projectWarning.setVisible(true);
projectWarning.setText(uploaderStatus.projectWarning);
}
}
}

View File

@ -1,56 +0,0 @@
package com.rusefi.ts_plugin;
import com.rusefi.TsTuneReader;
import com.rusefi.tune.xml.Constant;
import java.io.File;
import java.util.Map;
public class UploaderStatus {
static final String NO_PROJECT = "Please open project";
public String projectWarning;
public String tuneInfo;
public String tuneWarning;
void updateProjectStatus(String configurationName, boolean isProjectActive) {
if (!isProjectActive) {
this.projectWarning = NO_PROJECT;
} else if (!new File(TsTuneReader.getTsTuneFileName(configurationName)).exists()) {
this.projectWarning = "Tune not found " + configurationName;
} else {
this.projectWarning = null;
}
}
void readTuneState(String configurationName) {
Map<String, Constant> fileSystemValues = TuneUploder.getFileSystemValues(configurationName);
Constant engineMake = fileSystemValues.get("enginemake");
Constant engineCode = fileSystemValues.get("enginecode");
Constant vehicleName = fileSystemValues.get("VEHICLENAME");
String warning = "";
if (PluginEntry.isEmpty(engineMake)) {
warning += " engine make";
}
if (PluginEntry.isEmpty(engineCode)) {
warning += " engine code";
}
if (PluginEntry.isEmpty(vehicleName)) {
warning += " vehicle name";
}
if (warning.isEmpty()) {
tuneInfo = engineMake.getValue() + " " + engineCode.getValue() + " " + vehicleName.getValue();
tuneWarning = null;
} else {
tuneInfo = null;
tuneWarning = "<html>Please set " + warning + " on Base Settings tab<br>and reopen Project";
}
}
public boolean isProjectIsOk() {
return projectWarning == null;
}
public boolean isTuneOk() {
return tuneWarning == null;
}
}

View File

@ -1,44 +0,0 @@
package com.rusefi.ts_plugin.auth;
import com.devexperts.logging.Logging;
import com.rusefi.server.JsonUserDetailsResolver;
import com.rusefi.server.UserDetails;
import com.rusefi.ui.AuthTokenPanel;
import java.util.concurrent.CopyOnWriteArrayList;
public class InstanceAuthContext {
private final static Logging log = Logging.getLogging(InstanceAuthContext.class);
private static boolean isInitialized;
public static CopyOnWriteArrayList<Listener> listeners = new CopyOnWriteArrayList<>();
public static UserDetails self;
public synchronized static void startup() {
if (isInitialized)
return;
if (!AuthTokenPanel.hasToken()) {
// exiting without marking initialized - UI has a chance to re-start the process once token is provided
return;
}
log.info("Startup");
isInitialized = true;
new Thread(() -> {
self = new JsonUserDetailsResolver().apply(AuthTokenPanel.getAuthToken());
for (Listener listener : listeners)
listener.onUserDetails(self);
}).start();
}
public static boolean isOurController(int userId) {
if (self == null)
return false;
return self.getUserId() == userId;
}
public interface Listener {
void onUserDetails(UserDetails userDetails);
}
}

View File

@ -1,33 +0,0 @@
package com.rusefi.ts_plugin.auth;
import com.rusefi.server.UserDetails;
import javax.swing.*;
import java.awt.*;
public class SelfInfo {
private final JLabel jLabel = new JLabel();
public SelfInfo() {
InstanceAuthContext.listeners.add(new InstanceAuthContext.Listener() {
@Override
public void onUserDetails(UserDetails userDetails) {
setInfo();
}
});
setInfo();
}
private void setInfo() {
UserDetails userDetails = InstanceAuthContext.self;
if (userDetails == null) {
jLabel.setText("Authorizing...");
} else {
jLabel.setText("Logged in as " + userDetails.getUserName());
}
}
public Component getContent() {
return jLabel;
}
}

View File

@ -1,49 +0,0 @@
package com.rusefi.ts_plugin.util;
import com.rusefi.ts_plugin.PluginEntry;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.net.URL;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
public class ManifestHelper {
private static final String BUILT_DATE = "Built-Date";
private static final String BUILT_TIMESTAMP = "Built-Timestamp";
@NotNull
public static String getVersion() {
return getAttribute(BUILT_DATE);
}
@NotNull
public static String getBuildTimestamp() {
return getAttribute(BUILT_TIMESTAMP);
}
@NotNull
private static String getAttribute(String attributeName) {
// all this magic below to make sure we are reading manifest of the *our* jar file not TS main jar file
Class clazz = PluginEntry.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
if (!classPath.startsWith("jar")) {
// Class not from JAR
return "Local Run";
}
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
"/META-INF/MANIFEST.MF";
try {
Manifest manifest = new Manifest(new URL(manifestPath).openStream());
Attributes attributes = manifest.getMainAttributes();
String result = attributes.getValue(attributeName);
System.out.println(BUILT_DATE + " " + result);
return result == null ? "Unknown version" : result;
} catch (IOException e) {
e.printStackTrace();
return "Unknown version";
}
}
}

View File

@ -1,14 +0,0 @@
package com.rusefi;
import com.rusefi.core.SignatureHelper;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SignatureHelperTest {
@Test
public void test() {
String url = SignatureHelper.getUrl("rusEFI master.2020.07.06.frankenso_na6.2468827536").first;
assertEquals("https://rusefi.com/online/ini/rusefi/master/2020/07/06/frankenso_na6/2468827536.ini", url);
}
}

View File

@ -1,15 +0,0 @@
package com.rusefi.ts_plugin;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class ConnectPanelTest {
@Test
public void testFileName() {
assertEquals("1234", ConnectPanel.getLastFour("aaa1234.a"));
assertEquals("1234", ConnectPanel.getLastFour("a1234.a"));
assertEquals("123", ConnectPanel.getLastFour("123.a"));
assertEquals("1", ConnectPanel.getLastFour("1.a"));
}
}

View File

@ -1,19 +0,0 @@
package com.rusefi.ts_plugin;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.rusefi.MockitoTestHelper;
import com.rusefi.core.ui.FrameHelper;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
public class LogUploadSelectorSandbox {
public static void main(String[] args) {
String projectName = "mre_f4";
ControllerAccess controllerAccess = mock(ControllerAccess.class, MockitoTestHelper.NEGATIVE_ANSWER);
doReturn(new String[]{projectName}).when(controllerAccess).getEcuConfigurationNames();
new FrameHelper().showFrame(new LogUploadSelector(() -> controllerAccess).getContent());
}
}

View File

@ -1,47 +0,0 @@
package com.rusefi.ts_plugin;
import com.efiAnalytics.plugin.ecu.ControllerAccess;
import com.efiAnalytics.plugin.ecu.ControllerException;
import com.efiAnalytics.plugin.ecu.servers.ControllerParameterServer;
import com.opensr5.ini.IniFileModel;
import com.rusefi.TsTuneReader;
import com.rusefi.core.ui.FrameHelper;
import javax.swing.*;
import java.util.ArrayList;
import java.util.Objects;
import static com.rusefi.MockitoTestHelper.NEGATIVE_ANSWER;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
/**
* Sandbox for plugin body
*
* @see PluginLauncherSandbox for plugin auto-update launcher
*/
public class PluginBodySandbox {
private static final String PROJECT_NAME = "dev";
public static void main(String[] args) throws ControllerException {
String iniFile = TsTuneReader.getProjectModeFileName(PROJECT_NAME);
IniFileModel model = new IniFileModel().readIniFile(iniFile);
Objects.requireNonNull(model, "model");
java.util.List<String> fieldNamesList = new ArrayList<>(model.allIniFields.keySet());
String[] parameterNames = fieldNamesList.toArray(new String[0]);
ControllerParameterServer controllerParameterServer = mock(ControllerParameterServer.class, NEGATIVE_ANSWER);
doReturn(parameterNames).when(controllerParameterServer).getParameterNames(any());
doNothing().when(controllerParameterServer).subscribe(any(), any(), any());
ControllerAccess controllerAccess = mock(ControllerAccess.class, NEGATIVE_ANSWER);
doReturn(new String[]{PROJECT_NAME}).when(controllerAccess).getEcuConfigurationNames();
doReturn(controllerParameterServer).when(controllerAccess).getControllerParameterServer();
FrameHelper frameHelper = new FrameHelper();
frameHelper.getFrame().setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE);
frameHelper.showFrame(new PluginEntry(() -> controllerAccess).getContent());
}
}

View File

@ -1,12 +0,0 @@
package com.rusefi.ts_plugin;
import com.rusefi.core.ui.FrameHelper;
/**
* @see PluginBodySandbox
*/
public class RemoteTabSandbox {
public static void main(String[] args) {
new FrameHelper().showFrame(new RemoteTab().getContent());
}
}

View File

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="inifile" />
<orderEntry type="module" module-name="shared_ui" />
<orderEntry type="library" name="TunerStudioPluginAPI" level="project" />
<orderEntry type="module" module-name="ts_plugin_launcher" />
<orderEntry type="library" name="org.mockito:mockito-all:1.10.19" level="project" />
<orderEntry type="library" name="json-simple" level="project" />
<orderEntry type="library" name="httpclient" level="project" />
<orderEntry type="module" module-name="models" />
<orderEntry type="module" module-name="io" />
</component>
</module>