11import os
2- import sys
2+ import re
33import time
44import inspect
55from serial import SerialException
@@ -58,12 +58,16 @@ class Pycboard(Pyboard):
5858 '''Pycontrol board inherits from Pyboard and adds functionality for file transfer
5959 and pyControl operations.
6060 '''
61+ device_class2file = {} # Dict mapping device classes to the file in the devices folder where they are defined {device_class_name: device_file}
6162
6263 def __init__ (self , serial_port , baudrate = 115200 , verbose = True , print_func = print , data_logger = None ):
6364 self .serial_port = serial_port
6465 self .print = print_func # Function used for print statements.
6566 self .data_logger = data_logger # Instance of Data_logger class for saving and printing data.
6667 self .status = {'serial' : None , 'framework' :None , 'usb_mode' :None }
68+ self .device_files_on_pyboard = [] # List of files in devices folder on pyboard.
69+ if not Pycboard .device_class2file : # Scan devices folder to find files where device classes are defined.
70+ self .make_device_class2file_map ()
6771 try :
6872 super ().__init__ (self .serial_port , baudrate = 115200 )
6973 self .status ['serial' ] = True
@@ -106,6 +110,7 @@ def reset(self):
106110 try :
107111 self .exec ('from pyControl import *; import devices' )
108112 self .status ['framework' ] = True # Framework imported OK.
113+ self .device_files_on_pyboard = self .get_folder_contents ('devices' )
109114 except PyboardError as e :
110115 error_message = e .args [2 ].decode ()
111116 if (("ImportError: no module named 'pyControl'" in error_message ) or
@@ -221,25 +226,27 @@ def transfer_file(self, file_path, target_path=None):
221226
222227
223228 def transfer_folder (self , folder_path , target_folder = None , file_type = 'all' ,
224- show_progress = False ):
229+ files = 'all' , remove_files = True , show_progress = False ):
225230 '''Copy a folder into the root directory of the pyboard. Folders that
226231 contain subfolders will not be copied successfully. To copy only files of
227- a specific type, change the file_type argument to the file suffix (e.g. 'py').'''
232+ a specific type, change the file_type argument to the file suffix (e.g. 'py').
233+ To copy only specified files pass a list of file names as files argument.'''
228234 if not target_folder :
229235 target_folder = os .path .split (folder_path )[- 1 ]
230- files = os .listdir (folder_path )
231- if file_type != 'all' :
232- files = [f for f in files if f .split ('.' )[- 1 ] == file_type ]
236+ if files == 'all' :
237+ files = os .listdir (folder_path )
238+ if file_type != 'all' :
239+ files = [f for f in files if f .split ('.' )[- 1 ] == file_type ]
233240 try :
234241 self .exec ('os.mkdir({})' .format (repr (target_folder )))
235242 except PyboardError :
236- # Folder already exists, remove any files not in sending folder .
237- target_files = eval ( self . eval ( 'os.listdir({})' . format (
238- repr ( target_folder ))). decode () )
239- remove_files = list (set (target_files )- set (files ))
240- for f in remove_files :
241- target_path = target_folder + '/' + f
242- self .remove_file (target_path )
243+ # Folder already exists.
244+ if remove_files : # Remove any files not in sending folder.
245+ target_files = self . get_folder_contents ( target_folder )
246+ remove_files = list (set (target_files )- set (files ))
247+ for f in remove_files :
248+ target_path = target_folder + '/' + f
249+ self .remove_file (target_path )
243250 for f in files :
244251 file_path = os .path .join (folder_path , f )
245252 target_path = target_folder + '/' + f
@@ -249,7 +256,14 @@ def transfer_folder(self, folder_path, target_folder=None, file_type='all',
249256
250257 def remove_file (self , file_path ):
251258 '''Remove a file from the pyboard.'''
252- self .exec ('os.remove({})' .format (repr (file_path )))
259+ try :
260+ self .exec ('os.remove({})' .format (repr (file_path )))
261+ except PyboardError :
262+ pass # File does not exist.
263+
264+ def get_folder_contents (self , folder_path ):
265+ '''Get a list of the files in a folder on the pyboard.'''
266+ return eval (self .eval ('os.listdir({})' .format (repr (folder_path ))).decode ())
253267
254268 # ------------------------------------------------------------------------------------
255269 # pyControl operations.
@@ -259,7 +273,7 @@ def load_framework(self):
259273 '''Copy the pyControl framework folder to the board.'''
260274 self .print ('\n Transferring pyControl framework to pyboard.' , end = '' )
261275 self .transfer_folder (dirs ['framework' ], file_type = 'py' , show_progress = True )
262- self .transfer_folder (dirs ['devices' ] , file_type = ' py' , show_progress = True )
276+ self .transfer_folder (dirs ['devices' ], files = [ '__init__. py'], remove_files = False , show_progress = True )
263277 error_message = self .reset ()
264278 if not self .status ['framework' ]:
265279 self .print ('\n Error importing framework:' )
@@ -269,11 +283,14 @@ def load_framework(self):
269283 return
270284
271285 def load_hardware_definition (self , hwd_path = os .path .join (dirs ['config' ], 'hardware_definition.py' )):
272- '''Transfer a hardware definition file to pyboard. Defaults to transferring
273- file hardware_definition.py from config folder.'''
286+ '''Transfer a hardware definition file to pyboard. Device driver files needed for
287+ the hardware definition are transferred to the pyboard and any other device driver
288+ files on the pyboard are deleted.'''
274289 if os .path .exists (hwd_path ):
290+ self .make_device_class2file_map ()
291+ self .transfer_device_files (hwd_path , reset_folder = True )
275292 self .print ('\n Transferring hardware definition to pyboard.' , end = '' )
276- self .transfer_file (hwd_path , target_path = 'hardware_definition.py' )
293+ self .transfer_file (hwd_path , target_path = 'hardware_definition.py' )
277294 self .reset ()
278295 try :
279296 self .exec ('import hardware_definition' )
@@ -283,7 +300,51 @@ def load_hardware_definition(self, hwd_path=os.path.join(dirs['config'], 'hardwa
283300 self .print ('\n \n Error importing hardware definition:\n ' )
284301 self .print (error_message )
285302 else :
286- self .print ('Hardware definition file not found.' )
303+ self .print ('Hardware definition file not found.' )
304+
305+ def transfer_device_files (self , ref_file_path , reset_folder = False , return_filenames = False ):
306+ '''Transfer device driver files defining classes used in ref_file to the pyboard devices folder.
307+ If reset_folder=True then any files not used by ref_file are deleted from the pyboard filesystem,
308+ and any files that have changed on the computer are retransferred. If reset_folder=False then
309+ only files that are not already on the pyboard filesystem are transferred. If return_filenames=True
310+ then no files are transferred but a list of the filenames used in ref_file is returned.'''
311+ ref_file_name = os .path .split (ref_file_path )[- 1 ]
312+ with open (ref_file_path , 'r' ) as f :
313+ file_content = f .read ()
314+ device_files = [Pycboard .device_class2file [device_class ] for device_class in
315+ Pycboard .device_class2file .keys () if device_class in file_content
316+ and not ref_file_name == Pycboard .device_class2file [device_class ]]
317+ # Add any device driver files containing classes used in device_files.
318+ for device_file in device_files .copy ():
319+ device_files += self .transfer_device_files (os .path .join (dirs ['devices' ], device_file ), return_filenames = True )
320+ device_files = list (set (device_files )) # Remove duplicates.
321+ if return_filenames :
322+ return device_files
323+ else :
324+ if reset_folder :
325+ device_files += ['__init__.py' ]
326+ else :
327+ device_files = list (set (device_files )- set (self .device_files_on_pyboard ))
328+ if device_files :
329+ self .print (f'\n Transfering device driver files { device_files } to pyboard' , end = '' )
330+ self .transfer_folder (dirs ['devices' ], files = device_files , remove_files = reset_folder , show_progress = True )
331+ self .reset ()
332+ self .print (' OK' )
333+
334+
335+ def make_device_class2file_map (self ):
336+ '''Make dict mapping device class names to file in devices folder containing
337+ the class definition.'''
338+ Pycboard .device_class2file = {} # Dict {device_classname: device_filename}
339+ all_device_files = [f for f in os .listdir (dirs ['devices' ]) if f [- 3 :]== '.py' ]
340+ for device_file in all_device_files :
341+ with open (os .path .join (dirs ['devices' ],device_file ), 'r' ) as f :
342+ file_content = f .read ()
343+ pattern = "[\n \r ]class\s*(?P<dcname>\w+)\s*\("
344+ list (set ([d_name for d_name in re .findall (pattern , file_content )]))
345+ device_classes = list (set ([device_class for device_class in re .findall (pattern , file_content )]))
346+ for device_class in device_classes :
347+ Pycboard .device_class2file [device_class ] = device_file
287348
288349 def setup_state_machine (self , sm_name , sm_dir = None , uploaded = False ):
289350 '''Transfer state machine descriptor file sm_name.py from folder sm_dir
@@ -298,6 +359,7 @@ def setup_state_machine(self, sm_name, sm_dir=None, uploaded=False):
298359 if not os .path .exists (sm_path ):
299360 self .print ('Error: State machine file not found at: ' + sm_path )
300361 raise PyboardError ('State machine file not found at: ' + sm_path )
362+ self .transfer_device_files (sm_path )
301363 self .print ('\n Transferring state machine {} to pyboard. ' .format (sm_name ), end = '' )
302364 self .transfer_file (sm_path , 'task_file.py' )
303365 self .gc_collect ()
0 commit comments