package com.rusefi.binaryprotocol; import com.devexperts.logging.Logging; import com.opensr5.ConfigurationImage; import com.opensr5.io.ConfigurationImageFile; import com.opensr5.io.DataListener; import com.rusefi.ConfigurationImageDiff; import com.rusefi.NamedThreadFactory; import com.rusefi.core.SignatureHelper; import com.rusefi.Timeouts; import com.rusefi.binaryprotocol.test.Bug3923; import com.rusefi.config.generated.Fields; import com.rusefi.core.Pair; import com.rusefi.core.SensorCentral; import com.rusefi.io.*; import com.rusefi.io.commands.GetOutputsCommand; import com.rusefi.io.commands.HelloCommand; import com.rusefi.core.FileUtil; import com.rusefi.tune.xml.Msq; import com.rusefi.ui.livedocs.LiveDocsRegistry; import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.concurrent.*; import static com.devexperts.logging.Logging.getLogging; import static com.rusefi.binaryprotocol.IoHelper.*; import static com.rusefi.config.generated.Fields.*; /** * This object represents logical state of physical connection. *
* Instance is connected until we experience issues. Once we decide to close the connection there is no restart - * new instance of this class would need to be created once we establish a new physical connection. *
* Andrey Belomutskiy, (c) 2013-2020
* 3/6/2015
*/
public class BinaryProtocol {
private static final Logging log = getLogging(BinaryProtocol.class);
private static final ThreadFactory THREAD_FACTORY = new NamedThreadFactory("text pull");
private static final String USE_PLAIN_PROTOCOL_PROPERTY = "protocol.plain";
private static final String CONFIGURATION_RUSEFI_BINARY = "current_configuration.rusefi_binary";
private static final String CONFIGURATION_RUSEFI_XML = "current_configuration.msq";
/**
* This properly allows to switch to non-CRC32 mode
* todo: finish this feature, assuming we even need it.
*/
public static boolean PLAIN_PROTOCOL = Boolean.getBoolean(USE_PLAIN_PROTOCOL_PROPERTY);
private final LinkManager linkManager;
private final IoStream stream;
private final IncomingDataBuffer incomingData;
private boolean isBurnPending;
public String signature;
public boolean isGoodOutputChannels;
private final BinaryProtocolState state = new BinaryProtocolState();
// todo: this ioLock needs better documentation!
private final Object ioLock = new Object();
BinaryProtocolLogger binaryProtocolLogger;
public static boolean DISABLE_LOCAL_CACHE;
public static String findCommand(byte command) {
switch (command) {
case Fields.TS_PAGE_COMMAND:
return "PAGE";
case Fields.TS_COMMAND_F:
return "PROTOCOL";
case Fields.TS_CRC_CHECK_COMMAND:
return "CRC_CHECK";
case Fields.TS_BURN_COMMAND:
return "BURN";
case Fields.TS_HELLO_COMMAND:
return "HELLO";
case Fields.TS_READ_COMMAND:
return "READ";
case Fields.TS_GET_TEXT:
return "TS_GET_TEXT";
case Fields.TS_GET_FIRMWARE_VERSION:
return "GET_FW_VERSION";
case Fields.TS_CHUNK_WRITE_COMMAND:
return "WRITE_CHUNK";
case Fields.TS_OUTPUT_COMMAND:
return "TS_OUTPUT_COMMAND";
case Fields.TS_RESPONSE_OK:
return "TS_RESPONSE_OK";
default:
return "command " + (char) command + "/" + command;
}
}
public IoStream getStream() {
return stream;
}
public boolean isClosed;
public CommunicationLoggingListener communicationLoggingListener;
public BinaryProtocol(LinkManager linkManager, IoStream stream) {
this.linkManager = linkManager;
this.stream = stream;
communicationLoggingListener = linkManager.messageListener::postMessage;
incomingData = stream.getDataBuffer();
binaryProtocolLogger = new BinaryProtocolLogger(linkManager);
binaryProtocolLogger.needCompositeLogger = linkManager.getCompositeLogicEnabled();
}
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
public void doSend(final String command, boolean fireEvent) throws InterruptedException {
log.info("Sending [" + command + "]");
if (fireEvent && LinkManager.LOG_LEVEL.isDebugEnabled()) {
communicationLoggingListener.onPortHolderMessage(BinaryProtocol.class, "Sending [" + command + "]");
}
Future f = linkManager.submit(new Runnable() {
@Override
public void run() {
sendTextCommand(command);
}
@Override
public String toString() {
return "Runnable for " + command;
}
});
try {
f.get(Timeouts.COMMAND_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw new IllegalStateException(e);
} catch (TimeoutException e) {
log.error("timeout sending [" + command + "] giving up: " + e);
return;
}
/**
* this here to make CommandQueue happy
*/
linkManager.getCommandQueue().handleConfirmationMessage(CommandQueue.CONFIRMATION_PREFIX + command);
}
public static String getSignature(IoStream stream) throws IOException {
HelloCommand.send(stream);
return HelloCommand.getHelloResponse(stream.getDataBuffer());
}
/**
* this method reads configuration snapshot from controller
*
* @return true if everything fine
*/
public String connectAndReadConfiguration(Arguments arguments, DataListener listener) {
try {
signature = getSignature(stream);
log.info("Got " + signature + " signature");
SignatureHelper.downloadIfNotAvailable(SignatureHelper.getUrl(signature));
} catch (IOException e) {
return "Failed to read signature " + e;
}
String errorMessage = validateConfigVersion();
if (errorMessage != null)
return errorMessage;
readImage(arguments, Fields.TOTAL_CONFIG_SIZE);
if (isClosed)
return "Failed to read calibration";
startPullThread(listener);
binaryProtocolLogger.start();
return null;
}
/**
* @return null if everything is good, error message otherwise
*/
private String validateConfigVersion() {
int requestSize = 4;
byte[] packet = GetOutputsCommand.createRequest(TS_FILE_VERSION_OFFSET, requestSize);
String msg = "load TS_CONFIG_VERSION";
byte[] response = executeCommand(Fields.TS_OUTPUT_COMMAND, packet, msg);
if (!checkResponseCode(response, (byte) Fields.TS_RESPONSE_OK) || response.length != requestSize + 1) {
close();
return "Failed to " + msg;
}
int actualVersion = FileUtil.littleEndianWrap(response, 1, requestSize).getInt();
if (actualVersion != TS_FILE_VERSION) {
log.error("Got TS_CONFIG_VERSION " + actualVersion);
return "Incompatible firmware format=" + actualVersion + " while format " + TS_FILE_VERSION + " expected";
}
return null;
}
private void startPullThread(final DataListener textListener) {
if (!linkManager.COMMUNICATION_QUEUE.isEmpty()) {
log.info("Current queue size: " + linkManager.COMMUNICATION_QUEUE.size());
}
Runnable textPull = new Runnable() {
@Override
public void run() {
while (!isClosed) {
// FileLog.rlog("queue: " + LinkManager.COMMUNICATION_QUEUE.toString());
if (linkManager.COMMUNICATION_QUEUE.isEmpty() && linkManager.getNeedPullData()) {
linkManager.submit(new Runnable() {
@Override
public void run() {
isGoodOutputChannels = requestOutputChannels();
if (isGoodOutputChannels)
HeartBeatListeners.onDataArrived();
binaryProtocolLogger.compositeLogic(BinaryProtocol.this);
if (linkManager.isNeedPullText()) {
String text = requestPendingMessages();
if (text != null)
textListener.onDataArrived((text + "\r\n").getBytes());
}
if (linkManager.isNeedPullLiveData()) {
LiveDocsRegistry.LiveDataProvider liveDataProvider = LiveDocsRegistry.getLiveDataProvider();
LiveDocsRegistry.INSTANCE.refresh(liveDataProvider);
}
}
});
}
sleep(Timeouts.TEXT_PULL_PERIOD);
}
log.info("Port shutdown: Stopping text pull");
}
};
Thread tr = THREAD_FACTORY.newThread(textPull);
tr.start();
}
private void dropPending() {
synchronized (ioLock) {
if (isClosed)
return;
incomingData.dropPending();
}
}
public void uploadChanges(ConfigurationImage newVersion) {
ConfigurationImage current = getControllerConfiguration();
// let's have our own copy which no one would be able to change
newVersion = newVersion.clone();
int offset = 0;
while (offset < current.getSize()) {
Pair