Skip to content

Commit

Permalink
Enhanced GetSKC command to work with (at least some) Audi C5 clusters.
Browse files Browse the repository at this point in the history
  • Loading branch information
gmenounos committed Mar 27, 2024
1 parent 017597d commit 44d1fad
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 6 deletions.
267 changes: 267 additions & 0 deletions Cluster/AudiC5Cluster.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;
using BitFab.KW1281Test.Blocks;

namespace BitFab.KW1281Test.Cluster;

internal class AudiC5Cluster : ICluster
{
public void UnlockForEepromReadWrite()
{
string[] passwords =
[
"loginas9",
"n7KB2Qat",
];

var succeeded = false;
foreach (var password in passwords)
{
Log.WriteLine("Sending custom login block");
var blockBytes = new List<byte>([0x1B, 0x80]); // Custom 0x80
blockBytes.AddRange(Encoding.ASCII.GetBytes(password));
_kw1281Dialog.SendBlock(blockBytes);

var block = _kw1281Dialog.ReceiveBlock();
if (block is NakBlock)
{
continue;
}
else if (block is not AckBlock)
{
throw new InvalidOperationException(
$"Expected ACK block but received: {block}");
}

succeeded = true;
}

if (!succeeded)
{
throw new InvalidOperationException("Unable to login to cluster");
}

var @interface = _kw1281Dialog.KwpCommon.Interface;
@interface.SetBaudRate(19200);
@interface.SetParity(Parity.Even);
@interface.ClearReceiveBuffer();

Thread.Sleep(TimeSpan.FromSeconds(2));
}

public string DumpEeprom(uint? address, uint? length, string? dumpFileName)
{
ArgumentNullException.ThrowIfNull(address);
ArgumentNullException.ThrowIfNull(length);
ArgumentNullException.ThrowIfNull(dumpFileName);

WriteBlock([Constants.Hello]);

var blockBytes = ReadBlock();
Log.WriteLine($"Received block:{Utils.Dump(blockBytes)}");
if (BlockTitle(blockBytes) != Constants.Hello)
{
Log.WriteLine($"Warning: Expected block of type ${Constants.Hello:X2}");
}

string[] passwords =
[
"19xDR8xS",
"vdokombi",
"w10kombi",
"w10serie",
];

var succeeded = false;
foreach (var password in passwords)
{
Log.WriteLine("Sending login request");

blockBytes = [Constants.Login, 0x9D];
blockBytes.AddRange(Encoding.ASCII.GetBytes(password));
WriteBlock(blockBytes);

blockBytes = ReadBlock();
Log.WriteLine($"Received block:{Utils.Dump(blockBytes)}");

if (BlockTitle(blockBytes) == Constants.Ack)
{
succeeded = true;
break;
}
else
{
Log.WriteLine($"Warning: Expected block of type ${Constants.Ack:X2}");
}
}

if (!succeeded)
{
throw new InvalidOperationException("Unable to login to cluster");
}
else
{
Log.WriteLine("Succeeded");
}

Log.WriteLine($"Dumping EEPROM to {dumpFileName}");
DumpEeprom(address.Value, length.Value, maxReadLength: 0x10, dumpFileName);

_kw1281Dialog.SetDisconnected();

return dumpFileName;
}

private void DumpEeprom(
uint startAddr, uint length, byte maxReadLength, string fileName)
{
using var fs = File.Create(fileName, bufferSize: maxReadLength, FileOptions.WriteThrough);

var succeeded = true;
for (var addr = startAddr; addr < startAddr + length; addr += maxReadLength)
{
var readLength = (byte)Math.Min(startAddr + length - addr, maxReadLength);
var blockBytes = ReadEepromByAddress(addr, readLength);

if (blockBytes.Count != readLength)
{
succeeded = false;
blockBytes.AddRange(
Enumerable.Repeat((byte)0, readLength - blockBytes.Count));
}

fs.Write(blockBytes.ToArray(), offset: 0, blockBytes.Count);
fs.Flush();
}

if (!succeeded)
{
Log.WriteLine();
Log.WriteLine("**********************************************************************");
Log.WriteLine("*** Warning: Some bytes could not be read and were replaced with 0 ***");
Log.WriteLine("**********************************************************************");
Log.WriteLine();
}
}

private List<byte> ReadEepromByAddress(uint addr, byte readLength)
{
List<byte> blockBytes =
[
Constants.ReadEeprom,
readLength,
(byte)(addr >> 8),
(byte)(addr & 0xFF)
];
WriteBlock(blockBytes);

blockBytes = ReadBlock();
Log.WriteLine($"Received block:{Utils.Dump(blockBytes)}");

if (BlockTitle(blockBytes) != Constants.ReadEeprom)
{
throw new InvalidOperationException($"Expected block of type ${Constants.ReadEeprom:X2}");
}

var expectedLength = readLength + 4;
var actualLength = blockBytes.Count;
if (blockBytes.Count != expectedLength)
{
Log.WriteLine(
$"Warning: Expected block length ${expectedLength:X2} but length is ${actualLength:X2}");
}

return blockBytes.Skip(3).Take(actualLength - 4).ToList();
}

private static byte BlockTitle(IReadOnlyList<byte> blockBytes)
{
return blockBytes[2];
}

private void WriteBlock(IReadOnlyCollection<byte> bodyBytes)
{
byte checksum = 0x00;

WriteBlockByte(Constants.StartOfBlock);
WriteBlockByte((byte)(bodyBytes.Count + 3)); // Block length
foreach (var bodyByte in bodyBytes)
{
WriteBlockByte(bodyByte);
}

_kw1281Dialog.KwpCommon.WriteByte(checksum);
return;

void WriteBlockByte(byte b)
{
_kw1281Dialog.KwpCommon.WriteByte(b);
checksum ^= b;
}
}

private List<byte> ReadBlock()
{
var blockBytes = new List<byte>();
byte checksum = 0x00;

try
{
var header = ReadByte();
var blockSize = ReadByte();
for (var i = 0; i < blockSize - 2; i++)
{
ReadByte();
}

if (header != Constants.StartOfBlock)
{
throw new InvalidOperationException($"Expected $D1 header byte but got ${header:X2}");
}

if (checksum != 0x00)
{
throw new InvalidOperationException($"Expected $00 block checksum but got ${checksum:X2}");
}
}
catch (Exception e)
{
Log.WriteLine($"Error reading block: {e}");
Log.WriteLine($"Partial block: {Utils.Dump(blockBytes)}");
throw;
}

return blockBytes;

byte ReadByte()
{
var b = _kw1281Dialog.KwpCommon.ReadByte();
checksum ^= b;
blockBytes.Add(b);
return b;
}
}

private static class Constants
{
public const byte StartOfBlock = 0xD1;

public const byte Ack = 0x06;
public const byte Nak = 0x15;
public const byte Hello = 0x49;
public const byte Login = 0x53;
public const byte ReadEeprom = 0x72;
}

private readonly IKW1281Dialog _kw1281Dialog;

public AudiC5Cluster(IKW1281Dialog kw1281Dialog)
{
_kw1281Dialog = kw1281Dialog;
}
}
21 changes: 21 additions & 0 deletions Interface/FtdiInterface.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO.Ports;
using System.Reflection;
using System.Runtime.InteropServices;

Expand Down Expand Up @@ -112,6 +113,26 @@ public void SetBaudRate(int baudRate)
FT.AssertOk(status);
}

public void SetParity(Parity parity)
{
var ftParity = parity switch
{
Parity.None => FT.Parity.None,
Parity.Even => FT.Parity.Even,
Parity.Odd => FT.Parity.Odd,
Parity.Mark => FT.Parity.Mark,
Parity.Space => FT.Parity.Space,
_ => throw new ArgumentException($"Unsupported parity: {parity}", nameof(parity))
};

var status = _ft.SetDataCharacteristics(
_handle,
FT.Bits.Eight,
FT.StopBits.One,
ftParity);
FT.AssertOk(status);
}

public void SetDtr(bool on)
{
FT.Status status;
Expand Down
5 changes: 5 additions & 0 deletions Interface/GenericInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public void SetBaudRate(int baudRate)
_port.BaudRate = baudRate;
}

public void SetParity(Parity parity)
{
_port.Parity = parity;
}

public void SetDtr(bool on)
{
_port.DtrEnable = on;
Expand Down
3 changes: 3 additions & 0 deletions Interface/IInterface.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.IO.Ports;

namespace BitFab.KW1281Test.Interface
{
Expand All @@ -23,6 +24,8 @@ public interface IInterface : IDisposable

void SetBaudRate(int baudRate);

void SetParity(Parity parity);

void SetDtr(bool on);

void SetRts(bool on);
Expand Down
3 changes: 2 additions & 1 deletion KW1281Dialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ internal interface IKW1281Dialog
bool GroupRead(byte groupNumber, bool useBasicSetting = false);

public IKwpCommon KwpCommon { get; }
Block ReceiveBlock();
}

internal class KW1281Dialog : IKW1281Dialog
Expand Down Expand Up @@ -382,7 +383,7 @@ private void WriteByteAndReadAck(byte b)
KwpCommon.ReadComplement(b);
}

private Block ReceiveBlock()
public Block ReceiveBlock()
{
var blockBytes = new List<byte>();

Expand Down
29 changes: 27 additions & 2 deletions Tester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using static BitFab.KW1281Test.Interface.FT;
using BitFab.KW1281Test.Blocks;
using Parity = System.IO.Ports.Parity;

namespace BitFab.KW1281Test
{
Expand Down Expand Up @@ -506,7 +507,31 @@ public void GetSkc()
if (_controllerAddress == (int)ControllerAddress.Cluster)
{
var ecuInfo = Kwp1281Wakeup();
if (ecuInfo.Text.Contains("VDO"))
if (ecuInfo.Text.Contains("4B0920") ||
ecuInfo.Text.Contains("4Z7920"))
{
Log.WriteLine($"Cluster is Audi C5");

var cluster = new AudiC5Cluster(_kwp1281);

cluster.UnlockForEepromReadWrite();
var dumpFileName = cluster.DumpEeprom(0, 0x800, "AudiC5.bin");

var buf = File.ReadAllBytes(dumpFileName);

var skc = Utils.GetShort(buf, 0x7E2);
var skc2 = Utils.GetShort(buf, 0x7E4);
var skc3 = Utils.GetShort(buf, 0x7E6);
if (skc != skc2 || skc != skc3)
{
Log.WriteLine($"Warning: redundant SKCs do not match: {skc:D5} {skc2:D5} {skc3:D5}");
}
else
{
Log.WriteLine($"SKC: {skc:D5}");
}
}
else if (ecuInfo.Text.Contains("VDO"))
{
var cluster = new VdoCluster(_kwp1281);
string[] partNumberGroups = FindAndParsePartNumber(ecuInfo.Text);
Expand Down
Loading

0 comments on commit 44d1fad

Please sign in to comment.