Skip to content

Knife Game with DC Motor HAT and Solenoids

fatmonky edited this page Sep 3, 2019 · 13 revisions

Summary

A mixed reality application of the knife game (https://en.wikipedia.org/wiki/Knife_game), demonstrating the impact of visual & audio perception on tactile perception.

Concept

The user puts their left hand's index finger in the case, and the experience starts. On the screen, the user can see an image of a virtual hand, with a virtual knife stabbing to the left and right of the virtual index finger. This is accompanied by an audible "tock...tock...tock", accompanied by the slight vibrations felt by the physical hand. Suddenly the knife on the screen stabs into the virtual finger, releasing virtual blood. At the same time the user feels a stab on the user's physical finger.

Here is a GIF of what the setup looked like in motion. GIF of the setup in motion

And here is a slow motion GIF of the solenoids working (they are too fast for a regular motion recording!).

Working solenoids

Technical Setup

This interaction has an animation happening on screen triggering something in the physical world. Because of this, Unity first connects to a Spacebrew server on the same laptop it is running on. This decision was taken in order to reduce the latency between the animation and the haptic feedback from the solenoids. Spacebrew communicates with the Raspberry Pi which is connected to the same network as the laptop. Finally, the Raspberry Pi triggers the solenoid every time it receives the appropriate message from Spacebrew.

Instructions

Pre-Requisites

Hardware

  • 1x Raspberry Pi incl. Power Cable
  • 1x Stepper & DC Motor HAT
  • 3x Solenoids
  • 3x Male-Male Jumper Wires (to connect to ground)
  • 3x Male-Male Jumper Wires (to connect to power)
  • External Web Cam
  • MDF to build case

Software

Step 1: Connecting Stepper & DC Motor HAT with Raspberry Pi to control solenoids easily

Use example code https://learn.adafruit.com/adafruit-dc-and-stepper-motor-hat-for-raspberry-pi?view=all

Step 2: Write Python scripts to control solenoids

See the Python script below. Documentation in comments (preceded by #). This script needs to be installed in the Pi (with the Motor HAT). To do that, we copied the script in our computer's compiler, logged into the Raspberry Pi (via Terminal, using ssh pi@Pi's IP address), used the nano command to create a new Python file (e.g. "nano TestScript.py"), pasted the script into the new file, and ran the script using the python command (e.g. "python TestScript.py") Note: please note that space and line indents are a critical part of Python's syntax. The code below will need to be refactored and formatted to properly compile.

import sys import time from pySpacebrew.spacebrew import Spacebrew import RPi.GPIO as GPIO

#Setup Motor Hat from Adafruit_MotorHAT import Adafruit_MotorHAT, Adafruit_DCMotor import atexit

# create a default object, no changes to I2C address or frequency mh = Adafruit_MotorHAT(addr=0x60)

# recommended for auto-disabling motors on shutdown! def turnOffMotors(): mh.getMotor(1).run(Adafruit_MotorHAT.RELEASE) mh.getMotor(2).run(Adafruit_MotorHAT.RELEASE) mh.getMotor(3).run(Adafruit_MotorHAT.RELEASE)

atexit.register(turnOffMotors)

#Declare motors connected to the Motor Hat myMotorLeft = mh.getMotor(1) myMotorCenter = mh.getMotor(2) myMotorRight = mh.getMotor(3) myMotorLeft.setSpeed(150) myMotorCenter.setSpeed(255) myMotorRight.setSpeed(150)

# Setup Spacebrew with Publishers and Subscribers brew = Spacebrew("D&S-YUXI_Solenoid", description="Python Solenoid Motor Hat", $ brew.addSubscriber("flipMotorLeft", "boolean") brew.addSubscriber("flipMotorCenter", "boolean") brew.addSubscriber("flipMotorRight", "boolean")

GPIO.setwarnings(False) GPIO.setmode(GPIO.BCM) GPIO.setup(18, GPIO.IN) #down is False alreadySent = False # to 'debounce' the button

def handleBooleanLeft(value): print("Received: "+str(value)) if (value == 'true' or str(value) == 'True'): ActivateMotorLeft()

def handleBooleanCenter(value): print("Received: "+str(value)) if (value == 'true' or str(value) == 'True'): ActivateMotorCenter()

def handleBooleanRight(value): print("Received: "+str(value)) if (value == 'true' or str(value) == 'True'): ActivateMotorRight()

def ActivateMotorLeft(): print("Forward Left! ") myMotorLeft.run(Adafruit_MotorHAT.BACKWARD) print("Backward Left! ") myMotorLeft.run(Adafruit_MotorHAT.RELEASE) print("Sleeping Left")

def ActivateMotorCenter(): print("Forward Center! ") myMotorCenter.run(Adafruit_MotorHAT.BACKWARD) print("Backward Center! ") myMotorCenter.run(Adafruit_MotorHAT.RELEASE) print("Sleeping Center")

def ActivateMotorRight(): print("Forward Right! ") myMotorRight.run(Adafruit_MotorHAT.BACKWARD) print("Backward Right! ") myMotorRight.run(Adafruit_MotorHAT.RELEASE) print("Sleeping Right")

# for handling messages coming through spacebrew brew.subscribe("flipMotorLeft", handleBooleanLeft) brew.subscribe("flipMotorCenter", handleBooleanCenter) brew.subscribe("flipMotorRight", handleBooleanRight)

try: brew.start() while True: print("Staying alive") time.sleep(0.1)

finally: brew.stop()

Step 3: Unity

The scene was built on top of the base scene on the YUXI examples to take advantage of the Spacebrew connectivity.

Search for virtual hand and virtual knife on Google Poly (https://poly.google.com/). Animate the virtual knife by adding an Animator Component. Add an animation clip: record an animation clip of the knife going back-and-forth between the virtual hand's index finger, by recording in the Animation window and manually adjusting the knife's Position in the x, y, and z-axis on the Animation record window (https://www.youtube.com/watch?v=JeZkctmoBPw)

You will need to hard code the position (x,y and z coordinates) of the virtual assets (virtual hand and virtual knife). Do note there is a glitch: when Unity isn't played, the position of the virtual assets is not the same as when the Unity file is played. Because of this positioning discrepancy, we moved away from triggering the solenoids through collision detection on Unity and instead decided for triggering the solenoids via the animation itself, as shown below. Unity animation events setup

The code you are calling at each animation event is written on a script attached to the knife object. The code, described below, calls a function on the SpacebrewObject's Event script. Code on the knife

On the SpacebrewEvents script, attached to the SpacebrewObject, you add these functions, which are called by the previous animation. Code on SpacebrewEvents

On SpacebrewObject, you have to adjust the IP address of the server you're connecting to and create 3 publishers, one for each solenoid that you are activating. SpacebrewObject setup

It is important to pay attention to the names of the functions and the publishers. If you change the name of the publisher, make sure you also change the name that is being called by the function.

Step 4: Build set-up in the physical world

Laser cut MDF to fit three solenoids in an MDF box, which can fit a finger. Use black foam to block the casing entrance, to ensure that the index finger can only fit under the center solenoid. Ensure that the fiducial is visible to the webcam. For best effect, cover the entire setup such that the user cannot see their hands after putting it into the casing.

Clone this wiki locally