From Bcontrol

Overview of Dispatcher[edit]

Dispatcher is an interface between protocol code and the Real-Time Linux State Machine (RTLSM). It provides opening and closing of protocols, automatic segmentation of data from the RTLSM into trials, parsing of trials, and an automatic keeping track of trial and state machine history over trials. Dispatcher also takes care of the regular polling of the RTLSM. Finally if you ask it to, Dispatcher will add code to your state machine so a digital signature of the trial number and a time sync signal is sent out a DIO line at the beginning of each trial; this is intended for syncing the behavior system to electrophysiological recording systems.

One of the principal advantages of using Dispatcher is that it is aware of the trial structure, and thus knows precisely which state machine definition corresponded to which trial and exactly when state machine definitions switched, and can therefore precisely interpret events from the RTLSM and parse them for you. (Parsing is described here; as a brief example, if your state machine has states named "woohee" and "dinkle", Dispatcher will automatically provide you with a variable called "parsed_events" that is a structure with elements states.woohee and state.dinkle; the entries in those elements will tell you the time stamps of entering and exiting from those states.)

Dispatcher is written using Solo, but to use the functionality that Dispatcher provides, you don't need to know Solo at all.

Once again, here is documentation on the result of parsing -- i.e., this is documentation on the format in which information about state machine events is returned to you by dispatcher.

You can also use Dispatcher to run the StateMachineAssembler's disassembler for you. Look here for an example on how to do that.

Currently (14:07, 1 June 2007 (EDT)) there are only three protocols that work with dispatcher. They can be used as templates for you to make your own protocol. The protocols are PokesplotDemo, Minimal, and CenterPokeFollower. The last is planned as the answer key to one of the excercises, so-- don't look at it unless you have to!

Opening/closing Dispatcher[edit]

To start dispatcher, make sure you've called newstartup (See Startup Guide.). The command in the startup process that starts dispatcher is:

  >> dispatcher('init')

To rescan the existing protocols that dispatcher can start,

  >> dispatcher('rescan_protocols')

To close dispatcher,

  >> dispatcher('close')

Template code to write a dispatcher protocol[edit]

To write a dispatcher protocol, make a directory in Protocols, "Protocols/@mynewprotocol". Within that directory, place a constructor file, "Protocols/@mynewprotocol/mynewprotocol.m". (You should replace "mynewprotocol" with the name of your choice, of course.) The .m file must contain, at a minimum, what is in this example dispatcher template. With that plus the information below, you are good to go!

You might want to look at existing template protocols, like PokesplotDemo, or Minimal. You might also want to look at the Plugins page, to see what pre-existing code goodies are available for you to pick up.

Underscores are not allowed in Protocol names. If you do, you will get an obscure error message in Runrats indicating your protocol is not compatible with runrats.

The action string and its 7 possible values[edit]

Dispatcher interacts with your protocol by calling the constructor of your protocol code always with a single argument, a string usually called the "action." (E.g., if your code is in @yourprotocol/yourprotocol.m, it'll be yourprotocol.m that gets called with a single string as an argument.) This single argument can have one of 5 values:

This is the cue for initialization of the protocol: start any windows or GUIs that you may want to have, initialize variables, etc. Send the state machine definition for the first trial to dispatcher, to be forwarded to the RTLSM.
The protocol is to be closed. Delete your figure windows, delete your variables, etc.
Dispatcher has detected that the RTLSM has entered one of the "prepare_next_trial" states. (See trial structure.) Your protocol should prepare the next trial's state machine definition, and send it to dispatcher to be forwarded to the RTLSM. You can send any state machine definition that you like. (But you should make sure that your state machine is guaranteed to hit "check_next_trial_ready" at some point, otherwise the following trial may never start!) At the same time, you should indicate which states in your definition form the "prepare_next_trial" states. Note that this is also called when the experiment starts - at the beginning of the first trial.
An 'update' call (see below) is guaranteed to have been made immediately before the 'prepare_next_trial' call to your protocol. That is, you don't need to process the latest events in your 'prepare_next_trial' code, leave latest event processing to your 'update' code.
(Note: Since the state machine definition can be different from trial to trial, so can the set of trials that compose the "prepare_next_trial" set.)
Dispatcher has detected a state_0 boundary, meaning that a trial has been completed and the next trial is about to start. (See trial structure.)
We are in the middle of a trial. Dispatcher has polled the RTLSM to ask for the latest events, and is reporting them to you.

And another two that are relevant when the protocol is running under Runrats:

The user pressed the End Session button in Runrats. This is where you can compute any statistics that you want to report on what happened during the session. Many protocols also use this call to set the time of the end of the session in the protocol title. The session data will be saved after this call completes.
Like 'end_session' above, this is also called after the user pressed the End Session button in Runrats. However, 'end_session', is called first; after that the session's data is saved; only then is 'pre_saving_settings' called; and only after that are the settings for the next session saved. Thus, 'pre_saving_settings' is where you would change things for what you want the protocol to do for the next session.

In sum, 'end_session' is a good place to compile statistics about this day's data, and 'pre_saving_settings' is a good place to make changes that you want applied for the next session (which is why they should not be saved with the currently ending session's data).

When used under Runrats, protocols should also be able to respond to the last two actions described above, end_session and pre_saving_settings. (Note: these calls should probably be moved from Runrats to dispatcher, they're at a conceptual level that belongs in dispatcher. But as of this writing (24-Dec-2011) they are in RunRats.) For more info on what happens when the tech clicks "End Session", see the Runrats page.

Variables instantiated by Dispatcher[edit]

Any Dispatcher protocol should have, near the top of its code, the line "GetSoloFunctionArgs(obj);", where "obj" is an object of your protocol's class. (This line is already part of the default code in the "do not modify" section of the template.) As a consequence, in addition to calling your protocol code with the above 5 different possible actions strings, Dispatcher will instantiate certain variables within your protocol, so that you have the content of these variables available to you. They will be instantiated as regular local Matlab variables (not SoloParamHandles). In any method of your protocol (i.e., m-files within your @yourprotocol/ directory), the line "GetSoloFunctionArgs(obj);" will instantiate the same variables. The variables are:

The number of trials that have reached one of their "prepare_next_trial" states.
The number of times the RTLSM has gone through state_0.
In between the initial state_0 in a trial, and entering the first of the "prepare_next_trial" states in that trial, n_started_trials=1+n_done_trials. In between entering the first of the "prepare_next_trial" states and the final state_0 in a trial, n_started_trials=n_done_trials.
This is always 1 less than n_started_trials. A trial is completed when the jump to state 0, to start the next trial, occurs.
A structure (see parsing), the result of running disassemble.m with the 'parsed_structure' flag set to 1 on all the events that have occurred from the initial state_0 in the current trial until now.
parsed_events.states.state_0 is guaranteed to have at least one row, and that first row will be of the form [NaN t0] where t0 is the time of the start of the current trial. parsed_events.states.state_0 is guaranteed to have at most two rows. If the second row exists, the trial has been completed, and the second row is guaranteed to be of the form [t1 NaN] where t1 is the time of the end of the current trial.
A cell that is n_completed_trials-by-1 in size. Each element is the parsed_events structure for a completed trial. Thus, parsed_events_history{1} is the parsed_events structure for the 1st trial; parsed_events_history{4} is the parsed_events structure for the 4th trial; and so on.
A structure (see parsing), the result of running disassemble.m with the 'parsed_structure' flag set to 1 on all the events that have occurred from the last time the RTLSM was polled until now. If no events have occurred, all the elements of this structure will be empty. latest_parsed_events is a useful variable when you want to focus just on updating based on the most recent events.
A @StateMachineAssembler object, as was used for the current trial.
This is the object that is used to disassemble events into parsed_events and latest_parsed_events.
A cell that is n_completed_trials-by-1 in size. Each element is the current_assembler for a completed trial. Thus, current_assembler_history{1} is the current_assembler for the 1st trial; current_assembler_history{4} is the current_assembler for the 4th trial; and so on.
A numeric matrix containing all the events from the start of the current trial until now, as obtained directly from the RTLSM.
parsed_events = disassemble(current_assembler, raw_events, 'parsed_structure', 1);
The first row of raw_events is guaranteed to be the transition out of state 0 that initiated the current trial. If the trial is completed, the last row of raw_events is guaranteed to be the transition into state 0.
A cell that is n_completed_trials-by-1 in size. Each element is the raw_events for a completed trial. Thus, raw_events_history{1} is the raw_events for the 1st trial; raw_events_history{4} is the raw_events for the 4th trial; and so on.
parsed_events_history{n} = disassemble(current_events_history{n}, raw_events_history{n}, 'parsed_structure', 1);

Automatic history traces in Dispatcher[edit]

Dispatcher automatically makes a trial-by-trial history of a number of variables, as listed in the "Variables instantiated by Dispatcher" section above.

In addition, automatic history traces of all GUI SoloParamHandles in your protocol also get made every time a new trial gets sent to the RTLinux machine, that is, immediately after dispatcher('send_assembler') is called. In other words, a snapshot of what all the GUI settings were, for each trial, is automatically taken and stored. Dispatcher obtains this snapshot for you by calling


where protocol_object is your currently open protocol. What that call does is to loop over all SoloParamHandles in the protocol, check if they are a SoloParamHandle with a GUI component, and if they are, it calls push_history on that SoloParamHandle; this adds the current SoloParamHandle's value to the history entries of that SoloParamHandle. You can recover the history for a given SoloParamHandle by calling get_history on it.

For example, suppose StimID is a Display SoloParamHandle. Let's suppose n_done_trials == 5, that is, 5 trials have been done. Then


will return a 6-by-1 cell. The first 5 entries correspond to the value of StimID when the 5 done trials were started. The last entry corresponds to the value of StimID when the latest state machine diagram was sent to the RTLinux machine; the trial corresponding to that diagram is not yet a done trial.

What if I want a history trace of a non-GUI SoloParamHandle? It's an easy decision for Dispatcher to store a history of all GUI SoloParamHandles, but less obvious for non-GUI SoloParamHandles, which may themselves already have a history trace, or which may be very large in size. For this reason, Dispatcher does not automatically call push_history on non-GUI SoloParamHandles. If you want such a trace, you should make that call yourself. For example, suppose you want a history trace of a SoloParamHandle named mySPH. Then on each trial you could call


Be careful about when you make this call. The best place to put it is somewhere in your protocol's response to 'prepare_next_trial', before you call dispatcher('send_assembler') -- that way, like the GUI SoloParamHandles, you'll be taking a snapshot of what things were like when the state machine diagram was sent to the RTLinux machine.

Debugging in Dispatcher[edit]

Suppose you've written a protocol that runs under Dispatcher and you are now trying to debug it. You can do ground state-by-state debugging by having Dispatcher invoke the StateMachineAssembler's disassembler for you. For example,

   >> dispatcher('disassemble', [1 4:10]);

will print out disassembler information for trials 1 and 4 through 10. Load your data into a protocol, choose which trials you want to dissassemble, and go.

Synchronizing behavior and electrophysiological recording[edit]

How does one synchronize the behavior system with an electrophysiological recording system? One way is to have the behavior system put out, on a digital line, a signal that timestamps the start of a trial. This section describes how Dispatcher can do that for you. That same synchronizing signal will include a signal encoding the trial number that just started. (This is all done using the @StateMachineAssembler/add_trialnum_indicator.m method.)

If you make the call


then, after that, every StateMachineAssember that you pass to dispatcher (to send to the RTLSM with a dispatcher('send_assembler') call) will be automatically modified so that state_0 will not jump straight to the user's first state. Instead, the RTLSM will first go through a very swift sequence of states that put out, on one of the DIO lines, a binary sequence that is a signature of the trial number that is just starting; after that sequence is sent, the RTLSM will jump to the user's first state. None of the user-defined states are changed-- you define and use your state machine as always, this added functionality is added in for you.

The signal on the DIO line will be, in sequence: High, followed by a a 15-bit binary representation of the trial number (1 is High, 0 is Low), with most significant bit sent out first. That is, we go through a total of 16 states that cover both the initial sync signal and the trial number. The default is to go through each of these states in 1 ms, twice the FSM's cycle time (for a total of 16 ms for all 16 states). All of these added states will have the name 'sending_trialnum'.

The DIO line on which all this will happen is determined, using the Settings.m system, with DIOLINES; trialnum_indicator being the setting name. For example, the line

     DIOLINES; trialnum_indicator; 32

in Settings_Custom.conf would mean use DIO line 6 (in binary, 32 is 100000, so it is the 6th bit). If no such setting is found, no DOut is generated.

Note: This can ONLY be used with StateMachineAssemblers that were initialized with the 'full_trial_structure' flag.

Settings for Dispatcher[edit]

The Settings that dispatcher reads include:

  • GENERALS; Protocols_Directory
    The directories in which dispatcher will look for Protocols.
    More than one directory may be specified by separating them with
    colons-- for example, to also use your own Protocols directory, outside
    of ExperPort, you could put in Settings_custom.conf the following line: 
         GENERAL; Protocols_Directory;   ../MyProtocolsDir:./Protocols
    If you change this setting in your Settings_Custom.conf file, and
    want it to take effect immediately, do the following:
         >> Settings('load'); dispatcher rescan_protocols


NEXT: Solo

PREVIOUS: Trial Structure