## Task Methods & Attributes Full reference for `TaskBase` — every method you can override or call, and every attribute you can read, from inside your own task. For a walkthrough of building a task from scratch, see the [Task Development Guide](task.md). --- ### Methods you must override | Method | Called | Use it to | |--------|--------|-----------| | `start(self)` | Once, before the trial loop begins. | Configure hardware, pre-compute stimuli, open files, etc. | | `create_trial(self)` | Once at the start of every trial. | Build and run the trial. | | `after_trial(self)` | Immediately after each trial finishes. | Score performance, update adaptive parameters, call `register_value`. | | `close(self)` | Once after the trial loop ends (session finished, forced stop, or error). | Close files, stop hardware, release resources. | ```{admonition} create_trial with Bpod :class: note Each call starts with an empty state machine. Add states with `self.bpod.add_state` — when the method returns, the state machine is sent and run automatically, and `after_trial` is called once it finishes. Without Bpod, you're responsible for creating and running the trial logic yourself. ``` ```{admonition} after_trial must register "water" :class: warning Always call `self.register_value("water", ...)` with the amount of water consumed during the trial. The system sums it across the session to get total water consumption — if that total falls below a configurable threshold, an alarm is triggered (adjustable in the Settings tab of the GUI). ``` --- ### Methods you can call Do not override these — call them from inside your task's own methods. #### Registering controller events If you're using Bpod, all four of these are called automatically — you never need to call them yourself. With `BEHAVIOR_CONTROLLER` set to `OTHER`, call them with the right timestamps to populate `trial_data` (see below). | Method | Args | When to call it (`OTHER`) | |--------|------|------| | `register_start_trial` | `raspberry_timestamp: float, controller_timestamp: float` | At the beginning of each trial. | | `register_end_trial` | `controller_timestamp: float` | At the end of each trial. | | `register_enter_state` | `state_name: str, controller_timestamp: float` | Whenever you enter a state. | | `register_controller_event` | `name: str, controller_timestamp: float` | To register any other controller event. | - `raspberry_timestamp`: the Raspberry Pi's own clock — get it with `time_utils.now_timestamp()`. - `controller_timestamp`: read from the microcontroller's own clock (e.g. an Arduino's `millis()`, converted to seconds), if you're talking to one and it reports its own timing. - If you're not syncing against an external clock (no microcontroller, or one that doesn't report its own timing), `controller_timestamp` is exactly the same as `raspberry_timestamp`. `register_start_trial` uses the two timestamps together to compute a clock offset (`raspberry_timestamp - controller_timestamp`), used to convert every later `controller_timestamp` back to Raspberry Pi absolute time. #### Registering a Raspberry-side event ```python def register_raspberry_event(self, name: str, raspberry_timestamp: float) -> None ``` For events generated by the Raspberry Pi itself, asynchronously from the controller — e.g. executing a direct function, or a camera/touchscreen detection. Pass `time_utils.now_timestamp()` as `raspberry_timestamp`. #### Registering a value ```python def register_value(self, name: str, value: Any) -> None ``` Saves a custom value to the current trial row in the CSV — call it inside `after_trial`. ```python self.register_value("correct", 1) self.register_value("response_time", 0.432) ``` #### Executing a direct function ```python def execute_function(self, i: int) -> None ``` Runs the direct function registered at index `i` (1-99). See [Direct, Audio & Video Functions](functions.md) for how those get registered and the other ways they can be triggered. ```python self.execute_function(1) # executes the function registered at index 1 ``` --- ### Attributes you can read | Attribute | Type | Description | |-----------|------|--------------| | `self.name` | `str` | Name of the task (the class name). | | `self.subject` | `str` | Name of the subject running the session. | | `self.system_name` | `str` | Name of the system, as defined in the Settings tab. | | `self.bpod` | `BpodController` | Bpod interface — state machine construction, sending, etc. Mainly used inside `create_trial`. | | `self.arduino` | `ArduinoController` | Arduino interface, for tasks using an Arduino instead of Bpod. | | `self.settings` | `Settings` | Session parameters defined in the training protocol. Read and write its attributes to implement adaptive training. | | `self.cam_box` | `Camera \| NullCamera` | Camera attached to the operant box (`NullCamera` if not configured). | | `self.gpio` | `Gpio \| NullGpio` | Output pin control (`set_on()` / `set_off()`). See [Custom GPIO Interaction](gpio_trigger.md) for the input-pin trigger hook. | | `self.current_trial` | `int` | The current trial number, starting from 1. | | `self.date` | `str` | Date string of the current session, set once when it starts. | | `self.run_mode` | `str` | `"Manual"` or `"Auto"`. | | `self.force_stop` | `bool` | Set to `True` from your own task logic (e.g. in `after_trial`) to make the task stop. | | `self.stop_button_pressed` | `bool` | `True` once something external (e.g. the STOP TASK button) has asked the task to stop. Read-only — react to it, don't set it. | | `self.chrono` | `Chrono` | `self.chrono.get_seconds()` gives the time in seconds since the task started. | #### `self.info` Human-readable description of the task, set once (usually in `__init__`). Shown to the user when selecting the task in the GUI. ```python self.info = """ My Task ------- Describe what the task does and how it progresses here. """ ``` #### `self.calibrations` Holds the task's calibrations — call its methods to convert between hardware values and real-world units (see [Custom Calibrations](../advanced_customization/calibrations.md)). ```python gain = self.calibrations.sound_calibration.get_sound_gain( speaker=1, dB=70.0, sound_name="white_noise" ) ``` #### `self.should_stop` (read-only property) `True` once *any* stopping condition is met: the trial/time limit (Manual mode only — Auto mode runs until the subject leaves the corridor, not for a fixed number of trials), `force_stop`, or `stop_button_pressed`. `````{admonition} Needed for a task with its own waiting loop :class: tip Trial/time limits and `force_stop` are only checked *between* trials, by the task's own run loop — they can't interrupt a `create_trial` that's blocked inside its own waiting loop. If your task doesn't use Bpod and waits on its own condition inside `create_trial`, poll `self.should_stop` so the loop actually exits when asked to: ```python def create_trial(self): while not self.should_stop: ... # wait for your own condition ``` ````` #### `self.trial_data` (dict) Populated automatically at the end of each trial — available inside `after_trial`. **Keys always present:** - `"date"` (str), `"trial"` (int), `"subject"` (str), `"task"` (str), `"system_name"` (str) - `"TRIAL_START"` / `"TRIAL_END"` (float): absolute timestamps (UNIX epoch seconds) - `"ordered_list_of_events"` (list[str]): event names in the order they occurred (e.g. `["Port1In", "Port1Out", "Port1In"]`) **Keys added per state visited** (Bpod or manual): - `"STATE__START"` / `"STATE__END"` (list[float]): timestamps of every entry/exit for that state — a list because the same state can be visited more than once per trial. **Keys added per event type:** - `""` (list[float]): timestamps of every occurrence of that event (e.g. `"Port1In"`, `"Port1Out"`). When using Bpod, all state and event keys are filled in automatically. Without Bpod, call `register_enter_state` and `register_controller_event` / `register_raspberry_event` yourself to populate them. **Keys added by you:** any name passed to `register_value` inside `after_trial`. ```python def after_trial(self): if self.trial_data.get("correct") == 1: self.settings.difficulty += 1 t_start = self.trial_data["STATE_stimulus_START"][0] t_end = self.trial_data["STATE_stimulus_END"][0] response_time = t_end - t_start ```