Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions dronecan/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,23 @@ def periodic(self, period_seconds, callback):
:returns: EventHandle object. Call .remove() on it to cancel the event.
"""
priority = 0
callback_running = [False] # Use list to allow modification in nested function

def caller(scheduled_deadline):
# Event MUST be re-registered first in order to ensure that it can be cancelled from the callback
# Always reschedule first to maintain periodic timing
scheduled_deadline += period_seconds
event_holder[0] = self._scheduler.enterabs(scheduled_deadline, priority, caller, (scheduled_deadline,))
callback()

# Prevent callback overlap that can cause runaway scheduling
if callback_running[0]:
# Skip this execution if previous callback is still running
return

callback_running[0] = True
try:
callback()
finally:
callback_running[0] = False

first_deadline = self._scheduler.timefunc() + period_seconds
event_holder = [self._scheduler.enterabs(first_deadline, priority, caller, (first_deadline,))]
Expand Down Expand Up @@ -438,7 +449,9 @@ def execute_once():
while time.monotonic() < deadline:
execute_once()
else:
while True:
# Process available frames with a reasonable limit to prevent GUI blocking
max_frames_per_spin = 100 # Limit frames processed per spin(0) call
while count < max_frames_per_spin:
frame = self._can_driver.receive(0)
if frame:
self._recv_frame(frame)
Expand Down
14 changes: 13 additions & 1 deletion dronecan/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
import dronecan
import dronecan.dsdl as dsdl
import dronecan.dsdl.common as common
from logging import getLogger

logger = getLogger(__name__)


try:
Expand Down Expand Up @@ -893,9 +896,18 @@ def __init__(self):
def receive_frame(self, frame):
result = None
key = frame.transfer_key

# Clean up any stale transfers before processing new frames
self.remove_inactive_transfers(timeout=0.1) # Much shorter timeout

if key in self.active_transfers or frame.start_of_transfer:
# If the first frame was received, restart this transfer from scratch
if frame.start_of_transfer:
if key in self.active_transfers:
# Log when we're restarting an incomplete transfer (only for debugging)
# logger.debug('Restarting incomplete transfer for key %r (had %d frames)',
# key, len(self.active_transfers[key]))
pass
self.active_transfers[key] = []

self.active_transfers[key].append(frame)
Expand All @@ -911,7 +923,7 @@ def receive_frame(self, frame):

def remove_inactive_transfers(self, timeout=1.0):
t = time.monotonic()
transfer_keys = self.active_transfers.keys()
transfer_keys = list(self.active_transfers.keys()) # Create a copy to avoid iteration issues
for key in transfer_keys:
if t - self.active_transfer_timestamps[key] > timeout:
del self.active_transfers[key]
Expand Down