Build a Mobile App and trigger Subprocess on your Raspberry Pi

This tutorial will run you through how to easily build a very very simple Mobile App using the Python library Kivy from scratch, as well as how to use the app for triggering a subprocess on the Raspberry Pi. At this point I must also warn you that I also tried to create a iOS package using Xcode in order to deploy the app on my iPhone. I was unable to do so, since Apple seems to impose loads of limitations on apps and so it does not allow you to run subprocesses... However, since it was also a great learning experience, I felt like sharing the bitter part of my journey too!!

It has been a couple of weeks since my boyfriend Marco (who is the real mastermind behind all my cool projects btw) and I started playing around with his Raspberry Pi 3. We are both currently stuck in London and Madrid respectively because of CoVid, and therefore only see each other through a screen. One of our fist ideas was building a little circuit that I could initiate from my machine in Madrid and would flash an LED in Marco’s room (yes, the possibility of using it as a channel for morse code messages was discussed). So we built the circuit, it was super fun, and then we wondered if turning the LED on from an app in our phones would be possible… We discovered the magical python library for app development Kivy and this is how this story of (some?) triumphs and (many) failures begins.

So basically what you can expect from this post is the following: Firstly a tutorial on how to create a simple Kivy App from which you will be able to trigger a subprocess on the Raspberry Pi using an ssh command. And also my personal experience of unsuccessfully trying to use Xcode for packaging the app and deploying it on my iPhone. Let’s goooo!

Paswordless SSH access to the Pi

Before getting started, let’s take a brief detour to make sure we can SSH into the Pi without a password, since this is required in order to make the code below work. This can be done by using SSH keys and it’s a very very straightforward process. This official tutorial is very clear and easy to follow.

Once this is done, try connecting to your Pi through ssh with the command below and check wether it asks you to insert a password (it shouldn’t!!).

ssh pi@<your_IP_address> 

Kivy

Kivy is this open source python library for app development. Marco and I explored the webpage and it looked promising: in theory you could write some python code and the library would package it as a mobile app that you could later simply deploy on your iPhone or Android. The tutorials on it looked fast and easy. “Too good to be true?” — we thought. Now we know for sure: too good to be true.

Installation guide is here. I used the command

-m pip install kivy

and didn’t give problems. We decided to warm up by going through this tutorial for building the game Pong. I must admit that by the end of it we were a bit confused… Kivy scripts definitely require some time of going through the code and understanding how elements connect, what triggers what, how to change the appearance on the .kv file etc… However, since our plan was only to create a simple interface with a single button to turn on the LED, this tutorial was more than enough practice.

Building our LED App

  • Grid Layout and Buttons

And so we went for it! We started with a basic Kivy template. In a file called main.py we wrote:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout


class MyGrid(GridLayout)::
pass


class MyApp(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
MyApp().run()

If you run the file at this point, a black window will pop up. What we have done here is creating a very simple app, in which the main space is divided into a grid layout. This GridLayout allows to divide the space of the interface into rows and columns. The widgets that we will add later to the app will be children to MyGrid, and will be placed on cells within this grid. We have added no widgets yet, that is why it is all black, don’t worry! But for instance, go ahead and test the following code:

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 2
self.hello = Button(text="hello", font_size=40) #button 1
self.world = Button(text="world", font_size=40) #button 2

#Adding the buttons as widgets. self.add_widget(self.hello)
self.add_widget(self.world)


class MyApp(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
MyApp().run()

If you run the example above you will be able to see that in this case we have created a grid layout formed by two columns. We have also inserted two button widgets, one on each column: Hello and World. What would happen if we created a third button? Where in the layout would it be placed? Let’s give this a go:

class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 2
self.hello = Button(text="hello", font_size=40) #button1
self.world = Button(text="world", font_size=40) #button2
self.button = Button(text="button 3", font_size=40) #button3

#Adding the buttons as widgets.
self.add_widget(self.hello)
self.add_widget(self.world)
self.add_widget(self.button)

The new button is placed on a cell under the “Hello” button. Also, the cell where a potential 4th button would be placed becomes obvious. Such as:

And this is basically how GridLayout seems to work for simple interfaces! However, as we are only interested in creating a single button, let’s replace the code we have created with a single-column, single-button layout:

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class MyGrid(GridLayout):
def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.led = Button(text="Turn on LED", font_size=40)
self.add_widget(self.led)


class MyApp(App):
def build(self):
return MyGrid()
if __name__ == "__main__":
MyApp().run()

The code above will give us a layout with one button. Should look like this (not the prettiest App out there, I admit):

  • Adding Actions

By clicking on the button shown in the interface above you will be able to check yourself that nothing happens. Let’s fix that by adding a response action!

class MyGrid(GridLayout):

def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.led = Button(text="Turn on LED", font_size=40)
self.add_widget(self.led) self.led.bind(on_press=self.pressed) def pressed(self, instance):
print("Button has been pressed")

In the code above you can see we have added the function pressed. This function holds the actions that I want to initiate by pressing the button, in this example simply printing a sentence. This function is connected to the button using .bind, a Kivy method typically used for binding events or properties to callbacks. Now when you run the script and click on the button you will see the message “Button has been pressed” printed on the terminal. We have added a very simple callback action.

  • SSH into the Pi and Run a Script from the App

We will use the subprocess module for connecting to the Raspberry Pi and running our script. You can add this line to the beginning of the file.

import subprocess

Now you will need to specify two variables after the import statements.

  1. The IP address of your Raspberry Pi → I usually ssh into Marco’s Pi remotely using his WiFi’s IP address.
  2. The path to the script you want to run. → We created a while ago a script for flashing an LED, and we have this file already stored inside the Raspberry Pi. Here there is a simple example on how to create such a file!
ip = "your.ip.address"
filepath = "Documents/Python-Projects/your_file.py"

Now let’s change the pressed function by removing the print statement and adding:

class MyGrid(GridLayout):

def __init__(self, **kwargs):
super(MyGrid, self).__init__(**kwargs)
self.cols = 1
self.led = Button(text="Turn on LED", font_size=40)
self.add_widget(self.led) self.led.bind(on_press=self.pressed) def pressed(self, instance):
subprocess.run("ssh pi@{} python {}".format(ip, file_path), shell=True, check=True)

subprocess.run allows you to run in parallel the command

ssh pi@your.ip.address python Documents/Python-Projects/your_file.py

In which you are effectively connecting to the Pi through SSH and running your desired script. Exactly what we were looking for! On top of that, you won’t need to provide your password, since you have enabled password-less connection to the Pi through the SSH key.

Our app is now ready and you can test it by running the main.py file! 📱

  • A note on the KV File

Most of the Kivy Apps will have a .kv file attached to the main file. This .kv file is used to change and improve the appearance of the interface of your App. It basically contains a set of rules that describe how each widget should look like. It is very useful as Apps grow in complexity and the number of widgets increases!

The App we have just built is very very simple and has a single widget!! Therefore… we didn’t see the need for creating a .kv file! However, if you want to expand on this App by adding more widgets and functionalities, you might be interested in reading the .kv documentation.

Creating an iOS Package

At the beginning of this article I promised you failures… and here they are! It was all going smoothly, but the problems started when we tried to deploy the App on our phones. Firstly, I can’t put into words how long it took us to wrap our heads around the process to do this for iOS. There were hours and hours of swearing and banging our heads against the wall (Xcode is a nightmare). And the worse thing was, once everything was done and ready, we discovered that… Apple does not support the subprocess module!!!!! 🤬🤬🤬 We only found out about this at the very end, because the app kept crashing when we clicked on the button for no reason…! This, this, this and this posts all agree: Apple does not allow you to run subprocesses. We were so disappointed. 😭 (if there is a way around this I’m unaware of please leave a comment and you will make me very happy!!!)

Anyways, I felt like just learning how to create Xcode projects was in itself a valuable lesson and I felt like sharing. So here we go. I firstly ran all the commands below:

brew install autoconf automake libtool pkg-config
brew link libtool
sudo easy_install pip
# pip method if available (sudo might be needed.)
sudo pip install Cython==0.29.10

And then I downloaded Xcode. I did it from the AppStore because I wouldn’t work otherwise, and this took AGES. Start the download before going for dinner or heading to bed, don’t make the same mistakes I did. Finally I ran:

pip3 install -r requirements.txt

Then I compiled the distribution and cd’ed to the folder. Once you are in the folder you can run ls to see a breakdown of the content.

git clone git://github.com/kivy/kivy-ios
cd kivy-ios
ls

Within the list of files you should find a .py file called toolchain.py. This is the King of files — you will be using it a LOT. Any Python extension has to be compiled though what they call recipes. This is done through the toolchain, as well as creating the Xcode project and installing packages that don’t need compilation. These are the available commands:

toolchain <command> [<args>]

Available commands:
build Build a recipe (compile a library for the required target architecture)
clean Clean the build of the specified recipe
distclean Clean the build and the result
recipes List all the available recipes
status List all the recipes and their build status

Xcode:
create Create a new xcode project
update Update an existing xcode project (frameworks, libraries..)
launchimage Create Launch images for your xcode project
icon Create Icons for your xcode project
pip Install a pip dependency into the distribution
pip3 Install a pip dependency into the python 3 distribution

The Kivy recipe may depend on some others, like python and sdl2. So let’s start by building those. It will take a while to build so I’d recommend going for a coffee or having a Netflix episode ready to watch.

cd kivy-ios
python3 toolchain.py build python3
python3 toolchain.py build sdl2
python3 toolchain.py build python3 kivy

Please note that I did this a couple of weeks ago so I might be forgetting some other requirements. You can build any other recipe using the same command structure. You can also see a full list of the recipes you have built using the command:

python3 toolchain.py recipes

Now let’s create an Xcode project and open it in the application!

python3 toolchain.py create <title> <app_directory>
python3 toolchain.py create exampleapp ~/Desktop/kivy/example_app

Where example_app is a directory that must contain your main.py created above. You can open the app in Xcode with:

open exampleapp-ios/exampleapp.xcodeproj

Now an Xcode window should pop up. Press Play on the upper right corner and you are ready to go!! You should get a message saying “Build Succeeded” and instantly an iPhone simulator window should appear. Eventually the App should open on the screen (takes a bit).

And here it is! Our beautiful iOS App! 📱 Such a pity that it crashes every time you press the button…. Give it a go, try pressing submit and… Oh no… the App will suddenly crash!! 🤬 As annoying as this was at the moment, I learnt so much in the process of simply creating an Xcode project that I felt like sharing my story. It might seem really straightforward, but I encountered a lot of challenges when trying to build the different recipes and download the necessary requirements for the Xcode simulation to work. I hope my experience will help somebody else with getting started with this app!

Might give it a go soon with an old Samsung I have at home to see if with Android deployment proves to be easier! 📱In the meantime, if anyone knows of any way of getting around this limitation, please comment below and you will have my eternal gratitude and admiration!

Sources:

https://github.com/kivy/kivy-ios

https://kivy.org/doc/stable/guide/packaging-ios.html

https://kivy.org/doc/stable/tutorials/pong.html

https://kivy.org/

https://www.raspberrypi.org/documentation/remote-access/ssh/passwordless.md

https://docs.python.org/2/library/subprocess.html

ML Engineer @ SpoonGuru | From Madrid | Always learning ✌🏻