"""OCS agent module to read the data from beagleboneblack for CHWP encoder

Note
----
   This is confirmed to be work with the following versions of beagleboneblack software:
   - currently hwpdaq branch in spt3g_software_sa repository: 731ff39
   (sha256sum)
   - Encoder1.bin: c8281525bdd0efae66aede7cffc3520ab719cfd67f6c2d7fd01509a4289a9d32
   - Encoder2.bin: a6ed9d89e9cf26036bf1da9e7e2098da85bbfa6eb08d0caef1e3c40877dd5077
   - IRIG1.bin: 7bc37b30a1759eb792f0db176bcd6080f9c3c7ec78ba2e1614166b2031416091
   - IRIG2.bin: d206dd075f73c32684d8319c9ed19f019cc705a9f253725de085eb511b8c0a12

Data feeds
----------
HWPEncoder:
   (HWPEncoder_counter_sub)
   counter_sub: subsampled counter values [::NUM_SUBSAMPLE]
   counter_index_sub: subsampled index counter values

   (HWPEncoder_freq)
   approx_hwp_freq: approximate estimate of hwp rotation frequency
   diff_counter_mean: mean of diff(counter)
   diff_index_mean: mean of diff(counter_index)
   diff_counter_std: std of diff(counter)
   diff_index_std: std of diff(counter_index)

   (HWPEncoder_quad)
   quad: quadrature data

   (HWPEncoder_irig)
   irig_time: decoded time in second since the unix epoch
   rising_edge_cont: BBB clcok count values
                     for the IRIG on-time reference marker risinge edge
   irig_sec: seconds decoded from IRIG-B
   irig_min: minutes decoded from IRIG-B
   irig_hour: hours decoded from IRIG-B
   irig_day: days decoded from IRIG-B
   irig_year: years decoded from IRIG-B
   bbb_clock_freq: BBB clock frequency estimate using IRIG-B

   (HWPEncoder_irig_raw)
   irig_synch_pulse_clock_time: reference marker time in sec
   irig_synch_pulse_clock_counts: clock counts for reference markers
   irig_info: IRIG bit info

HWPEncoder_full: separated feed for full-sample HWP encoder data,
                 not to be included in influxdb database
   (HWPEncoder_counter)
   counter: BBB counter values for encoder signal edges
   counter_index: index numbers for detected edges by BBB
"""

import argparse
import calendar
import select
import socket
import struct
import time
from collections import deque

import numpy as np
import txaio

txaio.use_twisted()

from ocs import ocs_agent, site_config
from ocs.ocs_twisted import TimeoutLock

# These three values (COUNTER_INFO_LENGTH, COUNTER_PACKET_SIZE, IRIG_PACKET_SIZE)
# should be consistent with the software on beaglebone.
# The number of datapoints in every encoder packet from the Beaglebone
COUNTER_INFO_LENGTH = 120
# The size of the encoder packet from the beaglebone
#    (header + 3*COUNTER_INFO_LENGTH datapoint information + 1 quadrature readout)
COUNTER_PACKET_SIZE = 4 + 4 * COUNTER_INFO_LENGTH + 8 * COUNTER_INFO_LENGTH + 4
# The size of the IRIG packet from the Beaglebone
IRIG_PACKET_SIZE = 132

# The slit scaler value for rough HWP rotating frequency
NUM_SLITS = 570
# Number of encoder counter samples to publish at once
NUM_ENCODER_TO_PUBLISH = 4200
# Seconds to publish encoder data even before reaching NUM_ENCODER_TO_PUBLISH
SEC_ENCODER_TO_PUBLISH = 10
# Subsampling facot for the encoder counter data to influxdb
NUM_SUBSAMPLE = 500


def de_irig(val, base_shift=0):
    """Converts the IRIG signal into sec/min/hours/day/year depending on the parameters

    Parameters
    ----------
    val : int
       raw IRIG bit info of each 100msec chunk
    base_shift : int, optional
       number of bit shifts. This should be 0 except for seccods

    Returns
    -------
    int
       Either of sec/min/hourds/day/year

    """
    return (((val >> (0 + base_shift)) & 1)
            + ((val >> (1 + base_shift)) & 1) * 2
            + ((val >> (2 + base_shift)) & 1) * 4
            + ((val >> (3 + base_shift)) & 1) * 8
            + ((val >> (5 + base_shift)) & 1) * 10
            + ((val >> (6 + base_shift)) & 1) * 20
            + ((val >> (7 + base_shift)) & 1) * 40
            + ((val >> (8 + base_shift)) & 1) * 80)


def count2time(counts, t_offset=0.):
    """Quick etimation of time using Beagleboneblack clock counts

    Parameters
    ----------
    counts : list of int
       Beagleboneblack clock counter value
    t_offset : int, optional
       time offset in seconds

    Returns
    -------
    list of float
       Estimated time in seconds assuming the Beagleboneblack clock frequency is 200 MHz.
       Without specifying t_offset, output is just the difference
       from the first sample in the input list

    """
    t_array = np.array(counts, dtype=float) - counts[0]
    # Assuming BBB clock is 200MHz
    t_array *= 5.e-9
    t_array += t_offset

    return t_array.tolist()


class EncoderParser:
    """Class which will parse the incoming packets from the BeagleboneBlack and store the data

    Attributes
    ----------
    counter_queue : deque object
       deque to store the encoder counter data
    irig_queue : deque object
       deque to store the IRIG data
    is_start : int
       Used for procedures that only run when data collection begins
       Initialized to be 1, until the first IRIG parsing happens and set to 0
    start_time : list of int
       Will hold the time at which data collection started [hours, mins, secs]
    current_time : int
       Current unix timestamp in seconds parased from IRIG
    sock : scoket.sock
       a UDP socket to connect to the Beagleboneblack
    data : str
       String which will hold the raw data from the Beaglebone before it is parsed
    read_chunk_size : int
       Maximum data size to receive UDP packets in bytes

   Parameters
    ----------
    beaglebone_port : int, optional
       Port number to receive UDP packets from Beagleboneblack
       This must be the same as the localPort in the Beaglebone code
    read_chunk_size : int, optional
       Maximum data size to receive UDP packets in bytes
       read_chunk_size: This value shouldn't need to change

    """

    def __init__(self, beaglebone_port=8080, read_chunk_size=8196):
        # Creates twoe queues to hold the data from the encoder, IRIG, and quadrature respectively
        self.counter_queue = deque()
        self.irig_queue = deque()

        # Used for procedures that only run when data collection begins
        self.is_start = 1
        # Will hold the time at which data collection started [hours, mins, secs]
        self.start_time = [0, 0, 0]
        # Will be continually updated with unix in seconds
        self.current_time = 0

        # Creates a UDP socket to connect to the Beaglebone
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # Binds the socket to a specific ip address and port
        # The ip address can be blank for accepting any UDP packet to the port
        self.sock.bind(('', beaglebone_port))
        # self.sock.setblocking(0)

        # String which will hold the raw data from the Beaglebone before it is parsed
        self.data = ''
        self.read_chunk_size = read_chunk_size

        self.log = txaio.make_logger()

    def pretty_print_irig_info(self, irig_info, edge, print_out=False):
        """Takes the IRIG information, prints it to the screen, sets the current time,
        and returns the current time

        Parameters
        ----------
        irig_info : list of int
           IRIG bit info
        edge : int
           Clock count of rising edge of a reference marker bit
        print_out : bool, optional
           Set True to print out the parsed timestamp

        Returns
        -------
        current_time : int
           Current unix timestamp in seconds parased from IRIG

        """
        # Calls self.de_irig() to get the sec/min/hour of the IRIG packet
        secs = de_irig(irig_info[0], 1)
        mins = de_irig(irig_info[1], 0)
        hours = de_irig(irig_info[2], 0)
        day = de_irig(irig_info[3], 0) \
            + de_irig(irig_info[4], 0) * 100
        year = de_irig(irig_info[5], 0)

        # If it is the first time that the function is called then set self.start_time
        # to the current time
        if self.is_start == 1:
            self.start_time = [hours, mins, secs]
            self.is_start = 0

        if print_out:
            # Find the sec/min/hour digit difference from the start time
            dsecs = secs - self.start_time[2]
            dmins = mins - self.start_time[1]
            dhours = hours - self.start_time[0]

            # Corrections to make sure that dsecs/dmins/dhours are all positive
            if dhours < 0:
                dhours = dhours + 24

            if (dmins < 0) or ((dmins == 0) and (dsecs < 0)):
                dmins = dmins + 60
                dhours = dhours - 1

            if dsecs < 0:
                dsecs = dsecs + 60
                dmins = dmins - 1

            # Print UTC time, run time, and current clock count of the beaglebone
            print('Current Time:', ('%d:%d:%d' % (hours, mins, secs)),
                  'Run Time', ('%d:%d:%d' % (dhours, dmins, dsecs)),
                  'Clock Count', edge)

        # Set the current time in seconds (changed to seconds from unix epoch)
        # self.current_time = secs + mins*60 + hours*3600
        try:
            st_time = time.strptime("%d %d %d:%d:%d" % (year, day, hours, mins, secs),
                                    "%y %j %H:%M:%S")
            self.current_time = calendar.timegm(st_time)
        except ValueError:
            self.log.error(f'Invalid IRIG-B timestamp: {year} {day} {hours} {mins} {secs}')
            self.current_time = -1

        return self.current_time

    def check_data_length(self, start_index, size_of_read):
        """Checks to make sure that self.data is the right size
        Return false if the wrong size, return true if the data is the right size

        Parameters
        ----------
        start_index : int
           first index of the data to read
        size_of_read : int
           data size to read in bytes

        Returns
        -------
        bool
           False if the current data size is smaller than the data size suggested by the header info

        """
        if start_index + size_of_read > len(self.data):
            self.data = self.data[start_index:]
            return False

        return True

    def grab_and_parse_data(self):
        """Grabs self.data, determine what packet it corresponds to, parses the data.
        This is a while loop to look for an appropriate header in a packet from beaglebone.
        Then, the data will be passed to an appropriate parsing method
        and stored in either of counter_queue or irig_queue.
        The detailed structure of the queues can be found in parse_counter_info/parse_irig_info.

        If unexpected data length found, this will output some messages:
           Error 0: data length is shorter than the header size (4 bytes)
           Error 1: data length is shorter than the encoder counter info
                    even though the encoder packet header is found.
           Error 2: data length is shorter than the IRIG info
                    even though the IRIG packet header is found.
        """
        while True:
            # If there is data from the socket attached to the beaglebone then
            #     ready[0] = true
            # If not then continue checking for 2 seconds and if there is still no data
            #     ready[0] = false
            ready = select.select([self.sock], [], [], 2)
            if ready[0]:
                # Add the data from the socket attached to the beaglebone
                # to the self.data string
                data = self.sock.recv(self.read_chunk_size)
                if len(self.data) > 0:
                    self.data += data
                else:
                    self.data = data

                while True:
                    # Check to make sure that there is at least 1 int in the packet
                    # The first int in every packet should be the header
                    if not self.check_data_length(0, 4):
                        self.log.error('Error 0')
                        break

                    header = self.data[0:4]
                    # Convert a structure value from the beaglebone (header) to an int
                    header = struct.unpack('<I', header)[0]
                    # print('header ', '0x%x'%header)

                    # 0x1EAF = Encoder Packet
                    # 0xCAFE = IRIG Packet
                    # 0xE12A = Error Packet

                    # Encoder
                    if header == 0x1eaf:
                        # Make sure the data is the correct length for an Encoder Packet
                        if not self.check_data_length(0, COUNTER_PACKET_SIZE):
                            self.log.error('Error 1')
                            break
                        # Call the meathod self.parse_counter_info() to parse the Encoder Packet
                        self.parse_counter_info(self.data[4: COUNTER_PACKET_SIZE])
                        if len(self.data) >= COUNTER_PACKET_SIZE:
                            self.data = self.data[COUNTER_PACKET_SIZE:]

                    # IRIG
                    elif header == 0xcafe:
                        # Make sure the data is the correct length for an IRIG Packet
                        if not self.check_data_length(0, IRIG_PACKET_SIZE):
                            self.log.error('Error 2')
                            break
                        # Call the meathod self.parse_irig_info() to parse the IRIG Packet
                        self.parse_irig_info(self.data[4: IRIG_PACKET_SIZE])
                        if len(self.data) >= IRIG_PACKET_SIZE:
                            self.data = self.data[IRIG_PACKET_SIZE:]

                    # Error
                    # An Error Packet will be sent if there is a timing error in the
                    # synchronization pulses of the IRIG packet
                    # If you see 'Packet Error' check to make sure the IRIG is functioning as
                    # intended and that all the connections are made correctly
                    elif header == 0xe12a:
                        self.log.error('Packet Error')
                        # Clear self.data
                        self.data = ''
                    elif header == 0x1234:
                        self.log.error('Received timeout packet.')
                        # Clear self.data
                        self.data = ''
                    else:
                        self.log.error('Bad header')
                        # Clear self.data
                        self.data = ''

                    if len(self.data) == 0:
                        break
                break

            # If there is no data from the beaglebone 'Looking for data ...' will print
            # If you see this make sure that the beaglebone has been set up properly
            # print('Looking for data ...')

    def parse_counter_info(self, data):
        """Method to parse the Encoder Packet and put them to counter_queue

        Parameters
        ----------
        data : str
           string for the encoder ounter info

        Note:
           'data' structure:
           (Please note that '150' below might be replaced by COUNTER_INFO_LENGTH)
           [0] Readout from the quadrature
           [1-150] clock counts of 150 data points
           [151-300] corresponding clock overflow of the 150 data points (each overflow count
           is equal to 2^16 clock counts)
           [301-450] corresponding absolute number of the 150 data points ((1, 2, 3, etc ...)
           or (150, 151, 152, etc ...) or (301, 302, 303, etc ...) etc ...)

           counter_queue structure:
           counter_queue = [[64 bit clock counts],
                            [clock count indicese incremented by every edge],
                            quadrature,
                            current system time]
        """

        # Convert the Encoder Packet structure into a numpy array
        derter = np.array(struct.unpack('<' + 'I' + 'III' * COUNTER_INFO_LENGTH, data))

        # self.quad_queue.append(derter[0].item()) # merged to counter_queue
        self.counter_queue.append((derter[1:COUNTER_INFO_LENGTH + 1]
                                   + (derter[COUNTER_INFO_LENGTH + 1:2 * COUNTER_INFO_LENGTH + 1] << 32),
                                   derter[2 * COUNTER_INFO_LENGTH + 1:3 * COUNTER_INFO_LENGTH + 1],
                                   derter[0].item(), time.time()))

    def parse_irig_info(self, data):
        """Method to parse the IRIG Packet and put them to the irig_queue

        Parameters
        ----------
        data : str
           string for the IRIG info

        Note
        ----
           'data' structure:
           [0] clock count of the IRIG Packet which the UTC time corresponds to
           [1] overflow count of initial rising edge
           [2] binary encoding of the second data
           [3] binary encoding of the minute data
           [4] binary encoding of the hour data
           [5-11] additional IRIG information which we do mot use
           [12-21] synchronization pulse clock counts
           [22-31] overflow count at each synchronization pulse

           irig_queue structure:
           irig_queue = [Packet clock count,
                         Packet UTC time in sec,
                         [binary encoded IRIG data],
                         [synch pulses clock counts],
                         current system time]

        """

        # Convert the IRIG Packet structure into a numpy array
        unpacked_data = struct.unpack('<L' + 'L' + 'L' * 10 + 'L' * 10 + 'L' * 10, data)

        # Start of the packet clock count
        # overflow.append(unpacked_data[1])
        # print "overflow: ", overflow

        rising_edge_time = unpacked_data[0] + (unpacked_data[1] << 32)

        # Stores IRIG time data
        irig_info = unpacked_data[2:12]

        # Prints the time information and returns the current time in seconds
        irig_time = self.pretty_print_irig_info(irig_info, rising_edge_time)

        # Stores synch pulse clock counts accounting for overflow of 32 bit counter
        synch_pulse_clock_times = (np.asarray(unpacked_data[12:22])
                                   + (np.asarray(unpacked_data[22:32]) << 32)).tolist()

        # self.irig_queue = [Packet clock count,Packet UTC time in sec,
        #                    [binary encoded IRIG data],[synch pulses clock counts],
        #                    [current system time]]
        self.irig_queue.append((rising_edge_time, irig_time, irig_info,
                                synch_pulse_clock_times, time.time()))

    def __del__(self):
        self.sock.close()


class HWPBBBAgent:
    """OCS agent for HWP encoder DAQ using Beaglebone Black

    Attributes
    ----------
    rising_edge_count : int
       clock count values for the rising edge of IRIG reference marker,
       saved for calculating the beaglebone clock frequency
    irig_time : int
       unix timestamp from IRIG

    """

    def __init__(self, agent_obj, port=8080):
        self.active = True
        self.agent = agent_obj
        self.log = agent_obj.log
        self.lock = TimeoutLock()
        self.port = port
        self.take_data = False
        self.initialized = False
        # For clock count to time conversion
        self.rising_edge_count = 0
        self.irig_time = 0

        agg_params = {'frame_length': 60}
        self.agent.register_feed('HWPEncoder', record=True,
                                 agg_params=agg_params)
        agg_params = {'frame_length': 60, 'exclude_influx': True}
        self.agent.register_feed('HWPEncoder_full', record=True,
                                 agg_params=agg_params)
        self.parser = EncoderParser(beaglebone_port=self.port)

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

        **Process** - Start acquiring data.

        """
        time_encoder_published = 0
        counter_list = []
        counter_index_list = []
        quad_list = []
        quad_counter_list = []
        received_time_list = []

        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

            # Prepare data_cache for session.data
            self.hwp_freq = -1
            self.ct = time.time()
            data_cache = {
                'approx_hwp_freq': self.hwp_freq,
                'encoder_last_updated': self.ct,
                'irig_time': self.irig_time,
                'irig_last_updated': self.ct,
            }

            while self.take_data:
                # This is blocking until data are available
                self.parser.grab_and_parse_data()

                # IRIG data; normally every sec
                while len(self.parser.irig_queue):
                    irig_data = self.parser.irig_queue.popleft()
                    rising_edge_count = irig_data[0]
                    irig_time = irig_data[1]
                    irig_info = irig_data[2]
                    synch_pulse_clock_counts = irig_data[3]
                    sys_time = irig_data[4]
                    data = {'timestamp': sys_time, 'block_name': 'HWPEncoder_irig', 'data': {}}
                    data['data']['irig_time'] = irig_time
                    data['data']['rising_edge_count'] = rising_edge_count
                    data['data']['irig_sec'] = de_irig(irig_info[0], 1)
                    data['data']['irig_min'] = de_irig(irig_info[1], 0)
                    data['data']['irig_hour'] = de_irig(irig_info[2], 0)
                    data['data']['irig_day'] = de_irig(irig_info[3], 0) \
                        + de_irig(irig_info[4], 0) * 100
                    data['data']['irig_year'] = de_irig(irig_info[5], 0)

                    # Beagleboneblack clock frequency measured by IRIG
                    if self.rising_edge_count > 0 and irig_time > 0:
                        bbb_clock_freq = float(rising_edge_count - self.rising_edge_count) \
                            / (irig_time - self.irig_time)
                    else:
                        bbb_clock_freq = 0.
                    data['data']['bbb_clock_freq'] = bbb_clock_freq

                    self.agent.publish_to_feed('HWPEncoder', data)
                    self.rising_edge_count = rising_edge_count
                    self.irig_time = irig_time

                    # saving clock counts for every refernce edge and every irig bit info
                    data = {'timestamps': [], 'block_name': 'HWPEncoder_irig_raw', 'data': {}}
                    # 0.09: time difference in seconds b/w reference marker and
                    #       the first index marker
                    data['timestamps'] = sys_time + 0.09 + np.arange(10) * 0.1
                    data['data']['irig_synch_pulse_clock_time'] = list(irig_time + 0.09
                                                                       + np.arange(10) * 0.1)
                    data['data']['irig_synch_pulse_clock_counts'] = synch_pulse_clock_counts
                    data['data']['irig_info'] = list(irig_info)
                    self.agent.publish_to_feed('HWPEncoder', data)

                    data_cache['irig_time'] = self.irig_time
                    data_cache['irig_last_updated'] = sys_time
                    session.data.update(data_cache)

                # Reducing the packet size, less frequent publishing
                # Encoder data; packet coming rate = 570*2*2/150/4 ~ 4Hz packet at 2 Hz rotation
                while len(self.parser.counter_queue):
                    counter_data = self.parser.counter_queue.popleft()

                    counter_list += counter_data[0].tolist()
                    counter_index_list += counter_data[1].tolist()

                    quad_data = counter_data[2]
                    sys_time = counter_data[3]

                    received_time_list.append(sys_time)
                    quad_list.append(quad_data)
                    quad_counter_list.append(counter_data[0][0])
                    ct = time.time()

                    if len(counter_list) >= NUM_ENCODER_TO_PUBLISH \
                       or (len(counter_list)
                           and (ct - time_encoder_published) > SEC_ENCODER_TO_PUBLISH):
                        # Publishing quadratic data first
                        data = {'timestamps': [], 'block_name': 'HWPEncoder_quad', 'data': {}}
                        data['timestamps'] = received_time_list
                        data['data']['quad'] = quad_list
                        self.agent.publish_to_feed('HWPEncoder', data)

                        # Publishing counter data
                        # (full sampled data will not be recorded in influxdb)
                        data = {'timestamps': [], 'block_name': 'HWPEncoder_counter', 'data': {}}
                        data['data']['counter'] = counter_list
                        data['data']['counter_index'] = counter_index_list

                        data['timestamps'] = count2time(counter_list, received_time_list[0])
                        self.agent.publish_to_feed('HWPEncoder_full', data)

                        # Subsampled data for influxdb display
                        data_subsampled = {'block_name': 'HWPEncoder_counter_sub', 'data': {}}
                        data_subsampled['timestamps'] = np.array(data['timestamps'])[::NUM_SUBSAMPLE].tolist()
                        data_subsampled['data']['counter_sub'] = np.array(counter_list)[::NUM_SUBSAMPLE].tolist()
                        data_subsampled['data']['counter_index_sub'] = np.array(counter_index_list)[::NUM_SUBSAMPLE].tolist()
                        self.agent.publish_to_feed('HWPEncoder', data_subsampled)

                        # For rough estimation of HWP rotation frequency
                        data = {'timestamp': received_time_list[0],
                                'block_name': 'HWPEncoder_freq', 'data': {}}
                        dclock_counter = counter_list[-1] - counter_list[0]
                        dindex_counter = counter_index_list[-1] - counter_index_list[0]
                        # Assuming Beagleboneblack clock is 200 MHz
                        pulse_rate = dindex_counter * 2.e8 / dclock_counter
                        hwp_freq = pulse_rate / 2. / NUM_SLITS

                        diff_counter = np.diff(counter_list)
                        diff_index = np.diff(counter_index_list)

                        self.log.info(f'pulse_rate {pulse_rate} {hwp_freq}')
                        data['data']['approx_hwp_freq'] = hwp_freq
                        data['data']['diff_counter_mean'] = np.mean(diff_counter)
                        data['data']['diff_index_mean'] = np.mean(diff_index)
                        data['data']['diff_counter_std'] = np.std(diff_counter)
                        data['data']['diff_index_std'] = np.std(diff_index)
                        self.agent.publish_to_feed('HWPEncoder', data)

                        # Initialize lists
                        counter_list = []
                        counter_index_list = []
                        quad_list = []
                        quad_counter_list = []
                        received_time_list = []

                        time_encoder_published = ct

                        # Update session.data
                        self.hwp_freq = hwp_freq
                        self.ct = ct

                data_cache['approx_hwp_freq'] = self.hwp_freq
                data_cache['encoder_last_updated'] = self.ct
                session.data.update(data_cache)

        self.agent.feeds['HWPEncoder'].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 make_parser(parser=None):
    if parser is None:
        parser = argparse.ArgumentParser()

    # Add options specific to this agent.
    pgroup = parser.add_argument_group('Agent Options')
    pgroup.add_argument('--port', type=int, default=8080)

    return parser


# Portion of the code that runs
def main(args=None):
    parser = make_parser()
    args = site_config.parse_args(agent_class='HWPBBBAgent',
                                  parser=parser,
                                  args=args)
    agent, runner = ocs_agent.init_site_agent(args)
    hwp_bbb_agent = HWPBBBAgent(agent, port=args.port)
    agent.register_process('acq', hwp_bbb_agent.acq, hwp_bbb_agent._stop_acq, startup=True)

    runner.run(agent, auto_reconnect=True)


if __name__ == '__main__':
    main()
