Live data acquisition over SECoP

This commit is contained in:
2025-07-30 13:56:25 +02:00
parent e77c48ace0
commit 7bfc6b3cb8
3 changed files with 48 additions and 27 deletions

View File

@@ -27,7 +27,7 @@ import time
import os import os
import traceback import traceback
class ProgrammedSequence(fc.Readable): class ProgrammedSequence(fc.Drivable): # Drivable only for kill() funcitonality
"""An NMR device being driven by an instance of TNMR. Requires that an instance of TNMR is opened before creation. """An NMR device being driven by an instance of TNMR. Requires that an instance of TNMR is opened before creation.
Use Use
@@ -72,7 +72,12 @@ class ProgrammedSequence(fc.Readable):
imags=fc.ArrayOf(fc.FloatRange(), maxlen=4096), # imag values imags=fc.ArrayOf(fc.FloatRange(), maxlen=4096), # imag values
t =fc.ArrayOf(fc.FloatRange(), maxlen=4096)), # times (starting from zero) t =fc.ArrayOf(fc.FloatRange(), maxlen=4096)), # times (starting from zero)
default={ 'reals': [], 'imags': [], 't': [] }) default={ 'reals': [], 'imags': [], 't': [] })
status = fc.Parameter(datatype=frappy.datatypes.StatusType(fc.Readable, "DISABLED", 'PREPARED', 'BUSY'), default=('IDLE', 'ok - uncompiled')) target = fc.Parameter('dummy', fc.StructOf(reals=fc.ArrayOf(fc.FloatRange(), maxlen=4096), # real values
imags=fc.ArrayOf(fc.FloatRange(), maxlen=4096), # imag values
t =fc.ArrayOf(fc.FloatRange(), maxlen=4096)), # times (starting from zero)
default={ 'reals': [], 'imags': [], 't': [] }, readonly=True, visibility='w--')
status = fc.Parameter(datatype=frappy.datatypes.StatusType(fc.Drivable, "DISABLED", 'PREPARED', 'BUSY'), default=('IDLE', 'ok - uncompiled'))
pollinterval = fc.Parameter(default=1) pollinterval = fc.Parameter(default=1)
# basic # basic
@@ -99,6 +104,7 @@ class ProgrammedSequence(fc.Readable):
compiled_parameters = {} # so that we can store the values of parameters only when compiling, effectively giving us an instance of each parameter loaded into TNMR, as well as "targets" (those above) compiled_parameters = {} # so that we can store the values of parameters only when compiling, effectively giving us an instance of each parameter loaded into TNMR, as well as "targets" (those above)
inited = False inited = False
starting = False
approx_sequence_length = 0 approx_sequence_length = 0
### SETUP ### SETUP
@@ -150,6 +156,20 @@ class ProgrammedSequence(fc.Readable):
### READ/WRITE ### READ/WRITE
def read_status(self):
if not(self.inited):
self.status = ('ERROR', 'TNMR disconnected!')
else:
if(self.starting):
self.status = ('BUSY', 'starting')
else:
if(self.tnmr().acquisition_running()):
self.status = ('BUSY', 'acquiring')
elif(self.status[1] == 'acquiring'):
# we've just finished acquiring, in frappy's perspective
self.status = ('PREPARED', 'compiled')
return self.status
def write_title(self, t): def write_title(self, t):
self.title = t self.title = t
self.status = ('IDLE', 'ok - uncompiled') self.status = ('IDLE', 'ok - uncompiled')
@@ -196,7 +216,7 @@ class ProgrammedSequence(fc.Readable):
return self.read_acq_phase_cycle() return self.read_acq_phase_cycle()
def read_num_acqs(self): def read_num_acqs(self):
return self.tnmr().get_nmrparameter('Scans 1D') return int(self.tnmr().get_nmrparameter('Scans 1D'))
def write_num_acqs(self, t): def write_num_acqs(self, t):
if(self.status[0] != 'BUSY'): if(self.status[0] != 'BUSY'):
@@ -223,31 +243,23 @@ class ProgrammedSequence(fc.Readable):
return self.read_sequence_data() return self.read_sequence_data()
def read_status(self):
if(self.tnmr().acquisition_running()):
self.status = ('BUSY', 'acquiring')
elif(self.status[1] == 'acquiring'):
# we've just finished acquiring, in frappy's perspective
self.status = ('PREPARED', 'compiled')
return self.status
def read_value(self): def read_value(self):
newvals = {} newvals = {}
try: #try:
d = self.tnmr().get_data() d = self.tnmr().get_data()
newvals['reals'] = d[0] newvals['reals'] = d[0]
newvals['imags'] = d[1] newvals['imags'] = d[1]
newvals['t'] = [ self.compiled_parameters['acquisition_time'] * i/len(d[0]) for i in range(0, len(d[0])) ] newvals['t'] = [ self.compiled_parameters['acquisition_time'] * i/len(d[0]) for i in range(0, len(d[0])) ]
except: #except:
newvals['reals'] = [] # newvals['reals'] = []
newvals['imags'] = [] # newvals['imags'] = []
newvals['t'] = [] # newvals['t'] = []
return newvals return newvals
def read_num_acqs_actual(self): def read_num_acqs_actual(self):
try: try:
n = self.tnmr().get_nmrparameter('Actual Scans 1D') n = self.tnmr().get_nmrparameter('Actual Scans 1D')
return n return int(n)
except: except:
return 0 return 0
@@ -330,9 +342,8 @@ class ProgrammedSequence(fc.Readable):
def __zero_go(self): def __zero_go(self):
'''Tells TNMR to acquire data. Only call after __compile_sequence().''' '''Tells TNMR to acquire data. Only call after __compile_sequence().'''
if(self.status[0] != 'BUSY'): if(self.status[0] != 'BUSY' or self.starting):
self.status = ('BUSY', 'acquiring') self.tnmr().ZeroGo(lock=False, check_time=max(int(self.approx_sequence_length*1.5), 5))
self.tnmr().ZeroGo(lock=False, check_time=max(self.approx_sequence_length*5, 5))
def __compile_and_run(self, thread=True): def __compile_and_run(self, thread=True):
'''Compiles and runs the currently-loaded sequence '''Compiles and runs the currently-loaded sequence
@@ -341,9 +352,11 @@ class ProgrammedSequence(fc.Readable):
---------- ----------
thread: bool, determines if this should open a child thread and detach the process thread: bool, determines if this should open a child thread and detach the process
''' '''
self.starting = True
self.__compile_sequence() self.__compile_sequence()
time.sleep(1.0) time.sleep(1.0)
self.__zero_go() self.__zero_go()
self.starting = False

View File

@@ -41,6 +41,9 @@ def get_single_pulse_block(name, pulse_width, pulse_height, delay_time, phase_cy
delay_time = float(delay_time.strip()[:-1]) / 1e6 delay_time = float(delay_time.strip()[:-1]) / 1e6
else: else:
delay_time = float(delay_time.strip()) # assume in us delay_time = float(delay_time.strip()) # assume in us
delay_time_rounded = int(delay_time*10) / 10 # nearest 10ns
delay_time = delay_time_rounded
ph = name + '_phase' ph = name + '_phase'
rl = name + '_delay' rl = name + '_delay'

View File

@@ -155,8 +155,8 @@ class TNMR:
print('Zero-going...') print('Zero-going...')
ntnmr = self.get_instance() ntnmr = self.get_instance()
if not(self.acquisition_running()): if not(self.acquisition_running()):
print('Reset') #print('Reset')
ntnmr.Reset() # to avoid hardware issues? EDIT: Doesn't seem to do much... #ntnmr.Reset() # to avoid hardware issues? EDIT: Doesn't seem to do much...
if(CHECK_MODE == 'data'): if(CHECK_MODE == 'data'):
print('Artificially setting the zeroth point to NULL for error detection.') print('Artificially setting the zeroth point to NULL for error detection.')
ntnmr.SetDataPoint(1, [0,0]) ntnmr.SetDataPoint(1, [0,0])
@@ -258,7 +258,7 @@ class TNMR:
""" """
print('I: Saving') print('I: Saving')
if filepath == '': if filepath == '':
self.get_instance().Save self.get_instance().Save()
else: else:
self.get_instance().SaveAs(filepath) self.get_instance().SaveAs(filepath)
print(f'I: Saved to file {filepath}') print(f'I: Saved to file {filepath}')
@@ -378,6 +378,11 @@ class TNMR:
""" """
ntnmr = self.get_instance() ntnmr = self.get_instance()
if (self.acquisition_running()):
ntnmr.Abort()
print('W: Aborting currently running acquisition!')
print(f'Loading sequence at {filename}') print(f'Loading sequence at {filename}')
ntnmr.CloseActiveFile() ntnmr.CloseActiveFile()
success = ntnmr.OpenFile(TEMPLATE_FILE_PATH + 'tmp.tnt') success = ntnmr.OpenFile(TEMPLATE_FILE_PATH + 'tmp.tnt')