iflow-mcp_wegitor-logic_analyzer_mcp 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,310 @@
1
+ from mcp import types
2
+ from mcp.server.fastmcp import FastMCP, Context
3
+ from typing import Optional, Dict, Any, List, Union
4
+ from saleae.automation import DeviceType
5
+ from saleae import Saleae
6
+ import os
7
+ import json
8
+ import time
9
+ import logging
10
+ import sys # added for argv inspection
11
+ from logic_analyzer_mcp.mcp_tools_experimental import setup_mcp_tools_experimental
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ # Use shared saleae manager for instance creation/caching
16
+ from logic_analyzer_mcp.saleae_manager import get_saleae
17
+
18
+ def setup_mcp_tools(mcp: FastMCP, controller=None, enable_logic2: Optional[bool] = None) -> None:
19
+ """Setup MCP tools for Saleae Logic control.
20
+
21
+ If enable_logic2 is None, detection falls back to environment LOGIC2 or CLI args (--logic2).
22
+ """
23
+ # Determine whether to enable Logic2 experimental tools
24
+ if enable_logic2 is None:
25
+ env_val = os.environ.get("LOGIC2")
26
+ if env_val and str(env_val).lower() in ("1", "true", "yes"):
27
+ use_logic2 = True
28
+ elif any(arg == "logic2" or arg.startswith("--logic2") for arg in sys.argv[1:]):
29
+ use_logic2 = True
30
+ else:
31
+ use_logic2 = False
32
+ else:
33
+ use_logic2 = bool(enable_logic2)
34
+
35
+ if use_logic2:
36
+ try:
37
+ setup_mcp_tools_experimental(mcp, controller)
38
+ logger.info("Logic2 experimental MCP tools enabled (setup_mcp_tools_experimental called).")
39
+ except Exception as e:
40
+ logger.warning(f"setup_mcp_tools_experimental not available or failed: {e}")
41
+ else:
42
+ logger.info("Logic2 experimental MCP tools not enabled.")
43
+
44
+ # Add python-saleae specific tools
45
+ @mcp.tool("saleae_connect")
46
+ def saleae_connect(ctx: Context) -> Dict[str, Any]:
47
+ """Connect to Saleae Logic software using python-saleae."""
48
+ try:
49
+ from controllers.saleae_controller import SaleaeController
50
+ controller = SaleaeController()
51
+ if controller.connect():
52
+ return {"status": "success", "message": "Connected to Saleae Logic"}
53
+ return {"status": "error", "message": "Failed to connect to Saleae Logic"}
54
+ except Exception as e:
55
+ return {"status": "error", "message": f"Error connecting to Saleae Logic: {str(e)}"}
56
+
57
+ @mcp.tool("saleae_configure")
58
+ def saleae_configure(ctx: Context,
59
+ digital_channels: List[int],
60
+ digital_sample_rate: int,
61
+ analog_channels: Optional[List[int]] = None,
62
+ analog_sample_rate: Optional[int] = None,
63
+ trigger_channel: Optional[int] = None,
64
+ trigger_type: Optional[str] = None) -> Dict[str, Any]:
65
+ """Configure Saleae Logic capture settings."""
66
+ try:
67
+ from controllers.saleae_controller import SaleaeController
68
+ controller = SaleaeController()
69
+ if controller.connect():
70
+ if controller.configure_capture(
71
+ digital_channels=digital_channels,
72
+ digital_sample_rate=digital_sample_rate,
73
+ analog_channels=analog_channels,
74
+ analog_sample_rate=analog_sample_rate,
75
+ trigger_channel=trigger_channel,
76
+ trigger_type=trigger_type
77
+ ):
78
+ return {"status": "success", "message": "Configured Saleae Logic capture"}
79
+ return {"status": "error", "message": "Failed to configure capture"}
80
+ return {"status": "error", "message": "Failed to connect to Saleae Logic"}
81
+ except Exception as e:
82
+ return {"status": "error", "message": f"Error configuring Saleae Logic: {str(e)}"}
83
+
84
+ @mcp.tool("saleae_capture")
85
+ def saleae_capture(ctx: Context,
86
+ duration_seconds: float,
87
+ output_file: str) -> Dict[str, Any]:
88
+ """Start a capture with Saleae Logic and save to file."""
89
+ try:
90
+ from controllers.saleae_controller import SaleaeController
91
+ controller = SaleaeController()
92
+ if controller.connect():
93
+ if controller.start_capture(duration_seconds):
94
+ # Wait for capture to complete
95
+ time.sleep(duration_seconds + 1) # Add 1 second buffer
96
+ if controller.save_capture(output_file):
97
+ return {"status": "success", "message": f"Capture saved to {output_file}"}
98
+ return {"status": "error", "message": "Failed to save capture"}
99
+ return {"status": "error", "message": "Failed to start capture"}
100
+ return {"status": "error", "message": "Failed to connect to Saleae Logic"}
101
+ except Exception as e:
102
+ return {"status": "error", "message": f"Error during capture: {str(e)}"}
103
+
104
+ @mcp.tool("saleae_export")
105
+ def saleae_export(ctx: Context,
106
+ input_file: str,
107
+ output_file: str,
108
+ format: str = 'csv',
109
+ digital_channels: Optional[List[int]] = None,
110
+ analog_channels: Optional[List[int]] = None,
111
+ time_span: Optional[List[float]] = None) -> Dict[str, Any]:
112
+ """Export capture data to specified format."""
113
+ try:
114
+ from controllers.saleae_controller import SaleaeController
115
+ controller = SaleaeController()
116
+ return controller.export_data(
117
+ input_file=input_file,
118
+ output_file=output_file,
119
+ format=format,
120
+ digital_channels=digital_channels,
121
+ analog_channels=analog_channels,
122
+ time_span=time_span
123
+ )
124
+ except Exception as e:
125
+ return {"status": "error", "message": f"Error during export: {str(e)}"}
126
+
127
+ @mcp.tool("saleae_device_info")
128
+ def saleae_device_info(ctx: Context) -> Dict[str, Any]:
129
+ """Get information about the connected Saleae Logic device."""
130
+ try:
131
+ from controllers.saleae_controller import SaleaeController
132
+ controller = SaleaeController()
133
+ if controller.connect():
134
+ info = controller.get_device_info()
135
+ if info:
136
+ return {"status": "success", "device_info": info}
137
+ return {"status": "error", "message": "Failed to get device info"}
138
+ return {"status": "error", "message": "Failed to connect to Saleae Logic"}
139
+ except Exception as e:
140
+ return {"status": "error", "message": f"Error getting device info: {str(e)}"}
141
+
142
+ # Parser-related tools
143
+ @mcp.tool("parse_capture_file")
144
+ def parse_capture_file(ctx: Context,
145
+ capture_file: Optional[str] = None,
146
+ data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
147
+ """
148
+ Initialize parser with a capture file or direct data and return basic information.
149
+
150
+ Args:
151
+ capture_file: Path to the capture file (optional)
152
+ data: Direct data dictionary with duration, digital_channels, and analog_channels (optional)
153
+ """
154
+ try:
155
+ if data is not None:
156
+ # Validate required fields
157
+ required_fields = ['duration', 'digital_channels', 'analog_channels']
158
+ if not all(field in data for field in required_fields):
159
+ return {"status": "error", "message": f"Missing required fields: {required_fields}"}
160
+ return {
161
+ "status": "success",
162
+ "duration": data['duration'],
163
+ "digital_channels": data['digital_channels'],
164
+ "analog_channels": data['analog_channels']
165
+ }
166
+ elif capture_file is not None:
167
+ if not os.path.exists(capture_file):
168
+ return {"status": "error", "message": f"Capture file not found: {capture_file}"}
169
+
170
+ # Get Saleae instance
171
+ saleae_instance = get_saleae()
172
+ if saleae_instance is None:
173
+ # Try to launch Saleae software
174
+ try:
175
+ # First check if Saleae is installed in common locations
176
+ common_paths = [
177
+ os.path.expandvars(r"%ProgramFiles%\\Saleae\\Logic\\Logic.exe"),
178
+ os.path.expandvars(r"%ProgramFiles(x86)%\\Saleae\\Logic\\Logic.exe"),
179
+ os.path.expanduser("~\\AppData\\Local\\Programs\\Saleae\\Logic\\Logic.exe")
180
+ ]
181
+
182
+ saleae_path = None
183
+ for path in common_paths:
184
+ if os.path.exists(path):
185
+ saleae_path = path
186
+ break
187
+
188
+ if saleae_path is None:
189
+ # Fall back to offline mode
190
+ logger.warning("Saleae Logic software not found. Falling back to offline mode.")
191
+ return {
192
+ "status": "success",
193
+ # "message": "Running in offline mode",
194
+ "file_info": {
195
+ "path": capture_file,
196
+ "format": "Saleae Logic (.sal)",
197
+ "size": os.path.getsize(capture_file),
198
+ "modified": os.path.getmtime(capture_file)
199
+ }
200
+ }
201
+
202
+ # Try to launch with the found path
203
+ saleae_instance = Saleae()
204
+ saleae_instance.launch()
205
+ # Wait a bit for the software to start
206
+ time.sleep(2)
207
+ except Exception as launch_error:
208
+ # Fall back to offline mode
209
+ logger.warning(f"Failed to launch Saleae software: {launch_error}. Falling back to offline mode.")
210
+ return {
211
+ "status": "success",
212
+ # "message": "Running in offline mode",
213
+ "file_info": {
214
+ "path": capture_file,
215
+ "format": "Saleae Logic (.sal)",
216
+ "size": os.path.getsize(capture_file),
217
+ "modified": os.path.getmtime(capture_file)
218
+ }
219
+ }
220
+
221
+ try:
222
+ # Load the capture file using Saleae API
223
+ capture = saleae_instance.load_capture(capture_file)
224
+
225
+ return {
226
+ "status": "success",
227
+ "file_info": {
228
+ "path": capture_file,
229
+ "format": "Saleae Logic (.sal)",
230
+ "duration": capture.duration,
231
+ "digital_channels": capture.digital_channels,
232
+ "analog_channels": capture.analog_channels,
233
+ "digital_sample_rate": capture.digital_sample_rate,
234
+ "analog_sample_rate": capture.analog_sample_rate
235
+ }
236
+ }
237
+ except Exception as e:
238
+ # Fall back to offline mode
239
+ logger.warning(f"Failed to parse capture file with Saleae API: {e}. Falling back to offline mode.")
240
+ return {
241
+ "status": "success",
242
+ # "message": "Running in offline mode",
243
+ "file_info": {
244
+ "path": capture_file,
245
+ "format": "Saleae Logic (.sal)",
246
+ "size": os.path.getsize(capture_file),
247
+ "modified": os.path.getmtime(capture_file)
248
+ }
249
+ }
250
+ else:
251
+ return {"status": "error", "message": "Either capture_file or data must be provided"}
252
+ except Exception as e:
253
+ return {"status": "error", "message": f"Failed to parse capture file: {str(e)}"}
254
+
255
+ @mcp.tool("get_sample_rate")
256
+ def get_sample_rate(ctx: Context,
257
+ capture_file: Optional[str] = None,
258
+ sample_rate: Optional[float] = None,
259
+ channel: int = 0) -> Dict[str, Any]:
260
+ """
261
+ Get the sample rate for a specific channel.
262
+
263
+ Args:
264
+ capture_file: Path to the capture file (optional)
265
+ sample_rate: Direct sample rate value (optional)
266
+ channel: Channel number
267
+ """
268
+ try:
269
+ if sample_rate is not None:
270
+ return {
271
+ "status": "success",
272
+ "sample_rate": sample_rate
273
+ }
274
+ elif capture_file is not None:
275
+ if not os.path.exists(capture_file):
276
+ raise FileNotFoundError(f"Capture file not found: {capture_file}")
277
+ saleae_instance = get_saleae()
278
+ if saleae_instance is None:
279
+ return {"status": "error", "message": "Saleae instance not available"}
280
+ rate = saleae_instance.get_sample_rate(capture_file, channel)
281
+ return {
282
+ "status": "success",
283
+ "sample_rate": rate
284
+ }
285
+ else:
286
+ return {"status": "error", "message": "Either capture_file or sample_rate must be provided"}
287
+ except Exception as e:
288
+ return {"status": "error", "message": f"Failed to get sample rate: {str(e)}"}
289
+
290
+
291
+ @mcp.tool("get_digital_data_mcp")
292
+ def get_digital_data_mcp(ctx: Context,
293
+ capture_file: str,
294
+ channel: int = 0,
295
+ start_time: Optional[float] = None,
296
+ end_time: Optional[float] = None,
297
+ max_samples: Optional[int] = None) -> Dict[str, Any]:
298
+ """Get digital data from a capture file."""
299
+ try:
300
+ from controllers.saleae_controller import SaleaeController
301
+ controller = SaleaeController()
302
+ return controller.get_digital_data(
303
+ capture_file=capture_file,
304
+ channel=channel,
305
+ start_time=start_time,
306
+ end_time=end_time,
307
+ max_samples=max_samples
308
+ )
309
+ except Exception as e:
310
+ return {"status": "error", "message": f"Error getting digital data: {str(e)}"}