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 ("\n Stopping 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 ()
0 commit comments