Thursday, 18 December 2014

GPIO Xmas Tree Reaction Game

In order to bring a bit of Christmas based fun to the office (I am normally a bit of a 'bah humbug') I have created a reaction game using a Raspberry Pi and a GPIO Xmas Tree from pocketmoneytronics.


The game is pretty simple, random leds are lit up on the xmas tree, the player has to press the button when the green led on the top of the tree is lit up.  The quicker you are, the higher you score.

A random animation is played when the tree is waiting for the next player, you press the button to start, all the leds are lit up and then its time to play.  At the end the players position is displayed on the tree, with 1st place lighting up the top of the tree.

Have a go
You will need a Raspberry Pi, a GPIO Xmas Tree and a button:
  1. Plug the GPIO Xmas tree into the far left set of pins on the GPIO header
  2. Connect the button up between 3.3V and GPIO 4.

Download the code from github.com/martinohanlon/GPIOXmasTreeGame and run the game - open a terminal and run:
git clone https://github.com/martinohanlon/GPIOXmasTreeGame
cd GPIOXmasTreeGame/xmastreegame
python xmastreegame.py

How does it work

There was only 1 particular challenge to creating the game (other than my dodgy soldering which meant I ruined the first tree I brought)  - doing more than one thing at a time!

The libraries supplied by pocketmoneytronics are really good and there are some great examples, the problem I had is that when you tell the tree to "light up leds 1 & 4", that is all you program can do, it blocks because the tree uses Charlieplexing and the libraries don't support threading.

Charliewhat?  In summary, each gpio pin is actually controlling 2 leds and when you light up 2 leds on the tree the program is actually turning the leds on and off independently really quickly, so quickly that its tricking your eyes into thinking that both leds are actually turned on.

This is why you cant just "turn on led 1 and led 4", the tree doesn't work that way.

To get around this, I made a threaded version of the pocketmoneytronics tree.py module.

Using the original libraries, you would have used the following code to light up all the leds to 1 second:
tree.setup()
#turn all the leds on for 1 second
#the program stops here and nothing can happen until the leds turns off
tree.leds_on_and_wait(ALL, 1)
tree.cleanup()
Using my threaded class you would use:
#create the XmasTree object
tree = XmasTree()
#start the tree object
tree.start()
#turn all leds on
tree.leds_on(ALL)
#the program can now do what it wants and the leds will stay on
sleep(1)
tree.stop()
The rest of the program was pretty easy to create, wait for a button to be pressed, light up led's randomly with a random delay in between, get the difference in time between turning on the yellow led and the button being pressed and hey presto, a Christmas themed game.

The code
All the code is here github.com/martinohanlon/GPIOXmasTreeGame.
import threading
import RPi.GPIO as GPIO
from time import sleep, time
from ThreadedTree import XmasTree
from random import getrandbits, randint
from os.path import isfile

#CONSTANTS
#leds
L0 = 1
L1 = 2
L2 = 4
L3 = 8
L4 = 16
L5 = 32
L6 = 64
ALL = 1+2+4+8+16+32+64
#leds as a list
LEDS = [L0,L1,L2,L3,L4,L5,L6]
#leds as a list descending down the tree
LEDSDESC = [L0,L6,L5,L4,L2,L1,L3]
#gpio pin the game button is connected too
NEWGAMEBUTTONPIN = 4
#gpio pin which will cause the game to stop if trigger
STOPGAMEBUTTONPIN = 17

class TreeRandom(threading.Thread):
    def __init__(self, xmasTree):
        #setup threading
        threading.Thread.__init__(self)
        #setup properties
        self.stopped = False
        self.running = False
        self.xmasTree = xmasTree
        
    def run(self):
        self.running = True
        while not self.stopped:
            ledsToLight = 0
            #loop through all the lights, randomly pick which ones to light
            for led in LEDS:
                if getrandbits(1) == 1:
                    ledsToLight = ledsToLight + led
            #turn the leds on
            self.xmasTree.leds_on(ledsToLight)
            #delay
            sleep(1)
        #when its stopped turn the leds off
        self.xmasTree.leds_on(0)
        self.running = False

    def stop(self):
        #stop the animation
        self.stopped = True
        #wait for it to stop running
        while self.running:
            sleep(0.01)

class TreeGame():
    def __init__(self, xmasTree, scoresFile):
        self.scoresFile = scoresFile
        self.scores = self._loadScores()
        #print self.scores

    def play(self):
        #turn on all leds
        xmasTree.leds_on(ALL)
        #wait a bit
        sleep(2)
        #get a random number, which will be how many leds will be lit before the green one
        steps = randint(7,14)
        for step in range(0,steps):
            #light a random red led
            ledToLight = LEDS[randint(1,6)]
            xmasTree.leds_on(ledToLight)
            #wait for a random time between 0.5 and 1 second
            timeToSleep = randint(5,10) / 10.0
            sleep(timeToSleep)
        #light the green led
        xmasTree.leds_on(L0)
        #get the time
        startTime = time()
        #wait for button to be released (if its pressed)
        while(GPIO.input(NEWGAMEBUTTONPIN) == 1):
            sleep(0.001)
        #wait for the button to be pressed
        while(GPIO.input(NEWGAMEBUTTONPIN) == 0):
            sleep(0.001)
        #get the time
        endTime = time()
        timeDiff = endTime - startTime
        #put the score in the score list and find the position
        # loop through all the scores
        for score in range(0,len(self.scores)):
            # is this time less than the current score?
            if timeDiff < self.scores[score]:
                #record the players position
                position = score
                self.scores.insert(score,timeDiff)
                break
        #save to the score file
        self._saveScores()
        #flash the position
        self._displayPosition(position)

    def _displayPosition(self,position):
        #if there position was less than 6, flash it on the tree
        # else flash all the lights
        if position <= 6:
            ledToLight = LEDSDESC[position]
        else:
            ledToLight = ALL
        #flash the position
        for count in range(15):
            xmasTree.leds_on(ledToLight)
            sleep(0.2)
            xmasTree.leds_on(0)
            sleep(0.2)

    # load the scores files
    def _loadScores(self):
        scores = []
        #does the file exist?  If so open it
        if isfile(self.scoresFile):
            with open(self.scoresFile, "r") as file:
                for score in file:
                    scores.append(float(score))
        else:
            #no file so put an initial score which is massive
            scores.append(999)

        return scores

    # save the scores file
    def _saveScores(self):
        with open(self.scoresFile, "w") as file:
            for score in self.scores:
                file.write(str(score)+"\n")


#main program
if __name__ == "__main__":

    #setup GPIO
    GPIO.setmode(GPIO.BCM)

    #setup the new game button
    GPIO.setup(NEWGAMEBUTTONPIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
    #setup the stop game button
    GPIO.setup(STOPGAMEBUTTONPIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

    #create threaded tree object
    xmasTree = XmasTree()
    #start the xmas tree
    xmasTree.start()
    #create tree game oject
    treeGame = TreeGame(xmasTree, "scores.txt")

    try:
        stopGame = False
        #loop until the stop game pin is set
        while(not stopGame):
            #run the xmas tree random animation
            treeRandom = TreeRandom(xmasTree)
            treeRandom.start()
            
            #wait until a button is pressed to either start a new game or stop the game
            while(GPIO.input(NEWGAMEBUTTONPIN) == 0 and GPIO.input(STOPGAMEBUTTONPIN) == 0):
                sleep(0.01)
            
            #new game
            if GPIO.input(NEWGAMEBUTTONPIN) == 1:                
                #stop the animation
                treeRandom.stop()
                #run the game
                treeGame.play()
                #game over, start the animation again
                sleep(1)

            #stop game
            elif GPIO.input(STOPGAMEBUTTONPIN) == 1:
                stopGame = True
        
    finally:
        #stop tree random animation
        treeRandom.stop()
        #stop xmas tree
        xmasTree.stop()
        #cleanup gpio
        GPIO.cleanup()


Monday, 24 November 2014

Minecraft Sat Nav

A couple of months back the ordnance survey created version 2 of their Minecraft map of Great Britain, its got loads more detail than the original, and is even more brilliant than there first one.

It is however a pain to get around...  Welcome "Minecraft Sat Nav", utterly ridiculous, totally pointless but at the same time brilliant.

Imagine you are exploring Minecraft Great Britain and you suddenly realise you need to get to Macclesfield but dont know the way, simple fire up Minecraft Sat Nav (patent pending!) and type navigate Macclesfield and it will give you a street by street navigation between your location and a town which was previously one of the world's biggest producers of silk!


How does it work?  Here are some facts:
  1. Its a python program
  2. It uses a Canarymod minecraft server to host the map
  3. The RaspberryJuice plugin is used to talk to Minecraft
  4. It uses the MapQuest open API's to get the locations and directions
  5. I reverse engineered Ordance Survey's 'conversion tool' to work out how to turn eastings and northings into Minecraft co-ordinates
  6. I used Hannah Fry's awesome python code to turn latitude and longitude into eastings and northings
  7. Its got a low tech 'retro styled' command line interface
You want to have a go yourself?  Here's a guide:
  1. Buy yourself a copy of Adventures in Minecraft ;) - honestly you really cant go wrong and it'll teach you what you need to know to make your own Minecraft Sat Nav!
  2. Setup a Canarymod server with RaspberryJuice
  3. Download the Ordnance Survey Minecraft GB map
  4. Replace the default world in canarymod with the Minecraft GB map
  5. Download the Minecraft Sat Nav program
  6. Run the MinecraftSatNav.py python program
The commands are really simple:
  • teleport <location> e.g. teleport london
  • navigate <destination> e.g. navigate fort william
  • navigateFrom <start>,<dest> e.g. navigate sheffield, grindleford
  • exit
Enuf said..

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.