Connecting Unity with Python controlled shock hardware

Hi,

I am a PhD student designing an environment in unity to test human threat processing. I have developed an environment where the user is moved towards certain tokens that when collected, either reward the user or gives them an electric shock. However, I am new to Unity and programming in general, with a lack of knowledge in how to connect hardware to use in unity. I have tried to find answers but cant find the help I need. We are using a shock machine that has been used in previous experiments to shock participants using some Python code ran in PsychoPy. My question is, is it possible to connect Unity to PsychoPy to run this script, or connecting Unity to the shock machine directly?

This is the shock machine we use: SHK shockers

This is my Unity code that moves the user (the void Punish() is where I would like to put the code to trigger the shock):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class Level1Script : MonoBehaviour
{
    //Variables for reward
   // public TextMeshProUGUI rewardScore;
    private int count;

    //Variables for mixed
    private int mixedCount;

    //Variables for navigation
    public UnityEngine.AI.NavMeshAgent agent;
    public GameObject[] waypoints; //defining waypoints
    private int waypointInd;

    void Awake()
    {
        //Set Nav Variables
        agent = GetComponent<UnityEngine.AI.NavMeshAgent>();
        waypoints = GameObject.FindGameObjectsWithTag("Waypoint"); //finds any gameobject labbeled waypoint
        waypointInd = 0;

        //Set Reward Variables
        count = 0;
      //  SetCountText();

        //Set mixed variables
        mixedCount = 0;

        //Start movement
        agent.SetDestination(waypoints[waypointInd].transform.position);
    }

    void Update()
    {

        if (Vector3.Distance(this.transform.position, waypoints[waypointInd].transform.position) >= 1)// looks at distance between this character and the waypoint
        {
            agent.SetDestination(waypoints[waypointInd].transform.position);// makes the agent go to waypoint
        
        }

        else if (Vector3.Distance(this.transform.position, waypoints[waypointInd].transform.position) <= 1)
        {
            waypointInd = waypointInd + 1;
        }

        if (waypointInd >= waypoints.Length)
        { waypointInd = 0; }
    }

    void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("RewardToken"))
        {
            other.gameObject.SetActive(false);
            Reward();
        }

        if (other.gameObject.CompareTag("PunishmentToken"))
        {
            other.gameObject.SetActive(false);
            Punish();
        }

        if (other.gameObject.CompareTag("MixedToken"))
        {
            other.gameObject.SetActive(false);
            count = count + 1;            
        }
    }

    //void SetCountText()
    //{
    //    rewardScore.text = "Score: " + count.ToString();

    //}

    void Reward()
    {
        Debug.Log("Rewarded");
        count = count + 1;
       // SetCountText();
    }

    void Punish()
    { Debug.Log("ZappyZapZapZap"); }
}

This is the python code used in the previous experiment:

# import modules#
from psychopy import visual, core, monitors, event, sound
import numpy as np
import serial
import time
import random

ser=serial.Serial()
ser.port='COM8'
ser.open()

# VERSION
version=1 # 1=PNUPNU, 2=UPNUPN
power='75q'#intensity as string # 255 MAX

if int(power)>200:
    core.quit()

if version==1:
    COND_LIST=['P','N','U','P','N','U']
elif version==2:
    COND_LIST=['U','N','P','U','N','P']

# create objects e.g. window, sound object, text objects, fixations...
frameRate = 60.0
myMon = monitors.Monitor('LabComputer', distance=57)
myWin = visual.Window(size=(1280, 800), monitor = myMon, color=[1, 1, 1],colorSpace='rgb', units='pix', fullscr=True)
myMouse=event.Mouse(visible=0,win=myWin)
thisText=visual.TextStim(myWin, text='A', pos=[0,165],color=[-1,-1,-1],height=30,wrapWidth=1500)
thisCond=visual.TextStim(myWin, text='A', pos=[0,-300],color=[-1,-1,-1],height=30,wrapWidth=1500)
instructions=visual.TextStim(myWin, text='', pos=[0,0],color=[-1,-1,-1],height=30,wrapWidth=1500)
white_noise = sound.Sound('C:\\Users\\eegadmin\\Desktop\\SUMICH_SHOCK\\bang.wav')

# define directory
main_directory= 'C:\\Users\\eegadmin\\Desktop\\SHOCK\\'
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # 

#create output file
Output = main_directory + '_data.txt'
myfile2=open(Output,'w')


counts=['6','5','4','3','2','1']

instructions.text= 'PUT SOMETHING HERE, PRESS SPACE TO BEGIN 

this is now a new line
and now this is’

nBlocks=5

responded=0
while responded==0:
    instructions.draw()
    myWin.flip()
    for key in event.getKeys():
        if key in ['space']:
            responded=1

# MAIN COND LIST
for x in range(0,len(COND_LIST)):
    
    whichTrialProbe=[1,1,1,1,0]
    random.shuffle(whichTrialProbe)
    
    for b in range (0,nBlocks):
        
        print ('BEGIN BLOCK........................')
        thisISI=random.randint(11,17)
        
        if COND_LIST[x]=='P':
            thisCond.text='Shock at 1'
            whenShock=1
        elif COND_LIST[x]=='N':
            thisCond.text='No Shock'
            whenShock=9999
        elif COND_LIST[x]=='U':
            thisCond.text='Shock at any time'
            potential=[5,4,3,2,1,0]
            random.shuffle(potential)
            whenShock=potential[0]
        
        # IF SHOCK IS IN ISI
        if whenShock==0:
            ISIshock=random.randint(1,thisISI-1)
            probe1=[6,5,4,3,2,1]
            random.shuffle(probe1)
            probe1=probe1[0]
            accepted=0
            while accepted==0:
                probe2=random.randint(1,thisISI-1)
                if probe2!=ISIshock and probe2<ISIshock:
                    accepted=1
                if probe2!=ISIshock and ISIshock-probe2>10:
                    accepted=1

        # IF NO SHOCK
        if whenShock==9999:
            probe1=[6,5,4,3,2,1]
            random.shuffle(probe1)
            probe1=probe1[0]
            probe2=random.randint(1,thisISI-1)
            
        # IF SHOCK IN COUNTDOWN
        if whenShock>0 and whenShock <9999:
            ISIshock=9999
            if whenShock==5:
                probe1=6
                probe2=random.randint(5,thisISI-1)
            if whenShock==4:
                probe1=[5,6]
                random.shuffle(probe1)
                probe1=probe1[0]
                probe2=random.randint(6,thisISI-1)
            if whenShock==3:
                probe1=[4,5,6]
                random.shuffle(probe1)
                probe1=probe1[0]
                probe2=random.randint(7,thisISI-1)
            if whenShock==2:
                probe1=[3,4,5,6]
                random.shuffle(probe1)
                probe1=probe1[0]
                probe2=random.randint(8,thisISI-1)
            if whenShock==1:
                probe1=[2,3,4,5,6]
                random.shuffle(probe1)
                probe1=probe1[0]
                probe2=random.randint(9,thisISI-1)
                
        #print (str(COND_LIST[x]) + '<< THIS CONDITION')
        #print (str(whichTrialProbe[x])+ '<< if 0, then no probes delivered this trial')
        #print (str(thisISI) + '<< LENGTH OF ISI in seconds')
        #print (str(whenShock) + '<< WHEN SHOCK, if this is 0, then shock in ISI')
        #print (str(ISIshock) + '<< IF above is 0, then ISI shock is at...')
        #print (str(probe1) + '<< probe 1')
        #print (str(probe2) + '<< probe 2')
        
        
        #myfile2.write(str(COND_LIST[x]) + '<  this condition' + '

')
#myfile2.write(str(whichTrialProbe) + ‘< whether probes or not’ + ’
')
#myfile2.write(str(thisISI) + ‘< length of ISI’ + ’
')
#myfile2.write(str(whenShock) + ‘< either a number or zero, if NOT number then shock in ISI’ + ’
')
#myfile2.write(str(ISIshock) + ‘< IF ISI shock, then shock at this seconds’ + ’
')
#myfile2.write(str(probe1) + ‘< timing of probe1 (on which count)’ + ’
')
#myfile2.write(str(probe2) + ‘< timing of probe2 (in which second in ISI’ + ’
')

        # COUNTDOWN
        for i in range(0,len(counts)):
            thisText.text=counts

for n in range(0,int(frameRate*1)):
if COND_LIST[x]!=‘N’:
if i==abs(whenShock-6) and whenShock!=0 and n==45:
for ON in range(0,1):
ser.write(power)

if i==abs(probe1-6) and whichTrialProbe[x]==1:
white_noise.play()

thisText.draw()
thisCond.draw()
myWin.flip()

thisText.text=‘WAIT…’

print (‘ISI BEGIN…’)

# ISI
for ISI in range(0,int(frameRate*thisISI)):
for key in event.getKeys():
if key in [‘q’]:
myWin.close()
myfile2.close()
core.quit()

if whenShock==0:
if ISI==int(ISIshock*60):
for ON in range(0,1): #change to 24
ser.write(power)
if ISI==int(probe2*60) and whichTrialProbe[x]==1:
white_noise.play()

thisText.draw()
thisCond.draw()
myWin.flip()

if whenShock==0:
remaining=1
if ISIshock==10:
remaining=1
elif ISIshock==11:
remaining=2
elif ISIshock==12:
remaining=3
elif ISIshock==13:
remaining=4
elif ISIshock==14:
remaining=5
elif ISIshock==15:
remaining=6
elif ISIshock==16:
remaining=7
elif ISIshock==17:
remaining=8

thisText.text=‘WAIT…’
for remain in range(0,int(frameRate*remaining)):
thisText.draw()
myWin.flip()

ser.close()
myWin.close()
core.quit()
Cheers,
Brad

It seems like the device connects to the USB port so you could probably communicate with it as a HID device. Check out the new input manager…