Raspberry PI 4 Boot from USB HDD not working GPT

This is more of a note for me than a guide or help for anyone else.

At present the PI4 does NOT support boot from USB, but you can work around this like the old days by using an SD card for the /boot and setting this to load the root from USB Stick/HDD.

However, this only appears to work for me using MBR partition type and just hangs when using GPT. This may be because I’m also encrypting the disk and using headless network boot. But that side does work and the disk/paritions are decrypt and list in /dev/disk/by-label but the OS just will NOT boot for me. Copying everything to the exact same partition layout and labels under MBR and it just works.

It seems a weird problem, as the SD card that’s actually booting and has the kernel is MBR and I don’t see why it would then care, the kernel has to be aware of GPT and as I said lists everything correctly. I can even decrypt and mount during the dropbear network boot bit, but the OS just wont boot. Workaround at present is to place the OS on it’s own drive/stick and use GPT on bigger disks purely for data. I’ve only found this while trying to use a 5Tb disk for both OS and Data. the 2Tb disk I can use MBR on without loosing space so the workaround isn’t really needed, just use MBR.

Raspberry PI 4 Blink Power LED (Locate in Cluster)

I bought a small cluster rack for my Raspberry PI 4’s. I have 4 of them racked and the idea was to keep it simple, assign static IP addresses .80,.81,.82,.83 with {NAME}-00, {NAME}-01 etc, starting at the bottom. Makes complete sense! however before I bought the rack they were all laid out flat and I thought in order left to right. When it came time to rack them I carefully screwed them all in place and powered up. All good. That is until I needed to work on No.3, shut it down and found it 2nd up in the rack 🙁 No.1 was next and No.2 on top. I don’t think I could have made the order worse if I was trying to lol.

Anyway, I finally got a bit of time to sort out the order. I’d already powered down No.2 and I know 100% No.0 is on the bottom. but that still left 50/50 that I’d be moving the correct PI (having to take the entire rack apart). So I thought I must be able to blink the ACT LED. Well a quick test and yes you can but disk access overrides it, so it’s not exactly great. but then I found contrary to what one of the guides say that the PWR LED can be controlled, it’s not just hard wired to 3v (this may be true on earlier PI’s, no idea and I don’t need to test it atm).

So a nice quick script:

nano locate_pi.sh
#!/bin/bash

# Setup the keyboard interrupt.
trap '{ echo "Stop Blinking. Setting LED to ON." ; echo 1 > /sys/class/leds/led1/brightness ; exit 0 ; }' INT

# Blink the Power LED
echo "Blinking Power LED..."
i=0
while true ; do
echo $i > /sys/class/leds/led1/brightness
sleep 1
i=$(((i==0?1:0)))
done

Make it executable:

chmod +x locate_pi.sh

Then you can run it:

./locate_pi.sh

And hey presto a nice flashing LED on the PI in a rack. Now you could be really fancy and assign a few different patterns of flashes by doing a bit of coding in the ‘while’ or simply adjusting the sleep time if your lazy. That could allow you to find more than 1 pi if you set them to blink differently but for my use a simple blink is plenty (for now).

As always, hope this helps someone. you could even move the locate_pi.sh to /bin or /usr/bin to allow you to just run it from wherever you are.

NB: I’m assuming writing to /sys/class/leds/led1/brightness can only be done by root (unless you change the permissions), as such this script will need to be run as root to work but I haven’t test as another user to know if this is true.

Domoticz Bluetooth Presence Detection

Update script being used as presence detection. Previously would take arguments for -s and -b (switch and bluetooth) and only check 1 device. Required multiple cron entries, 1 for each device. This could lead to polling issues if more devices are added. This new script “Should” only poll once for all devices. There’s a pause when it’s searching for the first device but consecutive devices fly through so I think it’s working.

Python Script saved in /home/domoticz/domoticz/bluetooth/bluescan.py

#!/usr/bin/python

import bluetooth
import time
import argparse
import urllib
import urllib2
import json

parser = argparse.ArgumentParser(description='SwitchID BluetoothID')
parser.add_argument('-l', '--link', action='append', help='BluetoothMac-SwitchID', required=True)
parser.add_argument('-d', '--debug', help='Debug Output', required=False, action='store_true')
parser.add_argument('-u', '--updatefound', help='Always update Found Devices', required=False, action='store_true')
args = parser.parse_args()

def update_switch(args, switchid, status):
URL="http://127.0.0.1/json.htm?username=YWRtaW4=&password=ZG9tb3RpY3o=&type=command&param=switchlight&idx={0}&switchcmd={1}".format( switchid, status )
if args.debug:
print URL
request = urllib2.Request(URL)
response = urllib2.urlopen(request)
return response.read()

if args.debug:
print "Checking " + time.strftime("%a, %d %b %Y %H:%M:%S", time.gmtime())

for value in args.link:
s = value.split("-")
blueid, switchid = s[0], s[1]
if args.debug:
print ("Switch ID: %s" %switchid )
print ("Bluetooth ID: %s" %blueid )

#Get the current status
URL="http://127.0.0.1/json.htm?username=YWRtaW4=&password=ZG9tb3RpY3o=&type=devices&rid={0}".format( switchid )
if args.debug:
print URL
response = urllib2.urlopen(URL)
json_data = json.load(response)
current_status = json_data['result'][0]['Status']
if args.debug:
print ("Current Status: %s" %current_status )

result = bluetooth.lookup_name(blueid, timeout=10)

if (result != None):
if args.debug:
print "Found"
# status = 1
status = "On"
else:
if args.debug:
print "NOT Found"
# status = 0
status = "Off"

if current_status == status:
if args.debug:
print "Status UnChanged"
if status=="On" and args.updatefound:
update = update_switch( args, switchid, status )
if args.debug:
print update
else:
update = update_switch( args, switchid, status )
if args.debug:
print update

Cronjob as user domoticz

* * *   *       *       /home/domoticz/domoticz/bluetooth/bluescan.py -u -l C0:EE:FB:00:00:00-7 -l A0:82:1F:00:00:00-23

Had to us – as separator. : in mac address would be confusing, tried ; but conflicts with bash, was going to use a space but would need to account for that in the argument parser and – was just easier.

Google Translate Text-to-Speech Linux

Scroll down for the script, or read who/why we use it first 🙂

I’ve been using a brilliant script by Dan Fountain for a few years as part of a WooCommerce barcode scanner python program I made. It allows us to update/process orders without the need of working in the admin interface 90% of the time.
For this we use a Raspberry PI, and a handheld portable barcode scanner. One of the big things that was needed was feedback from the PI as to what it’s done or what it’s doing. I’ve attached an LCD screen and most recently added a whole web interface output (mainly for diagnostics), but when your scanning a bunch of orders especially in bulk you dont want to be looking at a very small LCD screen about 4m away. So I added speakers and TTS.

There’s a few different things the barcode program can do (get order status, add tracking code to the order) but the most important is update the status of the order.
We start processing orders first thing in the morning by scanning a ‘bulk’ QR code, then scanning each order that’s on the printer. Once they’ve all been scanned we scan another QR Code ‘Order Printed’. Quite simply this updates each order status from ‘processing’ to ‘printed’, and this is important in case Google Cloud Print fails to print an order (it does from time to time), anything left in ‘processing’ needs checking.

Anyway that’s not the important bit for this post! The important bit is the python program giving audio feedback. While we could have gone for a TTS engine local to the pi, Google Translate option gives a far better sounding voice. The above scenario would do the following:
We scan Bulk mode
Pi says ‘Bulk Capture Active’
We scan first order
PI says ‘One’
We scan second order
PI says ‘two’
and on
and on
Once all the orders are scanned, we then scan ‘Printed’
PI says ‘Bulk Capture Finished. Processing x’ where x is the number of orders.
PI says ‘One’
PI says ‘Two’
etc. etc.
Then Pi Finally says ‘Finished Bulk Processing’.

Now there’s certainly the ability to pass all the orders in one go via the WooCommerce API, but we handle them as individual requests within python so that we can do some order status checking before changing it’s status. i.e if an order has already been moved from ‘processing’ to say ‘cancelled’ we dont want to move it again to ‘printed’, at that point the PI would say ‘Error Processing Order xxxxxx’ where xxxxxx is the order number.

As you can see from the above flows, the actual text being read ends up being the same text over and over and over. The number ‘One’ can be read about 10 times as we bulk move things around queues. While it’s certainly possible to just fire the same thing at Google Translate over ad over, it’s far nicer to play friendly and cache what we can use again and again.

The code below is based on the awesome work of Dan Fountain, with the following updates:
Added Caching
Added MPG123 options (to speed up the play back a little)
Added a client to the wget request (without it google started blocking heavy requests when the cache is clear).

#!/bin/bash
#################################
# Speech Script by Dan Fountain #
#      [email protected]     #
#                               #
# Added caching by JDL          #
#################################

CACHE=/tmp/ttscache

mkdir -p $CACHE

INPUT=$*
STRINGNUM=0
MPG123OPTS="-h 3 -d 4 -m --stereo -q"

ary=($INPUT)
for key in "${!ary[@]}"
  do
    SHORTTMP[$STRINGNUM]="${SHORTTMP[$STRINGNUM]} ${ary[$key]}"
    LENGTH=$(echo ${#SHORTTMP[$STRINGNUM]})
    #echo "word:$key, ${ary[$key]}"
    #echo "adding to: $STRINGNUM"
    if [[ "$LENGTH" -lt "100" ]]; then
      #echo starting new line
      SHORT[$STRINGNUM]=${SHORTTMP[$STRINGNUM]}
    else
      STRINGNUM=$(($STRINGNUM+1))
      SHORTTMP[$STRINGNUM]="${ary[$key]}"
      SHORT[$STRINGNUM]="${ary[$key]}"
    fi
done

for key in "${!SHORT[@]}"
  do
    #echo "line: $key is: ${SHORT[$key]}"

    echo "Playing line: $(($key+1)) of $(($STRINGNUM+1))"
    NEXTURL=$(echo ${SHORT[$key]} | xxd -plain | tr -d '\n' | sed 's/\(..\)/%\1/g')
    URL="http://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=$NEXTURL&tl=En-gb"
    URLMD5=`/bin/echo $URL | /usr/bin/md5sum | /usr/bin/cut -f1 -d" "`
    if [ -s "$CACHE/$URLMD5" ]
    then
       mpg123 $MPG123OPTS "$CACHE/$URLMD5"
    else
       echo "Getting : $URL"
       wget -U "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)" "$URL" -O $CACHE/$URLMD5
       mpg123 $MPG123OPTS "$CACHE/$URLMD5"
    fi
done

##!/bin/bash
#say() { local IFS=+;/usr/bin/mplayer -ao alsa -really-quiet -noconsolecontrols "http://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&q=$*&tl=En-us"; }
#say $*

WooCommerce API: No order data specified to edit order

Yesterday I received my Barcode scanner, after a little playing scanning everything in sight I got to work on programming a Raspberry PI in Python to use the barcode scanner to update orders in WooCommerce.

I’m not going to go into full details in this post on how. but I will write it up soon.

A BIG problem I hit though was updating orders using the WooCommerce API. I kept getting error code 400 ‘No order data specified to edit order’. I’d followed the example but changed it to fit my code i.e dont print the output but return it for error checking.

Searching google for that exact phrase gave just 1 result, and it’s someone commenting they are getting the error with no response (and it’s 4 months old on a 2 year old post).

After looking through the API code and trying to follow along (I’m not the best programmer but I can generally follow what it’s trying). I found it looking for ‘order’

So after looking at how the bulk update is done and with a bit of playing I found that instead of

data = {'status': 'completed'}

you need

data = {"order":{"status":"completed"}}

Hey Presto it works. My specific code is now

def WooCommerceUpdateOrder(order, data):
   global wcapi
   putstring = "orders/%s" % order
   result = wcapi.put(putstring, data)
   return result

which is called using

data = { 'order': { "status": "completed" } }
updateresult = WooCommerceUpdateOrder(order, data)

Hope this helps someone.  I’ll post the full python program for the barcode reader later in the week. Basically it uses a PI, PI LCD Screen with 4 buttons, Barcode Reader, Speakers. 🙂

Rasberry 1-Wire Resolution

More of a pastebin post.

https://www.raspberrypi.org/forums/viewtopic.php?f=37&t=115775

Is an important URL for changing the resolution on a DS18B20 via the Raspberry PI. Pay attention to the note re RPi2 and changing the code.

I also changed the GPIO pin to 4 to save rewiring but disabled the config.txt option for 1wire and disabled the gpio and thrm in /etc/modules just in case while running this program.

It seems to be working, now have the resolution set at 12bits on a new sensor I received that was working in .5c steps.

Hyperion with Sunrise and Sunset

You may have gotten here from my hyperion with nagios writeup, this doesn’t follow on from that and is separate, but maybe of interest.

The basic idea: I’ve now got LED’s in my room and would like them to come on before I go to bed so I can see without falling over. The ones on the stair I just leave running, but like hell am I going to sleep with such a bright LED (I probably could, I can sleep in the day but I thought it would be a better idea for them to come on ready).

I could have just set this up on a basic cron and picked a time early enough to account for summer/winter before I go to bed. but where’s the fun in that. I know my PI can work out when the sunrise/sunset is. so it can’t be that difficult to set something up.

After a little bit of searching I come across sunwait you will need this or a similar program. I wont cover installing sunwait on the PI here. just the config I use with hyperion.

First I need a script that sunwait will call and tell it what it needs to do. Here’s my sun-light.sh

#!/bin/bash

COMMAND=hyperion-remote
COMMAND_PRIORITY=50
COMMAND_PATH="/usr/bin"
case "$1" in
sunset)
        /usr/bin/hyperion-remote -p 50 -e "Knight rider"
;;
sunrise)
        /usr/bin/hyperion-remote -p 50 -e "Little Chaser Blue"
;;

*)
        echo "Usage: $0 {sunrise|sunset}"
        exit 1
esac

exit 0

Don’t forget to make this script executable ‘chmod +x sun-lights.sh’

This is basically told to either run sunset or sunrise and will then call hyperion-remote passing the relevant priority and effect. (Little Chaser Blue is a copy and customisation of Knight Rider)

Then I added the following to /etc/crontab

0 02   * * *   root    sunwait -p sun up 51.xxxxN 3.xxxxW ; /root/sun-lights.sh sunrise
12 02   * * *   root    sunwait -p sun down 51.xxxxN 3.xxxxW ; /root/sun-lights.sh sunset

This basically run’s sunwait which will wait until the sun is either coming up or going down at the specified co-ordinates before running the bit after ;

The important bit to get your head around if you must have cron run this at a time well before the sun will rise or set. midnight and midday seems like a good safe bet.

I know I’ve skipped over the actual installation of sunwait and more details on hyperion and running the scripts to check it works, but it’s 1am and I just want to save this 🙂 So if you’ve got this far and are still confused, comment below and I’ll expand on the relevant bits.

Hyperion LED’s & Nagios (Part 3)

Hopefully you’d read Part 2. If not you’ll need to or this wont work.

So in this part we’re going to setup the nagios stuff to set off the alert LED’s. As a little bit of background my nagios installation is on a completely separate PI to the hyperion LED’s, but I have install hyperion on this pi to make use of hyperion-remote. Yes I was being lazy I could have used other methods instead of installing the whole thing.

First my nagios installation is in ‘/usr/local/nagios’, I’m not going to go through the commands to cd and edit, if you’ve installed and configured nagios I’ll assume you can do them 🙂

This is my /usr/local/nagios/libexec/notify-hyperion.sh

#!/bin/bash
STATE=$1
DURATION=23000
case $STATE in
"CRITICAL")
   EFFECT="Red Alert"
   ;;
"WARNING")
   EFFECT="Yellow Alert"
   ;;
"OK")
   EFFECT="Green Alert"
   ;;
*)
   ;;
esac

hyperion-remote -a osmc-l:19444 -d $DURATION -p 10 -e "$EFFECT" &
hyperion-remote -a rasp-light:19444 -d $DURATION -p 10 -e "$EFFECT" &
hyperion-remote -a webcam-pi:19444 -d $DURATION -p 10 -e "$EFFECT" &

For the nagios saavy amongst you, you’ll see I account for CRITICAL, WARNING & OK. Yes I do need to add DOWN, UNREACHABLE & UP for the host alerts

The DURATION sets how long in ms hyperion will run this effect for (best worked out in conjunction with the speed, freq & step from the hyperion config. I’ve got this just right to cut off the alert after (I think) 4 fades. I force the priority ‘-p 10’ to 10, anything else I do with hyperion generally has a priority 50, 100 or 1000 so these will take over.

The last 3 lines are 1 each for my hyperion installs, you will need to change the name’s or replace them with the IP addresses dependant upon your network configuration.

Don’t forget to make the script executable, and you can test it with ‘./notify-hyperion.sh OK’

With the above tested and working, I’ve added the following to my nagios command.cfg

# 'notify-host-by-hyperion' command definition
define command{
        command_name    notify-host-by-hyperion
        command_line    /usr/local/nagios/libexec/notify-hyperion.sh "$HOSTSTATE$"
}

# 'notify-service-by-hyperion' command definition
define command{
        command_name    notify-service-by-hyperion
        command_line    /usr/local/nagios/libexec/notify-hyperion.sh "$SERVICESTATE$"
}

Then added the following to contacts.cfg

define contact{
        contact_name                    nagios-hyperion
        alias                           Nagios Hyperion
        service_notification_period     24x7
        host_notification_period        24x7
        service_notification_options    w,u,c,r,f
        host_notification_options       d,u,r,f,s
        service_notification_commands   notify-service-by-hyperion
        host_notification_commands      notify-host-by-hyperion
        }

define contactgroup{
        contactgroup_name       nagioshyperion
        alias                   Nagios Hyperion Notifications
        members                 nagios-hyperion
        }

For my installation I’ve then added

contact_groups                  nagioshyperion

To my template’s. You could add this to each service/host.

Within my setup I’ve stopped using email alerts, so changing the contacts to hyperion was fine. Within the templates I then have the notify_interval setup for 15 minutes. This means it will fire an alert to hyperion every 15 minutes. If you use email on your system too, you may not want to do this. an alternative could be changing the duration above, so that the red and yellow alerts are constant and the green run’s for a limited time before clearing down.

I did contemplate using event filters instead of contacts, so I could have the emails turned back on at some point, but decided against it as I would have to check before sending a green alert that it’s not already in green or I’d just end up with green all the time.

After all of the above make sure you restart nagios for the new config to take effect.

As a side note, I was sat watching TV this evening all of a sudden my room was yellow and I thought WTF. I do have hyperion setup in my room to start the LED’s at sunset but it was still light out and shouldn’t have fired. Then it clicked NAGIOS. and yes hey presto nagios had throw this site into warning status as there were updates available. I can see it getting annoying if my ISP drops out and I end up with alerts for hours. but on the whole I’m really happy it works, all I need now is a red alert klaxon 🙂

If your interest in setting up hyperion at sunrise/sunset I’ll be writing that one up separately.

Hyperion LED’s & Nagios (Part 1)

Part 1 is more Background story on my use of WS2801 WS2812b and Hyperion with the Raspberry Pi. Skip to Part 2 for the techy bit.

I’ve been using Hyperion for a while. I setup light behind the TV first off (using sticky tape) WS2801 and RaspBMC. This work brilliantly and I loved it. Spent hours tweaking the config so the LED’s were picking up the correct colour to the screen.

With all that working and a length of LED’s left over I decided to run some up the stairs. They sit just under the banister lip so you can’t see them, just the light on the stairs. I set these to Rainbow swirl and let it. They’re been running for months, occasionally changing the effect to show off what they can do.

Then disaster struck, the power adapter stopped working. Have to be honest I didn’t really notice until I was going to bed at 2am and almost fell over. They’ve been there giving off light (possibly a bit bright if anything) and I just got used to being able to see in the middle of the night without any other lights.

Anyway I digress, ordering a new power adapter I went searching for more LED’s (yes you can’t have enough of them once you’ve been playing). I decided that I’d really like to run some in my bedroom, the effects are cool and there would be plenty of light I wont need to use the main light with them on.

So I looking at where I originally bought my WS2801’s and nothing 🙁 so off to google, the obvious thing was I was going to have a hard time sourcing them in the UK. but why they’re great. So off I went to the hyperion git site for info and found there’s newer versions WS2811 and WS2812b. ah that may help, another search and I found someone selling a load on ebay. So I bought all he had 3xreels of WS1812b’s.

They turned up and I connected them up to try them as directed by hyperion. It was at this point I read the important bits RPi2 isn’t working yet and there maybe a problem with the PI communicating with them due to the voltage. I really thought I was going to have to make another little circuit to (buffer?) get them working. As a last ditched attempt it was mentioned try removing the resistor and try running them direct from python. I did both at the same time (not the best decision for troubleshooting. But to my surprise they worked.

So I killed the python program and restart hyperion, yep they’re working.

So off I went to stick them to the ceiling (they have sticky tape on the back). Done. If only I’d thought about connecting them before I stuck them up. I now had to work up in the air joining the cables. Not to worry I’ve done worse.

So I go to get what I need, by the time I got back up they’ve come down 🙁 bloody gravity! Now you’d think at this point I’d connect them up and sort out attaching them later Nope! (didn’t even enter my head) I was now on track to get them to stay up. Enter ‘SuperGlue’, applied little dots along the strip and stuck them up (yes I glued my fingers to the ceiling too). Finally they’re up and staying there. Oh I should have connected them when they were down!

‘Bugger it, where’s my screw driver’ I connected them up, put a power connector on the end and powered them and the PI.

Then installed hyperion on yet another pi. and it all worked like magic.

Have a look at the video, there’s no light other than the TV and it’s dark outside, but the room is really bright.

[youtube=https://www.youtube.com/watch?v=khfJW3vXcCE]

Click here for Part 2

Weewx+Raspberry PI+HDMI+PyGame

I’ve been using wview with my WH1080 weather station for some time (actually 2 of them). My main setup has been using my server, and every now and again the WH1080 would seem to lock up and nothing could get data out of it. The solution was to drop it’s power, on reboot it would all start working again.

However wview also seemed to introduce lockups of it’s own and the only solution there was to reboot the server (not ideal). So when it came to setting up a second weather station (in a remote location) I needed something a bit more stable and started looking at alternatives. I was doing this on a Raspberry PI and found wview. After installing it sometime last year it seemed pretty stable (although the WH1080 still manages to lockup).

Back to my house and I’ve finally had enough of missing weather data. One thing I really liked with wview was the ability to pull the archive data if the weather station had been running when the machine hadn’t (providing the USB hadn’t locked up), I really recommend looking at wview if your starting out.

I’ve already covered setting up weewx on a Raspberry PI and I’m not going to post about my exact configuration here. Instead I’m going to share my Python code for displaying the various graphs and gauges straight to the TV. At the moment I have my weather PI connected to the TV via HDMI. This may change later and then I’ll have to adjust the code to pull the images to another pi before displaying them (I have a similar project for displaying webcams already).

So a few things before the code.

  1. It’s my first real attempt at using classes, so my code will be more than a little scattered.
  2. You need to have python, pygame, wview, (and for this exact code Bootstrap for wview but you could just change the file paths to the Standard guages and graphs), mysql and wview configured for mysql.
  3. I have this started using an init script (added below).
  4. This runs the python program as root, I need to find a way to run this as a normal user (but that will affect point 5&6).
  5. This program checks mysql is able to be connected to and restarts mysql of not.
  6. It also checks the freshness of the index.html file (not the best way but a quick way) to make sure wview is running keeping the files upto date. If not it reboot the PI, this causes the weather station to reboot so if the USB locks up the whole system resets fixing it.

So now onto the python code

#!/usr/bin/python
import os
import time
import pygame
import MySQLdb as mdb
import signal
import sys

imglocation = "/var/www/weewx/Bootstrap"

class pyscreen :
   screen = None;

   def __init__(self):
       "Initializes a new pygame screen using the framebuffer"
       disp_no = os.getenv("DISPLAY")
       if disp_no:
           print "I'm running under X display = {0}".format(disp_no)

       # Check which framebuffer drivers are available.
       drivers = ['fbcon', 'directfb', 'svgalib']
       found = False
       for driver in drivers:
           # Make sure that SDL_VIDEODRIVER is set
           if not os.getenv('SDL_VIDEODRIVER'):
               os.putenv('SDL_VIDEODRIVER', driver)
           try:
               pygame.display.init()
           except pygame.error:
               print 'Driver: {0} failed.'.format(driver)
               continue
           found = True
           break

       if not found:
           raise Exception('No suitable video driver found!')

       size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
       print "Framebuffer size: %d x %d" % (size[0], size[1])
       self.screen = pygame.display.set_mode(size, pygame.FULLSCREEN)
       pygame.mouse.set_visible(False)
       # Clear the screen to start
       self.screen.fill((0, 0, 0))
       # Initialise font support
       pygame.font.init()
       # Render the screen
       pygame.display.update()

   def __del__(self):
       "Destructor to make sure pygame shuts down, etc."

   def size(self):
       size = (pygame.display.Info().current_w, pygame.display.Info().current_h)
       return size

   def fill(self, colour):
       if self.screen.get_at((0,0)) != colour:
           self.screen.fill((0, 0, 0))
           self.screen.fill(colour)
           pygame.display.update()

   def image(self, img, locX, locY, sizX, sizY):
       try:
           if (( "week" not in img) and (os.stat(img).st_mtime > time.time() - 600)):
               # 600 = 10 mins.
               image = pygame.image.load(img)
               image = pygame.transform.scale(image, (sizX, sizY))
               self.screen.blit(image, (locX, locY))
           elif(( "week" in img) and (os.stat(img).st_mtime > time.time() - 7200)):
               # 7200 = 2 hours.
               image = pygame.image.load(img)
               image = pygame.transform.scale(image, (sizX, sizY))
               self.screen.blit(image, (locX, locY))
           else:
               pygame.draw.rect(self.screen, (255, 0, 0), (locX, locY, sizX, sizY), 0)
       except pygame.error, message:
           pygame.draw.rect(self.screen, (255, 0, 0), (locX, locY, sizX, sizY), 0)
       pygame.display.update()

   def text_object(self, msg, font):
       black = (0, 0, 0)
       textSurface = font.render(msg, True, black)
       return textSurface, textSurface.get_rect()

   def error(self, msg):
       largeText = pygame.font.Font('freesansbold.ttf', 115)
       TextSurf, TextRect = screeny.text_object(msg, largeText)
       size = screeny.size
       TextRect.center = ((pygame.display.Info().current_w/2),(pygame.display.Info().current_h/2))
       self.screen.blit(TextSurf, TextRect)

       pygame.display.update()

class fileman:
   global imglocation

   def __init__(self):
       "Init for fileman. Nothing to do atm."

   def __del__(self):
       "Destructor for fileman. Nothing to do again."

   def total_files(self):
       count=0
       for file in os.listdir(imglocation):
           if file.endswith(".png"):
               count=count+1
       return count

class sqly:
   def __init__(self):
       "do nothing"

   def test(self):
       try:
           con = mdb.connect('localhost', 'weewx', 'weewx', 'weather')

           cur = con.cursor()
           cur.execute("SELECT VERSION()")
           ver = cur.fetchone()
           return 0
       except mdb.Error, e:
           return 1

       finally:
            "do nothing"
   def restart(self):
       #wait 30 secs and try the connection again.
       time.sleep(30)
       if not mysql_con.test():
           try:
               os.system("service mysql start")
           except:
               "do nothing"

def sigterm_handler(_signo, _stack_frame):
    "When sysvinit send the TERM signal, cleanup before exiting"
    print("[" + get_now() + "] received signal {}, exiting...".format(_signo))
    sys.exit(0)

signal.signal(signal.SIGTERM, sigterm_handler)

def reboot():
    "check if we've been reboot in the last 30 mins"
    uptimef = open("/proc/uptime", "r")
    uptimestr = uptimef.read()
    uptimelst = uptimestr.split()
    uptimef.close()

    if float(uptimelst[0]) < 1800:
        "We've reboot in the last 30 mins, ignoring"
    else:
        try:
            os.system("reboot")
        except:
            "do nothing"


if __name__ == "__main__":
    try:
        screeny = pyscreen()
        filey = fileman()
        screeny.fill((0, 0, 255))
        size = screeny.size()
        border = 7
        sizewquarter = ((size[0]-(border*5))/4)
        sizehthird = ((size[1]-(border*4))/3)

        print("Total files : %d" % (filey.total_files()))
        while 1:
            mysql_con = sqly()
            if mysql_con.test():
                screeny.fill((250, 0, 0))
                screeny.error("Database Offline. Restarting!")
                mysql_con.restart()
            elif (os.stat("/var/www/weewx/Bootstrap/index.html").st_mtime < time.time() - 600):
                screeny.fill((254, 0, 0))
                screeny.error("NOT Updating. Reboot Imminent!")
                reboot()
            else:
                screeny.fill((0, 0, 255))
                screeny.image("/var/www/weewx/Bootstrap/barometerGauge.png", (border*1), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/outTempGauge.png", ((border*2)+(sizewquarter*1)), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/windDirGauge.png", ((border*3)+(sizewquarter*2)), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/windSpeedGauge.png", ((border*4)+(sizewquarter*3)), (border*1), sizewquarter, sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weekbarometer-Bootstrap.png", (border*1), ((border*2)+(sizehthird*1)), ((sizewquarter*2)+(border*1)), sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weektempchill-Bootstrap.png", (border*1), ((border*3)+(sizehthird*2)), ((sizewquarter*2)+(border*1)), sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weekwinddir-Bootstrap.png", ((border*3)+(sizewquarter*2)), ((border*2)+(sizehthird*1)), ((sizewquarter*2)+(border*1)), sizehthird)
                screeny.image("/var/www/weewx/Bootstrap/big_images/weekwind-Bootstrap.png", ((border*3)+(sizewquarter*2)), ((border*3)+(sizehthird*2)), ((sizewquarter*2)+(border*1)), sizehthird)
            time.sleep(1)
    except KeyboardInterrupt:
        "We've got an interupt"

and now the init.d code

#!/bin/sh
#
# init script for displayweewx
#

### BEGIN INIT INFO
# Provides:          displayweewx
# Required-Start:    $syslog $network
# Required-Stop:     $syslog $network
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
# Short-Description: init script to display weewx charts via HDMI output
# Description:       The python script queries mysql and file ages, so does not rely on mysql as a backup way to kick it.
### END INIT INFO

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
NAME=displayweewx
DAEMON=/root/displayweewx/main.py
DAEMONARGS=""
PIDFILE=/var/run/$NAME.pid
LOGFILE=/var/log/$NAME.log

. /lib/lsb/init-functions

test -f $DAEMON || exit 0

case "$1" in
    start)
        start-stop-daemon --start --background \
            --pidfile $PIDFILE --make-pidfile --startas /bin/bash \
            -- -c "exec stdbuf -oL -eL $DAEMON $DAEMONARGS > $LOGFILE 2>&1"
        log_end_msg $?
        ;;
    stop)
        start-stop-daemon --stop --pidfile $PIDFILE
        log_end_msg $?
        rm -f $PIDFILE
        ;;
    restart)
        $0 stop
        $0 start
        ;;
    status)
        start-stop-daemon --status --pidfile $PIDFILE
        log_end_msg $?
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 2
        ;;
esac

exit 0

After all of this we get the following on the TV

From far far away.
From far far away.