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