2022-06-21 23:05:04 -07:00
|
|
|
package com.rusefi.sensor_logs;
|
|
|
|
|
|
|
|
import java.io.*;
|
|
|
|
import java.util.*;
|
|
|
|
import java.util.function.Function;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* MLV .mlq binary log file
|
2022-08-16 21:22:45 -07:00
|
|
|
* https://www.efianalytics.com/TunerStudio/docs/MLG_Binary_LogFormat_1.0.pdf
|
|
|
|
* https://www.efianalytics.com/TunerStudio/docs/MLG_Binary_LogFormat_2.0.pdf
|
|
|
|
*
|
2022-06-21 23:05:04 -07:00
|
|
|
* </p>
|
|
|
|
* Andrey Belomutskiy, (c) 2013-2020
|
|
|
|
*/
|
2022-06-26 09:54:11 -07:00
|
|
|
public class BinarySensorLog<T extends BinaryLogEntry> implements SensorLog, AutoCloseable {
|
2022-06-21 23:05:04 -07:00
|
|
|
private final Function<T, Double> valueProvider;
|
|
|
|
private final Collection<T> entries;
|
2022-06-22 00:19:09 -07:00
|
|
|
private final TimeProvider timeProvider;
|
2022-06-21 23:05:04 -07:00
|
|
|
private DataOutputStream stream;
|
|
|
|
|
2022-11-12 17:59:47 -08:00
|
|
|
private final String outputFileName;
|
2022-06-21 23:05:04 -07:00
|
|
|
|
2022-11-12 17:59:47 -08:00
|
|
|
private int lineCounter;
|
2022-06-21 23:05:04 -07:00
|
|
|
|
2022-11-12 17:59:47 -08:00
|
|
|
public BinarySensorLog(Function<T, Double> valueProvider, Collection<T> sensors, TimeProvider timeProvider, String outputFileName) {
|
2022-06-22 00:19:09 -07:00
|
|
|
this.valueProvider = Objects.requireNonNull(valueProvider, "valueProvider");
|
2022-06-21 23:05:04 -07:00
|
|
|
this.entries = Objects.requireNonNull(sensors, "entries");
|
2022-06-22 00:19:09 -07:00
|
|
|
this.timeProvider = timeProvider;
|
2022-11-12 17:59:47 -08:00
|
|
|
this.outputFileName = outputFileName;
|
2022-06-22 00:19:09 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
public interface TimeProvider {
|
|
|
|
long currentTimestamp();
|
2022-06-21 23:05:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public double getSecondsSinceFileStart() {
|
|
|
|
throw new UnsupportedOperationException();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void writeSensorLogLine() {
|
|
|
|
if (stream == null) {
|
2023-07-30 21:01:10 -07:00
|
|
|
System.out.println(getClass().getSimpleName() + ": Writing to " + outputFileName);
|
2022-06-21 23:05:04 -07:00
|
|
|
|
|
|
|
try {
|
2022-11-12 17:59:47 -08:00
|
|
|
stream = new DataOutputStream(new FileOutputStream(outputFileName));
|
2022-06-21 23:05:04 -07:00
|
|
|
writeHeader();
|
|
|
|
} catch (Throwable e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
stream = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream != null) {
|
|
|
|
try {
|
|
|
|
stream.write(0);
|
2022-11-12 17:59:47 -08:00
|
|
|
stream.write(lineCounter++);
|
2022-06-22 00:19:09 -07:00
|
|
|
stream.writeShort((int) (timeProvider.currentTimestamp() * 100));
|
2022-06-21 23:05:04 -07:00
|
|
|
|
|
|
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
|
|
|
DataOutputStream dos = new DataOutputStream(baos);
|
|
|
|
|
|
|
|
for (T sensor : entries) {
|
2022-06-22 00:19:09 -07:00
|
|
|
Double value = valueProvider.apply(sensor);
|
|
|
|
if (value == null)
|
|
|
|
throw new NullPointerException("No value for " + sensor);
|
2022-06-21 23:05:04 -07:00
|
|
|
sensor.writeToLog(dos, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
byte[] byteArray = baos.toByteArray();
|
|
|
|
byte checkSum = 0;
|
|
|
|
for (byte b : byteArray) {
|
|
|
|
checkSum += b;
|
|
|
|
}
|
|
|
|
stream.write(byteArray);
|
|
|
|
stream.write(checkSum);
|
|
|
|
|
2022-11-12 17:59:47 -08:00
|
|
|
if (lineCounter % 20 == 0) {
|
2022-06-21 23:05:04 -07:00
|
|
|
// for not flush on each block of data but still flush
|
|
|
|
stream.flush();
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void writeHeader() throws IOException {
|
|
|
|
String headerText = "\"rusEFI " + "sdsr" + "\"\n" +
|
|
|
|
"\"Capture Date: " + new Date() + "\"\n";
|
|
|
|
|
|
|
|
for (char c : "MLVLG\0".toCharArray()) {
|
|
|
|
stream.write(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
int fieldsDataSize = 0;
|
|
|
|
for (BinaryLogEntry entry : entries) {
|
|
|
|
fieldsDataSize += entry.getByteSize();
|
|
|
|
}
|
|
|
|
|
2022-08-16 21:53:57 -07:00
|
|
|
// 0006h Format version = 02
|
|
|
|
stream.writeShort(2);
|
2022-06-21 23:05:04 -07:00
|
|
|
// 0008h Timestamp
|
|
|
|
stream.writeInt((int) (System.currentTimeMillis() / 1000));
|
|
|
|
// 000ch
|
2022-08-14 09:06:50 -07:00
|
|
|
int infoDataStart = Fields.MLQ_HEADER_SIZE + Fields.MLQ_FIELD_HEADER_SIZE * entries.size();
|
2022-07-30 13:44:29 -07:00
|
|
|
System.out.println("Total " + entries.size() + " fields");
|
2022-08-16 21:53:57 -07:00
|
|
|
stream.writeInt(infoDataStart);
|
|
|
|
stream.writeInt(infoDataStart + headerText.length());
|
2022-06-21 23:05:04 -07:00
|
|
|
// 0012h
|
|
|
|
stream.writeShort(fieldsDataSize);
|
|
|
|
// 0014h number of fields
|
|
|
|
stream.writeShort(entries.size());
|
|
|
|
|
|
|
|
for (BinaryLogEntry sensor : entries) {
|
|
|
|
String name = sensor.getName();
|
|
|
|
String unit = sensor.getUnit();
|
|
|
|
|
2022-08-16 21:22:45 -07:00
|
|
|
// 0000h type enum
|
|
|
|
stream.write(7);
|
2022-06-21 23:05:04 -07:00
|
|
|
// 0001h
|
2024-01-12 22:08:24 -08:00
|
|
|
writeLine(stream, name, Fields.MLQ_FIELD_HEADER_NAME_OR_CATEGORY);
|
2022-06-21 23:05:04 -07:00
|
|
|
// 0023h
|
2022-08-16 21:22:45 -07:00
|
|
|
writeLine(stream, unit, 10);
|
|
|
|
stream.write(0); // Display Style, 0=Float
|
|
|
|
// 002Eh 46 scale
|
|
|
|
stream.writeFloat(1);
|
|
|
|
// 0032h 50 transform
|
|
|
|
stream.writeFloat(0);
|
|
|
|
// 0036h precision digits
|
2022-06-21 23:05:04 -07:00
|
|
|
stream.write(2);
|
2024-01-12 22:08:24 -08:00
|
|
|
writeLine(stream, sensor.getCategory(), Fields.MLQ_FIELD_HEADER_NAME_OR_CATEGORY);
|
2022-06-21 23:05:04 -07:00
|
|
|
}
|
2022-08-14 09:06:50 -07:00
|
|
|
if (stream.size() != infoDataStart)
|
2022-08-16 21:53:57 -07:00
|
|
|
throw new IllegalStateException("We are doing something wrong :( stream.size=" + stream.size() + "/" + infoDataStart);
|
2022-06-21 23:05:04 -07:00
|
|
|
writeLine(stream, headerText, headerText.length());
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void close() {
|
2022-11-12 17:59:47 -08:00
|
|
|
System.out.println("Finishing " + outputFileName);
|
2022-06-21 23:05:04 -07:00
|
|
|
close(stream);
|
|
|
|
stream = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void close(Closeable closeable) {
|
|
|
|
try {
|
|
|
|
if (closeable != null) {
|
|
|
|
closeable.close();
|
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
|
|
|
// ignoring
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-12 17:59:47 -08:00
|
|
|
public String getOutputFileName() {
|
|
|
|
return outputFileName;
|
2022-06-21 23:05:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
private void writeLine(DataOutputStream stream, String name, int length) throws IOException {
|
|
|
|
for (int i = 0; i < Math.min(name.length(), length); i++) {
|
|
|
|
stream.write(name.charAt(i));
|
|
|
|
}
|
|
|
|
for (int i = name.length(); i < length; i++)
|
|
|
|
stream.write(0);
|
|
|
|
}
|
|
|
|
}
|