-
Notifications
You must be signed in to change notification settings - Fork 11
Servos Control a Virtual Marble
This is a tutorial to set up a mixed reality game using two analog feedback servo motors. These servo motors can be used as a potentiometer to set values in addition to being just output devices. In this example, the servos are used as input devices allowing you to interact with virtual objects projected on your desk as well as output devices displaying the activity of a virtual object, a marble looping on a path.
One of the servo motors is used as a physical and virtual gate input to control flow of virtual objects. A second servo is used as a turnstile door acting as an output moving the servo whenever a virtual object comes in contact with it.
This example will let you build the grey box level of an experience giving you inputs an d outputs on the marble's activity that can be used as a base to build more complex input or output interactions with virtual objects living in unity.
To achieve this, we will use Raspberry Pi to connect to servos to Unity using Spacebrew as a broker and a standard computer webcam to project virtual elements on a Mixed Reality Fiducial
- 1 X Raspberry Pi
- 1 X Servo/PMW Pi Hat https://learn.adafruit.com/adafruit-16-channel-pwm-servo-hat-for-raspberry-pi/overview
- 1 X MCP3008 ADC
- 2 X Analog feedback servo motors, Batan 2122 https://learn.adafruit.com/analog-feedback-servos
- 1 X 5V, 2A power plug (or less)
- Install the Servo Hat as explained here : https://learn.adafruit.com/adafruit-16-channel-pwm-servo-hat-for-raspberry-pi/overview
Make sure to : Solder male pins onto the Servo hat to enable it to be mounted onto the Raspberry pi. Also solder a few male pins on the Servo hat to enable connections to the two servo motors.
A few differences from the tutorial linked above :
- You only need to install pins for servos 0,1,2,3.
- Make sure to install female pins over Ground, 3.3V and 5V on the Servo hat.
- Add an entire strip of female pins over the labeled row of the Servo hat to be able to access the Raspberry Pi's regular pins.
-
Attach the servo hat onto Raspberry pi and connect the two servos on rows 0 and 1 of the servo hat .
-
To read the servo's potentiometer values, you will need to set up a circuit that transform the analog values from the white wire of your servo into digital values. This is done by setting an Analog to Digital Converter (ADC) circuit using an MCP3008.
To do that Place the MCP3008 on a breadboard and follow the instructions in the link below to connect it to the Servo hat.
What is different :
-
Note that the example we built from is for a potentiometer. The fact that the Servos's pin's are already connected on the Servo Hat means that we need to power and ground the MCP3008 directly from the Servo Hat. Use the 3.3V and the ground you set up with female pins on the Hat to do that.
-
Same goes with the Raspberry Pi's pin's access them directly from the row of female pins you mounted on the Servo Hat.
-
What is the potentiometer's middle pin in the example circuit used in the adafruit tutorial is equivalent to your servo's white cable.
This is what it should look like (refer to circuit photo above to see where the wires go)
- Power the raspberry pi and the servo hat using 5V power plug. 2A are recommended but you can make it work with anything running 5V and at least 600mA for a couple of Servos ;)
-
To Move the servo1 to get a boolean value: Follow the library installations from [here](https://learn.adafruit.com/raspberry-pi-analog-to-digital-converters/mcp3008 and run simplest.py). Of the 7 channel values displayed, we are only interested in the values from channel 0, which is mcp.read_adc(0). Clean up the code to only display the values from channel 0 and store it in a variable x. Create a function gatestat() that prints gate open or gate close depending on whether the servo1 value is less than or greater than 350. These two values will now need to be sent to spacebrew as boolean values.
-
Send the boolean to spacebrew: Add publisher ‘PMW ServoPot’ in the python code. In the spacebrew page you should be able to see the publisher PMW ServoPot. When you run the code, you should be able to see signal next to PMW Servo Pot every time the gate opens or closes on the servo.
-
Add subscriber for servo2, create dummy ping : Add subscriber ‘count_peeps’ in the python code. Go to http://spacebrew.github.io/spacebrew.js/spacebrew_button/index.html?server=192.168.1.165&name=count_peeps. When you click on the button, it creates a dummy ping on the spacebrew page next to subscriber count peeps.
-
Move servo2 depending on boolean from spacebrew ‘count peeps’: Follow the instructions on here to get the servo2 to move. Now you can get the servo to move a certain way when you click the button on spacebrew.
The first step is to create a GameObject and make it move on a loop following a defined path.
First, create a Sphere (Game Object > 3D Object > Sphere and name it Player.
Then, follow this tutorial or follow the steps below. Make sure to use our code (will come handy for the next steps).
https://www.youtube.com/watch?v=fKWTpi70a_E (turn the video sound off for your own sake)
Player Setup :
-
Tag the player as Player
-
Place the player on a plane
-
Add a Rigidbody Component to the player and uncheck gravity box. Make sure to check the Is Kinematic box or the Player will not be able to loop infinitely on its path.
-
Add A new Script Component to the player, name it MoveBall and give it the following C# code :
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MoveBall : MonoBehaviour {
public Transform[] target;
public float speed;
private int current;
//vessel to call a boolean from another script
private GateTriggerEvent Gte;
//public Rigidbody rb;
// Use this for initialization
void Start () {
//this part let us stop the ball when it touches the gate
//call boolean from another script and define it in this script's vessel
GameObject go = GameObject.Find ("Gate"); // the name of the game object where the boolean is created object
Gte = go.GetComponent <GateTriggerEvent> ();
}
void Update () {
//if the ball is not touching gate
if (Gte.detectionGate == false)
{
//move the ball from target to targer
if (transform.position != target[current].position)
{
Vector3 pos = Vector3.MoveTowards(transform.position,target[current].position, speed * Time.deltaTime);
GetComponent<Rigidbody>().MovePosition(pos);
}
else
{
current = (current + 1) % target.Length;
}
}
}
}
After compiling the code, your script box should look like this :
-
You can set the speed to something that suits you, 50 should be enough
-
Set target size to 4. Now it's time to place targets to set your player's path !
-
Create an Empty Game Object. Name it target (name must be similar to the one in the script) and place it on the same Y as the Player. Then duplicate your target object so that you have target, target (1),...., target(3).
-
Drag the targets to the MoveBall script in Player's component panel.
-
Try your unity simulation. If the Player doesn't go from target make sure you are using kinematic and adjust the target's to make them on the same level as the player.
note : unity doesn't like this code even though it works for this task. Expect an error about ArrayIndex to print when you run the script.
Now is time to create a turnstile that will react on each passage of the marble by activating the Servo. This could also be used to set a counter or trigger any event when the ball hit's the element !
The gist is that we will use collisions to send booleans to other unity scripts and trigger events, spacebrew events in our case.
-
Create a game object and place it on the Player's path.
-
Uncheck the mesh render box. The element is now invisible.
-
Check the mesh box again, you'll need to see what you're doing when setting it up.
-
Add a Box Collider Component to the Turnstile and check the Is Trigger box.
-
Create a script named TurnstileTriggerEvent. The script will have a public boolean that could be used by other scripts as well as a public Game Object, which will make the Turnstile listen to our Player.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TurnstileTriggerEvent : MonoBehaviour {
public bool detectionTurnstile;
public GameObject PlayerObject;
void Start()
{
detectionTurnstile= false;
}
void OnTriggerEnter(Collider other)
{
if(other.CompareTag("Player"))
{
detectionTurnstile = true ;
}
}
void OnTriggerExit(Collider other)
{
if(other.CompareTag("Player"))
{
detectionTurnstile= false ;
}
}
}
The detectionTurnstile boolean box in the Script Component should now be checked when the Player touches the Turnstile. Now let's use it to send an event in SpaceBrew.
If you want to add physics to the way the Turnstile is displayed, make the Turnstile Object we just built invisible (uncheck mesh) and create another Game object that will handle the pysics.
First we need to set up the SpaceBrewEvents script so that it can read the public boolean detectionTurnstile.
- first, enter this at the top of public class SpacebrewEvents : MonoBehaviour
public class SpacebrewEvents : MonoBehaviour {
SpacebrewClient sbClient;
//bool lightState = false;
// create an empty vessel to store the TurnstileTriggerEvent script here
private TurnstileTriggerEvent Tte;
// for debouncing
bool TurnstilePrevious;
note that you don't always need bool TurnstilePrevious but it is part of the logic we will use here to send only once to spacebrew. You don't need that if you're reading an already debounced boolean.
and in void start of the same script
void Start () {
GameObject go = GameObject.Find ("SpacebrewObject"); // the name of your client object
sbClient = go.GetComponent <SpacebrewClient> ();
GameObject gogo = GameObject.Find ("Turnstile");
Tte = gogo.GetComponent <TurnstileTriggerEvent> ();
TurnstilePrevious = false;
}
The SpaceBrewEvents script can now access the script running on our Turnstile object.
Now we can use the detectionTurnstile boolean for the logic of our Spacebrew publishing.
- Simply put an if statement in the void update function to publish messages to spacebrew.
if (Tte.detectionTurnstile == true && Tte.detectionTurnstile != TurnstilePrevious)
{
sbClient.sendMessage("TurnstileEvent", "boolean", "true");
}
TurnstilePrevious = Tte.detectionTurnstile;
That's it, now everytime the Marble passes through our Turnstile Servo it makes the Servo move !
(gif)
We now want to create a gate that will interrupt the path of the Marble if it is close. The first step is to make the Player stop when it touches a GameObject we will call Gate.
The logic we will use on the Gate size is the same as the one we used for the Turnstile. Reiterate the steps of the "Creating the turnstile and making it react to the passage of the Sphere." part.
Once you have done that :
- Look at this piece of the code in the MoveBall script, we already got everything set up and you should be able to understand the logic of how we access the boolean from what is done in step 1 of the previous section.
In MoveBall.cs
public class MoveBall : MonoBehaviour {
private GateTriggerEvent Gte;
void Start () {
//this part let us stop the ball when it touches the gate
//call boolean from another script and define it in this script's vessel
GameObject go = GameObject.Find ("Gate"); // the name of the game object where the boolean is created object
Gte = go.GetComponent <GateTriggerEvent> ();
}
void Update () {
//if the ball is not touching gate
if (Gte.detectionGate == false) {
... }
The bits are here in the Python code and input from the Servos is sent to SpaceBrew. From there it is simple to receive the message in Unity using SpaceBrewEvent script in the SpaceBrew GameObject.
We are still working on using that input to make the Gate GameObject move in Unity, but feel free to add to the tutorial if you get the last bit working!