Ejemplo n.º 1
0
    def open(self):
        """
        Starts Bpod.

        Connect to Bpod board through serial port, test handshake, retrieve firmware version,
        retrieve hardware description, enable input ports and configure channel synchronization.

        Example:

        .. code-block:: python

            my_bpod = Bpod().open("/dev/tty.usbmodem1293", "/Users/John/Desktop/bpod_workspace", "2afc_protocol")

        :param str serial_port: serial port to connect
        :param str workspace_path: path for bpod output files (no folders will be created)
        :param str session_name: this name will be used for output files
        :param int baudrate [optional]: baudrate for serial connection
        :param int sync_channel [optional]: Serial synchronization channel: 255 = no sync, otherwise set to a hardware channel number
        :param int sync_mode [optional]: Serial synchronization mode: 0 = flip logic every trial, 1 = every state
        :return: Bpod object created
        :rtype: pybpodapi.model.bpod
        """

        logger.info("Starting Bpod")

        self._bpodcom_connect(self.serial_port, self.baudrate)

        if not self._bpodcom_handshake():
            raise BpodErrorException('Error: Bpod failed to confirm connectivity. Please reset Bpod and try again.')

        #########################################################
        ### check the firmware version ##############################
        #########################################################
        firmware_version, machine_type = self._bpodcom_firmware_version()

        if firmware_version < int(settings.TARGET_BPOD_FIRMWARE_VERSION):
            raise BpodErrorException('Error: Old firmware detected. Please update Bpod 0.7+ firmware and try again.')

        if firmware_version > int(settings.TARGET_BPOD_FIRMWARE_VERSION):
            raise BpodErrorException('Error: Future firmware detected. Please update the Bpod python software.')

        self._hardware.firmware_version = firmware_version
        self._hardware.machine_type = machine_type
        #########################################################
        self._bpodcom_hardware_description(self._hardware)

        if not self._bpodcom_enable_ports(self._hardware):
            raise BpodErrorException('Error: Failed to enable Bpod inputs.')

        if not self._bpodcom_set_sync_channel_and_mode(sync_channel=self.sync_channel, sync_mode=self.sync_mode):
            raise BpodErrorException('Error: Failed to configure syncronization.')

        # check if any module is connected
        self.bpod_modules = self._bpodcom_get_modules_info(self._hardware)

        self._hardware.setup(self.bpod_modules)

        self.__initialize_input_command_handler()

        return self
Ejemplo n.º 2
0
    def manual_override(self,
                        channel_type,
                        channel_name,
                        channel_number,
                        value,
                        ignore_emulator=False):
        """
        Manually override a Bpod channel

        :param ChannelType channel_type: channel type input or output
        :param ChannelName channel_name: channel name like PWM, Valve, etc.
        :param channel_number:
        :param int value: value to write on channel
        """
        if channel_type == ChannelType.INPUT:
            input_channel_name = channel_name + str(channel_number)
            try:
                channel_number = self.hardware.channels.input_channel_names.index(
                    input_channel_name)
                self.trigger_input(channel_number,
                                   value,
                                   ignore_emulator=ignore_emulator)
            except:
                raise BpodErrorException(
                    'Error using manual_override: {name} is not a valid channel name.'
                    .format(name=channel_name))

        elif channel_type == ChannelType.OUTPUT:
            if channel_name == 'SoftCode':
                self.trigger_softcode(value, ignore_emulator=ignore_emulator)
            elif channel_name == 'Serial':
                self.trigger_serial(channel_number,
                                    value,
                                    ignore_emulator=ignore_emulator)
            else:
                output_channel_name = channel_name + \
                    str(channel_number)
                try:
                    channel_number = self.hardware.channels.output_channel_names.index(
                        output_channel_name)
                    self.trigger_output(channel_number,
                                        value,
                                        ignore_emulator=ignore_emulator)
                except:
                    raise BpodErrorException(
                        'Error using manual_override: {name} is not a valid channel name.'
                        .format(name=output_channel_name))
        else:
            raise BpodErrorException(
                'Error using manualOverride: first argument must be "Input" or "Output".'
            )
Ejemplo n.º 3
0
 def start_module_relay(self):
     if not self.bpod_modules.relay_is_active:
         self.bpod_modules.activate_module_relay(self)
         self.relay_active = True
     else:
         raise BpodErrorException(
             "Error: You must disable the active module relay before starting another one."
         )
Ejemplo n.º 4
0
    def reset_serial_messages(self):
        """
        Reset serial messages to equivalent byte codes (i.e. message# 4 = one byte, 0x4)
        """
        response = self._bpodcom_reset_serial_messages()

        if not response:
            raise BpodErrorException('Error: Failed to reset serial message library.')
Ejemplo n.º 5
0
    def __read(self, size=None, dtype=None):
        if not self.relay_active:
            raise BpodErrorException(
                "Error: you must start the module relay with start_moule_relay() before you can read bytes from a module"
            )

        if size is None:
            size = self.bpod_modules.bpod.data_available()
        return self.bpod_modules.module_read(self, size, dtype)
Ejemplo n.º 6
0
    def _bpodcom_load_serial_message(self, serial_channel, message_id,
                                     serial_message, n_messages):
        """
        Load serial message on channel

        :param TODO
        :rtype: bool
        """
        # self.__bpodcom_check_com_ready()

        if isinstance(serial_channel, BpodModule):
            serial_channel = serial_channel.serial_port

        self.msg_id_list[message_id] = True

        if len(serial_message) > 3:
            raise BpodErrorException(
                'Error: Serial messages cannot be more than 3 bytes in length.'
            )

        if not (1 <= message_id <= 255):
            raise BpodErrorException(
                'Error: Bpod can only store 255 serial messages (indexed 1-255). You used the message_id {0}'
                .format(message_id))

        message_container = [
            serial_channel - 1, n_messages, message_id,
            len(serial_message)
        ] + serial_message

        logger.debug("Requesting load serial message (%s)",
                     SendMessageHeader.LOAD_SERIAL_MESSAGE)
        logger.debug("Message: %s", message_container)

        bytes2send = ArduinoTypes.get_uint8_array(
            [ord(SendMessageHeader.LOAD_SERIAL_MESSAGE)] + message_container)

        self._arcom.write_array(bytes2send)

        response = self._arcom.read_uint8()  # type: int

        logger.debug("Confirmation: %s", response)

        return True if response == ReceiveMessageHeader.LOAD_SERIAL_MESSAGE_OK else False
Ejemplo n.º 7
0
    def manual_override(self, channel_type, channel_name, channel_number,
                        value):
        """
        Manually override a Bpod channel

        :param ChannelType channel_type: channel type input or output
        :param ChannelName channel_name: channel name like PWM, Valve, etc.
        :param channel_number:
        :param int value: value to write on channel
        """
        if channel_type == ChannelType.INPUT:
            input_channel_name = channel_name + str(channel_number)
            channel_number = self.hardware.channels.input_channel_names.index(
                input_channel_name)
            try:
                self._bpodcom_override_input_state(channel_number, value)
            except:
                raise BpodErrorException(
                    'Error using manual_override: {name} is not a valid channel name.'
                    .format(name=channel_name))

        elif channel_type == ChannelType.OUTPUT:
            if channel_name == 'Serial':
                self._bpodcom_send_byte_to_hardware_serial(
                    channel_number, value)

            else:
                try:
                    output_channel_name = channel_name + str(channel_number)
                    channel_number = self.hardware.channels.output_channel_names.index(
                        output_channel_name)
                    self._bpodcom_override_digital_hardware_state(
                        channel_number, value)
                except:
                    raise BpodErrorException(
                        'Error using manual_override: {name} is not a valid channel name.'
                        .format(name=output_channel_name))
        else:
            raise BpodErrorException(
                'Error using manualOverride: first argument must be "Input" or "Output".'
            )
Ejemplo n.º 8
0
    def load_serial_message(self, serial_channel, message_ID, serial_message):
        """
        Load serial message on Bpod

        :param int serial_channel: Serial port to send, 1, 2 or 3
        :param int message_ID: Unique id for the message. Should be between 1 and 255
        :param list(int) serial_message: Message to send. The message should be bigger than 3 bytes.
        """
        response = self._bpodcom_load_serial_message(serial_channel, message_ID, serial_message, 1)

        if not response:
            raise BpodErrorException('Error: Failed to set serial message.')
Ejemplo n.º 9
0
    def _bpodcom_module_write(self, module_index, message, dtype=None):
        if dtype is None:
            dtype = ArduinoTypes.UINT8

        if isinstance(message, str):
            message = [ord(c) for c in message]
        elif not isinstance(message, (list, np.ndarray)):
            message = [message]
        else:
            message = message

        msg = ArduinoTypes.get_array(message, dtype)

        if len(msg) > 64:
            raise BpodErrorException(
                "Error: module messages must be under 64 bytes per transmission"
            )

        to_send = [ord(SendMessageHeader.WRITE_TO_MODULE), module_index + 1, len(msg)]
        to_send = ArduinoTypes.get_uint8_array(to_send)

        self._arcom.write_array(to_send + msg)
Ejemplo n.º 10
0
    def _bpodcom_get_modules_info(self, hardware):

        bpod_modules = BpodModules(self)  # type: BpodModules

        input_modules = [inp for inp in hardware.inputs if inp == "U"]
        n_modules = len(input_modules)
        n_serial_events = int(hardware.max_serial_events / (n_modules + 1))

        self._arcom.write_char(SendMessageHeader.GET_MODULES)
        time.sleep(0.3)

        modules_requested_events = np.array([0] * n_modules)

        names = {}

        if self._arcom.bytes_available() > 1:
            for i in range(n_modules):
                connected = self._arcom.read_uint8() == 1
                firmware_version = None
                module_name = None
                events_names = []

                if connected:
                    firmware_version = self._arcom.read_uint32()
                    name_length = self._arcom.read_uint8()
                    name_string = self._arcom.read_char_array(name_length)
                    name_string = "".join(name_string)

                    if name_string not in names:
                        names[name_string] = 0
                    names[name_string] += 1

                    module_name = name_string + str(names[name_string])

                    flag = self._arcom.read_uint8()
                    while flag == 1:  # has more info to be read
                        param_type = self._arcom.read_uint8()

                        if param_type == ReceiveMessageHeader.MODULE_REQUESTED_EVENT:
                            modules_requested_events[i] = self._arcom.read_uint8()

                        elif param_type == ReceiveMessageHeader.MODULE_EVENT_NAMES:

                            n_event_names = self._arcom.read_uint8()
                            for _ in range(n_event_names):
                                n_chars = self._arcom.read_uint8()
                                event_name = self._arcom.read_char_array(n_chars)
                                events_names.append("".join(event_name))

                        flag = self._arcom.read_uint8()

                bpod_modules += BpodModules.create_module(
                    connected,
                    module_name,
                    firmware_version,
                    events_names,
                    n_serial_events,
                    serial_port=i + 1,
                )

        if (
            modules_requested_events.sum() + n_serial_events
        ) > hardware.max_serial_events:
            raise BpodErrorException(
                "Error: Connected modules requested too many events."
            )

        for i, module in enumerate(bpod_modules):
            if module.connected:

                if modules_requested_events[i] > module.n_serial_events:
                    n_to_reassign = modules_requested_events[i] - module.n_serial_events
                    module.n_serial_events = modules_requested_events[i]
                else:
                    n_to_reassign = 0

                index = n_modules - 1
                while n_to_reassign > 0:
                    if bpod_modules[index].n_serial_events >= n_to_reassign:
                        bpod_modules[index].n_serial_events = (
                            bpod_modules[index].n_serial_events - n_to_reassign
                        )
                        n_to_reassign = 0
                    else:
                        n_to_reassign = (
                            n_to_reassign - bpod_modules[index].n_serial_events
                        )
                        bpod_modules[index].n_serial_events = 0
                    index -= 1

        n_serial_events_array = [m.n_serial_events for m in bpod_modules]
        n_soft_codes = hardware.max_serial_events - sum(n_serial_events_array)

        bytes2send = ArduinoTypes.get_uint8_array(
            [ord("%")] + n_serial_events_array + [n_soft_codes]
        )

        self._arcom.write_array(bytes2send)
        res = self._arcom.read_uint8()

        if not res:
            raise BpodErrorException(
                "Error: Failed to configure module event assignment."
            )

        return bpod_modules
Ejemplo n.º 11
0
    def run_state_machine(self, sma):
        """

        Adds a new trial to current session and runs state machine on Bpod box.

        While state machine is running, messages are processed accordingly.

        When state machine stops, timestamps are updated and trial events are processed.

        Finally, data is released for registered data consumers / exporters.

        .. seealso::

            Send command "run state machine": :meth:`pybpodapi.bpod.bpod_base.BpodBase.run_state_machine`.

            Process opcode: :meth:`pybpodapi.bpod.bpod_base.BpodBase._BpodBase__process_opcode`.

            Update timestamps: :meth:`pybpodapi.bpod.bpod_base.BpodBase._BpodBase__update_timestamps`.

        :param (:class:`pybpodapi.state_machine.StateMachine`) sma: initialized state machine
        """
        if not self.bpod_com_ready:
            raise Exception('Bpod connection is closed')

        if self._skip_all_trials is True:
            return False

        self.session += Trial(sma)

        logger.info("Running state machine, trial %s", len(self.session.trials))

        self.trial_timestamps = []  # Store the trial timestamps in case bpod is using live_timestamps

        self._bpodcom_run_state_machine()
        if self._new_sma_sent:
            if self._bpodcom_state_machine_installation_status():
                self._new_sma_sent = False
            else:
                raise BpodErrorException('Error: The last state machine sent was not acknowledged by the Bpod device.', self)

        self.trial_start_timestamp = self._bpodcom_get_trial_timestamp_start()

        if self.bpod_start_timestamp is None:
            self.bpod_start_timestamp = self.trial_start_timestamp

        #####################################################
        # create a list of executed states
        state_change_indexes = []

        # flags used to stop a trial (or all trials)
        interrupt_task = False
        kill_task = False

        sma.is_running = True
        while sma.is_running:

            # read commands from the stdin ######################
            if self.stdin is not None:
                inline = self.stdin.readline()
                if inline is not None:
                    interrupt_task, kill_task = self.handle_inline(inline, sma)
            #####################################################

            # read commands from a net socket ###################
            if self.socketin is not None:
                inline = self.socketin.readline()

                if inline is not None:
                    inline = inline.decode().strip()
                    interrupt_task, kill_task = self.handle_inline(inline, sma)
            #####################################################

            if self.data_available():
                opcode, data = self._bpodcom_read_opcode_message()
                self.__process_opcode(sma, opcode, data, state_change_indexes)

            self.loop_handler()

            if interrupt_task or kill_task:
                self._skip_all_trials = True
                break

        self.session += EndTrial('The trial ended')

        if not interrupt_task:
            self.__update_timestamps(sma, state_change_indexes)
            self.session.add_trial_events()

        logger.info("Publishing Bpod trial")

        if interrupt_task and kill_task:
            self.close()
            exit(0)

        return not interrupt_task
Ejemplo n.º 12
0
 def trigger_event_by_name(self, event_name, event_data, ignore_emulator=False):
     if event_name not in self.hardware.channels.event_names:
         raise BpodErrorException(f'Unknown event name: {event_name}')
     event_index = self.hardware.channels.event_names.index(event_name)
     self.trigger_event(event_index, event_data, ignore_emulator=ignore_emulator)