Skip to content

Commit 6c9ec4c

Browse files
authored
Handle CAN frames properly (#175)
1 parent 87b2a8c commit 6c9ec4c

2 files changed

Lines changed: 44 additions & 2 deletions

File tree

‎src/ZWave.Serial/Logging.cs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,10 @@ internal static partial class Logging
101101
Level = LogLevel.Warning,
102102
Message = "Serial API frame transmission failed (attempt #{attempt})")]
103103
public static partial void LogSerialApiFrameTransmissionRetry(this ILogger logger, int attempt);
104+
105+
[LoggerMessage(
106+
EventId = 117,
107+
Level = LogLevel.Trace,
108+
Message = "Received CAN during frame delivery; deferring retransmission until the chip's data frame is processed")]
109+
public static partial void LogSerialApiCanDuringFrameDelivery(this ILogger logger);
104110
}

‎src/ZWave.Serial/ZWaveSerialPortCoordinator.cs‎

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ public sealed class ZWaveSerialPortCoordinator : IAsyncDisposable
4545

4646
private TaskCompletionSource<bool>? _frameDeliveryResultTaskSource;
4747

48+
// Tracks whether a CAN was received during the current frame delivery attempt.
49+
// Per spec 3.4.3, on CAN the chip drops our frame but has its own data frame to send.
50+
// We defer signaling delivery failure until the chip's data frame is processed, giving
51+
// the chip retransmission priority. The 1600ms ACK timeout acts as a fallback.
52+
private bool _pendingCanDeliveryFailure;
53+
4854
public ZWaveSerialPortCoordinator(
4955
ILogger logger,
5056
string portName,
@@ -137,7 +143,6 @@ private async Task ReadAsync()
137143
{
138144
case FrameType.ACK:
139145
case FrameType.NAK:
140-
case FrameType.CAN:
141146
{
142147
_logger.LogSerialApiFrameReceived(frame);
143148

@@ -153,6 +158,27 @@ private async Task ReadAsync()
153158

154159
break;
155160
}
161+
case FrameType.CAN:
162+
{
163+
_logger.LogSerialApiFrameReceived(frame);
164+
165+
if (_frameDeliveryResultTaskSource != null)
166+
{
167+
// Spec 3.4.3 (Figure 3.9): CAN means the chip detected a collision
168+
// and dropped our frame. The chip has priority to retransmit its own
169+
// data frame. Defer signaling delivery failure until we process and
170+
// ACK the chip's data frame, then the host can retransmit.
171+
_pendingCanDeliveryFailure = true;
172+
_logger.LogSerialApiCanDuringFrameDelivery();
173+
}
174+
else
175+
{
176+
// We received a frame delivery notification unexpectedly. Just ignore.
177+
_logger.LogSerialApiUnexpectedFrame(frame);
178+
}
179+
180+
break;
181+
}
156182
case FrameType.Data:
157183
{
158184
DataFrame dataFrame = frame.ToDataFrame();
@@ -165,6 +191,15 @@ private async Task ReadAsync()
165191
SendFrame(Frame.ACK);
166192

167193
await _dataFrameReceiveChannelWriter.WriteAsync(dataFrame, cancellationToken);
194+
195+
// Spec 3.4.3 (Figure 3.9): After a CAN, the chip retransmits its
196+
// data frame. Now that we've processed and ACKed it, signal delivery
197+
// failure so the host can retransmit with backoff.
198+
if (_pendingCanDeliveryFailure && _frameDeliveryResultTaskSource != null)
199+
{
200+
_frameDeliveryResultTaskSource.SetResult(false);
201+
_pendingCanDeliveryFailure = false;
202+
}
168203
}
169204
else
170205
{
@@ -248,6 +283,7 @@ private async Task WriteAsync()
248283
await Task.Delay(waitTimeMillis, cancellationToken);
249284
}
250285

286+
_pendingCanDeliveryFailure = false;
251287
_frameDeliveryResultTaskSource = new TaskCompletionSource<bool>();
252288

253289
// While writing frames, lock to ensure no frames are read and processed.
@@ -295,7 +331,7 @@ private async Task WriteAsync()
295331
break;
296332
}
297333

298-
// In the case of a NAK or CAN, retransmit our data frame.
334+
// In the case of a NAK or timeout, retransmit our data frame.
299335
_logger.LogSerialApiFrameTransmissionRetry(transmissionAttempt + 1);
300336
}
301337

0 commit comments

Comments
 (0)