_empty_response_frame_queue() drains the event queue, so stale response frames are never cleared
Repo: https://github.com/freewili/freewili-python
Version: freewili 0.0.51 (pyserial 3.5, pyfwfinder 0.5.0)
Environment: macOS 26.5.2 (arm64), Python 3.14.7
Hardware: FreeWili 2, firmware reports FW2 v07
Summary
FreeWiliSerial._empty_response_frame_queue() drains rf_event_queue instead of
rf_queue. It is byte-for-byte identical to _empty_event_response_frame_queue()
directly above it, so _empty_all() clears the event queue twice and never
clears the response-frame queue at all.
Since _wait_for_response_frame() reads from rf_queue, any stale frame sitting
there survives every _empty_all() call and is returned as the answer to the
next command. One leftover frame shifts every subsequent read by one.
Location
freewili/fw_serial.py, lines 276-292:
def _empty_event_response_frame_queue(self) -> None:
"""Empty the response frameevent queue. ..."""
while not self.serial_port.rf_event_queue.empty():
self.serial_port.rf_event_queue.get()
def _empty_response_frame_queue(self) -> None:
"""Empty the response frameevent queue. ..."""
while not self.serial_port.rf_event_queue.empty(): # <-- should be rf_queue
self.serial_port.rf_event_queue.get() # <-- should be rf_queue
For contrast, _wait_for_response_frame() (line 217) reads the queue that never
gets cleared:
return self.serial_port.rf_queue.get_nowait()
Impact
The failure is silent and misleading rather than loud. With one stale frame
queued, send_file() reports the previous command's status as its own result:
Requesting file transfer of test.txt (61 bytes) to test.txt...
Firmware response: Ok <- actually the previous command's reply
Sent 61/61 bytes of test.txt. 100.00%
Sent test.txt in 1.00 seconds: Send File Now <- actually the transfer's *start* ack
Ok('Sent test.txt in 1.00 seconds: Send File Now')
That returns Ok(...) while the real completion frame (success 61 bytes) is
still unread in the queue. The next call then consumes that, so the desync is
persistent once it starts. Depending on how many frames are queued it can also
surface as a spurious timeout:
Err('Failed to read response frame in 6.0 seconds: starting file transfer of test.txt')
even though the transfer is fine at the wire level.
With the queue drained correctly, the same transfer reports its own frames:
Firmware response: Send File Now
Sent 61/61 bytes of test.txt. 100.00%
Sent test.txt in 1.03 seconds: success 61 bytes
Reproduce
Any sequence that leaves an unread frame in rf_queue before a command will do.
The frames below were captured directly off the CDC port to show the device is
behaving correctly and the desync is host-side:
[? ... 4 FW2 v07 1] <- left unread in rf_queue
[h\x\f ... 5 Send File Now 1] <- transfer start ack
[h\x\f ... 6 success 61 bytes 1] <- transfer complete
_empty_all() before the transfer does not remove the first frame, so
_wait_for_response_frame() returns FW2 v07 as the "start ack" and everything
after is off by one.
Suggested fix
def _empty_response_frame_queue(self) -> None:
"""Empty the response frame queue."""
while not self.serial_port.rf_queue.empty():
self.serial_port.rf_queue.get()
Two small things worth doing alongside it, since they are what let the bug hide:
- The docstring on both methods reads "Empty the response frameevent queue",
which describes neither method clearly and makes the duplication easy to miss
on review.
_wait_for_response_frame() returns whatever frame is at the head of the
queue without checking that it corresponds to the command just sent. Each
frame carries its command path in rf_type_data (?, h\x\f, ...), so
matching on it would make a stale frame harmless instead of silently
corrupting the next result.
_empty_response_frame_queue()drains the event queue, so stale response frames are never clearedRepo: https://github.com/freewili/freewili-python
Version: freewili 0.0.51 (pyserial 3.5, pyfwfinder 0.5.0)
Environment: macOS 26.5.2 (arm64), Python 3.14.7
Hardware: FreeWili 2, firmware reports
FW2 v07Summary
FreeWiliSerial._empty_response_frame_queue()drainsrf_event_queueinstead ofrf_queue. It is byte-for-byte identical to_empty_event_response_frame_queue()directly above it, so
_empty_all()clears the event queue twice and neverclears the response-frame queue at all.
Since
_wait_for_response_frame()reads fromrf_queue, any stale frame sittingthere survives every
_empty_all()call and is returned as the answer to thenext command. One leftover frame shifts every subsequent read by one.
Location
freewili/fw_serial.py, lines 276-292:For contrast,
_wait_for_response_frame()(line 217) reads the queue that nevergets cleared:
Impact
The failure is silent and misleading rather than loud. With one stale frame
queued,
send_file()reports the previous command's status as its own result:That returns
Ok(...)while the real completion frame (success 61 bytes) isstill unread in the queue. The next call then consumes that, so the desync is
persistent once it starts. Depending on how many frames are queued it can also
surface as a spurious timeout:
even though the transfer is fine at the wire level.
With the queue drained correctly, the same transfer reports its own frames:
Reproduce
Any sequence that leaves an unread frame in
rf_queuebefore a command will do.The frames below were captured directly off the CDC port to show the device is
behaving correctly and the desync is host-side:
_empty_all()before the transfer does not remove the first frame, so_wait_for_response_frame()returnsFW2 v07as the "start ack" and everythingafter is off by one.
Suggested fix
Two small things worth doing alongside it, since they are what let the bug hide:
which describes neither method clearly and makes the duplication easy to miss
on review.
_wait_for_response_frame()returns whatever frame is at the head of thequeue without checking that it corresponds to the command just sent. Each
frame carries its command path in
rf_type_data(?,h\x\f, ...), somatching on it would make a stale frame harmless instead of silently
corrupting the next result.