Thursday, 13 November 2014

Minecraft: Pi Worksheet

I quite often run workshops about programming Minecraft on the Raspberry Pi either at Raspberry Jam's or Pycon or schools or just about anywhere else you can power on a Pi and if you have attended one of these you will have no doubt used my worksheet.



I have been asked a few times if I can share it, so, I have put the Minecraft: Pi Edition Worksheet on google docs and made it public.  Please feel free to use it, share it, copy it - although a link back is always welcome :)

Tuesday, 11 November 2014

Adventures in Minecraft Arrives

I've been working with David Whale (blog.whaleygeek.co.uk) since February writing a book called "Adventures in Minecraft" and today it started arriving through people's doors!

It's a book written especially for 11-15 year old's and its a fun way to not only get into programming using Python but also learn how to do amazing things with Minecraft.

All the adventures, projects and programs in the book work with Minecraft on the PC and Apple Mac as well as Minecraft: Pi Edition on the Raspberry Pi.


I have created a small mini site, including a forum for the readers of the book to come together get support, share ideas and I personally can't wait to see the projects you create.

Its available from Wiley, Amazon (UK, US) and loads of other book stockists.

Minecraft programming experts David Whale and Martin O′Hanlon walk you step–by–step through everything you need to know to:
  • Get started writing Minecraft programs in Python on your PC, Mac, or Raspberry Pi
  • Build houses and other structures in the blink of an eye, and make a 3D duplicating machine
  • Write interactive games like a field that charges you rent, and a treasure hunt using magic vanishing bridges
  • Build custom game control panels using simple electronic circuits
  • Easily build huge 2D and 3D structures such as spheres and pyramids
  • Build intelligent objects like a massive Minecraft clock, and program an alien invasion
  • Plan and write a complete interactive arena game
Using the programming skills you learn from this book, writing Minecraft programs offers endless possibilities to create anything you can imagine.

To make your journey that much easier, the Adventures in Minecraft companion website, wiley.com/go/adventuresinminecraft supplies you with a video for each adventure in the book, downloadable code files, helpful programming reference tables, a bonus adventure, and badges to collect for your Minecraft accomplishments.

Myself and David are really proud of what we have created and think it really is a great way to get into creating your own Minecraft mods, tools and games.

Ria at SilkStream who got a copy of the book early has written up a review.


Friday, 17 October 2014

Raspberry Pi, Xbox 360 Controller, Python

I was looking for a way of controlling my initio robot by remote and having purchased 3 xbox 360’s in the past (red ring of death :( replacements) I have got a few wireless controllers knocking about!  I decided re-using one of these was the way forward.

I grabbed a £5 xbox USB wireless receiver (you can get them on Amazon UK US) and robbed the code from zephods lego pi car project (http://blog.zephod.com/post/37120089376/lego-xbox-raspberry-pi) but I found it to be really unstable, often resulting in a ‘segmentation’ fault within a few minutes – so I decided to write my own.

Zephod’s code used the screen output of xboxdrv (a linux xbox controller driver) to create events which could be interpreted from python.  I decided on a different route and (after being shown the way by Dave Honess at Pycon) used pygame to interface with xboxdrv and the controller directly.

I originally just hacked together some code to make my solution work but after asking twitter whether anyone else would find it useful I created a generic python module to allow anyone to incorporate an xbox controller into their projects.


The module works by interpreting the pygame events which xboxdrv creates when the xbox controller is used (button pressed, button released, analogue stick moved, trigger pressed, etc) and keeps track of the values of all the buttons, sticks and triggers on the controller.

These values can be read directly from the properties on the class (e.g  xboxController.RTRIGGER) or the values can be passed to your program through the use of call backs i.e. when a button is pressed or a stick moved a function in your program is called and the details about what was changed and what the new value is are passed to it.

Installing and testing the module

If there is demand in the future I will wrap the module into a proper python package, but for the time being its just a separate python file (XboxController.py) which you can add to your python project.

Install xboxdrv
sudo apt-get install xboxdrv
Grab the code from GitHub (github.com/martinohanlon/XboxController) and copy the XboxController.py file to your project:
git clone https://github.com/martinohanlon/XboxController
cp XboxController/XboxContoller.py pathToYourProject
You need to run xboxdrv before you can use the module, run
sudo xboxdrv --silent &
You may get an error asking you to run xboxdrv with the option --detach-kernel-driver, if so run:
sudo xboxdrv --silent --detach-kernel-driver &
You can test the module by running the XboxController.py file
python XboxController.py
When you see the message on the screen saying the controller is running, press a button on your xbox controller.


Using the module

The module is pretty easy to use, but there are a few complex concepts to get your head around such as call backs and threading - first you need to import it into your code:
import XboxController
Then you can create an instance to the XboxController:
xboxCont = XboxController.XboxController(
    controllerCallBack = None,
    joystickNo = 0,
    deadzone = 0.1,
    scale = 1,
    invertYAxis = False)
You can adjust the behaviour of the module by passing different parameters:
  • joystickNo : specify the number of the pygame joystick you want to use, usually 0
  • deadzone : the minimum value which is reported from the analogue sticks, a deadzone is good to reduce sensitivity
  • scale : the scale the analogue sticks will report to, so 1 will mean values are returned between -1 and 1, 0 is always the middle
  • invertYAxis : the Y axis is reported as -1 being up and 1 being down, which is just weird, so this will invert it
  • controllerCallBack : pass the name of a function and the xbox controller will call this function each time the controller changes (i.e. a button is pressed) returning the id of the control (what button, stick or trigger) and the current value
To add a controller call back you would use:
def myCallBack(controlId, value):
    print "Control id = {}, Value = {}".format(controlId, value)

xboxCont = XboxController.XboxController(
    controllerCallBack = myCallBack)
You can also add other call back functions so that when specific buttons, sticks or triggers are pressed or moved it will call a specific function, e.g. to add a function which is called when the start button is pressed / released you would used the code:
def startButtonCallBack(value):
    print "Start button pressed / released"

xboxCont.setupControlCallback(
    self.xboxCont.XboxControls.START,
    startButtonCallBack)
The XboxController runs in its own thread, so you need to tell the controller to start using
xboxCont.start()
Control values can be read directly from the XboxController while it is running, by using the properties of the class e.g. to read the current value of the right trigger you would use:
print xboxCont.RTRIGGER
The XboxController also needs to be stopped at the end of your program using
xboxCont.stop()
For more information about the module, see the code in the the XboxController.py file.

Tuesday, 14 October 2014

Raspberry Pi Initio Robot - Driving Straight

I've been continuing my Raspberry Pi Robot project (see previous post) and the next challenge was to create some functions to allow my robot to be autonomous.

First challenge...  Drive in a straight line!  Not as easy as it seems.  You put both motors onto 100% power forward and it veers slightly to the right (or in my case it does!).  The reason is simple the motors which drive the left and right wheels don't turn at exactly the same rate.

What to do.  Well the Initio robot chasis I have is fitted with wheel encoders which 'tick' when the wheels go round to show how far they have moved, so if I wanted to make the robot go straight I needed to make left and right wheels moved the same distance.

So, I turn both motors on and count the encoder ticks, if the left one is getting ahead I slow it down a bit, unless the right catches up and vice versa!

I created a new python class called NavigationController which is where I will put all of my robots autonomous functions and created a straight function which when passed a power and a distance would drive straight until that distance was reached.  It constantly checks the encoder values for both wheels and adjusts the power of the 2 motors to make sure it goes straight.

Code
You can see all my robot code here https://github.com/martinohanlon/initio

#Navigation class to accurately move initio robot
#Martin O'Hanlon
#www.stuffaboutcode.com

#import modules
import sys
import motorControl
import time
import RPi.GPIO as GPIO
import math
import thread

#states
STATEMOVING = 1
STATESTOPPED = 0

#Constant of Proportionality
# multipler used to convert difference in motor position to power change
KP = 2

#Wheel circumference
#(in mm)
WHEELCIRCUMFERENCEMM = 174.0
#(in encoder ticks)
WHEELCIRCUMFERENCE = 36.0

#Encoder to mm ratio (wheel circumference in mm / wheel circumference to encoder ticks)
ENCODERTOMM = WHEELCIRCUMFERENCEMM / WHEELCIRCUMFERENCE

class NavigationController:
    #initialise - motors is the MotorControl object
    def __init__(self, motors, distanceInMM = False):
        self.motors = motors
        self.state = STATESTOPPED

        #if distanceinMM is set to True all distance are consider to be in millimeters
        # if False all distances are in encoder ticks
        self.distanceInMM = distanceInMM

    #navigation state property
    @property
    def state(self):
        return self.state

    #motors property
    @property
    def motors(self):
        return self.state

    #move the robot in a straight line
    def straight(self, power, distance = None, ownThread = False):
        # if distance is not passed, continues forever and launches a seperate thread
        # if distance is passed, loops until distance is reached
        if distance == None:
            #set a really long distance - this is a 'bit' dirty...  But simple!
            distance = 99999999999
            #call it in its own thread
            ownThread = True
            
        if ownThread:
            #call it in its own thread
            thread.start_new_thread(self._straight,(power, distance))
        else:
            self._straight(power, distance)

    #move the robot in a staight line
    def _straight(self, power, distance):

        #convert distance
        if self.distanceInMM: distance = self._convertMillimetersToTicks(distance)
        
        #if the state is moving, set it to STOP
        if(self.state != STATESTOPPED): self.stop()
        
        #turn on both motors
        self.motors.start(power, power)

        #change state to moving
        self.state = STATEMOVING

        #keep track of the last motor error, so we only change if we need too
        lastMotorError = 0
        
        #loop until the distance is reached or it has been STOPPED, correcting the power so the robot goes straight
        while(self.motors.motorA.currentTicks < distance and self.state == STATEMOVING):
            
            #get the number of ticks of each motor
            #get the error by minusing one from the other
            motorError = self.motors.motorA.currentTicks - self.motors.motorB.currentTicks
            #print("motorError = " + str(motorError))

            #only change if the motorError has changed
            if(motorError != lastMotorError):

                #work out the value to slow the motor down by using the KP
                powerChange = (motorError * KP)
                #print("powerChange = " + str(powerChange))

                #in the unlikely event they are equal!
                if(powerChange == 0):
                    self.motors.start(power, power)
                else:
                    #if its going backward, turn the power change into a negative
                    if power < 0: powerChange * -1
                        
                    #if its a positive number
                    if(motorError > 0):
                        #set motor A to power - 'slow down value'
                        #set motor B to power
                        self.motors.start(power - powerChange, power)
                    #if its a negative number
                    if(motorError < 0):
                        #set motor A to power
                        #set motor B to power - 'slow down value'
                        self.motors.start(power, power - powerChange)

            #update last motor error
            lastMotorError = motorError

        # if they havent already been stopped - stop the motors
        self.stop()

    #stops the robot
    def stop(self):
        self.motors.stop()
        self.state = STATESTOPPED

    def _convertMillimetersToTicks(self, millimeters):
        print ENCODERTOMM
        return int(round(millimeters / ENCODERTOMM,0))

#tests
if __name__ == '__main__':

    try:
        #setup gpio
        GPIO.setmode(GPIO.BOARD)

        #create motor control
        motors = motorControl.MotorController()

        #create navigation control, use True for distances in millimeters
        nav = NavigationController(motors, True)

        #run a straight line just through motor control
        print("straight line, no correction")
        motors.start(100,100)
        time.sleep(10)
        #stop
        print("stop")
        motors.stop()
        #get length
        print("encoder ticks")
        print(motors.motorA.totalTicks)
        print(motors.motorB.totalTicks)

        #time.sleep(10)

        #reset encoder ticks
        motors.motorA.resetTotalTicks()
        motors.motorB.resetTotalTicks()
        
        #run a straight line through nav control
        print("straight line, with correction")
        nav.straight(100, 500)
        #get length
        print("encoder ticks")
        print(motors.motorA.totalTicks)
        print(motors.motorB.totalTicks)

    #Ctrl C
    except KeyboardInterrupt:
        print "User cancelled"

    #Error
    except:
        print "Unexpected error:", sys.exc_info()[0]
        raise

    finally:
        print ("cleanup")
        #cleanup gpio
        GPIO.cleanup()

Next job - turning in an arc!

Monday, 13 October 2014

Minecraft RaspberryJuice and Canarymod


Believe it or not Bukkit is not the only Minecraft server that supports plugins!

I have been maintaining a Bukkit plugin called RaspberryJuice for a little while, which allows you to run the same programs you developed for Minecraft on the Raspberry Pi on the full version of Minecraft.

So to give people a choice and to ensure the longevity of the RaspberryJuice plugin I have migrated it to Canarymod.  Canarymod is another Minecraft server, with an open source API which allows you to interface directly with the server.  This is what RaspberryJuice does, it listens for the command which would normally be sent to the Pi edition of Minecraft and makes the same things happen in Minecraft.

You can get the RaspberryJuice source code plugin for Canarymod from https://github.com/martinohanlon/canaryraspberryjuice.

None of this could have been possible without the work of zhuowei who originally create the RaspberryJuice Bukkit plugin.

I will keep them both versions of the RaspberryJuice plugin's up to date for as long as its viable.

If you want to create your own Canarymod server with RaspberryJuice plugin follow these steps:

Download and install Canarymod
  • Create a folder for Canarymod, anywhere it doesn't matter
  • Head over to http://canarymod.net/releases and download the latest version of Canarymod and save the .jar file to the folder.
  • Rename the downloaded .jar file from canarymod-#.#.#-#.#.#.jar to canarymod.jar
  • Open Notepad, insert the following text:
java -jar CanaryMod.jar
pause
  • Save the file in the folder as start.bat
Run CanaryMod

Double click start.bat

You will be asked to confirm you accept the EULA.  Open the eula.txt file and change eula=false to eula=true and save the file.

Double click start.bat to run the CanaryMod again.

Test Canarymod

The version of Minecraft you use needs to match the version of Canarymod you downloaded.  So if you downloaded Canarymod 1.7.10 you need to set Minecraft to use 1.7.10 as well.  You can do this by picking the correct version from the "Edit Profile" window on the Minecraft Launcher.

Open Minecraft, Multiplayer, Direct Connect, Type "localhost", Join Server

You can stop Canarymod by typing stop in the canarymod command window

Download and Install Raspberry Juice
If everything has gone to plan you should see a message when you start up Canarymod that RaspberryJuice has been enabled.


You can now run your Minecraft: Pi Edition programs on the full version of Minecraft.  Here is a video of the Minecraft Analogue Clock running on Bukkit using Raspberry Juice.



Tuesday, 19 August 2014

Raspberry Pi - Initio Robot & Python Motor Control

When I was at the 2014 Raspberry Jamboree, I was luckily enough to 'acquire' (I won it, I didn't steal it!) a 4tronix initio robot chasis, unfortunately it sat on my shelf for nearly 6 months before I got time to have a go with it.



When I opened up the box I found it also came with a 4tronix PiRoCon robot controller as well - all good!

I wanted to control the robot using Python, so my first stop was the ever useful google, unfortunately other than one simple python example in 4tronix's PiRoCon github repository, the cupboard was bare.  I did however know that Cymplecy (SimpleSi)'s ScratchGPIO code supported the PiRoCon so this code also gave me some inspiration (!).  I have since found out that 4tronix have some downloadable examples on their site, check out the very bottom of this page - doh!

I wired the motor's up as per the instructions on 4tronix's website and the speed sensors as per the instructions on Cymplecy's blog and I set about creating a motor control class, which would allow me to control the speed of the motors and also get data from the speed sensors (encoders).

This is my starting point for more advanced features and automation, but as always start small, grow big.

There is a more 'complete' example in the code below, but you use the motor controller class like this:

import time
import motorControl

#setup gpio
GPIO.setmode(GPIO.BOARD)

#create motor control
motors = motorControl.MotorController()

#go forward - both motors at 100%
motors.start(100)
time.sleep(2)

#go forward in a curve
# - motor A at 100%
# - motor B at 50%
motors.start(100,50)
time.sleep(2)

#go backward at 100%
# - using negative power
motors.start(-100)
time.sleep(2)

#stop
motors.stop()

#cleanup gpio
GPIO.cleanup()

The full Motor Controller code is below and in my initio repository github.com/martinohanlon/initio:

#PiRoCon - 4Tronix Initio - Motor Controller
#Martin O'Hanlon
#www.stuffaboutcode.com

import sys
import time
import RPi.GPIO as GPIO

#motor pins
MOTORAFWRDPIN = 19
MOTORABWRDPIN = 21
MOTORBFWRDPIN = 24
MOTORBBWRDPIN = 26
#encoder pins
MOTORAENCODERPIN = 7
MOTORBENCODERPIN = 11

#motor states
STATEFORWARD = 1
STATESTOPPED = 0
STATEBACKWARD = -1

class MotorController:
    def __init__(self,
                 motorAForwardPin = MOTORAFWRDPIN,
                 motorABackwardPin = MOTORABWRDPIN,
                 motorBForwardPin = MOTORBFWRDPIN,
                 motorBBackwardPin = MOTORBBWRDPIN,
                 motorAEncoderPin = MOTORAENCODERPIN,
                 motorBEncoderPin = MOTORBENCODERPIN,):

        #setup motor classes
        self.motorA = Motor(motorAForwardPin, motorABackwardPin, motorAEncoderPin)
        self.motorB = Motor(motorBForwardPin, motorBBackwardPin, motorBEncoderPin)

    #motor properties
    @property
    def motorA(self):
        return self.motorA

    @property
    def motorB(self):
        return self.motorB

    #start
    def start(self, powerA, powerB = None):
        #if a second power isnt passed in, both motors are set the same
        if powerB == None: powerB = powerA
        self.motorA.start(powerA)
        self.motorB.start(powerB)

    #stop
    def stop(self):
        self.motorA.stop()
        self.motorB.stop()

    #rotate left
    def rotateLeft(self, power):
        self.motorA.start(power * -1)
        self.motorB.start(power)

    #rotate right
    def rotateRight(self, power):
        self.motorA.start(power)
        self.motorB.start(power * -1)

#class for controlling a motor
class Motor:
    def __init__(self, forwardPin, backwardPin, encoderPin):
        #persist values
        self.forwardPin = forwardPin
        self.backwardPin = backwardPin
        self.encoderPin = encoderPin

        #setup GPIO pins
        GPIO.setup(forwardPin, GPIO.OUT)
        GPIO.setup(backwardPin, GPIO.OUT)
        GPIO.setup(encoderPin,GPIO.IN,pull_up_down=GPIO.PUD_DOWN)
        #add encoder pin event
        GPIO.add_event_detect(encoderPin, GPIO.RISING, callback=self._encoderCallback,bouncetime=2)

        #setup pwm
        self.forwardPWM = GPIO.PWM(forwardPin,50)
        self.backwardPWM = GPIO.PWM(backwardPin,50)

        #setup encoder/speed ticks
        self.totalTicks = 0
        self.currentTicks = 0

        self.state = STATESTOPPED

    #motor state property
    @property
    def state(self):
        return self.state

    #start
    def start(self, power):        
        #forward or backward?
        # backward
        if power < 0:
            if self.state != STATEBACKWARD: self.stop()
            self._backward(power)
            self.state = STATEBACKWARD
            
        # forward  
        if power > 0:
            if self.state != STATEFORWARD: self.stop()
            self._forward(power)
            self.state = STATEFORWARD
            
        # stop
        if power == 0:
            self.stop()

    #stop
    def stop(self):
        #stop motor
        self.forwardPWM.stop()
        self.backwardPWM.stop()
        self.state = STATESTOPPED
        #reset ticks
        self.currentTicks = 0

    #private function to calculate the freq for the PWM
    def _calcPowerAndFreq(self, power):
        # make power between 0 and 100
        power = max(0,min(100,abs(power)))
        # make half of freq, minimum of 11
        freq = max(11,abs(power/2))
        return power, freq
    
    #forward
    def _forward(self, power):
        #start forward motor
        power, freq = self._calcPowerAndFreq(power)
        self.forwardPWM.ChangeFrequency(freq)
        self.forwardPWM.start(power)
        
    #backward
    def _backward(self, power):
        #start backward motor
        power, freq = self._calcPowerAndFreq(power)
        self.backwardPWM.ChangeFrequency(freq)
        self.backwardPWM.start(power)

    #encoder callback
    def _encoderCallback(self, pin):
        self.totalTicks += 1
        self.currentTicks += 1

#tests
if __name__ == '__main__':

    try:
        #setup gpio
        GPIO.setmode(GPIO.BOARD)
        
        #create motor control
        motors = MotorController()

        #forward
        print("forward")
        motors.start(100)
        time.sleep(2)
        
        print("encoder ticks")
        print(motors.motorA.totalTicks)
        print(motors.motorB.totalTicks)
        
        #backward 
        print("backward")
        motors.start(-50)
        time.sleep(2)

        #forward curve
        print("forward curve")
        motors.start(100,50)
        time.sleep(2)

        #rotate left
        print("rotate left")
        motors.rotateLeft(50)
        time.sleep(2)

        #rotate right
        print("rotate right")
        motors.rotateRight(50)
        time.sleep(2)
        
        #stop
        print("stop")
        motors.stop()

    #Ctrl C
    except KeyboardInterrupt:
        print "User cancelled"

    #Error
    except:
        print "Unexpected error:", sys.exc_info()[0]
        raise

    finally:
        print ("cleanup")
        #cleanup gpio
        GPIO.cleanup()

Its my first time doing any robotics but so far I have been impressed with the Initio, the build quality and support seem really good - let the robot roll!

Saturday, 19 July 2014

Minecraft Fractal Trees

I have been experimenting with the Minecraft Graphics Turtle I created, its a typical graphics turtle in that you issue it commands such as forward, backward, left, right but you can also tell it to go up and down and go 3d!

I wanted to create something that took advantage of the 3d world available in Minecraft and decided to see if I could make some 3d fractals.  Fractals are repeating patterns which when observed at different scales appear the same - I know that sounds like rubbish, but that's what they are!

These are the 3d fractal tree's I created in Minecraft:



This is what a 2d fractal tree looks like:

I found some python turtle code to create the 2d tree at interactivepython.org/runestone/static/pythonds/Recursion/graphical.html:
import turtle

def tree(branchLen,t):
    if branchLen > 5:
        t.forward(branchLen)
        t.right(20)
        tree(branchLen-15,t)
        t.left(40)
        tree(branchLen-15,t)
        t.right(20)
        t.backward(branchLen)

def main():
    t = turtle.Turtle()
    myWin = turtle.Screen()
    t.left(90)
    t.up()
    t.backward(100)
    t.down()
    t.color("green")
    tree(75,t)
    myWin.exitonclick()

main()

Its recursive, which means that a function calls itself, so in the example above the tree() function calls itself passing a smaller and smaller branch until the branch gets smaller than 5 and the function doesn't call itself any more and exits.  Recursion is the basis of all fractals, its how you get the repeating patterns.

I modded this code to use my Minecraft Graphics Turtle and rather than create 2 branches each time the function is called it creates 4 branches - 2 facing north to south and 2 facing east to west.

#Minecraft Turtle Example
import minecraftturtle
import minecraft
import block

def tree(branchLen,t):
    if branchLen > 6:
        if branchLen > 10:
            t.penblock(block.WOOD)
        else:
            t.penblock(block.LEAVES)

        #for performance
        x,y,z = t.position.x, t.position.y, t.position.z
        #draw branch
        t.forward(branchLen)
        
        t.up(20)
        tree(branchLen-2, t)
        
        t.right(90)
        tree(branchLen-2, t)

        t.left(180)
        tree(branchLen-2, t)

        t.down(40)
        t.right(90)
        tree(branchLen-2, t)

        t.up(20)
        
        #go back
        #t.backward(branchLen)
        #for performance - rather than going back over every line
        t.setposition(x, y, z)

#create connection to minecraft
mc = minecraft.Minecraft.create()

#get players position
pos = mc.player.getPos()

#create minecraft turtle
steve = minecraftturtle.MinecraftTurtle(mc, pos)

#point up
steve.setverticalheading(90)

#set speed
steve.speed(0)

#call the tree fractal
tree(20, steve)

The other change I made was to change the block type so that the shorter branches (the ones at the top) are made of leaves and the ones at the bottom are made of wood.

I created these on the full version of Minecraft using Bukkit and Raspberry Juice, so I could take hi-def pictures but the same code works on the raspberry pi.

If you want to have a go, download the minecraft turtle code from github.com/martinohanlon/minecraft-turtle and run the example_3dfractaltree.py program:

sudo apt-get install git-core
cd ~
git clone https://github.com/martinohanlon/minecraft-turtle.git
cd ~/minecraft-turtle
python example_3dfractaltree.py

I also made a fractal tree made out of random colours, check out example_3dfractaltree_colors.py.