-
-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathKwpCommon.cs
229 lines (191 loc) · 7.46 KB
/
KwpCommon.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
using BitFab.KW1281Test.Interface;
using System;
using System.Collections.Generic;
using System.Runtime;
using System.Threading;
namespace BitFab.KW1281Test
{
public interface IKwpCommon
{
IInterface Interface { get; }
int WakeUp(byte controllerAddress, bool evenParity = false);
byte ReadByte();
/// <summary>
/// Write a byte to the interface and receive its echo.
/// </summary>
/// <param name="b">The byte to write.</param>
void WriteByte(byte b);
void ReadComplement(byte b);
}
internal class KwpCommon : IKwpCommon
{
public IInterface Interface { get; }
public int WakeUp(byte controllerAddress, bool evenParity)
{
// Disable garbage collection int this time-critical method
var noGC = GC.TryStartNoGCRegion(1024 * 1024);
if (!noGC)
{
Log.WriteLine("Warning: Unable to disable GC so timing may be compromised.");
}
var protocolVersion = 0;
Interface.ReadTimeout = (int)TimeSpan.FromSeconds(2).TotalMilliseconds;
try
{
const int maxTries = 3;
for (var i = 1; i <= maxTries; i++)
{
try
{
protocolVersion = WakeUpNoRetry(controllerAddress, evenParity);
break;
}
catch (Exception ex)
{
Log.WriteLine(ex.Message);
if (i < maxTries)
{
Log.WriteLine("Retrying wakeup message...");
Thread.Sleep(TimeSpan.FromSeconds(1));
}
else
{
Log.WriteLine();
Log.WriteLine("Controller did not wake up.");
Log.WriteLine(" - Are you using a supported cable?");
Log.WriteLine(" - Is the cable plugged in and any necessary drivers installed?");
Log.WriteLine(" - Is the ignition on?");
Log.WriteLine(" - Is the controller address correct?");
Log.WriteLine(" - Is the baud rate correct (unexpected sync byte errors)? Try 10400, 9600, 4800.");
Log.WriteLine("You can try other software (e.g. VCDS-Lite) to verify that the cable/drivers/address are ok.");
throw new UnableToProceedException();
}
}
}
}
finally
{
if (GCSettings.LatencyMode == GCLatencyMode.NoGCRegion)
{
GC.EndNoGCRegion();
}
Interface.ReadTimeout = Interface.DefaultTimeoutMilliseconds;
}
return protocolVersion;
}
private int WakeUpNoRetry(byte controllerAddress, bool evenParity)
{
Thread.Sleep(300);
BitBang5Baud(controllerAddress, evenParity);
// Throw away anything that might be in the receive buffer
Interface.ClearReceiveBuffer();
Log.WriteLine("Reading sync byte");
// Buffer logging in memory until we're done with the wakeup, which is sensitive to timing
var logLines = new List<string>();
var syncByte = Interface.ReadByte();
if (syncByte != 0x55)
{
throw new InvalidOperationException(
$"Unexpected sync byte: Expected $55, Actual ${syncByte:X2}");
}
int protocolVersion;
try
{
var keywordLsb = Interface.ReadByte();
logLines.Add($"Keyword Lsb ${keywordLsb:X2}");
var keywordMsb = ReadByte();
logLines.Add($"Keyword Msb ${keywordMsb:X2}");
protocolVersion = ((keywordMsb & 0x7F) << 7) + (keywordLsb & 0x7F);
logLines.Add($"Protocol is KW {protocolVersion} (8N1)");
BusyWait.Delay(25);
var complement = (byte)~keywordMsb;
WriteByte(complement);
}
finally
{
foreach (var line in logLines)
{
Log.WriteLine(line);
}
}
if (protocolVersion >= 2000)
{
ReadComplement(
Utils.AdjustParity(controllerAddress, evenParity));
}
return protocolVersion;
}
public byte ReadByte()
{
return Interface.ReadByte();
}
public void WriteByte(byte b)
{
WriteByteAndDiscardEcho(b);
}
public void ReadComplement(byte b)
{
var expectedComplement = (byte)~b;
var actualComplement = Interface.ReadByte();
if (actualComplement != expectedComplement)
{
throw new InvalidOperationException(
$"Received complement ${actualComplement:X2} but expected ${expectedComplement:X2}");
}
}
/// <summary>
/// Send a byte at 5 baud manually to the interface. The byte will be sent as
/// 1 start bit, 7 data bits, 1 parity bit (even or odd), 1 stop bit.
/// https://www.blafusel.de/obd/obd2_kw1281.html
/// </summary>
/// <param name="b">The byte to send.</param>
/// <param name="evenParity">
/// False for odd parity (KWP1281), true for even parity (KWP2000).</param>
private void BitBang5Baud(byte b, bool evenParity)
{
b = Utils.AdjustParity(b, evenParity);
const int bitsPerSec = 5;
const long msPerBit = 1000 / bitsPerSec;
var waiter = new BusyWait(msPerBit);
// The first call to SetBreak takes extra time (at least with an FTDI cable on Linux)
// so do that here outside of the timing loop. Since the break state should already be
// false, this should have no effect other than to delay a couple milliseconds and it
// makes the timing of the rest of the bits be more accurate.
Interface.SetBreak(false);
BitBang(false); // Start bit
for (int i = 0; i < 8; i++)
{
bool bit = (b & 1) == 1;
BitBang(bit);
b >>= 1;
}
BitBang(true); // Stop bit
BusyWait.Delay(msPerBit);
return;
// Delay the appropriate amount and then set/clear the TxD line
void BitBang(bool bit)
{
waiter.DelayUntilNextCycle();
Interface.SetBreak(!bit);
}
}
/// <summary>
/// Write a byte to the interface and read/discard its echo.
/// </summary>
private void WriteByteAndDiscardEcho(byte b)
{
Interface.WriteByteRaw(b);
var echo = Interface.ReadByte();
#if false
if (echo != b)
{
throw new InvalidOperationException($"Wrote 0x{b:X2} to port but echo was 0x{echo:X2}");
}
#endif
}
public KwpCommon(IInterface @interface)
{
Interface = @interface;
}
}
}