@@ -81,14 +81,21 @@ def __init__(self):
8181 self .connection_state_observable = BehaviorSubject (ConnectionState .DISCONNECTED )
8282 self .status_observable = BehaviorSubject (StatusFlag (0 ))
8383 self .nus_observable = Subject ()
84- self .stream_buf = bytearray ()
85- self .output = []
8684 self .print_output = True
8785 self .fw_version = None
8886 self ._mpy_abi_version = 0
8987 self ._capability_flags = HubCapabilityFlag (0 )
9088 self ._max_user_program_size = 0
9189
90+ # buffered stdout from the hub for splitting into lines
91+ self ._stdout_buf = bytearray ()
92+
93+ # REVISIT: this can potentially waste a lot of RAM if not drained
94+ self ._stdout_line_queue = asyncio .Queue ()
95+
96+ # prior to Pybricks Profile v1.3.0, NUS was used for stdio
97+ self ._legacy_stdio = False
98+
9299 # indicates is we are currently downloading a program over NUS (legacy download)
93100 self ._downloading_via_nus = False
94101
@@ -98,13 +105,13 @@ def __init__(self):
98105 # File handle for logging
99106 self .log_file = None
100107
101- def line_handler (self , line ):
102- """Handles new incoming lines. Handle special actions if needed,
108+ def _line_handler (self , line : bytes ) -> None :
109+ """
110+ Handles new incoming lines. Handle special actions if needed,
103111 otherwise just print it as regular lines.
104112
105113 Arguments:
106- line (bytearray):
107- Line to process.
114+ line: Line to process.
108115 """
109116
110117 # The line tells us to open a log file, so do it.
@@ -134,45 +141,60 @@ def line_handler(self, line):
134141 self .log_file = None
135142 return
136143
144+ line_str = line .decode ()
145+
137146 # If we are processing datalog, save current line to the open file.
138147 if self .log_file is not None :
139- print (line . decode () , file = self .log_file )
148+ print (line_str , file = self .log_file )
140149 return
141150
142- # If there is nothing special about this line, print it if requested.
143- self .output .append (line )
144151 if self .print_output :
145- print (line .decode ())
152+ print (line_str )
153+ return
146154
147- def nus_handler (self , sender , data ):
148- self .nus_observable .on_next (data )
155+ self ._stdout_line_queue .put_nowait (line_str )
149156
150- # Store incoming data
151- if not self ._downloading_via_nus :
152- self .stream_buf += data
153- logger .debug ("NUS DATA: {0}" .format (data ))
157+ def _handle_line_data (self , data : bytes ) -> None :
158+ self ._stdout_buf .extend (data )
154159
155160 # Break up data into lines and take those out of the buffer
156161 lines = []
157162 while True :
158163 # Find and split at end of line
159- index = self .stream_buf .find (self .EOL )
164+ index = self ._stdout_buf .find (self .EOL )
160165 # If no more line end is found, we are done
161166 if index < 0 :
162167 break
163168 # If we found a line, save it, and take it from the buffer
164- lines .append (self .stream_buf [ 0 :index ])
165- del self .stream_buf [ 0 : index + len (self .EOL )]
169+ lines .append (self ._stdout_buf [ :index ])
170+ del self ._stdout_buf [ : index + len (self .EOL )]
166171
167172 # Call handler for each line that we found
168173 for line in lines :
169- self .line_handler (line )
174+ self ._line_handler (line )
170175
171- def pybricks_service_handler (self , _ : int , data : bytes ) -> None :
176+ def _nus_handler (self , sender , data : bytearray ) -> None :
177+ self .nus_observable .on_next (data )
178+
179+ # legacy firmware may use NUS for download and run, in which case
180+ # we need to ignore the incoming data
181+ if self ._downloading_via_nus :
182+ return
183+
184+ logger .debug ("NUS DATA: %r" , data )
185+
186+ # support legacy firmware where the Nordic UART service
187+ # was used for stdio
188+ if self ._legacy_stdio :
189+ self ._handle_line_data (data )
190+
191+ def _pybricks_service_handler (self , _ : int , data : bytes ) -> None :
172192 if data [0 ] == Event .STATUS_REPORT :
173193 # decode the payload
174194 (flags ,) = struct .unpack_from ("<I" , data , 1 )
175195 self .status_observable .on_next (StatusFlag (flags ))
196+ elif data [0 ] == Event .WRITE_STDOUT :
197+ self ._handle_line_data (data [1 :])
176198
177199 async def connect (self , device : BLEDevice ):
178200 """Connects to a device that was discovered with :meth:`pybricksdev.ble.find_device`
@@ -242,9 +264,12 @@ def handle_disconnect(_: BleakClient):
242264 6 if self .fw_version >= Version ("3.2.0b2" ) else 5
243265 )
244266
245- await self .client .start_notify (NUS_TX_UUID , self .nus_handler )
267+ if protocol_version < "1.3.0" :
268+ self ._legacy_stdio = True
269+
270+ await self .client .start_notify (NUS_TX_UUID , self ._nus_handler )
246271 await self .client .start_notify (
247- PYBRICKS_COMMAND_EVENT_UUID , self .pybricks_service_handler
272+ PYBRICKS_COMMAND_EVENT_UUID , self ._pybricks_service_handler
248273 )
249274
250275 self .connection_state_observable .on_next (ConnectionState .CONNECTED )
@@ -327,7 +352,8 @@ async def run(
327352
328353 # Reset output buffer
329354 self .log_file = None
330- self .output = []
355+ self ._stdout_buf .clear ()
356+ self ._stdout_line_queue = asyncio .Queue ()
331357 self .print_output = print_output
332358 self .script_dir , _ = os .path .split (py_path )
333359
0 commit comments