Posted on Leave a comment

Pi Wars 2019 Part 7 – Interfacing

Due to the limited wifi access on the day, I want to make my code as easy to interface as possible without the need for an external source (such as a laptop). With this in mind, I decided to purchase a Pimoroni GFX Hat to try and make a simple and easy to use menu to interface with my robot. Although the screen has to be functional, I also wanted to make sure that it looked good. Because of this, I wanted to make icons that could be used to identify each challenge, as well as some simple commands, such as shutdown, reboot and access settings.


Because of the size of the screen, I limited my icons to being  around 128×40 pixels to enable me to include a status bar at the top, and relevant text along the bottom. I started off by making image files, and using the Python PIL library to convert them to black and white pixels to be used on the screen. I soon found this to be a very slow process, as the user could rapidly press the next button, but the code would still be struggling to load an image, causing a large deal of lag. After a little bit of debugging, I soon found the speed issue to be the conversion of the image from a PNG to black and white image object in PIL. To resolve this, I decided to pre-define the pixels in each image in a series of icon functions. These an then be stored in a different module, and be called from the main menu program when required. I started off by making a black and white PNG image, and then used a small python script to generate the coordinates required. These can then be appended to an array of coordinates, which can then be drawn to the screen. The program saved me hours of time which I would have had to manually go through each image getting the coordinates of each black pixel.

from PIL import Image
im ="settings.png") # Image in question
pix = im.load() # Loading image

size = str(im.size) # Getting size of image
size = size.replace("(", "") # Manipulating size do can be numeric
size = size.replace(")", "") # Manipulating size do can be numeric
size = size.replace(" ", "") # Manipulating size do can be numeric

xmax, ymax = size.split(",") # setting x and y max of image
SettingsFile = open("export.txt", "w") # Creating file to add appends to to be copied into icon program
for x in range(int(xmax)): # Loop through each x coordinate of image
   for y in range(int(ymax)): # loop through each y coordinate of x row
        if str(pix[x,y]) == "(0, 0, 0, 255)" or str(pix[x,y]) == "(0, 0, 0)": # Check if pixel is black
            SettingsFile.write("cords.append((" + str(x) + "," + str(y) + "))\n") # Add the array append to the temp file
SettingsFile.close() # Close the written file

I did end up spending a little time on some of the icons, with my favourites below being the Spirit of Curiosity, Hubble Telescope Nebula Challenge & blastoff



Building A Setting Class

One thing I wanted the ability to do was to be able to load various settings between boots. To do this, I created a text file and settings class which can be loaded and saved within the program. I am planning to re-visit this in the future, as I feel there is possibly a better way to do it, however for the time being it seems to do the job. Initially, I created the base class which loads the settings from a text file. An example text file looks like:

[255, 89, 136]
[36, 169, 255]
[198, 255, 142]
[42, 205, 255]

To load this I created the class with the below code. It loads the lines of the text file and sets the values to the appropriate property of the class:

class Settings:
    def __init__(self):
        SettingsFile = open("Settings.txt", "r") # Read file contents to variable

        r = str(SettingsFile.readline()) # reading through each line to set appropriate value
        g = str(SettingsFile.readline()) 
        b = str(SettingsFile.readline()) 
        nebRed = str(SettingsFile.readline())
        nebBlu = str(SettingsFile.readline()) 
        nebYel = str(SettingsFile.readline())
        nebGre = str(SettingsFile.readline())

        self.blR = r.replace('\n', '') # clean values to make numeric, removing new lines
        self.blG = g.replace('\n', '')
        self.blB = b.replace('\n', '')
        self.nebRed = nebRed.replace('\n', '')
        self.nebBlue = nebBlu.replace('\n', '')
        self.nebYellow = nebYel.replace('\n', '')
        self.nebGreen = nebGre.replace('\n', '')

Once I then added a save file function to the class, so that the settings could be saved on the fly. This should then allow any values saved to to the settings class to be reloaded on the next boot.

def saveSettings(self):
    SettingsFile = open("Settings.txt", "w") # Open file to write
    SettingsFile.write((str(self.blR) + '\n' + str(self.blG) + '\n' + str(self.blB) + '\n' + str(self.nebRed) + '\n' + str(self.nebBlue) + '\n' + str(self.nebYellow) + '\n' + str(self.nebGreen))) # Save values with new lines to seperate
    SettingsFile.close() # Close file with new lines written

Display Location

I have mounted the pi on a stand on the upper chassis to make it easily accessible on the day, this does however mean that the display is upside down. To handle this, as part of the process of displaying the image to the screen, I have added an additional process to rotate the image 180 °. This means there is very limited impact to code and if it was needed to be changed, I would only need to alter the code  in one place.

def gfxDisplay(image):
    image = mdlPil.rotateImage(image, 180) # Rotate the image 180°
    for x in range(128): # Go through each x row
        for y in range(64): # go through each y row for each x row
            pixel = image.getpixel((x, y)) # Identify whether pixel needs to be on or off
            lcd.set_pixel(x, y, pixel) # Set GFX pixel to value # Update display

Status Monitoring On The Go

In addition to accessing programs and features, it is also useful to be ale to see the status of both the controller and motor controllers to see if they are connected. This should help with on the go trouble shooting on the day. Initially, I want to see if the controller is connected. To do this, I can query the Pi’s Bluetooth controller to see if the mac address is connected. Depending on the status, I  can then show a pre-defined icon, to show in the upper segment of the display:

def ControllerCheck(image):
    stdoutdata = sp.getoutput("hcitool con") # Check for connected devices

    if "00:06:F7:13:66:8F" in stdoutdata.split(): # Checking if PS3 mac address is in connected devices
        image = mdlPil.Controller(True, image) # Show connected icon
        image = mdlPil.Controller(False, image) # Show disconnected icon

    return image # Return the status bar for the menu

To check if the thunderborg’s are connected, I use their library to query each board to see if the controller has been found. This is then passed to the icon library which returns an image to the status bar. This can be seen below:

def TBCheck(image):
    TB1 = ThunderBorg.ThunderBorg() # Loading class
    TB1.i2cAddress = 10 # Setting address
    TB1.Init() # Initialising chip
    image = mdlPil.TB(TB1.foundChip, image, 1) # Passing if chip found to icon module

    TB2 = ThunderBorg.ThunderBorg() # Loading class
    TB2.i2cAddress = 11 # Setting address
    TB2.Init() # Initialising chip
    image = mdlPil.TB(TB1.foundChip, image, 2) # Passing if chip found to icon module
    return image # Retuning status bar to menu

Once all the items are put together, it enable a menu as seen below, with the status bar at the top, and icon in the middle:

Whats left to do?

To finish off, I need to work on a way to easily execute the challenge programs, but still be able to stop them using the menu. I think the best way to do this will be using threading, loading the challenge programs onto a thread, killing the thread when needed.

Overall I feel I have created both a easy to read but also visually appealing menu, using the GFX to its full capabilities. I hope that it shall prove very useful during the competition!

Leave a Reply

Your email address will not be published. Required fields are marked *