Skip to content

Commit

Permalink
Add support for CAN logging (#342)
Browse files Browse the repository at this point in the history
There are a couple of UI changes that I think most people will appreciate. First, the "start/stop recording" button is much bigger, so it's easier to press it with your thumb while you're driving. :) Second, all of the parameters are now always displayed on the left side of the window.

And, since I have a couple of CAN devices in my Corvette, so I added support for CAN logging. Currently, only one CAN interface is supported. But, it's cheap:

https://www.seeedstudio.com/USB-CAN-Analyzer-p-2888.html

If you need AFR in your logs, I recommend AEM's X-Series gauges, which support support CAN, so you don't need to wire them into your PCM.

There are also a few new log parameters for the 7603 operating system, but I have to admit that not all of them are reliable.

There are also a couple of tiny improvments to PCM Hammer. The long pause before "Kernel upload 48%" always makes my heart skip a beat, so now it prints "Permission to upload granted" before it start the upload. Also, it logs the VPW messages for OBDX devices.
  • Loading branch information
LegacyNsfw authored May 29, 2024
1 parent c1d244b commit 45232a2
Show file tree
Hide file tree
Showing 49 changed files with 2,248 additions and 282 deletions.
10 changes: 10 additions & 0 deletions Apps/PcmHammer/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,16 @@ private async void readPropertiesButton_Click(object sender, EventArgs e)
{
this.AddUserMessage("MEC query failed: " + mecResponse.Status.ToString());
}

var voltageResponse = await this.Vehicle.QueryVoltage();
if (voltageResponse.Status == ResponseStatus.Success)
{
this.AddUserMessage("Voltage: " + voltageResponse.Value.ToString());
}
else
{
this.AddUserMessage("Voltage query failed: " + voltageResponse.Status.ToString());
}
}
catch (Exception exception)
{
Expand Down
1 change: 1 addition & 0 deletions Apps/PcmLibrary/Devices/Device.cs
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ protected void Enqueue(Message message)
{
lock (this.queue)
{
this.Logger.AddDebugMessage("Received: " + message.ToString());
this.queue.Enqueue(message);
}
}
Expand Down
2 changes: 1 addition & 1 deletion Apps/PcmLibrary/Devices/OBDXProDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ async private Task<Response<Message>> ReadDVIPacket(int timeout = 0)
this.Enqueue(new Message(StrippedFrame, timestampmicro, 0));

// This can be useful for debugging, but is generally too noisy.
this.Logger.AddDebugMessage("RX: " + StrippedFrame.ToHex());
// this.Logger.AddDebugMessage("RX: " + StrippedFrame.ToHex());
return null;
}
else if (receive[0] == 0x7F)
Expand Down
297 changes: 297 additions & 0 deletions Apps/PcmLibrary/Logging/CanLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
using PcmHacking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PcmHacking
{
public class CanLogger : IDisposable
{
public class ParameterValue
{
public string Name { get; set; }
public string Units { get; set; }
public string Value { get; set; }

public override string ToString()
{
return this.Name;
}
}

private IPort canPort;
private CanParser parser = new CanParser();
List<UInt32> keySnapshot = new List<UInt32>();
ParameterDatabase parameterDatabase;

// Note that this is accessed by multiple threads, so it must only be used within "lock(messages)"
Dictionary<UInt32, ParameterValue> messages = new Dictionary<uint, ParameterValue>();

public CanLogger(ParameterDatabase parameterDatabase)
{
this.parameterDatabase = parameterDatabase;
}

public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.canPort?.Dispose();
}
}

public async Task SetPort(IPort port)
{
this.canPort?.Dispose();
this.canPort = port;

// Remove all known messages
this.keySnapshot.Clear();

lock (this.messages)
{
this.messages.Clear();
}

if (this.canPort != null)
{
SerialPortConfiguration configuration = new SerialPortConfiguration();
configuration.BaudRate = 2000000;
configuration.DataReceived = this.DataReceived;
await this.canPort.OpenAsync(configuration);

// Discover what messages are available on the bus.
Thread.Sleep(1500);
lock (this.messages)
{
foreach (UInt32 key in this.messages.Keys)
{
this.keySnapshot.Add(key);
}
}

this.keySnapshot.Sort();
}
}

public void DataReceived(byte[] buffer, int bytesReceived)
{
for(int i = 0; i < bytesReceived; i++)
{
CanMessage message;
if (this.parser.IsCompleteMessage(buffer[i], out message))
{
ParameterValue pv = this.TranslateValue(message);
if (pv != null)
{
lock (this.messages)
{
this.messages[message.MessageId] = pv;
}
}
}
}
}

private ParameterValue TranslateValue(CanMessage message)
{
IReadOnlyDictionary<UInt32, IEnumerable<CanParameter>> canParameters = this.parameterDatabase.GetCanParameters();
IEnumerable<CanParameter> parameters;
ParameterValue result = new ParameterValue();
double rawValue = 0;

if (!canParameters.TryGetValue(message.MessageId, out parameters))
{
if (message.Payload.Length >= 2)
{
rawValue = (message.Payload[0] << 8) | message.Payload[1];
result.Value = rawValue.ToString();
result.Units = "raw";
result.Name = this.messageId.ToString("X8");
}
else
{
result.Value = "Unknown";
result.Units = "";
result.Name = message.MessageId.ToString("X8");
}
}
else
{
foreach(CanParameter parameter in parameters)
{
switch(parameter.ByteCount)
{
case 0:
rawValue = 1; // TODO: this should probably increment with each new message.
result.Units = "";
result.Name = parameter.Name;
break;

case 1:
rawValue = message.Payload[(int)parameter.ByteIndex];
break;

case 2:
if (parameter.HighByteFirst)
{
rawValue = (message.Payload[(int)parameter.ByteIndex] << 8)
+ message.Payload[(int)parameter.ByteIndex + 1];
}
else
{
rawValue = (message.Payload[(int)parameter.ByteIndex + 1] << 8)
+ message.Payload[(int)parameter.ByteIndex];
}
break;

case 3:
if (parameter.HighByteFirst)
{
rawValue = (message.Payload[(int)parameter.ByteIndex] << 16)
+ (message.Payload[(int)parameter.ByteIndex + 1] << 8)
+ message.Payload[(int)parameter.ByteIndex + 2];
}
else
{
rawValue = (message.Payload[(int)parameter.ByteIndex + 2] << 16)
+ (message.Payload[(int)parameter.ByteIndex + 1] << 8)
+ message.Payload[(int)parameter.ByteIndex];
}
break;

case 4:
if (parameter.HighByteFirst)
{
rawValue = (message.Payload[(int)parameter.ByteIndex] << 24) +
+ (message.Payload[(int)parameter.ByteIndex + 1] << 16) +
+ (message.Payload[(int)parameter.ByteIndex + 2] << 8) +
+ message.Payload[(int)parameter.ByteIndex + 3];
}
else
{
rawValue = (message.Payload[(int)parameter.ByteIndex + 3] << 24) +
+ (message.Payload[(int)parameter.ByteIndex + 2] << 16) +
+ (message.Payload[(int)parameter.ByteIndex + 1] << 8) +
+ message.Payload[(int)parameter.ByteIndex];
}
break;
}

Conversion conversion = parameter.SelectedConversion ?? parameter.Conversions.First();
double convertedValue = 0;
string formattedValue;
ValueConverter.Convert(rawValue, parameter.Name, conversion, out convertedValue, out formattedValue);

result.Value = formattedValue;
result.Units = conversion.Units;
result.Name = parameter.Name;
}
}

return result;

}

private ParameterValue Deprecated(CanMessage message)
{
ParameterValue result = new ParameterValue();
int valueRaw = 0;
double value;
switch (message.MessageId)
{
case (uint)0x000a0301:
valueRaw = (this.messageData[0] << 8) | this.messageData[1];
value = valueRaw;
value = value * 0.01; // bar
value = value * 14.5037738; // psi
result.Value = ((int)value).ToString("0.00");
result.Units = "F";
result.Name = "AEM Pressue";
return result;

case (uint)0x000a0302:
valueRaw = (message.Payload[0] << 8) | message.Payload[1];
value = valueRaw;
value = (value * 1.8) + 32.0;
result.Value = ((int)value).ToString("0.00");
result.Units = "F";
result.Name = "AEM Temperature";
return result;

case (uint)0x00000180:
valueRaw = (message.Payload[0] << 8) | message.Payload[1];
value = valueRaw;
value = (value * 0.0001) * 14.7;
result.Value = value.ToString("0.00");
result.Units = "AFR";
result.Name = "AEM AFR 1";
return result;

case (uint)0x00000181:
valueRaw = (message.Payload[0] << 8) | message.Payload[1];
value = valueRaw;
value = (value * 0.0001) * 14.7;
result.Value = value.ToString("0.00");
result.Units = "AFR";
result.Name = "AEM AFR 2";
return result;

default:
if (message.Payload.Length >= 2)
{
valueRaw = (message.Payload[0] << 8) | message.Payload[1];
result.Value = valueRaw.ToString();
result.Units = "raw";
result.Name = this.messageId.ToString("X8");
}
else
{
result.Value = "";
result.Units = "";
result.Name = "Empty";
}
return result;
}
}

public IEnumerable<string> GetParameterNames()
{
foreach (UInt32 key in this.keySnapshot)
{
string name;
lock(this.messages)
{
name = this.messages[key].Name + "(" + this.messages[key].Units + ")";
}
yield return name;
}
}

public IEnumerable<ParameterValue> GetParameterValues()
{
foreach(UInt32 key in this.keySnapshot)
{
ParameterValue value;
lock(this.messages)
{
value = this.messages[key];
}
yield return value;
}
}

UInt32 messageId = 0;
byte[] messageData = new byte[8];

}
}
Loading

0 comments on commit 45232a2

Please sign in to comment.