Preparing The Environment

IDE

First we need to create our environment to work properly. We downloaded and installed the following requirements:

-Python 2.7 + 'python ./setup.py install'
-Pyserial 2.7 + 'install'
- Downloaded and installed pyserial following this tutorial (https://learn.adafruit. com/arduino-lesson-17-email-sending-movement-detector/installing-python-and- pyserial):
- Download pyserial-2.7.tar.gz (md5) (https://pypi.python.org/pypi/pyserial) and save it somewhere on the computer
-FTDI Drivers + 'install'
-Nadya Peek's Gestalt Master and save it
-086 and save it

Open command Prompt wherever you have saved the Pyserial-2.7 folder:
'cd' into the pyserial-2.7 folder, then run the command: 'sudo python setup.py install'

TESTING CONNECTIONS

Testing RS 485 connections

LISTING DEVICES

List usbPorts : 'ls /dev/.*'
In the example file, change our port's name

LINKS TO TUTORIALS WE HAVE USED:

Nadya's Library

Hardware

Electronics

The FabNet

Fabnet is a multi-drop network, meaning that multiple modules (a.k.a. nodes) share a single set of communication wires. Signalling is differential based on the RS-485 specification. Each node is assigned a four-byte IP address which uniquely identifies it over the network. Besides communication, Fabnet provides power at two voltages: high voltage (12V - 24V) is intended to drive motors, lamps and actuators, while low voltage (7.5V) supplies power to the logic circuits of the nodes.

WHAT WE NEED

Establishing connectivity between nodes and a computer, and providing proper power, requires several components: high and low voltage power supplies, a USB-RS485 converter cable, and an adaptor board (076-000A) with bias resistors.
OUR MACHINE have 3 Gestalt Nodes for control 3 AQS Nema 17 and for prepare all the stuff we made a BOM downloable here :

POWER SUPLLY

Our motors need 700 mAh minimun for works and the Gestalt Nodes as our instructor said us no to much so we decided to use an 12V- W Power supply for our Design.
2. The RS 485 : The FTDI cable provided is a little different than the other FTDI cables we've been using; it is an RS-485 version. Here's its pinout for reference:

FABNET ADAPTOR BOARD ASSEMBLY DRAWING (076-000A)

FABNET ADAPTOR BOARD ASSEMBLY DRAWING (076-000A)

Files

Fabnet BOM

The Motors

A stepper motor or step motor or stepping motor is a brushless DC electric motor that divides a full rotation into a number of equal steps. The motor's position can then be commanded to move and hold at one of these steps without any feedback sensor (an open-loop controller), as long as the motor is carefully sized to the application in respect to torque and speed.
DC brushed motors rotate continuously when DC voltage is applied to their terminals. The stepper motor is known by its property to convert a train of input pulses (typically square wave pulses) into a precisely defined increment in the shaft position. Each pulse moves the shaft through a fixed angle.

There are three main types of stepper motors

1. Permanent magnet stepper
2. Hybrid synchronous stepper
3. Variable reluctance stepper

Permanent magnet motors use a permanent magnet (PM) in the rotor and operate on the attraction or repulsion between the rotor PM and the stator electromagnets. Variable reluctance (VR) motors have a plain iron rotor and operate based on the principle that minimum reluctance occurs with minimum gap, hence the rotor points are attracted toward the stator magnet poles.

Performance

When current flows through one or more coils of the stator, a magnetic field is created creating the North-South poles. Then the rotor will be magnetically balanced by orienting its North-South poles towards the South-North poles of the stator. When the stator restores the orientation of its poles through a new received impulse towards its coils, the rotor will again move to balance magnetically. If this situation is maintained, we will obtain a permanent rotating movement of the shaft. The pitch angle depends on the relationship between the name of the magnetic poles of the stator and the name of the magnetic poles of the rotor.

Two Types: Unipolar and Bipolar

Unipolar

An unipolar stepper motor has one winding with center tap per phase. Each section of windings is switched on for each direction of magnetic field. Since in this arrangement a magnetic pole can be reversed without switching the direction of current, the commutation circuit can be made very simple (e.g., a single transistor) for each winding.

Bipolar

Bipolar motors have a single winding per phase. The current in a winding needs to be reversed in order to reverse a magnetic pole, so the driving circuit must be more complicated, typically with an H-bridge arrangement (however there are several off-the-shelf driver chips available to make this a simple affair). There are two leads per phase, none are common.

Sequence To Control Bipolar Stepper Motors

A bipolar stepper motor needs to reverse the current flowing through its coils in a given sequence to cause the shaft to move.

Sequence To Control Unipolar Stepper Motors

Simple Or Wave Drive

There are three sequences for controlling unipolar stepper motors.
Simple or wave drive: It is a sequence where one coil is activated at a time. This causes the engine to have a smoother pitch but instead has less torque and less retention.

Normal

Normal: It is the most used sequence and the one recommended by the manufacturers. With this sequence the motor advances one step at a time and there are always two coils activated. This results in greater torque and retention.

Half Step

Two coils are first activated and then only one and so on. This causes the motor to advance half the actual pitch. This translates into a smoother and more accurate turn.

IMPORTANT!

- CONNECT COMPONENTS WITHOUT CURRENT:

> FABNET TO POWER SUPPLY
> FABNET TO NODE 1
> NODE 1 TO NODE 2
- CONNECT USB TO COMPUTER and identify serial port
- change the serial port to mine in the example files (running in command line 'ls /dev/tty.usb*'to find it)
- SWITCH ON POWER SUPPLY
- RUN CODE IN TERMINAL:

Testing

htmaa folder
run singlenode.py example with:
'python singlenode.py'
Erase temporal file before testing each node
Repeat this operation for each one separately
Repeat this operation for each motor.

Identify each node in Network

When more than one node are attached to the same Fabnet, the command line will ask to identify each node, which is done by pressing the button on the selected node.
Some files will be created in your folder:
- test.vmp
- motionPladebugfile.txt
- 086-005a.pyc
Sometimes these files give you some problems. The best thing to do is to trash these files and restart the whole system. Every time these files are trashed you will need to re-identify each node.
If you don't plug in USB, power supply etc. in this order you might get errors while running the code, like one of the nodes not being recognised after setup.

Installing PySerial

Loading Single Node

Test

Installing PyGestalt

Running Our Program

TESTING XYPLOTTER EXAMPLES

'python xy_plotter.py'

TESTING XYZ EXAMPLES FROM TUTORIALS PAGE

'python pilu.py'

Code Used

Just below we have the codes we are using in our machine. Take a look!

1st - Example Code As The Base Of Our Code To Automate Our Machine

2nd - Parsing a Midi File

3rd - Starwars Theme Song Described With Our Machine

4th - Code Used To Automate Our Machine In Any Given Coordinates

5th - Code For Converting Midi Into Coordinates For Our Machine

6th - Final Code Used After Ajustments


Let's Start!

1st - Automating The Machine

We used the suggested example as the base of our code. I will be extracting the most relevant parts of the code



							# x axis has 30 notes 
							with 2mm of space between
							notes, UNITS: MM
x_notes = {
'C3': 0, 'D3': 2, 'G3': 4, 'A3': 6, 'B3': 8,
'C4': 10, 'D4': 12, 'E4': 14, 'F4': 16, 'Gb4': 18, 
'G4': 20, 'Ab4': 22, 'A4': 24, 'Bb4': 26, 'B4': 28,
'C5': 30, 'Db5': 32, 'D5': 34, 'Eb5': 36, 'E5': 38,
'F5': 40, 'Gb5': 42, 'G5': 44, 'Ab5': 46, 'A5': 48, 
'Bb5': 50, 'B5': 52,
'C6': 54, 'D6': 56, 'E6': 58
}

# y axis is the stepper motor that does the perforation it needs to move a full turn to do a perforation UNITS: rev
y_perforation = 1

# z axis is the roller motor, there is a 4mm space between each line of the song UNITS: MM
z_roller = 4



First, we create the variables that we will be using to generate our moves array. The X axis values in mm for each note of the music roll –stored in a associative array so I can consult the data with a simple command–, the value of the y axis in order to make a perforation and finally the distance in millimeters to each line of the roll.


 song = [['Bb5', 'Bb4', 'F5'], [], [], [], [], [], ['Bb4'],[], [], [], [],
 [], ['Bb4', 'F5', 'D5'], [], [], [], [], [], ['Bb4'], [], [], [], [], [],
 ['Eb5', 'Bb4'], [], ['D5'], [], ['C5'], [], ['Bb5', 'Bb4', 'F5'], [], [],
 [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'D5', 'F5'], [], [], [],
 [], [], ['Eb5', 'Bb4'], [], ['D5'], [], ['C5'], [], ['Eb5', 'Bb5', 'Bb4'],
 [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'D5', 'F5'], [],
 [], [], [], [], ['Eb5', 'C5'], [], ['B4', 'D5'], [], ['Eb5', 'C5'], [],
 ['A4', 'C5'], [], [], [], [], [], ['A4'], [], [], [], [], [], ['F4'],
 [], [], [], ['F4'], [], ['G4'], [], [], [], [], [], ['Bb4'], [], [],
 ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [], ['C5'], [], [],
 ['Bb4'], [], [], ['Bb4'], [], ['C5'], [], ['D5'], [], ['C5'], [], [], [], 
 ['G4'], [], ['A4'], [], [], [], [], [], ['F4'], [], [], [], ['F4'], [],
 ['G4'], [], [], [], [], [], ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'],
 [], [], ['D5'], [], [], ['C5'], [], [], ['Bb4'], [], [], ['Db5', 'F5'],
 [], [], [], [], [], ['A4', 'C5'], [], [], [], [], [], ['F4'], [], [], [],
 [], [], ['C4'], [], [], ['F4'], [], [], [], ['G4'], [], [], [], [], [],
 ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [],
 ['C5'], [], [], ['Bb4'], [], [], ['Bb4'], [], ['C5'], [], ['D5'], [], 
 ['C5'], [], [], [], ['G4'], [], ['F4', 'A4'], [], [], [], [], [], 
 ['F4', 'F5'], [], [], [], ['F5'], [], ['Bb5', 'Gb5'], [], [], [],
 ['F5', 'Ab5'], [], ['Eb5', 'Gb5'], [], [], [], ['Db5', 'F5'], [],
 ['Eb5', 'C5'], [], [], [], ['Bb4', 'Db5'], [], ['Ab4', 'C5'], [], [], [],
 ['Gb4', 'Bb4'], [], ['F4', 'F5']]
 



The song data is stored in a bidimensional array, first dimension are the lines of each song and second dimension are the notes within each line. I used this great online editor to understand the notes that the music box used and how the roll sheets are structured. By the way the above song array is the intro theme of Star Wars ;-)


 # JAVIBG
# Movement is defined
# 0. for each line in song
# 1. position the z_roller
# 2. if not empty then for each note
# 3. move the first x_note in the row
# 4. make the perforation with y_perforation one revolution
# 5. go back to the x_note 0
# 6. end for each note
# 7. end for each line

moves = [[0,0,0]]
current_z = 0

for line in song:
	if line:
		for note in line:
			aux = [x_notes[note],0, current_z]
			moves.append(aux)
			aux2 = [x_notes[note], y_perforation, current_z]
			moves.append(aux2)
		aux3 = [0,0,current_z]
		moves.append(aux3)
	current_z += z_roller
	moves.append([0,0,current_z])
	
for move in moves:
			stages.move(move, 0)
	status = stages.xAxisNode.spinStatusRequest()
	# This checks to see if the move is done.
	while status['stepsRemaining'] > 0:
		time.sleep(0.001)
		status = stages.xAxisNode.spinStatusRequest()



This is where all the magic happens: we iterate for each line of the song, creating the neccesary instuctions for the machine and appending each movement within the moves array, in pseudocode:
1. for each line in song position the z_roller
2. if not empty then for each note move the first x_note in the row
3. make the perforation with y_perforation one revolution
4. go back to the position x = 0
5. move to next line
The last for loop is taken straight from the example code and feeds the machine with the moves array and checks that the last move has happened before sending the next one.

2nd - Parsing a MIDI file

Without the ability of parsing song files and writting them into machine movement the machine can't be considered completed. Initially I though it could be really difficult to implement but I think I found a decent and pragmatic approach for this.
Coding a full GUI for writting the song was out of reach for this week so I spend some time looking for preexisting music roll editors and found this really cool project Musicboxmaniacs.com online editor. The app allows to make your own songs, play them online and export as mp3, PDF guides and more interestingly MIDI files.

Supermario Bros theme song found at Musicboxmaniacs.com

3rd - Starwars theme song described with our machine movement.




	# define two arrays the full song and each line
song = []
line = []

# pattern[1] is the part of the midi object that I need where notes are described
for note in pattern[1]:
    if isinstance(note, midi.NoteOnEvent):
        linejump = note.tick/240
        for i in range(0,linejump):
            song.append([])
        aux = note.data[0]
        line.append(midinotes[aux])
    if isinstance(note, midi.NoteOffEvent):
        if line:
            song.append(line)
            line = []

print song



4th - Code Used To Automating Our Machine In Any Given Coordinates

.

# The Picker Machine Code was developed by Javier Burón
García and Pilu Caballero during Machine design Assignment.
Based on Nadya Peek xy_plotter example.Two stage example Virtual
Machine file moves get set in Main.
# Dec 2014

# The Picker Machine Team are : Juan García, Daniel Amigo, Alvaro la Roja, Pilu Caballero and Javier Burón.
# FabLab CEU Madrid
# FabAcademy 2017
# The code is under Picker Machine License

# THE PICKER MACHINE : Our machine need to Pick and Cut A musicSheet based in 30 notes with 70 mm for works.
# The distance beetwen each notes are 2mm so the X Axis have to move there
# The punchHeader going DOWN/UP for make holes. Y Axis Moves.
# Finally cuts done the Z Axis, roll the MusicSheet every 4mm.

#------IMPORTS------- pyGestalt main Functions
from pygestalt import nodes
from pygestalt import interfaces
from pygestalt import machines
from pygestalt import functions
from pygestalt.machines import elements
from pygestalt.machines import kinematics
from pygestalt.machines import state
from pygestalt.utilities import notice
from pygestalt.publish import rpc

#remote procedure call dispatcher
import time
import io

# Variables : We defined the main variables as you see below
# X Axis : Has 30 Notes with a space between them : 2mm
# Y Axis : Does the perforation. It needs to move a full turn to do a perforation.
# Z Axis : Is the roller motor, there is a 4mm space between each line of the song.
# Our first attended we made an array based in the stepper moves 8, 16, 24... for x axis and Initialize Notes and Songs with the idea to change later for 30 notes.
# We was coding without the machine so was difficult for test the codes made but an array is an array with machine or not and we know each axis moves commented below.
# Javi had the stepper used for header design so gently could to try the first things and modify and advance.

# JAVIBG INITIALIZE NOTES AND SONG ARRAYS
#  x axis UNITS:
 MM

x_notes = {
'C3': 0, 'D3': 2, 'G3': 4, 'A3': 6, 'B3': 8,
'C4': 10, 'D4': 12, 'E4': 14, 'F4': 16, 'Gb4': 18, 'G4': 20, 'Ab4': 22, 'A4': 24,
'Bb4': 26, 'B4': 28,
'C5': 30, 'Db5': 32, 'D5': 34, 'Eb5': 36, 'E5': 38, 'F5': 40, 'Gb5': 42, 'G5': 44,
'Ab5': 46, 'A5': 48, 'Bb5': 50, 'B5': 52,
'C6': 54, 'D6': 56, 'E6': 58
}

# y axis UNITS: rev
y_perforation = 1

# z axis  UNITS: MM
z_roller = 4

#  SECOND DEFINE AN ARRAY TO WRITE THE SONG, EACH VECTOR IS ONE LINE, THIS SONG IS C MAJOR SCALE AND C MAJOR CHORD
song = [['Bb5', 'Bb4', 'F5'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'F5', 'D5'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Eb5', 'Bb4'], [], ['D5'], [], ['C5'], [], ['Bb5', 'Bb4', 'F5'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'D5', 'F5'], [], [], [], [], [], ['Eb5', 'Bb4'], [], ['D5'], [], ['C5'], [], ['Eb5', 'Bb5', 'Bb4'], [], [], [], [], [], ['Bb4'], [], [], [], [], [], ['Bb4', 'D5', 'F5'], [], [], [], [], [], ['Eb5', 'C5'], [], ['B4', 'D5'], [], ['Eb5', 'C5'], [], ['A4', 'C5'], [], [], [], [], [], ['A4'], [], [], [], [], [], ['F4'], [], [], [], ['F4'], [], ['G4'], [], [], [], [], [], ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [], ['C5'], [], [], ['Bb4'], [], [], ['Bb4'], [], ['C5'], [], ['D5'], [], ['C5'], [], [], [], ['G4'], [], ['A4'], [], [], [], [], [], ['F4'], [], [], [], ['F4'], [], ['G4'], [], [], [], [], [], ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [], ['C5'], [], [], ['Bb4'], [], [], ['Db5', 'F5'], [], [], [], [], [], ['A4', 'C5'], [], [], [], [], [], ['F4'], [], [], [], [], [], ['C4'], [], [], ['F4'], [], [], [], ['G4'], [], [], [], [], [], ['Bb4'], [], [], ['G4'], [], [], ['Eb5', 'Bb4'], [], [], ['D5'], [], [], ['C5'], [], [], ['Bb4'], [], [], ['Bb4'], [], ['C5'], [], ['D5'], [], ['C5'], [], [], [], ['G4'], [], ['F4', 'A4'], [], [], [], [], [], ['F4', 'F5'], [], [], [], ['F5'], [], ['Bb5', 'Gb5'], [], [], [], ['F5', 'Ab5'], [], ['Eb5', 'Gb5'], [], [], [], ['Db5', 'F5'], [], ['Eb5', 'C5'], [], [], [], ['Bb4', 'Db5'], [], ['Ab4', 'C5'], [], [], [], ['Gb4', 'Bb4'], [], ['F4', 'F5']]


#------VIRTUAL MACHINE------ Main functions
class virtualMachine(machines.virtualMachine):

#------Interfaces functions for RS 485 Serial Port Communication.

def initInterfaces(self):
# We need to change the name of our FabNet usbPort here. In my our case was : '/dev/tty.usbserial-FTZ29L92'
if self.providedInterface: self.fabnet = self.providedInterface  #providedInterface is defined in the virtualMachine class.
 else: self.fabnet = interfaces.gestaltInterface('FABNET', interfaces.serialInterface(baudRate = 115200, interfaceType = 'ftdi', portName = '/dev/tty.usbserial-FTZ29L92'))

#------Nodes/drivers Function for Gestalt Nodes. We modified the basic xy_plotter example adding zAxisNode and   compounds
def initControllers(self):

self.xAxisNode = nodes.networkedGestaltNode('X Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)
self.yAxisNode = nodes.networkedGestaltNode('Y Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)
self.zAxisNode = nodes.networkedGestaltNode('Z Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)
self.xyzNode = nodes.compoundNode(self.xAxisNode, self.yAxisNode, self.zAxisNode )

#------Function for state and position coordinates. We modified adding our apropiated Y axis variable in revolutions : ´rev´
def initCoordinates(self):

# JAVIBG Y MOTOR COORDINATE IN REVOLUTIONS
 self.position = state.coordinate(['mm', 'rev' , 'mm' ])

#------Kinematics function for storage the initial arguments for each motor . This function must to adjust testing each step for each axis .
# We added the z Kinematics and the number for direct drive all axes to 3
 def initKinematics(self):

self.xAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(7), elements.invert.forward(False)])
self.yAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(8), elements.invert.forward(False)])
self.zAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(8), elements.invert.forward(False)])
self.stageKinematics = kinematics.direct(3) #direct drive on all axes

 def initFunctions(self):

self.move = functions.move(virtualMachine = self, virtualNode = self.xyzNode, axes = [self.xAxis, self.yAxis, self.zAxis ] , kinematics = self.stageKinematics, machinePosition = self.position,planner = 'null')
self.jog = functions.jog(self.move) #an incremental wrapper for the move function
pass

 def initLast(self):

#self.machineControl.setMotorCurrents(aCurrent = 0.8, bCurrent = 0.8, cCurrent = 0.8)
#self.xNode.setVelocityRequest(0) #clear velocity on nodes. Eventually this will be put in the motion planner on initialization to match state.
pass

 def publish(self):

#self.publisher.addNodes(self.machineControl)
pass

 def getPosition(self):

return {'position':self.position.future()}

 def setPosition(self, position  = [None]):

self.position.future.set(position)

def setSpindleSpeed(self, speedFraction):

 #self.machineControl.pwmRequest(speedFraction)
pass

#------IF RUN DIRECTLY FROM TERMINAL------
if __name__ == '__main__':
	# The persistence file remembers the node you set. It'll generate the first time you run the
	# file. If you are hooking up a new node, delete the previous persistence file.
	stages = virtualMachine(persistenceFile = "test.vmp")

	# You can load a new program onto the nodes if you are so inclined. This is currently set to
	# the path to the 086-005 repository on Nadya's machine.
	#stages.xyNode.loadProgram('../../../086-005/086-005a.hex')

	# This is a widget for setting the potentiometer to set the motor current limit on the nodes.
	# The A4982 has max 2A of current, running the widget will interactively help you set.
	#stages.xyNode.setMotorCurrent(0.7)

	# This is for how fast the stepper moves, we can change for precision more slow.
	stages.xyzNode.setVelocityRequest(8)
	# Some random moves to test with moves = [[10,10,10], [20,20,20], [0,0,0]]
    # Move!
    

    # JAVIBG
    # Movement is defined
    # 0. for each line in song
    # 1. position the z_roller
    # 2. if not empty then for each note
    # 3. move the first x_note in the row
    # 4. make the perforation with y_perforation one revolution
    # 5. go back to the x_note 0
    # 6. end for each note
    # 7. end for each line

    moves = [[0,0,0]] # Iniatilize moves Variable
    current_z = 0 # Variable z value for initialize
        
    # conditional about move in lines
  
        if line:
            for note in line:
                aux = [x_notes[note],0, current_z]
                moves.append(aux)
                aux2 = [x_notes[note], y_perforation, current_z]
                moves.append(aux2)
            aux3 = [0,0,current_z]
            moves.append(aux3)
        current_z += z_roller
        moves.append([0,0,current_z])

        # JAVIBG this is the moves array for the current song. moves = [[0, 0, 0], [10, 0, 0], [10, 1, 0], [0, 0, 0], [0, 0, 4], [0, 0, 8], [12, 0, 8], [12, 1, 8], [0, 0, 8], [0, 0, 12], [0, 0, 16], [14, 0, 16], [14, 1, 16], [0, 0, 16], [0, 0, 20], [0, 0, 24], [16, 0, 24], [16, 1, 24], [0, 0, 24], [0, 0, 28], [0, 0, 32], [20, 0, 32], [20, 1, 32], [0, 0, 32], [0, 0, 36], [0, 0, 40], [24, 0, 40], [24, 1, 40], [0, 0, 40], [0, 0, 44], [0, 0, 48], [28, 0, 48], [28, 1, 48], [0, 0, 48], [0, 0, 52], [0, 0, 56], [0, 0, 60], [0, 0, 64], [0, 0, 68], [10, 0, 68], [10, 1, 68], [14, 0, 68], [14, 1, 68], [20, 0, 68], [20, 1, 68], [0, 0, 68], [0, 0, 72]]
    
	for move in moves:
                stages.move(move, 0)
		status = stages.xAxisNode.spinStatusRequest()
		# This checks to see if the move is done.
        while status['stepsRemaining'] > 0:
			time.sleep(0.001)
			status = stages.xAxisNode.spinStatusRequest()
							



5th - Code For Converting Midi Into Coordinates For Our Machine




			# using python midi https://github.com/vishnubob/python-midi
# install python libray with this command
# pip install git+https://github.com/vishnubob/python-midi

# library reads mid file and creates a python object
# need to transform this format

# midi.NoteOnEvent(tick=720, channel=0, data=[62, 127]),
#   midi.NoteOnEvent(tick=0, channel=0, data=[60, 127]),
#   midi.NoteOffEvent(tick=240, channel=0, data=[62, 127]),
#  midi.NoteOffEvent(tick=0, channel=0, data=[60, 127]),
#  midi.NoteOnEvent(tick=0, channel=0, data=[64, 127]),
#   midi.NoteOnEvent(tick=0, channel=0, data=[65, 127]),
#   midi.NoteOffEvent(tick=240, channel=0, data=[64, 127]),
#   midi.NoteOffEvent(tick=0, channel=0, data=[65, 127]),

# into this form

# song = [
# [],
# [],
# [],
# [D,C],
# [],
# [E,F],
# []
#]

import midi
pattern = midi.read_midifile("starwars.mid")

midinotes = {
48: 'C3', 50: 'D3', 55: 'G3', 57: 'A3', 59: 'B3',
60: 'C4', 62: 'D4', 64: 'E4', 65: 'F4', 66: 'Gb4', 67: 'G4', 68: 'Ab4', 69: 'A4', 70: 'Bb4', 71: 'B4',
72: 'C5', 73: 'Db5', 74: 'D5', 75: 'Eb5', 76: 'E5', 77: 'F5', 78: 'Gb5', 79: 'G5', 80: 'Ab5', 81: 'A5', 82: 'Bb5', 83: 'B5',
84: 'C6', 86: 'D6', 88: 'E6'
}

# define two arrays the full song and each line
song = []
line = []

# pattern[1] is the part of the midi object that I need where notes are described
for note in pattern[1]:
    if isinstance(note, midi.NoteOnEvent):
        linejump = note.tick/240
        for i in range(0,linejump):
            song.append([])
        aux = note.data[0]
        line.append(midinotes[aux])
    if isinstance(note, midi.NoteOffEvent):
        if line:
            song.append(line)
            line = []

print song

6th - Final Code Used After Adjustments

This code is a modification of Nadya's example of two nodes for 3 nodes.

								# Two stage example Virtual Machine file
# moves get set in Main
# usb port needs to be set in initInterfaces
# Nadya Peek Dec 2014

#------IMPORTS-------
from pygestalt import nodes
from pygestalt import interfaces
from pygestalt import machines
from pygestalt import functions
from pygestalt.machines import elements
from pygestalt.machines import kinematics
from pygestalt.machines import state
from pygestalt.utilities import notice
from pygestalt.publish import rpc	#remote procedure call dispatcher
import time
import io


#------VIRTUAL MACHINE------
class virtualMachine(machines.virtualMachine):
	
	def initInterfaces(self):
		if self.providedInterface: self.fabnet = self.providedInterface		#providedInterface is defined in the virtualMachine class.
		else: self.fabnet = interfaces.gestaltInterface('FABNET', interfaces.serialInterface(baudRate = 115200, interfaceType = 'ftdi', portName = 'COM4'))
		
	def initControllers(self):
		self.xAxisNode = nodes.networkedGestaltNode('X Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)
		self.yAxisNode = nodes.networkedGestaltNode('Y Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)
		self.zAxisNode = nodes.networkedGestaltNode('Z Axis', self.fabnet, filename = '086-005a.py', persistence = self.persistence)

		self.xyzNode = nodes.compoundNode(self.xAxisNode, self.yAxisNode, self.zAxisNode)

	def initCoordinates(self):
		self.position = state.coordinate(['mm', 'mm' , 'mm'])
	
	def initKinematics(self):
		self.xAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(7), elements.invert.forward(False)])
		self.yAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(5.99), elements.invert.forward(True)])	
		self.zAxis = elements.elementChain.forward([elements.microstep.forward(4), elements.stepper.forward(1.8), elements.leadscrew.forward(12.28), elements.invert.forward(False)])	
		self.stageKinematics = kinematics.direct(3)	#direct drive on all axes
	
	def initFunctions(self):
		self.move = functions.move(virtualMachine = self, virtualNode = self.xyzNode, axes = [self.xAxis, self.yAxis, self.zAxis], kinematics = self.stageKinematics, machinePosition = self.position,planner = 'null')
		self.jog = functions.jog(self.move)	#an incremental wrapper for the move function
		pass
		
	def initLast(self):
		#self.machineControl.setMotorCurrents(aCurrent = 0.8, bCurrent = 0.8, cCurrent = 0.8)
		#self.xNode.setVelocityRequest(0)	#clear velocity on nodes. Eventually this will be put in the motion planner on initialization to match state.
		pass
	
	def publish(self):
		#self.publisher.addNodes(self.machineControl)
		pass
	
	def getPosition(self):
		return {'position':self.position.future()}
	
	def setPosition(self, position  = [None, None, None]):
		self.position.future.set(position)

	def setSpindleSpeed(self, speedFraction):
		#self.machineControl.pwmRequest(speedFraction)
		pass

#------IF RUN DIRECTLY FROM TERMINAL------
if __name__ == '__main__':
	# The persistence file remembers the node you set. It'll generate the first time you run the
	# file. If you are hooking up a new node, delete the previous persistence file.
	stages = virtualMachine(persistenceFile = "test.vmp")

	# You can load a new program onto the nodes if you are so inclined. This is currently set to 
	# the path to the 086-005 repository on Nadya's machine. 
	#stages.xyNode.loadProgram('../../../086-005/086-005a.hex')
	
	# This is a widget for setting the potentiometer to set the motor current limit on the nodes.
	# The A4982 has max 2A of current, running the widget will interactively help you set. 
	#stages.xyNode.setMotorCurrent(0.7)

	# This is for how fast the 
	stages.xyzNode.setVelocityRequest(8)	
	
	# Some random moves to test with
	moves = [[0, 0, 0], [10, 0, 0], [10, 6, 0], [0, 0, 0], [0, 0, 1], [0, 0, 2], [12, 0, 2], [12, 6, 2], [0, 0, 2], [0, 0, 3], [0, 0, 4], [14, 0, 4], [14, 6, 4], [0, 0, 4], [0, 0, 5], [0, 0, 6], [16, 0, 6], [16, 6, 6], [0, 0, 6], [0, 0, 7], [0, 0, 8], [20, 0, 8], [20, 6, 8], [0, 0, 8], [0, 0, 9], [0, 0, 10], [24, 0, 10], [24, 6, 10], [0, 0, 10], [0, 0, 11], [0, 0, 12], [28, 0, 12], [28, 6, 12], [0, 0, 12], [0, 0, 13], [0, 0, 14], [0, 0, 15], [0, 0, 16], [0, 0, 17], [10, 0, 17], [10, 6, 17], [14, 0, 17], [14, 6, 17], [20, 0, 17], [20, 6, 17], [0, 0, 17], [0, 0, 72]]
	
	# Move!
	for move in moves:
		stages.move(move, 0)
		status = stages.xAxisNode.spinStatusRequest()
		# This checks to see if the move is done.
		while status['stepsRemaining'] > 0:
			time.sleep(0.001)
			status = stages.xAxisNode.spinStatusRequest()
			

-