1+ """
2+ BridgeUDP Python Server Implementation
3+
4+ This module provides the server-side RPC methods for testing the BridgeUDP class.
5+ It simulates UDP operations and handles packet transmission/reception.
6+ """
7+
8+ import socket
9+ import struct
10+ import threading
11+ import time
12+ from arduino .app_utils import *
13+
14+
15+ # Global state for UDP connections
16+ udp_connections = {}
17+ connection_counter = 0
18+ connection_lock = threading .Lock ()
19+
20+ # Simulated incoming packets buffer (connection_id -> list of packets)
21+ incoming_packets = {}
22+
23+
24+ class UDPConnection :
25+ """Represents a UDP connection"""
26+
27+ def __init__ (self , connection_id , host , port , is_multicast = False ):
28+ self .connection_id = connection_id
29+ self .host = host
30+ self .port = port
31+ self .is_multicast = is_multicast
32+ self .socket = None
33+ self .running = False
34+ self .receive_thread = None
35+
36+ # Create and bind socket
37+ self .socket = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
38+ self .socket .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
39+
40+ if is_multicast :
41+ # Multicast setup
42+ self .socket .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
43+ self .socket .bind (('' , port ))
44+
45+ # Join multicast group
46+ mreq = struct .pack ("4sl" , socket .inet_aton (host ), socket .INADDR_ANY )
47+ self .socket .setsockopt (socket .IPPROTO_IP , socket .IP_ADD_MEMBERSHIP , mreq )
48+ else :
49+ # Regular UDP
50+ self .socket .bind ((host , port ))
51+
52+ # Set socket to non-blocking for receive thread
53+ self .socket .settimeout (0.5 )
54+
55+ # Start receive thread
56+ self .running = True
57+ self .receive_thread = threading .Thread (target = self ._receive_loop , daemon = True )
58+ self .receive_thread .start ()
59+
60+ print (f"UDP Connection { connection_id } created: { host } :{ port } (multicast={ is_multicast } )" )
61+
62+ def _receive_loop (self ):
63+ """Background thread to receive UDP packets"""
64+ while self .running :
65+ try :
66+ data , addr = self .socket .recvfrom (4096 )
67+ if data :
68+ # Format: [IP (4 bytes), Port (2 bytes), Length (2 bytes), Data]
69+ ip_bytes = socket .inet_aton (addr [0 ])
70+ port_bytes = struct .pack ('>H' , addr [1 ])
71+ length_bytes = struct .pack ('>H' , len (data ))
72+
73+ packet = list (ip_bytes ) + list (port_bytes ) + list (length_bytes ) + list (data )
74+
75+ with connection_lock :
76+ if self .connection_id not in incoming_packets :
77+ incoming_packets [self .connection_id ] = []
78+ incoming_packets [self .connection_id ].append (packet )
79+
80+ print (f"Received { len (data )} bytes from { addr [0 ]} :{ addr [1 ]} on connection { self .connection_id } " )
81+ except socket .timeout :
82+ continue
83+ except Exception as e :
84+ if self .running :
85+ print (f"Error receiving on connection { self .connection_id } : { e } " )
86+ break
87+
88+ def send (self , target_host , target_port , data ):
89+ """Send data to target"""
90+ try :
91+ self .socket .sendto (bytes (data ), (target_host , target_port ))
92+ return len (data )
93+ except Exception as e :
94+ print (f"Error sending on connection { self .connection_id } : { e } " )
95+ return 0
96+
97+ def close (self ):
98+ """Close the connection"""
99+ self .running = False
100+ if self .receive_thread :
101+ self .receive_thread .join (timeout = 1.0 )
102+ if self .socket :
103+ self .socket .close ()
104+ print (f"UDP Connection { self .connection_id } closed" )
105+
106+
107+ def udp_connect (hostname : str , port : int ):
108+ """
109+ Create a UDP connection
110+ Returns: connection_id
111+ """
112+ global connection_counter
113+
114+ with connection_lock :
115+ connection_counter += 1
116+ conn_id = connection_counter
117+
118+ try :
119+ conn = UDPConnection (conn_id , hostname , port , is_multicast = False )
120+ udp_connections [conn_id ] = conn
121+ incoming_packets [conn_id ] = []
122+
123+ print (f"UDP connect: { hostname } :{ port } -> connection_id={ conn_id } " )
124+ return conn_id
125+ except Exception as e :
126+ print (f"Error in udp_connect: { e } " )
127+ return 0
128+
129+
130+ def udp_connect_multicast (hostname : str , port : int ):
131+ """
132+ Create a UDP multicast connection
133+ Returns: connection_id
134+ """
135+ global connection_counter
136+
137+ with connection_lock :
138+ connection_counter += 1
139+ conn_id = connection_counter
140+
141+ try :
142+ conn = UDPConnection (conn_id , hostname , port , is_multicast = True )
143+ udp_connections [conn_id ] = conn
144+ incoming_packets [conn_id ] = []
145+
146+ print (f"UDP connect multicast: { hostname } :{ port } -> connection_id={ conn_id } " )
147+ return conn_id
148+ except Exception as e :
149+ print (f"Error in udp_connect_multicast: { e } " )
150+ return 0
151+
152+
153+ def udp_close (connection_id : int ):
154+ """
155+ Close a UDP connection
156+ Returns: status message
157+ """
158+ with connection_lock :
159+ if connection_id in udp_connections :
160+ conn = udp_connections [connection_id ]
161+ conn .close ()
162+ del udp_connections [connection_id ]
163+
164+ if connection_id in incoming_packets :
165+ del incoming_packets [connection_id ]
166+
167+ print (f"UDP close: connection_id={ connection_id } " )
168+ return "closed"
169+ else :
170+ print (f"UDP close: connection_id={ connection_id } not found" )
171+ return "not found"
172+
173+
174+ def udp_write (connection_id : int , target_host : str , target_port : int , data : list ):
175+ """
176+ Write data to a UDP connection
177+ Returns: number of bytes written
178+ """
179+ with connection_lock :
180+ if connection_id not in udp_connections :
181+ print (f"UDP write: connection_id={ connection_id } not found" )
182+ return 0
183+
184+ conn = udp_connections [connection_id ]
185+
186+ # Send outside lock to avoid blocking
187+ written = conn .send (target_host , target_port , data )
188+ print (f"UDP write: connection_id={ connection_id } , target={ target_host } :{ target_port } , bytes={ written } " )
189+ return written
190+
191+
192+ def udp_read (connection_id : int , size : int ):
193+ """
194+ Read data from a UDP connection
195+ Returns: array of bytes (includes packet header)
196+ """
197+ with connection_lock :
198+ if connection_id not in incoming_packets :
199+ return []
200+
201+ packets = incoming_packets [connection_id ]
202+ if not packets :
203+ return []
204+
205+ # Get available bytes from first packet
206+ available = packets [0 ]
207+
208+ if size >= len (available ):
209+ # Return entire packet and remove from queue
210+ result = packets .pop (0 )
211+ print (f"UDP read: connection_id={ connection_id } , size={ len (result )} (complete packet)" )
212+ return result
213+ else :
214+ # Return partial data
215+ result = available [:size ]
216+ incoming_packets [connection_id ][0 ] = available [size :]
217+ print (f"UDP read: connection_id={ connection_id } , size={ size } (partial)" )
218+ return result
219+
220+
221+ # Test helper functions
222+ def send_test_packet (target_host : str , target_port : int , message : str ):
223+ """
224+ Send a test UDP packet to the specified host:port
225+ Useful for testing receive functionality
226+ """
227+ try :
228+ sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
229+ sock .sendto (message .encode (), (target_host , target_port ))
230+ sock .close ()
231+ print (f"Sent test packet to { target_host } :{ target_port } : { message } " )
232+ return True
233+ except Exception as e :
234+ print (f"Error sending test packet: { e } " )
235+ return False
236+
237+
238+ def send_test_packets_loop (target_host : str , target_port : int , count : int = 10 , interval : float = 1.0 ):
239+ """
240+ Send multiple test packets in a loop
241+ """
242+ for i in range (count ):
243+ message = f"Test packet { i + 1 } /{ count } "
244+ send_test_packet (target_host , target_port , message )
245+ time .sleep (interval )
246+
247+
248+ def echo_server (listen_port : int ):
249+ """
250+ Start a simple UDP echo server for testing
251+ """
252+ def echo_loop ():
253+ sock = socket .socket (socket .AF_INET , socket .SOCK_DGRAM )
254+ sock .bind (('0.0.0.0' , listen_port ))
255+ sock .settimeout (0.5 )
256+
257+ print (f"UDP Echo server started on port { listen_port } " )
258+
259+ while True :
260+ try :
261+ data , addr = sock .recvfrom (4096 )
262+ if data :
263+ # Echo back with "ECHO: " prefix
264+ response = b"ECHO: " + data
265+ sock .sendto (response , addr )
266+ print (f"Echo server: received { len (data )} bytes, sent { len (response )} bytes to { addr } " )
267+ except socket .timeout :
268+ continue
269+ except Exception as e :
270+ print (f"Echo server error: { e } " )
271+ break
272+
273+ thread = threading .Thread (target = echo_loop , daemon = True )
274+ thread .start ()
275+
276+
277+ if __name__ == "__main__" :
278+ # Register RPC methods
279+ Bridge .provide ("udp/connect" , udp_connect )
280+ Bridge .provide ("udp/connectMulticast" , udp_connect_multicast )
281+ Bridge .provide ("udp/close" , udp_close )
282+ Bridge .provide ("udp/write" , udp_write )
283+ Bridge .provide ("udp/read" , udp_read )
284+
285+ # Start echo server for testing
286+ echo_server (5000 )
287+
288+ # Example: Send test packets periodically
289+ # Uncomment to enable automatic packet sending for testing
290+ # threading.Thread(target=lambda: send_test_packets_loop("192.168.1.100", 8888), daemon=True).start()
291+
292+ print ("UDP Bridge Server ready" )
293+ print ("Available methods:" )
294+ print (" - udp/connect" )
295+ print (" - udp/connectMulticast" )
296+ print (" - udp/close" )
297+ print (" - udp/write" )
298+ print (" - udp/read" )
299+ print ("" )
300+ print ("Echo server running on port 5000" )
301+
302+ App .run ()
0 commit comments