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
9 changes: 9 additions & 0 deletions build-pi-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,7 @@ echo "════════════════════════
echo ""
echo "Useful Commands:"
echo " lablink-status - Show this status"
echo " lablink-version - Show version and git commit info"
echo " lablink-start - Start LabLink"
echo " lablink-stop - Stop LabLink"
echo " lablink-restart - Restart LabLink"
Expand Down Expand Up @@ -998,6 +999,12 @@ lablink-status
UPDATESCRIPT
chmod +x /usr/local/bin/lablink-update

# Install lablink-version command
if [ -f /opt/lablink/lablink-version.sh ]; then
cp /opt/lablink/lablink-version.sh /usr/local/bin/lablink-version
chmod +x /usr/local/bin/lablink-version
fi

echo "[LabLink] First boot setup complete!"
echo "[LabLink] ════════════════════════════════════════════════════════"
echo "[LabLink] "
Expand All @@ -1007,6 +1014,7 @@ echo "[LabLink] Access LabLink at: http://$(hostname).local"
echo "[LabLink] "
echo "[LabLink] Useful commands:"
echo "[LabLink] lablink-status - Check LabLink status"
echo "[LabLink] lablink-version - Show version and git commit info"
echo "[LabLink] lablink-logs - View logs"
echo "[LabLink] lablink-update - Update to latest code"
echo "[LabLink] "
Expand Down Expand Up @@ -1101,6 +1109,7 @@ fi
echo ""
echo "Quick Commands:"
echo " lablink-status - Show detailed status"
echo " lablink-version - Show version and git commit info"
echo " lablink-logs - View logs"
echo " lablink-restart - Restart services"
echo " lablink-update - Update to latest code"
Expand Down
26 changes: 26 additions & 0 deletions client/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1401,6 +1401,32 @@ def run_pi_diagnostics(self) -> Dict[str, Any]:
response.raise_for_status()
return response.json()

def run_usb_diagnostics(self, resource_string: str) -> Dict[str, Any]:
"""Run USB device diagnostics to troubleshoot connection issues.

Analyzes why a USB device's serial number may not be readable and
provides recommendations for resolving the issue.

Args:
resource_string: VISA resource string of the device to diagnose

Returns:
Dictionary containing:
- resource_string: The analyzed resource string
- has_serial: Whether a serial number is present
- serial_readable: Whether the serial number can be read
- usb_info: USB vendor/product/serial information
- issues: List of detected issues
- recommendations: List of recommended fixes
"""
response = self._session.post(
f"{self.base_url}/equipment/diagnostics/usb",
json={"resource_string": resource_string},
timeout=10
)
response.raise_for_status()
return response.json()

# ==================== State Management API ====================

def capture_state(
Expand Down
138 changes: 138 additions & 0 deletions client/ui/diagnostics_panel.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ def _setup_ui(self):
pi_diagnostics_btn.setToolTip("Run comprehensive diagnostic script on the Raspberry Pi server")
button_layout.addWidget(pi_diagnostics_btn)

usb_diagnostics_btn = QPushButton("USB Device Diagnostics")
usb_diagnostics_btn.clicked.connect(self.run_usb_diagnostics)
usb_diagnostics_btn.setToolTip("Diagnose USB connection issues and unreadable serial numbers")
button_layout.addWidget(usb_diagnostics_btn)

layout.addLayout(button_layout)

def set_client(self, client: LabLinkClient):
Expand Down Expand Up @@ -339,3 +344,136 @@ def save_output():
f"Failed to run Pi diagnostics:\n\n{str(e)}\n\n"
"Make sure the diagnose-pi.sh script is installed on the server."
)

def run_usb_diagnostics(self):
"""Run USB device diagnostics to troubleshoot connection issues."""
if not self.client:
QMessageBox.warning(
self, "Not Connected", "Please connect to a server first"
)
return

# Ask user for resource string
from PyQt6.QtWidgets import QInputDialog, QComboBox, QDialog, QVBoxLayout, QLabel

# Get list of discovered devices to help user choose
try:
devices_response = self.client.discover_devices()
devices = devices_response.get("devices", [])

# Create dialog with device selection
dialog = QDialog(self)
dialog.setWindowTitle("Select Device for USB Diagnostics")
dialog.resize(600, 200)

layout = QVBoxLayout(dialog)

layout.addWidget(QLabel("Select a device to diagnose:"))

device_combo = QComboBox()
for device in devices:
resource_name = device.get("resource_name", "")
if resource_name.startswith("USB"):
# Show resource name and any available device info
label = resource_name
if device.get("manufacturer"):
label += f" ({device['manufacturer']})"
device_combo.addItem(label, resource_name)

if device_combo.count() == 0:
device_combo.addItem("No USB devices found", "")

layout.addWidget(device_combo)

# Add manual entry option
layout.addWidget(QLabel("\nOr enter a resource string manually:"))
from PyQt6.QtWidgets import QLineEdit
manual_input = QLineEdit()
manual_input.setPlaceholderText("e.g., USB0::11975::37376::800886011797210043::0::INSTR")
layout.addWidget(manual_input)

# Buttons
from PyQt6.QtWidgets import QDialogButtonBox
button_box = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel
)
button_box.accepted.connect(dialog.accept)
button_box.rejected.connect(dialog.reject)
layout.addWidget(button_box)

if dialog.exec() == QDialog.DialogCode.Accepted:
# Use manual input if provided, otherwise use selected device
resource_string = manual_input.text().strip()
if not resource_string:
resource_string = device_combo.currentData()

if not resource_string:
QMessageBox.warning(self, "No Device", "Please select or enter a device")
return

# Run diagnostics
try:
diagnostics = self.client.run_usb_diagnostics(resource_string)

# Format and display results
message = f"USB Device Diagnostics\n"
message += f"{'=' * 50}\n\n"
message += f"Resource String: {diagnostics.get('resource_string', 'N/A')}\n\n"

usb_info = diagnostics.get('usb_info')
if usb_info:
message += f"USB Information:\n"
message += f" Vendor ID: {usb_info.get('vendor_id', 'N/A')}\n"
message += f" Product ID: {usb_info.get('product_id', 'N/A')}\n"
message += f" Serial Number: {usb_info.get('serial_number', 'N/A')}\n\n"

message += f"Serial Readable: {'Yes' if diagnostics.get('serial_readable') else 'No'}\n\n"

issues = diagnostics.get('issues', [])
if issues:
message += f"Issues Detected:\n"
for issue in issues:
message += f" • {issue}\n"
message += "\n"

recommendations = diagnostics.get('recommendations', [])
if recommendations:
message += f"Recommendations:\n"
for rec in recommendations:
message += f" • {rec}\n"

# Show results in a scrollable dialog
from PyQt6.QtWidgets import QTextEdit
result_dialog = QDialog(self)
result_dialog.setWindowTitle("USB Diagnostics Results")
result_dialog.resize(700, 500)

result_layout = QVBoxLayout(result_dialog)

text_edit = QTextEdit()
text_edit.setReadOnly(True)
text_edit.setPlainText(message)
text_edit.setFontFamily("Monospace")
result_layout.addWidget(text_edit)

close_btn = QPushButton("Close")
close_btn.clicked.connect(result_dialog.accept)
result_layout.addWidget(close_btn)

result_dialog.exec()

except Exception as e:
logger.error(f"Error running USB diagnostics: {e}")
QMessageBox.critical(
self,
"Error",
f"Failed to run USB diagnostics:\n\n{str(e)}"
)

except Exception as e:
logger.error(f"Error preparing USB diagnostics: {e}")
QMessageBox.critical(
self,
"Error",
f"Failed to prepare USB diagnostics:\n\n{str(e)}"
)
1 change: 1 addition & 0 deletions client/ui/ssh_deploy_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,7 @@ def _install_convenience_commands(self, ssh, server_path, username):
echo ""
echo "Status & Monitoring:"
echo " lablink-status - Show container status"
echo " lablink-version - Show version and git commit info"
echo " lablink-logs - View all logs (follow mode)"
echo " lablink-logs-server - View server logs only"
echo " lablink-logs-web - View web dashboard logs only"
Expand Down
14 changes: 14 additions & 0 deletions install-server.sh
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ setup_environment() {
else
print_step ".env file already exists"
fi

# Make version check script executable
if [ -f "lablink-version.sh" ]; then
chmod +x lablink-version.sh

# Create symlink in /usr/local/bin for easy access
if [ -w /usr/local/bin ] || [ -n "$SUDO" ]; then
$SUDO ln -sf "$LABLINK_DIR/lablink-version.sh" /usr/local/bin/lablink-version
print_step "Installed 'lablink-version' command"
fi
fi
}

deploy_with_docker() {
Expand Down Expand Up @@ -313,6 +324,9 @@ print_success() {
echo " Restart: sudo systemctl restart lablink.service"
fi

echo ""
echo "Utility Commands:"
echo " Check version: lablink-version"
echo ""
echo "For help and documentation: https://docs.lablink.io"
echo ""
Expand Down
116 changes: 116 additions & 0 deletions lablink-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/bin/bash
# LabLink Version Check Script
# Run this on the Raspberry Pi to check what version/commit is running

set -e

echo "======================================"
echo " LabLink Version Information"
echo "======================================"
echo ""

# Get the LabLink directory - try multiple locations
LABLINK_DIR=""

# Method 1: Check common deployment locations
for dir in "/opt/lablink" "$HOME/lablink" "$HOME/LabLink"; do
if [ -d "$dir" ] && [ -f "$dir/VERSION" ]; then
LABLINK_DIR="$dir"
break
fi
done

# Method 2: If not found, try to find it relative to script location (for symlinked command)
if [ -z "$LABLINK_DIR" ]; then
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/VERSION" ]; then
LABLINK_DIR="$SCRIPT_DIR"
fi
fi

# Method 3: Check current directory as last resort
if [ -z "$LABLINK_DIR" ] && [ -f "$(pwd)/VERSION" ]; then
LABLINK_DIR="$(pwd)"
fi

# Exit if LabLink directory not found
if [ -z "$LABLINK_DIR" ]; then
echo "Error: LabLink directory not found"
echo ""
echo "Searched locations:"
echo " - /opt/lablink (Pi image deployment)"
echo " - $HOME/lablink (SSH deployment)"
echo " - Script directory"
echo " - Current directory"
echo ""
echo "Please ensure you are running this on a system with LabLink installed."
exit 1
fi

cd "$LABLINK_DIR"
echo "LabLink Directory: $LABLINK_DIR"
echo ""

# Get version from VERSION file
if [ -f "VERSION" ]; then
VERSION=$(cat VERSION)
echo "Version: $VERSION"
else
echo "Version: Unknown (VERSION file not found)"
fi

echo ""

# Get git information
if [ -d ".git" ]; then
echo "Git Information:"
echo " Branch: $(git rev-parse --abbrev-ref HEAD)"
echo " Commit: $(git rev-parse --short HEAD)"
echo " Date: $(git log -1 --format=%ci)"
echo ""
echo "Latest Commit:"
echo " $(git log -1 --oneline)"
echo ""

# Check if there are uncommitted changes
if ! git diff-index --quiet HEAD --; then
echo "⚠️ WARNING: Uncommitted changes detected!"
git status --short
else
echo "✓ Working directory is clean"
fi
else
echo "Git Information: Not available (not a git repository)"
fi

echo ""

# Check if server is running
echo "Server Status:"
if command -v docker &> /dev/null; then
if docker ps --format '{{.Names}}' | grep -q 'lablink-server'; then
echo " ✓ Docker container 'lablink-server' is running"

# Try to get version from API
if command -v curl &> /dev/null; then
echo ""
echo "Running Server Version (from API):"
RESPONSE=$(curl -s http://localhost:8000/api/system/version 2>/dev/null || echo "")
if [ -n "$RESPONSE" ]; then
# Extract version and git info using basic parsing
echo "$RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE"
else
echo " Unable to query API (server may not be responding)"
fi
fi
else
echo " ✗ Docker container 'lablink-server' is not running"
fi
elif systemctl is-active --quiet lablink-server 2>/dev/null; then
echo " ✓ LabLink server service is running"
else
echo " ✗ LabLink server is not running"
fi

echo ""
echo "======================================"
10 changes: 9 additions & 1 deletion server/api/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,15 @@ def _run_diagnostic_on_host(script_path: str) -> dict:

except docker.errors.ContainerError as e:
# Container exited with non-zero code
output = e.stderr.decode('utf-8', errors='replace') if e.stderr else str(e)
# Handle both bytes and string stderr
if e.stderr:
if isinstance(e.stderr, bytes):
output = e.stderr.decode('utf-8', errors='replace')
else:
output = str(e.stderr)
else:
output = str(e)

return {
"success": False,
"output": output,
Expand Down
Loading
Loading