1515import h5py
1616import numpy as np
1717from blacs .tab_base_classes import Worker
18+ from labscript import LabscriptError
1819from labscript_utils .connections import _ensure_str
1920import labscript_utils .properties as properties
2021
@@ -56,28 +57,85 @@ def init(self):
5657 self .wait_timeout = None
5758 self .h5_file = None
5859 self .started = False
59-
60- self .prawnblaster = serial .Serial (self .com_port , 115200 , timeout = 1 )
60+ self .min_version = (1 , 1 , 0 )
61+
62+ self .conn = serial .Serial (self .com_port , 115200 , timeout = 1 )
6163 self .check_status ()
6264
6365 # configure number of pseudoclocks
64- self .prawnblaster .write (b"setnumpseudoclocks %d\r \n " % self .num_pseudoclocks )
65- assert self .prawnblaster .readline ().decode () == "ok\r \n "
66+ self .send_command_ok (f"setnumpseudoclocks { self .num_pseudoclocks } " )
6667
6768 # Configure pins
6869 for i , (out_pin , in_pin ) in enumerate (zip (self .out_pins , self .in_pins )):
69- self .prawnblaster .write (b"setoutpin %d %d\r \n " % (i , out_pin ))
70- assert self .prawnblaster .readline ().decode () == "ok\r \n "
71- self .prawnblaster .write (b"setinpin %d %d\r \n " % (i , in_pin ))
72- assert self .prawnblaster .readline ().decode () == "ok\r \n "
70+ self .send_command_ok (f"setoutpin { i } { out_pin } " )
71+ self .send_command_ok (f"setinpin { i } { in_pin } " )
7372
74- # Check if fast serial is available
7573 version , _ = self .get_version ()
76- self .fast_serial = version >= (1 , 1 , 0 )
74+ print (f'Connected to version: { version } ' )
75+ assert version >= self .min_version , f'Incompatible firmware, must be >= { self .min_version } '
76+
77+ if version >= (1 , 2 , 0 ):
78+ board = self .get_board ()
79+ else :
80+ board = 'pico1'
81+ print (f'Version { version } too low to use pico2 firmware, consider upgrading firmware' )
82+
83+ print (f'Connected to board: { board } ' )
84+ if board .strip () != self .pico_board .strip ():
85+ raise LabscriptError (f'firmware reports { board } attached, labscript expects { self .pico_board } ' )
86+
87+ current_status = self .read_status ()
88+ print (f'Current status is { current_status } ' )
89+
90+ def _read_full_buffer (self ):
91+ '''Used to get any extra lines from device after a failed send_command'''
92+
93+ resp = self .conn .readlines ()
94+ str_resp = '' .join ([st .decode () for st in resp ])
95+
96+ return str_resp
97+
98+ def send_command (self , command , readlines = False ):
99+ '''Sends the supplied string command and checks for a response.
100+
101+ Automatically applies the correct termination characters.
102+
103+ Args:
104+ command (str): Command to send. Termination and encoding is done automatically.
105+ readlines (bool, optional): Use pyserial's readlines functionality to read multiple
106+ response lines. Slower as it relies on timeout to terminate reading.
107+
108+ Returns:
109+ str: String response from the PrawnBlaster
110+ '''
111+ command += '\r \n '
112+ self .conn .write (command .encode ())
113+
114+ if readlines :
115+ str_resp = self ._read_full_buffer ()
116+ else :
117+ str_resp = self .conn .readline ().decode ()
77118
119+ return str_resp
120+
121+ def send_command_ok (self , command ):
122+ '''Sends the supplied string command and confirms 'ok' response.
123+
124+ Args:
125+ command (str): String command to send.
126+
127+ Raises:
128+ LabscriptError: If response is not `ok\\ r\\ n`
129+ '''
130+
131+ resp = self .send_command (command )
132+ if resp != 'ok\r \n ' :
133+ # get complete error message
134+ resp += self ._read_full_buffer ()
135+ raise LabscriptError (f"Command '{ command :s} ' failed. Got response '{ repr (resp )} '" )
136+
78137 def get_version (self ):
79- self .prawnblaster .write (b"version\r \n " )
80- version_str = self .prawnblaster .readline ().decode ()
138+ version_str = self .send_command ('version' , readlines = True )
81139 assert version_str .startswith ("version: " )
82140 version = version_str [9 :].strip ()
83141
@@ -91,6 +149,17 @@ def get_version(self):
91149 assert len (version ) == 3
92150
93151 return version , overclock
152+
153+ def get_board (self ):
154+ '''Responds with pico board version.
155+
156+ Returns:
157+ (str): Either "pico1" for a Pi Pico 1 board or "pico2" for a Pi Pico 2 board.'''
158+ resp = self .send_command ('board' )
159+ assert resp .startswith ('board:' ), f'Board command failed, got: { resp } '
160+ pico_str = resp .split (':' )[- 1 ].strip ()
161+
162+ return pico_str
94163
95164 def check_status (self ):
96165 """Checks the operational status of the PrawnBlaster.
@@ -128,8 +197,7 @@ def check_status(self):
128197 ):
129198 # Try to read out wait. For now, we're only reading out waits from
130199 # pseudoclock 0 since they should all be the same (requirement imposed by labscript)
131- self .prawnblaster .write (b"getwait %d %d\r \n " % (0 , self .current_wait ))
132- response = self .prawnblaster .readline ().decode ()
200+ response = self .send_command (f'getwait { 0 } , { self .current_wait } ' )
133201 if response != "wait not yet available\r \n " :
134202 # Parse the response from the PrawnBlaster
135203 wait_remaining = int (response )
@@ -202,8 +270,7 @@ def read_status(self):
202270 - **clock-status** (int): Clock status code
203271 """
204272
205- self .prawnblaster .write (b"status\r \n " )
206- response = self .prawnblaster .readline ().decode ()
273+ response = self .send_command ("status" , readlines = True )
207274 match = re .match (r"run-status:(\d) clock-status:(\d)(\r\n)?" , response )
208275 if match :
209276 return int (match .group (1 )), int (match .group (2 ))
@@ -231,11 +298,9 @@ def program_manual(self, values):
231298 pin = int (channel .split ()[1 ])
232299 pseudoclock = self .out_pins .index (pin )
233300 if value :
234- self .prawnblaster . write ( b "go high %d \r \n " % pseudoclock )
301+ self .send_command_ok ( f "go high { pseudoclock } " )
235302 else :
236- self .prawnblaster .write (b"go low %d\r \n " % pseudoclock )
237-
238- assert self .prawnblaster .readline ().decode () == "ok\r \n "
303+ self .send_command_ok (f"go low { pseudoclock } " )
239304
240305 return values
241306
@@ -309,15 +374,13 @@ def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
309374 clock_frequency = self .device_properties ["clock_frequency" ]
310375
311376 # Now set the clock details
312- self .prawnblaster .write (b"setclock %d %d\r \n " % (clock_mode , clock_frequency ))
313- response = self .prawnblaster .readline ().decode ()
314- assert response == "ok\r \n " , f"PrawnBlaster said '{ response } ', expected 'ok'"
377+ response = self .send_command_ok (f"setclock { clock_mode } { clock_frequency } " )
315378
316379 # Program instructions
317380 for pseudoclock , pulse_program in enumerate (pulse_programs ):
318381 total_inst = len (pulse_program )
319382 # check if it is more efficient to fully refresh
320- if not fresh and self .smart_cache [pseudoclock ] is not None and self . fast_serial :
383+ if not fresh and self .smart_cache [pseudoclock ] is not None :
321384 # get more convenient handles to smart cache arrays
322385 curr_inst = self .smart_cache [pseudoclock ]
323386
@@ -337,17 +400,17 @@ def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
337400 if new_inst / total_inst > 0.1 :
338401 fresh = True
339402
340- if (fresh or self .smart_cache [pseudoclock ] is None ) and self . fast_serial :
403+ if (fresh or self .smart_cache [pseudoclock ] is None ):
341404 print ('binary programming' )
342- self .prawnblaster .write (b"setb %d %d %d\r \n " % (pseudoclock , 0 , len (pulse_program )))
343- response = self .prawnblaster .readline ().decode ()
405+ self .conn .write (b"setb %d %d %d\r \n " % (pseudoclock , 0 , len (pulse_program )))
406+ response = self .conn .readline ().decode ()
344407 assert (
345408 response == "ready\r \n "
346409 ), f"PrawnBlaster said '{ response } ', expected 'ready'"
347410 program_array = np .array ([pulse_program ['half_period' ],
348411 pulse_program ['reps' ]], dtype = '<u4' ).T
349- self .prawnblaster .write (program_array .tobytes ())
350- response = self .prawnblaster .readline ().decode ()
412+ self .conn .write (program_array .tobytes ())
413+ response = self .conn .readline ().decode ()
351414 assert (
352415 response == "ok\r \n "
353416 ), f"PrawnBlaster said '{ response } ', expected 'ok'"
@@ -361,7 +424,7 @@ def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
361424
362425 # Only program instructions that differ from what's in the smart cache:
363426 if self .smart_cache [pseudoclock ][i ] != instruction :
364- self .prawnblaster .write (
427+ self .conn .write (
365428 b"set %d %d %d %d\r \n "
366429 % (
367430 pseudoclock ,
@@ -370,7 +433,7 @@ def transition_to_buffered(self, device_name, h5file, initial_values, fresh):
370433 instruction ["reps" ],
371434 )
372435 )
373- response = self .prawnblaster .readline ().decode ()
436+ response = self .conn .readline ().decode ()
374437 assert (
375438 response == "ok\r \n "
376439 ), f"PrawnBlaster said '{ response } ', expected 'ok'"
@@ -392,9 +455,7 @@ def start_run(self):
392455
393456 # Start in software:
394457 self .logger .info ("sending start" )
395- self .prawnblaster .write (b"start\r \n " )
396- response = self .prawnblaster .readline ().decode ()
397- assert response == "ok\r \n " , f"PrawnBlaster said '{ response } ', expected 'ok'"
458+ self .send_command_ok ("start" )
398459
399460 # set started = True
400461 self .started = True
@@ -405,9 +466,7 @@ def wait_for_trigger(self):
405466
406467 # Set to wait for trigger:
407468 self .logger .info ("sending hwstart" )
408- self .prawnblaster .write (b"hwstart\r \n " )
409- response = self .prawnblaster .readline ().decode ()
410- assert response == "ok\r \n " , f"PrawnBlaster said '{ response } ', expected 'ok'"
469+ self .send_command_ok ("hwstart" )
411470
412471 running = False
413472 while not running :
@@ -477,7 +536,7 @@ def transition_to_manual(self):
477536 def shutdown (self ):
478537 """Cleanly shuts down the connection to the PrawnBlaster hardware."""
479538
480- self .prawnblaster .close ()
539+ self .conn .close ()
481540
482541 def abort_buffered (self ):
483542 """Aborts a currently running buffered execution.
@@ -489,8 +548,8 @@ def abort_buffered(self):
489548 # Only need to send abort signal if we have told the PrawnBlaster to wait
490549 # for a hardware trigger. Otherwise it's just been programmed with
491550 # instructions and there is nothing we need to do to abort.
492- self .prawnblaster .write (b"abort\r \n " )
493- assert self .prawnblaster .readline ().decode () == "ok\r \n "
551+ self .conn .write (b"abort\r \n " )
552+ assert self .conn .readline ().decode () == "ok\r \n "
494553 # loop until abort complete
495554 while self .read_status ()[0 ] != 5 :
496555 time .sleep (0.5 )
0 commit comments