1+ #! python3
2+
13from ghpythonlib .componentbase import executingcomponent as component
24import socket
35import threading
79import System .Drawing as sd
810
911
10- class DFHTTPListener (component ):
11- def RunScript (self , i_load : bool , i_reset : bool , i_port : int , i_host : str ):
12+ class DFTCPListener (component ):
13+ def RunScript (self ,
14+ i_start : bool ,
15+ i_load : bool ,
16+ i_stop : bool ,
17+ i_port : int ,
18+ i_host : str ):
1219
1320 # Sticky defaults
14- sc .sticky .setdefault ('listen_addr' , None )
1521 sc .sticky .setdefault ('server_sock' , None )
1622 sc .sticky .setdefault ('server_started' , False )
1723 sc .sticky .setdefault ('cloud_buffer_raw' , [])
1824 sc .sticky .setdefault ('latest_cloud' , None )
19- sc .sticky .setdefault ('status_message' , "Waiting..." )
25+ sc .sticky .setdefault ('status_message' , 'Waiting...' )
26+ sc .sticky .setdefault ('prev_start' , False )
27+ sc .sticky .setdefault ('prev_stop' , False )
2028 sc .sticky .setdefault ('prev_load' , False )
21-
22- # Handle Reset or host/port change
23- addr = (i_host , i_port )
24- if i_reset or sc .sticky ['listen_addr' ] != addr :
25- # close old socket if any
26- old = sc .sticky .get ('server_sock' )
27- try :
28- if old :
29- old .close ()
30- except Exception :
31- pass
32-
33- sc .sticky ['listen_addr' ] = addr
34- sc .sticky ['server_sock' ] = None
35- sc .sticky ['server_started' ] = False
36- sc .sticky ['cloud_buffer_raw' ] = []
37- sc .sticky ['latest_cloud' ] = None
38- sc .sticky ['status_message' ] = "Reset" if i_reset else f"Addr → { i_host } :{ i_port } "
39- ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
29+ sc .sticky .setdefault ('client_socks' , []) # Track client sockets
4030
4131 # Client handler
4232 def handle_client (conn ):
33+ """
34+ reads the incoming bytes from a single client socket and stores valid data in a shared buffer
35+ """
4336 buf = b''
4437 with conn :
45- while True :
38+ while sc . sticky . get ( 'server_started' , False ) :
4639 try :
4740 chunk = conn .recv (4096 )
4841 if not chunk :
@@ -52,61 +45,77 @@ def handle_client(conn):
5245 line , buf = buf .split (b'\n ' , 1 )
5346 try :
5447 raw = json .loads (line .decode ())
55- except Exception as e :
56- sc .sticky ['status_message' ] = f"JSON error: { e } "
57- ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
48+ except Exception :
5849 continue
59-
60- if isinstance (raw , list ) and all (isinstance (pt , list ) and len (pt )== 6 for pt in raw ):
50+ if isinstance (raw , list ) and all (isinstance (pt , list ) and len (pt ) == 6 for pt in raw ):
6151 sc .sticky ['cloud_buffer_raw' ] = raw
62- sc .sticky ['status_message' ] = f"Buffered { len (raw )} pts"
63- else :
64- sc .sticky ['status_message' ] = "Unexpected format"
65- ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
66- except Exception as e :
67- sc .sticky ['status_message' ] = f"Socket error: { e } "
68- ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
52+ except Exception :
6953 break
7054
71- def accept_loop (srv_sock ):
72- while True :
73- try :
74- conn , _ = srv_sock .accept ()
75- threading .Thread (target = handle_client , args = (conn ,), daemon = True ).start ()
76- except Exception :
77- break
55+ # thread to accept incoming connections
56+ def server_loop (sock ):
57+ """
58+ runs in its own thread, continuously calling accept() on the listening socket
59+ Each time a client connects, it launches a new thread running handle_client to deal with that connection
60+ """
61+ try :
62+ conn , _ = sock .accept ()
63+ handle_client (conn )
64+ except Exception :
65+ pass
7866
79- # Start server
67+ # Start TCP server
8068 def start_server ():
81- try :
82- srv = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
83- srv .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
84- srv .bind ((i_host , i_port ))
85- srv .listen ()
86- sc .sticky ['server_sock' ] = srv
87- sc .sticky ['server_started' ] = True
88- sc .sticky ['status_message' ] = f"Listening on { i_host } :{ i_port } "
89- ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
90- threading .Thread (target = accept_loop , args = (srv ,), daemon = True ).start ()
91- except Exception as e :
92- sc .sticky ['status_message' ] = f"Server error: { e } "
93- ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
69+ """
70+ creates and binds a TCP socket on the given host/port, marks the server as started and then starts the accept_loop in a background thread
71+ """
72+ sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
73+ sock .setsockopt (socket .SOL_SOCKET , socket .SO_REUSEADDR , 1 )
74+ sock .bind ((i_host , i_port ))
75+ sock .listen (1 )
76+ sc .sticky ['server_sock' ] = sock
77+ sc .sticky ['server_started' ] = True
78+ sc .sticky ['status_message' ] = f'Listening on { i_host } :{ i_port } '
79+ # Only accept one connection to keep it long-lived
80+ threading .Thread (target = server_loop , args = (sock ,), daemon = True ).start ()
9481
95- if not sc .sticky ['server_started' ]:
82+ def stop_server ():
83+ sock = sc .sticky .get ('server_sock' )
84+ if sock :
85+ try :
86+ sock .close ()
87+ except Exception :
88+ pass
89+ sc .sticky ['server_sock' ] = None
90+ sc .sticky ['server_started' ] = False
91+ sc .sticky ['cloud_buffer_raw' ] = []
92+ sc .sticky ['status_message' ] = 'Stopped'
93+
94+ # Start or stop server based on inputs
95+ if i_start and not sc .sticky ['prev_start' ]:
9696 start_server ()
97+ if i_stop and not sc .sticky ['prev_stop' ]:
98+ stop_server ()
9799
100+ # Load buffered points into PointCloud
98101 if i_load and not sc .sticky ['prev_load' ]:
99- raw = sc .sticky [ 'cloud_buffer_raw' ]
102+ raw = sc .sticky . get ( 'cloud_buffer_raw' , [])
100103 if raw :
101104 pc = rg .PointCloud ()
102105 for x , y , z , r , g , b in raw :
103- col = sd .Color .FromArgb (int (r ), int (g ), int (b ))
104- pc .Add (rg .Point3d (x , y , z ), col )
105- sc .sticky ['latest_cloud' ] = pc
106- sc .sticky ['status_message' ] = f"Retrieved { pc .Count } pts"
106+ pc .Add (rg .Point3d (x , y , z ), sd .Color .FromArgb (int (r ), int (g ), int (b )))
107+ sc .sticky ['latest_cloud' ] = pc
108+ sc .sticky ['status_message' ] = f'Retrieved { pc .Count } pts'
107109 else :
108- sc .sticky ['status_message' ] = "No data buffered"
109- ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
110+ sc .sticky ['status_message' ] = 'No data buffered'
111+
112+ # Update previous states
113+ sc .sticky ['prev_start' ] = i_start
114+ sc .sticky ['prev_stop' ] = i_stop
110115 sc .sticky ['prev_load' ] = i_load
111116
112- return [sc .sticky ['latest_cloud' ]]
117+ # Update UI and output
118+ ghenv .Component .Message = sc .sticky ['status_message' ] # noqa: F821
119+
120+ o_cloud = sc .sticky ['latest_cloud' ]
121+ return [o_cloud ]
0 commit comments