@@ -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