Example #1
0
class stmACAgent:
    '''
    OCS agent class for stimulator AC source
    '''
    def __init__(self, agent, ipaddr=IPADDR_DEFAULT):
        '''
        Parameters
        ----------
        ipaddr : str
           IP address of AC supply
        '''
        self.active = True
        self.agent = agent
        self.log = agent.log
        self.lock = TimeoutLock()
        self.take_data = False
        self.initialized = False
        self._pcr = PCR500MA(ipaddr)

        agg_params = {'frame_length': 60}
        self.agent.register_feed('acsupply',
                                 record=True,
                                 agg_params=agg_params,
                                 buffer_time=1)

    def init_pcr500(self, session, params=None):
        '''Initialization of pcr500 AC supply
        '''
        if self.initialized:
            return True, "Already initialized."

        with self.lock.acquire_timeout(timeout=0, job='init') as acquired:
            if not acquired:
                self.log.warn(
                    "Could not start init because {} is already running".
                    format(self.lock.job))
                return False, "Could not acquire lock."
            try:
                self._pcr.checkID()
            except ValueError:
                pass
            print("AC supply PCR500 initialized.")

        self.initialized = True
        return True, "AC supply PCR500 initialized."

    def start_acq(self, session, params):
        '''Starts acquiring data.
        '''

        f_sample = params.get('sampling frequency', 0.1)
        sleep_time = 1 / f_sample - 0.1
        if not self.initialized:
            self.init_pcr500(session)

        #with self.lock.acquire_timeout(timeout=0, job='acq') as acquired:
        #    if not acquired:
        #        self.log.warn("Could not start acq because {} is already running".format(self.lock.job))
        #        return False, "Could not acquire lock."

        session.set_status('running')
        self.take_data = True
        session.data = {"fields": {}}
        while self.take_data:
            with self.lock.acquire_timeout(timeout=1, job='acq') as acquired:
                if not acquired:
                    print(
                        f"Lock could not be acquired because it is held by {self.lock.job}"
                    )
                    return False

                current_time = time.time()
                data = {
                    'timestamp': current_time,
                    'block_name': 'acsupply',
                    'data': {}
                }
                voltage = self._pcr.getVoltage()
                current = self._pcr.getCurrent()
                power = self._pcr.getPower()
                if not self.lock.release_and_acquire(timeout=10):
                    print(
                        f"Could not re-acquire lock now held by {self.lock.job}."
                    )
                    return False

            data['data']['voltage'] = voltage
            data['data']['current'] = current
            data['data']['power'] = power

            field_dict = {
                f'acsupply': {
                    'voltage': voltage,
                    'current': current,
                    'power': power
                }
            }
            session.data['fields'].update(field_dict)

            time.sleep(sleep_time)
            self.agent.publish_to_feed('acsupply', data)

            self.agent.feeds['acsupply'].flush_buffer()

        return True, 'Acquisition exited cleanly.'

    def stop_acq(self, session, params=None):
        """
        Stops the data acquisiton.
        """
        if self.take_data:
            self.take_data = False
            return True, 'requested to stop taking data.'

        return False, 'acq is not currently running.'

    def set_values(self, session, params=None):
        '''A task to set sensor parameters for AC supply

        volt : float
            operate AC voltage
        '''
        if params is None:
            params = {}

        with self.lock.acquire_timeout(3, job='set_values') as acquired:
            if not acquired:
                self.log.warn('Could not start set_values because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            volt = params.get('volt')
            if not volt is None:
                self.voltsetting = volt
            #    self._ble2.set_speed(speed)

    def get_values(self, session, params=None):
        '''A task to provide configuration information
        '''
        pass

    def switchPower(self, session, params=None, state=0):
        '''A task to turn switch, state 0 = off, 1 = on
        '''
        pass

    def get_settings(self, session, params=None):
        ''' Get relay states'''
        if params is None:
            params = {}

        with self.lock.acquire_timeout(3, job='get_settings') as acquired:
            if not acquired:
                self.log.warn('Could not start get_setting because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            setV = self.Voltage
            session.data = {'volt': setV}

        return True, f'Got AC status'

    def getACstatus(self, session, params=None):
        with self.lock.acquire_timeout(3, job='get_settings') as acquired:
            if not acquired:
                self.log.warn('Could not start get_setting because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            #print(self, session, params)
            volt = self._pcr._a('MEAS:VOLT:AC?')
            curr = self._pcr._a('MEAS:CURR:AC?')
            freq = self._pcr._a('MEAS:FREQ?')
            power = self._pcr._a('MEAS:POW:AC?')
            preac = self._pcr._a('MEAS:POW:AC:REAC?')
        print(volt, curr, freq, power, preac)
        return True, f'AC {volt}, {curr}, {freq}, {power}, {preac}'

    def rampVoltage(self, session, params=None):  # normal temperature control
        print(params)
        voltgoal = params.get('volt', 0)
        print(voltgoal)
        if (voltgoal < 0):
            print("Voltage cannot be negative!")
            return False, 'Voltage cannot be negative'

        while (abs(voltgoal - self._pcr.Voltage) > VoltStep):
            if (self._pcr.Voltage < voltgoal):
                self._pcr.Voltage = self._pcr.Voltage + VoltStep
            elif (self._pcr.Voltage > voltgoal):
                self._pcr.Voltage = self._pcr.Voltage - VoltStep

            with self.lock.acquire_timeout(timeout=3,
                                           job='set_voltage') as acquired:
                print("Set ", self._pcr.Voltage)
                self._pcr.setVoltage(self._pcr.Voltage)
                time.sleep(0.5)
                print(self._pcr.getCurrent())
            time.sleep(WaitTimeStep - 0.5)

        with self.lock.acquire_timeout(timeout=3,
                                       job='set_voltage') as acquired:
            print("last step to", voltgoal)
            self._pcr.Voltage = voltgoal
            print(self, self._pcr.getCurrent())
            self._pcr.setVoltage(self._pcr.Voltage)
            time.sleep(0.5)
            print(self._pcr.getCurrent())

        return True, f'Reached to voltage {voltgoal}'

    def forceZero(self, session, params=None):  #for site work
        while (self._pcr.Voltage > VoltStep):
            with self.lock.acquire_timeout(timeout=3,
                                           job='set_voltage') as acquired:
                self._pcr.Voltage = self._pcr.Voltage - VoltStep
                print("go down to ", self._pcr.Voltage)
                self._pcr.setVoltage(self._pcr.Voltage)
            time.sleep(WaitTimeForce)

        print("set to 0 Volt")
        with self.lock.acquire_timeout(timeout=3,
                                       job='set_voltage') as acquired:
            self._pcr.Voltage = 0.0
            self._pcr.setVoltage(0.0)

        return True, f'Ramped down to 0 volt.'
Example #2
0
class dS378Agent:
    '''OCS agent class for dS378 ethernet relay
    '''
    def __init__(self, agent, ip=IP_DEFAULT, port=17123):
        '''
        Parameters
        ----------
        ip : string
            IP address
        port : int
            Port number
        '''

        self.active = True
        self.agent = agent
        self.log = agent.log
        self.lock = TimeoutLock()
        self.take_data = False

        self._dev = dS378(ip=ip, port=port)

        self.initialized = False

        agg_params = {'frame_length': 60}
        self.agent.register_feed('relay',
                                 record=True,
                                 agg_params=agg_params,
                                 buffer_time=1)

    def start_acq(self, session, params):
        '''Starts acquiring data.
        '''
        if params is None:
            params = {}

        f_sample = params.get('sampling_frequency', 0.5)
        sleep_time = 1 / f_sample - 0.1

        with self.lock.acquire_timeout(timeout=0, job='acq') as acquired:
            if not acquired:
                self.log.warn(
                    f'Could not start acq because {self.lock.job} is already running'
                )
                return False, 'Could not acquire lock.'

            session.set_status('running')

            self.take_data = True
            session.data = {"fields": {}}
            last_release = time.time()

            while self.take_data:
                # Release lock
                if time.time() - last_release > LOCK_RELEASE_SEC:
                    last_release = time.time()
                    if not self.lock.release_and_acquire(
                            timeout=LOCK_RELEASE_TIMEOUT):
                        print(f'Re-acquire failed: {self.lock.job}')
                        return False, 'Could not re-acquire lock.'

                # Data acquisition
                current_time = time.time()
                data = {
                    'timestamp': current_time,
                    'block_name': 'relay',
                    'data': {}
                }

                d_status = self._dev.get_status()
                relay_list = self._dev.get_relays()
                data['data']['V_sppl'] = d_status['V_sppl']
                data['data']['T_int'] = d_status['T_int']
                for i in range(8):
                    data['data'][f'Relay_{i+1}'] = relay_list[i]

                field_dict = {
                    f'relay': {
                        'V_sppl': d_status['V_sppl'],
                        'T_int': d_status['T_int']
                    }
                }
                session.data['fields'].update(field_dict)

                self.agent.publish_to_feed('relay', data)
                session.data.update({'timestamp': current_time})

                time.sleep(sleep_time)

            self.agent.feeds['relay'].flush_buffer()

        return True, 'Acquisition exited cleanly.'

    def stop_acq(self, session, params=None):
        """
        Stops the data acquisiton.
        """
        if self.take_data:
            self.take_data = False
            return True, 'requested to stop taking data.'

        return False, 'acq is not currently running.'

    def set_relay(self, session, params=None):
        '''Turns the relay on/off or pulses it

        Parameters
        ----------
        relay_number : int
            relay_number, 1 -- 8
        on_off : int or RelayStatus
            1: on, 0: off
        pulse_time : int, 32 bit
            See document
        '''
        if params is None:
            params = {}

        with self.lock.acquire_timeout(3, job='set_values') as acquired:
            if not acquired:
                self.log.warn('Could not start set_values because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            if params.get('pulse_time') is None:
                params['pulse_time'] = 0

            self._dev.set_relay(relay_number=params['relay_number'],
                                on_off=params['on_off'],
                                pulse_time=params['pulse_time'])

        return True, f'Set values for BLE2'

    def get_relays(self, session, params=None):
        ''' Get relay states'''
        if params is None:
            params = {}

        with self.lock.acquire_timeout(3, job='get_relays') as acquired:
            if not acquired:
                self.log.warn('Could not start get_relays because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            d_status = self._dev.get_relays()
            session.data = {f'Relay_{i+1}': d_status[i] for i in range(8)}

        return True, f'Got relay status'
Example #3
0
class LATRtXYStageAgent:
    """
    Agent for connecting to the LATRt XY Stages
    
    Args: 
        ip_addr: IP address where RPi server is running
        port: Port the RPi Server is listening on
        mode: 'acq': Start data acquisition on initialize
        samp: default sampling frequency in Hz
    """
    def __init__(self, agent, ip_addr, port, mode=None, samp=2):

        self.ip_addr = ip_addr
        self.port = port

        self.xy_stage = None
        self.initialized = False
        self.take_data = False
        self.is_moving = False

        self.agent = agent
        self.log = agent.log
        self.lock = TimeoutLock()

        if mode == 'acq':
            self.auto_acq = True
        else:
            self.auto_acq = False
        self.sampling_frequency = float(samp)

        ### register the position feeds
        agg_params = {
            'frame_length': 10 * 60,  #[sec] 
        }

        self.agent.register_feed('positions',
                                 record=True,
                                 agg_params=agg_params,
                                 buffer_time=0)

    def init_xy_stage_task(self, session, params=None):
        """init_xy_stage_task(params=None)
        Perform first time setup for communivation with XY stages.

        Args:
            params (dict): Parameters dictionary for passing parameters to
                task.
        """

        if params is None:
            params = {}

        self.log.debug("Trying to acquire lock")
        with self.lock.acquire_timeout(timeout=0, job='init') as acquired:
            # Locking mechanism stops code from proceeding if no lock acquired
            if not acquired:
                self.log.warn(
                    "Could not start init because {} is already running".
                    format(self.lock.job))
                return False, "Could not acquire lock."
            # Run the function you want to run
            self.log.debug("Lock Acquired Connecting to Stages")

            self.xy_stage = XY_Stage(self.ip_addr, self.port)
            self.xy_stage.init_stages()
            print("XY Stages Initialized")

        # This part is for the record and to allow future calls to proceed,
        # so does not require the lock
        self.initialized = True
        if self.auto_acq:
            self.agent.start('acq')
        return True, 'XY Stages Initialized.'

    def move_x_cm(self, session, params):
        """
        params: 
            dict: { 'distance': float, 'velocity':float < 1.2}
        """

        with self.lock.acquire_timeout(timeout=3, job='move_x_cm') as acquired:
            if not acquired:
                self.log.warn(
                    f"Could not start x move because lock held by {self.lock.job}"
                )
                return False
            self.xy_stage.move_x_cm(params.get('distance', 0),
                                    params.get('velocity', 1))

        time.sleep(1)
        while True:
            ## data acquisition updates the moving field if it is running
            if not self.take_data:
                with self.lock.acquire_timeout(timeout=3,
                                               job='move_x_cm') as acquired:
                    if not acquired:
                        self.log.warn(
                            f"Could not check because lock held by {self.lock.job}"
                        )
                        return False, "Could not acquire lock"
                    self.is_moving = self.xy_stage.moving

            if not self.is_moving:
                break
        return True, "X Move Complete"

    def move_y_cm(self, session, params):
        """
        params: 
            dict: { 'distance': float, 'velocity':float < 1.2}
        """

        with self.lock.acquire_timeout(timeout=3, job='move_y_cm') as acquired:
            if not acquired:
                self.log.warn(
                    f"Could not start y move because lock held by {self.lock.job}"
                )
                return False, "could not acquire lock"
            self.xy_stage.move_y_cm(params.get('distance', 0),
                                    params.get('velocity', 1))

        time.sleep(1)
        while True:
            ## data acquisition updates the moving field if it is running
            if not self.take_data:
                with self.lock.acquire_timeout(timeout=3,
                                               job='move_y_cm') as acquired:
                    if not acquired:
                        self.log.warn(
                            f"Could not check for move because lock held by {self.lock.job}"
                        )
                        return False, "could not acquire lock"
                    self.is_moving = self.xy_stage.moving
            if not self.is_moving:
                break
        return True, "Y Move Complete"

    def set_position(self, session, params):
        """
        params: 
            dict: {'position': (float, float)}
        """
        with self.lock.acquire_timeout(timeout=3,
                                       job='set_position') as acquired:
            if not acquired:
                self.log.warn(
                    f"Could not set position because lock held by {self.lock.job}"
                )
                return False, "Could not acquire lock"

            self.xy_stage.position = params['position']
        return True, "Position Updated"

    def start_acq(self, session, params=None):
        """
        params: 
            dict: {'sampling_frequency': float, sampling rate in Hz}
        """
        if params is None:
            params = {}

        f_sample = params.get('sampling_frequency', self.sampling_frequency)
        pm = Pacemaker(f_sample, quantize=True)

        if not self.initialized or self.xy_stage is None:
            raise Exception("Connection to XY Stages not initialized")

        with self.lock.acquire_timeout(timeout=0, job='acq') as acquired:
            if not acquired:
                self.log.warn(
                    "Could not start acq because {} is already running".format(
                        self.lock.job))
                return False, "Could not acquire lock."

            self.log.info(
                f"Starting Data Acquisition for XY Stages at {f_sample} Hz")
            session.set_status('running')
            self.take_data = True
            last_release = time.time()

            while self.take_data:
                if time.time() - last_release > 1.:
                    if not self.lock.release_and_acquire(timeout=10):
                        self.log.warn(
                            f"Could not re-acquire lock now held by {self.lock.job}."
                        )
                        return False, "could not re-acquire lock"
                    last_release = time.time()
                pm.sleep()

                data = {
                    'timestamp': time.time(),
                    'block_name': 'positions',
                    'data': {}
                }
                pos = self.xy_stage.position
                self.is_moving = self.xy_stage.moving

                data['data']['x'] = pos[0]
                data['data']['y'] = pos[1]

                self.agent.publish_to_feed('positions', data)

        return True, 'Acquisition exited cleanly.'

    def stop_acq(self, session, params=None):
        """
        params: 
            dict: {}
        """
        if self.take_data:
            self.take_data = False
            return True, 'requested to stop taking data.'
        else:
            return False, 'acq is not currently running.'
Example #4
0
class FTSAerotechAgent:
    """
    Agent for connecting to the FTS mirror control

    Args:
        ip_addr: IP address of Motion Controller
        port: Port of Motion Controller
        mode: 'acq': Start data acquisition on initialize
        samp: default sampling frequency in Hz
 
    """

    def __init__(self, agent, ip_addr, port, mode=None, samp=2):

        self.ip_addr = ip_addr
        self.port = int(port)

        self.stage = None
        self.initialized = False
        self.take_data = False

        self.agent = agent
        self.log = agent.log
        self.lock = TimeoutLock()

        if mode == 'acq':
            self.auto_acq = True
        else:
            self.auto_acq = False
        self.sampling_frequency = float(samp)

        ### register the position feeds
        agg_params = {
            'frame_length' : 10*60, #[sec]
        }

        self.agent.register_feed('position',
                                 record = True,
                                 agg_params = agg_params,
                                 buffer_time = 0)

    def init_stage_task(self, session, params=None):
        """init_stage_task(params=None)
        Perform first time setup for communication with FTS stage.

        Args:
            params (dict): Parameters dictionary for passing parameters to
                task.
        """

        if params is None:
            params = {}
        
        if self.stage is not None and self.initialized:
            return True, 'Stages already Initialized'
           
        self.log.debug("Trying to acquire lock")
        with self.lock.acquire_timeout(timeout=0, job='init') as acquired:
            # Locking mechanism stops code from proceeding if no lock acquired
            if not acquired:
                self.log.warn("Could not start init because {} is already" \
                                "running".format(self.lock.job))
                return False, "Could not acquire lock."
            # Run the function you want to run
            self.log.debug("Lock Acquired Connecting to Stages")
            try:
                self.stage = FTSAerotechStage(self.ip_addr, self.port)
            except Exception as e:
                self.log.error(f"Error while connecting to FTS: {e}")
                reactor.callFromThread(reactor.stop)
                return False, "FTS Stage Initialization Failed"
        # This part is for the record and to allow future calls to proceed,
        # so does not require the lock
        self.initialized = True
        if self.auto_acq:
            self.agent.start('acq')
        return True, 'Stage Initialized.'

    def home_task(self, session, params=None):
        """ Home the stage to its negative limit
        """

        with self.lock.acquire_timeout(timeout=3, job='home') as acquired:
            if not acquired:
                self.log.warn("Could not start home because lock held by" \
                               f"{self.lock.job}")
                return False, "Could not get lock"
            try:
                self.stage.home()
            except Exception as e:
                self.log.error(f"Homing Failed: {e}")
                return False, "Homing Failed"
        return True, "Homing Complete"
    
    def move_to(self, session, params=None):
        """Move to absolute position relative to stage center (in mm)

        params: {'position':float between -74.8 and 74.8}
        """
        if params is None:
            return False, "No Position Given"
        if 'position' not in params:
            return False, "No Position Given"

        with self.lock.acquire_timeout(timeout=3, job='move') as acquired:
            if not acquired:
                self.log.warn("Could not start move because lock held by" \
                               f"{self.lock.job}")
                return False, "Could not get lock"
            return self.stage.move_to( params.get('position') )

        return False, "Move did not complete correctly?"
    
    def start_acq(self, session, params=None):
        """
        params:
            dict: {'sampling_frequency': float, sampling rate in Hz}

        The most recent position data is stored in session.data in the format::
            {"position":{"pos" : mirror position  }
        """
        if params is None:
            params = {}


        f_sample = params.get('sampling_frequency', self.sampling_frequency)
        pm = Pacemaker(f_sample, quantize=True)

        if not self.initialized or self.stage is None:
            raise Exception("Connection to Stages not initialized")

        with self.lock.acquire_timeout(timeout=0, job='acq') as acquired:
            if not acquired:
                self.log.warn(f"Could not start acq because {self.lock.job} " \
                            "is already running")
                return False, "Could not acquire lock."

            self.log.info("Starting Data Acquisition for FTS Mirror at" \
                           f"{f_sample} Hz")
            session.set_status('running')
            self.take_data = True
            last_release = time.time()

            while self.take_data:
                if time.time()-last_release > 1.:
                    if not self.lock.release_and_acquire(timeout=20):
                        self.log.warn("Could not re-acquire lock now held by" \
                                      f"{self.lock.job}.")
                        return False, "could not re-acquire lock"
                    last_release = time.time()
                pm.sleep()

                data = {
                    'timestamp':time.time(),
                    'block_name':'position',
                    'data':{}}
                success, pos = self.stage.get_position()
                if not success:
                    self.log.info("stage.get_position call failed")
                else:
                    data['data']['pos'] = pos
                    self.agent.publish_to_feed('position',data)

        return True, 'Acquisition exited cleanly.'

    def stop_acq(self, session, params=None):
        """
        params:
            dict: {}
        """
        if self.take_data:
            self.take_data = False
            return True, 'requested to stop taking data.'
        else:
            return False, 'acq is not currently running.'
Example #5
0
class BLE2Agent:
    '''OCS agent class for BLE2 motor driver
    '''
    def __init__(self, agent, port=PORT_DEFAULT):
        '''
        Parameters
        ----------
        port : string
            Port to connect
        '''
        self.active = True
        self.agent = agent
        self.log = agent.log
        self.lock = TimeoutLock()
        self.take_data = False

        self._ble2 = BLE2(port=port)

        self.initialized = False

        agg_params = {'frame_length': 60}
        self.agent.register_feed('motor',
                                 record=True,
                                 agg_params=agg_params,
                                 buffer_time=1)

    def init_ble2(self, session, params=None):
        '''Initialization of BLE2 motor driver'''
        if self.initialized:
            return True, 'Already initialized'

        with self.lock.acquire_timeout(0, job='init_ble2') as acquired:
            if not acquired:
                self.log.warn('Could not start init because '
                              '{} is already running'.format(self.lock.job))
                return False, 'Could not acquire lock.'

            session.set_status('starting')

            self._ble2.connect()
            session.add_message('BLE2 initialized.')

        self.initialized = True

        return True, 'BLE2 module initialized.'

    def start_acq(self, session, params):
        '''Starts acquiring data.
        '''
        if params is None:
            params = {}

        f_sample = params.get('sampling_frequency', 2.5)
        sleep_time = 1 / f_sample - 0.1

        if not self.initialized:
            self.agent.start('init_ble2')
            for _ in range(INIT_TIMEOUT):
                if self.initialized:
                    break
                time.sleep(0.1)

        if not self.initialized:
            return False, 'Could not initialize..'

        with self.lock.acquire_timeout(timeout=0, job='acq') as acquired:
            if not acquired:
                self.log.warn(
                    f'Could not start acq because {self.lock.job} is already running'
                )
                return False, 'Could not acquire lock.'

            session.set_status('running')

            self.take_data = True
            session.data = {"fields": {}}
            last_release = time.time()

            while self.take_data:
                # Release lock
                if time.time() - last_release > LOCK_RELEASE_SEC:
                    last_release = time.time()
                    if not self.lock.release_and_acquire(
                            timeout=LOCK_RELEASE_TIMEOUT):
                        print(f'Re-acquire failed: {self.lock.job}')
                        return False, 'Could not re-acquire lock.'

                # Data acquisition
                current_time = time.time()
                data = {
                    'timestamp': current_time,
                    'block_name': 'motor',
                    'data': {}
                }

                speed = self._ble2.get_status()
                data['data']['RPM'] = speed

                field_dict = {f'motor': {'RPM': speed}}
                session.data['fields'].update(field_dict)

                self.agent.publish_to_feed('motor', data)
                session.data.update({'timestamp': current_time})

                time.sleep(sleep_time)

            self.agent.feeds['motor'].flush_buffer()

        return True, 'Acquisition exited cleanly.'

    def stop_acq(self, session, params=None):
        """
        Stops the data acquisiton.
        """
        if self.take_data:
            self.take_data = False
            return True, 'requested to stop taking data.'

        return False, 'acq is not currently running.'

    def set_values(self, session, params=None):
        '''A task to set parameters for BLE2 motor driver

        Parameters
        ----------
        speed : int
            Motor rotation speed in RPM
        accl_time : float
            Acceleration time
        decl_time : float
            Deceleration time
        '''
        if params is None:
            params = {}

        with self.lock.acquire_timeout(3, job='set_values') as acquired:
            if not acquired:
                self.log.warn('Could not start set_values because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            speed = params.get('speed')
            if not speed is None:
                self._ble2.set_speed(speed)

            accl_time = params.get('accl_time')
            if not accl_time is None:
                self._ble2.set_accl_time(accl_time, accl=True)

            decl_time = params.get('decl_time')
            if not decl_time is None:
                self._ble2.set_accl_time(decl_time, accl=False)

        return True, f'Set values for BLE2'

    def start_rotation(self, session, params=None):
        '''Start rotation

        Parameters
        ----------
        forward : bool, default True
            Move forward if True
        '''
        if params is None:
            params = {}

        if not self.take_data:
            self.agent.start('acq')
            for _ in range(ACQ_TIMEOUT):
                if self.take_data:
                    break
                time.sleep(0.1)

        if not self.take_data:
            return False, 'Could not start acq.'

        with self.lock.acquire_timeout(3, job='set_values') as acquired:
            if not acquired:
                self.log.warn('Could not start set_values because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            if not self.take_data:
                return False, 'acq is not currently running.'

            forward = params.get('forward')
            if forward is None:
                forward = True

            self._ble2.start(forward=forward)

        return True, f'BLE2 rotation started.'

    def stop_rotation(self, session, params=None):
        '''Stop rotation'''
        if params is None:
            params = {}

        with self.lock.acquire_timeout(3, job='set_values') as acquired:
            if not acquired:
                self.log.warn('Could not start set_values because '
                              f'{self.lock.job} is already running')
                return False, 'Could not acquire lock.'

            self._ble2.stop()

        return True, f'BLE2 rotation stop command was published.'
Example #6
0
class RotationAgent:
    """Agent to control the rotation speed of the CHWP

    Args:
        kikusui_ip (str): IP address for the Kikusui power supply
        kikusui_port (str): Port for the Kikusui power supply
        pid_ip (str): IP address for the PID controller
        pid_port (str): Port for the PID controller
        pid_verbosity (str): Verbosity of PID controller output

    """
    def __init__(self, agent, kikusui_ip, kikusui_port, pid_ip, pid_port,
                 pid_verbosity):
        self.agent = agent
        self.log = agent.log
        self.lock = TimeoutLock()
        self._initialized = False
        self.take_data = False
        self.kikusui_ip = kikusui_ip
        self.kikusui_port = int(kikusui_port)
        self.pid_ip = pid_ip
        self.pid_port = pid_port
        self._pid_verbosity = pid_verbosity > 0
        self.cmd = None  # Command object for PSU commanding
        self.pid = None  # PID object for pid controller commanding

        agg_params = {'frame_length': 60}
        self.agent.register_feed('hwprotation',
                                 record=True,
                                 agg_params=agg_params)

    @ocs_agent.param('auto_acquire', default=False, type=bool)
    @ocs_agent.param('force', default=False, type=bool)
    def init_connection(self, session, params):
        """init_connection(auto_acquire=False, force=False)

        **Task** - Initialize connection to Kikusui Power Supply and PID
        Controller.

        Parameters:
            auto_acquire (bool, optional): Default is False. Starts data
                acquisition after initialization if True.
            force (bool, optional): Force initialization, even if already
                initialized. Defaults to False.

        """
        if self._initialized and not params['force']:
            self.log.info("Connection already initialized. Returning...")
            return True, "Connection already initialized"

        with self.lock.acquire_timeout(0, job='init_connection') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not run init_connection because {} is already running'
                    .format(self.lock.job))
                return False, 'Could not acquire lock'

            try:
                pmx = PMX(tcp_ip=self.kikusui_ip,
                          tcp_port=self.kikusui_port,
                          timeout=0.5)
                self.cmd = Command(pmx)
                self.log.info('Connected to Kikusui power supply')
            except ConnectionRefusedError:
                self.log.error(
                    'Could not establish connection to Kikusui power supply')
                reactor.callFromThread(reactor.stop)
                return False, 'Unable to connect to Kikusui PSU'

            try:
                self.pid = pd.PID(pid_ip=self.pid_ip,
                                  pid_port=self.pid_port,
                                  verb=self._pid_verbosity)
                self.log.info('Connected to PID controller')
            except BrokenPipeError:
                self.log.error(
                    'Could not establish connection to PID controller')
                reactor.callFromThread(reactor.stop)
                return False, 'Unable to connect to PID controller'

        self._initialized = True

        # Start 'iv_acq' Process if requested
        if params['auto_acquire']:
            self.agent.start('iv_acq')

        return True, 'Connection to PSU and PID controller established'

    def tune_stop(self, session, params):
        """tune_stop()

        **Task** - Reverse the drive direction of the PID controller and
        optimize the PID parameters for deceleration.

        """
        with self.lock.acquire_timeout(3, job='tune_stop') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not tune stop because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            self.pid.tune_stop()

        return True, 'Reversing Direction'

    def tune_freq(self, session, params):
        """tune_freq()

        **Task** - Tune the PID controller setpoint to the rotation frequency
        and optimize the PID parameters for rotation.

        """
        with self.lock.acquire_timeout(3, job='tune_freq') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not tune freq because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            self.pid.tune_freq()

        return True, 'Tuning to setpoint'

    @ocs_agent.param('freq', default=0., check=lambda x: 0. <= x <= 3.0)
    def declare_freq(self, session, params):
        """declare_freq(freq=0)

        **Task** - Store the entered frequency as the PID setpoint when
        ``tune_freq()`` is next called.

        Parameters:
            freq (float): Desired HWP rotation frequency

        """
        with self.lock.acquire_timeout(3, job='declare_freq') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not declare freq because {} is already running'.
                    format(self.lock.job))
                return False, 'Could not acquire lock'

            self.pid.declare_freq(params['freq'])

        return True, 'Setpoint at {} Hz'.format(params['freq'])

    @ocs_agent.param('p',
                     default=0.2,
                     type=float,
                     check=lambda x: 0. < x <= 8.)
    @ocs_agent.param('i', default=63, type=int, check=lambda x: 0 <= x <= 200)
    @ocs_agent.param('d',
                     default=0.,
                     type=float,
                     check=lambda x: 0. <= x < 10.)
    def set_pid(self, session, params):
        """set_pid(p=0.2, i=63, d=0.)

        **Task** - Set the PID parameters. Note these changes are for the
        current session only and will change whenever the agent container is
        reloaded.

        Parameters:
            p (float): Proportional PID value
            i (int): Integral PID value
            d (float): Derivative PID value

        """
        with self.lock.acquire_timeout(3, job='set_pid') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not set pid because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            self.pid.set_pid([params['p'], params['i'], params['d']])

        return True, f"Set PID params to p: {params['p']}, i: {params['i']}, d: {params['d']}"

    def get_freq(self, session, params):
        """get_freq()

        **Task** - Return the current HWP frequency as seen by the PID
        controller.

        """
        with self.lock.acquire_timeout(3, job='get_freq') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not get freq because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            freq = self.pid.get_freq()

        return True, 'Current frequency = {}'.format(freq)

    def get_direction(self, session, params):
        """get_direction()

        **Task** - Return the current HWP tune direction as seen by the PID
        controller.

        """
        with self.lock.acquire_timeout(3, job='get_direction') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not get freq because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            direction = self.pid.get_direction()

        return True, 'Current Direction = {}'.format(['Forward',
                                                      'Reverse'][direction])

    @ocs_agent.param('direction', type=str, default='0', choices=['0', '1'])
    def set_direction(self, session, params):
        """set_direction(direction='0')

        **Task** - Set the HWP rotation direction.

        Parameters:
            direction (str): '0' for forward and '1' for reverse.

        """
        with self.lock.acquire_timeout(3, job='set_direction') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not set direction because {} is already running'.
                    format(self.lock.job))
                return False, 'Could not acquire lock'

            self.pid.set_direction(params['direction'])

        return True, 'Set direction'

    @ocs_agent.param('slope',
                     default=1.,
                     type=float,
                     check=lambda x: -10. < x < 10.)
    @ocs_agent.param('offset',
                     default=0.1,
                     type=float,
                     check=lambda x: -10. < x < 10.)
    def set_scale(self, session, params):
        """set_scale(slope=1, offset=0.1)

        **Task** - Set the PID's internal conversion from input voltage to
        rotation frequency.

        Parameters:
            slope (float): Slope of the "rotation frequency vs input voltage"
                relationship
            offset (float): y-intercept of the "rotation frequency vs input
                voltage" relationship

        """
        with self.lock.acquire_timeout(3, job='set_scale') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not set scale because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            self.pid.set_scale(params['slope'], params['offset'])

        return True, 'Set scale'

    def set_on(self, session, params):
        """set_on()

        **Task** - Turn on the Kikusui drive voltage.

        """
        with self.lock.acquire_timeout(3, job='set_on') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not set on because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            time.sleep(1)
            self.cmd.user_input('on')

        return True, 'Set Kikusui on'

    def set_off(self, session, params):
        """set_off()

        **Task** - Turn off the Kikusui drive voltage.

        """
        with self.lock.acquire_timeout(3, job='set_off') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not set off because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            time.sleep(1)
            self.cmd.user_input('off')

        return True, 'Set Kikusui off'

    @ocs_agent.param('volt',
                     default=0,
                     type=float,
                     check=lambda x: 0 <= x <= 35)
    def set_v(self, session, params):
        """set_v(volt=0)

        **Task** - Set the Kikusui drive voltage.

        Parameters:
            volt (float): Kikusui set voltage

        """
        with self.lock.acquire_timeout(3, job='set_v') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not set v because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            time.sleep(1)
            self.cmd.user_input('V {}'.format(params['volt']))

        return True, 'Set Kikusui voltage to {} V'.format(params['volt'])

    @ocs_agent.param('volt',
                     default=32.,
                     type=float,
                     check=lambda x: 0. <= x <= 35.)
    def set_v_lim(self, session, params):
        """set_v_lim(volt=32)

        **Task** - Set the Kikusui drive voltage limit.

        Parameters:
            volt (float): Kikusui limit voltage

        """
        with self.lock.acquire_timeout(3, job='set_v_lim') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not set v lim because {} is already running'.format(
                        self.lock.job))
                return False, 'Could not acquire lock'

            time.sleep(1)
            print(params['volt'])
            self.cmd.user_input('VL {}'.format(params['volt']))

        return True, 'Set Kikusui voltage limit to {} V'.format(params['volt'])

    def use_ext(self, session, params):
        """use_ext()

        **Task** - Set the Kikusui to use an external voltage control. Doing so
        enables PID control.

        """
        with self.lock.acquire_timeout(3, job='use_ext') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not use external voltage because {} is already running'
                    .format(self.lock.job))
                return False, 'Could not acquire lock'

            time.sleep(1)
            self.cmd.user_input('U')

        return True, 'Set Kikusui voltage to PID control'

    def ign_ext(self, session, params):
        """ign_ext()

        **Task** - Set the Kiksui to ignore external voltage control. Doing so
        disables the PID and switches to direct control.

        """
        with self.lock.acquire_timeout(3, job='ign_ext') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not ignore external voltage because {} is already running'
                    .format(self.lock.job))
                return False, 'Could not acquire lock'

            time.sleep(1)
            self.cmd.user_input('I')

        return True, 'Set Kikusui voltage to direct control'

    @ocs_agent.param('test_mode', default=False, type=bool)
    def iv_acq(self, session, params):
        """iv_acq(test_mode=False)

        **Process** - Start Kikusui data acquisition.

        Parameters:
            test_mode (bool, optional): Run the Process loop only once.
                This is meant only for testing. Default is False.

        Notes:
            The most recent data collected is stored in the session data in the
            structure::

                >>> response.session['data']
                {'kikusui_volt': 0,
                 'kikusui_curr': 0,
                 'last_updated': 1649085992.719602}

        """
        with self.lock.acquire_timeout(timeout=0, job='iv_acq') as acquired:
            if not acquired:
                self.log.warn(
                    'Could not start iv acq because {} is already running'.
                    format(self.lock.job))
                return False, 'Could not acquire lock'

            session.set_status('running')
            last_release = time.time()
            self.take_data = True

            while self.take_data:
                # Relinquish sampling lock occasionally.
                if time.time() - last_release > 1.:
                    last_release = time.time()
                    if not self.lock.release_and_acquire(timeout=10):
                        self.log.warn(f"Failed to re-acquire sampling lock, "
                                      f"currently held by {self.lock.job}.")
                        continue

                data = {
                    'timestamp': time.time(),
                    'block_name': 'HWPKikusui_IV',
                    'data': {}
                }

                v_msg, v_val = self.cmd.user_input('V?')
                i_msg, i_val = self.cmd.user_input('C?')

                data['data']['kikusui_volt'] = v_val
                data['data']['kikusui_curr'] = i_val

                self.agent.publish_to_feed('hwprotation', data)

                session.data = {
                    'kikusui_volt': v_val,
                    'kikusui_curr': i_val,
                    'last_updated': time.time()
                }

                time.sleep(1)

                if params['test_mode']:
                    break

        self.agent.feeds['hwprotation'].flush_buffer()
        return True, 'Acqusition exited cleanly'

    def _stop_iv_acq(self, session, params):
        """
        Stop iv_acq process.
        """
        if self.take_data:
            self.take_data = False
            return True, 'requested to stop taking data'

        return False, 'acq is not currently running'