using HC_APTBS.Infrastructure.Kwp.Packets; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace HC_APTBS.Infrastructure.Kwp { /// /// Manages a dialog with a VW controller using the KW1281 protocol. /// public interface IKW1281Connection { ControllerInfo ReadEcuInfo(); void EndCommunication(); List ReadIdent(); List ReadEeprom(ushort address, byte count); List ReadRomEeprom(ushort address, byte count); void CustomReset(); void SendPacket(List packetBytes); List SendCustom(List packetCustomBytes); /// /// Keep the dialog alive by sending an ACK and receiving a response. /// void KeepAlive(); List ReadFaultCodes(); /// /// Clear all of the controllers fault codes. /// /// True if successful. bool ClearFaultCodes(); /// /// Set the controller's software coding and workshop code. /// /// /// /// /// True if successful. bool SetSoftwareCoding(int controllerAddress, int softwareCoding, int workshopCode); IKwpCommon KwpCommon { get; } } public class KW1281Connection : IKW1281Connection { public ControllerInfo ReadEcuInfo() { var packets = ReceivePackets(); return new ControllerInfo(packets.Where(b => !b.IsAckNak)); } public ControllerInfo ReadEcuInfoCustom(Int32 pkt_count) { var packets = new List(); for(var i=0;i !b.IsAckNak)); } public List ReadIdent() { var idents = new List(); bool moreAvailable; do { System.Diagnostics.Debug.WriteLine("Sending ReadIdent packet"); SendPacket(new List { (byte)PacketCommand.ReadIdent }); var packets = ReceivePackets(); var ident = new ControllerIdent(packets.Where(b => !b.IsAckNak)); idents.Add(ident); moreAvailable = packets .OfType() .Any(b => b.MoreDataAvailable); } while (moreAvailable); return idents; } /// /// Reads a range of bytes from the EEPROM. /// /// /// /// The bytes or null if the bytes could not be read public List ReadEeprom(ushort address, byte count) { System.Diagnostics.Debug.WriteLine($"Sending ReadEeprom packet (Address: ${address:X4}, Count: ${count:X2})"); SendPacket(new List { (byte)PacketCommand.ReadEeprom, count, (byte)(address >> 8), (byte)(address & 0xFF) }); var packets = ReceivePackets(); if (packets.Count == 1 && packets[0] is NakPacket) { // Permissions issue return null; } packets = packets.Where(b => !b.IsAckNak).ToList(); if (packets.Count != 1) { throw new InvalidOperationException($"ReadEeprom returned {packets.Count} blocks instead of 1"); } return packets[0].Body.ToList(); } public List ReadRomEeprom(ushort address, byte count) { //System.Diagnostics.Debug.WriteLine($"Sending ReadRomEeprom packet (Address: ${address:X4}, Count: ${count:X2})"); SendPacket(new List { (byte)PacketCommand.ReadRomEeprom, count, (byte)(address >> 8), (byte)(address & 0xFF) }); var packets = ReceivePackets(); if (packets.Count == 1 && packets[0] is NakPacket) { return new List(); } packets = packets.Where(b => !b.IsAckNak).ToList(); if (packets.Count != 1) { throw new InvalidOperationException($"ReadRomEeprom returned {packets.Count} blocks instead of 1"); } return packets[0].Body.ToList(); } public Dictionary CustomReadSoftwareVersion() { var versionPackets = new Dictionary(); System.Diagnostics.Debug.WriteLine("Sending Custom \"Read Software Version\" packets"); // The cluster can return 4 variations of software version, specified by the 2nd byte // of the packet: // 0x00 - Cluster software version // 0x01 - Unknown // 0x02 - Unknown // 0x03 - Unknown for (byte variation = 0x00; variation < 0x04; variation++) { var packets = SendCustom(new List { 0x84, variation }); foreach (var packet in packets.Where(b => !b.IsAckNak)) { if (variation == 0x00 || variation == 0x03) { System.Diagnostics.Debug.WriteLine($"{variation:X2}: {DumpMixedContent(packet)}"); } else { System.Diagnostics.Debug.WriteLine($"{variation:X2}: {DumpBinaryContent(packet)}"); } versionPackets[variation] = packet; } } return versionPackets; } private static string DumpMixedContent(Packet packet) { if (packet.IsNak) { return "NAK"; } return DumpMixedContent(packet.Body); } /// /// Todo: Move to utility class /// public static string DumpMixedContent(IEnumerable content) { char mode = '?'; var sb = new StringBuilder(); foreach (var b in content) { if (b >= 32 && b <= 126) { mode = 'A'; sb.Append((char)b); } else { if (mode == 'A') { sb.Append(' '); } mode = 'X'; sb.Append($"${b:X2} "); } } return sb.ToString(); } private static string DumpBinaryContent(Packet packet) { if (packet.IsNak) { return "NAK"; } return DumpBytes(packet.Body); } private static string DumpBytes(IEnumerable bytes) { var sb = new StringBuilder(); foreach (var b in bytes) { sb.Append($"${b:X2} "); } return sb.ToString(); } public void CustomReset() { System.Diagnostics.Debug.WriteLine("Sending Custom Reset packet"); SendCustom(new List { 0x82 }); } public List SendCustom(List packetCustomBytes) { SendPacket(packetCustomBytes); return ReceivePackets(); } public void EndCommunication() { System.Diagnostics.Debug.WriteLine("Sending EndCommunication packet"); SendPacket(new List { (byte)PacketCommand.End }); } public void SendPacket(List packetBytes) { var packetLength = (byte)(packetBytes.Count + 2); packetBytes.Insert(0, _packetCounter.Value); _packetCounter++; packetBytes.Insert(0, packetLength); foreach (var b in packetBytes) { WriteByteAndReadAck(b); Thread.Sleep(5); } KwpCommon.WriteByte(0x03); // Packet end, does not get ACK'd } private List ReceivePackets() { var packets = new List(); while (true) { var packet = ReceivePacket(); packets.Add(packet); // TODO: Maybe don't add the packet if it's an Ack //SendAckPacket(); if (packet is AckPacket || packet is NakPacket) { break; } SendAckPacket(); } return packets; } private void WriteByteAndReadAck(byte b) { KwpCommon.WriteByte(b); KwpCommon.ReadComplement(b); } private Packet ReceivePacket() { var packetBytes = new List(); var packetLength = KwpCommon.ReadAndAckByte(); packetBytes.Add(packetLength); var packetCounter = ReadPacketCounter(); packetBytes.Add(packetCounter); var packetCommand = KwpCommon.ReadAndAckByte(); packetBytes.Add(packetCommand); for (int i = 0; i < packetLength - 3; i++) { var b = KwpCommon.ReadAndAckByte(); packetBytes.Add(b); } var packetEnd = KwpCommon.ReadByte(); packetBytes.Add(packetEnd); if (packetEnd != 0x03) { throw new InvalidOperationException( $"Received packet end ${packetEnd:X2} but expected $03"); } switch (packetCommand) { case (byte)PacketCommand.ACK: return new AckPacket(packetBytes); case (byte)PacketCommand.AsciiData: if (packetBytes[3] == 0x00) return new CodingWscPacket(packetBytes); return new AsciiDataPacket(packetBytes); case (byte)PacketCommand.ReadEepromResponse: return new ReadEepromResponsePacket(packetBytes); case (byte)PacketCommand.ReadRomEepromResponse: return new ReadRomEepromResponse(packetBytes); case (byte)PacketCommand.Custom: return new CustomPacket(packetBytes); break; case (byte)PacketCommand.NAK: return new NakPacket(packetBytes); case (byte)PacketCommand.FaultCodesResponse: return new FaultCodesPacket(packetBytes); case (byte)PacketCommand.WriteEepromResponse: return new WriteEepromResponsePacket(packetBytes); default: return new UnknownPacket(packetBytes); } } private void SendAckPacket() { var packetBytes = new List { (byte)PacketCommand.ACK }; SendPacket(packetBytes); } private byte ReadPacketCounter() { var packetCounter = KwpCommon.ReadAndAckByte(); if (!_packetCounter.HasValue) { // First packet _packetCounter = packetCounter; } else if (packetCounter != _packetCounter) { throw new InvalidOperationException( $"Received packet counter ${packetCounter:X2} but expected ${_packetCounter:X2}"); } _packetCounter++; return packetCounter; } public void KeepAlive() { SendAckPacket(); var packet = ReceivePacket(); if (!(packet is AckPacket)) { throw new InvalidOperationException( $"Received 0x{packet.Title:X2} packet but expected ACK"); } } public List ReadFaultCodes() { System.Diagnostics.Debug.WriteLine($"Sending ReadFaultCodes packet"); SendPacket(new List { (byte)PacketCommand.FaultCodesRead }); var packets = ReceivePackets(); packets = packets.Where(b => !b.IsAckNak).ToList(); var faultCodes = new List(); var faultCodesData = new List(); foreach (var packet in packets) { if (!(packet is FaultCodesPacket)) { System.Diagnostics.Debug.WriteLine($"Expected FaultCodesPacket but got {packet.GetType()}"); return null; } faultCodesData.AddRange(packet.Body); } IEnumerable data = faultCodesData; while (true) { var code = data.Take(3).ToArray(); if (code.Length == 0) { break; } var dtc = code[0]; var status = code[1]; var faultCode = new FaultCode(dtc, status); if (faultCode.Dtc != FaultCode.None.Dtc) { faultCodes.Add(faultCode); } data = data.Skip(8); } return faultCodes; } public bool ClearFaultCodes() { System.Diagnostics.Debug.WriteLine($"Sending ClearFaultCodes packet"); SendPacket(new List { (byte)PacketCommand.FaultCodesDelete }); var packets = ReceivePackets(); if (packets.Count == 1) { var packet = packets[0]; if (packet is NakPacket) { return false; } else if (packet is AckPacket) { return true; } else { throw new InvalidOperationException($"ClearFaultCodes returned {packet.GetType()} packet instead of ACK/NAK"); } } else { throw new InvalidOperationException($"ClearFaultCodes returned {packets.Count} packets instead of 1"); } } public bool SetSoftwareCoding(int controllerAddress, int softwareCoding, int workshopCode) { // Workshop codes > 65535 overflow into the low bit of the software coding var bytes = new List { (byte)PacketCommand.SoftwareCoding, (byte)((softwareCoding * 2) / 256), (byte)((softwareCoding * 2) % 256), (byte)((workshopCode & 65535) / 256), (byte)(workshopCode % 256) }; if (workshopCode > 65535) { bytes[2]++; } System.Diagnostics.Debug.WriteLine($"Sending SoftwareCoding packet"); SendPacket(bytes); var packets = ReceivePackets(); if (packets.Count == 1 && packets[0] is NakPacket) { return false; } var controllerInfo = new ControllerInfo(packets.Where(b => !b.IsAckNak)); return controllerInfo.SoftwareCoding == softwareCoding && controllerInfo.WorkshopCode == workshopCode; } public IKwpCommon KwpCommon { get; } private byte? _packetCounter = null; public KW1281Connection(IKwpCommon kwpCommon) { KwpCommon = kwpCommon; } } public class KW1281KeepAlive : IDisposable { private readonly IKW1281Connection _kw1281Connection; private volatile bool _cancel = false; private Task _keepAliveTask = null; public KW1281KeepAlive(IKW1281Connection kw1281Connection) { _kw1281Connection = kw1281Connection; } public void Dispose() { Pause(); } private void Pause() { _cancel = true; if (_keepAliveTask != null) { _keepAliveTask.Wait(); } } private void Resume() { _keepAliveTask = Task.Run(KeepAlive); } private void KeepAlive() { _cancel = false; while (!_cancel) { _kw1281Connection.KeepAlive(); Console.Write("."); } } } }