pynrfjprog works, but unable to disconnect from target or emu

I'm migrating a production jig driver application from using multiple calls to an nrfjprog binary to the pynrfjprog library instead. It's gone almost perfectly, but I have a problem I can't figure out: after I've done all the steps--erased, reflashed, and reset the target device--I can't clean up the connection by closing the connection to the target device, or to the J-Link (emu), or to the DLL API in Python. It just hangs forever if I try. The only thing that works instead is killing the process and restarting it.

I'm running a virtualenv (managed by pipenv) of Python 3.11.4 on a Macbook Pro (macOS Sonoma 14.1), with pynrfjprog v10.23.2 (the latest as of this post). I'm flashing a u-blox BMD-340 module built around an nRF52840 chip. I have no issues whatsoever flashing it with other means.

I originally thought the problem was related to some threading that I had in my code, but I pared it down to something as simple as possible, and it still happens. Here's the code that I'm testing with:

import logging

logging.basicConfig(format='%(asctime)s - %(name)s/%(levelname)s - %(message)s', level=logging.INFO)

logging.info("Loading nrfjprog integration module...")
from pynrfjprog import LowLevel

nrf_api = LowLevel.API("NRF52")

logging.info("Opening connection to nrfjprog API")
nrf_api.open()

logging.info("Opening connection to J-Link probe")
nrf_api.connect_to_emu_without_snr()

emu_snr = nrf_api.read_connected_emu_snr()
logging.info("Probe connection established, SNR " + str(emu_snr))

nrf_api.connect_to_device()
device_info = nrf_api.read_device_info()
logging.info("Connected to target device: " + str(device_info))

logging.info("Disconnecting from target device")
nrf_api.disconnect_from_device()

logging.info("Closing connection to J-Link probe")
nrf_api.disconnect_from_emu()

logging.info("Closing connection to nrfjprog API")
nrf_api.close()

logging.info("All nrfjprog connections closed")
exit(0)

When I run this, the output stops before the disconnection from the target device completes. This shows a limited stack trace for where it's stuck after I press Ctrl+C:

JeffMBP:minijig jrowberg$ pipenv run python ./minijig.py
2023-12-29 15:46:03,579 - root/INFO - Loading nrfjprog integration module...
2023-12-29 15:46:03,657 - root/INFO - Opening connection to nrfjprog API
2023-12-29 15:46:03,685 - root/INFO - Opening connection to J-Link probe
2023-12-29 15:46:04,060 - root/INFO - Probe connection established, SNR 820101513
2023-12-29 15:46:04,369 - root/INFO - Connected to target device: (<DeviceVersion.NRF52840_xxAA_REV2: 86523907>, <DeviceName.NRF52840: 86523904>, <DeviceMemory.AA: 1>, <DeviceRevision.REV2: 21>)
2023-12-29 15:46:04,369 - root/INFO - Disconnecting from target device
^CTraceback (most recent call last):
  File "/Users/jrowberg/minijig/./minijig.py", line 20, in <module>
    nrf_api.disconnect_from_device()
  File "/Users/jrowberg/.local/share/virtualenvs/minijig-F0vOpSJS/lib/python3.11/site-packages/pynrfjprog/LowLevel.py", line 719, in disconnect_from_device
    result = self._lib.NRFJPROG_disconnect_from_device_inst(self._handle)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
KeyboardInterrupt

If I comment out the call to nrf_api.disconnect_from_device(), then it hangs inside the disconnect_from_emu() call instead. If I comment that out, then it hangs inside the nrf_api object's close() call instead. If I comment all of the clean-up out entirely and let the app attempt to deal with it, like a neanderthal, then the final build-in exit() call never returns, and a Ctrl+C reveals it's still stuck inside self._lib.NRFJPROG_close_dll_inst(ctypes.byref(self._handle)).

How can I fix this? Needing to kill and restart the jig driver script after every device goes through it kinda defeats the purpose.

Related