Friday, 21 March 2014

Talking Minecraft - Rasberry Jamboree 2014

I recently attended the Raspberry Jamboree in Manchester on the 28th February.  It was great fun and really good to meet a load of other people from the Raspberry Pi community.

I did a presentation about Minecraft on the Raspberry Pi and why I think its a good thing.


I also ran a Hacking Minecraft workshop, which you can download here.

You can find more information and code listings for the demos below:

A tutorial on using the Minecraft: Pi edition API.
Minecraft Music Visualiser.
Minecraft Auto Bridge.
Minecraft Snake Game.
Minecraft Cannon.

I also demo'd a program to built a house which then follows you wherever you go.

Download and Run
You can download the code direct from github, https://github.com/martinohanlon/minecraft-houses, so run minecraft, open/create a world and follow the instructions:

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

Code

#www.stuffaboutcode.com
#Raspberry Pi, Minecraft Snake

#import the minecraft.py module from the minecraft directory
import minecraft 
#import minecraft block module
import block 
#import time, so delays can be used
import time
#import random module to create random number
import random

HOUSEWIDTH=6
HOUSEHEIGHT=2

def buildHouse(mc, x, y, z):
    #draw floor
    mc.setBlocks(x,y-1,z,x+HOUSEWIDTH,y-1,z+HOUSEWIDTH,block.GRASS.id)
    
    #draw walls
    mc.setBlocks(x, y, z, x+HOUSEWIDTH, y+HOUSEHEIGHT, z, block.STONE.id)
    mc.setBlocks(x+HOUSEWIDTH, y, z, x+HOUSEWIDTH, y+HOUSEHEIGHT, z+HOUSEWIDTH, block.STONE.id)
    mc.setBlocks(x+HOUSEWIDTH, y, z+HOUSEWIDTH, x, y+HOUSEHEIGHT, z+HOUSEWIDTH, block.STONE.id)
    mc.setBlocks(x, y, z+HOUSEWIDTH, x, y+HOUSEHEIGHT, z, block.STONE.id)
    
    #draw windows
    mc.setBlocks(x+(HOUSEWIDTH/2)-2,y+1,z,x+(HOUSEWIDTH/2)-2,y+2,z,block.GLASS.id)
    mc.setBlocks(x+(HOUSEWIDTH/2)+2,y+1,z,x+(HOUSEWIDTH/2)+2,y+2,z,block.GLASS.id)

    #draw door
    #cobble arch
    mc.setBlocks(x+(HOUSEWIDTH/2)-1,y,z,x+(HOUSEWIDTH/2)+1,y+2,z,block.COBBLESTONE.id)
    # clear space for door
    mc.setBlocks(x+(HOUSEWIDTH/2),y,z,x+(HOUSEWIDTH/2),y+1,z,block.AIR.id)

    #draw torches
    mc.setBlock(x+(HOUSEWIDTH/2)-1,y+2,z-1,block.TORCH.id,1)
    mc.setBlock(x+(HOUSEWIDTH/2)+1,y+2,z-1,block.TORCH.id,1)
    
    #draw roof
    mc.setBlocks(x,y+HOUSEHEIGHT+1,z,x+HOUSEWIDTH,y+HOUSEHEIGHT+1,z+HOUSEWIDTH,block.WOOD_PLANKS.id)

def clearHouse(mc, x, y, z):
    mc.setBlocks(x,y-1,z,x+HOUSEWIDTH,y+HOUSEHEIGHT+1,z+HOUSEWIDTH,block.AIR.id)
    

#main program
if __name__ == "__main__":

    time.sleep(3)

    #Connect to minecraft by creating the minecraft object
    # - minecraft needs to be running and in a game
    mc = minecraft.Minecraft.create()

    playersPath = []
    lastPlayerPos = mc.player.getTilePos()
    playersPath.append(lastPlayerPos)

    lastHousePos = None

    while(True):
        playerPos = mc.player.getTilePos()
        if playerPos != lastPlayerPos:
            playersPath.append(playerPos)
        lastPlayerPos = playerPos

        #when a player has moved 15 blocks, moved their house and reset the path
        if len(playersPath) == 15:

            #clear the old house (if there was one)
            if lastHousePos is not None:
                clearHouse(mc, lastHousePos.x, lastHousePos.y, lastHousePos.z)
            
            #create house 10 blocks back, we dont want the house on top of us!
            lastHousePos = playersPath[5]
            lastHousePos.y = mc.getHeight(lastHousePos.x,lastHousePos.z)
            buildHouse(mc,lastHousePos.x, lastHousePos.y, lastHousePos.z)
            
            #clear list
            playersPath[:] = []
            
        

Monday, 17 February 2014

Minecraft - Music Visualiser

Not much to say about this...  Its a music visualiser / graphic equaliser for Minecraft!

I read a post on the raspberry pi forum, by a guy called SpaceGerbil who had created a music visualiser using an 8x8 led matrix, so I went code robbing.

After a pretty major code overhaul, including the removal of a global variable (tish tish), I had a music visualiser for Minecraft, I basically replaced the 8x8 led matrix functions with a class for creating columns of different colour wool in Minecraft.


The only problem was it ran like a dog, a fat asthmatic dog at that, with the music cutting in and out.  So I cranked up the overclocking to the max and increased the chunk size (i.e. the amount of data the program reads ahead and analyses).  This got me to the point where it worked, but the refresh rate was slow, so I moved to 2 Pi's, one running Minecraft, the other running the music analyser and visualiser program, success this was much better!  The video was actually recorded on the PC version of Minecraft using a Bukkit server with the Raspberry Juice plugin with the analyser and visualiser program running on a Pi.

The music visualisation is created by using an FFT algorithm and I am hopeful that I'll be able to change it to use the new interface created which uses the GPU to calculate the FFT's, but the lack of a python interface held me back.  Ill keep on looking though.

Download and run
You can download the code direct from githubhttps://github.com/martinohanlon/minecraft-music, so run minecraft, open/create a world and follow the instructions:

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

Code

#!/usr/bin/env python

# 8 band Audio equaliser from wav file
# Original code from Space Gerbil
# http://www.raspberrypi.org/phpBB3/viewtopic.php?p=314087

# Heavily Modded to be a minecraft equaliser
#  www.stuffaboutcode.com
 
import alsaaudio as aa
from struct import unpack
import numpy as np
import wave
import threading
import sys
import minecraft
import block
import time
import copy

class MCEqualiser():
    def __init__(self):
        #open connection to minecraft
        self.mc = minecraft.Minecraft.create()
        pos = self.mc.player.getTilePos()
        #store variables
        self.x = pos.x + 5
        self.y = pos.y
        self.z = pos.z
        #clear area
        self.drawnMatrix = np.array([0,0,0,0,0,0,0,0])

    def drawEqualiser(self, newMatrix):
        x,y,z = self.x,self.y,self.z
        #loop through the columns
        for column in range(0,8):
            # only update columns which have changed
            if self.drawnMatrix[column] != newMatrix[column]:
                # do I need to add or take away block?
                #  add blocks
                if self.drawnMatrix[column] < newMatrix[column]:
                    self.mc.setBlocks(x+column,y+self.drawnMatrix[column],z,x+column,y+newMatrix[column],z,block.WOOL.id,column)
                #  remove blocks
                if self.drawnMatrix[column] > newMatrix[column]:
                    self.mc.setBlocks(x+column,y+self.drawnMatrix[column],z,x+column,y+newMatrix[column],z,block.AIR.id)
        self.drawnMatrix = newMatrix.copy()

         
# Initialise matrix
matrix    = np.array([0,0,0,0,0,0,0,0])
power     = []
weighting = [2,2,8,8,16,32,64,64] # Change these according to taste

# Set up audio
wavfile = wave.open("/home/pi/minecraft-music/hot.wav","r")
sample_rate = wavfile.getframerate()
no_channels = wavfile.getnchannels()
chunk       = 4096 
#chunk       = 8192
#chunk       = 16384 #use this value if running it all on one Pi
output = aa.PCM(aa.PCM_PLAYBACK, aa.PCM_NORMAL)
output.setchannels(no_channels)
output.setrate(sample_rate)
output.setformat(aa.PCM_FORMAT_S16_LE)
output.setperiodsize(chunk)

# Return power array index corresponding to a particular frequency
def piff(val):
    return int(2*chunk*val/sample_rate)
   
def calculate_levels(data, chunk,sample_rate,matrix):
#    global matrix
    # Convert raw data (ASCII string) to numpy array
    data = unpack("%dh"%(len(data)/2),data)
    data = np.array(data, dtype='h')
    # Apply FFT - real data
    fourier=np.fft.rfft(data)
    # Remove last element in array to make it the same size as chunk
    fourier=np.delete(fourier,len(fourier)-1)
    # Find average 'amplitude' for specific frequency ranges in Hz
    power = np.abs(fourier)   
    matrix[0]= int(np.mean(power[piff(0)    :piff(156):1]))
    matrix[1]= int(np.mean(power[piff(156)  :piff(313):1]))
    matrix[2]= int(np.mean(power[piff(313)  :piff(625):1]))
    matrix[3]= int(np.mean(power[piff(625)  :piff(1250):1]))
    matrix[4]= int(np.mean(power[piff(1250) :piff(2500):1]))
    matrix[5]= int(np.mean(power[piff(2500) :piff(5000):1]))
    matrix[6]= int(np.mean(power[piff(5000) :piff(10000):1]))
    matrix[7]= int(np.mean(power[piff(10000):piff(20000):1]))
    # Tidy up column values for the LED matrix
    matrix=np.divide(np.multiply(matrix,weighting),1000000)
    # Set floor at 0 and ceiling at 8 for LED matrix
    matrix=matrix.clip(0,8) 
    return matrix

#Create Minecraft Equaliser object
mcequaliser = MCEqualiser()

try:
    # Process audio file   
    print "Processing....."
    data = wavfile.readframes(chunk)
    while data!='':
        output.write(data)   
        matrix=calculate_levels(data, chunk,sample_rate,matrix)
        mcequaliser.drawEqualiser(matrix)
        data = wavfile.readframes(chunk)

except KeyboardInterrupt:
    print "User Cancelled (Ctrl C)"

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

Monday, 27 January 2014

Raspberry Pi GPS Helmet Cam

I've been snowboarding for the past 20 years, and for most of that time I've been video'ing mine and my buddies adventures with a helmet cam. An old video of me snowboarding in Morzine, France.

I wanted to make my own helmet cam which would also show data about what was going on (e.g. speed, altitude, temperature).
Raspberry Pi GPS Helmet Cam

The starting point was my Raspberry Pi GPS Tracking Car Dash Cam, this gave me some code for gathering GPS data, recording video and generating data overlay video's.

I came up with a 1 led, 1 button design; the led flashes when the cam is 'ready' (quickly when there isn't a GPS fix, slowly when there is GPS fix), the led comes on when the camera is recording, a short button press starts / stops the camera and a long button press shutdowns the helmet cam.

I set about writing the code which would run at start-up of the Pi and control the camera, waiting for the button to be pressed, controlling the led, reading the GPS data and temperature data and start / stop the camera.

The program is multi-threaded and simply starts up a thread for each 'thing' (led, button, GPS, temperature sensor) that needs to be 'controlled', the main program then polls these controllers asking them if anything has changed and acts accordingly (e.g. starting / stopping the camera, shutting down the pi).

When the camera is started , the program uses the excellent python module, picamera, to start the video capture and writes the gps and temperature data to a file while the video is recording.  I made a change to the picamera module (which has since been introduced), this gave me a function to read the current frame number while the video was being recorded, allowing me to sync the data I have read to an exact position in the video.

I then use the data file to create a data video which I ultimately overly on top of the video taken from the helmet cam.  The data video is created in exactly the same way as my Raspberry Pi GPS Car Dash Cam, by creating individual images for each frame using PIL (python imaging library).


A single frame image from a data video

I then use mencoder to join the images together into a single video.

Hardware
The helmet cam is a Raspberry Pi model A inside a small sandwich box, a control box and a Raspberry Pi camera board on the end of a long ribbon cable.



The control box houses an Adafruit Ultimate GPS breakout board, a waterproof led and button, a temperature sensor and a very badly soldered piece of strip board which ties it all together.

It was my first time using stripboard, so moving my breadboard build to something more robust was a big job for me, but armed with a piece of paper and a set of crayons I came up with a design!



The camera is mounted on a small piece of wood, cut so when its mounted on my helmet, it, roughly, points in the right direction.

I got a 1m cable for the camera which I shielded with tin foil, as without it, it caused the GPS unit to loose fix when it was recording and then wrapped it in a polyester braided sheath.


The camera, mount and cable are then attached using sticky backed velcro to my helmet, so I could take it off when not in use.

The whole set-up was powered by a usb power bank.

Code
https://github.com/martinohanlon/pelmetcam

There are a number of python modules which make up the helmet cam code:
- pelmetcam.py - this is the main program which controls the helmet cam
- tempSensorController.py - module which continually reads from the temperature sensor
- GPSController.py - module which continually reads from the GPS sensor
- createDataOverlay.py - module which creates the data overlay images

I also created a few bash scripts to make things easier to manage:
- runPelmetcam.sh - this is run when the pi boots and starts up the helmet cam, including the GPS daemon, temp sensor modules and shuts down the pi when the program finishes
- runPelmetcam.init - init.d script to make runPelmetcam.sh run at boot, see this post for information on running commands at boot
- createVideos.sh - runs the commands to make the main video into an MP4, creating the data overlay images and encoding them into a video file

Challenges
Before I went away I wanted to make sure it would operate in cold weather and test simple things, like my code would work if temperatures went negative, unfortunately an unusually mild winter in the UK mean't the only thing I could do was stick it in the freezer!  It performed perfectly for the 20 minutes I left it in there.  I can also confirm that the light does go off when you close the freezer door!


After the unit had been on for a while I started to notice that the temperature sensor was reporting temperatures much higher than expected (i.e. +9 C when it was -5 outside), I don't know for sure but I'm pretty sure the GPS unit generates a little bit of heat, which obviously when trapped inside a small sealed box warmed it up a bit!

If I was to do it again I wouldn't bother putting the GPS unit in the control box; it seemed like a good idea due to the interference the camera creates and a desire to have it 'outside' to get a better GPS fix, but with the shielding on the camera cable and the sensitivity of the GPS Unit, I didn't need to worry.

There is a current bug in the raspberry pi firmware which means if you try to use the raspberry pi camera at the same time as using a 1-wire sensor (like my temperature one) the camera will fail to start up.  There are several reported workarounds, in the end I ended up reverting to an old firmware which didn't suffer from this bug.

Stability
I wasn't expecting my Pi powered helmet cam to be very robust, I was secretly only expecting to get 1 or 2 runs out of it.  I thought the combination of wet conditions, very cold temperatures, dodgy wiring / soldering and some pretty aggressive snowboarding would mean that it just self destructed.

However, it proved to be very robust, I used it all week and recorded hours of footage with the camera.

The only component which failed was a cheap micro usb power cable which split and caused the pi to boot and reboot continuously as it shorted out, ultimately leading to a corrupt file system.

Full Length Videos
You can watch the unabridged videos taken using the helmet cam on my youtube channel:

Les Deux Alpes 2014 - Snowboarding "Vallee Blanche Off The Side"
Les Deux Alpes 2014 - Snowboarding - "Boarder Cross Lee Wins"
Les Deux Alpes 2014 - Snowboarding "Under the Vandri Lift into the Trees"
Les Deux Alpes 2014 - Snowboarding "Piste Down To Lac Noir Lift"

Shopping List
I was asked what 'bits' you need to create your own helmet cam.  A lot of these bits I already had, but I think this is a complete shopping list:
- Raspberry Pi - Model A
- Raspberry Pi - Camera Board
- Sandisk Class 10 32GB SD Card
- Adafruit Ultimate GPS Breakout Board
- Waterproof Push Button
- Waterproof Ultrabright Red LED
- Electronic Project Enclosure
- 1m ribbon camera cable
- Tin Foil (for shielding camera cable)
- Portable Battery Charger USB Power Bank
- 15mm Polyester Braiding
- 8m Polyester Braiding
- DS18B20 Temperature Sensor
- 4.7k resistor (for temperature sensor)
- 10k resistor (pull down for button)
- ?k resistor (appropriate for your LED)
- Stripboard
- Plenty of wire

Saturday, 11 January 2014

Raspberry Pi - Camera, Python & PiCamera

For my raspberry pi car cam project I needed to find a better way of syncing data with video so I modified raspivid and created python class to run raspivid.  I did this hack mainly because I couldn't be bothered and didn't really have the skill to write an interface to the camera in python, luckily someone else has!

Picamera is a python interface for the raspberry pi camera board, created by Dave Jones (aka waveform80), think of it as the python equivalent of raspivid and raspistill.  Using picamera you can 'programmatically' take videos and images and is much easier way of capturing video/images in your python projects.

There is some great documentation for Picamera at picamera.readthedocs.org, which includes detailed instructions about how to install picamera, getting started and a complete reference for the module.  You can also find the code at github.com/waveform80/picamera.

Install picamera
sudo apt-get install python-picamera

Take a video
The following code will record a video and save it to 'foo.h264'.
import picamera

with picamera.PiCamera() as camera:
    camera.resolution = (640, 480)
    camera.start_preview()
    camera.start_recording('foo.h264')
    camera.wait_recording(60)
    camera.stop_recording()
    camera.stop_preview()

Take a picture
The following code will take a single picture and save it to 'foo.jpg'.
import time
import picamera

with picamera.PiCamera() as camera:
    camera.resolution = (1024, 768)
    camera.start_preview()
    # Camera warm-up time
    time.sleep(2)
    camera.capture('foo.jpg')
There is much more information on http://picamera.readthedocs.org and its well worth a read.

There are also some great functions such as split recording, capturing images while recording and pulling out data from the encoder while the video is recording (such as frame number).

Saturday, 7 December 2013

Raspberry Pi - Auto CD Ripper

I've got a lot of CD's I have never got round to ripping to MP3.  I didn't fancy sitting in front of a PC shovelling them in for hours, so I wrote I little program to automate the ripping using an raspberry pi and an old USB CD/DVD drive.

USB CD/DVD drives are pretty cheap nowadays, check out Amazon (UK, US), if you get one without a separate power supply, you will also need a powered USB hub.

When my program runs, it opens the cd drawer and waits for a CD to be put in, when a CD is inserted it will rip the CD and when finished open the drawer so you can put in the next.  The plan is to stick it in by the tv and just let it rip.


Its a pretty simple python program which starts up a great command line cd ripper called Ripit plus an mp3 encoder called lame.

If you want to have a go yourself.

Install ripit, lame and eject
You will need a few utilities to do the CD ripping.

sudo apt-get install ripit lame eject

Download my program
You can download my program from github, github.com/martinohanlon/AutoRipper.git

sudo apt-get install git-core
cd ~
git clone https://github.com/martinohanlon/AutoRipper.git

Run it
You have to pass the program 2 parameters:
  • an output directory of where the CD's will be ripped to
  • a timeout in seconds which is the amount of time the program will wait in between CD rips before it quits
cd AutoRipper
python autoRipper.py /home/pi 60

The code

#!/usr/bin/env python

import subprocess
import time
import pygame
import datetime
import argparse

RIPITDIRTEMPLATE = "'\"/$artist/$album\"'"

class AutoRipper():
    def __init__(self,cdDrive,outputPath,timeout):
        self.cdDrive = cdDrive
        self.outputPath = outputPath
        self.timeout = timeout
        self.cdDrive.init()

    def start(self):
        print "AutoRipper - Waiting for Audio Disk"
        #loop until a disk hasnt been inserted within the timeout
        lastTimeDiskFound = datetime.datetime.now()
        while (lastTimeDiskFound + datetime.timedelta(0,self.timeout)) > datetime.datetime.now():
            #is there a disk in the drive?
            if self.cdDrive.get_empty() == False:
                # Disk found
                # is it an audio cd?
                if self.cdDrive.get_track_audio(0) == True:
                    print "AutoRipper - Audio disk found, starting ripit."
                    #run ripit
                    # getting subprocess to run ripit was difficult
                    #  due to the quotes in the --dirtemplate option
                    #   this works though!
                    ripit = subprocess.Popen("ripit --outputdir " + self.outputPath + " --dirtemplate=" + RIPITDIRTEMPLATE + " --nointeraction", shell=True)
                    ripit.communicate()
                    # rip complete - eject disk
                    print "AutoRipper - rip complete, ejecting"
                    # use eject command rather than pygame.cd.eject as I had problems with my drive
                    subprocess.call(["eject"])
                else:
                    print "AutoRipper - Disk inserted isnt an audio disk."
                    subprocess.call(["eject"])
                lastTimeDiskFound = datetime.datetime.now()
                print "AutoRipper - Waiting for disk"
            else:
                # No disk - eject the tray
                subprocess.call(["eject"])
            # wait for a bit, before checking if there is a disk
            time.sleep(5)

        # timed out, a disk wasnt inserted
        print "AutoRipper - timed out waiting for a disk, quitting"
        # close the drawer
        subprocess.call(["eject", "-t"])
        #finished - cleanup
        self.cdDrive.quit()

if __name__ == "__main__":

    print "StuffAboutCode.com Raspberry Pi Auto CD Ripper"

    #Command line options
    parser = argparse.ArgumentParser(description="Auto CD Ripper")
    parser.add_argument("outputPath", help="The location to rip the CD to")
    parser.add_argument("timeout", help="The number of seconds to wait for the next CD")
    args = parser.parse_args()

    #Initialize the CDROM device
    pygame.cdrom.init()

    # make sure we can find a drive
    if pygame.cdrom.get_count() == 0:
        print "AutoRipper - No drives found!"
    elif pygame.cdrom.get_count() > 1:
        print "AutoRipper - More than 1 drive found - this isnt supported - sorry!"
    elif pygame.cdrom.get_count() == 1:
        print "AutoRipper - Drive found - Starting"
        autoRipper = AutoRipper(pygame.cdrom.CD(0),args.outputPath,int(args.timeout))
        autoRipper.start()

    #clean up

    pygame.cdrom.quit()

I had problems with my cd drive where occasionally it wouldn't open the drawer with the button on the front, if you have the same, just type the command eject and it'll pop right out.

Wednesday, 4 December 2013

Raspberry Pi - Python & Temp Sensor DS18B20

I got a DS18B20 temperature sensor a little while back and I wanted to get it connected to a Raspberry Pi, so I could temperature in some of my data logging projects.  There are a couple of really good tutorials which describe how to get the sensor up and running (Cambridge University, Adafruit), but the code examples they provided didn't really fit my needs.

Setting up the sensor
The DS18B20 is a 1-wire digital sensor and is very easy to setup.  It has 3 pins, 3.3v in, data & ground and you will also need a 4.7K-10K resistor 'pull-up' the data line.

Looking at the sensor with the flat side facing you:
  • Pin 1 -> Raspberry Pi GND
  • Pin 2 -> Raspberry Pi GPIO 4
  • Pin 3 -> Raspberry Pi 3V
  • 4.7K resistor goes between Pin 2 & 3


Setup the software
In order to read data from the sensor I needed to install some modules using modprobe.  Once I had I could read the data from the sensor (including the current temperature) just like reading a file.

Install modules:

sudo modprobe w1-gpio
sudo modprobe w1-therm

The sensor appeared as a directory in /sys/bus/w1/devices directory.  The name of the directory is 28-########## with the hashes being the Id of the sensor:

cd /sys/bus/w1/devices
ls
cd 28-***********  (whatever the Id of your sensor)

I could then read the temperature data from the w1_slave file:

cat w1_slave


The data from the sensor looks like this:

f6 01 4b 46 7f ff 0a 10 eb : crc=eb YES
f6 01 4b 46 7f ff 0a 10 eb t=24437

The first line tells us whether the data read was successful with YES.
The second line displays the temperature as t=#####.

The temperature is returned in 1000's of a degress so 24437 is 24.437 centigrade.

Python program
I created a python module which would periodically sample the temperature from the sensor and allow a calling program to read the temperature from module as and when required, similar to the module I wrote for reading GPS data.

There is a more complete example of how to use the module in the code below but simply you use it like this:

#create temp sensor controller, passing Id of sensor and a time to wait between reads
tempcontrol = TempSensorController("28-000003aaea41", 1)

#start up temp sensor controller
tempcontrol.start()

#read temperature
print tempcontrol.temperature.C
print tempcontrol.temperature.F

#stop the controller
tempcontrol.stopController()

Temperature Sensor Controller code

import threading
import time

DEVICESDIR = "/sys/bus/w1/devices/"

#class for holding temperature values
class Temperature():
    def __init__(self, rawData):
        self.rawData = rawData
    @property
    def C(self):
        return float(self.rawData) / 1000
    @property
    def F(self):
        return self.C * 9.0 / 5.0 + 32.0

#class for controlling the temperature sensor
class TempSensorController(threading.Thread):
    def __init__(self, sensorId, timeToSleep):
        threading.Thread.__init__(self)
       
        #persist the file location
        self.tempSensorFile = DEVICESDIR + sensorId + "/w1_slave"

        #persist properties
        self.sensorId = sensorId
        self.timeToSleep = timeToSleep

#update the temperature
        self.updateTemp()
       
        #set to not running
        self.running = False
       
    def run(self):
        #loop until its set to stopped
        self.running = True
        while(self.running):
            #update temperature
            self.updateTemp()
            #sleep
            time.sleep(self.timeToSleep)
        self.running = False
       
    def stopController(self):
        self.running = False

    def readFile(self):
        sensorFile = open(self.tempSensorFile, "r")
        lines = sensorFile.readlines()
        sensorFile.close()
        return lines

    def updateTemp(self):
        data = self.readFile()
        #the output from the tempsensor looks like this
        #f6 01 4b 46 7f ff 0a 10 eb : crc=eb YES
        #f6 01 4b 46 7f ff 0a 10 eb t=31375
        #has a YES been returned?
        if data[0].strip()[-3:] == "YES":
            #can I find a temperature (t=)
            equals_pos = data[1].find("t=")
            if equals_pos != -1:
                tempData = data[1][equals_pos+2:]
                #update temperature
                self.temperature = Temperature(tempData)
                #update success status
                self.updateSuccess = True
            else:
                self.updateSuccess = False
        else:
            self.updateSuccess = False
       
if __name__ == "__main__":

    #create temp sensor controller, put your controller Id here
    # look in "/sys/bus/w1/devices/" after running
    #  sudo modprobe w1-gpio
    #  sudo modprobe w1-therm
    tempcontrol = TempSensorController("28-000003aaea41", 1)

    try:
        print("Starting temp sensor controller")
        #start up temp sensor controller
        tempcontrol.start()
        #loop forever, wait for Ctrl C
        while(True):
            print tempcontrol.temperature.C
            print tempcontrol.temperature.F
            time.sleep(5)
    #Ctrl C
    except KeyboardInterrupt:
        print "Cancelled"
   
    #Error
    except:
        print "Unexpected error:", sys.exc_info()[0]
        raise

    #if it finishes or Ctrl C, shut it down
    finally:
        print "Stopping temp sensor controller"
        #stop the controller
        tempcontrol.stopController()
        #wait for the tread to finish if it hasn't already
        tempcontrol.join()
       
    print "Done"


Thursday, 21 November 2013

Coding shapes in Minecraft

Over the past 10 months or so, I have created quite a few minecraft projects and in doing so I have also created quite a few functions which I thought other people might find useful.  To make it easier to use these functions, I thought I would create an extension module for the Minecraft: Pi edition api.  I couldn't think of a good name for it, so its just minecraftstuff.

The first class I have included in minecraftstuff is MinecraftDrawing which allows you to easily create 2d and 3d lines and shapes in minecraft.  I will add other 'stuff' to the extension over time.


To use the minecraftstuff extension you just need to put the minecraftstuff.py python module in the same directory that the minecraft api is in.  If you are using my programs or tutorials I tend to put the api in a directory called minecraft in the programs directory, so ~/my-minecraft-program/minecraft, but it doesn't matter you just need to put the minecraftstuff.py file in the same directory as the minecraft.py, block.py, connection.py, etc files.

You can download the extension directly to your raspberry pi from my github, so change directory to where your minecraft api is stored and use wget https://raw.github.com/martinohanlon/minecraft-stuff/master/minecraftstuff.py to download it:

cd my-minecraft-program/minecraft

wget https://raw.github.com/martinohanlon/minecraft-stuff/master/minecraftstuff.py

Once you have download the extension, its pretty easy to use:

You need to import the minecraftstuff module to your code:

#import minecraftstuff.py module
import minecraft.minecraftstuff as minecraftstuff

Once you have connected to minecraft you can create the MinecraftDrawing object by passing in the minecraft connection:

#connect to minecraft
mc = minecraft.Minecraft.create()
#create minecraft drawing object and pass in the minecraft connection
mcdrawing = minecraftstuff.MinecraftDrawing(mc)

The minecraft drawing class supports the following functions:
  • drawLine
  • drawSphere
  • drawCircle
  • drawFace
#call drawLine passing in 2 sets of x,y,z co-ordinates for the start and end of the line and a block type
mcdrawing.drawLine(x1,y1,z1,x2,y2,z2,blockType,blockData)

#call drawSphere passing in the co-ordinate of the centre of the sphere, a radius and a block type
mcdrawing.drawSphere(x,y,z,radius,blockType,blockData)

#call drawCircle passing in the co-ordinate of the centre of the circle, a radius and a block type
mcdrawing.drawCircle(x,y,z,radius,blockType,blockData)

#call drawFace to create any flat shape
# by passing in a number of points which it then joins together # if passed True it fills in the gaps with the blockType passed
# passing False draws a wire-frame of the shape
# some points
shapePoints = []
shapePoints.append(minecraft.Vec3(x1,y1,z1))
shapePoints.append(minecraft.Vec3(x2,y2,z2))
shapePoints.append(minecraft.Vec3(x3,y3,z3))
# draw the points together and True fills it in
mcdrawing.drawFace(shapePoints, True, block.GOLD_BLOCK.id)

The code for the demo in the video is below, which may also help you in using MinecraftDrawing:

#www.stuffaboutcode.com
#Minecraft Stuff Extensions Demo

#import the minecraft.py module 
import minecraft.minecraft as minecraft
#import block.py module 
import minecraft.block as block
#import minecraftstuff.py module
import minecraft.minecraftstuff as minecraftstuff
#import time
import time

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

#get players position
playerPos = mc.player.getTilePos()

#create minecraft drawing object
mcdrawing = minecraftstuff.MinecraftDrawing(mc)

# Draw some lines
mc.postToChat("Draw lines")
time.sleep(5)
# pass 2 sets of co-ordinates and a block
#  - the start of the line and the end of the line
mcdrawing.drawLine(playerPos.x, playerPos.y + 2, playerPos.z,
                   playerPos.x + 10, playerPos.y + 2, playerPos.z,
                   block.STONE)
time.sleep(1)
mcdrawing.drawLine(playerPos.x, playerPos.y + 2, playerPos.z,
                   playerPos.x, playerPos.y + 2, playerPos.z + 10,
                   block.STONE)
time.sleep(1)
mcdrawing.drawLine(playerPos.x, playerPos.y + 2, playerPos.z,
                   playerPos.x - 10, playerPos.y + 2, playerPos.z,
                   block.STONE)
time.sleep(1)
mcdrawing.drawLine(playerPos.x, playerPos.y + 2, playerPos.z,
                   playerPos.x, playerPos.y + 2, playerPos.z - 10,
                   block.STONE)
time.sleep(1)
mcdrawing.drawLine(playerPos.x, playerPos.y + 2, playerPos.z,
                   playerPos.x, playerPos.y + 10, playerPos.z,
                   block.STONE)
time.sleep(1)
mcdrawing.drawLine(playerPos.x, playerPos.y + 2, playerPos.z,
                   playerPos.x + 10, playerPos.y + 10, playerPos.z - 10,
                   block.STONE)
time.sleep(1)
mcdrawing.drawLine(playerPos.x, playerPos.y + 2, playerPos.z,
                   playerPos.x - 10, playerPos.y + 10, playerPos.z - 10,
                   block.STONE)
time.sleep(5)

# Draw a sphere
mc.postToChat("Draw a sphere")
time.sleep(2)
#pass the middle coordinate and a radius
mcdrawing.drawSphere(playerPos.x, playerPos.y + 25, playerPos.z, 5, block.DIAMOND_BLOCK.id)
time.sleep(5)

# Draw some circles
mc.postToChat("Draw some circles")
time.sleep(2)
#pass the middle coordinate and a radius
mcdrawing.drawCircle(playerPos.x, playerPos.y + 25, playerPos.z, 8,block.WOOD.id)
time.sleep(1)
mcdrawing.drawCircle(playerPos.x, playerPos.y + 25, playerPos.z, 11,block.WOOD.id)
time.sleep(5)

# Draw some shapes
#  - by passing a list of points, which then get joined together
mc.postToChat("Draw some shapes")
time.sleep(2)
# some points
shapePoints = []
shapePoints.append(minecraft.Vec3(playerPos.x + 1, playerPos.y + 27, playerPos.z + 10))
shapePoints.append(minecraft.Vec3(playerPos.x + 5, playerPos.y + 36 , playerPos.z + 10))
shapePoints.append(minecraft.Vec3(playerPos.x + 10, playerPos.y + 26, playerPos.z + 10))
# draw the points together and True fills it in
mcdrawing.drawFace(shapePoints, True, block.GOLD_BLOCK.id)

time.sleep(2)
#some points
shapePoints = []
shapePoints.append(minecraft.Vec3(playerPos.x, playerPos.y + 26, playerPos.z + 10))
shapePoints.append(minecraft.Vec3(playerPos.x + 11, playerPos.y + 26 , playerPos.z + 10))
shapePoints.append(minecraft.Vec3(playerPos.x + 11, playerPos.y + 37, playerPos.z + 10))
shapePoints.append(minecraft.Vec3(playerPos.x + 0, playerPos.y + 37, playerPos.z + 10))
# draw the points together and False, DONT fill it in
mcdrawing.drawFace(shapePoints, False, block.IRON_BLOCK.id)

# www.stuffaboutcode.com

The code for minecraftstuff is on github at https://github.com/martinohanlon/minecraft-stuff.