33import time as _time
44import select as _select
55
6+ # VFS escape byte - signals start of VFS command from device
7+ ESCAPE = 0x18
8+
69
710class ConnError (Exception ):
811 """General connection error"""
@@ -15,15 +18,36 @@ class Timeout(ConnError):
1518class Conn ():
1619 def __init__ (self , log = None ):
1720 self ._log = log
18- self ._buffer = bytearray (b'' )
21+ self ._buffer = bytearray ()
22+ self ._escape_handlers = {} # {escape_byte: handler}
23+ self ._active_handler = None # Currently processing handler
1924
2025 @property
2126 def fd (self ):
2227 """Return file descriptor for select()"""
2328 return None
2429
30+ def register_escape_handler (self , escape , handler ):
31+ """Register handler for escape byte.
32+
33+ handler.process(data) returns:
34+ None - need more data
35+ b'' - done
36+ bytes - done with leftovers
37+ """
38+ self ._escape_handlers [escape ] = handler
39+
40+ def unregister_escape_handler (self , escape ):
41+ """Unregister handler for escape byte."""
42+ self ._escape_handlers .pop (escape , None )
43+ if self ._active_handler is self ._escape_handlers .get (escape ):
44+ self ._active_handler = None
45+
2546 def _has_data (self , timeout = 0 ):
2647 """Check if data is available to read using select()"""
48+ if self ._active_handler and hasattr (self ._active_handler , 'pending' ):
49+ if self ._active_handler .pending :
50+ return True
2751 fd = self .fd
2852 if fd is None :
2953 return False
@@ -38,6 +62,62 @@ def _write_raw(self, data):
3862 """Write data to device, return bytes written (must be implemented by subclass)"""
3963 raise NotImplementedError
4064
65+ def _process_data (self , data ):
66+ """Process incoming data, intercept escape sequences.
67+
68+ Detects registered escape bytes and routes data to handlers.
69+ Returns output bytes (may be empty if all data was for handler).
70+
71+ Handler protocol:
72+ handler.process(data) returns:
73+ - None: handler needs more data, keep routing to it
74+ - b'': handler done, no leftover data
75+ - bytes: handler done, these are leftover bytes
76+ """
77+ if not data :
78+ return data
79+
80+ output = b''
81+
82+ while data :
83+ if self ._active_handler :
84+ # Route data to active handler
85+ result = self ._active_handler .process (data )
86+ if result is None :
87+ # Handler needs more data
88+ return output or None
89+ # Handler done
90+ self ._active_handler = None
91+ data = result # May be b'' or leftover bytes
92+ else :
93+ # Find registered escape byte in data
94+ earliest_pos = len (data )
95+ earliest_handler = None
96+ for esc , handler in self ._escape_handlers .items ():
97+ try :
98+ pos = data .index (esc )
99+ if pos < earliest_pos :
100+ earliest_pos = pos
101+ earliest_handler = handler
102+ except ValueError :
103+ pass
104+
105+ if earliest_handler :
106+ output += data [:earliest_pos ]
107+ self ._active_handler = earliest_handler
108+ data = data [earliest_pos :] # Include escape, handler verifies
109+ else :
110+ output += data
111+ break
112+
113+ # Check for soft reboot in output (VFS-specific)
114+ if output and self ._escape_handlers .get (ESCAPE ):
115+ handler = self ._escape_handlers [ESCAPE ]
116+ if hasattr (handler , 'check_reboot' ):
117+ handler .check_reboot (output )
118+
119+ return output or b''
120+
41121 def _read_to_buffer (self , wait_timeout = 0 ):
42122 """Read available data into buffer
43123
@@ -46,6 +126,7 @@ def _read_to_buffer(self, wait_timeout=0):
46126 """
47127 if self ._has_data (wait_timeout ):
48128 data = self ._read_available ()
129+ data = self ._process_data (data )
49130 if data :
50131 self ._buffer += data
51132 return True
@@ -60,19 +141,20 @@ def flush(self):
60141 @property
61142 def busy (self ):
62143 """True if connection is busy with internal protocol exchange"""
63- return False
144+ return self . _active_handler is not None
64145
65146 def read (self , timeout = 0 ):
66147 """Read available data from device (non-blocking by default).
67148
68149 Returns device output bytes, or None if no data available.
69- On ConnIntercept, also services VFS requests transparently.
150+ When escape handler is active, services requests transparently.
70151
71152 Arguments:
72153 timeout: how long to wait for data (0 = non-blocking)
73154 """
74155 if self ._has_data (timeout ):
75- return self ._read_available ()
156+ data = self ._read_available ()
157+ return self ._process_data (data )
76158 return None
77159
78160 def read_bytes (self , count , timeout = 1 ):
@@ -88,22 +170,16 @@ def read_bytes(self, count, timeout=1):
88170 raise Timeout ("No data received" )
89171 data = bytes (self ._buffer [:count ])
90172 del self ._buffer [:count ]
91- if self ._log :
92- self ._log .debug ("rd: %s" , data )
93173 return data
94174
95175 def write (self , data ):
96176 """Write data to device"""
97- if self ._log :
98- self ._log .debug ("wr: %s" , bytes (data ))
99177 while data :
100178 count = self ._write_raw (data )
101179 data = data [count :]
102180
103181 def read_until (self , end , timeout = 1 ):
104182 """Read until end marker is found"""
105- if self ._log :
106- self ._log .debug ("wait for %s" , end )
107183 start_time = _time .time ()
108184 while True :
109185 # Use select() with 1ms timeout instead of sleep - wakes immediately on data
@@ -119,8 +195,6 @@ def read_until(self, end, timeout=1):
119195 index = self ._buffer .index (end )
120196 data = self ._buffer [:index ]
121197 del self ._buffer [:index + len (end )]
122- if self ._log :
123- self ._log .debug ("rd: %s" , bytes (data + end ))
124198 return data
125199
126200 def read_line (self , timeout = None ):
0 commit comments