Ostrich Egg-Bot kit arrived

Quite a space between my last post and now.  Busy finishing my latest game, COD:MW3, learning a lot more about the Maya API (I highly recommend the new book “Maya Python for Games and Film“), and taking an awesome two week cruise through the Panama Canal.

A “shipping the game” present to myself was to pickup the “Ostrich Egg-Bot” kit from Evil Mad Scientist Laboratories (the ‘big-brother’ of the “Original Egg-Bot” kit).  Just brought it home, and unboxed:

Hope to get a more interesting blog post on it as it is assembled and working!  My current plans are to create cool lights based around LED’s and etched patterns on mason jars.  We’ll see what happens…

Arduino 4wd robot & Ping))) sensor

More photos at the bottom. Click to enlarge.

Armed with the knowledge of my previous “Ponar” post, I successfully completed construction and programming of my “Arduino-based 4wd robot with servo-actuated Ping))) sensor”.   It was an enjoyable process with a lot of trial and error since: I’m still learning a lot about how the Arduino works, how hardware hooked up to it works, and electronics work in general.  To get things started, here is a video of it driving around my kitchen:

Here’s the parts list:

Here’s the Arduino program:

How it works:

  • I wanted a simple robot to drive around avoiding obstacles using the ping sensor.  To do that, I sketched out (on paper) the general program, that went like this:
    • Ping to see if there is any obstacles in front.
    • If not, drive forward, while pinging.
    • If there is an obstacle, stop, and take a distance reading to the left and right of the robot.
    • Turn for a fixed period of time in the further direction.
    • Ping again:  If still blocked, keep turning that direction.  If not blocked, go back to drive mode.
  • There are basically three modes the robot can live in:  Drive forward (mode 1), stop and scan (mode 2), and turn (mode 3).  When driving, it can do three things:  Drive straight, turn left, turn right.
  • The hardware is configured like so:
    • The Adafruit Motorshield is plugged into the top of the Arduino.
    • The Arduino is powered off of 5xAA batteries (7.5v) in the belly of the robot.  These are wired to a switch sticking out the back of the robot.
    • The Motorshiled is powered off a separate 4xC battery pack (6v) on top of the robot.  These are wired to another switch on the back of the robot.
    • The 4 DC wheel motors & servo are powered off the Motorshield.
    • The Ping))) sensor draws power form the Arduino.

Things Learned:

  • Next time, use 2-wheeled robot instead:  easier to steer around the house.  The “tank steering” method of this robot seems like it uses a lot of power and doesn’t work very good.
  • I read it in a couple places, and it turns out it’s probably needed:  Solder a 1uf capacitor between the leads on the DC motors to help “smooth out” their operation.  Otherwise it seems little “hiccups” can happen while driving.
  • Voltage, voltage, voltage:
    • I was having all sorts of problems getting the motors to work properly:  They’d start\stop “hiccuping” a lot.  Per the above note I added in extra capacitors on the motors themselves, but it didn’t solve all the problems:
    • Originally I had the motors hooked up to the 5xAA battery pack (7.5v) that came with the robot chassis.  The motors per the specs are rated at max for 6v.  But… I didn’t know this.  I thought it needed moar power.  So I went out and got a 6xC (9v) pack and powered the motors through that.  The stuttering got even worse (although, it went pretty fast when it behaved) and even did something really stupid:  Put one of the batteries in backwards… which caused it to melt-down and leak acid everywhere.  Sigh…
    • Finally (after I knew the correct voltage for the motors and servo) I went out and got a 4xC pack (6v), and that, combined with the extra capacitors, finally gave it a smooth ride.
  • I had to solder in extra connections to the analog pins on the motorshield to allow fo the Ping))) sensor to passthrough it to the Arduino (acting as a digital pin).  However, the motorshield has the pins in the order of “signal\-\+”, while the cables from the Ping))) (and servo) are “signal\+\-“.  Not sure why the motorshield would break ordering convention, but I had to splice and re-wire the Ping))) cable to match the board.
  • The motorshield docs say that servo2 is pin9, but it’s actually pin10.  That took me a while to figure out… :-S
  • I ran into a lot of problems with the programming of the robot itself:
    • The behavior is to run, ping, scan, repeat.  But during “scan” it wouldn’t drive the servo full left\right:  Sometimes it would only  got left, never make it right.
    • To solve this, I put a “timer” in the code, that would only execute the main loop if a certain amount of time in ms had passed (30ms to be exact, which makes it run around 30fps in game terms).  This seemed to make it behave exactly how I wanted.
    • I thought I needed to tell the motors to run on every loop:  It turns out they’re a state machine:  once you tell them to run, they keep running until you tell them otherwise.  Knowing this helped me clean up the code.
  • There doesn’t seem to be any official documentation I could find to the motorshield library, I had to crack open this header file to deduce what it could do:  AFMotor.h

Picture time!  Click to enlarge:

And a final shot of the wiring on the board per request:

arduino_4wd_wiring

Ponar

While at the last Maker Faire, I picked up a 4wd robot kit.

My thought is to hook an ultrasonic sensor to it, to do some simple obstacle avoidance.  Since I’ve never worked with any of these components before (servo’s, ultrasonic sensors, motor-drivers, etc), I want to break down each step so I can really grasp how this stuff works.

I was looking at my bucket of parts and it dawned on me:  I have a servo, I have a Ping))) sensor, a spare Arduino Uno, a bunch of Erector Set parts, and knowledge of Processing programming:  I could (should!) make a simple sonar system.  So the “Ponar” was born:  Ping))) + Arduino + Servo + Processing = Ponar.

How it works:

The Arduino program sweeps the servo back and forth over a 90deg arc.  At each degree, the Ping))) sensor returns back a distance reading.  The degrees and distance values are passed over the serial port to the PC, where the Processing application turns them into a ‘traditional looking’ (in my head at least) sonar read-out.

See it in action:

Want to make one too?  Here’s the steps I went through:

Parts List:

Software List:

Hardware setup:

  • Assemble the servo, bracket, ping, and erector set into a pleasing arrangement.
  • Connect the Arduino’s 5v and ground pins to the mini beadboard with the jumpers.
  • Use jumpers to connect the Vin of the Ping))) and the servo to the Arduino powered row on the mini breadboard.
  • Use jumpers to connect the ground of the Ping))) and the servo to the Arduino grounded row on the mini breadboard.
  • Use a jumper to connect the signal line of the Ping))) to the Arduino’s digital pin 7.
  • Use a jumper to connect the signal line of the servo to the Arduino’s digital pin 9.
  • Connect the Arduino to the PC with the usb cable.
  • Upload the Arduino sketch to the Arduino:  It should start sweeping left and right.
  • Run the Processing sketch:  It should sample the serial stream being passed from the Arduino, and display the sonar view on-screen.

For a couple days work, it was really informative, and actually pretty fun.

Ponar sees beer

 

New Processing sketch: adventureLines

Haven’t posted much lately, been quite busy finaling my current game.

But I’ve had a bit of spare brain-power to work on some Processing stuff.

See more about it over on it’s page.

Click image to get full-size version.

pyglet, second steps…

With my first post on pyglet I wanted to figure out how to make simple primitive shapes. For this post, I wanted to understand the basics of how pyglet’s sprites work, learn their strengths and shorcomings.  In a nutshell I learned that:

  • It’s very easy to center their pivot, translate, rotate, and scale them.  Easier than PyGame.
  • Their drawing in OpenGL can be optimized via batches of vert lists.
  • Sprite have a draw() method(), but you don’t access it when using batches.
  • Sprites don’t have an update() method, so you need to roll your own.
  • I already knew this, but worth bringing up:  Unless I’m missing it, they have no concept of a rect (rectangle) representation, no build in collision of any type.
  • How pyglet sets up resource directories (easier than the docs make it once you figure it out).

Armed with that knowledge, I came up with the below example:  A simple window framework that will create randomly moving\scaling sprites when you click in the window.  They’ll bounce off the walls accurately based on a custom rect solution that can track the rotations to the rects.  There may be more optimized ways of computing \ drawing them, but as a first pass I’m pleased.

"""
sprite02_forBlog.py
Eric Pavey - www.akeric.com - 2011-04-03
Released under the Apache Licence, v2.0
http://www.apache.org/licenses/LICENSE-2.0
"""

import os
import sys
import math
import random
import pyglet

FPS = 60
pyglet.resource.path = ['resource/sprites']
pyglet.resource.reindex()
# The name of the sprite we're going to load:
IMAGE = 'boxOrange01.png'

def getSmoothConfig():
    """
    Sets up a configuration that allows of smoothing\antialiasing of the window.
    The return of this is passed to the config parameter of the created window.
    """
    try:
        # Try and create a window config with multisampling (antialiasing)
        config = pyglet.gl.Config(sample_buffers=1, samples=4,
                        depth_size=16, double_buffer=True)
    except pyglet.window.NoSuchConfigException:
        print "Smooth contex could not be aquiried."
        config = None
    return config

class Sprite(pyglet.sprite.Sprite):
    """
    Let's create a pyglet sprite, that will randomly move around the screen
    bouncing off the walls, accurately tracking it's collision rect even wheb
    rotated.
    """
    # Load the image and center the pivot:
    image = pyglet.resource.image(IMAGE) # pyglet.image.Texture
    image.anchor_x = image.width/2
    image.anchor_y = image.height/2

    def __init__(self, window, x, y, scale=1, batch=None):
        """
        window : pyglet.window.Window : The enclosing window that this sprite
            will be draw in.
        x, y, : float : init position
        scale : float : init scale
        batch : pyglet.graphics.Batch :  Default None.  the Batch to add the
            sprite to.
        """
        super(Sprite, self).__init__(Sprite.image, x, y, batch=batch)
        self.window = window
        self.scale = scale
        self.px = x
        self.py = y
        # Random starting speed\direction deltas:
        self.dx = (random.random() - 0.5) * 1000
        self.dy = (random.random() - 0.5) * 1000
        # how much to change the scale each frame
        self.scaleVal = .01

    def update(self, dt):

        # Cycle our scaling:
        if self.scale > 1.5 or self.scale < .5:
            self.scaleVal *= -1
        self.scale += self.scaleVal

        # Get our rotated rect, and then sort our x & y positions for wall
        # collision below:
        rect = self.getRect()
        xs = sorted(xy[0] for xy in rect)
        ys = sorted(xy[1] for xy in rect)

        # Do wall collision.  If a wall is hit, reverse direction, and offset
        # away from the wall based on the distance by which the wall was passed:
        if xs[0] <= 0:
            self.dx *= -1
            self.x += -xs[0]
        elif xs[-1] >= self.window.width:
            self.dx *= -1
            self.x -= xs[-1]-self.window.width

        if ys[0] <= 0:
            self.dy *= -1
            self.y += -ys[0]
        elif ys[-1] >= self.window.height:
            self.dy *= -1
            self.y -= ys[-1]-self.window.height

        self.px = self.x
        self.py = self.y
        self.x += self.dx * dt
        self.y += self.dy * dt

        # Using this, "forward" of the sprite is the "up" direction of the texture.
        self.radians = math.atan2((self.x-self.px), (self.y-self.py))
        self.rotation = math.degrees(self.radians)

    def getRect(self):
        """
        Returns the four scaled\rotated rect points in clockwise order :
        lt, rt, rb, lb
        """
        left = self.x - self.width/2
        right = self.x + self.width/2
        top = self.y + self.height/2
        bottom = self.y - self.height/2

        lt = (left,top)
        rt = (right,top)
        lb = (left,bottom)
        rb = (right,bottom)

        # Get rotated positions:
        if  self.rotation:
            # Note, as seen below, each of the y's in the first column to the left
            # are subtracted, rather than added like their 'x' counterpart.  I'm
            # not sure why this is needed, but it's very bad if you don't.
            ltx = self.x + ((lt[0]-self.x)*math.cos(self.radians) - \
                            (lt[1]-self.y)*math.sin(self.radians))
            lty = self.y - ((lt[0]-self.x)*math.sin(self.radians) + \
                            (lt[1]-self.y)*math.cos(self.radians))
            lt = (ltx, lty)

            rtx = self.x + ((rt[0]-self.x)*math.cos(self.radians) - \
                            (rt[1]-self.y)*math.sin(self.radians))
            rty = self.y - ((rt[0]-self.x)*math.sin(self.radians) + \
                            (rt[1]-self.y)*math.cos(self.radians))
            rt = (rtx, rty)

            rbx = self.x + ((rb[0]-self.x)*math.cos(self.radians) - \
                            (rb[1]-self.y)*math.sin(self.radians))
            rby = self.y - ((rb[0]-self.x)*math.sin(self.radians) + \
                            (rb[1]-self.y)*math.cos(self.radians))
            rb = (rbx, rby)

            lbx = self.x + ((lb[0]-self.x)*math.cos(self.radians) - \
                            (lb[1]-self.y)*math.sin(self.radians))
            lby = self.y - ((lb[0]-self.x)*math.sin(self.radians) + \
                            (lb[1]-self.y)*math.cos(self.radians))
            lb = (lbx, lby)

        return lt, rt, rb, lb

class SpriteWindow(pyglet.window.Window):

    def __init__(self):
        super(SpriteWindow, self).__init__(fullscreen=False,
                                           caption='pyglet sprite test',
                                           config=getSmoothConfig())

        # Schedule the update of this window, so it will advance in time.  If we
        # don't, the window will only update on events like mouse motion.
        pyglet.clock.schedule_interval(self.update, 1.0/FPS)

        # Set the background color:
        pyglet.gl.glClearColor(0,0,1,0)

        # Used for optimized sprite *drawing*.  It holds vertex lists, not Sprite objects.
        self.sprite_batch = pyglet.graphics.Batch()
        # Used for sprite *updating*, holds our Sprite objects.
        self.sprites = []

        # A label to draw how many sprites we have:
        self.spriteLabel = pyglet.text.Label(str(len(self.sprites)), font_name='Courier',
                                  font_size=36, x=self.width/2, y=32)

        # Setup debug framerate display:
        self.fps_display = pyglet.clock.ClockDisplay()

        # Run the application
        pyglet.app.run()

    #----------------------------
    # Scheduled Events:
    # via pyglet.clock.schedule_interval in __init__

    def update(self, dt):
        """
        Do all upating here:
        """
        for sprite in self.sprites:
            sprite.update(dt)

        self.spriteLabel.text=str(len(self.sprites))

    #----------------------------
    # Window() events:
    # Overridden Window() methods:

    def on_draw(self):
        """
        Do all drawing here.
        """
        self.clear()

        # Draw all our sprites:
        self.sprite_batch.draw()

        # Draw text:
        self.fps_display.draw()
        self.spriteLabel.draw()

    def on_mouse_press(self, x, y, button, modifiers):
        """
        Interaction with mouse.
        LMB creates sprite, RMB deletes sprite.
        """
        if button == 1:
            # Create,... a SPRITE, added to our render batch:
            sprite = Sprite(self, x, y, batch=self.sprite_batch)
            self.sprites.append(sprite)
        else:
            if len(self.sprites):
                # Make sure it's deleted:
                self.sprites[-1].delete()
                self.sprites.pop()

if __name__ == '__main__':
    """
    Launch the app from an icon.
    """
    sys.exit(SpriteWindow())