55Uses a Network Tables-like architecture with variable ID mapping.
66"""
77
8+ import select
89import struct
910import sys
1011import time
1112from machine import Timer
12- from micropython import const
13+ from micropython import const , kbd_intr
1314
1415# Message framing constants
1516MSG_START_1 = const (0xAA )
8990
9091class Puppet :
9192 """
92- Core XRP Puppet Protocol implementation.
93+ Core XRP Puppet Protocol implementation.
9394 Manages bidirectional communication, variable registry, and message handling.
9495 """
9596
@@ -129,6 +130,11 @@ def __init__(self):
129130 self ._update_timer = Timer (- 1 )
130131 self ._update_timer_running = False
131132
133+ # STDIO polling (for USB_STDIO transport)
134+ self ._poll_timer = Timer (- 1 )
135+ self ._poll_timer_running = False
136+ self ._poll_stdin_poll = None
137+
132138 # Program state
133139 self ._program_running = False
134140
@@ -142,46 +148,86 @@ def _init_transport(self):
142148 # Try BLE first
143149 try :
144150 from ble .blerepl import uart
145- self ._transport = uart
146- self ._transport_type = 'BLE'
147- self ._transport .set_data_callback (self ._data_callback )
148- return
149- except (ImportError , AttributeError ):
150- pass
151-
152- # Fallback to USB serial
153- try :
154- # For USB serial, we'll use sys.stdin/sys.stdout or machine.UART
155- # Check if we can use UART
156- try :
157- from machine import UART
158- # Try to open UART for USB serial (typically UART(0))
159- self ._transport = UART (0 , baudrate = 115200 )
160- self ._transport_type = 'USB'
161- # For USB serial, we'll need to poll in a timer
162- self ._usb_poll_timer = Timer (- 2 )
163- self ._usb_poll_timer .init (period = 10 , mode = Timer .PERIODIC ,
164- callback = lambda t : self ._poll_usb ())
151+ if len (uart ._connections ) > 0 :
152+ self ._transport = uart
153+ self ._transport_type = 'BLE'
154+ self ._transport .set_data_callback (self ._data_callback )
165155 return
166- except :
167- # Last resort: use sys.stdin/stdout
168- self ._transport = sys
156+ else :
169157 self ._transport_type = 'USB_STDIO'
158+ self ._start_poll_timer ()
170159 return
171- except :
172- pass
173-
174- raise RuntimeError ("No transport available (BLE or USB serial)" )
175-
176- def _poll_usb (self ):
160+ except ImportError :
161+ self ._transport_type = 'USB_STDIO'
162+ self ._start_poll_timer ()
163+ return
164+
165+
166+ def _poll_stdio (self ):
177167 """
178- Poll USB serial for incoming data.
168+ Poll STDIO for incoming data using select.poll.
169+ Reads raw bytes from the stdin buffer.
179170 """
180- if self ._transport_type == 'USB' and self ._transport .any ():
181- data = self ._transport .read (self ._transport .any ())
182- if data :
183- self ._data_callback (data )
184-
171+ data_read = False
172+ while True :
173+ events = self ._stdin_poll .poll (0 )
174+ if not events :
175+ if data_read :
176+ self ._process_rx_buffer ()
177+ break
178+ data = sys .stdin .buffer .read (1 )
179+ self ._rx_buffer .extend (data )
180+ data_read = True
181+
182+ #if data:
183+ #print(f"Received data: {data}")
184+ #self._data_callback(data)
185+
186+ def _start_poll_timer (self ):
187+ """
188+ Start STDIO polling for USB_STDIO transport.
189+ Uses select.poll on the raw stdin buffer and a timer to check it.
190+ """
191+ if self ._transport_type != 'USB_STDIO' or self ._poll_timer_running :
192+ return
193+ self ._stdin_poll = select .poll ()
194+ self ._stdin_poll .register (sys .stdin .buffer , select .POLLIN )
195+
196+ kbd_intr (- 1 ) #the data will have 03 in it, don't do a ctrl-c for that data.
197+
198+ self ._poll_timer .init (period = 20 , mode = Timer .PERIODIC ,
199+ callback = lambda t : self ._poll_stdio ())
200+ self ._poll_timer_running = True
201+
202+ def _stop_poll_timer (self ):
203+ """
204+ Stop the STDIO polling timer and unregister from poll.
205+ """
206+ if self ._poll_timer_running :
207+ self ._poll_timer .deinit ()
208+ kbd_intr (03 ) #start watching for ctrl-c again
209+ self ._poll_timer_running = False
210+ if self ._stdin_poll is not None :
211+ try :
212+ self ._stdin_poll .unregister (sys .stdin .buffer )
213+ except (OSError , AttributeError ):
214+ pass
215+ self ._stdin_poll = None
216+ def start (self ):
217+ """
218+ Start the STDIO polling.
219+ """
220+ if self ._transport_type == 'USB_STDIO' :
221+ self ._start_poll_timer ()
222+
223+ def stop (self ):
224+ """
225+ Stop the STDIO polling.
226+ """
227+ if self ._transport_type == 'USB_STDIO' :
228+ self ._stop_poll_timer ()
229+
230+
185231 def _data_callback (self , data ):
186232 """
187233 Handle incoming data from transport layer.
@@ -209,11 +255,19 @@ def _process_rx_buffer(self):
209255 if idx == - 1 :
210256 # No start sequence found, clear buffer except last byte
211257 if len (self ._rx_buffer ) > 1 :
258+ #see if anything in the buffer is a ctrl-c
259+ for i in range (len (self ._rx_buffer ) - 1 ):
260+ if self ._rx_buffer [i ] == 0x03 :
261+ self .stop () #stop the USB handler if that is what is running and that will reenable the ctrl-c handler
212262 self ._rx_buffer = self ._rx_buffer [- 1 :]
213263 return
214264
215265 # Remove everything before start sequence
216266 if idx > 0 :
267+ #check for a ctrl-c in the non packet parts
268+ for i in range (idx - 1 ):
269+ if self ._rx_buffer [i ] == 0x03 :
270+ self .stop () #stop the USB handler if that is what is running and that will reenable the ctrl-c handler
217271 self ._rx_buffer = self ._rx_buffer [idx :]
218272
219273 # Found start, move to length
@@ -275,11 +329,9 @@ def _write_data(self, data):
275329 """
276330 if self ._transport_type == 'BLE' :
277331 self ._transport .write_data (data )
278- elif self ._transport_type == 'USB' :
279- self ._transport .write (data )
332+
280333 elif self ._transport_type == 'USB_STDIO' :
281334 sys .stdout .buffer .write (data )
282- sys .stdout .buffer .flush ()
283335
284336 def _pack_message (self , msg_type , payload_data ):
285337 """
0 commit comments