Adding a shutdown button to your Raspberry Pi Flask App

If you’re running a Flask App on your Raspberry Pi in Kiosk mode you may find that you want to add a shutdown button to the ensemble so that you can switch the Pi off safely without having to SSH into it and manually issue the shutdown command.

Based on Adding a Shutdown Button to the Raspberry Pi B+

The Hardware

  • a momentary button
  • a breadboard
  • a couple of jumper wires
  • a raspberry pi (I’m using a 3)

Wire the momentary switch up to Pin 18 and Gnd of the Raspberry Pi. As it’s a momentary switch it doesn’t matter which way around the wires are.

The Python Script

So, to make this button do anything you need a fairly short python script

import RPi.GPIO as GPIO
import os

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def shutdown(channel):
    os.system("sudo shutdown -h now")

GPIO.add_event_detect(18, GPIO.FALLING, callback=shutdown, bouncetime=2000)

Break the code down a little

  • GPIO.setmode(GPIO.BCM). This is where you decide whether you want to use the GPIO numbers (BCM – which change with different revisions of the Raspberry Pi or the board numbers (BOARD) which correlate to the numbers actually printed on the Pi. If you fancy going deeper into this there’s a clear and consise explanation of the difference between the two settings in this Stack Exchange answer and a very useful walkthrough of using the GPIO header and pins on Raspberry Pi Spy.
  • GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP). This sets up the channel number (acording to the schema you specified in the previous line, whether it’s an input or and out put and whether it’s pulling up or down (for down use PUD_DOWN.
  • Shutdown function – what you want to do when the interrupt fires.
  • the event_detected() function – this is where the really cool stuff happens. According to the Rasperry Pi GPIO wiki:

    The event_detected() function is designed to be used in a loop with other things, but unlike polling it is not going to miss the change in state of an input while the CPU is busy working on other things. This could be useful when using something like Pygame or PyQt where there is a main loop listening and responding to GUI events in a timely basis.

    This is also why it’s really useful to us.

    What we’re setting up:

    • the channel (again, acording to the schema you specified earlier).
    • which bit of the signal we’re looking for
    • the callback
    • how long we should wait to debounce.

If you transfer this to your Pi and run it (python /route/to/file/rpi_shutdown.py) when you press the button your Pi should shut itself down. You may need to pip install RPi.GPIO to make it work especially if you have an active virtualenv.

Adding the shutdown functionality to your Flask App

In the original tutorial this is called from /etc/rc.local so that it’s accissible from everywhere, unfortunately Python threads really badly and, if you’re using a virtualenv then the chances of it working at all are slim to none so you want to add the watcher to your flask app. This isn’t actually difficult to do but where you do it will depend on whether or not your app is modular. You need the function to be called in the whereever you initialise your app. In my case this is usually in a file called something like run.py which I use to call all the rest of the functions.

run.py

# shutdown button imports
import RPi.GPIO as GPIO
import os

GPIO.setmode(GPIO.BCM)
GPIO.setup(18, GPIO.IN, pull_up_down=GPIO.PUD_UP)


def shutdown(channel):
    os.system("sudo shutdown -h now")


if __name__ == '__main__':
    # initialise the button inside the main loop
    GPIO.add_event_detect(18, GPIO.FALLING, callback=shutdown, bouncetime=2000)
    app.run()

Now when you run your app you’re also setting up the gpio for the shutdown button so you can arbitrarily shutdown the pi from inside your app. Very useful if you’re running in kiosk mode.

Urbex: South Marsden Hotel

Today I went for a wander around South Marston Hotel with Joe of Loath, Skippy and theorbtwo.

The hotel went into administration in July 2016, 3 months before it was due to close leaving “[h]undreds of guests and more than 1,000 leisure members … [without refunds] for […] bookings already made” (source: Swindon Advertiser) and leaving the 45 employees without wages for the month.

The majority of the assets were auctioned off leaving many of the rooms with lot numbers above the doors and what remains has been trashed by the removal of any and all saleable goods (mostly copper, lead and hard wood).

The site has a registered planning application dating from November 2015 (yup that’s before the closure was announced) for the demolition of the existing hotel buildings and redevelopment to provide up to 70 dwellings. So it’s unlikely that these (now) scruffy buildings will be here much longer.

Joe of Loath did a good job of documenting the state of the property in his report. So the majority of my photographs focus on details, things that amused or saddened me.


Somehow the swimming pool in the spa complex is more of less intact – the retaining rail around the baby pool (pictured) is missing which, combined with the depth of the pool next to it makes it faintly terrifying. As Joe said, the entire hot tub has been removed and the presence of demolition detritus in the pool itself reminds me a little of a drained canal. Albeit one that has less mud than usual in it.


It almost feels like the wilful destruction of this motivational quote on the wall in the not-so-soft-anymore-play area is a commentary on society.


This is from the balcony café adjacent to the softplay. Given the state of the place (there is rotten food heaped next to one of the walk in fridges in what remains of the kitchen area – be grateful I can’t post smells), I think I’d rather they didn’t.


Decorations: hundreds of them, ghosts of Christmases, halloweens, easters. At one point we had to walk over fake trees in order to pass through a corridor. Another missed photograph there.


The majority of the office area rooms are filled with the what didn’t sell in the auction, there is very little that is personal here – with the exception of the personnel files – it’s corporate furniture, corporate crockery – corporate, impersonal junk.


Joe doesn’t remember that being there. Looks like a previous explorer might have forgotten it. While we’re on the subject of previous explorers…


I feel like this in rooms full of mirrors a lot.


These are the kitchen in the on site flat. Very low ceilings and just as trashed as the rest of the site.


I find the very existence of this sign both worrying and telling of the state of the hotel prior to it’s closure. This is on the “out” door from the kitchen.


What remains of the cellar (the place where they stored the alcohol, it’s not actually underground).


Possibly the most post-apocalyptic part of the whole site.

Judging by the difference between the first lot of photos taken by Joe and what we saw today this site is getting destroyed pretty quickly. I know it looks like a horrible mess, but, if you visit please please don’t destroy it further.

3D Printing with Ninjaflex on a Mendel90

Today, for the first time I tried printing with Ninjaflex. Having not printed with it before, I was a little concerned that it might prove a touch difficult. It really didn’t, but I did learn that most people set their printers to run a lot hotter and faster than I do.

Printer: Mendel90
Filament: 3mm Ninjaflex in blue

Before I started I did a bit of reading around the subject:

Based on the advice in those articles (and a test print at 195°C which failed to extrude properly) I tried the following settings:

  • Print speed: 30mm/s
  • Printing temperature: 210°C
  • Bed temperature: 50°C
  • Retraction:
    • Minimum travel: 1mm
    • Z hop when extracting: 0.1mm

If you would like the full profile I used it’s here: https://raw.githubusercontent.com/HACManchester/cura-settings/master/ninjaflex.ini

I coated the bed in dilute PVA just as I would if I were using PLA in the printer although it appears you can also get good results using blue printers tape or possibly even nothing on the bed.

2016-08-18 14.52.57

The final print is a bit bobbly in places but has generally come out pretty well:

2016-08-18 15.53.59 2016-08-18 15.54.11

JQuery snippet: automatic highlights

Sometimes you want to highlight a word automagically without having to go through and manually add <span> tags all over the place. Of course you also want to do this without breaking anything else that has been wrapped in a <p> tag. Ahem, WordPress I’m looking at you.

The first line of this iterates over every single <p> tag on the page.

The if statement then checks to see whether or not the paragraph in question actually contains the phrase and if it does replaces the sections of it that contain the phrase with the alternative text.

NOTE: if you don’t nest the actual replace within a $(this).html(); call what you end up with is just the text being returned and any child elements (e.g. p or img elements being removed from the DOM. This might not bother you if you’re working with a site only you are ever likely to update because you will be aware of the issues, however, if you’re working with clients you probably want to make sure nothing unexpected is going to happen.

(function($) {
  $('p').each(function() {
    var search_this = $(this).text();
    if (search_this.indexOf('Highlight phrase') >= 0) {
      $(this).html($(this).html().replace(/Highlight phrase/g, "<span>Highlight </span> phrase"));
    }
  });
})(jQuery);

Obviously this wont highlight any instances of the word “highlight” that have been incorrectly spelled but that, is a different problem.

WordPress Basics: get recent featured images by category

Sometimes you want to have an image slider which not only contains the most recent images on your site but does so by category to ensure that all of the categories on your site are included:

First get the categories:

$categories=get_categories();

By default this returns the categories in alphabetical order and ignores any empty ones. That’s fine by me so I’m not going to fiddle with it.

Now we’ve got our categories we need to loop over them. Don’t forget to close your foreach!

 foreach ($categories as $category) :
    # code here
endforeach; 

Inside that foreach loop we’re going to run a new WP_Query(), first though we need to set up our args:

$args = array(
            'cat' => $category->term_id,
            'post_type' => 'post',
            'posts_per_page' => '5'
          );

This pulls out the first 5 posts in each category. Once we’ve done that we can run our WP_Query().

$images_query = new WP_Query( $args );

if ( $images_query->have_posts() ): ?>
    <?php while ( $images_query->have_posts() ) : $images_query->the_post();
        if ( has_post_thumbnail() ) :
            the_post_thumbnail('post-thumbnail', array("class" => "slider_image"));
endif; endwhile; endif;?>

Excluding a category

In this example I don’t want to pull out featured images associated posts in the category 'Blog' so I added

&& $category->cat_name != 'Blog'

to the if statement inside my query.

Argh! I have posts in more than one category and the images are duplicating

To get rid of duplicate posts add the following on the line above your call to get_categories:

$do_not_duplicate = array()

Then, under $images_query->the_post(); add

$do_not_duplicate[] = $post->ID;

Now, that we have an array containing the ID’s of the posts that have already been pulled we can add a line to our $args to make the query check it:

$post__not_in => $do_not_duplicate

Voila, no more duplicate images.

Flask basics: lose the favicon 404

If the fact that the Flask development server cant find your favicon annoys you shove your favicon into your static folder and do this:

@app.route("/favicon.ico")
def favicon():
    return(url_for('static',filename='favicon.ico')

NOTE: this is one of several ways of getting rid of that particular error and is the one I’m using today. I’ll add more as I use them.

Running a Flask App on a Raspberry Pi in kiosk mode

Got a Flask app you want to run on a Raspberry Pi in kiosk mode? Here’s how.

Setup

Set up your Raspberry Pi to work in kiosk mode – I usually set it to go to Google or the BBC News, something I know is unlikely to fall over while I’m testing.

Once you know it is working rename .xsession to xsession so that it doesn’t run automatically. This allows you to boot your pi normally and check that your app is working before you setup all the auto-booting magic.

Transfer your Flask App on to your pi. Personally I favour packaging apps as a tarball (using setuptools) and pip installing later but you don’t have to. The simplest way to get the files from your computer to your pi is probably via sftp, just set up as a normal connection using the ip of the pi and shunt files as usual. I’ve tried a variety of ftp clients and recommend Transmit (Mac), however if you’d prefer an open source alternative Cyberduck (Windows/Mac) is also very good.

Virtualenv and flask app installation

While Virtualenv isn’t strictly necessary it’s still good practice so lets grab that and set up a venv for our app

  1. Install Virtualenv using either sudo apt-get install virtualenv or pip install virtualenv.
  2. Jump into your project folder cd my/project/folder and create a Virtualenv:
    virtualenv -p python3 venv

    If you want a python2.* environment then you can miss out the -p python3.

  3. Activate your Virtualenv:

    cd venv
      source /bin/activate

  4. Install your Flask app either using pip install your_flask_app or by installing it and it’s dependencies. NOTE: If you’re using bcrypt you will first need to sudo apt-get install python3-dev otherwise it will fail. If it still fails you may also need sudo apt-get install libffi-dev.

Start your app to make sure everything’s working.

Auto-start Virtualenv and Flask

There are a variety of ways to do this: chron (Running A Python Script At Boot Using Cron), daemons (see Getting a Python script to run in the background (as a service) on boot and http://raspberrypi.stackexchange.com/questions/12580/run-python-flask-server-from-daemon), rc.local.

Partly because cron was failing me (and I couldn’t work out why) I went with the second simplest solution which was adding the following to /etc/rc.local before exit 0:

. home/pi/PATH_TO_VENV]/bin/activate
python home/pi/PATH_TO_FILE_THAT_STARTS_APP.py

Reboot your pi and navigate to http://127.0.0.1:5000/ to check that everything works (or just skip this step if you’re super confident).

Auto-start Chromium in Kiosk mode

Put your .xsession file back: mv xsession .xsession

Then edit it so it’s hitting http://127.0.0.1:5000/ rather than whichever random site you told it to go to before.

Reboot one more time just to prove to yourself that everything is absolutely working. Do a dance1 and go make yourself a $beverage – mines a black coffee, ta :).

Raspberry Pi Kiosk Mode using Raspbian Lite

Download the latest Rasbian Lite from the Raspberry Pi Foundation (at time of writing we’re on Jessie) and unpack it so you’ve got a .img file.

Setting up the SD card

NOTE: The following section is a truncated version of the Mac instructions from the Raspberry Pi Foundation. Instructions for other operating systems (or further details for Mac users if the below fails) are available here: https://www.raspberrypi.org/documentation/installation/installing-images/README.md.

  • Open a terminal window and run:
    diskutil list
  • From the list identify the disk (not partition) of your SD card in this instance it is disk4:Screen Shot 2016-07-05 at 14.41.44
  • Unmount the SD card
    diskutil unmountDisk /dev/disk<NUMBER>
  • Copy the data to the card
    sudo dd bs=1m if=route/to/disk/image.img of=/dev/rdisk<NUMBER>
  • Wait. There will be no on screen info during this time.
  • Run:
    sudo diskutil eject /dev/rdisk

    then remove the disk from your computer and plug it into your Pi.

Setting up the Pi

The default login is set to:

Username: Pi
Password: Raspberry

SSH

This is worth doing as if you have a short peice of network cable you can create a direct connection between your pi and your main computer, allowing you to use your full keyboard and screen to set up your pi. Cool huh?

If you have a screen and keyboard you can setup SSH using raspi-config:

  1. type sudo raspi-config
  2. select interfacing options
  3. select SSH
  4. choose Yes
  5. select OK
  6. choose finish

Running headless? In that case you will want to create a file named ssh on the boot partician of the SD card. When the Pi boots, it looks for the ‘ssh’ file. If it is found, SSH is enabled, and the file is deleted. The content of the file does not matter: it could contain text, or nothing at all.

Do these instructions look familiar? They’re the same ones as appear here: https://www.raspberrypi.org/documentation/remote-access/ssh/.

WiFi

If you don’t need WiFi for your end setup skip this step and just use a network cable to connect yourself directly to the pi.

Setup your network by editing wpa_supplicant.conf to include your network data (instructions via The Pi Hut). Open the file using Nano:

sudo nano /etc/wpa_supplicant/wpa_supplicant.conf

Then add the following to the bottom of it:

network={
         ssid="YOUR_SSID"
         psk="YOUR_PASSWORD"
}

Then save (ctrl+o) and exit (ctrl+x). Run sudo reboot to connect to your network.

Code editor

If like me, you have a preference as to text editor you will want to install that next:

sudo apt-get install vim

Check everything’s up-to-date

Even though you only just downloaded the disk image you will want to run the following:

sudo apt-get update && sudo apt-get -y upgrade

Install the stuff you need in order to have a GUI

sudo apt-get install --no-install-recommends xserver-xorg xinit xserver-xorg-video-fbdev lxde lxde-common lightdm
  • --no-install-recommends stops it installing ALL THE THINGS which it will otherwise do. If however you need it to respond to keyboard or mouse input, then you want to leave this bit out.
  • x gives you a screen session
  • lxde gives you the UI
  • light dm is the login manager

Do some configuration

Type sudo raspi-config into the command line and do the following:

  1. Expand the file system: “`Advanced Options > Expand Filesystem“`.
  2. Disable overscan: “`Advanced Options > Disable Overscan“`. This helps ensure the display fills the entire screen rather than leaving black bars down the sides.

Install Chromium

This should be simple… but really isn’t. Fortunately this blog post explains how to do it.

These links take you to the latest libcrypt11Chromium and Chromium codecs. Once you have the correct links you need to wget them onto the Pi then install. (It’s simplest to run headless when doing this because copy/paste).

wget http://launchpadlibrarian.net/237755896/libgcrypt11_1.5.3-2ubuntu4.3_armhf.deb
wget http://launchpadlibrarian.net/263322752/chromium-browser_51.0.2704.79-0ubuntu0.14.04.1.1121_armhf.deb
wget http://launchpadlibrarian.net/263322754/chromium-codecs-ffmpeg-extra_51.0.2704.79-0ubuntu0.14.04.1.1121_armhf.deb

sudo dpkg -i libgcrypt11_1.5.3-2ubuntu4.3_armhf.deb
sudo dpkg -i chromium-browser_51.0.2704.79-0ubuntu0.14.04.1.1121_armhf.deb
sudo dpkg -i chromium-codecs-ffmpeg-extra_51.0.2704.79-0ubuntu0.14.04.1.1121_armhf.deb

You may need to run sudo apt-get -f install after you’ve done this to install the dependencies.

Unclutter

Install unclutter to hide the curser:

sudo apt-get install unclutter

Auto-login

sudo vim /etc/lightdm/lightdm.conf

Then uncomment and amend the following lines so that they match the below:

autologin-user=pi
autologin-user-timeout=0

Setup your .xsession script

If you don't have a .xsession file in /home/pi/ then create one and add the following to it:

# Dont want screensavers or screen blanking
xset s off &
xset -dpms &
xset s noblank &

# Hide the mouse cursor
[ ! -x "`which unclutter 2>/dev/null`" ] || unclutter -idle 10 -noevents &

# Sit and wait until you can hit the URL you'll be showing in the kiosk
while ! curl http://urlofthepageyouwanttoload.co.uk 2>/dev/null | grep -q "String that's on the page you want to load";
    do sleep 1
done

# Sit for 3 seconds
sleep 3

# Open chrome in incognito mode + kiosk mode
/usr/bin/chromium-browser --incognito --kiosk http://urlofthepageyouwanttoload.co.uk

Make sure everything works

Run sudo reboot. If the chromium browser doesn't fill the whole screen you may need to edit .config/chromium/Default/Preferences

It's an awful file to navigate visually and the code is in one very long string, I've put it on separate lines here to make it more readable.

"window_placement":{
    "bottom":1050,
    "docked":false,
    "left":0,
    "maximized":false,
    "right":1680,
    "top":0,
    "work_area_bottom":1050,
    "work_area_left":0,
    "work_area_right":1680,
    "work_area_top":0}},

You need to change the values for "bottom" and "right" to the maximum dimensions of your screen and ensure that "top" and "left" are 0.

EDIT: as Jacob says in the comments these settings don't appear to be in the file any more. However, you can fix it by adding --window-size=1920,1080 to the browser launch line in your .xsession script.

Things I read in order to make this happen (that aren't explicitly mentioned above)

Thanks guys 🙂

Jessie on Raspberry PI 2 with Docker and Chromium

Kiosk Display on Raspbian Jessie

Inkscape – where have all the triangles gone?

Triangles are one of those things that seem like they should be a fundamental part of any vector graphics program but sometimes don’t appear in the menu. Like Adobe Illustrator, Inkscape classes triangles as polygons. So, to make a triangle you first select the star/polygon tool:

Screen Shot 2016-05-03 at 20.34.48

When you click on it you’ll get a new menu at the top, you want the following settings:

Screen Shot 2016-05-03 at 20.43.46

It actually doesn’t matter with a three sided shape whether you select star or polygon as your starting point, however, if you use star you’ll need to set the spoke ratio to 0.5 otherwise you’ll get additional unexpected corners in your shape.

You don’t want any rounding for a true triangle so set rounded to 0.

If you want an equilateral triangle then set “Randomized” to zero.

Pull out your triangle (holding down the ctrl key if you wish to align it horizontally/vertically).

Making the triangle have precise dimensions

Once you’ve drawn your triangle click on the Select tool:

Screen Shot 2016-05-03 at 20.34.48-1

This will change the handles on your triangle from this:

Screen Shot 2016-05-03 at 20.54.21

to this:

And will allow you to manipulate the width and height of the shape using the menu bar at the top:

Screen Shot 2016-05-03 at 21.03.20

If the lock is closed then changing either the width (W) or the height (H) will also change the other property. If it’s open they can be changed independently. Click the lock to toggle between the two.

If you click in either the width or height box you can type the dimension you wish the shape to have in, or you can use the up and down arrows on the right hand side to change the dimension by 0.1 of a unit (clicking in the box and using the up/down arrow keys will also change the value by a 0.1 of a unit).

The units ( cm / mm / px / % / &c ) can be changed by clicking on the dropdown to the right of the width/height boxes.

Flask basics: dynamically adding form elements

Sometimes you want to dynamically add elements to forms if certain information pre-exists. In this instance I wanted to add an option change the information displayed but I didn’t want the form fields to be there all the time and it was neater to simply not render them than remove them using JavaScript.

First, I was checking to see if there was more than one “narrative” in the database, and if there is I want to give the user the option of jumping between them, if there isn’t then they don’t need the extra fields and having them could cause confusion and result in the user attempting to input data which will cause errors.

A bit of searching revealed that dynamic form field generation is not only really simple to do, it’s a common enough request that its made it into the docs. However I’m putting it here because neither the docs, nor the in depth mailing list post included a fully worked example, and frankly writing it down means I stand a better chance of remembering what I did.

This form is actually a hybrid static/dynamic form. The static elements are defined in my forms file as UploadForm so I need to pass forms.UploadForm to my instantiated form.

from wtforms import StringField, RadioField

def admin(): class OptionalFields(forms.UploadForm): pass

Since I only want to render my new form fields if a certain criteria is met I wrap them in an if statement. Inside that statement I create a static field, and and unknown quantity of dynamically created ones. The static field is there because the existence of multiple “narratives” doesn’t necessarily mean that the user will want to jump between them at this point.

    if len(check_for_things) > 1:
        OptionalFields.pick_narrative =
                       RadioField(label="Swap?",
                                  choices=[("True", "Yes"),
                                           ("False", "No")])
        for thing in check_for_things:
            setattr(OptionalFields, thing,
                    StringField(thing.title()))

Once I’ve done that I need to change my form instance to the one I’ve just created. As I’ve been using form=forms.FormImUsing it’s just a case of changing forms.FormImUsing to OptionalFields

So, that displays the data, now I need to get at it. Because these fields are dynamically generated attempting to access them via form.fieldname.data results in an error and it’s equally not possible to pass a variable to fieldname.

Fortunately however it is possible to iterate over form.data.items() and pull out just the items you’re looking for.

    for fieldname, data in form.data.items():
        if fieldname == 'pick_narrative':
            # do whatever you'd normally do with the data
        if fieldname in check_for_things:
            # do whatever you'd normally do with the data

Putting it back: how to output the variable data to the form so the user can see it

Sometimes, you want to output previously input data to a form so that the user can edit it. It’s pretty easy to do this with wtforms, you just pass the form it’s data as an obj:

edit_this = models.Narrative.get(models.narrative.unique_data == unique_data)
form = OptionalFields(obj=edit_this)

It’s even easy enough to pass a bit of data that uses a different name to your form so say you use site in your database and website in your form you just do this:

form = OptionalFields(obj=edit_this, website = edit_this.site)

However, it suddenly starts looking a lot more complicated when you don’t know the field names, this is where dictionaries and kwargs come in handy. While you’re looping over the things you’re checking you might as well create a dictionary of the ‘thing’ against its ‘value’. Then you just unpack the dictionary as part of the function call:

    if len(check_for_things) > 1:
        OptionalFields.pick_narrative =
                       RadioField(label="Swap?",
                                  choices=[("True", "Yes"),
                                           ("False", "No")])
        thread_dict = {}
        for thing in check_for_things:
            setattr(OptionalFields, thing,
                    StringField(thing.title()))

            if thing_to_be_output == check_against_this:
                 thread_dict[thing] = value

        form = OptionalFields(obj=edit_this, **thread_dict)

And now, magically you have the current values visible in your form, even though you didn’t know what the form fields would be called in advance.