Showing preview only (314K chars total). Download the full file or copy to clipboard to get everything.
Repository: gmenounos/kw1281test
Branch: master
Commit: d091a1cf0624
Files: 67
Total size: 295.5 KB
Directory structure:
gitextract_0ekb2w7j/
├── .gitattributes
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .vscode/
│ └── launch.json
├── BlockTitle.cs
├── Blocks/
│ ├── AckBlock.cs
│ ├── ActuatorTestResponseBlock.cs
│ ├── AdaptationResponseBlock.cs
│ ├── AsciiDataBlock.cs
│ ├── Block.cs
│ ├── CodingWscBlock.cs
│ ├── CustomBlock.cs
│ ├── FaultCodesBlock.cs
│ ├── GroupReadResponseBlock.cs
│ ├── GroupReadResponseWithTextBlock.cs
│ ├── NakBlock.cs
│ ├── RawDataReadResponseBlock.cs
│ ├── ReadEepromResponseBlock.cs
│ ├── ReadRomEepromResponse.cs
│ ├── SecurityAccessMode2Block.cs
│ ├── SensorValue.cs
│ ├── UnknownBlock.cs
│ └── WriteEepromResponseBlock.cs
├── BusyWait.cs
├── Cluster/
│ ├── AudiC5Cluster.cs
│ ├── BoschRBxCluster.cs
│ ├── ICluster.cs
│ ├── MarelliCluster.cs
│ ├── MotometerBOOCluster.cs
│ ├── VdoCluster.cs
│ └── VdoKeyFinder.cs
├── ControllerAddress.cs
├── ControllerIdent.cs
├── ControllerInfo.cs
├── EDC15/
│ ├── Edc15VM.cs
│ └── Loader.a66
├── Interface/
│ ├── FtdiInterface.cs
│ ├── GenericInterface.cs
│ ├── IInterface.cs
│ └── LinuxInterface.cs
├── KW1281Dialog.cs
├── Kwp2000/
│ ├── DiagnosticService.cs
│ ├── KW2000Dialog.cs
│ ├── Kwp2000Message.cs
│ ├── NegativeResponseException.cs
│ └── ResponseCode.cs
├── KwpCommon.cs
├── LICENSE.txt
├── Logging/
│ ├── ConsoleLog.cs
│ ├── FileLog.cs
│ └── ILog.cs
├── Program.cs
├── Publish_Mac.ps1
├── Publish_Win.ps1
├── README.md
├── Tester.cs
├── Tests/
│ ├── BitFab.KW1281Test.Tests.csproj
│ ├── Cluster/
│ │ ├── MarelliClusterTests.cs
│ │ └── VdoClusterTests.cs
│ ├── GlobalUsings.cs
│ ├── ProgramTests.cs
│ ├── TesterTests.cs
│ └── UtilsTests.cs
├── UnableToProceedException.cs
├── UnexpectedProtocolException.cs
├── Utils.cs
└── kw1281test.slnx
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain
================================================
FILE: .github/FUNDING.yml
================================================
custom: ["https://paypal.me/GregMenounos"]
================================================
FILE: .gitignore
================================================
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- Backup*.rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
/GitHub
/kw1281test.csproj
.DS_Store
================================================
FILE: .vscode/launch.json
================================================
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": []
}
================================================
FILE: BlockTitle.cs
================================================
namespace BitFab.KW1281Test
{
public enum BlockTitle : byte
{
ReadIdent = 0x00,
ReadRam = 0x01,
GroupReadResponseWithText = 0x02,
ReadRomEeprom = 0x03,
ActuatorTest = 0x04,
FaultCodesDelete = 0x05,
End = 0x06, // end output, end of communication
FaultCodesRead = 0x07, // get errors, all errors output
ACK = 0x09,
NAK = 0x0A,
SoftwareCoding = 0x10,
BasicSettingRawDataRead = 0x11,
RawDataRead = 0x12,
ReadEeprom = 0x19,
WriteEeprom = 0x1A,
Custom = 0x1B,
AdaptationRead = 0x21,
AdaptationTest = 0x22,
BasicSettingRead = 0x28,
GroupRead = 0x29,
AdaptationSave = 0x2A,
Login = 0x2B,
SecurityAccessMode2 = 0x3D,
SecurityImmoAccess1 = 0x5C,
SecurityAccessMode1 = 0xD7,
AdaptationResponse = 0xE6,
GroupReadResponse = 0xE7,
ReadEepromResponse = 0xEF,
RawDataReadResponse = 0xF4,
ActuatorTestResponse = 0xF5,
AsciiData = 0xF6,
WriteEepromResponse = 0xF9,
FaultCodesResponse = 0xFC,
ReadRomEepromResponse = 0xFD,
ReadRamResponse = 0xFE,
}
// http://nefariousmotorsports.com/forum/index.php?topic=8274.0
// Block Title - Answer
// $00 ECU identification read - $F6
// $01 RAM cells read - $FE
// $02 RAM cells write - $09
// $03 ROM/EPROM/EEPROM read - $FD
// $04 Actuator test - $F5
// $05 Fault codes delete -$FC
// $06 Output end
// $07 Fault codes read - $FC
// $08 ADC channel read - $FB
// $09 Acknowledge
// $0A NoAck
// $0C EPROM/EEPROM write - $F9
// $10 Parameter coding - $F6
// $11 Basic setting read - $F4
// $12 Measuring values read - $F4
// $19 EEPROM (serial) read - $EF
// $1A EEPROM (serial) write - $F9
// $1B Custom usage
// $21 Adaption read - $E6
// $22 Adaption transfer - $E6
// $27 Start download routine - $E8
// $28 Basic setting normed read - $E7
// $29 Measuring values normed read - $E7
// $2A Adaption save - $E6
// $2B Login request - $09/$FD
// $3D Security access mode 2 - $09/$D7
// $D7 Security access mode 1 - $3D
}
================================================
FILE: Blocks/AckBlock.cs
================================================
using System;
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class AckBlock : Block
{
public AckBlock(List<byte> bytes) : base(bytes)
{
// Dump();
}
private void Dump()
{
Log.WriteLine("Received ACK block");
}
}
}
================================================
FILE: Blocks/ActuatorTestResponseBlock.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class ActuatorTestResponseBlock : Block
{
public ActuatorTestResponseBlock(List<byte> bytes) : base(bytes)
{
Dump();
}
public string ActuatorName
{
get
{
var id = Utils.Dump(Body).Trim();
if (_idToName.TryGetValue(id, out string? name))
{
return name!;
}
return id;
}
}
private void Dump()
{
Log.Write("Received \"Actuator Test Response\" block:");
foreach (var b in Body)
{
Log.Write($" {b:X2}");
}
Log.WriteLine();
}
private static readonly Dictionary<string, string> _idToName = new()
{
{ "02 96", "Tachometer" },
{ "02 95", "Coolant Temp Gauge" },
{ "02 98", "Fuel Gauge" },
{ "02 97", "Speedometer" },
{ "03 1E", "Segment Test" },
{ "02 72", "Glow Plug Warning" },
{ "02 F2", "Coolant Temp Warning" },
{ "02 F3", "Oil Pressure Warning" },
{ "01 F5", "Oil Level Warning" },
{ "04 16", "Brake Pad Warning" },
{ "04 3B", "Low Washer Fluid Warning" },
{ "04 3A", "Low Fuel Warning" },
{ "01 F6", "Immobilizer Warning" },
{ "04 17", "Brake Warning" },
{ "02 99", "Seatbelt Warning" },
{ "02 9A", "Gong" },
{ "03 FF", "Chime" },
{ "04 AB", "End" },
};
}
}
================================================
FILE: Blocks/AdaptationResponseBlock.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class AdaptationResponseBlock : Block
{
public AdaptationResponseBlock(List<byte> bytes) : base(bytes)
{
Dump();
}
public byte ChannelNumber => Body[0];
public ushort ChannelValue => (ushort)(Body[1] * 256 + Body[2]);
private void Dump()
{
Log.Write("Received \"Adaptation Response\" block:");
foreach (var b in Body)
{
Log.Write($" {b:X2}");
}
Log.WriteLine();
}
}
}
================================================
FILE: Blocks/AsciiDataBlock.cs
================================================
using System;
using System.Collections.Generic;
using System.Text;
namespace BitFab.KW1281Test.Blocks
{
internal class AsciiDataBlock : Block
{
public AsciiDataBlock(List<byte> bytes) : base(bytes)
{
// Dump();
}
public bool MoreDataAvailable => Bytes[3] > 0x7F;
public override string ToString()
{
var sb = new StringBuilder();
foreach (var b in Body)
{
sb.Append((char)(b & 0x7F));
}
return sb.ToString();
}
private void Dump()
{
Log.Write($"Received Ascii data block: \"{ToString()}\"");
if (MoreDataAvailable)
{
Log.Write(" (More data available via ReadIdent)");
}
Log.WriteLine();
}
}
}
================================================
FILE: Blocks/Block.cs
================================================
using System.Collections.Generic;
using System.Linq;
namespace BitFab.KW1281Test.Blocks
{
/// <summary>
/// KWP1281 block
/// </summary>
class Block
{
public Block(List<byte> bytes)
{
Bytes = bytes;
}
/// <summary>
/// Returns the entire raw block bytes.
/// </summary>
public List<byte> Bytes { get; }
public byte Title => Bytes[2];
/// <summary>
/// Returns the body of the block, excluding the length, counter, title and end bytes.
/// </summary>
public List<byte> Body => Bytes.Skip(3).Take(Bytes.Count - 4).ToList();
public bool IsAck => Title == (byte)BlockTitle.ACK;
public bool IsNak => Title == (byte)BlockTitle.NAK;
public bool IsAckNak => IsAck || IsNak;
}
}
================================================
FILE: Blocks/CodingWscBlock.cs
================================================
using System.Collections.Generic;
using System.Linq;
namespace BitFab.KW1281Test.Blocks
{
internal class CodingWscBlock : Block
{
public CodingWscBlock(List<byte> bytes) : base(bytes)
{
var data = bytes.Skip(4).ToList();
SoftwareCoding = (data[0] * 256 + data[1]) / 2;
WorkshopCode = data[2] * 256 + data[3];
// Workshop codes > 65535 overflow into the low bit of the software coding
if ((data[1] & 1) == 1)
{
WorkshopCode += 65536;
}
}
public override string ToString()
{
return $"Software Coding {SoftwareCoding:d5}, Workshop Code: {WorkshopCode:d5}";
}
public int SoftwareCoding { get; }
public int WorkshopCode { get; }
}
}
================================================
FILE: Blocks/CustomBlock.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class CustomBlock : Block
{
public CustomBlock(List<byte> bytes) : base(bytes)
{
// Dump();
}
private void Dump()
{
Log.Write("Received Custom block:");
for (var i = 3; i < Bytes.Count - 1; i++)
{
Log.Write($" {Bytes[i]:X2}");
}
Log.WriteLine();
}
}
}
================================================
FILE: Blocks/FaultCodesBlock.cs
================================================
using System.Collections.Generic;
using System.Linq;
namespace BitFab.KW1281Test.Blocks
{
internal class FaultCodesBlock : Block
{
public FaultCodesBlock(List<byte> bytes) : base(bytes)
{
FaultCodes = new();
IEnumerable<byte> data = Body;
while (true)
{
var code = data.Take(3).ToArray();
if (code.Length == 0)
{
break;
}
var dtc = code[0] * 256 + code[1];
var status = code[2];
var faultCode = new FaultCode(dtc, status);
if (!faultCode.Equals(FaultCode.None))
{
FaultCodes.Add(faultCode);
}
data = data.Skip(3);
}
}
public List<FaultCode> FaultCodes { get; }
}
internal struct FaultCode
{
public FaultCode(int dtc, int status)
{
Dtc = dtc;
Status = status;
}
public override string ToString()
{
var status1 = Status & 0x7F;
var status2 = (Status >> 7) * 10;
return $"{Dtc:d5} - {status1:d2}-{status2:d2}";
}
public int Dtc { get; }
public int Status { get; }
public static readonly FaultCode None = new FaultCode(0xFFFF, 0x88);
}
}
================================================
FILE: Blocks/GroupReadResponseBlock.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BitFab.KW1281Test.Blocks
{
internal class GroupReadResponseBlock : Block
{
public GroupReadResponseBlock(List<byte> bytes) : base(bytes)
{
SensorValues = new List<SensorValue>();
var bodyBytes = new List<byte>(Body);
while (bodyBytes.Count > 2)
{
var valueBytes = bodyBytes.Take(3).ToArray();
SensorValues.Add(
new SensorValue(valueBytes[0], valueBytes[1], valueBytes[2]));
bodyBytes = bodyBytes.Skip(3).ToList();
}
if (bodyBytes.Count > 0)
{
throw new InvalidOperationException(
$"{nameof(GroupReadResponseBlock)} body ({Utils.DumpBytes(Body)}) should be a multiple of 3 bytes long.");
}
}
public List<SensorValue> SensorValues { get; }
public override string ToString()
{
var sb = new StringBuilder();
foreach(var sensorValue in SensorValues)
{
if (sb.Length > 0)
{
sb.Append(" | ");
}
sb.Append(sensorValue.ToString());
}
return sb.ToString();
}
}
}
================================================
FILE: Blocks/GroupReadResponseWithTextBlock.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BitFab.KW1281Test.Blocks
{
internal class GroupReadResponseWithTextBlock : Block
{
public GroupReadResponseWithTextBlock(List<byte> bytes)
: base(bytes)
{
var bodyBytes = new List<byte>(Body);
while (bodyBytes.Count > 2)
{
var subBlockHeader = bodyBytes.Take(3).ToArray();
bodyBytes = bodyBytes.Skip(3).ToList();
int subBlockBodyLength = subBlockHeader[2];
if (bodyBytes.Count < subBlockBodyLength)
{
throw new InvalidOperationException(
$"{nameof(GroupReadResponseWithTextBlock)} body ({Utils.DumpBytes(Body)}) contains extra bytes after sub-blocks.");
}
var subBlock = new SubBlock
{
BlockType = subBlockHeader[0],
Data = subBlockHeader[1],
Body = bodyBytes.Take(subBlockBodyLength).ToArray()
};
bodyBytes = bodyBytes.Skip(subBlockBodyLength).ToList();
SubBlocks.Add(subBlock);
if (subBlock.BlockType == 0x8D)
{
var text = Encoding.ASCII.GetString(subBlock.Body, 0, subBlock.Body.Length);
_text = text.Split((char)0x03).ToList();
}
}
if (bodyBytes.Count > 0)
{
throw new InvalidOperationException(
$"{nameof(GroupReadResponseWithTextBlock)} body ({Utils.DumpBytes(Body)}) contains extra bytes after sub-blocks.");
}
}
private readonly List<string> _text = new();
public string GetText(int i)
{
if (i >= 0 && i < _text.Count)
{
return $"\"{_text[i]}\"";
}
return i.ToString();
}
public override string ToString()
{
var sb = new StringBuilder();
foreach (var subBlock in SubBlocks)
{
sb.Append(subBlock.ToString());
}
return sb.ToString();
}
readonly List<SubBlock> SubBlocks = new();
class SubBlock
{
public byte BlockType { get; init; }
public byte Data { get; init; }
public byte[] Body { get; init; } = Array.Empty<byte>();
public override string ToString()
{
switch(BlockType)
{
case 0x8D:
return $"(${BlockType:X2} ${Data:X2} {Encoding.ASCII.GetString(Body, 0, Body.Length).Replace((char)0x03, '|')})";
default:
return $"(${BlockType:X2} ${Data:X2}{Utils.Dump(Body)})";
}
}
}
}
}
================================================
FILE: Blocks/NakBlock.cs
================================================
using System;
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
class NakBlock : Block
{
public NakBlock(List<byte> bytes) : base(bytes)
{
}
}
}
================================================
FILE: Blocks/RawDataReadResponseBlock.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class RawDataReadResponseBlock : Block
{
public RawDataReadResponseBlock(List<byte> bytes) : base(bytes)
{
}
public override string ToString()
{
return $"Raw Data:{Utils.DumpDecimal(Body)}";
}
}
}
================================================
FILE: Blocks/ReadEepromResponseBlock.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class ReadEepromResponseBlock : Block
{
public ReadEepromResponseBlock(List<byte> bytes) : base(bytes)
{
Dump();
}
private void Dump()
{
Log.Write("Received \"Read EEPROM Response\" block:");
foreach (var b in Body)
{
Log.Write($" {b:X2}");
}
Log.WriteLine();
}
}
}
================================================
FILE: Blocks/ReadRomEepromResponse.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class ReadRomEepromResponse : Block
{
public ReadRomEepromResponse(List<byte> bytes) : base(bytes)
{
Dump();
}
private void Dump()
{
Log.Write("Received \"Read ROM/EEPROM Response\" block:");
foreach (var b in Body)
{
Log.Write($" {b:X2}");
}
Log.WriteLine();
}
}
}
================================================
FILE: Blocks/SecurityAccessMode2Block.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class SecurityAccessMode2Block : Block
{
public SecurityAccessMode2Block(List<byte> bytes) : base(bytes)
{
Dump();
}
private void Dump()
{
Log.Write("Received \"Security Access Mode 2\" block:");
foreach (var b in Body)
{
Log.Write($" ${b:X2}");
}
Log.WriteLine();
}
}
}
================================================
FILE: Blocks/SensorValue.cs
================================================
using System;
namespace BitFab.KW1281Test.Blocks
{
public class SensorValue
{
public byte SensorID { get; }
public byte A { get; }
public byte B { get; }
public SensorValue(byte sensorID, byte a, byte b)
{
SensorID = sensorID;
A = a;
B = b;
}
public override string ToString()
{
// https://www.blafusel.de/obd/obd2_kw1281.html#7
return SensorID switch
{
1 => $"{0.2 * A * B} rpm",
2 => $"{(A * 0.002 * B):F1} %",
3 => $"{(0.002 * A * B):F1} Deg",
4 => $"{(Math.Abs(B - 127) * 0.01 * A):F1} \u00B0{(B > 127 ? "ATDC" : "BTDC")}", // Degrees
5 => $"{(A * (B-100) * 0.1):F1} \u00B0C", // Degrees C
6 => $"{(0.001 * A * B):F1} V",
7 => $"{0.01 * A * B} km/h",
8 => $"{(0.1 * A * B):F1}",
9 => $"{((B - 127) * 0.02 * A):F1} Deg",
10 => $"{(B == 0 ? "Cold" : "Warm")}",
11 => $"{(0.0001 * A * (B - 128) + 1):F1}",
12 => $"{(0.001 * A * B):F1} \u2126", // Ohm
13 => $"{((B - 127) * 0.001 * A):F1} mm",
14 => $"{(0.005 * A * B):F1} bar",
15 => $"{(0.01 * A * B):F1} ms",
16 => $"{Convert.ToString(A, 2).PadLeft(8, '0')} {Convert.ToString(B, 2).PadLeft(8, '0')}",
17 => $"\"{(char)A}{(char)B}\"",
18 => $"{(0.04 * A * B):F1} mbar",
19 => $"{(A * B * 0.01):F1} l",
20 => $"{(A * (B - 128) / 128.0):F1} %",
21 => $"{(0.001 * A * B):F1} V",
22 => $"{(0.001 * A * B):F1} ms",
23 => $"{(B / 256.0 * A):F1} %",
24 => $"{(0.001 * A * B):F1} A",
25 => $"{((B * 1.421) + (A / 182.0)):F1} g/s",
26 => $"{B - A} C",
27 => $"{(Math.Abs(B - 128) * 0.01 * A):F1} \u00B0{(B < 128 ? "ATDC" : "BTDC")}", // Degrees
28 => $"{B - A}",
29 => $"{(B<A ? "Map 1" : "Map 2")}",
30 => $"{(B / 12.0 * A):F1} Deg k/w",
31 => $"{(B / 2560.0 * A):F1}",
32 => $"{(B>128 ? B-256 : B)}",
33 => $"{(A == 0 ? 100.0*B : 100.0*B/A):F1} %",
34 => $"{((B - 128) * 0.01 * A):F1} kW",
35 => $"{(0.01 * A * B):F1} l/h",
36 => $"{A * 2560 + B * 10} km",
// 37 => ???,
38 => $"{((B - 128) * 0.001 * A):F1} Deg k/w",
39 => $"{(B/256.0*A):F1} mg/h",
40 => $"{(B * 0.1 + (25.5 * A) - 400):F1} A",
41 => $"{(B + A * 255)} Ah",
42 => $"{(B * 0.1 + (25.5 * A) - 400):F1} Kw",
43 => $"{(B * 0.1 + (25.5 * A)):F1} V",
44 => $"{A:D2}:{B:D2}",
45 => $"{(0.1 * A * B / 100.0):F1}",
46 => $"{((A * B - 3200) * 0.0027):F1} Deg k/w",
47 => $"{((B - 128) * A)} ms",
48 => $"{B + A * 255}",
49 => $"{(B / 4.0 * A * 0.1):F1} mg/h",
50 => $"{(A == 0 ? (B - 128) / 0.01 : (B - 128) / (0.01 * A)):F1} mbar",
51 => $"{(((B - 128) / 255.0) * A):F1} mg/h",
52 => $"{(B * 0.02 * A - A):F1} Nm",
53 => $"{((B - 128) * 1.4222 + 0.006 * A):F1} g/s",
54 => $"{A * 256 + B}",
55 => $"{(A * B / 200.0):F1} s",
56 => $"{A * 256 + B} WSC",
57 => $"{A * 256 + B + 65536} WSC",
58 => $"{(B > 128 ? 1.0225 * (256 - B) : 1.0225 * B):F1} /s",
59 => $"{((A * 256 + B) / 32768.0):F1}",
60 => $"{((A * 256 + B) * 0.01):F1} sec",
61 => $"{(A==0 ? (B - 128) : (B - 128) / A):F1}",
62 => $"{(0.256 * A * B):F1} S",
63 => $"\"{(char)A}{(char)B}\"?",
64 => $"{A+B} \u2126", // Ohm
65 => $"{(0.01 * A * (B - 127)):F1} mm",
66 => $"{((A * B) / 511.12):F1} V",
67 => $"{((640 * A) + B * 2.5):F1} Deg",
68 => $"{((256 * A + B) / 7.365):F1} deg/s",
69 => $"{((256 * A + B) * 0.3254):F1} Bar",
70 => $"{((256 * A + B) * 0.192):F1} m/s\u00B2", // squared
_ => $"({SensorID} {A} {B})",
};
}
}
}
================================================
FILE: Blocks/UnknownBlock.cs
================================================
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class UnknownBlock : Block
{
public UnknownBlock(List<byte> bytes) : base(bytes)
{
Dump();
}
private void Dump()
{
Log.Write($"Received ${Title:X2} block:");
foreach (var b in Bytes)
{
Log.Write($" 0x{b:X2}");
}
Log.WriteLine();
}
}
}
================================================
FILE: Blocks/WriteEepromResponseBlock.cs
================================================
using System;
using System.Collections.Generic;
namespace BitFab.KW1281Test.Blocks
{
internal class WriteEepromResponseBlock : Block
{
public WriteEepromResponseBlock(List<byte> bytes) : base(bytes)
{
Dump();
}
private void Dump()
{
Log.Write("Received \"Write EEPROM Response\" block:");
foreach (var b in Body)
{
Log.Write($" {b:X2}");
}
Log.WriteLine();
}
}
}
================================================
FILE: BusyWait.cs
================================================
using System.Diagnostics;
namespace BitFab.KW1281Test;
public class BusyWait
{
private readonly long _ticksPerCycle;
private long? _nextTickTimestamp;
public BusyWait(long msPerCycle)
{
_ticksPerCycle = msPerCycle * TicksPerMs;
}
public void DelayUntilNextCycle()
{
_nextTickTimestamp ??= Stopwatch.GetTimestamp() + _ticksPerCycle;
while (Stopwatch.GetTimestamp() < _nextTickTimestamp)
{
}
_nextTickTimestamp += _ticksPerCycle;
}
public static void Delay(long ms)
{
var waiter = new BusyWait(ms);
waiter.DelayUntilNextCycle();
}
private static readonly long TicksPerMs = Stopwatch.Frequency / 1000;
}
================================================
FILE: Cluster/AudiC5Cluster.cs
================================================
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;
}
}
================================================
FILE: Cluster/BoschRBxCluster.cs
================================================
using BitFab.KW1281Test.Kwp2000;
using System;
using System.Linq;
using System.Threading;
using Service = BitFab.KW1281Test.Kwp2000.DiagnosticService;
namespace BitFab.KW1281Test.Cluster;
class BoschRBxCluster : ICluster
{
public void UnlockForEepromReadWrite()
{
SecurityAccess(0xFB);
}
public string DumpEeprom(
uint? optionalAddress, uint? optionalLength, string? optionalFileName)
{
uint address = optionalAddress ?? 0x10400;
uint length = optionalLength ?? 0x400;
string filename = optionalFileName ?? $"RBx_0x{address:X6}_mem.bin";
_kwp2000.DumpMem(address, length, filename);
return filename;
}
public bool SecurityAccess(byte accessMode)
{
const byte identificationOption = 0x94;
var responseMsg = _kwp2000.SendReceive(Service.readEcuIdentification, new byte[] { identificationOption });
if (responseMsg.Body[0] != identificationOption)
{
throw new InvalidOperationException($"Received unexpected identificationOption: {responseMsg.Body[0]:X2}");
}
Log.WriteLine(Utils.DumpAscii(responseMsg.Body.Skip(1)));
const int maxTries = 16;
for (var i = 0; i < maxTries; i++)
{
responseMsg = _kwp2000.SendReceive(Service.securityAccess, [accessMode]);
if (responseMsg.Body[0] != accessMode)
{
throw new InvalidOperationException($"Received unexpected accessMode: {responseMsg.Body[0]:X2}");
}
var seedBytes = responseMsg.Body.Skip(1).ToArray();
var seed = (uint)(
(seedBytes[0] << 24) |
(seedBytes[1] << 16) |
(seedBytes[2] << 8) |
seedBytes[3]);
var key = CalcRBxKey(seed);
try
{
responseMsg = _kwp2000.SendReceive(Service.securityAccess,
new[] {
(byte)(accessMode + 1),
(byte)((key >> 24) & 0xFF),
(byte)((key >> 16) & 0xFF),
(byte)((key >> 8) & 0xFF),
(byte)(key & 0xFF)
});
Log.WriteLine("Success!!!");
return true;
}
catch (NegativeResponseException)
{
if (i < (maxTries - 1))
{
Log.WriteLine("Trying again.");
}
}
}
return false;
}
/// <summary>
/// Toggle an Audi A4 RB4 cluster between Adapted mode (6) and New mode (4).
/// Cluster should already be logged in and unlocked for EEPROM read/write.
/// </summary>
public void ToggleRB4Mode()
{
_kwp2000.StartDiagnosticSession(0x84, 0x14);
Thread.Sleep(350);
byte[] bytes = _kwp2000.ReadMemoryByAddress(0x010450, 2);
if (bytes[0] != (byte)'A' && bytes[1] != (byte)'U')
{
Log.WriteLine("Cluster is not an Audi cluster!");
}
else
{
try
{
bytes = _kwp2000.ReadMemoryByAddress(0x010000, 0x10);
Log.WriteLine("Cluster is in New mode (4).");
}
catch (NegativeResponseException)
{
Log.WriteLine("Cluster is in Adapted mode (6).");
}
Log.WriteLine("Toggling cluster mode...");
foreach (var address in new uint[] { 0x01044F, 0x01052F, 0x01062F })
{
bytes = _kwp2000.ReadMemoryByAddress(address, 1);
bytes[0] ^= 0x12;
_kwp2000.WriteMemoryByAddress(address, 1, bytes);
}
}
Log.WriteLine("Resetting cluster...");
_kwp2000.EcuReset(0x01);
}
static uint CalcRBxKey(uint seed)
{
uint key = 0x03249272 + (seed ^ 0xf8253947);
return key;
}
private readonly KW2000Dialog _kwp2000;
public BoschRBxCluster(KW2000Dialog kwp2000)
{
_kwp2000 = kwp2000;
}
}
================================================
FILE: Cluster/ICluster.cs
================================================
namespace BitFab.KW1281Test.Cluster
{
internal interface ICluster
{
void UnlockForEepromReadWrite();
string DumpEeprom(uint? address, uint? length, string? dumpFileName);
}
}
================================================
FILE: Cluster/MarelliCluster.cs
================================================
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
namespace BitFab.KW1281Test.Cluster
{
class MarelliCluster : ICluster
{
public void UnlockForEepromReadWrite()
{
// Nothing to do
}
public string DumpEeprom(uint? address, uint? length, string? dumpFileName)
{
address ??= GetDefaultAddress();
dumpFileName ??= $"marelli_mem_${address:X4}.bin";
_ = DumpMem(dumpFileName, (ushort)address, (ushort?)length);
return dumpFileName;
}
private ushort GetDefaultAddress()
{
if (HasSmallEeprom())
{
return 3072; // $0C00
}
else if (HasLargeEeprom())
{
return 14336; // $3800
}
else
{
Log.WriteLine();
Log.WriteLine("Unsupported Marelli cluster version.");
Log.WriteLine("You can try the following commands to see if either produces a dump file.");
Log.WriteLine("Then please contact the program author with the results.");
Log.WriteLine();
var prefix = string.Join(' ', Program.CommandAndArgs.Take(4));
Log.WriteLine($"{prefix} DumpMarelliMem 3072 1024");
Log.WriteLine($"{prefix} DumpMarelliMem 14336 2048");
throw new UnableToProceedException();
}
}
/// <summary>
/// Dumps memory from a Marelli cluster to a file.
/// </summary>
private byte[] DumpMem(
string filename,
ushort address,
ushort? count = null)
{
byte entryH; // High byte of code entry point
byte regBlockH; // High byte of register block
if (_ecuInfo.Contains("M73 D0")) // Audi TT
{
entryH = 0x00; // $0000
regBlockH = (byte)((address == 0x3800) ? 0x20 : 0x08);
count ??= (ushort)((address == 0x3800) ? 0x800 : 0x400);
}
else if (HasSmallEeprom())
{
entryH = 0x02; // $0200
regBlockH = 0x08; // $0800
count ??= 1024; // $0400
}
else if (HasLargeEeprom())
{
entryH = 0x18; // $1800
regBlockH = 0x20; // $2000
count ??= 2048; // $0800
}
else if (address == 3072 && count == 1024)
{
Log.WriteLine("Untested cluster version! You may need to disconnect your battery if this fails.");
entryH = 0x02;
regBlockH = 0x08;
}
else if (address == 14336 && count == 2048)
{
Log.WriteLine("Untested cluster version! You may need to disconnect your battery if this fails.");
entryH = 0x18;
regBlockH = 0x20;
}
else
{
Log.WriteLine("Unsupported cluster software version");
return [];
}
Log.WriteLine($"entryH: 0x{entryH:X2}, regBlockH: 0x{regBlockH:X2}, count: 0x{count:X4}");
Log.WriteLine("Sending block 0x6C");
_kwp1281.SendBlock([0x6C]);
Thread.Sleep(250);
Log.WriteLine("Writing data to cluster microcontroller");
var data = new byte[]
{
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x34,
entryH, 0x00, // Entry point $xx00
};
if (!WriteMarelliBlockAndReadAck(data))
{
return [];
}
// Now we write a small memory dump program to the 68HC12 processor
Log.WriteLine("Writing memory dump program to cluster microcontroller");
Log.WriteLine($"(Entry: ${entryH:X2}00, RegBlock: ${regBlockH:X2}00, Start: ${address:X4}, Count: ${count:X4})");
var startH = (byte)(address / 256);
var startL = (byte)(address % 256);
var end = address + count;
var endH = (byte)(end / 256);
var endL = (byte)(end % 256);
var program = new byte[]
{
entryH, 0x00, // Address $xx00
0x14, 0x50, // orcc #$50
0x07, 0x32, // bsr FeedWatchdog
// Set baud rate to 9600
0xC7, // clrb
0x7B, regBlockH, 0xC8, // stab SC1BDH
0xC6, 0x34, // ldab #$34
0x7B, regBlockH, 0xC9, // stab SC1BDL
// Enable transmit, disable UART interrupts
0xC6, 0x08, // ldab #$08
0x7B, regBlockH, 0xCB, // stab SC1CR2
0xCE, startH, startL, // ldx #start
// SendLoop:
0xA6, 0x30, // ldaa 1,X+
0x07, 0x0F, // bsr SendByte
0x8E, endH, endL, // cpx #end
0x26, 0xF7, // bne SendLoop
// Poison the watchdog to force a reboot
0xCC, 0x11, 0x11, // ldd #$1111
0x7B, regBlockH, 0x17, // stab COPRST
0x7A, regBlockH, 0x17, // staa COPRST
0x3D, // rts
// SendByte:
0xF6, regBlockH, 0xCC, // ldab SC1SR1
0x7A, regBlockH, 0xCF, // staa SC1DRL
// TxBusy:
0x07, 0x06, // bsr FeedWatchdog
// Loop until TC (Transmit Complete) bit is set
0x1F, regBlockH, 0xCC, 0x40, 0xF9, // brclr SC1SR1,$40,TxBusy
0x3D, // rts
// FeedWatchdog:
0xCC, 0x55, 0xAA, // ldd #$55AA
0x7B, regBlockH, 0x17, // stab COPRST
0x7A, regBlockH, 0x17, // staa COPRST
0x3D, // rts
};
if (!WriteMarelliBlockAndReadAck(program))
{
return Array.Empty<byte>();
}
Log.WriteLine("Receiving memory dump");
var kwpCommon = _kwp1281.KwpCommon;
var mem = new List<byte>();
for (int i = 0; i < count; i++)
{
var b = kwpCommon.ReadByte();
mem.Add(b);
}
File.WriteAllBytes(filename, mem.ToArray());
Log.WriteLine($"Saved memory dump to {filename}");
Log.WriteLine("Done");
_kwp1281.SetDisconnected(); // Don't try to send EndCommunication block
return mem.ToArray();
}
private bool WriteMarelliBlockAndReadAck(byte[] data)
{
var kwpCommon = _kwp1281.KwpCommon;
var count = (ushort)(data.Length + 2); // Count includes 2-byte checksum
var countH = (byte)(count / 256);
var countL = (byte)(count % 256);
kwpCommon.WriteByte(countH);
kwpCommon.WriteByte(countL);
var sum = (ushort)(countH + countL);
foreach (var b in data)
{
kwpCommon.WriteByte(b);
sum += b;
}
kwpCommon.WriteByte((byte)(sum / 256));
kwpCommon.WriteByte((byte)(sum % 256));
var expectedAck = new byte[] { 0x03, 0x09, 0x00, 0x0C };
Log.WriteLine("Receiving ACK");
var ack = new List<byte>();
for (int i = 0; i < 4; i++)
{
var b = kwpCommon.ReadByte();
ack.Add(b);
}
if (!ack.SequenceEqual(expectedAck))
{
Log.WriteLine($"Expected ACK but received {Utils.Dump(ack)}");
return false;
}
return true;
}
private readonly string[] _smallEepromEcus =
[
"1C0920800", // Beetle 1C0920800C M73 V07
"1C0920806", // Beetle 1C0920806G M73 V03
"1C0920901", // Beetle 1C0920901C M73 V07
"1C0920905", // Beetle 1C0920905F M73 V03
"1C0920906", // Beetle 1C0920906A M73 V03
"8N1919880E KOMBI+WEGFAHRS. M73 D23", // Audi TT
"8N1920930", // Audi TT 8N1920930B M73 D23
];
private bool HasSmallEeprom() => _smallEepromEcus.Any(model => _ecuInfo.Contains(model));
private readonly string[] _largeEepromEcus =
[
"1C0920821", // KOMBI+WEGFAHRS. M73 V08 (Beetle 2003)
"1C0920921", // Beetle 1C0920921G M73 V08
"1C0920941", // Beetle 1C0920941LX M73 V03
"1C0920951", // Beetle 1C0920951A M73 V02
"8D0920900R", // KOMBI+WEGFAHRS. M73 D54 (Audi A4 B5 2001)
"8L0920900B", // KOMBI+WEGFAHRS. M73 D13 (Audi A3 8L 2002, ASZ diesel engine)
"8L0920900E", // KOMBI+WEGFAHRS. M73 D56
"8N1919880E KOMBI+WEGFAHRS. M73 D26", // Audi TT
"8N1920980", // Audi TT 8N1920980E M73 D14
"8N2919910A", // KOMBI+WEGFAHRS. M73 D29, Audi TT
"8N2920930", // Audi TT 8N2920930C M73 D55
"8N2920980", // Audi TT 8N2920980A M73 D14
];
// 1C0920821 KOMBI+WEGFAHRS. M73 V08
private bool HasLargeEeprom() => _largeEepromEcus.Any(model => _ecuInfo.Contains(model));
/// <summary>
/// Search for the SKC using the 2 methods described here:
/// https://github.com/gmenounos/kw1281test/issues/50#issuecomment-1770255129
/// </summary>
public static ushort? GetSkc(byte[] buf)
{
// If the EEPROM contains a 14-digit Immobilizer ID then the SKC should be immediately prior to that
var immoIdOffset = FindImmobilizerId(buf);
if (immoIdOffset is >= 2)
{
return Utils.GetShortBE(buf, immoIdOffset.Value-2);
}
// Otherwise search for 00,01,0F or 00,02,0F or 00,03,0F or 00,04,0F and the SKC should be immediately prior
var keyCountOffset = FindKeyCount(buf);
if (keyCountOffset is >= 2)
{
return Utils.GetShortBE(buf, keyCountOffset.Value-2);
}
return null;
}
/// <summary>
/// Search the buffer for a 14 byte long string of uppercase letters and numbers beginning with VWZ or AUZ
/// </summary>
private static int? FindImmobilizerId(IReadOnlyList<byte> buf)
{
for (var i = 0; i < buf.Count - 14; i++)
{
if (!(buf[i] == 'V' && buf[i + 1] == 'W') &&
!(buf[i] == 'A' && buf[i + 1] == 'U'))
{
continue;
}
if (buf[i + 2] != 'Z')
{
continue;
}
var isValid = true;
for (var j = 3; j < 14; j++)
{
var b = buf[i + j];
if (b is >= (byte)'0' and <= (byte)'9' or >= (byte)'A' and <= (byte)'Z')
{
continue;
}
isValid = false;
break;
}
if (isValid)
{
return i;
}
}
return null;
}
/// <summary>
/// Search the buffer for the 3 byte sequence 00,01,0F or 00,02,0F or 00,03,0F or 00,04,0F
/// (2nd digit is probably the number of keys)
/// </summary>
private static int? FindKeyCount(IReadOnlyList<byte> buf)
{
for (var i = 0; i < buf.Count - 3; i++)
{
if (buf[i] != 0)
{
continue;
}
if (buf[i + 1] != 1 && buf[i + 1] != 2 && buf[i + 1] != 3 && buf[i + 1] != 4)
{
continue;
}
if (buf[i + 2] != 0x0F)
{
continue;
}
return i;
}
return null;
}
private readonly IKW1281Dialog _kwp1281;
private readonly string _ecuInfo;
public MarelliCluster(IKW1281Dialog kwp1281, string ecuInfo)
{
_kwp1281 = kwp1281;
_ecuInfo = ecuInfo;
}
}
}
================================================
FILE: Cluster/MotometerBOOCluster.cs
================================================
using BitFab.KW1281Test.Blocks;
using System;
using System.Collections.Generic;
using System.Linq;
namespace BitFab.KW1281Test.Cluster;
internal class MotometerBOOCluster : ICluster
{
public void UnlockForEepromReadWrite()
{
string softwareVersion = GetClusterInfo();
if (softwareVersion.Length < 10 ||
!VersionToLogin.TryGetValue(softwareVersion[..10], out ushort login))
{
Log.WriteLine("Warning: Unknown software version. Login may fail.");
login = 11899;
}
_kwp1281!.Login(login, workshopCode: 0);
Log.WriteLine($"Sending Custom $08 $15 block");
if (SendCustom(0x08, 0x15))
{
return;
}
Log.WriteLine("$08 $15 failed. Trying all combinations (this may take a while)...");
for (int first = 0; first < 0x100; first++)
{
Log.WriteLine($"Trying ${first:X2} $00-$FF");
for (int second = 0; second < 0x100; second++)
{
if (SendCustom(first, second))
{
Log.WriteLine($"Combination ${first:X2} ${second:X2} Succeeded.");
Log.WriteLine("Please report this to the program author.");
return;
}
}
}
Log.WriteLine("All combinations failed. EEPROM access will likely fail.");
}
private bool SendCustom(int first, int second)
{
_kwp1281.SendBlock(new List<byte> { 0x1B, (byte)first, (byte)second });
var block = _kwp1281.ReceiveBlocks().FirstOrDefault();
if (block is NakBlock)
{
return false;
}
else if (block is AckBlock)
{
return true;
}
else
{
throw new InvalidOperationException(
$"Expected ACK or NAK block but got: {block}");
}
}
private readonly Dictionary<string, ushort> VersionToLogin = new()
{
{ "a0prj008.1", 10164 },
{ "A0prj008.2", 10164 },
{ "a4prj010.1", 21597 },
{ "a4prj012.1", 21597 },
{ "h1340_05.2", 21701 },
{ "h1340_06.2", 21601 },
{ "h9340_08.1", 11899 },
{ "h9340_08.2", 11899 },
{ "h9340_09.1", 11899 },
{ "h9340_10.1", 11899 },
{ "h9340_10.2", 11899 },
{ "h9340_10.3", 11899 },
{ "h9340_11.2", 11899 },
{ "se110_05.2", 13473 },
{ "v9119_07.1", 19126 },
{ "v9119_07.3", 11064 },
{ "v9230_03.1", 10501 },
{ "v9230_03.2", 10501 },
{ "v9230_05.1", 44479 },
{ "v9230_05.2", 44479 },
{ "v9230_06.3", 23775 },
{ "v9230_07.2", 10164 },
{ "v9230_08.1", 10164 },
{ "v9230_08.2", 10164 },
{ "vw110_04.2", 08721 },
{ "VW230_06.1", 47165 },
{ "vw340_07.2", 05555 },
};
public string DumpEeprom(
uint? optionalAddress, uint? optionalLength, string? optionalFileName)
{
uint address = optionalAddress ?? 0;
uint length = optionalLength ?? 0x100;
string filename = optionalFileName ?? $"BOOMM0_0x{address:X6}_eeprom.bin";
#if false
var identInfo = _kwp1281.ReadIdent().First().ToString()
.Split(Environment.NewLine).First() // Sometimes ReadIdent() can return multiple lines
.Replace(' ', '_');
var dumpFileName = filename ?? $"{identInfo}_0x{startAddress:X4}_eeprom.bin";
foreach (var c in Path.GetInvalidFileNameChars())
{
dumpFileName = dumpFileName.Replace(c, 'X');
}
foreach (var c in Path.GetInvalidPathChars())
{
dumpFileName = dumpFileName.Replace(c, 'X');
}
Log.WriteLine($"Saving EEPROM dump to {dumpFileName}");
DumpEeprom(startAddress, length, maxReadLength: 16, dumpFileName);
Log.WriteLine($"Saved EEPROM dump to {dumpFileName}");
return dumpFileName;
#endif
throw new NotImplementedException();
}
private string GetClusterInfo()
{
Log.WriteLine("Sending 0x43 block");
_kwp1281.SendBlock([0x43]);
var blocks = _kwp1281.ReceiveBlocks().Where(b => !b.IsAckNak).ToList();
foreach (var block in blocks)
{
Log.WriteLine($"{Utils.DumpAscii(block.Body)}");
}
return Utils.DumpAscii(blocks[0].Body);
}
private readonly IKW1281Dialog _kwp1281;
public MotometerBOOCluster(IKW1281Dialog kwp1281)
{
_kwp1281 = kwp1281;
}
}
================================================
FILE: Cluster/VdoCluster.cs
================================================
using BitFab.KW1281Test.Blocks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace BitFab.KW1281Test.Cluster;
internal class VdoCluster : ICluster
{
public void UnlockForEepromReadWrite()
{
var (isUnlocked, softwareVersion) = Unlock();
if (!isUnlocked)
{
Log.WriteLine("Unknown cluster software version. EEPROM access will likely fail.");
}
if (!RequiresSeedKey())
{
Log.WriteLine(
"Cluster is unlocked for ROM/EEPROM access. Skipping Seed/Key login.");
return;
}
SeedKeyAuthenticate(softwareVersion);
if (RequiresSeedKey())
{
Log.WriteLine("Failed to unlock cluster.");
}
else
{
Log.WriteLine("Cluster is unlocked for ROM/EEPROM access.");
}
}
public string DumpEeprom(
uint? optionalAddress, uint? optionalLength, string? optionalFileName)
{
var address = optionalAddress ?? 0;
var length = optionalLength ?? 0x800;
var filename = optionalFileName ?? $"VDO_0x{address:X6}_eeprom.bin";
DumpEeprom((ushort)address, (ushort)length, maxReadLength: 16, filename);
return filename;
}
/// <summary>
/// http://www.maltchev.com/kiti/VAG_guide.txt
/// </summary>
public Dictionary<int, Block> CustomReadSoftwareVersion()
{
var versionBlocks = new Dictionary<int, Block>();
Log.WriteLine("Sending Custom \"Read Software Version\" blocks");
// The cluster can return 4 variations of software version, specified by the 2nd byte
// of the block:
// 0x00 - Cluster software version
// 0x01 - Unknown
// 0x02 - Unknown
// 0x03 - Unknown
for (byte variation = 0x00; variation < 0x04; variation++)
{
var blocks = SendCustom([0x84, variation]);
foreach (var block in blocks.Where(b => !b.IsAckNak))
{
if (variation is 0x00 or 0x03)
{
Log.WriteLine($"{variation:X2}: {DumpMixedContent(block)}");
}
else
{
Log.WriteLine($"{variation:X2}: {DumpBinaryContent(block)}");
}
versionBlocks[variation] = block;
}
}
return versionBlocks;
}
public void CustomReset()
{
Log.WriteLine("Sending Custom Reset block");
SendCustom([0x82]);
}
public List<byte> CustomReadMemory(uint address, byte count)
{
Log.WriteLine($"Sending Custom \"Read Memory\" block (Address: ${address:X6}, Count: ${count:X2})");
var blocks = SendCustom(
[
0x86,
count,
(byte)(address & 0xFF),
(byte)((address >> 8) & 0xFF),
(byte)((address >> 16) & 0xFF),
]);
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
// Permissions issue?
return [];
}
return blocks[0].Body.ToList();
}
/// <summary>
/// Read the low 64KB of the cluster's NEC controller ROM.
/// For MFA clusters, that should cover the entire ROM.
/// For FIS clusters, the ROM is 128KB and more work is needed to retrieve the high 64KB.
/// </summary>
/// <param name="address"></param>
/// <param name="count"></param>
/// <returns></returns>
public List<byte> CustomReadNecRom(ushort address, byte count)
{
Log.WriteLine($"Sending Custom \"Read NEC ROM\" block (Address: ${address:X4}, Count: ${count:X2})");
var blocks = SendCustom(
[
0xA6,
count,
(byte)(address & 0xFF),
(byte)((address >> 8) & 0xFF),
]);
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
throw new InvalidOperationException($"Custom \"Read NEC ROM\" returned {blocks.Count} blocks instead of 1");
}
return blocks[0].Body.ToList();
}
public List<byte> MapEeprom()
{
// Unlock partial EEPROM read
Unlock();
var map = new List<byte>();
const byte blockSize = 1;
for (ushort addr = 0; addr < 2048; addr += blockSize)
{
var blockBytes = _kwp1281.ReadEeprom(addr, blockSize);
blockBytes = Enumerable.Repeat(
blockBytes == null ? (byte)0 : (byte)0xFF,
blockSize).ToList();
map.AddRange(blockBytes);
}
return map;
}
public void DumpMem(string dumpFileName, uint startAddress, uint length)
{
const byte blockSize = 15;
bool succeeded = true;
using (var fs = File.Create(dumpFileName, blockSize, FileOptions.WriteThrough))
{
for (var addr = startAddress; addr < startAddress + length; addr += blockSize)
{
var readLength = (byte)Math.Min(startAddress + length - addr, blockSize);
var blockBytes = CustomReadMemory(addr, readLength);
if (blockBytes.Count != readLength)
{
succeeded = false;
blockBytes.AddRange(
Enumerable.Repeat((byte)0, readLength - blockBytes.Count));
Log.WriteLine($"{readLength - blockBytes.Count} missing");
}
fs.Write(blockBytes.ToArray(), 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<Block> SendCustom(List<byte> blockCustomBytes)
{
if (blockCustomBytes[0] > 0x80 && !_additionalCustomCommandsUnlocked)
{
CustomUnlockAdditionalCommands();
_additionalCustomCommandsUnlocked = true;
}
blockCustomBytes.Insert(0, (byte)BlockTitle.Custom);
_kwp1281.SendBlock(blockCustomBytes);
return _kwp1281.ReceiveBlocks();
}
public (bool succeeded, string? softwareVersion) Unlock()
{
var versionBlocks = CustomReadSoftwareVersion();
if (versionBlocks.Count == 0)
{
Log.WriteLine("Cluster did not return software version.");
return (succeeded: false, softwareVersion: null);
}
// Now we need to send an unlock code that is unique to each ROM version
Log.WriteLine("Sending Custom \"Unlock partial EEPROM read\" block");
var softwareVersion = SoftwareVersionToString(versionBlocks[0].Body);
var unlockCodes = GetClusterUnlockCodes(softwareVersion);
var unlocked = false;
foreach (var unlockCode in unlockCodes)
{
var unlockCommand = new List<byte> { 0x9D };
unlockCommand.AddRange(unlockCode);
var unlockResponse = SendCustom(unlockCommand);
if (unlockResponse.Count != 1)
{
throw new InvalidOperationException(
$"Received multiple responses from unlock request.");
}
if (unlockResponse[0].IsAck)
{
Log.WriteLine(
$"Unlock code for software version '{softwareVersion}' is{Utils.Dump(unlockCode)}");
if (unlockCodes.Length > 1)
{
Log.WriteLine("Please report this to the program author.");
}
unlocked = true;
break;
}
else if (!unlockResponse[0].IsNak)
{
throw new InvalidOperationException(
$"Received non-ACK/NAK ${unlockResponse[0].Title:X2} from unlock request.");
}
}
return (unlocked, softwareVersion);
}
private const int MaxAccessLevel = 7;
/// <summary>
/// Tries to perform seed/key authentication with cluster.
/// </summary>
/// <param name="softwareVersion">Software version string like "VQMJ07LM 09.00"</param>
public void SeedKeyAuthenticate(string? softwareVersion)
{
// Perform Seed/Key authentication
Log.WriteLine("Sending Custom \"Seed request\" block");
var response = SendCustom([0x96, 0x01]);
var responseBlocks = response.Where(b => !b.IsAckNak).ToList();
if (responseBlocks is [CustomBlock customBlock])
{
Log.WriteLine($"Block: {Utils.Dump(customBlock.Body)}");
var keyBytes = VdoKeyFinder.FindKey(
customBlock.Body.ToArray(), MaxAccessLevel);
Log.WriteLine("Sending Custom \"Key response\" block");
var keyResponse = new List<byte> { 0x96, 0x02 };
keyResponse.AddRange(keyBytes);
_ = SendCustom(keyResponse);
}
}
public bool RequiresSeedKey()
{
var accessLevel = GetAccessLevel();
return accessLevel != MaxAccessLevel;
}
private int? GetAccessLevel()
{
Log.WriteLine("Sending Custom \"Get Access Level\" block");
var response = SendCustom([0x96, 0x04]);
var responseBlocks = response.Where(b => !b.IsAckNak).ToList();
if (responseBlocks is [CustomBlock])
{
int accessLevel = responseBlocks[0].Body.First();
Log.WriteLine($"Access level is {accessLevel}.");
return accessLevel;
}
else
{
Log.WriteLine("Access level is unknown.");
return null;
}
}
/// <summary>
/// Given a VDO cluster EEPROM dump, attempt to determine the SKC and return it if found.
/// </summary>
/// <param name="bytes">A portion of a VDO cluster EEPROM dump.</param>
/// <param name="startAddress">The start address of bytes within the EEPROM.</param>
/// <returns>The SKC or null if the SKC could not be determined.</returns>
public static ushort? GetSkc(byte[] bytes, int startAddress)
{
string text = Encoding.ASCII.GetString(bytes);
// There are several EEPROM formats. We can determine the format by locating the
// 14-character immobilizer ID and noting its offset in the dump.
var immoMatch = Regex.Match(
text,
@"[A-Z]{2}Z\dZ0[A-Z]\d{7}");
if (!immoMatch.Success)
{
Log.WriteLine("GetSkc: Unable to find Immobilizer ID in cluster dump.");
return null;
}
ushort skc;
var index = immoMatch.Index + startAddress;
switch (index)
{
case 0x090:
case 0x0AC:
// Immo2
skc = Utils.GetBcd(bytes, 0x0BA - startAddress);
return skc;
case 0x0A2:
// VWK501
skc = Utils.GetShort(bytes, 0x0CC - startAddress);
return skc;
case 0x0E0:
// VWK503
skc = Utils.GetShort(bytes, 0x10A - startAddress);
return skc;
default:
Log.WriteLine(
$"GetSkc: Unknown EEPROM (Immobilizer offset: 0x{immoMatch.Index:X3})");
return null;
}
}
/// <summary>
/// http://www.maltchev.com/kiti/VAG_guide.txt
/// This unlocks additional custom commands $81-$AF
/// </summary>
private void CustomUnlockAdditionalCommands()
{
Log.WriteLine("Sending Custom \"Unlock Additional Commands\" block");
SendCustom([0x80, 0x01, 0x02, 0x03, 0x04]);
}
/// <summary>
/// Different cluster models have different unlock codes. Return the appropriate one based
/// on the cluster's software version.
/// </summary>
internal static byte[][] GetClusterUnlockCodes(string softwareVersion)
{
switch(softwareVersion)
{
case "VT5P07MH 09.00": // 7H5920872L VDO V03
return [[0x00, 0x07, 0x43, 0x35]];
case "VAT500LL 01.00":
case "VAT500LL 01.20": // 1J0920905L V01
case "VAT500MH 01.10": // 1J0920925D V06
case "VAT500MH 01.20": // 1J5920925C V09
return [[0x01, 0x04, 0x3D, 0x35]];
case "$01 $00 $14 $01": // 1J0919860B V15
return [[0x01, 0x08, 0x05, 0x02]];
case "V798MLA 01.00": // 7D0920800F V01, 1J0919951C V55
return [[0x02, 0x03, 0x05, 0x09]];
case "$00 $00 $13 $01": // 8D0919880M D02
return [[0x09, 0x06, 0x05, 0x02]];
case "VSQX01LM 01.00": // 6Q0920800 V11
return [[0x31, 0x39, 0x34, 0x46]];
case "VCLM09MH $00 $09": // 3BD920848E V03
return [[0x32, 0x31, 0x36, 0x31]];
case "VCB07LL 09.00": // 1JD920826E V01
return [[0x33, 0x34, 0x46, 0x4A]];
case "VKQ501HH 09.00":
case "VQMJ07HH 08.40": // 6Y0920843L V04
case "VQMJ07LM 08.40": // 6Q0920923Q V02
case "VQMJ07LM 09.00": // 6Q0920804Q V06
return [[0x34, 0x3F, 0x43, 0x39]];
case "VQMJ06LM 09.00": // 6Q0920903 V02
return [[0x35, 0x3D, 0x47, 0x3E]];
case "SS5501LM 00.80":
case "SS5501ML 00.80":
return [[0x36, 0x3B, 0x36, 0x3D]];
case "VWK501LL 00.88": // 1J0920906L V58
case "VWK501MH 00.88":
case "VWK501LL 01.00":
case "VWK501MH 01.00":
return [[0x36, 0x3D, 0x3E, 0x47]];
case "VT5X02LL 09.40":
return [[0x36, 0x3F, 0x45, 0x42]];
case "VQMJ09HH 05.10": // 6QE920827C V06
return [[0x37, 0x42, 0x47, 0x43]];
case "VWK502MH 09.00":
return [[0x38, 0x37, 0x3E, 0x31]];
case "VT5X02LL 09.00":
return [[0x38, 0x39, 0x3A, 0x47]];
case "S599CAA 01.00": // 1M0920800C V15
case "V599HLA 00.91": // 7D0920841A V18
case "V599LLA 00.91": // 7D0920801B V18
case "V599LLA 01.00": // 1J0920800L V59
case "V599LLA 03.00": // 1J0920900J V60
case "V599MLA 01.00": // 7D0920821D V22
case "V599MLA 03.00": // 3B0920920B V26
return [[0x38, 0x3F, 0x40, 0x35]];
case "MPV300LL 04.00":
case "MPV501MH 01.00": // 7M3920820H V57
return [[0x38, 0x47, 0x34, 0x3A]];
case "VWK501MH 00.92": // 3B0920827C V06
case "VWK501MH 01.10":
return [[0x39, 0x34, 0x34, 0x40]];
case "VBK700LL 00.96":
case "VBK700LL 01.00":
case "VBKX00MH 01.00":
return [[0x3A, 0x39, 0x31, 0x43]];
case "MPV300LL 02.00":
return [[0x3B, 0x47, 0x03, 0x02]];
case "SS5501LM 01.00": // 1M0920802D V05
case "SS5501ML 01.00":
return [[0x3C, 0x34, 0x47, 0x35]];
case "VSQX01LM 01.20":
return [[0x3D, 0x36, 0x40, 0x36]];
case "S599CAA 00.80":
return [[0x3D, 0x39, 0x3B, 0x35]];
case "KB5M07HH 09.00": // 3U0920842B V06
case "VWK503LL 09.00":
case "VWK503MH 09.00": // 1J0920927 V02
return [[0x3E, 0x35, 0x3D, 0x3A]];
case "VMMJ08MH 09.00": // 1J5920826L V75
return [[0x3E, 0x47, 0x3D, 0x48]];
case "MPV300LL 00.90":
case "MPV500LL 00.90":
return [[0x3F, 0x38, 0x43, 0x38]];
case "SS5500LM 01.00":
return [[0x40, 0x39, 0x39, 0x38]];
case "VSQX01LM 01.10": // 6Q0920900 V18
return [[0x43, 0x43, 0x3D, 0x37]];
case "MPV300LL 03.00":
return [[0x43, 0x43, 0x43, 0x39]];
case "KPQMLA` $01": // 6Y1920860G V12
return [[0x47, 0x3B, 0x31, 0x3F]];
case "K5MJ07HH 09.00": // 5J0920840B V92
case "K5MJ07LM 08.10": // 5J0920810C V2721446
case "K5MJ07LM 09.00": // 5J0920900B V2823466
return [[0x47, 0x3F, 0x39, 0x44]];
default:
return ClusterUnlockCodes;
}
}
private static string SoftwareVersionToString(List<byte> versionBytes)
{
if (versionBytes.Count < 9 || versionBytes.Count > 10)
{
return Utils.DumpMixedContent(versionBytes);
}
var asciiPart = Encoding.ASCII.GetString(versionBytes.ToArray()[0..^2]);
return $"{asciiPart} {versionBytes[^1]:X2}.{versionBytes[^2]:X2}";
}
internal static readonly byte[][] ClusterUnlockCodes =
[
[0x00, 0x00, 0x00, 0x00],
[0x00, 0x00, 0x03, 0x02],
[0x00, 0x01, 0x03, 0x02],
[0x00, 0x02, 0x03, 0x02],
[0x00, 0x02, 0x09, 0x07],
[0x00, 0x03, 0x03, 0x02],
[0x00, 0x03, 0x04, 0x02],
[0x00, 0x04, 0x03, 0x02],
[0x00, 0x04, 0x06, 0x07],
[0x00, 0x05, 0x03, 0x02],
[0x00, 0x06, 0x03, 0x02],
[0x00, 0x07, 0x02, 0x04],
[0x00, 0x07, 0x03, 0x08],
[0x00, 0x07, 0x43, 0x35],
[0x00, 0x08, 0x02, 0x04],
[0x01, 0x00, 0x03, 0x02],
[0x01, 0x00, 0x09, 0x05],
[0x01, 0x01, 0x00, 0x04],
[0x01, 0x01, 0x00, 0x05],
[0x01, 0x01, 0x00, 0x06],
[0x01, 0x01, 0x00, 0x07],
[0x01, 0x01, 0x00, 0x08],
[0x01, 0x01, 0x00, 0x09],
[0x01, 0x01, 0x01, 0x00],
[0x01, 0x01, 0x01, 0x01],
[0x01, 0x01, 0x01, 0x02],
[0x01, 0x01, 0x01, 0x03],
[0x01, 0x01, 0x01, 0x04],
[0x01, 0x01, 0x01, 0x05],
[0x01, 0x01, 0x01, 0x06],
[0x01, 0x01, 0x03, 0x02],
[0x01, 0x01, 0x03, 0x07],
[0x01, 0x01, 0x05, 0x08],
[0x01, 0x01, 0x07, 0x09],
[0x01, 0x02, 0x03, 0x02],
[0x01, 0x03, 0x03, 0x02],
[0x01, 0x04, 0x02, 0x02],
[0x01, 0x04, 0x03, 0x02],
[0x01, 0x04, 0x3D, 0x35],
[0x01, 0x05, 0x03, 0x02],
[0x01, 0x05, 0x06, 0x08],
[0x01, 0x05, 0x3D, 0x35],
[0x01, 0x06, 0x00, 0x02],
[0x01, 0x06, 0x02, 0x00],
[0x01, 0x06, 0x03, 0x02],
[0x01, 0x06, 0x04, 0x02],
[0x01, 0x07, 0x00, 0x03],
[0x01, 0x08, 0x02, 0x05],
[0x01, 0x08, 0x03, 0x00],
[0x01, 0x08, 0x05, 0x02],
[0x02, 0x00, 0x03, 0x02],
[0x02, 0x00, 0x06, 0x01],
[0x02, 0x01, 0x03, 0x02],
[0x02, 0x02, 0x03, 0x02],
[0x02, 0x02, 0x04, 0x01],
[0x02, 0x02, 0x09, 0x02],
[0x02, 0x03, 0x03, 0x02],
[0x02, 0x03, 0x05, 0x09],
[0x02, 0x04, 0x00, 0x02],
[0x02, 0x04, 0x03, 0x02],
[0x02, 0x05, 0x00, 0x02],
[0x02, 0x05, 0x03, 0x02],
[0x02, 0x05, 0x06, 0x09],
[0x02, 0x05, 0x08, 0x01],
[0x02, 0x06, 0x03, 0x02],
[0x02, 0x06, 0x06, 0x09],
[0x02, 0x09, 0x02, 0x06],
[0x02, 0x09, 0x04, 0x02],
[0x02, 0x09, 0x04, 0x03],
[0x02, 0x32, 0x3B, 0x37],
[0x03, 0x00, 0x03, 0x02],
[0x03, 0x00, 0x03, 0x07],
[0x03, 0x00, 0x07, 0x01],
[0x03, 0x01, 0x03, 0x02],
[0x03, 0x02, 0x03, 0x02],
[0x03, 0x02, 0x05, 0x02],
[0x03, 0x03, 0x03, 0x02],
[0x03, 0x03, 0x08, 0x04],
[0x03, 0x03, 0x09, 0x03],
[0x03, 0x04, 0x03, 0x02],
[0x03, 0x05, 0x03, 0x02],
[0x03, 0x06, 0x03, 0x02],
[0x03, 0x08, 0x02, 0x05],
[0x04, 0x00, 0x03, 0x02],
[0x04, 0x01, 0x03, 0x02],
[0x04, 0x01, 0x03, 0x08],
[0x04, 0x02, 0x03, 0x02],
[0x04, 0x02, 0x06, 0x06],
[0x04, 0x03, 0x03, 0x02],
[0x04, 0x04, 0x03, 0x02],
[0x04, 0x04, 0x09, 0x04],
[0x04, 0x05, 0x03, 0x02],
[0x04, 0x05, 0x05, 0x02],
[0x04, 0x06, 0x03, 0x02],
[0x04, 0x07, 0x00, 0x07],
[0x05, 0x00, 0x03, 0x02],
[0x05, 0x01, 0x03, 0x02],
[0x05, 0x01, 0x04, 0x08],
[0x05, 0x02, 0x03, 0x02],
[0x05, 0x02, 0x03, 0x09],
[0x05, 0x02, 0x09, 0x02],
[0x05, 0x03, 0x03, 0x02],
[0x05, 0x04, 0x03, 0x02],
[0x05, 0x05, 0x03, 0x02],
[0x05, 0x05, 0x08, 0x09],
[0x05, 0x05, 0x09, 0x05],
[0x05, 0x06, 0x03, 0x02],
[0x05, 0x08, 0x05, 0x02],
[0x06, 0x00, 0x02, 0x02],
[0x06, 0x00, 0x03, 0x00],
[0x06, 0x00, 0x03, 0x02],
[0x06, 0x01, 0x03, 0x02],
[0x06, 0x02, 0x03, 0x02],
[0x06, 0x03, 0x03, 0x02],
[0x06, 0x04, 0x03, 0x02],
[0x06, 0x04, 0x07, 0x01],
[0x06, 0x05, 0x03, 0x02],
[0x06, 0x06, 0x03, 0x02],
[0x06, 0x06, 0x09, 0x06],
[0x06, 0x09, 0x01, 0x02],
[0x06, 0x09, 0x03, 0x09],
[0x06, 0x09, 0x05, 0x03],
[0x07, 0x00, 0x03, 0x02],
[0x07, 0x00, 0x06, 0x04],
[0x07, 0x01, 0x03, 0x02],
[0x07, 0x02, 0x03, 0x02],
[0x07, 0x03, 0x03, 0x02],
[0x07, 0x03, 0x05, 0x03],
[0x07, 0x04, 0x03, 0x02],
[0x07, 0x05, 0x03, 0x02],
[0x07, 0x06, 0x03, 0x02],
[0x07, 0x07, 0x09, 0x04],
[0x07, 0x07, 0x09, 0x07],
[0x08, 0x00, 0x03, 0x02],
[0x08, 0x01, 0x03, 0x02],
[0x08, 0x01, 0x06, 0x05],
[0x08, 0x02, 0x01, 0x04],
[0x08, 0x02, 0x03, 0x02],
[0x08, 0x02, 0x03, 0x05],
[0x08, 0x03, 0x03, 0x02],
[0x08, 0x04, 0x02, 0x02],
[0x08, 0x04, 0x03, 0x02],
[0x08, 0x05, 0x03, 0x02],
[0x08, 0x06, 0x03, 0x02],
[0x08, 0x06, 0x07, 0x06],
[0x08, 0x08, 0x09, 0x08],
[0x09, 0x00, 0x03, 0x02],
[0x09, 0x01, 0x01, 0x07],
[0x09, 0x01, 0x03, 0x02],
[0x09, 0x02, 0x03, 0x02],
[0x09, 0x02, 0x06, 0x06],
[0x09, 0x03, 0x03, 0x02],
[0x09, 0x03, 0x09, 0x06],
[0x09, 0x04, 0x03, 0x02],
[0x09, 0x05, 0x02, 0x03],
[0x09, 0x05, 0x03, 0x02],
[0x09, 0x05, 0x05, 0x08],
[0x09, 0x06, 0x03, 0x02],
[0x09, 0x06, 0x04, 0x09],
[0x09, 0x06, 0x05, 0x02],
[0x09, 0x09, 0x03, 0x02],
[0x09, 0x09, 0x09, 0x09],
[0x31, 0x39, 0x34, 0x46],
[0x31, 0x44, 0x35, 0x43],
[0x32, 0x31, 0x36, 0x31],
[0x32, 0x37, 0x3E, 0x31],
[0x33, 0x34, 0x46, 0x4A],
[0x34, 0x3F, 0x43, 0x39],
[0x35, 0x3B, 0x39, 0x3D],
[0x35, 0x3C, 0x31, 0x3C],
[0x35, 0x3D, 0x04, 0x01],
[0x35, 0x3D, 0x47, 0x3E],
[0x35, 0x40, 0x3F, 0x38],
[0x35, 0x43, 0x31, 0x38],
[0x35, 0x47, 0x34, 0x3C],
[0x36, 0x3B, 0x36, 0x3D],
[0x36, 0x3D, 0x3E, 0x47],
[0x36, 0x3F, 0x45, 0x42],
[0x36, 0x40, 0x36, 0x3D],
[0x37, 0x39, 0x3C, 0x47],
[0x37, 0x3B, 0x32, 0x02],
[0x37, 0x3D, 0x43, 0x43],
[0x37, 0x42, 0x47, 0x43],
[0x38, 0x34, 0x34, 0x37],
[0x38, 0x37, 0x3E, 0x31],
[0x38, 0x39, 0x39, 0x40],
[0x38, 0x39, 0x3A, 0x47],
[0x38, 0x3F, 0x40, 0x35],
[0x38, 0x43, 0x38, 0x3F],
[0x38, 0x47, 0x34, 0x3A],
[0x39, 0x34, 0x34, 0x40],
[0x39, 0x43, 0x43, 0x43],
[0x3A, 0x31, 0x31, 0x36],
[0x3A, 0x34, 0x47, 0x38],
[0x3A, 0x39, 0x31, 0x43],
[0x3A, 0x39, 0x41, 0x43],
[0x3A, 0x3B, 0x35, 0x3C],
[0x3A, 0x3B, 0x35, 0x4C],
[0x3A, 0x3D, 0x35, 0x3E],
[0x3B, 0x33, 0x3E, 0x37],
[0x3B, 0x3A, 0x37, 0x3E],
[0x3B, 0x46, 0x23, 0x10],
[0x3B, 0x46, 0x23, 0x1B],
[0x3B, 0x46, 0x23, 0x1D],
[0x3B, 0x47, 0x03, 0x02],
[0x3C, 0x31, 0x3C, 0x35],
[0x3C, 0x34, 0x47, 0x35],
[0x3D, 0x36, 0x40, 0x36],
[0x3D, 0x39, 0x3B, 0x35],
[0x3E, 0x35, 0x3D, 0x3A],
[0x3E, 0x35, 0x43, 0x30],
[0x3E, 0x35, 0x43, 0x39],
[0x3E, 0x35, 0x43, 0x40],
[0x3E, 0x35, 0x43, 0x41],
[0x3E, 0x35, 0x43, 0x42],
[0x3E, 0x35, 0x43, 0x43],
[0x3E, 0x35, 0x43, 0x44],
[0x3E, 0x39, 0x31, 0x43],
[0x3E, 0x39, 0x35, 0x40],
[0x3E, 0x39, 0x43, 0x34],
[0x3E, 0x3F, 0x40, 0x35],
[0x3E, 0x47, 0x3D, 0x48],
[0x3F, 0x31, 0x3B, 0x47],
[0x3F, 0x38, 0x43, 0x38],
[0x3F, 0x43, 0x35, 0x3E],
[0x40, 0x30, 0x3E, 0x39],
[0x40, 0x34, 0x34, 0x39],
[0x40, 0x39, 0x39, 0x38],
[0x40, 0x43, 0x35, 0x3E],
[0x41, 0x43, 0x35, 0x3E],
[0x42, 0x43, 0x35, 0x3E],
[0x42, 0x45, 0x3F, 0x36],
[0x43, 0x31, 0x39, 0x3A],
[0x43, 0x43, 0x35, 0x3E],
[0x43, 0x43, 0x3D, 0x37],
[0x43, 0x43, 0x43, 0x39],
[0x43, 0x45, 0x31, 0x3D],
[0x44, 0x43, 0x35, 0x3E],
[0x45, 0x39, 0x34, 0x43],
[0x47, 0x3A, 0x39, 0x38],
[0x47, 0x3B, 0x31, 0x3F],
[0x47, 0x3C, 0x39, 0x37],
[0x47, 0x3E, 0x3D, 0x36],
[0x47, 0x3F, 0x39, 0x44],
];
private static string DumpMixedContent(Block block)
{
if (block.IsNak)
{
return "NAK";
}
return Utils.DumpMixedContent(block.Body);
}
private static string DumpBinaryContent(Block block)
{
if (block.IsNak)
{
return "NAK";
}
return Utils.DumpBytes(block.Body);
}
private void DumpEeprom(
ushort startAddr, ushort length, byte maxReadLength, string fileName)
{
bool succeeded = true;
using (var fs = File.Create(fileName, maxReadLength, FileOptions.WriteThrough))
{
for (uint addr = startAddr; addr < (startAddr + length); addr += maxReadLength)
{
byte readLength = (byte)Math.Min(startAddr + length - addr, maxReadLength);
List<byte>? blockBytes = _kwp1281.ReadEeprom((ushort)addr, readLength);
if (blockBytes == null)
{
blockBytes = Enumerable.Repeat((byte)0, readLength).ToList();
succeeded = false;
}
fs.Write(blockBytes.ToArray(), 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();
}
}
public void WriteRam(ushort address, byte value)
{
Log.WriteLine("Sending Custom \"Write RAM\" block");
SendCustom(
[
0x87,
1, // Count
(byte)(address & 0xFF),
(byte)((address >> 8) & 0xFF),
value
]);
}
private readonly IKW1281Dialog _kwp1281;
private bool _additionalCustomCommandsUnlocked;
public VdoCluster(IKW1281Dialog kwp1281)
{
_kwp1281 = kwp1281;
_additionalCustomCommandsUnlocked = false;
}
}
================================================
FILE: Cluster/VdoKeyFinder.cs
================================================
using System;
using System.Collections.Generic;
using System.Linq;
namespace BitFab.KW1281Test.Cluster
{
public static class VdoKeyFinder
{
/// <summary>
/// Takes a 10-byte seed block, desired access level and optional cluster software version and generates an
/// 8-byte key block.
/// </summary>
public static byte[] FindKey(
byte[] seed, int accessLevel)
{
if (seed.Length != 10)
{
throw new InvalidOperationException(
$"Unexpected seed length: {seed.Length} (Expected 10)");
}
byte[] secret;
switch (seed[8])
{
case 0x01 when seed[9] == 0x00:
secret = Secrets0100[accessLevel];
break;
case 0x03 when seed[9] == 0x00:
secret = Secrets0300[accessLevel];
break;
case 0x09 when seed[9] == 0x00:
secret = Secrets0900[accessLevel];
break;
case 0x0B when seed[9] == 0x00:
secret = Secrets0B00[accessLevel];
break;
case 0x0D when seed[9] == 0x00:
secret = Secrets0D00[accessLevel];
break;
default:
Log.WriteLine(
$"Unexpected seed suffix: ${seed[8]:X2} ${seed[9]:X2}");
secret = Secrets0100[accessLevel]; // Try something
break;
}
Log.WriteLine($"Access level {accessLevel} secret: {Utils.DumpBytes(secret)}");
var key = CalculateKey(
[seed[1], seed[3], seed[5], seed[7]],
secret);
return [(byte)accessLevel, key[0], key[1], 0x00, key[2], 0x00, key[3]];
}
/// <summary>
/// Table of secrets, one for each access level.
/// </summary>
private static readonly byte[][] Secrets0100 =
[
[0xe5, 0x7c, 0x20, 0xb3], // AccessLevel 0
[0x67, 0xb8, 0xf0, 0xe2],
[0x59, 0xd0, 0x4f, 0xcb],
[0x46, 0x83, 0xb6, 0x27],
[0xc9, 0xde, 0xe3, 0xca],
[0x7f, 0x50, 0x44, 0xbc],
[0x4b, 0xd0, 0x7f, 0xad],
[0x55, 0x16, 0xa8, 0x94] // AccessLevel 7
];
/// <summary>
/// Table of secrets, one for each access level.
/// </summary>
private static readonly byte[][] Secrets0300 =
[
[0x4c, 0x29, 0x92, 0x1b], // AccessLevel 0
[0x42, 0x0a, 0x0b, 0x66],
[0x1c, 0x4c, 0x91, 0x4d],
[0xe2, 0xfd, 0xa2, 0x28],
[0x48, 0x34, 0x58, 0x71],
[0xb1, 0xf5, 0xd0, 0xb8],
[0xac, 0xfc, 0x5e, 0x6c],
[0x98, 0xe1, 0x56, 0x5f] // AccessLevel 7
];
/// <summary>
/// Table of secrets, one for each access level.
/// </summary>
private static readonly byte[][] Secrets0900 =
[
[0xa7, 0xd2, 0xe9, 0x8d], // AccessLevel 0
[0xe6, 0xfa, 0x9e, 0xba],
[0x63, 0x92, 0xe3, 0x08],
[0x55, 0x3e, 0x68, 0x24],
[0x03, 0x2a, 0x70, 0xdc],
[0xe7, 0xb4, 0x71, 0x86],
[0x4f, 0x58, 0xcd, 0x81],
[0xfd, 0x8e, 0x31, 0x96] // AccessLevel 7
];
/// <summary>
/// Table of secrets, one for each access level.
/// </summary>
private static readonly byte[][] Secrets0D00 =
[
[0xc9, 0x18, 0xe6, 0x6e], // AccessLevel 0
[0x69, 0xc3, 0x08, 0xcd],
[0x37, 0x15, 0xd3, 0x23],
[0xe1, 0xe1, 0xa9, 0x3b],
[0x19, 0x74, 0x72, 0x18],
[0x08, 0x2b, 0x49, 0x1a],
[0x82, 0xd1, 0x7d, 0x50],
[0x0a, 0x5b, 0x41, 0x4f] // AccessLevel 7
];
private static readonly byte[][] Secrets0B00 =
[
[0x47, 0x36, 0x9a, 0xbb], // AccessLevel 0
[0xad, 0x4e, 0x61, 0x44],
[0xd3, 0xd6, 0x42, 0x59],
[0x13, 0x6f, 0x43, 0x74],
[0xfc, 0xb8, 0x59, 0x2e],
[0x09, 0x58, 0x9d, 0x7f],
[0x24, 0x27, 0xc3, 0x9d],
[0x87, 0xed, 0x34, 0x63] // AccessLevel 7
];
/// <summary>
/// Takes a 4-byte seed and calculates a 4-byte key.
/// </summary>
private static byte[] CalculateKey(
IReadOnlyList<byte> seed,
IReadOnlyList<byte> secret)
{
var work = new byte[] { seed[0], seed[1], seed[2], seed[3], 0x00, 0x00 };
var secretBuf = secret.ToArray();
Scramble(work);
var y = work[0] & 0x07;
var temp = y + 1;
var a = LeftRotate(0x01, y);
do
{
var set = ((secretBuf[0] ^ secretBuf[1] ^ secretBuf[2] ^ secretBuf[3]) & 0x40) != 0;
secretBuf[3] = SetOrClearBits(secretBuf[3], a, set);
RightRotateFirst4Bytes(secretBuf, 0x01);
temp--;
}
while (temp != 0);
for (var x = 0; x < 2; x++)
{
work[4] = work[0];
work[0] ^= work[2];
work[5] = work[1];
work[1] ^= work[3];
work[3] = work[5];
work[2] = work[4];
LeftRotateFirstTwoBytes(work, work[2] & 0x07);
y = x << 1;
var carry = true;
(work[0], carry) = Utils.SubtractWithCarry(work[0], secretBuf[y], carry);
(work[1], _) = Utils.SubtractWithCarry(work[1], secretBuf[y + 1], carry);
}
Scramble(work);
return [work[0], work[1], work[2], work[3]];
}
private static void Scramble(byte[] work)
{
work[4] = work[0];
work[0] = work[1];
work[1] = work[3];
work[3] = work[2];
work[2] = work[4];
}
private static byte SetOrClearBits(
byte value, byte mask, bool set)
{
if (set)
{
return (byte)(value | mask);
}
else
{
return (byte)(value & (byte)(mask ^ 0xFF));
}
}
/// <summary>
/// Right-Rotate the first 4 bytes of a buffer count times.
/// </summary>
private static void RightRotateFirst4Bytes(
byte[] buf, int count)
{
while (count != 0)
{
var carry = (buf[0] & 0x01) != 0;
(buf[3], carry) = Utils.RightRotate(buf[3], carry);
(buf[2], carry) = Utils.RightRotate(buf[2], carry);
(buf[1], carry) = Utils.RightRotate(buf[1], carry);
(buf[0], _) = Utils.RightRotate(buf[0], carry);
count--;
}
}
private static void LeftRotateFirstTwoBytes(
byte[] work, int count)
{
while (count > 0)
{
var carry = (work[1] & 0x80) != 0;
(work[0], carry) = Utils.LeftRotate(work[0], carry);
(work[1], _) = Utils.LeftRotate(work[1], carry);
count--;
}
}
/// <summary>
/// Left-Rotate a value count-times.
/// </summary>
private static byte LeftRotate(
byte value, int count)
{
while (count != 0)
{
var carry = (value & 0x80) != 0;
(value, _) = Utils.LeftRotate(value, carry);
count--;
}
return value;
}
}
}
================================================
FILE: ControllerAddress.cs
================================================
namespace BitFab.KW1281Test
{
/// <summary>
/// VW controller addresses
/// </summary>
enum ControllerAddress
{
Ecu = 0x01,
CentralElectric = 0x09,
Cluster = 0x17,
CanGateway = 0x19,
Immobilizer = 0x25,
CentralLocking = 0x35,
Navigation = 0x37,
CCM = 0x46,
Radio = 0x56,
RadioManufacturing = 0x7C,
}
}
================================================
FILE: ControllerIdent.cs
================================================
using BitFab.KW1281Test.Blocks;
using System;
using System.Collections.Generic;
using System.Text;
namespace BitFab.KW1281Test
{
/// <summary>
/// The info returned by the controller to a ReadIdent block.
/// </summary>
internal class ControllerIdent
{
public ControllerIdent(IEnumerable<Block> blocks)
{
var sb = new StringBuilder();
foreach (var block in blocks)
{
if (block is AsciiDataBlock asciiBlock)
{
sb.Append(asciiBlock);
}
else if (block is CodingWscBlock codingWscBlock)
{
sb.AppendLine();
sb.Append(codingWscBlock);
}
else
{
Log.WriteLine($"ReadIdent returned block of type {block.GetType()}");
}
}
Text = sb.ToString();
}
public string Text { get; }
public override string ToString()
{
return Text;
}
}
}
================================================
FILE: ControllerInfo.cs
================================================
using BitFab.KW1281Test.Blocks;
using System;
using System.Collections.Generic;
using System.Text;
namespace BitFab.KW1281Test
{
/// <summary>
/// The info returned when a controller wakes up.
/// </summary>
internal class ControllerInfo
{
public ControllerInfo(IEnumerable<Block> blocks)
{
var sb = new StringBuilder();
foreach (var block in blocks)
{
if (block is AsciiDataBlock asciiBlock)
{
sb.Append(asciiBlock);
if (asciiBlock.MoreDataAvailable)
{
MoreDataAvailable = true;
}
}
else if (block is CodingWscBlock codingBlock)
{
sb.Append($"{Environment.NewLine}{codingBlock}");
SoftwareCoding = codingBlock.SoftwareCoding;
WorkshopCode = codingBlock.WorkshopCode;
}
else
{
Log.WriteLine($"Controller wakeup returned block of type {block.GetType()}");
}
}
Text = sb.ToString();
}
public string Text { get; }
public bool MoreDataAvailable { get; }
public int SoftwareCoding { get; }
public int WorkshopCode { get; }
public override string ToString()
{
return Text;
}
}
}
================================================
FILE: EDC15/Edc15VM.cs
================================================
using BitFab.KW1281Test.Kwp2000;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace BitFab.KW1281Test.EDC15
{
public class Edc15VM
{
public byte[] ReadWriteEeprom(
string filename,
List<KeyValuePair<ushort, byte>>? addressValuePairs = null)
{
addressValuePairs ??= [];
var kwp2000 = new KW2000Dialog(_kwpCommon, (byte)_controllerAddress);
_ = kwp2000.SendReceive(DiagnosticService.startDiagnosticSession, [0x89]);
_ = kwp2000.SendReceive(DiagnosticService.startDiagnosticSession, [0x85]);
const byte accMod = 0x41;
var resp = kwp2000.SendReceive(DiagnosticService.securityAccess, [accMod]);
// ECU normally doesn't require seed/key authentication the first time it wakes up in
// KWP2000 mode so sending an empty key is sufficient.
var buf = new List<byte> { accMod + 1 };
if (!resp.Body.SequenceEqual(new byte[] { accMod, 0x00, 0x00 }))
{
// Normally we'll only get here if we wake up the ECU and it's already in KWP2000 mode,
// which can happen if a previous download attempt did not complete. In that case we
// need to calculate and send back a real key.
var seedBuf = resp.Body.Skip(1).Take(4).ToArray();
var keyBuf = LVL41Auth(0x508DA647, 0x3800000, seedBuf);
buf.AddRange(keyBuf);
}
_ = kwp2000.SendReceive(DiagnosticService.securityAccess, buf.ToArray());
var loader = Edc15VM.GetLoader();
var len = loader.Length;
// Ask the ECU to accept our loader and store it in RAM
_ = kwp2000.SendReceive(DiagnosticService.requestDownload, [
0x40, 0xE0, 0x00, // Load address 0x40E000
0x00, // Not compressed, not encrypted
(byte)(len >> 16), (byte)(len >> 8), (byte)(len & 0xFF) // Length
],
excludeAddresses: true);
// Break the loader into blocks and send each one
var maxBlockLen = resp.Body[0];
var s = new MemoryStream(loader);
while (true)
{
Thread.Sleep(5);
var blockBytes = new byte[maxBlockLen];
var readCount = s.Read(blockBytes, 0, maxBlockLen - 1);
if (readCount == 0)
{
break;
}
_ = kwp2000.SendReceive(
DiagnosticService.transferData, blockBytes.Take(readCount).ToArray(),
excludeAddresses: true);
}
// Ask the ECU to execute our loader
kwp2000.SendMessage(
DiagnosticService.startRoutineByLocalIdentifier, [0x02],
excludeAddresses: true);
_ = kwp2000.ReceiveMessage();
// Custom loader command to send all 512 bytes of the EEPROM
kwp2000.SendMessage(
(DiagnosticService)0xA6, [],
excludeAddresses: true);
resp = kwp2000.ReceiveMessage();
if (!resp.IsPositiveResponse(DiagnosticService.transferData))
{
throw new InvalidOperationException($"Dump EEPROM failed.");
}
var eeprom = new byte[512];
for (var i = 0; i < 512; i++)
{
eeprom[i] = _kwpCommon.Interface.ReadByte();
}
File.WriteAllBytes(filename, eeprom);
Log.WriteLine($"Saved EEPROM to {filename}");
_ = kwp2000.ReceiveMessage();
// Now write any supplied values
foreach (var addressValuePair in addressValuePairs)
{
var service = (DiagnosticService)(
addressValuePair.Key > 0xFF
? 0xA8 // Write 1 byte to EEPROM (Page 1)
: 0xA7); // Write 1 byte to EEPROM (Page 0)
kwp2000.SendMessage(
service, [],
excludeAddresses: true);
resp = kwp2000.ReceiveMessage();
if (!resp.IsPositiveResponse(DiagnosticService.transferData))
{
throw new InvalidOperationException($"Write EEPROM failed.");
}
var address = (byte)(addressValuePair.Key & 0xFF);
var value = addressValuePair.Value;
_kwpCommon.WriteByte(address);
_kwpCommon.WriteByte(value);
Log.WriteLine($"Sent: {address:X2} {value:X2}");
resp = kwp2000.ReceiveMessage();
if (!resp.IsPositiveResponse(DiagnosticService.transferData))
{
throw new InvalidOperationException($"Write EEPROM failed.");
}
}
// Custom loader command to reboot the ECU to return it to normal operation.
kwp2000.SendMessage(
(DiagnosticService)0xA2, [],
excludeAddresses: true);
_ = kwp2000.ReceiveMessage();
var b = _kwpCommon.Interface.ReadByte();
if (b == 0x55)
{
Log.WriteLine($"Reboot successful!");
}
return eeprom;
}
public static void DisplayEepromInfo(ReadOnlySpan<byte> eeprom)
{
var skc = Utils.GetShort(eeprom, 0x12E);
Log.WriteLine($"SKC: {skc:D5}");
double odometerKm =
eeprom[0x1BF] +
(eeprom[0x1C0] << 8) +
(eeprom[0x1C1] << 16) +
((eeprom[0x1C2] & 0x3F) << 24);
odometerKm /= 100.0;
Log.WriteLine($"Odometer: {odometerKm} km");
var vin = Utils.DumpAscii(eeprom.Slice(0x140, 17).ToArray());
Log.WriteLine($"VIN: {vin}");
var immoNumber = Utils.DumpAscii(eeprom.Slice(0x131, 14).ToArray());
Log.WriteLine($"Immo Number: {immoNumber}");
var immoId = Utils.DumpBytes(eeprom.Slice(0x126, 7).ToArray());
Log.WriteLine($"Immo Id: {immoId}");
const ushort immo1Addr = 0x1B0;
var immo1 = eeprom[immo1Addr];
const ushort immo2Addr = 0x1DE;
var immo2 = eeprom[immo2Addr];
var immoStatus = immo1 == 0x60 && immo2 == 0x60 ? "Off" : "On";
Log.WriteLine($"Immo is {immoStatus} (${immo1Addr:X3}=${immo1:X2}, ${immo2Addr:X3}=${immo2:X2})");
}
/// <summary>
/// This algorithm borrowed from https://github.com/fjvva/ecu-tool
/// Thanks to Javier Vazquez Vidal https://github.com/fjvva
/// </summary>
private static byte[] LVL41Auth(long key, long key3, byte[] buf)
{
// long Key3 = 0x3800000;
long tempstring = buf[0];
tempstring <<= 8;
var keyread1 = tempstring + buf[1];
tempstring = buf[2];
tempstring <<= 8;
var keyread2 = tempstring + buf[3];
// Process the algorithm
var key2 = key;
key2 &= 0xFFFF;
key >>= 16;
var key1 = key;
for (byte counter = 0; counter < 5; counter++)
{
var keyTemp = keyread1;
keyTemp &= 0x8000;
keyread1 <<= 1;
var temp1 = keyTemp & 0x0FFFF;
if (temp1 == 0)
{
var temp2 = keyread2 & 0xFFFF;
var temp3 = keyTemp & 0xFFFF0000;
keyTemp = temp2 + temp3;
keyread1 &= 0xFFFE;
temp2 = keyTemp & 0xFFFF;
temp2 >>= 0x0F;
keyTemp &= 0xFFFF0000;
keyTemp += temp2;
keyread1 |= keyTemp;
keyread2 <<= 0x01;
}
else
{
keyTemp = keyread2 + keyread2;
keyread1 &= 0xFFFE;
var temp2 = keyTemp & 0xFF;
temp2 |= 1;
var temp3 = key3 & 0xFFFFFF00;
key3 = temp2 + temp3;
key3 &= 0xFFFF00FF;
key3 |= keyTemp;
temp2 = keyread2 & 0xFFFF;
temp3 = keyTemp & 0xFFFF0000;
keyTemp = temp2 + temp3;
temp2 = keyTemp & 0xFFFF;
temp2 >>= 0x0F;
keyTemp &= 0xFFFF0000;
keyTemp += temp2;
keyTemp |= keyread1;
key3 ^= key1;
keyTemp ^= key2;
keyread2 = key3;
keyread1 = keyTemp;
}
}
//Done with the key generation
keyread2 &= 0xFFFF; // Clean first and second word from garbage
keyread1 &= 0xFFFF;
var keybuf = new byte[4];
keybuf[1] = (byte)keyread1;
keyread1 >>= 8;
keybuf[0] = (byte)keyread1;
keybuf[3] = (byte)keyread2;
keyread2 >>= 8;
keybuf[2] = (byte)keyread2;
return keybuf;
}
/// <summary>
/// Loader that can read/write the serial EEPROM.
/// </summary>
private static byte[] GetLoader()
{
var assembly = Assembly.GetEntryAssembly()!;
var resourceStream = assembly.GetManifestResourceStream(
"BitFab.KW1281Test.EDC15.Loader.bin");
if (resourceStream == null)
{
throw new InvalidOperationException(
$"Unable to load BitFab.KW1281Test.EDC15.Loader.bin embedded resource.");
}
var loaderLength = resourceStream.Length + 4; // Add 4 bytes for checksum correction
loaderLength = (loaderLength + 7) / 8 * 8; // Round up to a multiple of 8 bytes
var buf = new byte[loaderLength];
resourceStream.ReadExactly(buf, 0, (int)resourceStream.Length);
// In order for this loader to be executed by the ECU, the checksum of all the bytes
// must be EFCD8631.
// Patch the loader with the location of the end (actually 1 byte past the end)
ushort loaderEnd = (ushort)(0xE000 + loaderLength);
buf[0x0E] = (byte)(loaderEnd & 0xFF);
buf[0x0F] = (byte)(loaderEnd >> 8);
// Take the checksum of the loader up to but not including the checksum correction
ushort r6 = 0xEFCD;
ushort r1 = 0x8631;
Checksum(ref r6, ref r1, buf.Take(buf.Length - 4).ToArray());
// Calculate the checksum correction bytes and insert them at the end of the loader
var padding = CalcPadding(r6, r1);
Array.Copy(padding, 0, buf, buf.Length - 4, 4);
return buf;
}
/// <summary>
/// Calculate the checksum correction padding needed to result in a checksum of EFCD8631
/// </summary>
/// <param name="r6"></param>
/// <param name="r1"></param>
/// <returns></returns>
private static byte[] CalcPadding(ushort r6, ushort r1)
{
var paddingH = (ushort)(0xDF9B ^ r6);
var paddingL = (ushort)(r1 - 0xAB85);
return
[
(byte)(paddingL & 0xFF),
(byte)(paddingL >> 8),
(byte)(paddingH & 0xFF),
(byte)(paddingH >> 8)
];
}
/// <summary>
/// EDC15 checksum algorithm (sub_1584).
/// Calculates a 32-bit checksum of an array of bytes based on an initial 32-bit seed.
/// Based on https://www.ecuconnections.com/forum/viewtopic.php?f=211&t=49704&sid=5cf324c44d2c74d372984f428ffea5ed
/// </summary>
/// <param name="r6">Input: High word of seed, Output: High word of checksum</param>
/// <param name="r1">Input: Low word of seed, Output: Low word of checksum</param>
/// <param name="buf">Buffer to calculate checksum for</param>
static void Checksum(ref ushort r6, ref ushort r1, byte[] buf)
{
int r3 = 0; // Buffer index
int r0 = buf.Length;
while (true)
{
r1 ^= GetBuf(buf, r3); r3 += 2;
r1 = Rol(r1, r6, out ushort c);
r6 = (ushort)(r6 - GetBuf(buf, r3) - c); r3 += 2;
r6 ^= r1;
if (r3 >= r0)
{
break;
}
r1 = (ushort)(r1 - GetBuf(buf, r3) - 1); r3 += 2;
r1 += 0xDAAD;
r6 ^= GetBuf(buf, r3); r3 += 2;
r6 = Ror(r6, r1);
if (r3 >= r0)
{
break;
}
}
}
/// <summary>
/// Rotates a 16-bit value right by count bits.
/// </summary>
private static ushort Ror(ushort value, ushort count)
{
count &= 0xF;
value = (ushort)((value >> count) | (value << (16 - count)));
return value;
}
/// <summary>
/// Rotates a 16-bit value left by count bits. Carry will be equal to the last bit rotated
/// or 0 if the low 4 bits of count are 0;
/// </summary>
private static ushort Rol(ushort value, ushort count, out ushort carry)
{
count &= 0xF;
value = (ushort)((value << count) | (value >> (16 - count)));
carry = ((value & 1) == 0 || (count == 0)) ? (ushort)0 : (ushort)1;
return value;
}
private static ushort GetBuf(byte[] buf, int ix)
{
return (ushort)(buf[ix] + (buf[ix + 1] << 8));
}
private readonly IKwpCommon _kwpCommon;
private readonly int _controllerAddress;
public Edc15VM(IKwpCommon kwpCommon, int controllerAddress)
{
_kwpCommon = kwpCommon;
_controllerAddress = controllerAddress;
}
}
}
================================================
FILE: EDC15/Loader.a66
================================================
; Custom loader. When loaded at address 40E000 and started via startRoutineByLocalIdentifier 0x02,
; it will accept several custom KWP2000-like commands via the K-Line to allow dumping the EEPROM
; and other special functions.
; The following Linux command will convert the Intel Hex-86 format output to raw binary:
; srec_cat Loader.H86 -Intel -Output Loader.bin -Binary
$M167
$NOLI
$INCLUDE (REG167.INC)
$LI
All SECTION CODE AT 0E000H
DB 0A5H, 0A5H, 14H, 0E0H, 00H, 00H, 3CH, 0E0H
DB 00H, 00H
DW RoutineStart
DB 00H, 00H
DW RoutineEnd
DB 00H, 00H, 02H, 47H, 13H, 00H, 00H, 01H
DB 00H, 02H, 00H, 03H, 00H, 04H, 00H, 05H
DB 00H, 06H, 00H, 07H, 00H, 08H, 00H, 09H
DB 00H, 0AH, 00H, 0BH, 00H, 0CH, 00H, 0DH
DB 00H, 0EH, 00H, 0FH, 80H, 0FH, 0A0H, 0FH
DB 0C0H, 0FH, 00H, 10H, 45H, 2FH, 7DH, 64H
DB 9BH, 0C3H
; Entry
RoutineStart PROC FAR
BCLR IEN ; Disable interrupts
BSET S0REN ; Receiver enabled
MOV R0,#0E7FEH ; "SP"
; Receive and dispatch message
E04A: CALL E128 ; Receive message
CMP R6,#1 ; Timeout?
JMPR CC_Z,E0C6
CMP R6,#2 ; Bad checksum?
JMPR CC_Z,E082 ; Send NAK
MOV R1,#0E600H ; Receive buffer
MOVB RL2,[R1+#0001H] ; Service => RL2
CMPB RL2,#0023H ; 23: Read flash
JMPR CC_Z,Cmd23
CMPB RL2,#0036H ; 36: Program flash
JMPR CC_Z,Cmd36
CMPB RL2,#00A2H ; A2: Reboot
JMPR CC_Z,CmdA2
CMPB RL2,#00A3H ; A3: Respond with 0x55
JMPR CC_Z,CmdA3
CMPB RL2,#00A4H ; A4: Set baud rate
JMPR CC_Z,CmdA4
CMPB RL2,#00A5H ; A5: Erase flash
JMPR CC_Z,CmdA5
CMPB RL2,#00A6H ; A6: Dump EEPROM
JMPR CC_Z,CmdA6
CMPB RL2,#00A7H ; A7: Write 1 byte to EEPROM (Page 0)
JMPR CC_Z,CmdA7
CMPB RL2,#00A8H ; A8: Write 1 byte to EEPROM (Page 1)
JMPR CC_Z,CmdA8
E082: CALL SendNAK
JMPR CC_UC,E04A ; Receive and dispatch message
; Read flash
Cmd23: CALL E23E
JMPR CC_UC,E04A ; Receive and dispatch message
; Program flash
Cmd36: CALL E1C4 ; Program flash
Cmd36x: CALL SendAck ; Send ACK
JMPR CC_UC,E04A ; Receive and dispatch message
; Reboot
CmdA2: CALL SendACK
CALL DELAY256
MOVB RL7,#0055H
CALL XmitRL7
SRST ; Reboot
; Respond with 0x55
CmdA3: MOVB RL7,#0055H
CmdA3x: CALL XmitRL7
JMPR CC_UC,E04A ; Receive and dispatch message
; Set baud rate (buf[2] => S0BG)
CmdA4: CALL SendACK
CALL Delay256
MOV R2,#00H
MOVB RL2,[R1+#0002H]
MOV S0BG,R2
MOVB RL7,#00AAH
JMPR CC_UC,CmdA3x ; XmitRL7 + jump to dispatcher
; Erase flash
CmdA5: CALL SendACK
CALL E2E6 ; Erase flash
JMPR CC_UC,Cmd36x ; SendACK + jump to dispatcher
; Dump EEPROM
CmdA6: CALL SendACK
CALL E1C6 ; Dump EEPROM
JMPR CC_UC,Cmd36x ; SendACK + jump to dispatcher
; Write 1 byte to EEPROM (Page 0)
CmdA7: CALL SendACK
CALL E324 ; Write 1 byte to EEPROM (Page 0)
JMPR CC_UC,Cmd36x ; SendACK + jump to dispatcher
; Write 1 byte to EEPROM (Page 1)
CmdA8: CALL SendACK
CALL E356
JMPR CC_UC,Cmd36x ; SendACK + jump to dispatcher
; Handle timeout?
E0C6: MOVB RL7,[R1]
ADDB RL7,#1
JMPR CC_UC,CmdA3x ; XmitRL7 + jump to dispatcher
RoutineStart ENDP
; Send NAK
SendNAK PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
MOV R1,#0E600H ; Transmit buf
MOVB RL2,#01H ; Length 1
MOVB [R1],RL2
MOVB RL2,#007FH ; Code 7F (NAK)
JMPR CC_UC,SendACK2
SendNAK ENDP
; Send ACK
SendACK PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
MOV R1,#0E600H ; Transmit buf
MOVB RL2,#01H
MOVB [R1],RL2
MOVB RL2,#0076H ; Code 76 (transferData positive response)
SendACK2:
MOVB [R1+#0001H],RL2
CALL E15E ; Send message
JMPR CC_UC,E156x ; Pop R2,R1 + RET
SendACK ENDP
; Receive message into buf (E600) - Status returned in R6 (0: Success, 1: Timeout, 2: Checksum error)
; This code has a bug where it does not initialize R6 to 0 and doesn't call read-with-timeout so 1 will never be returned.
E128 PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
MOV [-R0],R3 ; Push R3
MOV R1,#0E600H ; Receive buf
CALL E192 ; Receive a byte in R7
MOV R2,#00H ; 0 => R2
MOVB RL2,RL7 ; RL7 => RL2 (RL2 is message length)
MOVB RL3,RL7 ; RL7 => RL3 (RL3 is message checksum)
MOVB [R1],RL7 ; Put received byte in buf
ADD R1,#1 ; Advance to next buf location
E140: CALL E1A2 ; Receive a byte in R7. R6: 0 if success, 1 if timeout
CMP R6,#0 ; Success?
JMPR CC_NZ,E156 ; Return if error
MOVB [R1],RL7 ; Put received byte in buf
ADDB RL3,RL7 ; RL3 += RL7
ADD R1,#1 ; Advance to next buf location
CMPD1 R2,#00H ; (R2-- == 0)?
JMPR CC_NZ,E140 ; Loop if not
SUBB RL3,RL7 ; RL3 - RL7 => RL3
CMPB RL3,RL7 ; (RL3 == RL7)?
JMPR CC_Z,E156 ; Return if true
MOV R6,#02H ; Checksum error
E156: MOV R3,[R0+] ; Pop R3
E156x: MOV R2,[R0+] ; Pop R2
E156y: MOV R1,[R0+] ; Pop R1
RET
E128 ENDP
; Send message in buf (E600)
E15E PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
MOV [-R0],R3 ; Push R3
MOV R1,#0E600H ; Transmit buf
MOV R2,#00H
MOVB RL2,[R1] ; RL2 is length
MOVB RL3,#00H ; R3 is checksum
E16E: MOVB RL7,[R1+] ; message byte => RL7
ADDB RL3,RL7 ; checksum += byte
CALL XmitRL7
CMPD1 R2,#00H
JMPR CC_NZ,E16E ; Loop until all characters sent
MOVB RL7,RL3 ; checksum => RL7
CALL XmitRL7
JMPR CC_UC,E156 ; Pop R3,R2,R1 + RET
E15E ENDP
; Transmit byte in R7
XmitRL7 PROC NEAR
MOVB S0TBUF,RL7 ; Transmit RL7
JMPR CC_UC,E192x ; Receive echo in R7
XmitRL7 ENDP
; Receive a byte in R7
E192 PROC NEAR
E192x:
SRVWDT ; Feed watchdog
JNB S0RIR,E192x ; Loop until receive flag set
MOV R7,S0RBUF ; R7 = received byte
BCLR S0RIR ; Clear receive flag
RET
E192 ENDP
; Receive a byte in R7. R6: 0 if success, 1 if timeout.
E1A2 PROC NEAR
MOV [-R0],R1 ; Push R1
MOV R1,#0FFFFH ; Timeout to receive a byte
MOV R6,#00H ; Success => R6
E1AA: SRVWDT ; Feed watchdog
JB S0RIR,E1BA ; Jump if byte received
CMPD1 R1,#00H
JMPR CC_NZ,E1AA ; Loop is not timeout
MOV R6,#01H ; Timeout => R6
JMPR CC_UC,E1C0
E1BA: MOV R7,S0RBUF ; Byte => R7
BCLR S0RIR ; Clear receive flag
E1C0: JMPR CC_UC,E156y ; Pop R1 + RET
E1A2 ENDP
; Dump EEPROM
E1C6 PROC NEAR
BSET DP2.8 ; P2.8 is an output
BSET P2.8 ; 1 => P2.8
BSET DP2.9 ; P2.9 is an output
BSET P2.9 ; 1 => P2.9
CALL Delay256
CALL Delay256
CALL E246 ; 11 01 00 - Start bit
MOVB RL7,#00ACH ; 1010 1100 (Dummy Write)
CALL E2AC ; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
CALL E27A ; Clock (P2.9) one bit of P2.8 into R7.0
MOVB RL7,#00H ; Address 0
CALL E2AC ; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
CALL E27A ; Clock (P2.9) one bit of P2.8 into R7.0
CALL E25C ; 0x 01 11
CALL E246 ; 11 01 00
MOVB RL7,#00ADH ; 1010 1101 (Read)
CALL E2AC ; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
CALL E27A ; Clock (P2.9) one bit of P2.8 into R7.0
MOV R5,#00H
MOV R2,#01FEH ; Count = 512-2
E20A: MOV R5,#07H
E20C: CALL Delay256
CMPD1 R5,#00H
JMPR CC_NZ,E20C
CALL E2C6 ; Clock (P2.9) 8 bits of P2.8 into RL7 (MSB first)
CALL E298 ; 0x 01 00
CALL XmitRL7
CMPD1 R2,#00H
JMPR CC_NZ,E20A
CALL E2C6 ; Clock (P2.9) 8 bits of P2.8 into RL7 (MSB first)
CALL XmitRL7
BSET P2.9 ; 1 => P2.9
BSET P2.8 ; 1 => P2.8
CALL Delay7
BCLR P2.9 ; 0 => P2.9
CALL Delay7
BCLR P2.8 ; 0 => P2.8
CALL Delay7
JMPR CC_UC,E25C ; 0x 01 11
E1C6 ENDP
; 11 01 00
E246 PROC NEAR
BSET P2.8 ; 1 => P2.8
BSET P2.9 ; 1 => P2.9
CALL Delay7
BCLR P2.8 ; 0 => P2.8
CALL Delay7
BCLR P2.9 ; 0 => P2.9
CALL Delay7
RET
E246 ENDP
; 0x 01 11
E25C PROC NEAR
BCLR P2.8 ; 0 => P2.8
CALL Delay7
BSET P2.9 ; 1 => P2.9
CALL Delay7
BSET P2.8 ; 1 => P2.8
RET
E25C ENDP
; x1 x0
E26C PROC NEAR
CALL Delay7
BSET P2.9 ; 1 => P2.9
CALL Delay7
BCLR P2.9 ; 0 => P2.9
RET
E26C ENDP
; Clock (P2.9) one bit of P2.8 into R7.0
E27A PROC NEAR
MOV R7,#00H
BCLR DP2.8 ; P2.8 is an input
CALL Delay7
BSET P2.9 ; 1 => P2.9
CALL Delay7
BMOV R7.0,P2.8 ; P2.8 => R7.0
BCLR P2.9 ; 0 => P2.9
CALL Delay7
BCLR P2.8 ; 0 => P2.8
BSET DP2.8 ; P2.8 is an output
RET
E27A ENDP
; 0x 01 00
E298 PROC NEAR
BCLR P2.8 ; 0 => P2.8
CALL Delay7
BSET P2.9 ; 1 => P2.9
CALL Delay7
BCLR P2.9 ; 0 => P2.9
CALL Delay7
RET
E298 ENDP
; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
E2AC PROC NEAR
MOV [-R0],R1 ; Push R1
MOV R1,#07H ; Count = 7
SHL R7,#08H
E2B2: SHL R7,#01H
BMOV P2.8,C ; High bit of R7 => P2.8
CALL E26C ; x1 x0 (clock)
CMPD1 R1,#00H ; Count-- == 0?
JMPR CC_NZ,E2B2 ; Repeat
BCLR P2.8 ; 0 => P2.8
JMPR CC_UC,PopR1
E2AC ENDP
; Clock (P2.9) 8 bits of P2.8 into RL7 (MSB first)
E2C6 PROC NEAR
MOV [-R0],R1 ; Push R1
BCLR DP2.8 ; P2.8 is an input
MOV R1,#07H ; Count = 7
MOV R7,#00H
E2CE: BSET P2.9 ; 1 => P2.9
CALL Delay7
SHL R7,#01H
BMOV R7.0,P2.8 ; P2.8 => Low bit of R7
BCLR P2.9 ; 0 => P2.9
CALL Delay7
CMPD1 R1,#00H ; Count-- == 0?
JMPR CC_NZ,E2CE ; Repeat
BSET DP2.8 ; P2.8 is an output
BCLR P2.8 ; 0 => P2.8
JMPR CC_UC,PopR1
E2C6 ENDP
Delay256 PROC NEAR
MOV [-R0],R3 ; Push R3
MOV R3,#0100H
JMPR CC_UC,DelayLoop
Delay256 ENDP
Delay7 PROC NEAR
MOV [-R0],R3 ; Push R3
MOV R3,#7
DelayLoop:
SRVWDT ; Feed watchdog
SUB R3,#1
CMP R3,#00H
JMPR CC_NZ,DelayLoop
MOV R3,[R0+] ; Pop R3
RET
Delay7 ENDP
; Write 1 byte to EEPROM (Page 0)
E324 PROC NEAR
E324x:
CALL E246 ; 11 01 00 - Start bit
MOV R7,#00ACH ; 1010 1100 (Write)
CALL E2AC ; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
CALL E27A ; Clock (P2.9) one bit of P2.8 into R7.0
CMP R7,#0
JMPR CC_NZ,E324x
JMPR CC_UC,E356y
E324 ENDP
; Write 1 byte to EEPROM (Page 1)
E356 PROC NEAR
E356x:
CALL E246 ; 11 01 00 - Start bit
MOV R7,#00AEH ; 1010 1110
CALL E2AC ; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
CALL E27A ; Clock (P2.9) one bit of P2.8 into R7.0
CMP R7,#0
JMPR CC_NZ,E356x
E356y:
CALL E192 ; Receive a byte in R7
CALL E2AC ; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
CALL E27A ; Clock (P2.9) one bit of P2.8 into R7.0
CALL E192 ; Receive a byte in R7
CALL E2AC ; Clock (P2.9) all bits of RL7 (MSB first) to P2.8
CALL E27A ; Clock (P2.9) one bit of P2.8 into R7.0
JMPR CC_UC,E25C ; 0x 01 11
E356 ENDP
; Program flash
E1C4 PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
MOV [-R0],R3 ; Push R3
MOV [-R0],R4 ; Push R4
MOV R3,#0E600H ; Buf
MOV R2,#00H
MOVB RL2,[R3+#0002H] ; Address high
MOVB RH1,[R3+#0003H] ; Address medium
MOVB RL1,[R3+#0004H] ; Address low
MOV R4,#00H
MOVB RL4,[R3] ; Count
SUB R4,#4
SHR R4,#01H
SUB R4,#1
ADD R3,#5
E1EA: SRVWDT ; Feed watchdog
MOVB RL7,[R3+]
MOVB RH7,[R3+]
CALL E208 ; Program flash (R1: Address, R7: Data)
ADD R1,#2
ADDC R2,#0
CMPD1 R4,#00H
JMPR CC_NZ,E1EA
PopR4: MOV R4,[R0+] ; Pop R4
PopR3: MOV R3,[R0+] ; Pop R3
PopR2: MOV R2,[R0+] ; Pop R2
PopR1: MOV R1,[R0+] ; Pop R1
RET
E1C4 ENDP
; Program flash (R1: Address, R7: Data)
E208 PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R7 ; Push R7
MOV R1,#0AAAAH ; Address AAA
MOVB RL7,#00AAH ; Data AA
CALL E374 ; Write extended data byte (source is RL7)
MOV R1,#5555H ; Address 555
MOVB RL7,#0055H ; Data 55
CALL E374 ; Write extended data byte (source is RL7)
MOV R1,#0AAAAH ; Address AAA
MOVB RL7,#00A0H ; Data A0
CALL E374 ; Write extended data byte (source is RL7)
MOV R7,[R0+] ; Pop R7
MOV R1,[R0+] ; Pop R1
CALL E384 ; Write extended data word (source is R7)
CALL E354 ; Wait until operation is complete
RET
E208 ENDP
; Read flash
E23E PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
MOV [-R0],R3 ; Push R3
MOV [-R0],R4 ; Push R4
MOV [-R0],R5 ; Push R5
MOV R3,#0E600H ; Buf
MOV R2,#00H
MOVB RL2,[R3+#0002H] ; Address high
MOVB RH1,[R3+#0003H] ; Address medium
MOVB RL1,[R3+#0004H] ; Address low
MOV R4,#00H
MOVB RL4,[R3+#0005H] ; Count
ADDB RL4,#1
MOVB [R3],RL4
ADD R3,#1
MOVB RL7,#0036H
MOVB [R3],RL7
ADD R3,#1
SUBB RL4,#1
E270: CALL E290 ; Read extended data (returned in RL7)
MOVB [R3],RL7
ADD R3,#1
ADD R1,#1
ADDC R2,#0
CMPD1 R4,#00H
JMPR CC_NZ,E270
CALL E15E ; Send message in buf (E600)
MOV R5,[R0+] ; Pop R5
JMPR CC_UC,PopR4
E23E ENDP
; Read extended data (returned in RL7)
E290 PROC NEAR
MOV [-R0],R1 ; Push R1
SRVWDT ; Feed watchdog
CALL E2A4 ; Set Data Page Pointer 0 based on R2
AND R1,#3FFFH
MOVB RL7,[R1]
JMPR CC_UC,PopR1
E290 ENDP
; Set Data Page Pointer 0 based on R1, R2
E2A4 PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
SHL R2,#02H
AND R2,#00FCH
ROL R1,#02H
AND R1,#3
OR R2,R1
MOV DPP0,R2 ; R2 => Data Page Pointer 0
JMPR CC_UC,PopR2
E2A4 ENDP
; Erase flash
E2E6 PROC NEAR
MOV [-R0],R1 ; Push R1
MOV [-R0],R2 ; Push R2
MOV [-R0],R3 ; Push R3
MOV [-R0],R7 ; Push R7
MOV R3,#0E600H ; Buf
MOV R2,#0020H
MOV R1,#0AAAAH ; Address AAA
MOVB RL7,#00AAH ; Data AA
CALL E374 ; Write extended data byte (source is RL7)
MOV R1,#5555H ; Address 555
MOVB RL7,#0055H ; Data 55
CALL E374 ; Write extended data byte (source is RL7)
MOV R1,#0AAAAH ; Address AAA
MOVB RL7,#0080H ; Data 80
CALL E374 ; Write extended data byte (source is RL7)
MOV R1,#0AAAAH ; Address AAA
MOVB RL7,#00AAH ; Data AA
CALL E374 ; Write extended data byte (source is RL7)
MOV R1,#5555H ; Address 555
MOVB RL7,#0055H ; Data 55
CALL E374 ; Write extended data byte (source is RL7)
MOV R1,#0AAAAH ; Address AAA
MOVB RL7,#0010H ; Data 10
CALL E374 ; Write extended data byte (source is RL7)
CALL Delay256
MOVB RL7,#0080H
CALL E354 ; Wait until erase operation is complete
MOV R7,[R0+] ; Pop R7
JMPR CC_UC,PopR3
E2E6 ENDP
; Wait until operation is complete
E354 PROC NEAR
MOV [-R0],R2 ; Push R2
MOV [-R0],R7 ; Push R7
SUB R2,#0018H
MOVB RH7,RL7
ANDB RH7,#0080H
E362: CALL E290 ; Read extended data (returned in RL7)
ANDB RL7,#0080H
CMPB RL7,RH7
JMPR CC_NZ,E362
MOV R7,[R0+] ; Pop R7
MOV R2,[R0+] ; Pop R2
RET
E354 ENDP
; Write extended data byte (source is RL7)
E374 PROC NEAR
MOV [-R0],R1 ; Push R1
CALL E2A4 ; Set Data Page Pointer 0 based on R2
AND R1,#3FFFH
MOVB [R1],RL7
JMPR CC_UC,E384x ; Pop R1 + RET
E374 ENDP
; Write extended data word (source is R7)
E384 PROC NEAR
MOV [-R0],R1 ; Push R1
CALL E2A4 ; Set Data Page Pointer 0 based on R2
AND R1,#3FFFH
MOV [R1],R7
E384x: MOV R1,[R0+] ; Pop R1
RET
E384 ENDP
RoutineEnd:
All ENDS
END
================================================
FILE: Interface/FtdiInterface.cs
================================================
using System;
using System.IO.Ports;
using System.Reflection;
using System.Runtime.InteropServices;
namespace BitFab.KW1281Test.Interface
{
internal class FtdiInterface : IInterface
{
private readonly FT _ft;
private IntPtr _handle = IntPtr.Zero;
private readonly byte[] _buf = new byte[1];
public FtdiInterface(string serialNumber, int baudRate)
{
_ft = new FT();
var status = _ft.Open(
serialNumber, FT.OpenExFlags.BySerialNumber, out _handle);
FT.AssertOk(status);
status = _ft.SetBaudRate(_handle, (uint)baudRate);
FT.AssertOk(status);
status = _ft.SetDataCharacteristics(
_handle,
FT.Bits.Eight,
FT.StopBits.One,
FT.Parity.None);
FT.AssertOk(status);
status = _ft.SetFlowControl(
_handle,
FT.FlowControl.None, 0, 0);
FT.AssertOk(status);
SetRts(false);
SetDtr(true);
_readTimeout = _writeTimeout = ((IInterface)this).DefaultTimeoutMilliseconds;
ReadTimeout = _readTimeout; // Also sets the write timeout
// Should allow faster response times for small packets
status = _ft.SetLatencyTimer(_handle, 2);
FT.AssertOk(status);
}
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
SetDtr(false);
var status = _ft.Close(_handle);
_handle = IntPtr.Zero;
FT.AssertOk(status);
}
_ft.Dispose();
}
public byte ReadByte()
{
var status = _ft.Read(_handle, _buf, 1, out uint countOfBytesRead);
FT.AssertOk(status);
if (countOfBytesRead != 1)
{
throw new TimeoutException("Read timed out");
}
var b = _buf[0];
return b;
}
/// <summary>
/// Write a byte to the interface but do not read/discard its echo.
/// </summary>
public void WriteByteRaw(byte b)
{
_buf[0] = b;
var status = _ft.Write(_handle, _buf, 1, out uint countOfBytesWritten);
FT.AssertOk(status);
if (countOfBytesWritten != 1)
{
throw new InvalidOperationException(
$"Expected to write 1 byte but wrote {countOfBytesWritten} bytes");
}
}
public void SetBreak(bool on)
{
FT.Status status;
if (on)
{
status = _ft.SetBreakOn(_handle);
}
else
{
status = _ft.SetBreakOff(_handle);
}
FT.AssertOk(status);
}
public void ClearReceiveBuffer()
{
var status = _ft.Purge(_handle, FT.PurgeMask.RX);
FT.AssertOk(status);
}
public void SetBaudRate(int baudRate)
{
var status = _ft.SetBaudRate(_handle, (uint)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;
if (on)
{
status = _ft.SetDtr(_handle);
}
else
{
status = _ft.ClrDtr(_handle);
}
FT.AssertOk(status);
}
public void SetRts(bool on)
{
FT.Status status;
if (on)
{
status = _ft.SetRts(_handle);
}
else
{
status = _ft.ClrRts(_handle);
}
FT.AssertOk(status);
}
private int _readTimeout;
public int ReadTimeout
{
get => _readTimeout;
set
{
var status = _ft.SetTimeouts(
_handle,
(uint)value,
(uint)WriteTimeout);
FT.AssertOk(status);
_readTimeout = value;
}
}
private int _writeTimeout;
public int WriteTimeout
{
get => _writeTimeout;
set
{
var status = _ft.SetTimeouts(
_handle,
(uint)ReadTimeout,
(uint)value);
FT.AssertOk(status);
_writeTimeout = value;
}
}
}
class FT : IDisposable
{
private IntPtr _d2xx = IntPtr.Zero;
// Delegates used to call into the FTID D2xx DLL
#pragma warning disable CS0649
private readonly FTDll.SetVidPid _setVidPid;
private readonly FTDll.OpenBySerialNumber _openBySerialNumber;
private readonly FTDll.Close _close;
private readonly FTDll.SetBaudRate _setBaudRate;
private readonly FTDll.SetDataCharacteristics _setDataCharacteristics;
private readonly FTDll.SetFlowControl _setFlowControl;
private readonly FTDll.SetDtr _setDtr;
private readonly FTDll.ClrDtr _clrDtr;
private readonly FTDll.SetRts _setRts;
private readonly FTDll.ClrRts _clrRts;
private readonly FTDll.SetTimeouts _setTimeouts;
private readonly FTDll.SetLatencyTimer _setLatencyTimer;
private readonly FTDll.Purge _purge;
private readonly FTDll.SetBreakOn _setBreakOn;
private readonly FTDll.SetBreakOff _setBreakOff;
private readonly FTDll.Read _read;
private readonly FTDll.Write _write;
#pragma warning restore CS0649
public FT()
{
string libName;
bool isMacOs = false;
bool isLinux = false;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
libName = "libftd2xx.dylib";
isMacOs = true;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
libName = "ftd2xx.so";
isLinux = true;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
libName = Environment.Is64BitProcess ? "ftd2xx64.dll" : "ftd2xx.dll";
}
else
{
throw new InvalidOperationException($"Unknown OS: {RuntimeInformation.OSDescription}");
}
_d2xx = NativeLibrary.Load(
libName, typeof(FT).Assembly, DllImportSearchPath.SafeDirectories);
InitDelegate(nameof(_openBySerialNumber), out _openBySerialNumber);
InitDelegate(nameof(_close), out _close);
InitDelegate(nameof(_setBaudRate), out _setBaudRate);
InitDelegate(nameof(_setDataCharacteristics), out _setDataCharacteristics);
InitDelegate(nameof(_setFlowControl), out _setFlowControl);
InitDelegate(nameof(_setDtr), out _setDtr);
InitDelegate(nameof(_clrDtr), out _clrDtr);
InitDelegate(nameof(_setRts), out _setRts);
InitDelegate(nameof(_clrRts), out _clrRts);
InitDelegate(nameof(_setTimeouts), out _setTimeouts);
InitDelegate(nameof(_setLatencyTimer), out _setLatencyTimer);
InitDelegate(nameof(_purge), out _purge);
InitDelegate(nameof(_setBreakOn), out _setBreakOn);
InitDelegate(nameof(_setBreakOff), out _setBreakOff);
InitDelegate(nameof(_read), out _read);
InitDelegate(nameof(_write), out _write);
if (isMacOs || isLinux)
{
InitDelegate(nameof(_setVidPid), out _setVidPid);
}
else
{
_setVidPid = (uint vid, uint pid) => Status.Ok;
}
if (isMacOs || isLinux)
{
var vidStr = Environment.GetEnvironmentVariable("FTDI_VID");
var pidStr = Environment.GetEnvironmentVariable("FTDI_PID");
if (!string.IsNullOrEmpty(vidStr) && !string.IsNullOrEmpty(pidStr))
{
var vid = Utils.ParseUint(vidStr);
var pid = Utils.ParseUint(pidStr);
Log.WriteLine($"Setting FTDI VID=0x{vid:X4}, PID=0x{pid:X4}");
var status = SetVidPid(vid, pid);
AssertOk(status);
}
}
}
private void InitDelegate<T>(string fieldName, out T delegateVal) where T : Delegate
{
var symbolNameAttribute = typeof(T).GetCustomAttribute<SymbolNameAttribute>();
if (symbolNameAttribute == null)
{
throw new InvalidOperationException(
$"Type {typeof(T)} is missing required SymbolName attribute.");
}
var nativeMethodName = symbolNameAttribute.Name;
var export = NativeLibrary.GetExport(_d2xx, nativeMethodName);
delegateVal = Marshal.GetDelegateForFunctionPointer<T>(export);
}
public void Dispose()
{
if (_d2xx != IntPtr.Zero)
{
NativeLibrary.Free(_d2xx);
_d2xx = IntPtr.Zero;
}
}
public static void AssertOk(FT.Status status)
{
if (status != FT.Status.Ok)
{
throw new InvalidOperationException(
$"D2xx library returned {status} instead of Ok");
}
}
public Status SetVidPid(
uint vid,
uint pid)
{
return _setVidPid(vid, pid);
}
public Status Open(
string serialNumber,
OpenExFlags flags,
out IntPtr handle)
{
return _openBySerialNumber(serialNumber, flags, out handle);
}
public Status Close(
IntPtr handle)
{
return _close(handle);
}
public Status SetBaudRate(
IntPtr handle,
uint baudRate)
{
return _setBaudRate(handle, baudRate);
}
public Status SetDataCharacteristics(
IntPtr handle,
Bits wordLength,
StopBits stopBits,
Parity parity)
{
return _setDataCharacteristics(handle, wordLength, stopBits, parity);
}
public Status SetFlowControl(
IntPtr handle,
FlowControl flowControl,
byte xonChar,
byte xoffChar)
{
return _setFlowControl(handle, flowControl, xonChar, xoffChar);
}
public Status SetDtr(
IntPtr handle)
{
return _setDtr(handle);
}
public Status ClrDtr(
IntPtr handle)
{
return _clrDtr(handle);
}
public Status SetRts(
IntPtr handle)
{
return _setRts(handle);
}
public Status ClrRts(
IntPtr handle)
{
return _clrRts(handle);
}
public Status SetTimeouts(
IntPtr handle,
uint readTimeoutMS,
uint writeTimeoutMS)
{
return _setTimeouts(handle, readTimeoutMS, writeTimeoutMS);
}
public Status SetLatencyTimer(
IntPtr handle,
byte timerMS)
{
return _setLatencyTimer(handle, timerMS);
}
public Status Purge(
IntPtr handle,
PurgeMask mask)
{
return _purge(handle, mask);
}
public Status SetBreakOn(
IntPtr handle)
{
return _setBreakOn(handle);
}
public Status SetBreakOff(
IntPtr handle)
{
return _setBreakOff(handle);
}
public Status Read(
IntPtr handle,
byte[] buffer,
uint countOfBytesToRead,
out uint countOfBytesRead)
{
return _read(handle, buffer, countOfBytesToRead, out countOfBytesRead);
}
public Status Write(
IntPtr handle,
byte[] buffer,
uint countOfBytesToWrite,
out uint countOfBytesWritten)
{
return _write(handle, buffer, countOfBytesToWrite, out countOfBytesWritten);
}
public enum Status : uint
{
Ok = 0,
InvalidHandle,
DeviceNotFound,
DeviceNotOpened,
IOError,
insufficient_resources,
InvalidParameter,
InvalidBaudRate,
DeviceNotOpenedForErase,
DeviceNotOpenedForWrite,
FailedToWriteDevice,
EepromReadFailed,
EepromWriteFailed,
EepromEraseFailed,
EepromNotPresent,
EepromNotProgrammed,
InvalidArgs,
NotSupported,
OtherError,
DeviceListNotReady,
};
[Flags]
public enum OpenExFlags : uint
{
BySerialNumber = 1,
ByDescription = 2,
ByLocation = 4
};
public enum Bits : byte
{
Eight = 8,
Seven = 7
};
public enum StopBits : byte
{
One = 0,
Two = 2
};
public enum Parity : byte
{
None = 0,
Odd = 1,
Even = 2,
Mark = 3,
Space = 4
};
public enum FlowControl : ushort
{
None = 0x0000,
RtsCts = 0x0100,
DtrDsr = 0x0200,
XonXoff = 0x0400
};
[Flags]
public enum PurgeMask : uint
{
RX = 1,
TX = 2
};
}
[AttributeUsage(AttributeTargets.Delegate)]
internal class SymbolNameAttribute : Attribute
{
public SymbolNameAttribute(string name)
{
Name = name;
}
public string Name { get; private set; }
}
static class FTDll
{
[SymbolName("FT_SetVIDPID")]
public delegate FT.Status SetVidPid(
uint vid, uint pid);
[SymbolName("FT_OpenEx")]
public delegate FT.Status OpenBySerialNumber(
[MarshalAs(UnmanagedType.LPStr)] string serialNumber,
FT.OpenExFlags flags,
out IntPtr handle);
[SymbolName("FT_Close")]
public delegate FT.Status Close(
IntPtr handle);
[SymbolName("FT_SetBaudRate")]
public delegate FT.Status SetBaudRate(
IntPtr handle,
uint baudRate);
[SymbolName("FT_SetDataCharacteristics")]
public delegate FT.Status SetDataCharacteristics(
IntPtr handle,
FT.Bits wordLength,
FT.StopBits stopBits,
FT.Parity parity);
[SymbolName("FT_SetFlowControl")]
public delegate FT.Status SetFlowControl(
IntPtr handle,
FT.FlowControl flowControl,
byte xonChar,
byte xoffChar);
[SymbolName("FT_SetDtr")]
public delegate FT.Status SetDtr(
IntPtr handle);
[SymbolName("FT_ClrDtr")]
public delegate FT.Status ClrDtr(
IntPtr handle);
[SymbolName("FT_SetRts")]
public delegate FT.Status SetRts(
IntPtr handle);
[SymbolName("FT_ClrRts")]
public delegate FT.Status ClrRts(
IntPtr handle);
[SymbolName("FT_SetTimeouts")]
public delegate FT.Status SetTimeouts(
IntPtr handle,
uint readTimeoutMS,
uint writeTimeoutMS);
[SymbolName("FT_SetLatencyTimer")]
public delegate FT.Status SetLatencyTimer(
IntPtr handle,
byte timerMS);
[SymbolName("FT_Purge")]
public delegate FT.Status Purge(
IntPtr handle,
FT.PurgeMask mask);
[SymbolName("FT_SetBreakOn")]
public delegate FT.Status SetBreakOn(
IntPtr handle);
[SymbolName("FT_SetBreakOff")]
public delegate FT.Status SetBreakOff(
IntPtr handle);
[SymbolName("FT_Read")]
public delegate FT.Status Read(
IntPtr handle,
byte[] buffer,
uint countOfBytesToRead,
out uint countOfBytesRead);
[SymbolName("FT_Write")]
public delegate FT.Status Write(
IntPtr handle,
byte[] buffer,
uint countOfBytesToWrite,
out uint countOfBytesWritten);
}
}
================================================
FILE: Interface/GenericInterface.cs
================================================
using System.IO.Ports;
namespace BitFab.KW1281Test.Interface
{
internal class GenericInterface : IInterface
{
public GenericInterface(string portName, int baudRate)
{
var timeout = ((IInterface)this).DefaultTimeoutMilliseconds;
_port = new SerialPort(portName)
{
BaudRate = baudRate,
DataBits = 8,
Parity = Parity.None,
StopBits = StopBits.One,
Handshake = Handshake.None,
RtsEnable = false,
DtrEnable = true,
ReadTimeout = timeout,
WriteTimeout = timeout
};
_port.Open();
}
public void Dispose()
{
SetDtr(false);
_port.Close();
}
public byte ReadByte()
{
var b = (byte)_port.ReadByte();
return b;
}
public void WriteByteRaw(byte b)
{
_buf[0] = b;
_port.Write(_buf, 0, 1);
}
public void SetBreak(bool on)
{
_port.BreakState = on;
}
public void ClearReceiveBuffer()
{
_port.DiscardInBuffer();
}
public void SetBaudRate(int baudRate)
{
_port.BaudRate = baudRate;
}
public void SetParity(Parity parity)
{
_port.Parity = parity;
}
public void SetDtr(bool on)
{
_port.DtrEnable = on;
}
public void SetRts(bool on)
{
_port.RtsEnable = on;
}
public int ReadTimeout
{
get => _port.ReadTimeout;
set => _port.ReadTimeout = value;
}
public int WriteTimeout
{
get => _port.WriteTimeout;
set => _port.WriteTimeout = value;
}
private readonly SerialPort _port;
private readonly byte[] _buf = new byte[1];
}
}
================================================
FILE: Interface/IInterface.cs
================================================
using System;
using System.IO.Ports;
namespace BitFab.KW1281Test.Interface
{
public interface IInterface : IDisposable
{
int DefaultTimeoutMilliseconds => (int)TimeSpan.FromSeconds(8).TotalMilliseconds;
/// <summary>
/// Read a byte from the interface.
/// </summary>
/// <returns>The byte.</returns>
byte ReadByte();
/// <summary>
/// Write a byte to the interface but do not read/discard its echo.
/// </summary>
void WriteByteRaw(byte b);
void SetBreak(bool on);
void ClearReceiveBuffer();
void SetBaudRate(int baudRate);
void SetParity(Parity parity);
void SetDtr(bool on);
void SetRts(bool on);
int ReadTimeout { get; set; }
int WriteTimeout { get; set; }
}
}
================================================
FILE: Interface/LinuxInterface.cs
================================================
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.IO.Ports;
using tcflag_t = System.UInt32;
using cc_t = System.Byte;
using speed_t = System.UInt32;
namespace BitFab.KW1281Test.Interface;
public class LinuxInterface : IInterface
{
private const uint CBAUD = 0x100F; // Clear normal baudrates
private const uint CBAUDEX = 0x1000;
private const uint BOTHER = 0x1000; // Other baudrate
private const int IBSHIFT = 16; // Shift from CBAUD to CIBAUD
private const uint PARENB = 0x0100; // Enable parity bit
private const uint PARODD = 0x0200; // Use odd parity rather than even parity
private const string libc = "libc";
#pragma warning disable SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
// Linux ioctl function
[DllImport(libc, SetLastError = true)]
private static extern int ioctl(int fd, int request, ref int data);
[DllImport(libc, SetLastError = true)]
private static extern int ioctl(int fd, uint request, IntPtr data);
// Native method declarations
[DllImport(libc)]
private static extern int open(string pathname, int flags);
[DllImport(libc)]
private static extern int close(int fd);
[DllImport(libc, CallingConvention = CallingConvention.StdCall)]
private static extern int read(int fd, byte[] buf, int count);
[DllImport(libc, CallingConvention = CallingConvention.StdCall)]
private static extern int write(int fd, byte[] buf, int count);
[DllImport(libc, CallingConvention = CallingConvention.StdCall)]
private static extern int tcflush(int fd, int queue);
#pragma warning restore SYSLIB1054 // Use 'LibraryImportAttribute' instead of 'DllImportAttribute' to generate P/Invoke marshalling code at compile time
private const int NCCS = 19;
// Define the termios structure to interact with terminal I/O settings
[StructLayout(LayoutKind.Sequential)]
public struct Termios
{
public tcflag_t c_iflag; // input mode flags
public tcflag_t c_oflag; // output mode flags
public tcflag_t c_cflag; // control mode flags
public tcflag_t c_lflag; // local mode flags
public cc_t c_line; // line discipline
[MarshalAs(UnmanagedType.ByValArray, SizeConst = NCCS)]
public cc_t[] c_cc; // control characters
public speed_t c_ispeed; // input speed
public speed_t c_ospeed; // output speed
}
private const int _IOC_NRBITS = 8;
private const int _IOC_TYPEBITS = 8;
private const int _IOC_SIZEBITS = 14;
private const int _IOC_NRSHIFT = 0;
private const int _IOC_TYPESHIFT = (_IOC_NRSHIFT + _IOC_NRBITS);
private const int _IOC_SIZESHIFT = (_IOC_TYPESHIFT + _IOC_TYPEBITS);
private const int _IOC_DIRSHIFT = (_IOC_SIZESHIFT + _IOC_SIZEBITS);
private const int _IOC_READ = 2;
private const int _IOC_WRITE = 1;
#pragma warning disable IDE1006 // Naming Styles
private static uint _IOC(int dir, int type, int nr, int size)
{
return (uint)((dir << _IOC_DIRSHIFT) |
(type << _IOC_TYPESHIFT) |
(nr << _IOC_NRSHIFT) |
(size << _IOC_SIZESHIFT));
}
private static int _IOC_TYPECHECK(Type type)
{
return Marshal.SizeOf(type);
}
private static uint _IOR(int type, int nr, Type size)
{
return _IOC(_IOC_READ, type, nr, _IOC_TYPECHECK(size));
}
private static uint _IOW(int type, int nr, Type size)
{
return _IOC(_IOC_WRITE, type, nr, _IOC_TYPECHECK(size));
}
#pragma warning restore IDE1006 // Naming Styles
private static readonly uint TCGETS2 = _IOR('T', 0x2A, typeof(Termios));
private static readonly uint TCSETS2 = _IOW('T', 0x2B, typeof(Termios));
private const int TIOCSBRK = 0x5427;
private const int TIOCCBRK = 0x5428;
private const int TIOCM_RTS = 0x004;
private const int TIOCMGET = 0x5415;
private const int TIOCMSET = 0x5418;
private const int TIOCM_DTR = 0x002;
private const int O_RDWR = 2;
private const int O_NOCTTY = 00000400;
private const int TCIFLUSH = 0; // Discard data received but not yet read
private const int VTIME = 5;
private const int VMIN = 6;
private int _fd = -1;
private IntPtr _termios;
public int ReadTimeout { get; set; }
public int WriteTimeout { get; set; }
public LinuxInterface(string portName, int baudRate)
{
_fd = open(portName, O_RDWR | O_NOCTTY);
if (_fd == -1)
{
throw new IOException($"Failed to open port {portName}");
}
// Allocate struct and memory
_termios = Marshal.AllocHGlobal(Marshal.SizeOf<Termios>());
var termios = GetTtyConfiguration();
// Update termio struct with timeouts
termios.c_iflag = 0;
termios.c_oflag = 0;
termios.c_lflag = 0;
var timeout = ((IInterface)this).DefaultTimeoutMilliseconds;
termios.c_cc[VTIME] = (byte)(timeout / 100);
termios.c_cc[VMIN] = 0;
SetTtyConfiguration(termios);
SetBaudRate(baudRate);
}
private Termios GetTtyConfiguration()
{
if (ioctl(_fd, TCGETS2, _termios) == -1)
{
throw new IOException("Failed to get the UART configuration");
}
var termios = Marshal.PtrToStructure<Termios>(_termios);
return termios;
}
private void SetTtyConfiguration(Termios termios)
{
// Get a C pointer to struct
Marshal.StructureToPtr(termios, _termios, fDeleteOld: true);
// Update configuration
if (ioctl(_fd, TCSETS2, _termios) == -1)
{
throw new IOException("Failed to set the UART configuration");
}
}
private readonly byte[] _buffer = new byte[1];
public byte ReadByte()
{
// Console.WriteLine("XX ReadByte");
int bytesRead = read(_fd, _buffer, 1);
if (bytesRead != 1)
{
throw new IOException("Failed to read byte from UART");
}
return _buffer[0];
}
public void WriteByteRaw(byte b)
{
_buffer[0] = b;
int bytesWritten = write(_fd, _buffer, 1);
if (bytesWritten != 1)
{
throw new IOException("Failed to write byte to UART");
}
}
public void SetBreak(bool on)
{
var iov = on ? TIOCSBRK : TIOCCBRK;
int data = 0;
if (ioctl(_fd, iov, ref data) == -1)
{
throw new IOException("Failed to set/clear UART break");
}
}
public void ClearReceiveBuffer()
{
if (tcflush(_fd, TCIFLUSH) == -1)
{
throw new IOException("Failed to clear the UART receive buffer");
}
}
public void SetBaudRate(int baudRate)
{
var termios = GetTtyConfiguration();
// Output speed
termios.c_cflag &= ~(CBAUD | CBAUDEX);
termios.c_cflag |= BOTHER;
termios.c_ospeed = (uint)baudRate;
// Input speed
termios.c_cflag &= ~((CBAUD | CBAUDEX) << IBSHIFT);
termios.c_cflag |= (BOTHER << IBSHIFT);
termios.c_ispeed = (uint)baudRate;
SetTtyConfiguration(termios);
}
public void SetParity(Parity parity)
{
var termios = GetTtyConfiguration();
// Set parity
switch (parity)
{
case Parity.None:
termios.c_cflag &= ~PARENB; // Disable parity
break;
case Parity.Odd:
termios.c_cflag |= PARENB; // Enable parity
termios.c_cflag |= PARODD; // Set odd parity
break;
case Parity.Even:
termios.c_cflag |= PARENB; // Enable parity
termios.c_cflag &= ~PARODD; // Set even parity
break;
case Parity.Mark:
// Mark parity is not supported on Linux, set as None
termios.c_cflag &= ~PARENB; // Disable parity
break;
case Parity.Space:
// Space parity is not supported on Linux, set as None
termios.c_cflag &= ~PARENB; // Disable parity
break;
}
SetTtyConfiguration(termios);
}
public void SetDtr(bool on)
{
// Get the current control lines state
int controlLinesState = 0;
if (ioctl(_fd, TIOCMGET, ref controlLinesState) == -1)
{
throw new IOException("Failed to get control lines state.");
}
// Set DTR flag
if (on)
{
controlLinesState |= TIOCM_DTR;
}
else
{
controlLinesState &= ~TIOCM_DTR;
}
// Set the modified control lines state
if (ioctl(_fd, TIOCMSET, ref controlLinesState) == -1)
{
throw new IOException("Failed to set DTR");
}
}
public void SetRts(bool on)
{
// Get the current control lines state
int controlLinesState = 0;
if (ioctl(_fd, TIOCMGET, ref controlLinesState) == -1)
{
throw new IOException("Failed to get uart line state");
}
// Set RTS flag
if (on)
controlLinesState |= TIOCM_RTS;
else
controlLinesState &= ~TIOCM_RTS;
// Set the modified control lines state
if (ioctl(_fd, TIOCMSET, ref controlLinesState) == -1)
{
throw new IOException("Failed to set RTS");
}
}
public void Dispose()
{
// Dispose of unmanaged resources.
Dispose(true);
// Suppress finalization.
GC.SuppressFinalize(this);
}
private bool _disposed = false;
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
// TODO: Dispose managed state (managed objects).
}
// Free unmanaged resources (unmanaged objects) and override a finalizer below.
if (_termios != IntPtr.Zero)
{
Marshal.FreeHGlobal(_termios);
_termios = IntPtr.Zero;
}
if (_fd != -1)
{
_ = close(_fd);
_fd = -1;
}
// TODO: Set large fields to null.
_disposed = true;
}
}
================================================
FILE: KW1281Dialog.cs
================================================
using BitFab.KW1281Test.Blocks;
using BitFab.KW1281Test.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BitFab.KW1281Test;
/// <summary>
/// Manages a dialog with a VW controller using the KW1281 protocol.
/// </summary>
internal interface IKW1281Dialog
{
ControllerInfo Connect();
void EndCommunication();
void SetDisconnected();
List<Block> Login(ushort code, int workshopCode);
List<ControllerIdent> ReadIdent();
/// <summary>
/// Corresponds to VDS-Pro function 19
/// </summary>
List<byte>? ReadEeprom(ushort address, byte count);
bool WriteEeprom(ushort address, List<byte> values);
/// <summary>
/// Corresponds to VDS-Pro functions 21 and 22
/// </summary>
List<byte>? ReadRomEeprom(ushort address, byte count);
/// <summary>
/// Corresponds to VDS-Pro functions 20 and 25
/// </summary>
List<byte>? ReadRam(ushort address, byte count);
bool AdaptationRead(byte channelNumber);
bool AdaptationTest(byte channelNumber, ushort channelValue);
bool AdaptationSave(byte channelNumber, ushort channelValue, int workshopCode);
void SendBlock(List<byte> blockBytes);
List<Block> ReceiveBlocks();
List<byte>? ReadCcmRom(byte seg, byte msb, byte lsb, byte count);
/// <summary>
/// Keep the dialog alive by sending an ACK and receiving a response.
/// </summary>
void KeepAlive();
ActuatorTestResponseBlock? ActuatorTest(byte value);
List<FaultCode>? ReadFaultCodes();
/// <summary>
/// Clear all of the controllers fault codes.
/// </summary>
/// <param name="controllerAddress"></param>
/// <returns>Any remaining fault codes.</returns>
List<FaultCode>? ClearFaultCodes(int controllerAddress);
/// <summary>
/// Set the controller's software coding and workshop code.
/// </summary>
/// <param name="controllerAddress"></param>
/// <param name="softwareCoding"></param>
/// <param name="workshopCode"></param>
/// <returns>True if successful.</returns>
bool SetSoftwareCoding(int controllerAddress, int softwareCoding, int workshopCode);
bool GroupRead(byte groupNumber, bool useBasicSetting = false);
List<byte> ReadSecureImmoAccess(List<byte> blockBytes);
public IKwpCommon KwpCommon { get; }
Block ReceiveBlock();
}
internal class KW1281Dialog : IKW1281Dialog
{
public ControllerInfo Connect()
{
_isConnected = true;
var blocks = ReceiveBlocks();
return new ControllerInfo(blocks.Where(b => !b.IsAckNak));
}
public List<Block> Login(ushort code, int workshopCode)
{
Log.WriteLine("Sending Login block");
SendBlock(
[
(byte)BlockTitle.Login,
(byte)(code >> 8),
(byte)(code & 0xFF),
(byte)(workshopCode >> 16),
(byte)((workshopCode >> 8) & 0xFF),
(byte)(workshopCode & 0xFF)
]);
return ReceiveBlocks();
}
public List<ControllerIdent> ReadIdent()
{
var idents = new List<ControllerIdent>();
bool moreAvailable;
do
{
Log.WriteLine("Sending ReadIdent block");
SendBlock(new List<byte> { (byte)BlockTitle.ReadIdent });
var blocks = ReceiveBlocks();
var ident = new ControllerIdent(blocks.Where(b => !b.IsAckNak));
idents.Add(ident);
moreAvailable = blocks
.OfType<AsciiDataBlock>()
.Any(b => b.MoreDataAvailable);
} while (moreAvailable);
return idents;
}
/// <summary>
/// Reads a range of bytes from the EEPROM.
/// </summary>
/// <param name="address"></param>
/// <param name="count"></param>
/// <returns>The bytes or null if the bytes could not be read</returns>
public List<byte>? ReadEeprom(ushort address, byte count)
{
Log.WriteLine($"Sending ReadEeprom block (Address: ${address:X4}, Count: ${count:X2})");
SendBlock(new List<byte>
{
(byte)BlockTitle.ReadEeprom,
count,
(byte)(address >> 8),
(byte)(address & 0xFF)
});
var blocks = ReceiveBlocks();
if (blocks.Count == 1 && blocks[0] is NakBlock)
{
// Permissions issue
return null;
}
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
throw new InvalidOperationException($"ReadEeprom returned {blocks.Count} blocks instead of 1");
}
return blocks[0].Body.ToList();
}
/// <summary>
/// Reads a range of bytes from the RAM.
/// </summary>
/// <param name="address"></param>
/// <param name="count"></param>
/// <returns>The bytes or null if the bytes could not be read</returns>
public List<byte>? ReadRam(ushort address, byte count)
{
Log.WriteLine($"Sending ReadRam block (Address: ${address:X4}, Count: ${count:X2})");
SendBlock(new List<byte>
{
(byte)BlockTitle.ReadRam,
count,
(byte)(address >> 8),
(byte)(address & 0xFF)
});
var blocks = ReceiveBlocks();
if (blocks.Count == 1 && blocks[0] is NakBlock)
{
// Permissions issue
return null;
}
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
throw new InvalidOperationException($"ReadEeprom returned {blocks.Count} blocks instead of 1");
}
return blocks[0].Body.ToList();
}
/// <summary>
/// Reads a range of bytes from the CCM ROM.
/// </summary>
/// <param name="seg">0-15</param>
/// <param name="msb">0-15</param>
/// <param name="lsb">0-255</param>
/// <param name="count">8(-12?)</param>
/// <returns>The bytes or null if the bytes could not be read</returns>
public List<byte>? ReadCcmRom(byte seg, byte msb, byte lsb, byte count)
{
Log.WriteLine(
$"Sending ReadEeprom block (Address: ${seg:X2}{msb:X2}{lsb:X2}, Count: ${count:X2})");
var block = new List<byte>
{
(byte)BlockTitle.ReadEeprom,
count,
msb,
lsb,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
(byte)(seg << 4)
};
Log.WriteLine($"SEND {Utils.Dump(block)}");
SendBlock(block);
var blocks = ReceiveBlocks();
#if true
foreach (var b in blocks)
{
Log.WriteLine($"Received:{Utils.Dump(b.Bytes)}");
}
#endif
if (blocks.Count == 1 && blocks[0] is NakBlock)
{
// Log.WriteLine($"RECV {Utils.Dump(blocks.First().Bytes)}");
// Permissions issue
return null;
}
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
throw new InvalidOperationException($"ReadEeprom returned {blocks.Count} blocks instead of 1");
}
return blocks[0].Body.ToList();
}
public bool WriteEeprom(ushort address, List<byte> values)
{
Log.WriteLine($"Sending WriteEeprom block (Address: ${address:X4}, Values: {Utils.DumpBytes(values)}");
byte count = (byte)values.Count;
var sendBody = new List<byte>
{
(byte)BlockTitle.WriteEeprom,
count,
(byte)(address >> 8),
(byte)(address & 0xFF),
};
sendBody.AddRange(values);
SendBlock(sendBody.ToList());
var blocks = ReceiveBlocks();
if (blocks.Count == 1 && blocks[0] is NakBlock)
{
// Permissions issue
Log.WriteLine("WriteEeprom failed");
return false;
}
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
Log.WriteLine($"WriteEeprom returned {blocks.Count} blocks instead of 1");
return false;
}
var block = blocks[0];
if (block is not WriteEepromResponseBlock)
{
Log.WriteLine($"Expected WriteEepromResponseBlock but got {block.GetType()}");
return false;
}
if (!Enumerable.SequenceEqual(block.Body, sendBody.Skip(1).Take(4)))
{
Log.WriteLine("WriteEepromResponseBlock body does not match WriteEepromBlock");
return false;
}
return true;
}
public List<byte>? ReadRomEeprom(ushort address, byte count)
{
Log.WriteLine($"Sending ReadRomEeprom block (Address: ${address:X4}, Count: ${count:X2})");
SendBlock(new List<byte>
{
(byte)BlockTitle.ReadRomEeprom,
count,
(byte)(address >> 8),
(byte)(address & 0xFF)
});
var blocks = ReceiveBlocks();
if (blocks.Count == 1 && blocks[0] is NakBlock)
{
// Permissions issue
return null;
}
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
throw new InvalidOperationException($"ReadRomEeprom returned {blocks.Count} blocks instead of 1");
}
return blocks[0].Body.ToList();
}
public void EndCommunication()
{
if (_isConnected)
{
Log.WriteLine("Sending EndCommunication block");
SendBlock(new List<byte> { (byte)BlockTitle.End });
_isConnected = false;
}
}
public void SetDisconnected()
{
_isConnected = false;
_blockCounter = null;
}
public void SendBlock(List<byte> blockBytes)
{
Thread.Sleep(25); // For better support of 4D0919035AJ
var blockLength = (byte)(blockBytes.Count + 2);
blockBytes.Insert(0, _blockCounter!.Value);
_blockCounter++;
blockBytes.Insert(0, blockLength);
Thread.Sleep(TimeInterval.R6);
foreach (var b in blockBytes)
{
WriteByteAndReadAck(b);
Thread.Sleep(TimeInterval.R6);
}
KwpCommon.WriteByte(0x03); // Block end, does not get ACK'd
}
public List<Block> ReceiveBlocks()
{
var blocks = new List<Block>();
try
{
while (true)
{
var block = ReceiveBlock();
blocks.Add(block); // TODO: Maybe don't add the block if it's an Ack
if (block is AckBlock || block is NakBlock)
{
break;
}
SendAckBlock();
}
}
catch (Exception ex)
{
Log.WriteLine($"Error receiving blocks: {ex.Message}");
if (blocks.Count > 0)
{
Log.WriteLine("Blocks received:");
foreach (var block in blocks)
{
Log.WriteLine($"Block: {Utils.DumpBytes(block.Bytes)}");
}
}
throw;
}
return blocks;
}
private void WriteByteAndReadAck(byte b)
{
KwpCommon.WriteByte(b);
KwpCommon.ReadComplement(b);
}
public Block ReceiveBlock()
{
var blockBytes = new List<byte>();
try
{
var blockLength = ReadAndAckByteFirst();
blockBytes.Add(blockLength);
var blockCounter = ReadBlockCounter();
blockBytes.Add(blockCounter);
var blockTitle = ReadAndAckByte();
blockBytes.Add(blockTitle);
for (var i = 0; i < blockLength - 3; i++)
{
var b = ReadAndAckByte();
blockBytes.Add(b);
}
var blockEnd = KwpCommon.ReadByte();
blockBytes.Add(blockEnd);
if (blockEnd != 0x03)
{
throw new InvalidOperationException(
$"Received block end ${blockEnd:X2} but expected $03. Block bytes: {Utils.Dump(blockBytes)}");
}
return (BlockTitle)blockTitle switch
{
BlockTitle.ACK => new AckBlock(blockBytes),
BlockTitle.GroupReadResponseWithText => new GroupReadResponseWithTextBlock(blockBytes),
BlockTitle.ActuatorTestResponse => new ActuatorTestResponseBlock(blockBytes),
BlockTitle.AsciiData =>
blockBytes[3] == 0x00 ? new CodingWscBlock(blockBytes) : new AsciiDataBlock(blockBytes),
BlockTitle.Custom => new CustomBlock(blockBytes),
BlockTitle.NAK => new NakBlock(blockBytes),
BlockTitle.ReadEepromResponse => new ReadEepromResponseBlock(blockBytes),
BlockTitle.FaultCodesResponse => new FaultCodesBlock(blockBytes),
BlockTitle.ReadRomEepromResponse => new ReadRomEepromResponse(blockBytes),
BlockTitle.WriteEepromResponse => new WriteEepromResponseBlock(blockBytes),
BlockTitle.AdaptationResponse => new AdaptationResponseBlock(blockBytes),
BlockTitle.GroupReadResponse => new GroupReadResponseBlock(blockBytes),
BlockTitle.RawDataReadResponse => new RawDataReadResponseBlock(blockBytes),
BlockTitle.SecurityAccessMode2 => new SecurityAccessMode2Block(blockBytes),
_ => new UnknownBlock(blockBytes),
};
}
catch (Exception ex)
{
Log.WriteLine($"Error receiving block: {ex.Message}");
Log.WriteLine($"Partial block: {Utils.DumpBytes(blockBytes)}");
if (ex is TimeoutException)
{
Log.WriteLine($"Read timeout: {KwpCommon.Interface.ReadTimeout}");
Log.WriteLine($"Write timeout: {KwpCommon.Interface.WriteTimeout}");
}
throw;
}
}
private void SendAckBlock()
{
var blockBytes = new List<byte> { (byte)BlockTitle.ACK };
SendBlock(blockBytes);
}
private byte ReadBlockCounter()
{
var blockCounter = ReadAndAckByte();
if (!_blockCounter.HasValue)
{
// First block
_blockCounter = blockCounter;
}
else if (blockCounter != _blockCounter)
{
throw new InvalidOperationException(
$"Received block counter ${blockCounter:X2} but expected ${_blockCounter:X2}");
}
_blockCounter++;
return blockCounter;
}
private byte ReadAndAckByte()
{
var b = KwpCommon.ReadByte();
Thread.Sleep(TimeInterval.R6);
var complement = (byte)~b;
KwpCommon.WriteByte(complement);
return b;
}
/// <summary>
/// https://github.com/gmenounos/kw1281test/issues/93
/// </summary>
private byte ReadAndAckByteFirst(int count = 0)
{
if (count > 5)
{
throw new InvalidOperationException(
$"Cannot sync with {count} repeated attempts.");
}
var b = KwpCommon.ReadByte();
if (b == 0x55)
{
var keywordLsb = KwpCommon.ReadByte();
var keywordMsb = KwpCommon.ReadByte();
var complement = (byte)~keywordMsb;
BusyWait.Delay(25);
KwpCommon.WriteByte(complement);
Log.WriteLine($"Warning. Sync repeated.");
return ReadAndAckByteFirst(count);
}
else
{
Thread.Sleep(TimeInterval.R6);
var complement = (byte)~b;
KwpCommon.WriteByte(complement);
return b;
}
}
public void KeepAlive()
{
SendAckBlock();
var block = ReceiveBlock();
if (block is not AckBlock)
{
throw new InvalidOperationException(
$"Received 0x{block.Title:X2} block but expected ACK");
}
}
public ActuatorTestResponseBlock? ActuatorTest(byte value)
{
Log.WriteLine($"Sending actuator test 0x{value:X2} block");
SendBlock(new List<byte>
{
(byte)BlockTitle.ActuatorTest,
value
});
var blocks = ReceiveBlocks();
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
Log.WriteLine($"ActuatorTest returned {blocks.Count} blocks instead of 1");
return null;
}
var block = blocks[0];
if (block is not ActuatorTestResponseBlock)
{
Log.WriteLine($"Expected ActuatorTestResponseBlock but got {block.GetType()}");
return null;
}
return (ActuatorTestResponseBlock)block;
}
public List<FaultCode>? ReadFaultCodes()
{
Log.WriteLine($"Sending ReadFaultCodes block");
SendBlock(new List<byte>
{
(byte)BlockTitle.FaultCodesRead
});
var blocks = ReceiveBlocks();
blocks = blocks.Where(b => !b.IsAckNak).ToList();
var faultCodes = new List<FaultCode>();
foreach (var block in blocks)
{
if (block is not FaultCodesBlock)
{
Log.WriteLine($"Expected FaultCodesBlock but got {block.GetType()}");
return null;
}
var faultCodesBlock = (FaultCodesBlock)block;
faultCodes.AddRange(faultCodesBlock.FaultCodes);
}
return faultCodes;
}
public List<FaultCode>? ClearFaultCodes(int controllerAddress)
{
Log.WriteLine($"Sending ClearFaultCodes block");
SendBlock(new List<byte>
{
(byte)BlockTitle.FaultCodesDelete
});
var blocks = ReceiveBlocks();
blocks = blocks.Where(b => !b.IsAckNak).ToList();
var faultCodes = new List<FaultCode>();
foreach (var block in blocks)
{
if (block is not FaultCodesBlock)
{
Log.WriteLine($"Expected FaultCodesBlock but got {block.GetType()}");
return null;
}
var faultCodesBlock = (FaultCodesBlock)block;
faultCodes.AddRange(faultCodesBlock.FaultCodes);
}
return faultCodes;
}
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>
{
(byte)BlockTitle.SoftwareCoding,
(byte)((softwareCoding * 2) / 256),
(byte)((softwareCoding * 2) % 256),
(byte)((workshopCode & 65535) / 256),
(byte)(workshopCode % 256)
};
if (workshopCode > 65535)
{
bytes[2]++;
}
Log.WriteLine($"Sending SoftwareCoding block");
SendBlock(bytes);
var blocks = ReceiveBlocks();
if (blocks.Count == 1 && blocks[0] is NakBlock)
{
return false;
}
var controllerInfo = new ControllerInfo(blocks.Where(b => !b.IsAckNak));
return
controllerInfo.SoftwareCoding == softwareCoding &&
controllerInfo.WorkshopCode == workshopCode;
}
public bool AdaptationRead(byte channelNumber)
{
var bytes = new List<byte>
{
(byte)BlockTitle.AdaptationRead,
channelNumber
};
Log.WriteLine($"Sending AdaptationRead block");
SendBlock(bytes);
return ReceiveAdaptationBlock();
}
public bool AdaptationTest(byte channelNumber, ushort channelValue)
{
var bytes = new List<byte>
{
(byte)BlockTitle.AdaptationTest,
channelNumber,
(byte)(channelValue / 256),
(byte)(channelValue % 256)
};
Log.WriteLine($"Sending AdaptationTest block");
SendBlock(bytes);
return ReceiveAdaptationBlock();
}
public bool AdaptationSave(byte channelNumber, ushort channelValue, int workshopCode)
{
var bytes = new List<byte>
{
(byte)BlockTitle.AdaptationSave,
channelNumber,
(byte)(channelValue / 256),
(byte)(channelValue % 256),
(byte)(workshopCode >> 16),
(byte)((workshopCode >> 8) & 0xFF),
(byte)(workshopCode & 0xFF)
};
Log.WriteLine($"Sending AdaptationSave block");
SendBlock(bytes);
return ReceiveAdaptationBlock();
}
private bool ReceiveAdaptationBlock()
{
var responseBlock = ReceiveBlock();
if (responseBlock is NakBlock)
{
Log.WriteLine($"Received a NAK.");
return false;
}
if (responseBlock is not AdaptationResponseBlock adaptationResponse)
{
Log.WriteLine($"Expected an Adaptation response block but received a ${responseBlock.Title:X2} block.");
return false;
}
Log.WriteLine($"Adaptation value: {adaptationResponse.ChannelValue}");
return true;
}
public bool GroupRead(byte groupNumber, bool useBasicSetting = false)
{
if (groupNumber == 0)
{
return RawDataRead(useBasicSetting);
}
if (useBasicSetting)
{
Log.WriteLine($"Sending Basic Setting Read blocks...");
}
else
{
Log.WriteLine($"Sending Group Read blocks...");
}
GroupReadResponseWithTextBlock? textBlock = null;
Log.WriteLine("[Up arrow | Down arrow | Q to quit]", LogDest.Console);
while (true)
{
if (Console.KeyAvailable)
{
var keyInfo = Console.ReadKey(intercept: true);
if (keyInfo.Key == ConsoleKey.UpArrow)
{
if (groupNumber < 255)
{
groupNumber++;
}
}
else if (keyInfo.Key == ConsoleKey.DownArrow)
{
if (groupNumber > 1)
{
groupNumber--;
}
}
else if (keyInfo.Key == ConsoleKey.Q)
{
break;
}
}
var bytes = new List<byte>
{
(byte)(useBasicSetting ? BlockTitle.BasicSettingRead : BlockTitle.GroupRead),
groupNumber
};
SendBlock(bytes);
var responseBlock = ReceiveBlock();
if (responseBlock is NakBlock)
{
Overlay($"Group {groupNumber:D3}: Not Available");
}
else if (responseBlock is GroupReadResponseWithTextBlock groupReadResponseWithText)
{
Log.WriteLine($"{groupReadResponseWithText}", LogDest.File);
textBlock = groupReadResponseWithText;
}
else if (responseBlock is GroupReadResponseBlock groupReading)
{
Overlay($"Group {groupNumber:D3}: {groupReading}");
}
else if (responseBlock is RawDataReadResponseBlock rawData)
{
if (textBlock != null && rawData.Body.Count > 0)
{
var sb = new StringBuilder($"Group {groupNumber:D3}: ");
sb.Append(textBlock.GetText(rawData.Body[0]));
sb.Append(Utils.DumpDecimal(rawData.Body.Skip(1)));
Overlay(sb.ToString());
}
else
{
Overlay($"Group {groupNumber:D3}: {rawData}");
}
}
else
{
Log.WriteLine($"Expected a Group Reading response block but received a ${responseBlock.Title:X2} block.");
return false;
}
}
Log.WriteLine(LogDest.Console);
return true;
}
private bool RawDataRead(bool useBasicSetting)
{
if (useBasicSetting)
{
Log.WriteLine($"Sending Basic Setting Raw Data Read block");
}
else
{
Log.WriteLine($"Sending Raw Data Read block");
}
Log.WriteLine("[Press a key to quit]", LogDest.Console);
while (!Console.KeyAvailable)
{
var bytes = new List<byte>
{
(byte)(useBasicSetting ? BlockTitle.BasicSettingRawDataRead : BlockTitle.RawDataRead)
};
SendBlock(bytes);
var responseBlock = ReceiveBlock();
if (responseBlock is not RawDataReadResponseBlock rawDataReadResponse)
{
Log.WriteLine($"Expected a Raw Data Read response block but received a ${responseBlock.Title:X2} block.");
return false;
}
Overlay(rawDataReadResponse.ToString());
}
Log.WriteLine(LogDest.Console);
return true;
}
public List<byte> ReadSecureImmoAccess(List<byte> blockBytes)
{
blockBytes.Insert(0, (byte)BlockTitle.SecurityImmoAccess1);
Log.WriteLine($"Sending ReadSecureImmoAccess block: {Utils.DumpBytes(blockBytes)}");
SendBlock(blockBytes);
var blocks = ReceiveBlocks();
if (blocks.Count == 1 && blocks[0] is NakBlock)
{
return [];
}
blocks = blocks.Where(b => !b.IsAckNak).ToList();
if (blocks.Count != 1)
{
throw new InvalidOperationException($"ReadRomEeprom returned {blocks.Count} blocks instead of 1");
}
return blocks[0].Body.ToList();
}
/// <summary>
/// Erase the current console line and replace it with message.
/// Also writes the message to the log.
/// </summary>
private static void Overlay(string message)
{
(int left, int top) = Console.GetCursorPosition();
Console.SetCursorPosition(0, top);
if (left > 0)
{
Log.Write(new string(' ', left), LogDest.Console);
Console.SetCursorPosition(0, top);
}
Log.Write(message, LogDest.Console);
Log.WriteLine(message, LogDest.File);
}
private static class TimeInterval
{
/// <summary>
/// Time to wait in milliseconds after receiving a byte from the ECU before sending the next byte.
/// Valid range: 1-50ms (according to SAE J2818)
/// </summary>
public const int R6 = 2;
}
public IKwpCommon KwpCommon { get; }
private bool _isConnected;
private byte? _blockCounter;
public KW1281Dialog(IKwpCommon kwpCommon)
{
KwpCommon = kwpCommon;
_isConnected = false;
_blockCounter = null;
}
}
/// <summary>
/// Used for commands such as ActuatorTest which need to be kept alive with ACKs while waiting
/// for user input.
/// </summary>
internal class KW1281KeepAlive : IDisposable
{
private readonly IKW1281Dialog _kw1281Dialog;
private volatile bool _cancel = false;
private Task? _keepAliveTask = null;
public KW1281KeepAlive(IKW1281Dialog kw1281Dialog)
{
_kw1281Dialog = kw1281Dialog;
}
public ActuatorTestResponseBlock? ActuatorTest(byte value)
{
Pause();
var result = _kw1281Dialog.ActuatorTest(value);
Resume();
return result;
}
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)
{
_kw1281Dialog.KeepAlive();
Log.Write(".", LogDest.Console);
}
}
}
================================================
FILE: Kwp2000/DiagnosticService.cs
================================================
namespace BitFab.KW1281Test.Kwp2000
{
public enum DiagnosticService : byte
{
startDiagnosticSession = 0x10,
ecuReset = 0x11,
readEcuIdentification = 0x1A,
stopDiagnosticSession = 0x20,
readMemoryByAddress = 0x23,
securityAccess = 0x27,
startRoutineByLocalIdentifier = 0x31,
requestDownload = 0x34,
transferData = 0x36,
writeMemoryByAddress = 0x3D,
testerPresent = 0x3E,
startCommunication = 0x81,
stopCommunication = 0x82,
accessTimingParameters = 0x83,
};
}
================================================
FILE: Kwp2000/KW2000Dialog.cs
================================================
using BitFab.KW1281Test.Kwp2000;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Threading;
using Service = BitFab.KW1281Test.Kwp2000.DiagnosticService;
namespace BitFab.KW1281Test
{
internal class KW2000Dialog
{
private const byte _testerAddress = 0xF1;
/// <summary>
/// Inter-command delay (milliseconds)
/// </summary>
public int P3 { get; set; } = 55;
/// <summary>
/// Inter-byte delay (milliseconds)
/// </summary>
public int P4 { get; set; } = 5;
public void DumpMem(uint address, uint length, string dumpFileName)
{
StartDiagnosticSession(0x84, 0x14);
Thread.Sleep(350);
Log.WriteLine($"Saving memory dump to {dumpFileName}");
DumpMemory(address, length, maxReadLength: 32, dumpFileName);
Log.WriteLine($"Saved memory dump to {dumpFileName}");
EcuReset(0x01);
}
private void DumpMemory(
uint startAddr, uint length, byte maxReadLength, string fileName)
{
using var fs = File.Create(fileName, maxReadLength, FileOptions.WriteThrough);
for (uint addr = startAddr; addr < (startAddr + length); addr += maxReadLength)
{
var readLength = (byte)Math.Min(startAddr + length - addr, maxReadLength);
try
{
var blockBytes = ReadMemoryByAddress(addr, readLength);
fs.Write(blockBytes, 0, blockBytes.Length);
if (blockBytes.Length != readLength)
{
throw new InvalidOperationException(
$"Expected {readLength} bytes from ReadMemoryByAddress() but received {blockBytes.Length} bytes");
}
}
catch (NegativeResponseException)
{
// Access not allowed?
Log.WriteLine("Failed to read memory.");
}
finally
{
fs.Flush();
}
}
}
public void StartDiagnosticSession(byte v1, byte v2)
{
var responseMessage = SendReceive(Service.startDiagnosticSession, new[] { v1, v2 });
if (responseMessage.Body[0] != v1)
{
throw new InvalidOperationException($"Unexpected diagnosticMode: {responseMessage.Body[0]:X2}");
}
}
public void EcuReset(byte value)
{
var responseMessage = SendReceive(Service.ecuReset, new[] { value });
}
public byte[] ReadMemoryByAddress(uint address, byte count)
{
var addressBytes = Utils.GetBytes(address);
var responseMessage = SendReceive(Service.readMemoryByAddress,
new byte[]
{
addressBytes[2], addressBytes[1], addressBytes[0],
count
});
return responseMessage.Body.ToArray();
}
public byte[] WriteMemoryByAddress(uint address, byte count, byte[] data)
{
var addressBytes = Utils.GetBytes(address);
var messageBytes = new List<byte>
{
addressBytes[2],
addressBytes[1],
addressBytes[0],
count
};
messageBytes.AddRange(data);
var responseMessage = SendReceive(Service.writeMemoryByAddress,
messageBytes.ToArray());
return responseMessage.Body.ToArray();
}
public Kwp2000Message SendReceive(
Service service, byte[] body, bool excludeAddresses = false)
{
SendMessage(service, body, excludeAddresses);
while (true)
{
var message = ReceiveMessage();
if (message.SrcAddress.HasValue)
{
if (message.SrcAddress != _controllerAddress)
{
throw new InvalidOperationException($"Unexpected SrcAddress: {message.SrcAddress:X2}");
}
if (message.DestAddress != _testerAddress)
{
throw new InvalidOperationException($"Unexpected DestAddress: {message.DestAddress:X2}");
}
}
if ((byte)message.Service == 0x7F)
{
if (message.Body[0] == (byte)service &&
message.Body[1] == (byte)ResponseCode.reqCorrectlyRcvdRspPending)
{
continue;
}
throw new NegativeResponseException(message);
}
if (!message.IsPositiveResponse(service))
{
throw new InvalidOperationException($"Unexpected response: {message.Service}");
}
return message;
}
}
public void SendMessage(Service service, byte[] body, bool excludeAddresses = false)
{
static void Sleep(int ms)
{
var maxTick = Stopwatch.GetTimestamp() + Stopwatch.Frequency / 1000 * ms;
while (Stopwatch.GetTimestamp() < maxTick)
;
}
Kwp2000Message message;
if (excludeAddresses)
{
message = new Kwp2000Message(service, body);
}
else
{
message = new Kwp2000Message(
_controllerAddress, _testerAddress, service, body);
}
var checksum = message.CalcChecksum();
Sleep(P3);
foreach (var b in message.HeaderBytes)
{
_kwpCommon.WriteByte(b);
Sleep(P4);
}
_kwpCommon.WriteByte((byte)message.Service);
Sleep(P4);
foreach (var b in message.Body)
{
_kwpCommon.Writ
gitextract_0ekb2w7j/ ├── .gitattributes ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .vscode/ │ └── launch.json ├── BlockTitle.cs ├── Blocks/ │ ├── AckBlock.cs │ ├── ActuatorTestResponseBlock.cs │ ├── AdaptationResponseBlock.cs │ ├── AsciiDataBlock.cs │ ├── Block.cs │ ├── CodingWscBlock.cs │ ├── CustomBlock.cs │ ├── FaultCodesBlock.cs │ ├── GroupReadResponseBlock.cs │ ├── GroupReadResponseWithTextBlock.cs │ ├── NakBlock.cs │ ├── RawDataReadResponseBlock.cs │ ├── ReadEepromResponseBlock.cs │ ├── ReadRomEepromResponse.cs │ ├── SecurityAccessMode2Block.cs │ ├── SensorValue.cs │ ├── UnknownBlock.cs │ └── WriteEepromResponseBlock.cs ├── BusyWait.cs ├── Cluster/ │ ├── AudiC5Cluster.cs │ ├── BoschRBxCluster.cs │ ├── ICluster.cs │ ├── MarelliCluster.cs │ ├── MotometerBOOCluster.cs │ ├── VdoCluster.cs │ └── VdoKeyFinder.cs ├── ControllerAddress.cs ├── ControllerIdent.cs ├── ControllerInfo.cs ├── EDC15/ │ ├── Edc15VM.cs │ └── Loader.a66 ├── Interface/ │ ├── FtdiInterface.cs │ ├── GenericInterface.cs │ ├── IInterface.cs │ └── LinuxInterface.cs ├── KW1281Dialog.cs ├── Kwp2000/ │ ├── DiagnosticService.cs │ ├── KW2000Dialog.cs │ ├── Kwp2000Message.cs │ ├── NegativeResponseException.cs │ └── ResponseCode.cs ├── KwpCommon.cs ├── LICENSE.txt ├── Logging/ │ ├── ConsoleLog.cs │ ├── FileLog.cs │ └── ILog.cs ├── Program.cs ├── Publish_Mac.ps1 ├── Publish_Win.ps1 ├── README.md ├── Tester.cs ├── Tests/ │ ├── BitFab.KW1281Test.Tests.csproj │ ├── Cluster/ │ │ ├── MarelliClusterTests.cs │ │ └── VdoClusterTests.cs │ ├── GlobalUsings.cs │ ├── ProgramTests.cs │ ├── TesterTests.cs │ └── UtilsTests.cs ├── UnableToProceedException.cs ├── UnexpectedProtocolException.cs ├── Utils.cs └── kw1281test.slnx
SYMBOL INDEX (458 symbols across 55 files)
FILE: BlockTitle.cs
type BlockTitle (line 3) | public enum BlockTitle : byte
FILE: Blocks/AckBlock.cs
class AckBlock (line 6) | internal class AckBlock : Block
method AckBlock (line 8) | public AckBlock(List<byte> bytes) : base(bytes)
method Dump (line 13) | private void Dump()
FILE: Blocks/ActuatorTestResponseBlock.cs
class ActuatorTestResponseBlock (line 5) | internal class ActuatorTestResponseBlock : Block
method ActuatorTestResponseBlock (line 7) | public ActuatorTestResponseBlock(List<byte> bytes) : base(bytes)
method Dump (line 25) | private void Dump()
FILE: Blocks/AdaptationResponseBlock.cs
class AdaptationResponseBlock (line 5) | internal class AdaptationResponseBlock : Block
method AdaptationResponseBlock (line 7) | public AdaptationResponseBlock(List<byte> bytes) : base(bytes)
method Dump (line 16) | private void Dump()
FILE: Blocks/AsciiDataBlock.cs
class AsciiDataBlock (line 7) | internal class AsciiDataBlock : Block
method AsciiDataBlock (line 9) | public AsciiDataBlock(List<byte> bytes) : base(bytes)
method ToString (line 16) | public override string ToString()
method Dump (line 26) | private void Dump()
FILE: Blocks/Block.cs
class Block (line 9) | class Block
method Block (line 11) | public Block(List<byte> bytes)
FILE: Blocks/CodingWscBlock.cs
class CodingWscBlock (line 6) | internal class CodingWscBlock : Block
method CodingWscBlock (line 8) | public CodingWscBlock(List<byte> bytes) : base(bytes)
method ToString (line 22) | public override string ToString()
FILE: Blocks/CustomBlock.cs
class CustomBlock (line 5) | internal class CustomBlock : Block
method CustomBlock (line 7) | public CustomBlock(List<byte> bytes) : base(bytes)
method Dump (line 12) | private void Dump()
FILE: Blocks/FaultCodesBlock.cs
class FaultCodesBlock (line 6) | internal class FaultCodesBlock : Block
method FaultCodesBlock (line 8) | public FaultCodesBlock(List<byte> bytes) : base(bytes)
type FaultCode (line 38) | internal struct FaultCode
method FaultCode (line 40) | public FaultCode(int dtc, int status)
method ToString (line 46) | public override string ToString()
FILE: Blocks/GroupReadResponseBlock.cs
class GroupReadResponseBlock (line 8) | internal class GroupReadResponseBlock : Block
method GroupReadResponseBlock (line 10) | public GroupReadResponseBlock(List<byte> bytes) : base(bytes)
method ToString (line 32) | public override string ToString()
FILE: Blocks/GroupReadResponseWithTextBlock.cs
class GroupReadResponseWithTextBlock (line 8) | internal class GroupReadResponseWithTextBlock : Block
method GroupReadResponseWithTextBlock (line 10) | public GroupReadResponseWithTextBlock(List<byte> bytes)
method GetText (line 52) | public string GetText(int i)
method ToString (line 62) | public override string ToString()
class SubBlock (line 76) | class SubBlock
method ToString (line 84) | public override string ToString()
FILE: Blocks/NakBlock.cs
class NakBlock (line 6) | class NakBlock : Block
method NakBlock (line 8) | public NakBlock(List<byte> bytes) : base(bytes)
FILE: Blocks/RawDataReadResponseBlock.cs
class RawDataReadResponseBlock (line 5) | internal class RawDataReadResponseBlock : Block
method RawDataReadResponseBlock (line 7) | public RawDataReadResponseBlock(List<byte> bytes) : base(bytes)
method ToString (line 11) | public override string ToString()
FILE: Blocks/ReadEepromResponseBlock.cs
class ReadEepromResponseBlock (line 5) | internal class ReadEepromResponseBlock : Block
method ReadEepromResponseBlock (line 7) | public ReadEepromResponseBlock(List<byte> bytes) : base(bytes)
method Dump (line 12) | private void Dump()
FILE: Blocks/ReadRomEepromResponse.cs
class ReadRomEepromResponse (line 5) | internal class ReadRomEepromResponse : Block
method ReadRomEepromResponse (line 7) | public ReadRomEepromResponse(List<byte> bytes) : base(bytes)
method Dump (line 12) | private void Dump()
FILE: Blocks/SecurityAccessMode2Block.cs
class SecurityAccessMode2Block (line 5) | internal class SecurityAccessMode2Block : Block
method SecurityAccessMode2Block (line 7) | public SecurityAccessMode2Block(List<byte> bytes) : base(bytes)
method Dump (line 12) | private void Dump()
FILE: Blocks/SensorValue.cs
class SensorValue (line 5) | public class SensorValue
method SensorValue (line 13) | public SensorValue(byte sensorID, byte a, byte b)
method ToString (line 20) | public override string ToString()
FILE: Blocks/UnknownBlock.cs
class UnknownBlock (line 5) | internal class UnknownBlock : Block
method UnknownBlock (line 7) | public UnknownBlock(List<byte> bytes) : base(bytes)
method Dump (line 12) | private void Dump()
FILE: Blocks/WriteEepromResponseBlock.cs
class WriteEepromResponseBlock (line 6) | internal class WriteEepromResponseBlock : Block
method WriteEepromResponseBlock (line 8) | public WriteEepromResponseBlock(List<byte> bytes) : base(bytes)
method Dump (line 13) | private void Dump()
FILE: BusyWait.cs
class BusyWait (line 5) | public class BusyWait
method BusyWait (line 10) | public BusyWait(long msPerCycle)
method DelayUntilNextCycle (line 15) | public void DelayUntilNextCycle()
method Delay (line 25) | public static void Delay(long ms)
FILE: Cluster/AudiC5Cluster.cs
class AudiC5Cluster (line 12) | internal class AudiC5Cluster : ICluster
method UnlockForEepromReadWrite (line 14) | public void UnlockForEepromReadWrite()
method DumpEeprom (line 57) | public string DumpEeprom(uint? address, uint? length, string? dumpFile...
method DumpEeprom (line 120) | private void DumpEeprom(
method ReadEepromByAddress (line 152) | private List<byte> ReadEepromByAddress(uint addr, byte readLength)
method BlockTitle (line 182) | private static byte BlockTitle(IReadOnlyList<byte> blockBytes)
method WriteBlock (line 187) | private void WriteBlock(IReadOnlyCollection<byte> bodyBytes)
method ReadBlock (line 208) | private List<byte> ReadBlock()
class Constants (line 250) | private static class Constants
method AudiC5Cluster (line 263) | public AudiC5Cluster(IKW1281Dialog kw1281Dialog)
FILE: Cluster/BoschRBxCluster.cs
class BoschRBxCluster (line 9) | class BoschRBxCluster : ICluster
method UnlockForEepromReadWrite (line 11) | public void UnlockForEepromReadWrite()
method DumpEeprom (line 16) | public string DumpEeprom(
method SecurityAccess (line 28) | public bool SecurityAccess(byte accessMode)
method ToggleRB4Mode (line 84) | public void ToggleRB4Mode()
method CalcRBxKey (line 122) | static uint CalcRBxKey(uint seed)
method BoschRBxCluster (line 130) | public BoschRBxCluster(KW2000Dialog kwp2000)
FILE: Cluster/ICluster.cs
type ICluster (line 3) | internal interface ICluster
method UnlockForEepromReadWrite (line 5) | void UnlockForEepromReadWrite();
method DumpEeprom (line 7) | string DumpEeprom(uint? address, uint? length, string? dumpFileName);
FILE: Cluster/MarelliCluster.cs
class MarelliCluster (line 9) | class MarelliCluster : ICluster
method UnlockForEepromReadWrite (line 11) | public void UnlockForEepromReadWrite()
method DumpEeprom (line 16) | public string DumpEeprom(uint? address, uint? length, string? dumpFile...
method GetDefaultAddress (line 26) | private ushort GetDefaultAddress()
method DumpMem (line 55) | private byte[] DumpMem(
method WriteMarelliBlockAndReadAck (line 200) | private bool WriteMarelliBlockAndReadAck(byte[] data)
method HasSmallEeprom (line 248) | private bool HasSmallEeprom() => _smallEepromEcus.Any(model => _ecuInf...
method HasLargeEeprom (line 268) | private bool HasLargeEeprom() => _largeEepromEcus.Any(model => _ecuInf...
method GetSkc (line 274) | public static ushort? GetSkc(byte[] buf)
method FindImmobilizerId (line 296) | private static int? FindImmobilizerId(IReadOnlyList<byte> buf)
method FindKeyCount (line 337) | private static int? FindKeyCount(IReadOnlyList<byte> buf)
method MarelliCluster (line 365) | public MarelliCluster(IKW1281Dialog kwp1281, string ecuInfo)
FILE: Cluster/MotometerBOOCluster.cs
class MotometerBOOCluster (line 8) | internal class MotometerBOOCluster : ICluster
method UnlockForEepromReadWrite (line 10) | public void UnlockForEepromReadWrite()
method SendCustom (line 48) | private bool SendCustom(int first, int second)
method DumpEeprom (line 99) | public string DumpEeprom(
method GetClusterInfo (line 130) | private string GetClusterInfo()
method MotometerBOOCluster (line 146) | public MotometerBOOCluster(IKW1281Dialog kwp1281)
FILE: Cluster/VdoCluster.cs
class VdoCluster (line 11) | internal class VdoCluster : ICluster
method UnlockForEepromReadWrite (line 13) | public void UnlockForEepromReadWrite()
method DumpEeprom (line 39) | public string DumpEeprom(
method CustomReadSoftwareVersion (line 54) | public Dictionary<int, Block> CustomReadSoftwareVersion()
method CustomReset (line 86) | public void CustomReset()
method CustomReadMemory (line 92) | public List<byte> CustomReadMemory(uint address, byte count)
method CustomReadNecRom (line 120) | public List<byte> CustomReadNecRom(ushort address, byte count)
method MapEeprom (line 138) | public List<byte> MapEeprom()
method DumpMem (line 157) | public void DumpMem(string dumpFileName, uint startAddress, uint length)
method SendCustom (line 190) | private List<Block> SendCustom(List<byte> blockCustomBytes)
method Unlock (line 203) | public (bool succeeded, string? softwareVersion) Unlock()
method SeedKeyAuthenticate (line 253) | public void SeedKeyAuthenticate(string? softwareVersion)
method RequiresSeedKey (line 276) | public bool RequiresSeedKey()
method GetAccessLevel (line 282) | private int? GetAccessLevel()
method GetSkc (line 307) | public static ushort? GetSkc(byte[] bytes, int startAddress)
method CustomUnlockAdditionalCommands (line 352) | private void CustomUnlockAdditionalCommands()
method GetClusterUnlockCodes (line 362) | internal static byte[][] GetClusterUnlockCodes(string softwareVersion)
method SoftwareVersionToString (line 493) | private static string SoftwareVersionToString(List<byte> versionBytes)
method DumpMixedContent (line 752) | private static string DumpMixedContent(Block block)
method DumpBinaryContent (line 762) | private static string DumpBinaryContent(Block block)
method DumpEeprom (line 772) | private void DumpEeprom(
method WriteRam (line 803) | public void WriteRam(ushort address, byte value)
method VdoCluster (line 820) | public VdoCluster(IKW1281Dialog kwp1281)
FILE: Cluster/VdoKeyFinder.cs
class VdoKeyFinder (line 7) | public static class VdoKeyFinder
method FindKey (line 13) | public static byte[] FindKey(
method CalculateKey (line 131) | private static byte[] CalculateKey(
method Scramble (line 180) | private static void Scramble(byte[] work)
method SetOrClearBits (line 189) | private static byte SetOrClearBits(
method RightRotateFirst4Bytes (line 205) | private static void RightRotateFirst4Bytes(
method LeftRotateFirstTwoBytes (line 219) | private static void LeftRotateFirstTwoBytes(
method LeftRotate (line 234) | private static byte LeftRotate(
FILE: ControllerAddress.cs
type ControllerAddress (line 6) | enum ControllerAddress
FILE: ControllerIdent.cs
class ControllerIdent (line 11) | internal class ControllerIdent
method ControllerIdent (line 13) | public ControllerIdent(IEnumerable<Block> blocks)
method ToString (line 37) | public override string ToString()
FILE: ControllerInfo.cs
class ControllerInfo (line 11) | internal class ControllerInfo
method ControllerInfo (line 13) | public ControllerInfo(IEnumerable<Block> blocks)
method ToString (line 48) | public override string ToString()
FILE: EDC15/Edc15VM.cs
class Edc15VM (line 11) | public class Edc15VM
method ReadWriteEeprom (line 13) | public byte[] ReadWriteEeprom(
method DisplayEepromInfo (line 147) | public static void DisplayEepromInfo(ReadOnlySpan<byte> eeprom)
method LVL41Auth (line 181) | private static byte[] LVL41Auth(long key, long key3, byte[] buf)
method GetLoader (line 256) | private static byte[] GetLoader()
method CalcPadding (line 299) | private static byte[] CalcPadding(ushort r6, ushort r1)
method Checksum (line 321) | static void Checksum(ref ushort r6, ref ushort r1, byte[] buf)
method Ror (line 350) | private static ushort Ror(ushort value, ushort count)
method Rol (line 361) | private static ushort Rol(ushort value, ushort count, out ushort carry)
method GetBuf (line 369) | private static ushort GetBuf(byte[] buf, int ix)
method Edc15VM (line 377) | public Edc15VM(IKwpCommon kwpCommon, int controllerAddress)
FILE: Interface/FtdiInterface.cs
class FtdiInterface (line 8) | internal class FtdiInterface : IInterface
method FtdiInterface (line 14) | public FtdiInterface(string serialNumber, int baudRate)
method Dispose (line 48) | public void Dispose()
method ReadByte (line 62) | public byte ReadByte()
method WriteByteRaw (line 78) | public void WriteByteRaw(byte b)
method SetBreak (line 90) | public void SetBreak(bool on)
method ClearReceiveBuffer (line 104) | public void ClearReceiveBuffer()
method SetBaudRate (line 110) | public void SetBaudRate(int baudRate)
method SetParity (line 116) | public void SetParity(Parity parity)
method SetDtr (line 136) | public void SetDtr(bool on)
method SetRts (line 150) | public void SetRts(bool on)
class FT (line 198) | class FT : IDisposable
method FT (line 223) | public FT()
method InitDelegate (line 291) | private void InitDelegate<T>(string fieldName, out T delegateVal) wher...
method Dispose (line 304) | public void Dispose()
method AssertOk (line 313) | public static void AssertOk(FT.Status status)
method SetVidPid (line 322) | public Status SetVidPid(
method Open (line 329) | public Status Open(
method Close (line 337) | public Status Close(
method SetBaudRate (line 343) | public Status SetBaudRate(
method SetDataCharacteristics (line 350) | public Status SetDataCharacteristics(
method SetFlowControl (line 359) | public Status SetFlowControl(
method SetDtr (line 368) | public Status SetDtr(
method ClrDtr (line 374) | public Status ClrDtr(
method SetRts (line 380) | public Status SetRts(
method ClrRts (line 386) | public Status ClrRts(
method SetTimeouts (line 392) | public Status SetTimeouts(
method SetLatencyTimer (line 400) | public Status SetLatencyTimer(
method Purge (line 407) | public Status Purge(
method SetBreakOn (line 414) | public Status SetBreakOn(
method SetBreakOff (line 420) | public Status SetBreakOff(
method Read (line 426) | public Status Read(
method Write (line 435) | public Status Write(
type Status (line 444) | public enum Status : uint
type OpenExFlags (line 468) | [Flags]
type Bits (line 476) | public enum Bits : byte
type StopBits (line 482) | public enum StopBits : byte
type Parity (line 488) | public enum Parity : byte
type FlowControl (line 497) | public enum FlowControl : ushort
type PurgeMask (line 505) | [Flags]
class SymbolNameAttribute (line 513) | [AttributeUsage(AttributeTargets.Delegate)]
method SymbolNameAttribute (line 516) | public SymbolNameAttribute(string name)
class FTDll (line 524) | static class FTDll
FILE: Interface/GenericInterface.cs
class GenericInterface (line 5) | internal class GenericInterface : IInterface
method GenericInterface (line 7) | public GenericInterface(string portName, int baudRate)
method Dispose (line 26) | public void Dispose()
method ReadByte (line 32) | public byte ReadByte()
method WriteByteRaw (line 38) | public void WriteByteRaw(byte b)
method SetBreak (line 44) | public void SetBreak(bool on)
method ClearReceiveBuffer (line 49) | public void ClearReceiveBuffer()
method SetBaudRate (line 54) | public void SetBaudRate(int baudRate)
method SetParity (line 59) | public void SetParity(Parity parity)
method SetDtr (line 64) | public void SetDtr(bool on)
method SetRts (line 69) | public void SetRts(bool on)
FILE: Interface/IInterface.cs
type IInterface (line 6) | public interface IInterface : IDisposable
method ReadByte (line 14) | byte ReadByte();
method WriteByteRaw (line 19) | void WriteByteRaw(byte b);
method SetBreak (line 21) | void SetBreak(bool on);
method ClearReceiveBuffer (line 23) | void ClearReceiveBuffer();
method SetBaudRate (line 25) | void SetBaudRate(int baudRate);
method SetParity (line 27) | void SetParity(Parity parity);
method SetDtr (line 29) | void SetDtr(bool on);
method SetRts (line 31) | void SetRts(bool on);
FILE: Interface/LinuxInterface.cs
class LinuxInterface (line 12) | public class LinuxInterface : IInterface
method ioctl (line 26) | [DllImport(libc, SetLastError = true)]
method ioctl (line 29) | [DllImport(libc, SetLastError = true)]
method open (line 33) | [DllImport(libc)]
method close (line 36) | [DllImport(libc)]
method read (line 39) | [DllImport(libc, CallingConvention = CallingConvention.StdCall)]
method write (line 42) | [DllImport(libc, CallingConvention = CallingConvention.StdCall)]
method tcflush (line 45) | [DllImport(libc, CallingConvention = CallingConvention.StdCall)]
type Termios (line 53) | [StructLayout(LayoutKind.Sequential)]
method _IOC (line 82) | private static uint _IOC(int dir, int type, int nr, int size)
method _IOC_TYPECHECK (line 90) | private static int _IOC_TYPECHECK(Type type)
method _IOR (line 95) | private static uint _IOR(int type, int nr, Type size)
method _IOW (line 100) | private static uint _IOW(int type, int nr, Type size)
method LinuxInterface (line 130) | public LinuxInterface(string portName, int baudRate)
method GetTtyConfiguration (line 157) | private Termios GetTtyConfiguration()
method SetTtyConfiguration (line 167) | private void SetTtyConfiguration(Termios termios)
method ReadByte (line 181) | public byte ReadByte()
method WriteByteRaw (line 193) | public void WriteByteRaw(byte b)
method SetBreak (line 203) | public void SetBreak(bool on)
method ClearReceiveBuffer (line 215) | public void ClearReceiveBuffer()
method SetBaudRate (line 223) | public void SetBaudRate(int baudRate)
method SetParity (line 240) | public void SetParity(Parity parity)
method SetDtr (line 271) | public void SetDtr(bool on)
method SetRts (line 298) | public void SetRts(bool on)
method Dispose (line 320) | public void Dispose()
method Dispose (line 331) | protected virtual void Dispose(bool disposing)
FILE: KW1281Dialog.cs
type IKW1281Dialog (line 15) | internal interface IKW1281Dialog
method Connect (line 17) | ControllerInfo Connect();
method EndCommunication (line 19) | void EndCommunication();
method SetDisconnected (line 21) | void SetDisconnected();
method Login (line 23) | List<Block> Login(ushort code, int workshopCode);
method ReadIdent (line 25) | List<ControllerIdent> ReadIdent();
method ReadEeprom (line 30) | List<byte>? ReadEeprom(ushort address, byte count);
method WriteEeprom (line 32) | bool WriteEeprom(ushort address, List<byte> values);
method ReadRomEeprom (line 37) | List<byte>? ReadRomEeprom(ushort address, byte count);
method ReadRam (line 42) | List<byte>? ReadRam(ushort address, byte count);
method AdaptationRead (line 44) | bool AdaptationRead(byte channelNumber);
method AdaptationTest (line 46) | bool AdaptationTest(byte channelNumber, ushort channelValue);
method AdaptationSave (line 48) | bool AdaptationSave(byte channelNumber, ushort channelValue, int works...
method SendBlock (line 50) | void SendBlock(List<byte> blockBytes);
method ReceiveBlocks (line 52) | List<Block> ReceiveBlocks();
method ReadCcmRom (line 54) | List<byte>? ReadCcmRom(byte seg, byte msb, byte lsb, byte count);
method KeepAlive (line 59) | void KeepAlive();
method ActuatorTest (line 61) | ActuatorTestResponseBlock? ActuatorTest(byte value);
method ReadFaultCodes (line 63) | List<FaultCode>? ReadFaultCodes();
method ClearFaultCodes (line 70) | List<FaultCode>? ClearFaultCodes(int controllerAddress);
method SetSoftwareCoding (line 79) | bool SetSoftwareCoding(int controllerAddress, int softwareCoding, int ...
method GroupRead (line 81) | bool GroupRead(byte groupNumber, bool useBasicSetting = false);
method ReadSecureImmoAccess (line 83) | List<byte> ReadSecureImmoAccess(List<byte> blockBytes);
method ReceiveBlock (line 86) | Block ReceiveBlock();
class KW1281Dialog (line 89) | internal class KW1281Dialog : IKW1281Dialog
method Connect (line 91) | public ControllerInfo Connect()
method Login (line 98) | public List<Block> Login(ushort code, int workshopCode)
method ReadIdent (line 114) | public List<ControllerIdent> ReadIdent()
method ReadEeprom (line 142) | public List<byte>? ReadEeprom(ushort address, byte count)
method ReadRam (line 174) | public List<byte>? ReadRam(ushort address, byte count)
method ReadCcmRom (line 208) | public List<byte>? ReadCcmRom(byte seg, byte msb, byte lsb, byte count)
method WriteEeprom (line 246) | public bool WriteEeprom(ushort address, List<byte> values)
method ReadRomEeprom (line 293) | public List<byte>? ReadRomEeprom(ushort address, byte count)
method EndCommunication (line 319) | public void EndCommunication()
method SetDisconnected (line 329) | public void SetDisconnected()
method SendBlock (line 335) | public void SendBlock(List<byte> blockBytes)
method ReceiveBlocks (line 357) | public List<Block> ReceiveBlocks()
method WriteByteAndReadAck (line 391) | private void WriteByteAndReadAck(byte b)
method ReceiveBlock (line 397) | public Block ReceiveBlock()
method SendAckBlock (line 459) | private void SendAckBlock()
method ReadBlockCounter (line 465) | private byte ReadBlockCounter()
method ReadAndAckByte (line 482) | private byte ReadAndAckByte()
method ReadAndAckByteFirst (line 494) | private byte ReadAndAckByteFirst(int count = 0)
method KeepAlive (line 521) | public void KeepAlive()
method ActuatorTest (line 532) | public ActuatorTestResponseBlock? ActuatorTest(byte value)
method ReadFaultCodes (line 559) | public List<FaultCode>? ReadFaultCodes()
method ClearFaultCodes (line 586) | public List<FaultCode>? ClearFaultCodes(int controllerAddress)
method SetSoftwareCoding (line 613) | public bool SetSoftwareCoding(int controllerAddress, int softwareCodin...
method AdaptationRead (line 645) | public bool AdaptationRead(byte channelNumber)
method AdaptationTest (line 659) | public bool AdaptationTest(byte channelNumber, ushort channelValue)
method AdaptationSave (line 675) | public bool AdaptationSave(byte channelNumber, ushort channelValue, in...
method ReceiveAdaptationBlock (line 694) | private bool ReceiveAdaptationBlock()
method GroupRead (line 714) | public bool GroupRead(byte groupNumber, bool useBasicSetting = false)
method RawDataRead (line 804) | private bool RawDataRead(bool useBasicSetting)
method ReadSecureImmoAccess (line 839) | public List<byte> ReadSecureImmoAccess(List<byte> blockBytes)
method Overlay (line 866) | private static void Overlay(string message)
class TimeInterval (line 879) | private static class TimeInterval
method KW1281Dialog (line 894) | public KW1281Dialog(IKwpCommon kwpCommon)
class KW1281KeepAlive (line 906) | internal class KW1281KeepAlive : IDisposable
method KW1281KeepAlive (line 912) | public KW1281KeepAlive(IKW1281Dialog kw1281Dialog)
method ActuatorTest (line 917) | public ActuatorTestResponseBlock? ActuatorTest(byte value)
method Dispose (line 925) | public void Dispose()
method Pause (line 930) | private void Pause()
method Resume (line 939) | private void Resume()
method KeepAlive (line 944) | private void KeepAlive()
FILE: Kwp2000/DiagnosticService.cs
type DiagnosticService (line 3) | public enum DiagnosticService : byte
FILE: Kwp2000/KW2000Dialog.cs
class KW2000Dialog (line 12) | internal class KW2000Dialog
method DumpMem (line 26) | public void DumpMem(uint address, uint length, string dumpFileName)
method DumpMemory (line 39) | private void DumpMemory(
method StartDiagnosticSession (line 69) | public void StartDiagnosticSession(byte v1, byte v2)
method EcuReset (line 78) | public void EcuReset(byte value)
method ReadMemoryByAddress (line 83) | public byte[] ReadMemoryByAddress(uint address, byte count)
method WriteMemoryByAddress (line 97) | public byte[] WriteMemoryByAddress(uint address, byte count, byte[] data)
method SendReceive (line 116) | public Kwp2000Message SendReceive(
method SendMessage (line 157) | public void SendMessage(Service service, byte[] body, bool excludeAddr...
method ReceiveMessage (line 199) | public Kwp2000Message ReceiveMessage()
method KW2000Dialog (line 232) | public KW2000Dialog(IKwpCommon kwpCommon, byte controllerAddress)
FILE: Kwp2000/Kwp2000Message.cs
class Kwp2000Message (line 9) | public class Kwp2000Message
method CalcChecksum (line 23) | public byte CalcChecksum()
method Kwp2000Message (line 34) | public Kwp2000Message(
method Kwp2000Message (line 45) | public Kwp2000Message(
method Kwp2000Message (line 56) | public Kwp2000Message(
method ToString (line 93) | public override string ToString()
method IsPositiveResponse (line 107) | public bool IsPositiveResponse(DiagnosticService service)
method DescribeService (line 112) | public string DescribeService()
method CalcFormatByte (line 142) | private static byte CalcFormatByte(IList<byte> body, bool excludeAddre...
method CalcLengthByte (line 153) | private static byte? CalcLengthByte(IList<byte> body)
FILE: Kwp2000/NegativeResponseException.cs
class NegativeResponseException (line 5) | public class NegativeResponseException : Exception
method NegativeResponseException (line 7) | public NegativeResponseException(Kwp2000Message kwp2000Message)
FILE: Kwp2000/ResponseCode.cs
type ResponseCode (line 6) | public enum ResponseCode
FILE: KwpCommon.cs
type IKwpCommon (line 9) | public interface IKwpCommon
method WakeUp (line 13) | int WakeUp(byte controllerAddress, bool evenParity = false, bool failQ...
method ReadByte (line 15) | byte ReadByte();
method WriteByte (line 21) | void WriteByte(byte b);
method ReadComplement (line 23) | void ReadComplement(byte b);
class KwpCommon (line 26) | internal class KwpCommon : IKwpCommon
method WakeUp (line 30) | public int WakeUp(byte controllerAddress, bool evenParity, bool failQu...
method WakeUpNoRetry (line 90) | private int WakeUpNoRetry(byte controllerAddress, bool evenParity)
method ReadByte (line 146) | public byte ReadByte()
method WriteByte (line 151) | public void WriteByte(byte b)
method ReadComplement (line 156) | public void ReadComplement(byte b)
method BitBang5Baud (line 175) | private void BitBang5Baud(byte b, bool evenParity)
method WriteByteAndDiscardEcho (line 215) | private void WriteByteAndDiscardEcho(byte b)
method KwpCommon (line 227) | public KwpCommon(IInterface @interface)
FILE: Logging/ConsoleLog.cs
class ConsoleLog (line 5) | internal class ConsoleLog : ILog
method Write (line 7) | public void Write(string message, LogDest dest)
method WriteLine (line 15) | public void WriteLine(LogDest dest)
method WriteLine (line 23) | public void WriteLine(string message, LogDest dest)
method Close (line 31) | public void Close()
method Dispose (line 35) | public void Dispose()
FILE: Logging/FileLog.cs
class FileLog (line 6) | internal class FileLog : ILog
method FileLog (line 10) | public FileLog(string filename)
method WriteLine (line 15) | public void WriteLine(string message, LogDest dest)
method WriteLine (line 27) | public void WriteLine(LogDest dest)
method Write (line 39) | public void Write(string message, LogDest dest)
method Close (line 51) | public void Close()
method Dispose (line 56) | public void Dispose()
FILE: Logging/ILog.cs
type LogDest (line 5) | internal enum LogDest
type ILog (line 12) | internal interface ILog : IDisposable
method Write (line 14) | void Write(string message, LogDest dest = LogDest.All);
method WriteLine (line 16) | void WriteLine(LogDest dest = LogDest.All);
method WriteLine (line 18) | void WriteLine(string message, LogDest dest = LogDest.All);
method Close (line 20) | void Close();
FILE: Program.cs
class Program (line 23) | class Program
method Main (line 29) | static void Main(string[] args)
method Run (line 56) | void Run(string[] args)
method AutoScan (line 449) | private static void AutoScan(IInterface @interface)
method ParseAddressesAndValues (line 487) | internal static bool ParseAddressesAndValues(
method OpenPort (line 551) | private static IInterface OpenPort(string portName, int baudRate)
method ShowUsage (line 571) | private static void ShowUsage()
FILE: Tester.cs
class Tester (line 13) | internal class Tester
method Tester (line 20) | public Tester(IInterface @interface, int controllerAddress)
method Kwp1281Wakeup (line 27) | public ControllerInfo Kwp1281Wakeup(bool evenParityWakeup = false, boo...
method Kwp2000Wakeup (line 43) | public KW2000Dialog Kwp2000Wakeup(bool evenParityWakeup = false)
method EndCommunication (line 59) | public void EndCommunication()
method ActuatorTest (line 66) | public void ActuatorTest()
method AdaptationRead (line 91) | public void AdaptationRead(
method AdaptationSave (line 102) | public void AdaptationSave(
method AdaptationTest (line 113) | public void AdaptationTest(
method BasicSettingRead (line 124) | public void BasicSettingRead(byte groupNumber)
method ClarionVWPremium4SafeCode (line 129) | public void ClarionVWPremium4SafeCode()
method ClearFaultCodes (line 160) | public void ClearFaultCodes()
method DelcoVWPremium5SafeCode (line 185) | public void DelcoVWPremium5SafeCode()
method DumpCcmRom (line 210) | public void DumpCcmRom(string? filename)
method DumpClusterNecRom (line 264) | public void DumpClusterNecRom(string? filename)
method FindLogins (line 311) | public void FindLogins(ushort goodLogin, int workshopCode)
method ReadWriteEdc15Eeprom (line 341) | public byte[] ReadWriteEdc15Eeprom(
method DumpEeprom (line 365) | public void DumpEeprom(uint address, uint length, string? filename)
method DumpMarelliMem (line 383) | public void DumpMarelliMem(
method DumpMem (line 397) | public void DumpMem(uint address, uint length, string? filename)
method DumpRam (line 408) | public void DumpRam(uint startAddr, uint length, string? filename)
method DumpRom (line 442) | public void DumpRom(uint startAddr, uint length, string? filename)
method DumpRBxMem (line 480) | public string? DumpRBxMem(
method GetClusterId (line 505) | public void GetClusterId()
method GetSkc (line 542) | public void GetSkc()
method FindAndParsePartNumber (line 773) | internal static string[] FindAndParsePartNumber(string ecuInfo)
method GroupRead (line 789) | public void GroupRead(byte groupNumber)
method LoadEeprom (line 794) | public void LoadEeprom(uint address, string filename)
method MapEeprom (line 812) | public void MapEeprom(string? filename)
method ReadEeprom (line 830) | public void ReadEeprom(uint address)
method ReadRam (line 847) | public void ReadRam(uint address)
method ReadRom (line 864) | public void ReadRom(uint address)
method ReadFaultCodes (line 881) | public void ReadFaultCodes()
method ReadIdent (line 894) | public void ReadIdent()
method ReadSoftwareVersion (line 902) | public void ReadSoftwareVersion()
method Reset (line 915) | public void Reset()
method SetSoftwareCoding (line 928) | public void SetSoftwareCoding(
method ToggleRB4Mode (line 942) | public void ToggleRB4Mode()
method WriteEeprom (line 951) | public void WriteEeprom(uint address, byte value)
method WriteRam (line 958) | public void WriteRam(uint address, byte value)
method ClusterWriteRam (line 974) | private void ClusterWriteRam(ushort address, byte value)
method BOOClusterDumpEeprom (line 997) | private string BOOClusterDumpEeprom(ushort startAddress, ushort length...
method ClusterDumpEeprom (line 1022) | private string ClusterDumpEeprom(
method CcmMapEeprom (line 1041) | private void CcmMapEeprom(string? filename)
method ClusterMapEeprom (line 1060) | private void ClusterMapEeprom(string? filename)
method CcmDumpEeprom (line 1071) | private void CcmDumpEeprom(ushort startAddress, ushort length, string?...
method UnlockControllerForEepromReadWrite (line 1082) | private void UnlockControllerForEepromReadWrite()
method DumpEeprom (line 1128) | private void DumpEeprom(
method WriteEeprom (line 1159) | private void WriteEeprom(
method CcmLoadEeprom (line 1181) | private void CcmLoadEeprom(ushort address, string filename)
method ClusterLoadEeprom (line 1200) | private void ClusterLoadEeprom(ushort address, string filename)
method ClusterDumpMem (line 1219) | private void ClusterDumpMem(uint startAddress, uint length, string? fi...
method DecodeClusterId (line 1247) | private static (byte, byte) DecodeClusterId(byte b1, byte b2, byte b3,...
FILE: Tests/Cluster/MarelliClusterTests.cs
class MarelliClusterTests (line 5) | [TestClass]
method GetSkc_ReturnsCorrectSkc (line 8) | [TestMethod]
method GetSkc_NoImmoIdAndNoKeyCountPattern_ReturnsNull (line 20) | [TestMethod]
FILE: Tests/Cluster/VdoClusterTests.cs
class VdoClusterTests (line 6) | [TestClass]
method GetClusterUnlockCodes_ReturnsCorrectCode (line 9) | [TestMethod]
method ClusterUnlockCodes_ContainsKnownCodes (line 58) | [TestMethod]
method ClusterUnlockCodes_ContainsNoDuplicates (line 91) | [TestMethod]
FILE: Tests/ProgramTests.cs
class ProgramTests (line 3) | [TestClass]
method ParseAddressesAndValues_NumberOfArgumentsIsOdd_ReturnsFalse (line 6) | [TestMethod]
method ParseAddressesAndValues_ValidArguments_ReturnsList (line 14) | [TestMethod]
method ParseAddressesAndValues_AddressTooLarge_ReturnsFalse (line 26) | [TestMethod]
method ParseAddressesAndValues_ValueTooLarge_ReturnsFalse (line 35) | [TestMethod]
FILE: Tests/TesterTests.cs
class TesterTests (line 3) | [TestClass]
method FindAndParsePartNumber_ReturnsExpectedGroups (line 6) | [TestMethod]
FILE: Tests/UtilsTests.cs
class UtilsTests (line 5) | [TestClass]
method DumpMixedContent (line 8) | [TestMethod]
FILE: UnableToProceedException.cs
class UnableToProceedException (line 5) | class UnableToProceedException : Exception
FILE: UnexpectedProtocolException.cs
class UnexpectedProtocolException (line 5) | [Serializable]
method UnexpectedProtocolException (line 8) | public UnexpectedProtocolException()
method UnexpectedProtocolException (line 12) | public UnexpectedProtocolException(string? message) : base(message)
method UnexpectedProtocolException (line 16) | public UnexpectedProtocolException(string? message, Exception? innerEx...
FILE: Utils.cs
class Utils (line 8) | internal static class Utils
method Dump (line 10) | public static string Dump(IEnumerable<byte> bytes)
method DumpBytes (line 21) | public static string DumpBytes(IEnumerable<byte> bytes)
method DumpDecimal (line 31) | public static string DumpDecimal(IEnumerable<byte> bytes)
method DumpAscii (line 41) | public static string DumpAscii(IEnumerable<byte> bytes)
method DumpMixedContent (line 51) | public static string DumpMixedContent(IEnumerable<byte> content)
method ParseUint (line 81) | public static uint ParseUint(string numberString)
method GetShort (line 104) | public static ushort GetShort(ReadOnlySpan<byte> buf, int offset)
method GetShortBE (line 112) | public static ushort GetShortBE(byte[] buf, int offset)
method GetBcd (line 120) | public static ushort GetBcd(byte[] buf, int offset)
method GetBytes (line 138) | public static byte[] GetBytes(uint value)
method RightRotate (line 156) | public static (byte result, bool carry) RightRotate(
method LeftRotate (line 173) | public static (byte result, bool carry) LeftRotate(
method SubtractWithCarry (line 187) | public static (byte result, bool carry) SubtractWithCarry(
method AdjustParity (line 196) | public static byte AdjustParity(
Condensed preview — 67 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (319K chars).
[
{
"path": ".gitattributes",
"chars": 2518,
"preview": "###############################################################################\n# Set default behavior to automatically "
},
{
"path": ".github/FUNDING.yml",
"chars": 43,
"preview": "custom: [\"https://paypal.me/GregMenounos\"]\n"
},
{
"path": ".gitignore",
"chars": 5784,
"preview": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## G"
},
{
"path": ".vscode/launch.json",
"chars": 253,
"preview": "{\n // Use IntelliSense to learn about possible attributes.\n // Hover to view descriptions of existing attributes.\n"
},
{
"path": "BlockTitle.cs",
"chars": 2256,
"preview": "namespace BitFab.KW1281Test\n{\n public enum BlockTitle : byte\n {\n ReadIdent = 0x00,\n ReadRam = 0x01,"
},
{
"path": "Blocks/AckBlock.cs",
"chars": 334,
"preview": "using System;\nusing System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class AckBlock : Blo"
},
{
"path": "Blocks/ActuatorTestResponseBlock.cs",
"chars": 1689,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class ActuatorTestResponseBlock : "
},
{
"path": "Blocks/AdaptationResponseBlock.cs",
"chars": 618,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class AdaptationResponseBlock : Bl"
},
{
"path": "Blocks/AsciiDataBlock.cs",
"chars": 855,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal c"
},
{
"path": "Blocks/Block.cs",
"chars": 834,
"preview": "using System.Collections.Generic;\nusing System.Linq;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n /// <summary>\n /// KW"
},
{
"path": "Blocks/CodingWscBlock.cs",
"chars": 823,
"preview": "using System.Collections.Generic;\nusing System.Linq;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class CodingWsc"
},
{
"path": "Blocks/CustomBlock.cs",
"chars": 483,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class CustomBlock : Block\n {\n "
},
{
"path": "Blocks/FaultCodesBlock.cs",
"chars": 1411,
"preview": "using System.Collections.Generic;\nusing System.Linq;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class FaultCode"
},
{
"path": "Blocks/GroupReadResponseBlock.cs",
"chars": 1370,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace BitFab.KW1281Test.Bloc"
},
{
"path": "Blocks/GroupReadResponseWithTextBlock.cs",
"chars": 2989,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\n\nnamespace BitFab.KW1281Test.Bloc"
},
{
"path": "Blocks/NakBlock.cs",
"chars": 204,
"preview": "using System;\nusing System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n class NakBlock : Block\n {\n"
},
{
"path": "Blocks/RawDataReadResponseBlock.cs",
"chars": 351,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class RawDataReadResponseBlock : B"
},
{
"path": "Blocks/ReadEepromResponseBlock.cs",
"chars": 497,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class ReadEepromResponseBlock : Bl"
},
{
"path": "Blocks/ReadRomEepromResponse.cs",
"chars": 498,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class ReadRomEepromResponse : Bloc"
},
{
"path": "Blocks/SecurityAccessMode2Block.cs",
"chars": 503,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class SecurityAccessMode2Block : B"
},
{
"path": "Blocks/SensorValue.cs",
"chars": 4499,
"preview": "using System;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n public class SensorValue\n {\n public byte SensorID { g"
},
{
"path": "Blocks/UnknownBlock.cs",
"chars": 465,
"preview": "using System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class UnknownBlock : Block\n {\n "
},
{
"path": "Blocks/WriteEepromResponseBlock.cs",
"chars": 514,
"preview": "using System;\nusing System.Collections.Generic;\n\nnamespace BitFab.KW1281Test.Blocks\n{\n internal class WriteEepromRes"
},
{
"path": "BusyWait.cs",
"chars": 719,
"preview": "using System.Diagnostics;\n\nnamespace BitFab.KW1281Test;\n\npublic class BusyWait\n{\n private readonly long _ticksPerCycl"
},
{
"path": "Cluster/AudiC5Cluster.cs",
"chars": 7647,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.IO.Ports;\nusing System.Linq;\nusing System."
},
{
"path": "Cluster/BoschRBxCluster.cs",
"chars": 4109,
"preview": "using BitFab.KW1281Test.Kwp2000;\nusing System;\nusing System.Linq;\nusing System.Threading;\nusing Service = BitFab.KW1281"
},
{
"path": "Cluster/ICluster.cs",
"chars": 205,
"preview": "namespace BitFab.KW1281Test.Cluster\n{\n internal interface ICluster\n {\n void UnlockForEepromReadWrite();\n\n "
},
{
"path": "Cluster/MarelliCluster.cs",
"chars": 12952,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Threading;\n\nnamespace "
},
{
"path": "Cluster/MotometerBOOCluster.cs",
"chars": 4555,
"preview": "using BitFab.KW1281Test.Blocks;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace BitFab.KW"
},
{
"path": "Cluster/VdoCluster.cs",
"chars": 27740,
"preview": "using BitFab.KW1281Test.Blocks;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusi"
},
{
"path": "Cluster/VdoKeyFinder.cs",
"chars": 7879,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\n\nnamespace BitFab.KW1281Test.Cluster\n{\n public st"
},
{
"path": "ControllerAddress.cs",
"chars": 407,
"preview": "namespace BitFab.KW1281Test\n{\n /// <summary>\n /// VW controller addresses\n /// </summary>\n enum ControllerA"
},
{
"path": "ControllerIdent.cs",
"chars": 1098,
"preview": "using BitFab.KW1281Test.Blocks;\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace BitFab.KW"
},
{
"path": "ControllerInfo.cs",
"chars": 1484,
"preview": "using BitFab.KW1281Test.Blocks;\nusing System;\nusing System.Collections.Generic;\nusing System.Text;\n\nnamespace BitFab.KW"
},
{
"path": "EDC15/Edc15VM.cs",
"chars": 14395,
"preview": "using BitFab.KW1281Test.Kwp2000;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nus"
},
{
"path": "EDC15/Loader.a66",
"chars": 15519,
"preview": "; Custom loader. When loaded at address 40E000 and started via startRoutineByLocalIdentifier 0x02,\n; it will accept seve"
},
{
"path": "Interface/FtdiInterface.cs",
"chars": 17575,
"preview": "using System;\nusing System.IO.Ports;\nusing System.Reflection;\nusing System.Runtime.InteropServices;\n\nnamespace BitFab.K"
},
{
"path": "Interface/GenericInterface.cs",
"chars": 2023,
"preview": "using System.IO.Ports;\n\nnamespace BitFab.KW1281Test.Interface\n{\n internal class GenericInterface : IInterface\n {\n"
},
{
"path": "Interface/IInterface.cs",
"chars": 833,
"preview": "using System;\nusing System.IO.Ports;\n\nnamespace BitFab.KW1281Test.Interface\n{\n public interface IInterface : IDispos"
},
{
"path": "Interface/LinuxInterface.cs",
"chars": 10472,
"preview": "using System;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.IO.Ports;\n\nusing tcflag_t = System.UI"
},
{
"path": "KW1281Dialog.cs",
"chars": 28165,
"preview": "using BitFab.KW1281Test.Blocks;\nusing BitFab.KW1281Test.Logging;\nusing System;\nusing System.Collections.Generic;\nusing "
},
{
"path": "Kwp2000/DiagnosticService.cs",
"chars": 584,
"preview": "namespace BitFab.KW1281Test.Kwp2000\n{\n public enum DiagnosticService : byte\n {\n startDiagnosticSession = 0"
},
{
"path": "Kwp2000/KW2000Dialog.cs",
"chars": 7777,
"preview": "using BitFab.KW1281Test.Kwp2000;\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System"
},
{
"path": "Kwp2000/Kwp2000Message.cs",
"chars": 4639,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing System.Linq;\nusing System.Text;\n\nnamesp"
},
{
"path": "Kwp2000/NegativeResponseException.cs",
"chars": 315,
"preview": "using System;\n\nnamespace BitFab.KW1281Test.Kwp2000\n{\n public class NegativeResponseException : Exception\n {\n "
},
{
"path": "Kwp2000/ResponseCode.cs",
"chars": 1280,
"preview": "namespace BitFab.KW1281Test.Kwp2000\n{\n /// <summary>\n /// \n /// </summary>\n public enum ResponseCode\n {\n"
},
{
"path": "KwpCommon.cs",
"chars": 7818,
"preview": "using BitFab.KW1281Test.Interface;\nusing System;\nusing System.Collections.Generic;\nusing System.Runtime;\nusing System.T"
},
{
"path": "LICENSE.txt",
"chars": 1068,
"preview": "MIT License\n\nCopyright © 2021 Greg Menounos\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "Logging/ConsoleLog.cs",
"chars": 746,
"preview": "using System;\n\nnamespace BitFab.KW1281Test.Logging\n{\n internal class ConsoleLog : ILog\n {\n public void Wri"
},
{
"path": "Logging/FileLog.cs",
"chars": 1311,
"preview": "using System;\nusing System.IO;\n\nnamespace BitFab.KW1281Test.Logging\n{\n internal class FileLog : ILog\n {\n p"
},
{
"path": "Logging/ILog.cs",
"chars": 401,
"preview": "using System;\n\nnamespace BitFab.KW1281Test.Logging\n{\n internal enum LogDest\n {\n All,\n Console,\n "
},
{
"path": "Program.cs",
"chars": 21243,
"preview": "global using static BitFab.KW1281Test.Program;\n\nusing BitFab.KW1281Test.Interface;\nusing BitFab.KW1281Test.Logging;\nusi"
},
{
"path": "Publish_Mac.ps1",
"chars": 1265,
"preview": "dotnet publish kw1281test.csproj /p:PublishProfile=Win\ndotnet publish kw1281test.csproj /p:PublishProfile=Mac\ndotnet pub"
},
{
"path": "Publish_Win.ps1",
"chars": 1275,
"preview": "dotnet publish kw1281test.csproj /p:PublishProfile=Win\ndotnet publish kw1281test.csproj /p:PublishProfile=Mac\ndotnet pub"
},
{
"path": "README.md",
"chars": 5795,
"preview": "# kw1281test\nVW KW1281 Protocol Test Tool\n\nThis tool can send some KW1281 (and a few KW2000) commands over a dumb serial"
},
{
"path": "Tester.cs",
"chars": 43229,
"preview": "using BitFab.KW1281Test.Cluster;\nusing BitFab.KW1281Test.EDC15;\nusing BitFab.KW1281Test.Interface;\nusing System;\nusing "
},
{
"path": "Tests/BitFab.KW1281Test.Tests.csproj",
"chars": 1304,
"preview": "<Project Sdk=\"Microsoft.NET.Sdk\">\n\n <PropertyGroup>\n <TargetFramework>net10.0</TargetFramework>\n <ImplicitUsings"
},
{
"path": "Tests/Cluster/MarelliClusterTests.cs",
"chars": 831,
"preview": "using BitFab.KW1281Test.Cluster;\n\nnamespace BitFab.KW1281Test.Tests.Cluster;\n\n[TestClass]\npublic class MarelliClusterTes"
},
{
"path": "Tests/Cluster/VdoClusterTests.cs",
"chars": 4978,
"preview": "using BitFab.KW1281Test.Cluster;\nusing Shouldly;\n\nnamespace BitFab.KW1281Test.Tests.Cluster\n{\n [TestClass]\n publi"
},
{
"path": "Tests/GlobalUsings.cs",
"chars": 189,
"preview": "global using Microsoft.VisualStudio.TestTools.UnitTesting;\nusing System.Diagnostics.CodeAnalysis;\n\n[assembly: Paralleliz"
},
{
"path": "Tests/ProgramTests.cs",
"chars": 1362,
"preview": "namespace BitFab.KW1281Test.Tests;\n\n[TestClass]\npublic class ProgramTests\n{\n [TestMethod]\n public void ParseAddres"
},
{
"path": "Tests/TesterTests.cs",
"chars": 1023,
"preview": "namespace BitFab.KW1281Test.Tests\n{\n [TestClass]\n public class TesterTests\n {\n [TestMethod]\n [Dat"
},
{
"path": "Tests/UtilsTests.cs",
"chars": 883,
"preview": "using Shouldly;\n\nnamespace BitFab.KW1281Test.Tests;\n\n[TestClass]\npublic class UtilsTests\n{\n [TestMethod]\n [DataRo"
},
{
"path": "UnableToProceedException.cs",
"chars": 107,
"preview": "using System;\n\nnamespace BitFab.KW1281Test\n{\n class UnableToProceedException : Exception\n {\n }\n}\n"
},
{
"path": "UnexpectedProtocolException.cs",
"chars": 439,
"preview": "using System;\n\nnamespace BitFab.KW1281Test\n{\n [Serializable]\n internal class UnexpectedProtocolException : Except"
},
{
"path": "Utils.cs",
"chars": 5007,
"preview": "using System;\nusing System.Collections.Generic;\nusing System.Globalization;\nusing System.Text;\n\nnamespace BitFab.KW1281"
},
{
"path": "kw1281test.slnx",
"chars": 120,
"preview": "<Solution>\n <Project Path=\"kw1281test.csproj\" />\n <Project Path=\"Tests/BitFab.KW1281Test.Tests.csproj\" />\n</Solution>\n"
}
]
About this extraction
This page contains the full source code of the gmenounos/kw1281test GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 67 files (295.5 KB), approximately 81.8k tokens, and a symbol index with 458 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.