YAML Protocol Specification¶
This document describes the YAML protocol format for defining olfactometer experiments with precise timing control.
Protocol Structure Overview¶
A protocol consists of three main sections:
- Protocol metadata: Name, version, and description
- Timing configuration: Sample rates, pulse widths, and timing constraints
- Sequence definition: Phases with device actions and precise timing
Top-Level Configuration¶
protocol:
name: "Descriptive Protocol Name"
version: "1.0"
description: "Detailed description of the experimental paradigm"
timing:
base_unit: "ms" # Time unit for all timing values
sample_rate: 1000 # Hz - 1000=1ms precision, 10000=0.1ms precision
camera_interval: 100 # ms - interval between camera pulses (0=disabled)
camera_pulse_duration: 5 # ms - duration of camera trigger pulses
preload_lead_ms: 2 # ms - S-bits switch before LOAD_REQ pulse
load_req_ms: 1 # ms - duration of LOAD_REQ pulses
rck_pulse_ms: 1 # ms - duration of register clock (RCK) pulses
trig_pulse_ms: 5 # ms - duration of microscope trigger pulses
setup_hold_samples: 100 # samples - S-bit stability margin around load events
seed: 42 # RNG seed for reproducible randomization (optional)
sequence:
- phase: "Phase Name"
duration: 30000 # ms - total phase duration
times: 5 # number of repetitions (preferred over 'repeat')
repeat: 0 # legacy: 0=no repeat, 1=repeat once
randomize: true # randomize order within state lists
actions:
- device: "device.name"
state: "STATE_NAME" # for digital devices
value: 2.5 # for analog devices (volts)
timing: 1000 # ms - offset within phase
Timing Configuration Details¶
Sample Rate & Precision¶
- sample_rate: Determines temporal resolution
1000
Hz = 1 ms precision (standard)10000
Hz = 0.1 ms precision (high precision)- base_unit: Always "ms" for millisecond timing
Hardware Pulse Timing¶
- preload_lead_ms: Time before LOAD_REQ when S-bits switch to new state
- load_req_ms: Duration of load request pulses sent to Teensy
- rck_pulse_ms: Duration of register clock pulses for valve commits
- setup_hold_samples: Extra samples for S-bit stability around load events
Camera & Trigger Timing¶
- camera_interval: Interval between continuous camera pulses (0 = disabled)
- camera_pulse_duration: Width of camera trigger pulses
- trig_pulse_ms: Width of microscope trigger pulses
Device Types & States¶
Device Configuration Reference¶
Olfactometers (8-Valve Systems)¶
Control large olfactometer valve arrays with 8 possible states.
Device Keys:
olfactometer.left
- Left olfactometer systemolfactometer.right
- Right olfactometer system
Available States:
OFF
- All valves closed (state 0)AIR
- Clean air delivery (state 1)ODOR1
throughODOR5
- Specific odor channels (states 2-6)FLUSH
- System flush/clean (state 7)
Multi-State Selection:
# Single state
state: "ODOR1"
# Sequential selection (uses repeat index)
state: "ODOR1,ODOR2,ODOR3"
# Copy from other side
state: "COPY" # mirrors the resolved left olfactometer state
Switch Valves (2-State Systems)¶
Control binary switch valves for clean/odor selection.
Device Keys:
switch_valve.left
- Left switch valveswitch_valve.right
- Right switch valve
Available States:
CLEAN
- Clean air path (state 0)ODOR
- Odor delivery path (state 1)
Mass Flow Controllers (Analog Outputs)¶
Set voltage levels (0-5V) for MFC setpoints.
Device Keys:
mfc.air_left_setpoint
- Left air MFC setpointmfc.air_right_setpoint
- Right air MFC setpointmfc.odor_left_setpoint
- Left odor MFC setpointmfc.odor_right_setpoint
- Right odor MFC setpoint
Usage:
Trigger Signals¶
Generate precise trigger pulses for external equipment.
Device Keys:
triggers.microscope
- Single pulse triggers for microscopetriggers.camera_continuous
- Continuous periodic camera triggers
Microscope Triggers:
# Single pulse at specified timing
- device: "triggers.microscope"
state: true
timing: 30000 # Pulse occurs at 30s mark
Camera Triggers:
# Start continuous pulses
- device: "triggers.camera_continuous"
state: true
timing: 1000
# Stop continuous pulses
- device: "triggers.camera_continuous"
state: false
timing: 60000
Sequence Definition¶
Phase Structure¶
Each phase represents a distinct experimental period with defined duration and actions.
- phase: "Descriptive Phase Name"
duration: 30000 # Total phase duration in ms
times: 5 # Number of repetitions (recommended)
repeat: 0 # Legacy: 0=no repeat, 1=repeat once
randomize: true # Randomize state order within lists
actions: # List of device actions
- device: "device.name"
state: "STATE"
timing: 1000 # Offset within phase (ms)
Action Timing¶
All action timing is relative to the start of each phase repetition.
Example Timeline:
- phase: "Trial Phase"
duration: 60000 # 60 second phase
times: 3 # Repeat 3 times
actions:
- device: "olfactometer.left"
state: "ODOR1"
timing: 0 # At phase start: 0s, 60s, 120s
- device: "triggers.microscope"
state: true
timing: 30000 # Mid-phase: 30s, 90s, 150s
Randomization¶
When randomize: true
and multi-state lists are provided:
- phase: "Randomized Odors"
times: 4
randomize: true
actions:
- device: "olfactometer.left"
state: "ODOR1,ODOR2,ODOR3,ODOR4" # Will be shuffled each protocol run
timing: 0
The seed
parameter in timing configuration ensures reproducible randomization.
Advanced Features¶
State Copying¶
The right olfactometer can mirror the left side's resolved state:
- device: "olfactometer.left"
state: "ODOR1,ODOR2,ODOR3"
timing: 0
- device: "olfactometer.right"
state: "COPY" # Uses same resolved state as left
timing: 100 # Slight delay for timing control
Precise Timing Control¶
Hardware Constraints:
- S-bits switch
preload_lead_ms
before LOAD_REQ pulses setup_hold_samples
provide stability margins around load events- Overlapping preload windows are detected and rejected at compile time
Timing Guardrails: The compiler enforces timing constraints to prevent hardware conflicts and ensure reliable valve switching.
Complete Example¶
protocol:
name: "Odor Discrimination Task"
timing:
sample_rate: 1000
seed: 42
sequence:
- phase: "Baseline"
duration: 30000
times: 1
actions:
- device: "olfactometer.left"
state: "AIR"
timing: 0
- device: "triggers.camera_continuous"
state: true
timing: 1000
- phase: "Odor Presentation"
duration: 60000
times: 5
randomize: true
actions:
- device: "olfactometer.left"
state: "ODOR1,ODOR2,ODOR3,ODOR4,ODOR5"
timing: 0
- device: "switch_valve.left"
state: "ODOR"
timing: 10000
- device: "triggers.microscope"
state: true
timing: 15000