Skip to content

Commit c98b355

Browse files
pi-anlclaude
andcommitted
python-ecosys/debugpy: Add VS Code debugging support for MicroPython.
This implementation provides a Debug Adapter Protocol (DAP) server that enables VS Code to debug MicroPython code with full breakpoint, stepping, and variable inspection capabilities. Features: - Manual breakpoints via debugpy.breakpoint() - Line breakpoints set from VS Code - Stack trace inspection - Variable scopes (locals/globals) - Source code viewing - Stepping (into/over/out) - Non-blocking architecture for MicroPython's single-threaded environment - Conditional debug logging based on VS Code's logToFile setting Implementation highlights: - Uses MicroPython's sys.settrace() for execution monitoring - Handles path mapping between VS Code and MicroPython - Efficient O(n) fibonacci demo (was O(2^n) recursive) - Compatible with MicroPython's limited frame object attributes - Comprehensive DAP protocol support Files: - debugpy/: Core debugging implementation - test_vscode.py: VS Code integration test - VSCODE_TESTING_GUIDE.md: Setup and usage instructions - dap_monitor.py: Protocol debugging utility Usage: ```python import debugpy debugpy.listen() # Start debug server debugpy.debug_this_thread() # Enable tracing debugpy.breakpoint() # Manual breakpoint ``` 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 6e24cff commit c98b355

15 files changed

+1656
-0
lines changed

python-ecosys/debugpy/README.md

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# MicroPython debugpy
2+
3+
A minimal implementation of debugpy for MicroPython, enabling remote debugging
4+
such as VS Code debugging support.
5+
6+
## Features
7+
8+
- Debug Adapter Protocol (DAP) support for VS Code integration
9+
- Basic debugging operations:
10+
- Breakpoints
11+
- Step over/into/out
12+
- Stack trace inspection
13+
- Variable inspection (globals, locals generally not supported)
14+
- Expression evaluation
15+
- Pause/continue execution
16+
17+
## Requirements
18+
19+
- MicroPython with `sys.settrace` support (enabled with `MICROPY_PY_SYS_SETTRACE`)
20+
- Socket support for network communication
21+
- JSON support for DAP message parsing
22+
23+
## Usage
24+
25+
### Basic Usage
26+
27+
```python
28+
import debugpy
29+
30+
# Start listening for debugger connections
31+
host, port = debugpy.listen() # Default: 127.0.0.1:5678
32+
print(f"Debugger listening on {host}:{port}")
33+
34+
# Enable debugging for current thread
35+
debugpy.debug_this_thread()
36+
37+
# Your code here...
38+
def my_function():
39+
x = 10
40+
y = 20
41+
result = x + y # Set breakpoint here in VS Code
42+
return result
43+
44+
result = my_function()
45+
print(f"Result: {result}")
46+
47+
# Manual breakpoint
48+
debugpy.breakpoint()
49+
```
50+
51+
### VS Code Configuration
52+
53+
Create a `.vscode/launch.json` file in your project:
54+
55+
```json
56+
{
57+
"version": "0.2.0",
58+
"configurations": [
59+
{
60+
"name": "Attach to MicroPython",
61+
"type": "python",
62+
"request": "attach",
63+
"connect": {
64+
"host": "127.0.0.1",
65+
"port": 5678
66+
},
67+
"pathMappings": [
68+
{
69+
"localRoot": "${workspaceFolder}",
70+
"remoteRoot": "."
71+
}
72+
],
73+
"justMyCode": false
74+
}
75+
]
76+
}
77+
```
78+
79+
### Testing
80+
81+
1. Build the MicroPython Unix coverage port:
82+
```bash
83+
cd ports/unix
84+
make CFLAGS_EXTRA="-DMICROPY_PY_SYS_SETTRACE=1"
85+
```
86+
87+
2. Run the test script:
88+
```bash
89+
cd lib/micropython-lib/python-ecosys/debugpy
90+
../../../../ports/unix/build-coverage/micropython test_debugpy.py
91+
```
92+
93+
3. In VS Code, open the debugpy folder and press F5 to attach the debugger
94+
95+
4. Set breakpoints in the test script and observe debugging functionality
96+
97+
## API Reference
98+
99+
### `debugpy.listen(port=5678, host="127.0.0.1")`
100+
101+
Start listening for debugger connections.
102+
103+
**Parameters:**
104+
- `port`: Port number to listen on (default: 5678)
105+
- `host`: Host address to bind to (default: "127.0.0.1")
106+
107+
**Returns:** Tuple of (host, port) actually used
108+
109+
### `debugpy.debug_this_thread()`
110+
111+
Enable debugging for the current thread by installing the trace function.
112+
113+
### `debugpy.breakpoint()`
114+
115+
Trigger a manual breakpoint that will pause execution if a debugger is attached.
116+
117+
### `debugpy.wait_for_client()`
118+
119+
Wait for the debugger client to connect and initialize.
120+
121+
### `debugpy.is_client_connected()`
122+
123+
Check if a debugger client is currently connected.
124+
125+
**Returns:** Boolean indicating connection status
126+
127+
### `debugpy.disconnect()`
128+
129+
Disconnect from the debugger client and clean up resources.
130+
131+
## Architecture
132+
133+
The implementation consists of several key components:
134+
135+
1. **Public API** (`public_api.py`): Main entry points for users
136+
2. **Debug Session** (`server/debug_session.py`): Handles DAP protocol communication
137+
3. **PDB Adapter** (`server/pdb_adapter.py`): Bridges DAP and MicroPython's trace system
138+
4. **Messaging** (`common/messaging.py`): JSON message handling for DAP
139+
5. **Constants** (`common/constants.py`): DAP protocol constants
140+
141+
## Limitations
142+
143+
This is a minimal implementation with the following limitations:
144+
145+
- Single-threaded debugging only
146+
- No conditional breakpoints
147+
- No function breakpoints
148+
- Limited variable inspection (no nested object expansion)
149+
- No step back functionality
150+
- No hot code reloading
151+
- Simplified stepping implementation
152+
153+
## Compatibility
154+
155+
Tested with:
156+
- MicroPython Unix port
157+
- VS Code with Python/debugpy extension
158+
- CPython 3.x (for comparison)
159+
160+
## Contributing
161+
162+
This implementation provides a foundation for MicroPython debugging. Contributions are welcome to add:
163+
164+
- Conditional breakpoint support
165+
- Better variable inspection
166+
- Multi-threading support
167+
- Performance optimizations
168+
- Additional DAP features
169+
170+
## License
171+
172+
MIT License - see the MicroPython project license for details.

python-ecosys/debugpy/dap_monitor.py

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
#!/usr/bin/env python3
2+
"""DAP protocol monitor - sits between VS Code and MicroPython debugpy."""
3+
4+
import socket
5+
import threading
6+
import json
7+
import time
8+
import sys
9+
10+
class DAPMonitor:
11+
def __init__(self, listen_port=5679, target_host='127.0.0.1', target_port=5678):
12+
self.listen_port = listen_port
13+
self.target_host = target_host
14+
self.target_port = target_port
15+
self.client_sock = None
16+
self.server_sock = None
17+
18+
def start(self):
19+
"""Start the DAP monitor proxy."""
20+
print(f"DAP Monitor starting on port {self.listen_port}")
21+
print(f"Will forward to {self.target_host}:{self.target_port}")
22+
print("Start MicroPython debugpy server first, then connect VS Code to port 5679")
23+
24+
# Create listening socket
25+
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
26+
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
27+
listener.bind(('127.0.0.1', self.listen_port))
28+
listener.listen(1)
29+
30+
print(f"Listening for VS Code connection on port {self.listen_port}...")
31+
32+
try:
33+
# Wait for VS Code to connect
34+
self.client_sock, client_addr = listener.accept()
35+
print(f"VS Code connected from {client_addr}")
36+
37+
# Connect to MicroPython debugpy server
38+
self.server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
39+
self.server_sock.connect((self.target_host, self.target_port))
40+
print(f"Connected to MicroPython debugpy at {self.target_host}:{self.target_port}")
41+
42+
# Start forwarding threads
43+
threading.Thread(target=self.forward_client_to_server, daemon=True).start()
44+
threading.Thread(target=self.forward_server_to_client, daemon=True).start()
45+
46+
print("DAP Monitor active - press Ctrl+C to stop")
47+
while True:
48+
time.sleep(1)
49+
50+
except KeyboardInterrupt:
51+
print("\nStopping DAP Monitor...")
52+
except Exception as e:
53+
print(f"Error: {e}")
54+
finally:
55+
self.cleanup()
56+
57+
def forward_client_to_server(self):
58+
"""Forward messages from VS Code client to MicroPython server."""
59+
try:
60+
while True:
61+
data = self.receive_dap_message(self.client_sock, "VS Code")
62+
if data is None:
63+
break
64+
self.send_raw_data(self.server_sock, data)
65+
except Exception as e:
66+
print(f"Client->Server forwarding error: {e}")
67+
68+
def forward_server_to_client(self):
69+
"""Forward messages from MicroPython server to VS Code client."""
70+
try:
71+
while True:
72+
data = self.receive_dap_message(self.server_sock, "MicroPython")
73+
if data is None:
74+
break
75+
self.send_raw_data(self.client_sock, data)
76+
except Exception as e:
77+
print(f"Server->Client forwarding error: {e}")
78+
79+
def receive_dap_message(self, sock, source):
80+
"""Receive and log a DAP message."""
81+
try:
82+
# Read headers
83+
header = b""
84+
while b"\r\n\r\n" not in header:
85+
byte = sock.recv(1)
86+
if not byte:
87+
return None
88+
header += byte
89+
90+
# Parse content length
91+
header_str = header.decode('utf-8')
92+
content_length = 0
93+
for line in header_str.split('\r\n'):
94+
if line.startswith('Content-Length:'):
95+
content_length = int(line.split(':', 1)[1].strip())
96+
break
97+
98+
if content_length == 0:
99+
return None
100+
101+
# Read content
102+
content = b""
103+
while len(content) < content_length:
104+
chunk = sock.recv(content_length - len(content))
105+
if not chunk:
106+
return None
107+
content += chunk
108+
109+
# Log the message
110+
try:
111+
message = json.loads(content.decode('utf-8'))
112+
msg_type = message.get('type', 'unknown')
113+
command = message.get('command', message.get('event', 'unknown'))
114+
seq = message.get('seq', 0)
115+
116+
print(f"\n[{source}] {msg_type.upper()}: {command} (seq={seq})")
117+
118+
if msg_type == 'request':
119+
args = message.get('arguments', {})
120+
if args:
121+
print(f" Arguments: {json.dumps(args, indent=2)}")
122+
elif msg_type == 'response':
123+
success = message.get('success', False)
124+
req_seq = message.get('request_seq', 0)
125+
print(f" Success: {success}, Request Seq: {req_seq}")
126+
body = message.get('body')
127+
if body:
128+
print(f" Body: {json.dumps(body, indent=2)}")
129+
msg = message.get('message')
130+
if msg:
131+
print(f" Message: {msg}")
132+
elif msg_type == 'event':
133+
body = message.get('body', {})
134+
if body:
135+
print(f" Body: {json.dumps(body, indent=2)}")
136+
137+
except json.JSONDecodeError:
138+
print(f"\n[{source}] Invalid JSON: {content}")
139+
140+
return header + content
141+
142+
except Exception as e:
143+
print(f"Error receiving from {source}: {e}")
144+
return None
145+
146+
def send_raw_data(self, sock, data):
147+
"""Send raw data to socket."""
148+
try:
149+
sock.send(data)
150+
except Exception as e:
151+
print(f"Error sending data: {e}")
152+
153+
def cleanup(self):
154+
"""Clean up sockets."""
155+
if self.client_sock:
156+
self.client_sock.close()
157+
if self.server_sock:
158+
self.server_sock.close()
159+
160+
if __name__ == "__main__":
161+
monitor = DAPMonitor()
162+
monitor.start()
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""MicroPython debugpy implementation.
2+
3+
A minimal port of debugpy for MicroPython to enable VS Code debugging support.
4+
This implementation focuses on the core DAP (Debug Adapter Protocol) functionality
5+
needed for basic debugging operations like breakpoints, stepping, and variable inspection.
6+
"""
7+
8+
__version__ = "0.1.0"
9+
10+
from .public_api import listen, wait_for_client, breakpoint, debug_this_thread
11+
from .common.constants import DEFAULT_HOST, DEFAULT_PORT
12+
13+
__all__ = [
14+
"listen",
15+
"wait_for_client",
16+
"breakpoint",
17+
"debug_this_thread",
18+
"DEFAULT_HOST",
19+
"DEFAULT_PORT",
20+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Common utilities and constants for debugpy

0 commit comments

Comments
 (0)