Skip to content

Commit 7b2ae6e

Browse files
author
Alex J Lennon
committed
Add filtering, sorting, limiting, and force refresh to list_devices tool
- Add force_refresh parameter to bypass cache and rescan all devices - Add ssh_status_filter to filter by SSH status (ok, error, refused, timeout, unknown) - Add power_state_filter to filter Tasmota devices by power state (on, off) - Add sort_by parameter (ip, friendly_name, status, last_seen) with sort_order (asc/desc) - Add limit parameter to cap number of results - Update tool definition, handler, and documentation - Add helper function _get_ssh_status() for consistent SSH status determination
1 parent ed5bff4 commit 7b2ae6e

17 files changed

Lines changed: 815 additions & 362 deletions

=3.5.0

Whitespace-only changes.

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,18 @@ Data flow: AI → MCP Server → Tools → Lab Framework → Hardware
146146

147147
## Tools
148148

149-
- **Device**: `list_devices` (with filtering, summary stats, power state), `test_device`, `ssh_to_device`, `verify_device_identity`, `verify_device_by_ip`, `update_device_ip`, `update_device_friendly_name`
149+
- **Device**: `list_devices` (with filtering, sorting, limiting, summary stats, power state) - Shows device inventory with Power Switch column: Tasmota devices display their own power state (🟢 ON/🔴 OFF), other devices show which power switch controls them. Supports filtering by type, status, SSH status, power state, and search. Supports sorting by IP, friendly_name, status, or last_seen. Supports limiting results. Use force_refresh to bypass cache. `test_device`, `ssh_to_device`, `verify_device_identity`, `verify_device_by_ip`, `update_device_ip`, `update_device_friendly_name`
150150
- **VPN**: `vpn_status`, `connect_vpn`, `disconnect_vpn`
151151
- **Power**: `start_power_monitoring` (DMM or Tasmota), `get_power_logs`, `analyze_power_logs`, `monitor_low_power`, `compare_power_profiles` - Power monitoring via DMM (SCPI) or Tasmota energy monitoring
152-
- **Tasmota**: `tasmota_control`, `list_tasmota_devices`, `power_cycle_device` - Power cycle devices via Tasmota switches. Tasmota devices show power state (ON/OFF) and consumption (Watts) in device list
152+
- **Tasmota**: `tasmota_control`, `list_tasmota_devices`, `power_cycle_device` - Power cycle devices via Tasmota switches. Tasmota devices show power state (🟢 ON/🔴 OFF) in the Power Switch column and consumption (Watts) in the Type column of the device list
153153
- **Test Equipment**: `list_test_equipment`, `query_test_equipment` - Auto-detect and query test equipment (DMM, oscilloscopes) via SCPI protocol
154154
- **OTA/Containers**: `check_ota_status`, `trigger_ota_update`, `list_containers`, `deploy_container`, `get_system_status`, `get_firmware_version`, `get_foundries_registration_status`, `get_secure_boot_status`, `get_device_identity`
155155
- **Process Management**: `kill_stale_processes` - Kill duplicate processes that might interfere
156156
- **Remote Access**: `create_ssh_tunnel`, `list_ssh_tunnels`, `close_ssh_tunnel`, `access_serial_port`, `list_serial_devices` - SSH tunnels and serial port access
157157
- **Change Tracking**: `get_change_history`, `revert_changes` - Track and revert changes for security/debugging
158158
- **Batch/Regression**: `batch_operation`, `regression_test`, `get_device_groups`
159159
- **Network Mapping**: `create_network_map` - Visual map of network with device type, uptime, friendly names, power switches
160-
- **Device Verification**: `verify_device_identity`, `verify_device_by_ip`, `update_device_ip` - Verify device identity in DHCP environments. Device list shows SSH status, last seen timestamps, and power switch relationships
160+
- **Device Verification**: `verify_device_identity`, `verify_device_by_ip`, `update_device_ip` - Verify device identity in DHCP environments. Device list shows SSH status, last seen timestamps, and power switch information (Tasmota devices show their own state, other devices show controlling switch)
161161
- **Help**: `help` - Get usage documentation and examples
162162

163163
## Resources

lab_testing/config.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import os
99
from pathlib import Path
10-
from typing import Optional
10+
from typing import List, Optional
1111

1212
# Default paths - can be overridden via environment variables
1313
DEFAULT_LAB_TESTING_ROOT = Path("/data_drive/esl/ai-lab-testing")
@@ -106,24 +106,25 @@ def get_logs_dir() -> Path:
106106
def get_target_network() -> str:
107107
"""
108108
Get the target network for lab testing operations.
109-
109+
110110
Priority:
111111
1. TARGET_NETWORK environment variable
112112
2. Value from lab_devices.json config file (if exists)
113113
3. Default: 192.168.2.0/24
114-
114+
115115
Returns:
116116
Network CIDR string (e.g., "192.168.2.0/24")
117117
"""
118118
# Check environment variable first
119119
env_network = os.getenv("TARGET_NETWORK")
120120
if env_network:
121121
return env_network
122-
122+
123123
# Check config file
124124
try:
125125
if LAB_DEVICES_JSON.exists():
126126
import json
127+
127128
with open(LAB_DEVICES_JSON) as f:
128129
config = json.load(f)
129130
infrastructure = config.get("lab_infrastructure", {})
@@ -134,27 +135,28 @@ def get_target_network() -> str:
134135
except Exception:
135136
# If config read fails, fall back to default
136137
pass
137-
138+
138139
# Default target network
139140
return DEFAULT_TARGET_NETWORK
140141

141142

142-
def get_lab_networks() -> list[str]:
143+
def get_lab_networks() -> List[str]:
143144
"""
144145
Get list of lab networks for scanning.
145-
146+
146147
Priority:
147148
1. Networks from lab_devices.json config file
148149
2. TARGET_NETWORK environment variable (as single-item list)
149150
3. Default: [target_network]
150-
151+
151152
Returns:
152153
List of network CIDR strings
153154
"""
154155
# Check config file first
155156
try:
156157
if LAB_DEVICES_JSON.exists():
157158
import json
159+
158160
with open(LAB_DEVICES_JSON) as f:
159161
config = json.load(f)
160162
infrastructure = config.get("lab_infrastructure", {})
@@ -164,7 +166,7 @@ def get_lab_networks() -> list[str]:
164166
return lab_networks
165167
except Exception:
166168
pass
167-
169+
168170
# Fall back to target network
169171
return [get_target_network()]
170172

lab_testing/resources/help.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def get_help_content() -> Dict[str, Any]:
2626
},
2727
"tools": {
2828
"device_management": {
29-
"list_devices": "List all devices with status, IPs, types, firmware, SSH status, last seen, and power switches. Returns brief summary (always visible) then full table. Supports filtering by device_type_filter, status_filter, search_query. Includes summary statistics (counts by type/status/SSH status). Shows Tasmota power state/consumption and test equipment detection.",
29+
"list_devices": "List all devices with status, IPs, types, firmware, SSH status, last seen, and power switches. Returns brief summary (always visible) then full table. Supports filtering by device_type_filter, status_filter, ssh_status_filter, power_state_filter, search_query. Supports sorting (sort_by: ip/friendly_name/status/last_seen, sort_order: asc/desc) and limiting results (limit). Includes summary statistics (counts by type/status/SSH status). Shows Tasmota power state/consumption and test equipment detection. Power Switch column: For Tasmota devices, shows their own power state (🟢 ON/🔴 OFF); for other devices, shows which power switch controls them (if configured). Use force_refresh=true to bypass cache and rescan all devices.",
3030
"test_device": "Test connectivity to a device (ping and SSH check). Best practice: Use before running operations on devices. In DHCP environments, use verify_device_identity to ensure correct device.",
3131
"ssh_to_device": "Execute SSH command on a device (requires device_id, command, optional username). Best practice: Test device connectivity first with test_device.",
3232
"verify_device_identity": "Verify device identity at given IP matches expected device (important for DHCP). Updates IP in config if verified and changed.",
@@ -92,8 +92,13 @@ def get_help_content() -> Dict[str, Any]:
9292
"1. Use 'vpn_status' to check VPN connection",
9393
"2. Use 'list_devices' to see available devices (shows brief summary first, then full table)",
9494
"3. Filter devices: 'list_devices(device_type_filter=\"tasmota_device\")' or 'list_devices(status_filter=\"online\")'",
95-
"4. Search devices: 'list_devices(search_query=\"192.168.2.18\")'",
96-
"5. Use 'test_device' to verify connectivity",
95+
"4. Filter by SSH status: 'list_devices(ssh_status_filter=\"error\")' to find devices with SSH issues",
96+
"5. Filter Tasmota by power: 'list_devices(power_state_filter=\"on\")' to find powered-on Tasmota devices",
97+
"6. Search devices: 'list_devices(search_query=\"192.168.2.18\")'",
98+
"7. Sort results: 'list_devices(sort_by=\"ip\", sort_order=\"asc\")' or 'list_devices(sort_by=\"last_seen\", sort_order=\"desc\")'",
99+
"8. Limit results: 'list_devices(limit=10)' to show only first 10 devices",
100+
"9. Force refresh: 'list_devices(force_refresh=true)' to bypass cache and rescan",
101+
"10. Use 'test_device' to verify connectivity",
97102
],
98103
"device_discovery": [
99104
"1. Devices are automatically discovered via network scanning",
@@ -156,11 +161,14 @@ def get_help_content() -> Dict[str, Any]:
156161
"For low power: set appropriate threshold_mw, monitor for sufficient duration",
157162
"For regression: organize devices by tags/groups, use batch operations",
158163
"Tag devices in config for easy rack management and grouping",
159-
"Use list_devices filters to quickly find specific devices (type, status, search)",
164+
"Use list_devices filters to quickly find specific devices (type, status, SSH status, power state, search)",
165+
"Use list_devices sorting (sort_by, sort_order) to organize results by IP, name, status, or last seen",
166+
"Use list_devices limit parameter to cap results for large networks",
167+
"Use list_devices force_refresh=true to bypass cache when you need fresh data",
160168
"Device list shows brief summary first - always visible without expanding",
161169
"In DHCP environments: verify device identity before operations, use update_device_ip if IP changed",
162170
"Device discovery is optimized with parallel SSH identification and caching",
163-
"Tasmota devices show power state and consumption in device list",
171+
"Tasmota devices show power state (🟢 ON/🔴 OFF) in the Power Switch column and consumption (Watts) in the Type column of the device list",
164172
"Test equipment (DMM) can be queried with SCPI commands via query_test_equipment",
165173
],
166174
"foundries_io_integration": {

lab_testing/server.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
# Development mode: Set up auto-reload
6363
try:
6464
from lab_testing.server.dev_reload import setup_auto_reload
65+
6566
setup_auto_reload()
6667
except ImportError:
6768
pass # dev_reload module not available
@@ -89,7 +90,9 @@ async def handle_list_tools() -> List[Tool]:
8990

9091

9192
@server.call_tool()
92-
async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> List[Union[TextContent, ImageContent]]:
93+
async def handle_call_tool(
94+
name: str, arguments: Dict[str, Any]
95+
) -> List[Union[TextContent, ImageContent]]:
9396
"""Handle tool execution requests"""
9497
request_id = str(uuid.uuid4())[:8]
9598
start_time = time.time()

lab_testing/server/dev_reload.py

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -38,38 +38,38 @@ def _should_reload_module(module_name: str) -> bool:
3838
module_file = _get_module_file(module_name)
3939
if not module_file or not module_file.exists():
4040
return False
41-
41+
4242
current_mtime = module_file.stat().st_mtime
4343
cached_mtime = _module_mtimes.get(module_name)
44-
44+
4545
if cached_mtime is None or current_mtime > cached_mtime:
4646
_module_mtimes[module_name] = current_mtime
4747
return cached_mtime is not None # Only reload if we've seen it before
48-
48+
4949
return False
5050

5151

5252
def reload_if_changed(module_name: str) -> bool:
5353
"""
5454
Reload a module if its file has changed.
55-
55+
5656
Args:
5757
module_name: Full module name (e.g., 'lab_testing.server.tool_handlers')
58-
58+
5959
Returns:
6060
True if module was reloaded, False otherwise
6161
"""
6262
if not _should_reload_module(module_name):
6363
return False
64-
64+
6565
try:
6666
if module_name in sys.modules:
6767
importlib.reload(sys.modules[module_name])
6868
return True
6969
except Exception as e:
7070
# Log but don't fail - reload errors shouldn't crash the server
7171
print(f"Warning: Failed to reload {module_name}: {e}", file=sys.stderr)
72-
72+
7373
return False
7474

7575

@@ -92,12 +92,12 @@ def reload_lab_testing_modules():
9292
"lab_testing.resources.health",
9393
"lab_testing.config",
9494
]
95-
95+
9696
reloaded = []
9797
for module_name in modules_to_check:
9898
if reload_if_changed(module_name):
9999
reloaded.append(module_name)
100-
100+
101101
return reloaded
102102

103103

@@ -110,16 +110,8 @@ def setup_auto_reload():
110110
"""Set up auto-reload for development mode"""
111111
if not is_dev_mode():
112112
return
113-
113+
114114
# Initialize mtimes for all modules
115115
reload_lab_testing_modules()
116-
117-
print("Development mode enabled: Auto-reload active", file=sys.stderr)
118-
119-
120-
121-
122-
123-
124-
125116

117+
print("Development mode enabled: Auto-reload active", file=sys.stderr)

lab_testing/server/tool_definitions.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def get_all_tools() -> List[Tool]:
1919
name="list_devices",
2020
description=(
2121
"List all devices on the target network with their status, firmware, and relationships. "
22-
"Supports filtering by type, status, and search queries. Includes summary statistics."
22+
"Supports filtering by type, status, SSH status, power state, and search queries. "
23+
"Includes summary statistics. Supports sorting and limiting results."
2324
),
2425
inputSchema={
2526
"type": "object",
@@ -41,6 +42,34 @@ def get_all_tools() -> List[Tool]:
4142
"description": "Include summary statistics (counts by type/status) in response (default: true)",
4243
"default": True,
4344
},
45+
"force_refresh": {
46+
"type": "boolean",
47+
"description": "Bypass cache and rescan all devices (default: false)",
48+
"default": False,
49+
},
50+
"ssh_status_filter": {
51+
"type": "string",
52+
"description": "Filter by SSH status (e.g., 'ok', 'error', 'refused', 'timeout', 'unknown')",
53+
},
54+
"power_state_filter": {
55+
"type": "string",
56+
"description": "Filter Tasmota devices by power state (e.g., 'on', 'off')",
57+
},
58+
"sort_by": {
59+
"type": "string",
60+
"description": "Sort results by field: 'ip', 'friendly_name', 'status', 'last_seen' (default: type then friendly_name)",
61+
},
62+
"sort_order": {
63+
"type": "string",
64+
"description": "Sort order: 'asc' or 'desc' (default: 'asc')",
65+
"enum": ["asc", "desc"],
66+
"default": "asc",
67+
},
68+
"limit": {
69+
"type": "integer",
70+
"description": "Maximum number of devices to return (default: no limit)",
71+
"minimum": 1,
72+
},
4473
},
4574
"required": [],
4675
},

0 commit comments

Comments
 (0)