Arduino controls Blender object in real time

I am new to Blender and Python, but have made a living as a programmer. I discovered Blender while researching 3D graphics to illustrate a concept I am working on. I found the Arduino the same way. I was pleased to find out that the two had been connected by a few people in interesting ways, so I thought I’d see if I could animate my idea in real time by using inputs from the Arduino to manipulate the Blender object I had in mind.

I found several examples, but as a ‘noob’ I hit some problems right away. Not the least of which was that the documentation lags well behind the current versions of both Blender and Python and I was trying to work in the latest (stable) releases, Blender 2.6 and Python 3.2. Another problem was that most of the examples that I found were written in earlier versions of both.

However, I have found that the best way to learn a new system is to dive right in with a problem to solve and figure it out. And I have for the most part. So, I’m posting the solution in order that one, others who are trying to do something similar may find my solution useful, and two, that I can receive any constructive comment on this approach. There are always other possibly better ways to do things.

This solution is intended to manipulate an arm bone at the shoulder joint. From a position of the arm extended out to the side, the rotation on the X axis would be around the long axis of the humerus. Rotation around the Y axis would have the effect of raising and lowering the ‘arm’ laterally. And rotation on the Z axis would sweep the ‘arm’ forward and back.

I used this code found on line for the Arduino. It waits to receive a character ‘a’ and replies with 5 values in a string over the serial USB connection. (I am only using three values at this time.) I’d give credit, but the author didn’t sign it.

/*
Analog input, serial output

Reads an analog input pin, maps the result to a range from 0 to 255 and prints the results to the serial monitor.

 The circuit:
 * potentiometers connected to analog pin 0,1,2,3,4.
   Center pin of the potentiometer goes to the analog pin.
   side pins of the potentiometer go to +5V and ground

 This example code is in the public domain.

 */

// These constants won't change.  They're used to give names
// to the pins used:
const int analogInPin1 = A0;  // Analog input pin that the potentiometer is attached to
const int analogInPin2 = A1;  // Analog input pin that the potentiometer is attached to
const int analogInPin3 = A2;  // Analog input pin that the potentiometer is attached to
const int analogInPin4 = A3;  // Analog input pin that the potentiometer is attached to
const int analogInPin5 = A4;  // Analog input pin that the potentiometer is attached to


const int analogOutPin = 11; // Analog output pin that the LED is attached to

int sensorValue1 = 0;        // value read from the pot
int sensorValue2 = 0;
int sensorValue3 = 0;
int sensorValue4 = 0;
int sensorValue5 = 0;


int outputValue=0;

void setup() {
  // initialize serial communications at 9600 bps:
  Serial.begin(9600);
  analogWrite(analogOutPin, outputValue);
}

void loop() {
  if ( Serial.available()) {
    char ch = Serial.read();
    if ('a'==ch) {
  //    val = analogRead(potPin)/4;
  //    Serial.write(val);
  
        // read the analog in value:
        sensorValue1 = analogRead(analogInPin1)/4;
        sensorValue2 = analogRead(analogInPin2)/4;            
        sensorValue3 = analogRead(analogInPin3)/4;            
        sensorValue4 = analogRead(analogInPin4)/4;  
        sensorValue5 = analogRead(analogInPin5)/4;  
      
        // map it to the range of the analog out:
        outputValue = map(sensorValue1, 0, 1023, 0, 255);  
        // change the analog out value:
        analogWrite(analogOutPin, outputValue);          
      
        // print the results to the serial monitor:
        Serial.print(sensorValue1);      
        Serial.print(" ");      
        Serial.print(sensorValue2);      
        Serial.print(" ");  
        Serial.print(sensorValue3);      
        Serial.print(" ");  
        Serial.print(sensorValue4);      
        Serial.print(" ");
        Serial.print(sensorValue5);      
        Serial.print(" ");
        Serial.println();  
    }
  }
  
  // wait 10 milliseconds before the next loop
  // for the analog-to-digital converter to settle
  // after the last reading:  
  delay(10);                    
}

This is the Python script:

# doSerial.py  -- Daedelus 11/8/2011
# Script to read serial values from Arduino and use them to rotate a beam.
# Values are in the range of 0 to 255 from analog input to Arduino.
# Want to rotate the beam around three axis.

import bge
import serial

from math import pi

cont = bge.logic.getCurrentController()
obj = cont.owner

sens = cont.sensors["readSer"]

serialport = serial.Serial('COM4', 9600)  

p = pi/255  # pi radians = 180 degrees so since Arduino input is 0 to 255,
            # in order to spread the 255 over pi radians, devide by 255

x0=[0.0,0.0,0.0]  #current val from Arduino
x1=[0.0,0.0,0.0]  #calculated rotation
x2=[0.0,0.0,0.0]  #previous rotation

nPoints=3  # three for now 

def readSer():

    if sens.positive:  #only want one value set from the sensor each loop
            
        try:
                                                     
            serialport.write('a'.encode('ascii'))  # tell Arduino to send valuse
            
            line=serialport.readline() #read a line from the serial port
                             
            for i in range(nPoints):
                x0[i]=(float)(line.split()[i]) - 127 #read each value of a line
                x1[i]= (x0[i] - x2[i]) * p           # calc the difference to new position
                x2[i] = x0[i]                        # save the position
            #print('x={0}, y={1}, z={2}' .format(x0[0], x0[1], x0[2]))
            obj.applyRotation([-x1[0], -x1[1], -x1[2]], 0) # apply the rotation to the beam
            
        except:
            print("Exception")

One logic ‘Always’ sensor block with a positive pulse button is connected to a controller block that calls the Python module doSerial.readSer.

Thanks to all the people who provided tips and examples that I found.

<iframe width=“420” height=“315” src=“http://www.youtube.com/embed/zIje3HGcXME” frameborder=“0” allowfullscreen></iframe>

Here is a clip of the Arduino real time control of a Blender object.

Thanks for the code!
But I try to execute it in Blender 2.60a and fail to import serial module. ¿How I can install it?

That’s correct, you have to get the serial package separately. Try http://pyserial.sourceforge.net/pyserial.html

After playing with this a while, I see that I need an absolute position rather than the relative ‘applyRotation’. Too much error creeps in. Any suggestions?

Maybe you can try something like this:


nCoords=3
for i in range(nCoords):
     x0[i] = (float)(line.split()[i]) - 127 #read each value of a line
     x1[i] = x0[i] * p           # convert to radians
mat_rot_x = mathutils.Matrix.Rotation(-x1[0], 3, 'X')
mat_rot_y = mathutils.Matrix.Rotation(-x1[1], 3, 'Y')
mat_rot_z = mathutils.Matrix.Rotation(-x1[2], 3, 'Z')
obj.localOrientation = mat_rot_x * mat_rot_y * mat_rot_z

The following code works for me with your Arduino code. I’m using absolute angle values instead of relative angle values.


import bge
import mathutils
import serial

from math import pi

cont = bge.logic.getCurrentController()
obj = cont.owner

serialport = serial.Serial('/dev/ttyUSB0', 9600)

# tell Arduino to send values
serialport.write('a'.encode('ascii'))

# read a line from the serial port
line=serialport.readline()

# Pi radians = 180 degrees so, since Arduino sends values between 0 and
# 255, we devide by 255 in order to spread the 255 over pi radians (from
# -90 to +90 degrees).
p = pi/255

# There is one angle for each axis, and there are 3 axes.
Angles=[0.0,0.0,0.0]  # inicial angles for X, Y and Z axes
AnglesNumber=3       # number/quantity of angles/axes
for i in range(AnglesNumber):
     # Read each value of the line from the serial port.
     # The - 127 is to be negative values (from -127 to +127).
     Angles[i] = Angles[i] + (float)(line.split()[i]) - 127
     # And we convert each value to radians (from -pi/2 to +pi/2).
     Angles[i] = Angles[i] * p

mat_rot_x = mathutils.Matrix.Rotation(Angles[0], 3, 'X')
mat_rot_y = mathutils.Matrix.Rotation(Angles[1], 3, 'Y')
mat_rot_z = mathutils.Matrix.Rotation(Angles[2], 3, 'Z')

obj.localOrientation = mat_rot_x * mat_rot_y * mat_rot_z

I put ‘/dev/ttyUSB0’ instead of 'COM4 because I’m using Linux instead of Windows.

If the inicial angles of the object are differents to default angles, it’s needed to put those inicial angles in my code, or create other script to set up a property, in the start of the game, or something like that; and change appropriately my code, of course.

I also have created a operator to use Arduino with the ‘3D View’, instead of with BGE. The following code creates that operator, which can be called through out the space bar and typing “Arduino changes location”, or typing “bpy.ops.wm.arduino_changes_location()” in the console or a script. And, to finish the operator, press ‘Esc’ key.

In this case, the user changes the location instead of orientation; it doesn’t matter, I think. And it affects to all selected objects. This kind of things maybe can be used to model, animate…

Windows users must to change ‘/dev/ttyUSB0’ to something like ‘COM4’.

And I still have the problem with the inicial location of the objects.


import bpy
from mathutils import Vector
import serial

class ArduinoChangesLocation(bpy.types.Operator):
    '''Operator which changes selected objects location'''
    '''from Arduino and in real time.'''
    bl_idname = "wm.arduino_changes_location"
    bl_label = "Arduino changes location"

    _timer = None

    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)

        if event.type == 'TIMER':
            serialport = serial.Serial('/dev/ttyUSB0', 9600)
            serialport.write('a'.encode('ascii'))
            line=serialport.readline()
            Coords=[0.0,0.0,0.0] # Inicial location
            CoordsNumber=3
            for i in range(CoordsNumber):
                Coords[i] = Coords[i] + (float)(line.split()[i]) - 127
            objects = context.selected_objects
            for object in objects:
                object.location=Vector((Coords[0]/100, Coords[1]/100, Coords[2]/100))

        return {'PASS_THROUGH'}

    def execute(self, context):
        context.window_manager.modal_handler_add(self)
        self._timer = context.window_manager.event_timer_add(0.1, context.window)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        context.window_manager.event_timer_remove(self._timer)
        return {'CANCELLED'}


def register():
    bpy.utils.register_class(ArduinoChangesLocation)


def unregister():
    bpy.utils.unregister_class(ArduinoChangesLocation)


if __name__ == "__main__":
    register()

    # test call
    bpy.ops.wm.arduino_changes_location()

Hello, i know this tread is a little bit outdated but i need help with implementing serial (arduino) in blender.
i have tried the codes above but none of them works for me. the problem is that blender stucks aslong he is resieving data. i have bytes/ values in blender terminal but blender main window freezes and i cant see anything moving.
what can the problem be?
i have pyseial installed, arduino uno, and a potentiometer.
sorry for bad spelling!
greets, anders

hello.
i need help with pyserial and blender.
ive tried the codes above, but none of them works for me. blender terminal recieve data but the main blender window stucks and blender crashes.
have anyone a idea?
greets, anders

hello, i need some help with arduino to blender thing.
i tried all the codes above. none of them works for me. then i tried a simple code: only show values from a poti in blender terminal. it works: i became values but blender main window freezes as long as the code is running.
what could be the problem? anyone an idea?
greets, anders

hello, i need some help with arduino to blender thing.
i tried all the codes above. none of them works for me. then i tried a simple code: only show values from a poti in blender terminal. it works: i became values but blender main window freezes as long as the code is running.
what could be the problem? anyone an idea?
greets, anders

I’d also like to use this script, and blender also freezes when I try to run it. Not sure what’s wrong, I have issues with the other script as well (the BGE script)

Hi,

Importing serial port in blender-2.66 is bit tricky. The old pyserial module is not compatible with new blender and python3.3 that comes with blender. I am a Linux user and I like to share how I have solved it in Linux to develop a small game inside blender using bge.

  1. You need to add the module path by the using “sys.path.append(‘path to pyserial module’)”; I also added the “sys.path.append(‘path to pyserial module/serial/’)”. Typically pyserial module can be copied in “…/blender-2.66a-linux-glibc211-i686/2.66/python/lib/python3.3/site-packages/”.

  2. In the file “…/blender-2.66a-linux-glibc211-i686/2.66/python/lib/python3.3/site-packages/serial/serialposix.py”
    line 62 change it in the following number formats and dictionary creation style (use hexadecimal notation instead of octal notation)

baudrate_constants = dict([ (0, 0x0000), (50, 0x0001), (75, 0x0002), (110, 0x0003),
(134, 0x0004), (150, 0x0005), (200, 0x0006), (300, 0x0007),
(600, 0x0008), (1200, 0x0009), (1800, 0x000A), (2400, 0x000B),
(4800, 0x000C), (9600, 0x000D), (19200, 0x000E), (38400, 0x000F),
(‘BOTHER’, 0x1000), (57600, 0x1001), (115200, 0x1002), (230400, 0x1003),
(460800, 0x1004), (500000, 0x1005), (576000, 0x1006), (921600, 0x1007),
(1000000, 0x1008), (1152000, 0x1009), (1500000, 0x100A), (2000000, 0x100B),
(2500000, 0x100C), (3000000, 0x100D), (3500000, 0x100E), (4000000, 0x100F) ])

   Change all the "except Exception, msg:" to "except Exception as msg:" and similar instructions.

3 In the file “…/blender-2.66a-linux-glibc211-i686/2.66/python/lib/python3.3/site-packages/serial/serialutil.py”
line 292 comment the if as shown below

#if isinstance(port, basestring):
self.portstr = port
#else:
#self.portstr = self.makeDeviceName(port)

   Change all the " except TypeError, err:" to " except TypeError as err:" and similar instructions.

This will make serial port running inside blender game engine using python script.

Note make sure you are not opening the serial port on every frame of bge cycle. so use a separate init script and call it at the beginning. Use GameLogic.serial = “open your port” in init script and accrss the opened serial port over rest of the frames.

Hope it will help :).

Hi abhi! Thank you this helped a lot! I’m running blender 2.66a on Debian wheezy and it works after applying all your changes!

Hi,
Good to hear that this worked for you in Debian. I have tested it in Fedora.
Thanks.

Thank you,
Your code has been really usefull
I use it with an accelerometer on a Arduino Uno.

So many applications are possible…

Also you can do it with blender 2.7 in windows x64 or x86 just copy the folder pySerial installed in C:\Python26\Lib\site-packages<b>serial and files C:\Python26\Lib\site-packages<b>pyserial-2.7-py3.3.egg-info, C:\Python26\Lib\site-packages<b>README to C:\Program Files\Blender Foundation\Blender\2.71\python\lib, here I attach the necessary files.

Attachments

lib.zip (236 KB)

Hi everyone

I am quite new to the Blender Game Engine (Python code) and i am having some trouble with this project.
I do exactly as it is said by daedalus (arduino, python and serial all working correctly). I start the game, on the console i don’t see any error but blender actually “freeze”.

What could be the problem?

SOLVED:

put the timeout = 10 after the serial port …com4’, 9600, timeout =10) as in the arduino sketch is setted a delay of 10 secs :wink:

Thank you DanielCas, I was looking for this and it works in Blender 2.72 yay! :smiley: