From 15d1c6813a59fff28728b31350738671b239a649 Mon Sep 17 00:00:00 2001 From: "Earle F. Philhower, III" Date: Fri, 14 Mar 2025 10:08:37 -0700 Subject: [PATCH] Redo UF2 discovery for Windows compatibility (#2853) Windows Python doesn't seem to kill the worker Thread properly when the IDE is exited, leading to a) multiple Python3 instances on a PC after many uses and b) errors updating the core when it tries to re-install Python3 while still having the older version's EXE loaded and in use. When an exception happens on the input() call under Windows, it seems like it can still leave a thread running. Add one more flag, caught in a global exception handler. Works around https://github.com/arduino/arduino-cli/issues/2867 --- tools/pluggable_discovery.py | 140 ++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 66 deletions(-) diff --git a/tools/pluggable_discovery.py b/tools/pluggable_discovery.py index 51dec16..f68500e 100755 --- a/tools/pluggable_discovery.py +++ b/tools/pluggable_discovery.py @@ -5,89 +5,96 @@ import sys import time import threading - toolspath = os.path.dirname(os.path.realpath(__file__)) try: - sys.path.insert(0, os.path.join(toolspath, ".")) # Add pyserial dir to search path + sys.path.insert(0, os.path.join(toolspath, ".")) # Add uf2conv dir to search path import uf2conv # If this fails, we can't continue and will bomb below except ImportError: sys.stderr.write("uf2conv not found next to this tool.\n") sys.exit(1) +scannerStop = threading.Event() +dropDead = False -scannerGo = False +class ScannerDarkly(threading.Thread): + + loopTime = 0.0 # Set to 0 for 1st pass to get immediate response for arduino-cli, then bumped to 2.0 for ongoing checks + + # https://stackoverflow.com/questions/12435211/threading-timer-repeat-function-every-n-seconds + def __init__(self, event): + threading.Thread.__init__(self) + self.stopped = event + + def run(self): + global dropDead + boards = False; + while not self.stopped.wait(self.loopTime): + if self.stopped.is_set() or dropDead: + return + self.loopTime = 2.0 + l = uf2conv.get_drives() + if (len(l) > 0) and not boards: + boards = True + print ("""{ + "eventType": "add", + "port": { + "address": "UF2_Board", + "label": "UF2 Board", + "protocol": "uf2conv", + "protocolLabel": "UF2 Devices", + "properties": { + "mac": "ffffffffffff", + "pid" : "0x2e8a", + "vid" : "0x000a" + } + } + }""", flush=True) + elif (len(l) == 0) and boards: + boards = False + print("""{ + "eventType": "remove", + "port": { + "address": "UF2_Board", + "protocol": "uf2conv" + } + }""", flush = True) -def scanner(): - global scannerGo - scannerGo = True - boards = False - while scannerGo: - l = uf2conv.get_drives() - if (len(l) > 0) and scannerGo and not boards: - boards = True - print ("""{ - "eventType": "add", - "port": { - "address": "UF2_Board", - "label": "UF2 Board", - "protocol": "uf2conv", - "protocolLabel": "UF2 Devices", - "properties": { - "mac": "ffffffffffff", - "pid" : "0x2e8a", - "vid" : "0x000a" - } - } -}""", flush=True) - elif (len(l) == 0) and scannerGo and boards: - boards = False - print("""{ - "eventType": "remove", - "port": { - "address": "UF2_Board", - "protocol": "uf2conv" - } -}""", flush = True) - n = time.time() + 2 - while scannerGo and (time.time() < n): - time.sleep(.1) - scannerGo = True def main(): - global scannerGo - while True: - cmdline = input() - cmd = cmdline.split()[0] - if cmd == "HELLO": - print(""" { + global scannerStop + global dropDead + try: + while True: + cmdline = input() + cmd = cmdline.split()[0] + if cmd == "HELLO": + print(""" { "eventType": "hello", "message": "OK", "protocolVersion": 1 }""", flush = True) - elif cmd == "START": - print("""{ + elif cmd == "START": + print("""{ "eventType": "start", "message": "OK" }""", flush = True); - elif cmd == "STOP": - scannerGo = False - while not scannerGo: - time.sleep(.1) - print("""{ + elif cmd == "STOP": + scannerStop.set() + print("""{ "eventType": "stop", "message": "OK" }""", flush = True) - elif cmd == "QUIT": - scannerGo = False - print("""{ + elif cmd == "QUIT": + scannerStop.set() + print("""{ "eventType": "quit", "message": "OK" }""", flush = True) - return - elif cmd == "LIST": - l = uf2conv.get_drives() - if len(l) > 0: - print ("""{ + return + elif cmd == "LIST": + l = uf2conv.get_drives() + if len(l) > 0: + print ("""{ "eventType": "list", "ports": [ { @@ -103,18 +110,19 @@ def main(): } ] }""", flush=True) - else: - print ("""{ + else: + print ("""{ "eventType": "list", "ports": [ ] }""", flush=True) - elif cmd == "START_SYNC": - print("""{ + elif cmd == "START_SYNC": + print("""{ "eventType": "start_sync", "message": "OK" }""", flush = True) - scannerGo = True - threading.Thread(target = scanner).start() - time.sleep(.5) + thread = ScannerDarkly(scannerStop) + thread.start() + except: + dropDead = True main()