Key release event missed occasionally

I programmed an experiment where I track key presses and releases during the response period to make a dial move. Basically, it starts updating the positions of two icons on a circle when a button is pressed, and stops doing this when you release that button (which is the ‘final’ position response of the subject). I used the new psychopy.hardware.keyboard for this.

See full code below. The dial starts turning after a keypress, immediately some triggers are sent to the EEG and eye-tracker, and then the code enters a while loop to track the key release:
while key_release == [] and count < maxTurn:

In this loop the positions are updated while the key has not been released yet and while a certain endpoint is not reached (count < maxTurn).

Now to the issue: most of the times this works perfectly. But occasionally the key release seems to be missed. So you release the key, but the dial keeps turning until this endpoint is reached.

Any ideas why this happens?

def presentResponse(upperOrRight, trialType, condition):

    kb.clearEvents()

    triggerProbe = setCorrectTrigger(blockType = 'regular', 
                                    trialType = trialType, 
                                    condition = condition, 
                                    moment = 'probe') # defines the correct trigger

    mywin.callOnFlip(portBioSemi.setData, triggerProbe) # send trigger to EEG on flip
    mywin.callOnFlip(tracker.send_message, 'trig' + str(triggerProbe)) # send trigger to eye-tracker on flip


    if upperOrRight == 0: # upper trial
        createSemicircle.pos = upper_semiCircle
        createSemicircle.ori = 0
        turnUpper.pos = upper_turnUpper
        turnLower.pos = upper_turnLower
    elif upperOrRight == 1: # right trial
        createSemicircle.pos = right_semiCircle
        createSemicircle.ori = -90
        turnUpper.pos = right_turnUpper
        turnLower.pos = right_turnLower 

    pause = False
    stop = False
    respTime = []
    key_release = []
    
    clockws = False
    counterclockws = False 
    count = 0 # positions not updated yet

    responseCircle.setAutoDraw(True) 
    createSemicircle.setAutoDraw(True) 
    turnLower.setAutoDraw(True) 
    turnUpper.setAutoDraw(True) 
    probe.setAutoDraw(True) 

    startTime = time.time()
    mywin.flip()
    core.wait(2/monitorHZ) # two frames, 0.008 s
    portBioSemi.setData(0) # turn off probe trigger

    key_press = event.waitKeys(keyList = ['z', 'm', 'q', 'escape']) # wait for key press
    
    if 'z' in key_press: # if z, turn to left
        respTime = time.time()
        triggerResp = setCorrectTrigger(blockType = 'regular', 
                                        trialType = trialType, 
                                        condition = condition, 
                                        moment = 'leftpress')
        counterclockws = True

        portBioSemi.setData(triggerResp) #count 1
        #tracker.send_message('trig' + str(triggerResp))
        core.wait(2/monitorHZ) # two frames, 0.008 s
        portBioSemi.setData(0) # turn off probe trigger

        while key_release == [] and count < maxTurn:

            key_release = kb.getKeys(keyList = ['z'], waitRelease = True, clear = False)

            positions = turnPositionsCircle(turnUpper.pos, turnLower.pos, thisTurn = -radStep)
            turnUpper.pos = positions[0]
            turnLower.pos = positions[1]

            count += 1 # one step updated

            mywin.flip()
        
        endTime = time.time()

    elif 'm' in key_press: # if m turn right
        respTime = time.time()
        triggerResp = setCorrectTrigger(blockType = 'regular', 
                                        trialType = trialType, 
                                        condition = condition, 
                                        moment = 'rightpress')
        clockws = True

        portBioSemi.setData(triggerResp) #count 1
        tracker.send_message('trig' + str(triggerResp))
        core.wait(2/monitorHZ) # two frames, 0.008 s
        portBioSemi.setData(0) # turn off probe trigger

        while key_release == [] and count < maxTurn:

            key_release = kb.getKeys(keyList = ['m'], waitRelease = True, clear = False)

            positions = turnPositionsCircle(turnUpper.pos, turnLower.pos, thisTurn = radStep)
            turnUpper.pos = positions[0]
            turnLower.pos = positions[1]  

            count += 1 # one step updated
            mywin.flip()
        
        endTime = time.time()

    elif 'q' in key_press:
        core.quit()
        sys.exit() 

    elif 'escape' in key_press:
        responseCircle.setAutoDraw(False) 
        createSemicircle.setAutoDraw(False) 
        turnLower.setAutoDraw(False) 
        turnUpper.setAutoDraw(False) 
        probe.setAutoDraw(False) 

        pauseText.draw()
        mywin.flip()

        key_pause = event.waitKeys(keyList = 'space')

        respTime = 0
        endTime = 0

    if respTime != [] and endTime != []:
        responseTime = respTime - startTime
        responseTime *= 100
        responseTime = round(responseTime)

        responseDuration = endTime - respTime
        responseDuration *=100
        responseDuration = round(responseDuration)

        responseCircle.setAutoDraw(False) 
        createSemicircle.setAutoDraw(False) 
        turnLower.setAutoDraw(False) 
        turnUpper.setAutoDraw(False) 
        probe.setAutoDraw(False) 
        
    return count, counterclockws, startTime, respTime, endTime, responseTime, responseDuration, triggerProbe, triggerResp```

@Michael

This isn’t related to your issue, but you should avoid code like this, it can easily not be accurate enough to keep up precisely with the number of frames you want:

Instead just call win.flip() the number of times that you need. In your case, just replace the lines above with:

for _ in range(3): # one flip, followed by two more.
   win.flip()

Otherwise I’m not sure what is going wrong here, but a warning sign for me is mixing two ways of interfacing with the keyboard: the Keyboard class and the older event module. I know that the Keyboard class doesn’t have a .waitKeys() method, but it is probably better to implement that yourself rather than mix the behaviour of these two approaches. I would worry that they could interact and behave differently in subtle ways.