psychopy.org | Reference | Downloads | Github

Sending a trigger via serial port (Biosemi) synchronized on keyboard press (USB HID library)

Hi community,
I recently acquired a simple button box (blackbox toolkit, 1000hz sampling) that can be easily interfaced using the new psychopy’s keyboard component linked to the USB HID library. The button box contains two buttons, detected as ‘1’ and ‘2’ by keyboard.getKeys() respectively. So far, so good.
Here is the issue. I am planning to record the EMG activity of response muscles in a random dot motion experiment, so I need to send a trigger synchronized to the button press. Of course I could do that in my psychopy loop (see code below), but I would loose some temporal resolution (the trigger would be tethered to the refresh rate, and would not reflect the time at which the button was pressed). My question is thus very simple, but probably hard to solve. Is there a way I could easily modify the code below to send a trigger synchronized with the button press ? Of course I could use a thread to monitor my button box, or the iohub module, but I’d prefer using the (much simpler) keyboard component.
All the best

from psychopy import core, data, event, visual, gui
import numpy as np
import os
from pathlib import Path
from psychopy.hardware import keyboard
import serial

port = serial.Serial("COM3",baudrate =115200)


#######################Parameters for RDK stimuli
framerate = 60.0
speed_deg_sec =8.#8.0 #in degrees/s 12 in Pilly in Seitz
dot_density = 16.7#in deg-2 s-1
rayon_cercle = 9.0#in deg
number_of_dots = int(np.round(dot_density * np.pi * np.square(rayon_cercle) * (1/framerate)))
#######################

expName="RDK_SAT_task"
expInfo={'participant':''}

expName='RDK_task'
expInfo={'participant':''}
dlg=gui.DlgFromDict(dictionary=expInfo,title=expName)
if dlg.OK==False: core.quit() #user pressed cancel

win = visual.Window(fullscr=True, monitor='mathieu',size=(1920, 1080), color=[0,0,0], colorSpace='rgb255', units='deg')
police = 'Consolas'
win.setMouseVisible(False)

# initialize keyboards
resp = keyboard.Keyboard()
defaultKeyboard = keyboard.Keyboard()#for escaping only


dots=visual.DotStim(win=win, name='random_dots',
    nDots= number_of_dots, 
    dotSize=4,
    units = 'deg',
    speed= speed_deg_sec/framerate,
    dir= 0, 
    coherence= 0.0,
    fieldPos=[0.0, 0.0],
    fieldSize=rayon_cercle*2,
    fieldShape='circle',
    signalDots='different', 
    noiseDots='position', 
    dotLife= -1,
    color=[255,255,255], colorSpace='rgb255')

kb_delay=0
dots.setFieldCoherence(0.2)
dots.setDir(0)#0 (right) or 180 (left)   


win.callOnFlip(resp.clock.reset)
win.callOnFlip(port.write,b'1')#trigger for stimulus onset
win.callOnFlip(resp.clearEvents, eventType='keyboard')
while True:   

    if kb_delay==1:#check keyboard buffer AFTER first draw of stimulus and clock reset        
        theseKeys = resp.getKeys(keyList=['1','2'], waitRelease=False)
        if len(theseKeys):
            theseKeys = theseKeys[0] 
            if theseKeys.name == '1':#left button press
                port.write(b'2')#trigger for left response
            if theseKeys.name == '2':#right button press
                port.write(b'3')#trigger for right response
            break
        if defaultKeyboard.getKeys(keyList=["escape"], waitRelease=False):
            core.quit()              
    
    dots.draw()
    win.flip()  
    if kb_delay == 0:
        kb_delay = 1      
core.quit()