Skip to content

Commit c141504

Browse files
committed
Implements functionallity in pycboard to automatically transfer device driver files used in tasks from the pyControl/devices folder on the computer to the devices folder on the pyboard. Previously a default set of device driver files located in pyControl/devices were transferred with the framework, and if the user wanted to use additional device driver files (located in pyControl/devices/more devices) they had to manually move these to pyControl/devices then reload the framework. Now all device driver files are located in the devices folder on the computer and are only transferred to the board when needed. This is both simpler for the user and saves RAM and filesystem space on the pyboard.
The current implementation has the following behaviour: - When a pycboard object is instantiated the device driver files in pyControl/devices are scanned to find all classes defined in them, and a dictionary is created mapping device classes to the files where they are defined. - When a task file is transferred to the pyboard, the file is scanned for any device classes used, and any correponding driver files that are not already on the pyboard are transferred. - When a hardware definition is loaded to the pyboard the devices folder on the pyboard is reset, by deleting any driver files that are not used by the hardware definition, and transfering any driver files that are used in the hardware definition and that are either not already on the pyboard or have changed on the computer. Device files that were in more devices have either been moved to devices or deleted if they were no longer used.
1 parent b67c7b5 commit c141504

14 files changed

Lines changed: 389 additions & 445 deletions

com/pycboard.py

Lines changed: 81 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
import sys
2+
import re
33
import time
44
import inspect
55
from 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('\nTransferring 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('\nError 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('\nTransferring 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\nError 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'\nTransfering 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('\nTransferring state machine {} to pyboard. '.format(sm_name), end='')
302364
self.transfer_file(sm_path, 'task_file.py')
303365
self.gc_collect()

0 commit comments

Comments
 (0)