-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Async equipment discovery with responsive UI and connection fixes #98
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The equipment discovery endpoint was defined as GET on the server but called with POST by the client, causing 405 Method Not Allowed errors. Since discovery is an action that triggers hardware scanning, POST is more semantically correct for REST principles. Changes: - Updated server endpoint from @router.get to @router.post - Updated all test files to use POST instead of GET - Updated API documentation to reflect POST method - Updated curl examples in getting started guide Fixes USB equipment discovery for the GUI client.
Added ability to configure discovery settings from the client GUI, making it easy to enable serial port scanning for equipment like BK Precision power supplies that connect via USB-to-Serial adapters. Changes: - Added GET/POST /api/discovery/settings endpoints to read/update scan types - Added client methods get_discovery_settings() and update_discovery_settings() - Added DiscoverySettingsDialog to equipment panel with checkboxes for: * TCPIP/Network devices * USB devices * Serial devices (COM/ttyUSB) - enables serial port scanning * GPIB devices - Modified discover_equipment() to show settings dialog before scanning - Settings are loaded from server and applied before each discovery scan - Improved discovery feedback showing found devices or helpful message if none This allows users to easily enable serial scanning without SSH/config edits.
The discovery manager and VISA scanner store configuration objects at initialization time. When the API endpoint updates settings, it was only updating the global settings object, not the discovery manager's config. This caused serial/GPIB scan toggles in the UI to have no effect because the VISA scanner was still filtering based on the old config values. Now the /api/discovery/settings endpoint updates: - Global settings object (for persistence across restarts) - Discovery manager's config object (for immediate effect) - VISA scanner's config object (for scan filtering) This allows the UI toggle for serial scanning to work without requiring a server restart, fixing the issue where the BK Precision power supply connected via USB-to-Serial wasn't being discovered even when serial scanning was enabled in the UI.
…VISA The equipment manager's discover_devices() method was bypassing the discovery manager and calling list_resources() directly, which ignored all discovery configuration settings including scan_serial. Now it uses the discovery manager's scan() method which: - Respects scan type configuration (TCPIP, USB, Serial, GPIB) - Uses the real-time config updates from the API - Returns discovered devices with proper filtering This fixes the issue where enabling serial scanning in the UI had no effect because the equipment discovery endpoint was using a different code path. Falls back to direct VISA scanning if discovery manager is unavailable.
After discovering equipment, users can now immediately connect to devices via a new dialog that: - Shows all discovered resources in a selectable list - Allows choosing equipment type (power_supply, oscilloscope, etc.) - Allows selecting or entering the model name - Defaults to power_supply/BK Precision 9206B for convenience - Calls the connect API and shows success/failure messages This eliminates the confusing workflow where discovered devices didn't appear in the equipment list (they need to be connected first). Now the workflow is: 1. Scan for Equipment (with serial enabled) 2. Dialog shows discovered devices 3. Select device and click Connect 4. Device appears in equipment list as connected
The BK Precision 1902B is a DC power supply (60V/15A, 900W), not an electronic load. - Added BK1902B class to bk_power_supply.py with correct specs - Updated equipment manager to import from bk_power_supply module - Moved model mapping from electronic loads to power supplies section This resolves the connection error when trying to connect to the discovered device.
BK1902B was incorrectly defined in both bk_electronic_load.py and bk_power_supply.py, causing a name conflict. Removed the incorrect electronic load version since BK1902B is actually a DC power supply (60V/15A, 900W), not an electronic load.
Import BK1902B from bk_power_supply module instead of bk_electronic_load since it's a power supply, not an electronic load.
The resource_manager was being closed during shutdown but not set to None, causing it to remain in an unusable state. This fix: 1. Sets resource_manager to None after closing during shutdown 2. Adds lazy initialization in connect_device to re-create if needed This resolves connection failures when resource_manager hasn't been initialized or was closed during a previous shutdown.
Fixed chicken-and-egg problem where connect() calls _query("*IDN?") to verify
connection, but _query() checks self.connected which is only set to True AFTER
the *IDN? query succeeds.
Now _query(), _write(), and _query_binary() only check if self.instrument exists,
allowing them to be used during the connection verification process.
The lazy initialization was checking if resource_manager is None, but a closed ResourceManager object still exists (not None) and causes "Invalid session handle" errors. Now we: 1. Test if the resource_manager is valid before use 2. Recreate it if it's in a closed/invalid state This ensures we always have a valid ResourceManager when connecting devices.
BK Precision devices require specific serial settings to communicate properly: - Baud rate: 9600 - Data bits: 8 - Parity: None - Stop bits: 1 - Flow control: None - Termination: \n Added connect() override in BKPowerSupplyBase to configure these settings before querying the device, resolving timeout errors with BK1902B over serial.
BK Precision devices typically use CR+LF (\r\n) line termination instead of just LF (\n). Updated both read and write termination to use \r\n to match the expected protocol format.
Some BK Precision models may not respond to *IDN? immediately or at all. Updated connect() and get_info() to gracefully handle *IDN? failures: - Connection proceeds even if *IDN? times out - Uses default manufacturer/model values from __init__ if query fails - Logs warnings instead of failing completely This allows connection to BK1902B and similar models that may have limited SCPI command support.
The validation was checking self.resource_manager.list_resources (attribute) instead of calling list_resources() as a method. This meant it never detected closed resource managers. Now properly calls the method to trigger an exception on closed/invalid resource managers.
The get_status() method was setting connected=False when *IDN? timed out, causing the UI to show the device as disconnected even though it was successfully connected. Now get_status() tries to get firmware info from *IDN? but gracefully continues without it if the query fails, relying on self.connected for the actual connection status.
This resolves the "Python3 has stopped responding" warnings that occurred during equipment discovery. Changes: - Add async discovery methods to LabLinkClient using aiohttp - discover_equipment_async() - connect_device_async() - update_discovery_settings_async() - Update equipment_panel.py to use asyncio.create_task() for non-blocking discovery - Update connect_dialog.py to use async connection - Add user feedback message informing that discovery is running in background The UI now remains responsive during the 10-30 second discovery process. Discovery and connection operations no longer block the Qt event loop.
This resolves the issue where serial scanning settings had to be re-enabled every time discovery was run. Changes: - Create server/config/persistence.py for runtime settings persistence - save_discovery_settings(): Saves settings to config/runtime_settings.json - load_discovery_settings(): Loads persisted settings from JSON file - apply_persisted_settings(): Applies persisted settings to Settings object - Update discovery API endpoint to persist settings changes - Calls save_discovery_settings() after updating settings - Settings now survive server restarts - Load persisted settings on server startup in main.py lifespan handler - Loads after config validation but before equipment initialization - Overrides .env defaults with runtime_settings.json values Discovery settings (scan_serial, scan_gpib, etc.) now persist across server restarts. Users no longer need to re-enable serial scanning each time.
Changed from ./config to ./data directory to avoid permission issues and ensure the settings file is stored in a writable location that already exists (data directory is created on server startup).
Changed from 'server.config.persistence' to 'config.persistence' to match the server's module structure. Added separate ImportError handling to better diagnose import issues.
This reverts commit fee1e70.
Wait 500ms after successful device connection before refreshing the equipment list to ensure the server has fully registered the equipment.
Reverted connect_dialog.py back to yesterday's synchronous approach. Connection is fast (~1s) so it doesn't need async. Only discovery is slow (10-30s) and needs to be async to prevent UI freezing. This aligns with yesterday's working code while keeping the async discovery improvement for the UI freezing issue.
aiohttp is stricter than requests library and doesn't allow boolean values in query parameters. Convert True/False to 'true'/'false' strings before passing to aiohttp.
Server API returns 'id' but client expected 'equipment_id', causing connected equipment to not appear in the list. Fixed field mappings: - 'id' → 'equipment_id' - 'resource_string' → 'resource_name' - Use 'nickname' or 'model' for 'name' - Default 'connection_status' to 'connected' for listed equipment - Map 'serial_number' to 'idn' if needed This fixes the issue where successful connections didn't show equipment in the main window.
Added missing endpoint to retrieve current readings from connected equipment. This endpoint: - Calls equipment's get_readings() method - Converts response to JSON dict - Returns 404 if equipment not found - Returns 501 if equipment doesn't support readings Fixes the 'refresh readings' button that was getting 404 errors.
…ontext The issue was calling connect_dialog.exec() (a blocking modal dialog) from within an async coroutine, which froze the Qt event loop. Solution: - Use QTimer.singleShot(0, ...) to schedule dialogs on main Qt thread - Move connect dialog logic to separate _show_connect_dialog() method - This ensures dialogs run on main thread, not in async context - Removed the blocking progress message before async discovery This fixes UI freezing during discovery and connection phases.
Added debug logging to show: - Requested equipment_id - All available equipment IDs in the manager - Enhanced error message with available IDs This will help diagnose why equipment shows as connected in UI but returns 404 when requesting readings.
- Added Equipment Discovery & Connection section to README - Documented async discovery implementation in ROADMAP - Highlighted UI responsiveness improvements and bug fixes
- Wrapped aiohttp import in try/except block - Added runtime checks in async methods with informative error messages - Prevents ImportError during test collection when aiohttp not installed - Async methods will raise clear ImportError if aiohttp missing at runtime
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Summary
This PR implements asynchronous equipment discovery to eliminate UI freezing and fixes critical connection workflow issues that prevented discovered equipment from appearing in the client interface.
Problem Statement
The equipment discovery workflow had two critical UX issues:
UI Freezing: The client became completely unresponsive during equipment discovery (10-30 seconds), showing "Python3 has stopped responding" warnings. VISA enumeration blocked the Qt event loop with synchronous HTTP calls.
Connection Failures: Equipment connected successfully on the server but didn't appear in the client UI due to field name mismatches between the server API (
id,resource_string) and client models (equipment_id,resource_name).Missing Endpoints: No
/readingsendpoint existed to retrieve current equipment readings, causing 404 errors when users clicked "Refresh Readings".Changes
🚀 Async Discovery Implementation
Client (
client/api/client.py)aiohttp:discover_equipment_async()- Non-blocking discoveryconnect_device_async()- Non-blocking connectionupdate_discovery_settings_async()- Non-blocking settings updatesTrue→"true")Client (
client/ui/equipment_panel.py)discover_equipment()to useasyncio.create_task()QTimer.singleShot()to schedule modal dialogs on Qt main threadImpact: UI remains fully responsive during discovery. No more "Python3 has stopped responding" errors.
🔧 Equipment Connection Fixes
Client (
client/models/equipment.py)from_api_dict():id→ Clientequipment_idresource_string→ Clientresource_namenickname→ Clientname(with fallback tomodel)connection_statusto"connected"for listed equipmentserial_numbertoidnwhen neededServer (
server/api/equipment.py)GET /equipment/{equipment_id}/readingsendpointImpact: Discovered equipment now appears correctly in the client UI with proper connection status.
🔌 BK Precision 1902B Support
Server (
server/equipment/bk_power_supply.py,server/equipment/manager.py)*IDN?query (device doesn't support standard SCPI)Testing
Manual Testing Completed:
/dev/ttyUSB0)Known Issues:
Breaking Changes
None. All changes are additive or fix existing bugs.
Documentation
README.mdwith Equipment Discovery & Connection featuresROADMAP.mdwith November 2025 UX improvementsCommits
Migration Guide
Server Update: