1
+ #! python3
2
+
1
3
from ghpythonlib .componentbase import executingcomponent as component
2
4
import socket
3
5
import threading
7
9
import System .Drawing as sd
8
10
9
11
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 ):
12
19
13
20
# Sticky defaults
14
- sc .sticky .setdefault ('listen_addr' , None )
15
21
sc .sticky .setdefault ('server_sock' , None )
16
22
sc .sticky .setdefault ('server_started' , False )
17
23
sc .sticky .setdefault ('cloud_buffer_raw' , [])
18
24
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 )
20
28
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
40
30
41
31
# Client handler
42
32
def handle_client (conn ):
33
+ """
34
+ reads the incoming bytes from a single client socket and stores valid data in a shared buffer
35
+ """
43
36
buf = b''
44
37
with conn :
45
- while True :
38
+ while sc . sticky . get ( 'server_started' , False ) :
46
39
try :
47
40
chunk = conn .recv (4096 )
48
41
if not chunk :
@@ -52,61 +45,77 @@ def handle_client(conn):
52
45
line , buf = buf .split (b'\n ' , 1 )
53
46
try :
54
47
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 :
58
49
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 ):
61
51
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 :
69
53
break
70
54
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
78
66
79
- # Start server
67
+ # Start TCP server
80
68
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 ()
94
81
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' ]:
96
96
start_server ()
97
+ if i_stop and not sc .sticky ['prev_stop' ]:
98
+ stop_server ()
97
99
100
+ # Load buffered points into PointCloud
98
101
if i_load and not sc .sticky ['prev_load' ]:
99
- raw = sc .sticky [ 'cloud_buffer_raw' ]
102
+ raw = sc .sticky . get ( 'cloud_buffer_raw' , [])
100
103
if raw :
101
104
pc = rg .PointCloud ()
102
105
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'
107
109
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
110
115
sc .sticky ['prev_load' ] = i_load
111
116
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