Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
d19020f
Added publish to PyPI
PaulFirs Dec 20, 2025
443dd2e
Remove old publish workflow and add new test workflow for building Py…
PaulFirs Dec 21, 2025
36a887d
Rename workflow from "Upload Python Package" to "Test Builds"
PaulFirs Dec 21, 2025
943f1e3
Refactor workflow triggers to streamline event handling
PaulFirs Dec 21, 2025
7bc0041
Fix indentation in workflow triggers for consistency
PaulFirs Dec 21, 2025
da9ba78
Rename job from "release-build" to "install-dependencies" and fix act…
PaulFirs Dec 21, 2025
3f3c316
Add step to run bmlab-scan in test workflow
PaulFirs Dec 21, 2025
b34eea2
Rename step to "Test bmlab-scan" for clarity in workflow
PaulFirs Dec 21, 2025
805454f
Update bmlab-scan step to include log level for detailed output
PaulFirs Dec 21, 2025
28c00f4
Update logging level to DEBUG for JLinkProgrammer and bmlab-scan step
PaulFirs Dec 21, 2025
fa0b373
Rename job to "test-build" and add lsusb command for device detection
PaulFirs Dec 21, 2025
acd48b7
Add print statements for target detection in JLink programmer
PaulFirs Dec 21, 2025
65ea6e4
Log
PaulFirs Dec 21, 2025
05f6429
Test
PaulFirs Dec 21, 2025
5ab5be6
test
PaulFirs Dec 21, 2025
6c58552
class
PaulFirs Dec 21, 2025
9df6eb0
tmp
PaulFirs Dec 21, 2025
2cd3267
tmp
PaulFirs Dec 21, 2025
91a8689
tmp
PaulFirs Dec 21, 2025
bc7104e
tmp
PaulFirs Dec 21, 2025
a12809d
tmp
PaulFirs Dec 21, 2025
86a1f1b
tmp
PaulFirs Dec 21, 2025
be10c33
tmp
PaulFirs Dec 21, 2025
77a5892
tmp
PaulFirs Dec 21, 2025
14841d1
tmp
PaulFirs Dec 21, 2025
63e8f43
tmp
PaulFirs Dec 21, 2025
9836de2
tmp
PaulFirs Dec 21, 2025
af565c0
tmp
PaulFirs Dec 21, 2025
efd1421
tmp
PaulFirs Dec 21, 2025
26d14b6
tmp
PaulFirs Dec 21, 2025
9335445
tmp
PaulFirs Dec 21, 2025
7fabfa9
tmp
PaulFirs Dec 21, 2025
a289bed
tmp
PaulFirs Dec 21, 2025
b51b6b4
Refactor CI workflow to check for three JLink STM32F103RE devices
PaulFirs Dec 22, 2025
db56c88
Enhance CI workflow with comprehensive tests for JLink STM32F103RE de…
PaulFirs Dec 23, 2025
48611a1
Refactor Test 1 to simplify serial number validation for STM32F103RE …
PaulFirs Dec 23, 2025
b8d0c57
Enhance test workflow by capturing JLinkRemoteServer logs and ensurin…
PaulFirs Dec 23, 2025
213a042
Enhance test workflow to dynamically retrieve and utilize JLink seria…
PaulFirs Dec 23, 2025
efe062d
Add logging for selected JLink serial number in network scan test
PaulFirs Dec 23, 2025
43d1c59
Fix regex pattern for serial number extraction in Test 1
PaulFirs Dec 23, 2025
eb3bd80
Refactor serial number extraction in Test 1 for improved accuracy
PaulFirs Dec 23, 2025
a848b8f
Improve error handling in Test 3 by capturing and displaying scan out…
PaulFirs Dec 23, 2025
a46fb33
Update Test 3 to verify IP address in network scan instead of serial …
PaulFirs Dec 23, 2025
5ca6e19
Enhance Test 2 with failure message and add JLinkRemoteServer setup i…
PaulFirs Dec 23, 2025
035968f
Update Test 5 to check for JLink Remote Servers in scan output
PaulFirs Dec 23, 2025
e49d1a9
Remove sudo from network setup commands in test workflow
PaulFirs Dec 23, 2025
c232b80
Add sudo to network setup commands in test workflow
PaulFirs Dec 23, 2025
e2615d4
Add whoami command before setting up virtual network in test workflow
PaulFirs Dec 23, 2025
b4079d1
Add debug steps for sudoers and network command verification
PaulFirs Dec 23, 2025
9b12ba9
Refactor test workflow: remove debug sudoers step and adjust Test 6 s…
PaulFirs Dec 23, 2025
f3d70b2
Refactor network setup in test workflow: streamline IP address additi…
PaulFirs Dec 23, 2025
46ff1d9
Update JLinkRemoteServer command to allow external connections
PaulFirs Dec 23, 2025
93aebe9
Enhance test workflow: streamline JLinkRemoteServer setup with Docker…
PaulFirs Dec 25, 2025
8a0725e
Add step to build JLinkRemoteServer Docker image in test workflow
PaulFirs Dec 25, 2025
525a5ab
Update JLink packages to version 896 in Dockerfile
PaulFirs Dec 25, 2025
55268fe
Fix Dockerfile: update JLink package paths to include workflow directory
PaulFirs Dec 25, 2025
ef2e886
Enhance Dockerfile: include additional dependencies for JLink install…
PaulFirs Dec 25, 2025
6bfc292
Fix Dockerfile: correct JLink package names and remove unnecessary de…
PaulFirs Dec 25, 2025
e0eeb45
Update JLink package versions in Dockerfile to 896 for architecture-s…
PaulFirs Dec 25, 2025
a8fe911
Refactor Dockerfile to use JLink version as a build argument and upda…
PaulFirs Dec 25, 2025
680081d
Refactor Dockerfile: streamline JLink installation logic by removing …
PaulFirs Dec 25, 2025
7c47fc5
Comment out JLink package copy commands in Dockerfile
PaulFirs Dec 25, 2025
5a88662
Update JLink version to V896 in Dockerfile build argument
PaulFirs Dec 25, 2025
ce3a527
Update JLink installation URLs to version V794e in Dockerfile
PaulFirs Dec 25, 2025
efce289
Add libglib2.0-0 to required packages in Dockerfile
PaulFirs Dec 25, 2025
b94b2bd
Add libice6 to required packages in jlinkRemoteServerEmu.dockerfile
PaulFirs Dec 25, 2025
2b6dde9
Add udev to required packages in jlinkRemoteServerEmu.dockerfile
PaulFirs Dec 25, 2025
f32c31e
Add libsm6 to required packages in jlinkRemoteServerEmu.dockerfile
PaulFirs Dec 25, 2025
aab1ef5
Refactor jlinkRemoteServerEmu.dockerfile to streamline package instal…
PaulFirs Dec 25, 2025
9f63ef5
Update VNET_BASE in test.yml to 192.168.3
PaulFirs Dec 25, 2025
cb23f50
Fix Docker network setup in test workflow by ensuring removal of exis…
PaulFirs Dec 25, 2025
ef940d7
Refactor Docker container setup for JLinkRemoteServer to use explicit…
PaulFirs Dec 25, 2025
1b26041
Add logging for JLinkRemoteServer Docker containers in test workflow
PaulFirs Dec 25, 2025
5cff009
Add cleanup step for jlink-net Docker network and containers in test …
PaulFirs Dec 25, 2025
cb8fcad
Add network connectivity checks for JLinkRemoteServer IPs in test wor…
PaulFirs Dec 25, 2025
80003a5
Add device mapping for USB in JLinkRemoteServer Docker container setup
PaulFirs Dec 25, 2025
eaeb6b9
Enhance JLinkRemoteServer Docker setup with improved network configur…
PaulFirs Jan 5, 2026
9f22929
Add comprehensive cleanup steps for JLinkRemoteServer and Docker cont…
PaulFirs Jan 5, 2026
cd8823c
Refine JLinkRemoteServer process termination command to ensure accura…
PaulFirs Jan 5, 2026
039a3fe
Increase sleep duration for container startup and improve connectivit…
PaulFirs Jan 5, 2026
ccda8b7
Enhance test workflow with container readiness wait and result valida…
PaulFirs Jan 5, 2026
8d7f188
Increase container startup wait time and enhance network scan result …
PaulFirs Jan 5, 2026
5ae9d61
Refactor test workflow for improved container management and cleanup …
PaulFirs Jan 5, 2026
04f233c
Enhance port accessibility validation with retry logic in tests
PaulFirs Jan 5, 2026
e20a069
Enhance Docker test setup with serial number logging and validation f…
PaulFirs Jan 5, 2026
4df8c31
Enhance test workflow with serial number validation and container log…
PaulFirs Jan 5, 2026
ea3408e
Enhance scan result validation with output delimiters and improved de…
PaulFirs Jan 5, 2026
667cae8
Enhance Test 1 to extract and display STM32F103RE serial numbers with…
PaulFirs Jan 5, 2026
82253d8
Enhance device count validation in Test 6 and Test 7 with total devic…
PaulFirs Jan 5, 2026
e9991ad
Enhance Docker test setup for STM32F103RE with improved cleanup, vali…
PaulFirs Jan 5, 2026
a804a91
Fix network scanning and cleanup CI
PaulFirs Jan 6, 2026
8fd10da
Fix Docker network creation: remove existing network first
PaulFirs Jan 6, 2026
23ddc3d
Refactor Docker network setup in CI: remove redundant network removal…
PaulFirs Jan 6, 2026
bb9ff2b
Fix validation logic in Test 6 and 7
PaulFirs Jan 6, 2026
92ef4b7
Comment out device count checks in Test 6 and Test 7 for network scan…
PaulFirs Jan 6, 2026
b52582a
Refactor CI workflow: streamline Docker network cleanup and improve t…
PaulFirs Jan 6, 2026
60e41a4
Simplify validation: remove redundant device_count check
PaulFirs Jan 6, 2026
bb81db3
Add sleep before Test 7 to ensure stability in network scan
PaulFirs Jan 6, 2026
8f9ff5f
Add delay after disconnect to ensure device release
PaulFirs Jan 6, 2026
3c4fb62
Increase delay after device disconnect to ensure full release before …
PaulFirs Jan 6, 2026
32e0f7c
Update CHANGELOG for v0.1.5
PaulFirs Jan 6, 2026
96bfc54
Merge: combine CI improvements with RTT command rename
PaulFirs Jan 6, 2026
cf234ad
Remove obsolete JLink Linux binary workflow files
PaulFirs Jan 6, 2026
59540f2
Remove JLinkRemoteServer log file
PaulFirs Jan 6, 2026
14e9285
Update changelog to clarify network scanning performance changes
PaulFirs Jan 6, 2026
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
57 changes: 57 additions & 0 deletions .github/workflows/jlinkRemoteServerEmu.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
##
# Usage example:
# Build:
# docker build -t my-jlink-image -f jlinkRemoteServerEmu.dockerfile .
# Run (with USB access and custom network):
# docker network create --subnet=192.168.100.0/24 jlink-net
# docker run -d --rm --name jlink1 --network jlink-net --ip 192.168.100.10 \
# --device=/dev/bus/usb:/dev/bus/usb \
# my-jlink-image \
# -select usb=SERIAL1 -device STM32F103RE -endian little -speed 4000 -if swd
# Replace SERIAL1 with your J-Link serial number.
#
FROM ubuntu:22.04

## Install required packages and JLink dependencies
RUN apt-get update && \
apt-get install -y wget libusb-1.0-0 udev && \
rm -rf /var/lib/apt/lists/*


# Copy both JLink packages into the container (they must be present in build context)
# COPY .github/workflows/JLink_Linux_${JLINK_VERSION}_x86_64.deb /tmp/JLink_Linux_${JLINK_VERSION}_x86_64.deb
# COPY .github/workflows/JLink_Linux_${JLINK_VERSION}_arm64.deb /tmp/JLink_Linux_${JLINK_VERSION}_arm64.deb

# Workaround: replace udevadm with a stub to avoid postinst errors in Docker
RUN if [ -f /bin/udevadm ]; then mv /bin/udevadm /bin/udevadm.real; fi && \
echo '#!/bin/bash' > /bin/udevadm && \
echo 'exit 0' >> /bin/udevadm && \
chmod +x /bin/udevadm

# Install the appropriate JLink package depending on architecture (force install, then fix deps)
WORKDIR /tmp
RUN ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then \
wget --post-data "accept_license_agreement=accepted" \
https://www.segger.com/downloads/jlink/JLink_Linux_V794e_x86_64.deb \
-O JLink.deb && \
dpkg --force-depends -i JLink.deb && \
rm JLink.deb; \
elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then \
wget --post-data "accept_license_agreement=accepted" \
https://www.segger.com/downloads/jlink/JLink_Linux_V794e_arm64.deb \
-O JLink.deb && \
dpkg --force-depends -i JLink.deb && \
rm JLink.deb; \
fi

# Restore real udevadm after J-Link installation
RUN if [ -f /bin/udevadm.real ]; then rm /bin/udevadm && mv /bin/udevadm.real /bin/udevadm; fi

# Add user (optional)
RUN useradd -ms /bin/bash jlink
USER jlink

WORKDIR /home/jlink

ENTRYPOINT ["/opt/SEGGER/JLink/JLinkRemoteServer"]
174 changes: 174 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
name: Test Builds

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

env:
VNET_BASE: 192.168.3
VNET_MASK: 24
jobs:
test-build:
runs-on: self-hosted

steps:
- name: Cleanup before start
if: always()
run: |
pkill -9 -f JLinkRemoteServer || true
docker rm -f jlink100 jlink101 jlink102 2>/dev/null || true
docker network rm jlink-net 2>/dev/null || true
sleep 2

- uses: actions/checkout@v4

- name: Build release distributions
run: |
python3 -m venv .venv
. .venv/bin/activate
pip install -e ".[dev]"

- name: Test 1 — bmlab-scan (no arguments)
run: |
. .venv/bin/activate
bmlab-scan | tee scan1.txt
count=$(grep -c "Target: *STM32F103RE" scan1.txt)
awk '/JLink Programmer/ {serial=""} /Serial:/ {serial=$2} /Target: *STM32F103RE/ && serial {print serial}' scan1.txt | sort | uniq > serials.txt

if [ "$count" -ne 3 ]; then
echo "Test 1 failed: expected 3 STM32F103RE, found $count"
cat scan1.txt
exit 1
fi

- name: Test 2 — bmlab-scan -p jlink
run: |
. .venv/bin/activate
bmlab-scan -p jlink | tee scan2.txt
diff scan1.txt scan2.txt

- name: Test 3 — Start JLinkRemoteServer and scan via network
run: |
. .venv/bin/activate
serial=$(head -n1 serials.txt)
nohup JLinkRemoteServer -select usb=$serial -device STM32F103RE -endian little -speed 4000 -if swd > /dev/null 2>&1 &
sleep 3
bmlab-scan --network 127.0.0.1/32 | tee scan3.txt
grep -q "Target: *STM32F103RE" scan3.txt
grep -q "IP: *127.0.0.1" scan3.txt
pkill -9 -f JLinkRemoteServer || true
sleep 1

- name: Test 4 — bmlab-scan --network 127.0.0.1/32 -p jlink
run: |
. .venv/bin/activate
serial=$(head -n1 serials.txt)
nohup JLinkRemoteServer -select usb=$serial -device STM32F103RE -endian little -speed 4000 -if swd > /dev/null 2>&1 &
sleep 3
bmlab-scan --network 127.0.0.1/32 -p jlink | tee scan4.txt
diff scan3.txt scan4.txt
pkill -9 -f JLinkRemoteServer || true
sleep 1

- name: Test 5 — bmlab-scan --network 127.0.0.2/32 (negative)
run: |
. .venv/bin/activate
! bmlab-scan --network 127.0.0.2/32 | tee scan5.txt | grep -q "STM32F103RE"
grep -qi "No JLink Remote Servers found on the network." scan5.txt

- name: Build JLinkRemoteServer Docker image
run: |
docker build -t my-jlink-image -f .github/workflows/jlinkRemoteServerEmu.dockerfile .

- name: Cleanup jlink-net Docker network
run: |
docker rm -f jlink100 jlink101 jlink102 2>/dev/null || true
docker network rm jlink-net 2>/dev/null || true

- name: Setup JLinkRemoteServer Docker containers
run: |
docker network rm jlink-net 2>/dev/null || true
docker network create --driver bridge --subnet=$VNET_BASE.0/$VNET_MASK --gateway=$VNET_BASE.1 jlink-net

serial1=$(sed -n '1p' serials.txt)
serial2=$(sed -n '2p' serials.txt)
serial3=$(sed -n '3p' serials.txt)

docker run -d --rm \
--device=/dev/bus/usb:/dev/bus/usb \
--name jlink100 \
--network jlink-net \
--ip $VNET_BASE.100 \
my-jlink-image \
-select usb=$serial1 -device STM32F103RE -endian little -speed 4000 -if swd
sleep 3

docker run -d --rm \
--device=/dev/bus/usb:/dev/bus/usb \
--name jlink101 \
--network jlink-net \
--ip $VNET_BASE.101 \
my-jlink-image \
-select usb=$serial2 -device STM32F103RE -endian little -speed 4000 -if swd
sleep 3

docker run -d --rm \
--device=/dev/bus/usb:/dev/bus/usb \
--name jlink102 \
--network jlink-net \
--ip $VNET_BASE.102 \
my-jlink-image \
-select usb=$serial3 -device STM32F103RE -endian little -speed 4000 -if swd
sleep 3

- name: Test 6 — Multiple RemoteServers network scan
run: |
. .venv/bin/activate
bmlab-scan --network $VNET_BASE.0/$VNET_MASK | tee scan6.txt

f103_count=$(grep -c "Target:.*STM32F103RE" scan6.txt || echo 0)

if [ "$f103_count" -ne 3 ]; then
echo "Expected 3 STM32F103RE devices, found $f103_count"
cat scan6.txt
exit 1
fi

for ip in 100 101 102; do
if ! grep -q "IP:.*$VNET_BASE\.$ip" scan6.txt; then
echo "No device found at $VNET_BASE.$ip"
cat scan6.txt
exit 1
fi
done

- name: Test 7 — Network scan with IP range filter
run: |
. .venv/bin/activate
bmlab-scan --network $VNET_BASE.0/$VNET_MASK --start-ip 100 --end-ip 150 | tee scan7.txt

f103_count=$(grep -c "Target:.*STM32F103RE" scan7.txt || echo 0)

if [ "$f103_count" -ne 3 ]; then
echo "Expected 3 STM32F103RE devices, found $f103_count"
cat scan7.txt
exit 1
fi

for ip in 100 101 102; do
if ! grep -q "IP:.*$VNET_BASE\.$ip" scan7.txt; then
echo "No device found at $VNET_BASE.$ip"
cat scan7.txt
exit 1
fi
done

- name: Cleanup after Docker tests
if: always()
run: |
docker rm -f jlink100 jlink101 jlink102 || true
docker network rm jlink-net || true
pkill -9 -f JLinkRemoteServer || true
sleep 2
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.7] - 2026-01-06

### Fixed
- **Network scanning now correctly detects target MCU for all devices**:
- Added thread lock (`threading.Lock`) to serialize JLink connections during parallel scanning
- Prevents state conflicts in pylink library when multiple threads connect simultaneously
- Previously parallel scans could incorrectly detect STM32F765ZG instead of actual STM32F103RE
- Added 500ms delay after disconnect to ensure device is fully released
- **CI workflow improvements**:
- Removed excessive debug output from test steps
- Simplified Docker container setup and cleanup
- Fixed validation logic: removed redundant device counting, only validate target MCU and IPs
- Added network cleanup before creation to prevent "pool overlaps" errors
- Strict validation restored: exactly 3 STM32F103RE devices required at specified IPs

### Changed
- Network scanning: target detection is now serialized with lock protection
- Port checks remain parallel for speed
- Target MCU detection runs sequentially to prevent pylink state conflicts

## [0.1.6] - 2026-01-04

### Changed
Expand Down
25 changes: 14 additions & 11 deletions src/bmlab_toolkit/jlink_programmer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
from .programmer import Programmer, DBGMCU_IDCODE_ADDRESSES, DEVICE_ID_MAP, DEFAULT_MCU_MAP

# Configure default logging level for JLinkProgrammer
logging.basicConfig(level=logging.WARNING, format='%(levelname)s - %(message)s')
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')

# Suppress pylink logger to avoid communication timeout errors during disconnect
pylink_logger = logging.getLogger('pylink')
pylink_logger.setLevel(logging.WARNING)
pylink_logger.setLevel(logging.INFO)


class JLinkProgrammer(Programmer):
"""JLink programmer implementation."""

def __init__(self, serial: Optional[int] = None, ip_addr: Optional[str] = None, log_level: int = logging.WARNING):
def __init__(self, serial: Optional[int] = None, ip_addr: Optional[str] = None, log_level: int = logging.DEBUG):
"""
Initialize JLink programmer.

Expand Down Expand Up @@ -356,20 +356,23 @@ def scan() -> List[Dict[str, Any]]:

# Try to detect target MCU
try:
temp_jlink = pylink.JLink()
temp_jlink.open(serial_no=emu.SerialNumber)
temp_jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)
print(f"serial found {emu.SerialNumber}")
# temp_jlink = pylink.JLink()
# jlink.open(serial_no=emu.SerialNumber)
# jlink.set_tif(pylink.enums.JLinkInterfaces.SWD)

# Create temporary programmer instance to use detect_target
temp_programmer = JLinkProgrammer.__new__(JLinkProgrammer)
temp_programmer._jlink = temp_jlink
temp_programmer.logger = logging.getLogger(__name__)
temp_programmer = JLinkProgrammer(serial=emu.SerialNumber)
detected = temp_programmer.connect_target()
# temp_programmer._jlink = jlink
# temp_programmer.logger = logging.getLogger(__name__)

detected = temp_programmer.detect_target()
# detected = temp_programmer.detect_target()
if detected:
print(f"Detected target for JLink S/N {emu.SerialNumber}: {detected}")
device_info['target'] = detected

temp_jlink.close()
# jlink.close()
except Exception:
# If detection fails, just skip it
pass
Expand Down
50 changes: 32 additions & 18 deletions src/bmlab_toolkit/scan_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@
import argparse
import logging
import ipaddress
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
from .constants import SUPPORTED_PROGRAMMERS, DEFAULT_PROGRAMMER, PROGRAMMER_JLINK
from .jlink_programmer import JLinkProgrammer
import socket
import time

# Global lock to serialize JLink connections (prevents state conflicts)
_jlink_connection_lock = threading.Lock()


def scan_network_ip(ip_str, log_level):
"""Scan a single IP for JLink Remote Server."""
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Expand All @@ -32,32 +37,40 @@ def scan_network_ip(ip_str, log_level):
return None

# Try to connect and detect MCU
# Use lock to prevent parallel connections from interfering with each other
prog = None
try:
prog = JLinkProgrammer(ip_addr=ip_str, log_level=log_level)
mcu = prog.connect_target()
if not mcu:
# Connection failed
prog._jlink.close()
print(f"{ip_str}: No target detected")
return None

# Successfully connected - get device info
device_info = {
'ip': ip_str,
'type': 'jlink-remote',
'status': 'Connected',
'target': mcu if mcu else 'Unknown'
}

return device_info
with _jlink_connection_lock:
prog = JLinkProgrammer(ip_addr=ip_str, log_level=log_level)
mcu = prog.connect_target()
if not mcu:
# Connection failed
prog._jlink.close()
print(f"{ip_str}: No target detected")
return None

# Successfully connected - get device info
device_info = {
'ip': ip_str,
'type': 'jlink-remote',
'status': 'Connected',
'target': mcu if mcu else 'Unknown'
}

# Disconnect immediately after getting info
prog.disconnect_target()
# Delay to ensure device is fully released before next scan
time.sleep(0.5)

return device_info
except Exception as e:
# Connection or detection failed
return None
finally:
# Always close the connection
try:
prog.disconnect_target()
if prog:
prog.disconnect_target()
except:
pass

Expand Down Expand Up @@ -198,6 +211,7 @@ def main():

# USB scan mode
else:
print(f"Scanning for USB JLink programmers...\n")
devices = JLinkProgrammer.scan()

if not devices:
Expand Down