Thursday 16 November 2017

Techno Beer

Pi and Beer

Not satisfied with just being a simple home brewer I decided to hook up a Raspberry Pi to control the temperature of my beer fermentation. Pretty useful when I'm too lazy to go into the garage, at work or just wanting to show off some real-time home automation. 

In this post I go through how I did this and the technologies used. This is no Craft Beer Pi setup, but it's a good jumping off framework guide.


OK, this isn't yeast, but it is beer related

What's in it?

Basically I used OpenVPN, MQTT, Energenie and the reasonably priced DS18b20 temperature probes to build this.


OpenVPN is simple using the software at PiVPN. I don't think I need to elaborate on this much further as it isn't strictly beer related, but it does allow connection to your internal MQTT.

For MQTT just setup the Mosquitto server with:
> sudo apt-get install mosquitto

This gets the main servers installed and running. It's basic and with no authentication, but it will be there.

Energenie sell the Pi-Mote board which fits onto the pins of any Raspberry Pi. Example Python and instructions are on the Energenie site. My own code for the beer control implements a simple Python class to wrap the GPIO calls required to control the power sockets.
I did contemplate and even bought a set of relays to switch mains power and then realised I'd be crazy to mess about with mains voltage in a hobby project. Energenie have done the risky work and packaged it into a remotely controlled solution. 

The DS18B20 is a popular probe to use with liquids so I bought a pack of these on Amazon. These are inserted into the beer so a food grade heat shrink is required. Adafruit have exactly the heat shrink needed.

I avoided messing around with strip boards or bread boards and used the excellent ProtoZero board from ProtoBoards

Build the Pi

Disassembled view
The Pi-mote doesn't need any additional work to operate on the Pi. Just plug in and it's ready to go. The Energenie plugs into the top set of 26 pins which makes it compatible with early Raspberry Pi boards.

Incorporating the temperature probe only requires 3 pins and a 4.7k ohm resistor to work (3.3V power, ground and GPIO pin 4). Multiple probes can be chained off pin 4. I've used JST connectors to allow disconnection of the probes when not in use and to allow easy cleaning.


A stacking header is needed to provide pins to the Pi-mote board. Pin 4 isn't required by the Pi-mote and doesn't interfere with the Dallas 1-wire protocol used by the temperature probe.

Assembled boards and sensor
I've used the Pi Zero W for this project as it has all the wireless connectivity built in. 
Beer environments involve liquid so it's also wise to move the exposed hardware into a food container.

The ProtoZero board has ample room to expand the number of temperature probes that can be supported as can be seen in the Fritzing image below.
ProtoZero with 2 probes

2 probe extension in action

Energenie sockets

A commercial solution to DIY electrocution. Powertails also exist for 120V. I'm in the UK and the Adafruit switches wouldn't work so this is the next best thing. 

Setup is very simple. Plug in and press and hold the green button until the light flashes. This is set to program mode. Send an "on" signal to associate with the Pi-mote. The programs are remembered even after disconnection from mains power.

I plugged this into the heating mat for the beer fermenter bucket.
With the Python code running this is switched "on" to add heat and "off" when the temperature is at the max level.

The Pi-mote can control up to 4 sockets which is more than enough for beer!

Dashboard

Once all data is going into MQTT then getting the results out is really easy. 
Android has MQTT Dash and MQTT Dashboard in the app store. I like the look of MQTT Dash the best for viewing my beer temperature. iOS doesn't seem to have may MQTT apps which work as well as these but I'm keeping an eye out.

Both apps take in a topic and allow read and write access to the data. Metrics such as temperature readings are read-only whilst configuring the heaters can be write access to switch on/off. The apps need configuring to show what you are interested in and how you would like to view the information. It's fairly quick to do and once setup the dashboards can be viewed easily.

MQTT Dashboard Subscriptions

MQTT Dashboard Publishing

MQTT Dash growing Lactobacillus (high temp)

MQTT connection

MQTT provides a message bus to publish and subscribe messages. It's light weight and built for IoT devices.
Development of applications is quick and easy with many supported development languages as well as shell scripting.

A service bus provides a logical disconnect between the presentation layer (UI) and device data. Data on the bus can also be consumed by other devices or applications to provide further automation or new services.
For example; if the service was to go offline a watchdog application could send an email or text to alert that the device needs attention.

Connections can be encrypted and access to the server secured if you are worried of someone stealing data or turning devices on and off without your permission.

Code example

Control is really simple and my code is available on GitHub at https://github.com/AidanHolmes/beerpi.

This provides Python class wrappers for the Energenie and DS18B20 temperature sensors along with a control program to manage temperature using MQTT. 

Saturday 7 October 2017

GPS logger and Google Maps


It has been a while since I posted about the Raspberry Pi Zero, PaPiRus and GPS logger.
Since the post I've updated further with a web service for displaying routes with Google maps. I actually use this GPS logger a lot when I'm out and about walking. This means I'm keeping it up-to-date for my own use, which I hope others also find useful.

I could represent the routes on the e-ink screen directly, but a squiggly line on a small screen just isn't going to be as good as a plot on a proper mapping service. All the important questions asked in the pub at the end of a walk are answered on the device screen, such as how far you've gone, for how long and the pace. The map plots are good for recalling an old route or recalling the point you go lost. 

Check out the previous post with details on the original build. 

All code can be cloned from GitHub at https://github.com/AidanHolmes/gpstracker

My implementation uses the original Pi Zero without the wireless or bluetooth from the W version. A wireless Zero makes connection to the web interface much more straight forward, but be aware of security implications running on public WiFi. 

Mapping GPS Routes

All logs are stored on the SD card and read by the web interface.
From the main screen the stored routes are listed with summary info. Summary information is built on this screen so it can take a bit of time to show as log files do not yet save this summary info in the logs.
Root view of all logs

Clicking on a stored route takes you to the Google Map screen and shows a plot of where you went. 

Single route - Helvellyn Walk
Here's an example of a walk up Helvellyn in Cumbria. 
Clicking on the start icon for any route shows distance and time.

Multiple routes are also shown on the map.
Multi-route GPS log
This is example of a log which contained 2 routes. Different colours are used and the start-end markers have different numbers. 
A single log can hold multiple routes when the log button is pressed and held to start and stop logging.

Implementation

Python Flask is used to display the web service from the Pi Zero. It keeps things fairly small and simple.

A Google Maps API key is required. They are free to register one so if you are using my code then add to config.py your own key. For example:
webconfig = {
    'interface' : '0.0.0.0',
    'port' : 80,
    'googlekey' : 'BBffSyUUP6agt11EblRT-123sdddFFAD'
}

Start the web interface
> python gpstracker/web.py

Access the root web page of your Raspberry Pi by entering the IP address into your browser. 
Logs are read from the logdir location as specified in config.py.

Read the web.py code for an example of how the TrackerGPS object is used to read data from a log file.

To get data from a file into an instance of TrackerGPS, each log line (JSON) needs feeding into a data entry with a call to data.gps_serial_data. Once the data is loaded it can be commited to as an entry with a call to data.commit_data(). Loop around to load all lines and then the TrackerGPS object can be queried for summary data. 

Alternatively a log file can be read with a call to readsessionlog(openfile, filterrecords=filt). This reads the log and returns an array of the GPSSummary objects, with one for each session.

Enabling Ethernet Gadget on a Zero

The Pi Zero with its Gadget USB feature is awesome to hook up to a PC and extend the features with a web interface. Note that the non-zero Pis up to the Pi 3 do not have a gadget USB and this will not work.
Simply add the following to your /boot/cmdline.txt and it will function as a network device in Windows when plugged into the USB port.

modules-load=dwc2,g_ether

For full instructions check out the latest configuration requirements with a quick Google search.
Adafruit have a tutorial here.

Connection sharing

RNDIS devices on a Windows OS are a bit tricky to get working first time. Connection sharing must be enabled to allow the Raspberry Pi to connect through to Google Maps. 
Plugging in the USB will not immediately grant a IP address to the Raspberry Pi. It can take a few minutes for the Pi to acquire a new address. On top of this Windows sometimes need persuasion by disabling and re-enabling the connection sharing to the RNDIS device

Run Ifconfig on your Raspberry Pi to check you have a routable IP address in the 192.168.x.x subnet.
Routable IP network address



Tuesday 13 June 2017

Pimoroni HyperPixel

Something to Compare

HyperPixel 800x480
I've just picked up the HyperPixel screen from Pimoroni and it is a quality screen for the Raspberry Pi. I thought it would be good to post a bit about getting this up and running along with a comparison with the cheaper and bigger 5 inch HDMI  XPT2046 touchscreen that I got from eBay.

At the end of the day these screens are likely to be embedded (for me that I means glued) into most projects. The 7in official screen is available, but I feel it's just a bit too big for embedding and too small for development.

The HyperPixel fits in-between the cheap and expensive touchscreens. At the cheap end screens are resistive touch where as the top end are capacitive touch. A stylus is usually required for accurate touch on a resistive screen, although a finger nail can be an good enough substitute. Capacitive touch will pick up from a finger tip without much pressure on the screen.
The HyperPixel is a capacitive touch screen whereas the XPT2046 uses resistive. In my opinion each have their pros and cons. Resistive is good for single screen clicks but a bit poor for dragging and dropping type actions. Capacitive won't work through gloves or with a stylus (unless conductive), but is better for swiping and dragging.

HyperPixel

Setup of the HyperPixel is really straight forward. It's a single command install provided by Pimoroni. There are detailed instructions for manual installation if you like to know what's going on. 
I ran the simple one on a fresh Jessie install and it worked straight after reboot. Happy days compared to the XPT2046. 

Booting up to the desktop presents a really clear display with excellent viewing angles. I need to tilt the XPT2046 to see it clearly, whereas this is clear at all angles.
The screen fits in my Short Crust Plus case very neatly, but the lid or other extenders will not fit as the screen is in the way. 
Touch detection is accurate and works to the edges reliably which is also a plus against the cheaper XPT2046 which just doesn't like the edges.
In the packaging there is a nylon thread which is to support one side of the screen. I found this to be too long for use with a bare Pi 3 board and too short when used with a Pimoroni coupe case. There may be a bit of DIY to cut this down to size. The screen sits neatly on the USB ports so the support isn't really necessary.
Nylon Thread Support

The Pixel desktop isn't a touchscreen friendly environment and the small icons are a pain to access on the small 3.5'' display. You will see that I have a wireless keyboard and mouse attached and this is necessary for both screens to setup the software and quickly navigate in a browser. What this screen, and other small touchscreens need is a window manager like Android to really work with just touch. 

The HyperPixel screen connects using the DPI parallel display interface. This means that you lose the SPI and I2C capability. There are still free GPIO pins available but the ability to interface with these isn't straight forward when the screen is attached. Some research is needed to discover the available pins. In comparison the XPT2046 avoids this by connecting to the HDMI and keeping a lot of pins free for other components. 

XPT2046 5'' Display

This screen is the cheaper choice and at 5'' gives a bit more for your money. The viewing angle is poor and even a slight tilt reduces colour contrast. Colours are not as bright as the HyperPixel. On a plus point the bigger screen is better on the old eyesight for reading text.

Out of the box the screen is a bit fiddly to setup. A disc of instructions are provided and clear enough to step through with a bit of patience. It works with the latest Jessie install quite happily. A bit of tweaking is needed in config.txt to prevent to the overscan at the bottom of the screen.

As the screen connects up to the HDMI it refreshes as well as the HyperPixel. Touch response isn't as good and the accuracy is poor near the edges. Mouse and keyboard are still needed to manage Pixel.

Once connected the 40 pin header still has a lot of free pins to play with. Only the SPI and 2 GPIO pins are used. The screen actually only connects to 26 pins allowing jumper wires to squeeze in.
The screen also has a pass through for all 26 pins allowing another header to be attached to the back of the screen. 

What Does This Mean?

HyperPixel is a good high quality screen. If you want mobile phone quality then this is the one. 

Both screens are useless in my opinion for Pixel desktop. This isn't the fault of the screens and is more to do with Pixel being designed for mouse and keyboard. 
If you are coding up your own interfaces, for example in Pygame, then you can cater for the touchscreen inputs and have a great experience. This should open the doors to cool custom applications and games on the Pi.

Directly these screens are hard to compare. They are for different purposes and it will depend on what they will be running.

Overall the XPT2046 screen is better for people who want to add extra hardware and run a display. Attaching I2C devices or running stuff off the GPIO pins is much easier than the HyperPixel. 

HyperPixel appears to be for applications which need a clear screen and capacitive touch at a decent price.

Virtual Keyboard Setup

XVKBD on the HyperPixel
If you are determined to use the touchscreen then I'd recommend XVKBD
> sudo apt-get install xvkbd

Run the keyboard from the command line with 
> xvkbd &

Matchbox on the XPT2046

Another keyboard is Matchbox
> sudo apt-get install matchbox-keyboard

Run matchbox from the command line with 
> matchbox-keyboard &

Both keyboards work well, but the xvkbd has slightly larger buttons and is a bit easier to read and touch first time. Both are a bit clunky to use and will need scripting into the start up scripts to load automatically.

Modmypi has a quick post about Matchbox setup, but the principles are the same for xvkbd desktop icon setup as well.


Sunday 30 April 2017

Raspberry Pi GPS

Raspberry Pi GPS

I covered a whole GPS solution using a Pi Zero in a previous post. This covered a lot of different components and the only one I haven't posted about is the GP-20U7 module for GPS.
This approach will also work for other GPS modules as GPSD supports lots of devices over serial UART and USB.
I've chosen the GP-20U7 basically because it's one of the cheaper options for GPS and it does a reasonable job. This GPS device provides information every second. GPS lock is a bit slow from cold start.
My post here will cover GPS usage using Python2.7 and not using other tools or languages.

Sadly the standard GPSD python library is just 2.7 but there are python 3 libraries ported outside the standard distro for the Pi.

GPS Installation

A couple of packages are needed to get GPS services up and running
> sudo apt-get install gpsd python-gps

There are some tools to see GPS on X and the command line.
Note that the clients will also install dependencies for X so I skip these for headless Pi Zero builds. If you would like to try them then install
> sudo apt-get install gpsd-clients

Edit /etc/defaults/gpsd. For the GP-20U7 module add info to the file
# Default settings for the gpsd init script and the hotplug wrapper.

# Start the gpsd daemon automatically at boot time
START_DAEMON="true"

# Use USB hotplugging to add new USB devices automatically to the daemon
USBAUTO="true"

# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
DEVICES="/dev/ttyAMA0"

# Other options you want to pass to gpsd
GPSD_OPTIONS=""

Ensure the serial ports are enabled and the login shell is disabled.
Run raspi-config and access Advanced options
> sudo raspi-config

Select the Serial option.


When asked if you want the login shell accessible, answer No.
This removes references from the /etc/inittab and /boot/cmdline.txt.
The serial port should now be free for the GPS module to use.

Hardware Setup

Ensure there's no power to the hardware before plugging in the GPS module.

The image shows a Raspberry Pi Zero, but the 40 pin header is the same for Pi 2 and Pi 3 so the wiring is the same. 
Power to VCC is from the 3.3V pin. GND can connect to any group pin on the Raspberry Pi header. TX pin on the GPS module must connect to the RX pin on the Raspberry Pi.

The connector on the GP-20U7 is a JST female, and unless you have a male JST adapter the wires will need cutting to add whatever connector you need. I use 0.1 inch female crimped housing which can be found from various online vendors (linked to one vendor as an example).

Running GPSD

Executing the service requires enabling the service and socket. This will ensure the service runs on start-up. 
> sudo systemctl enable gpsd.service
> sudo systemctl enable gpsd.socket
> sudo systemctl start gpsd.service

The service will run and respond to requests for GPS data using the loopback interface (127.0.0.1) on the default port of 2947.
To change the interface and port the /etc/systemd/system/sockets.target.wants/gpsd.socket file can be updated with the required settings

Other Pi Issues

The year may be incorrectly displayed if the Pi clock hasn't been updated over NTP, set manually or maintained by a real time clock. I've not yet experimented with different clock settings for the GP-20U7 and it may or may not be an issue.

Example Code

All example code is for python 2.7.

None of the code is GP-20U7 specific. If you have a GPS module working with GPSD then this should work.

Each example is more advanced than the last. This should help you discover the appropriate level of code to start working from.

Python Example - 1

This example is about as simple as possible to test that GPSD is running as expected

import gps

gpsobj = gps.gps('127.0.0.1', 2947) ;
gpsobj.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)

try:
    for rpt in gpsobj:
        try:
            print rpt

        except StopIteration:
            # Attempt to restart
            gpsobj = gps.gps()
            gpsobj.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)

except:
    gpsobj.stream(gps.WATCH_DISABLE)

print ("\nTerminated GPS Example 1")

This spits out a report from the python gpsd library. There's a lot of information here and can take a bit of time to figure out what is what. There is detailed documentation for the gpsd_json data, which explains all of the output.

Note that gpsd requires a continuous loop to keep up and read data. Clients which do not read data before the stream buffer is full will be disconnected by the gpsd daemon. 

Python Example - 2

This example shows the extraction of TPV data, which is typically what you need for any GPS application as this record provides all the location, time and speed info.
In addition to this the SKY information is also read to provide some information on satellites, although this isn't essential.
All data fields cannot be assumed to be present and each needs to be checked.
The key field to check is the mode data field. Until this equals 2 or 3 the location isn't fixed.
When mode is 2 then the data is 2D information and although the location is fixed, there's no altitude data.
When mode is 3 then the data is 3D meaning all longitude, latitude and altitude is fixed.

import gps
import dateutil.parser

kmtomiles = 0.621371

info = {'mode':0,
        'gpstime':'',
        'error_time':0,
        'latitude':0,
        'longitude':0,
        'error_latitude':0,
        'error_longitude':0,
        'altitude':0,
        'error_altitude':0,
        'speedkm':0,
        'speedmiles':0,
        'error_speed':0,
        'climb':0,
        'error_climb':0,
        'satellites':0,
        'satellites_used':0}

gpsobj = gps.gps() ;
gpsobj.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)

# Print a header for values being displayed
print(str(info.keys()).strip('[]'))

try:
    while True:
        try:
            gpsdat = gpsobj.next()
            if gpsdat['class'] == 'TPV': # Read time-position-velocity data
                if hasattr(gpsdat, 'time'):
                    nix_time = dateutil.parser.parse(gpsdat.time)
                    info['gpstime'] = str(nix_time)
                if hasattr(gpsdat, 'ept'): info['error_time'] = float(gpsdat.ept)
                if hasattr(gpsdat, 'mode'): info['mode'] = int(gpsdat.mode)
                if hasattr(gpsdat, 'lat'): info['latitude'] = float(gpsdat.lat)
                if hasattr(gpsdat, 'lon'): info['longitude'] = float(gpsdat.lon)
                if hasattr(gpsdat, 'epy'): info['error_latitude'] = float(gpsdat.epy)
                if hasattr(gpsdat, 'epx'): info['error_longitude'] = float(gpsdat.epx)
                if hasattr(gpsdat, 'alt'): info['altitude'] = float(gpsdat.alt)
                if hasattr(gpsdat, 'epv'): info['error_altitude'] = float(gpsdat.epv)
                if hasattr(gpsdat, 'speed'):
                    info['speedkm'] = float(gpsdat.speed)
                    info['speedmiles'] = float(gpsdat.speed) * kmtomiles
                if hasattr(gpsdat, 'eps'): info['error_speed'] = float(gpsdat.eps)
                if hasattr(gpsdat, 'climb'): info['climb'] = float(gpsdat.climb)
                if hasattr(gpsdat, 'epc'): info['error_climb'] = float(gpsdat.epc)
            if hasattr(gpsdat, 'satellites'): # Read sky data
                satellites_used = 0
                for sat in gpsdat.satellites:
                    if hasattr(sat, 'used'):
                        if sat.used:
                            satellites_used += 1
                info['satellites'] = len(gpsdat.satellites)
                info['satellites_used'] = satellites_used
                
            print(str(info.values()).strip('[]'))
                
        except KeyError:
            pass # Ignore key errors
        except StopIteration:
        # Attempt to restart
            gpsobj = gps.gps()
            gpsobj.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)

except KeyboardInterrupt:
    gpsobj.stream(gps.WATCH_DISABLE)
    print ("\nTerminated GPS Example 2")

Python Example - 3

Let's get into something more advanced with a threaded GPS application. An application needs to constantly poll and consume GPSD data to keep up-to-date and prevent GPSD from closing the data stream. This is a bit problematic for applications that need to do other things like wait for user input. 
The solution is to run the GPS updates in the background and provide a utility to get position data when required.
This example provides a GPSWorker object which does all the hard work in another thread. At the end of the example you can see a simulated application which waits for user input.

Enter q to quit and any other input followed by Enter to print GPS data. This demonstrates an application that has to wait on other inputs and processing without concern about consuming GPS data.
The example here just extracts longitude, latitude and altitude, but any of the other data points can be read in an application. 

import gps
import dateutil.parser
from threading import Thread, Lock

class GPSWorker(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.info = {'mode':0,
                     'gpstime':'',
                     'error_time':0,
                     'latitude':0,
                     'longitude':0,
                     'error_latitude':0,
                     'error_longitude':0,
                     'altitude':0,
                     'error_altitude':0,
                     'speedkm':0,
                     'speedmiles':0,
                     'error_speed':0,
                     'climb':0,
                     'error_climb':0,
                     'satellites':0,
                     'satellites_used':0}

        self.gpsobj = gps.gps() ;
        self.__quit = False
        self.__lock = Lock()
        self.__started = False 

    def start(self):
        self.gpsobj.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)
        if not self.__started:
            Thread.start(self)
        
    def stop(self):
        if self.is_alive():
            self.gpsobj.stream(gps.WATCH_DISABLE)

    def terminate(self):
        self.__quit = True

        # start the streaming again to unblock the wait
        self.gpsobj.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)
        if self.is_alive():
            self.join()
            self.__started = False

    def get_location_info(self):
        self.__lock.acquire()
        ret = self.info.copy()
        self.__lock.release()
        return ret
            
    def run(self):
        # Print a header for values being displayed
        #print(str(info.keys()).strip('[]'))
        kmtomiles = 0.621371

        while not self.__quit:
            try:
                gpsdat = self.gpsobj.next()
                self.__lock.acquire()
                if gpsdat['class'] == 'TPV': # Read time-position-velocity data
                    if hasattr(gpsdat, 'time'):
                        nix_time = dateutil.parser.parse(gpsdat.time)
                        self.info['gpstime'] = str(nix_time)
                    if hasattr(gpsdat, 'ept'): self.info['error_time'] = float(gpsdat.ept)
                    if hasattr(gpsdat, 'mode'): self.info['mode'] = int(gpsdat.mode)
                    if hasattr(gpsdat, 'lat'): self.info['latitude'] = float(gpsdat.lat)
                    if hasattr(gpsdat, 'lon'): self.info['longitude'] = float(gpsdat.lon)
                    if hasattr(gpsdat, 'epy'): self.info['error_latitude'] = float(gpsdat.epy)
                    if hasattr(gpsdat, 'epx'): self.info['error_longitude'] = float(gpsdat.epx)
                    if hasattr(gpsdat, 'alt'): self.info['altitude'] = float(gpsdat.alt)
                    if hasattr(gpsdat, 'epv'): self.info['error_altitude'] = float(gpsdat.epv)
                    if hasattr(gpsdat, 'speed'):
                        self.info['speedkm'] = float(gpsdat.speed)
                        self.info['speedmiles'] = float(gpsdat.speed) * kmtomiles
                    if hasattr(gpsdat, 'eps'): self.info['error_speed'] = float(gpsdat.eps)
                    if hasattr(gpsdat, 'climb'): self.info['climb'] = float(gpsdat.climb)
                    if hasattr(gpsdat, 'epc'): self.info['error_climb'] = float(gpsdat.epc)
                if hasattr(gpsdat, 'satellites'): # Read sky data
                    satellites_used = 0
                    for sat in gpsdat.satellites:
                        if hasattr(sat, 'used'):
                            if sat.used:
                                satellites_used += 1
                    self.info['satellites'] = len(gpsdat.satellites)
                    self.info['satellites_used'] = satellites_used
                                
            except KeyError:
                pass
            except StopIteration:
                # Attempt to restart
                self.gpsobj = gps.gps()
                self.gpsobj.stream(gps.WATCH_ENABLE | gps.WATCH_NEWSTYLE)
            finally:
                self.__lock.release()

# Main execution
if __name__ == "__main__":
    mygps = GPSWorker()
    mygps.start()
    try:
        while True:
            # Do stuff in your application
            # The application can call .start and .stop to control the gpsd stream

            # simulate stuff with user input
            response = raw_input("Type 'q' to quit and anything else to check GPS: ")
            if response == 'q' or response == 'Q':
                break

            # Where am I?
            info = mygps.get_location_info()
            if info['mode'] < 2:
                print ("No GPS lock")
            elif info['mode'] == 2:
                print ("2D Lock - LAT:{0}, LON:{1}".format(
                    info['longitude'],
                    info['latitude']))
            else:
                print ("3D Lock - LAT:{0}, LON:{1}, ALT:{2}".format(
                    info['longitude'],
                    info['latitude'],
                    info['altitude']))
            
    except KeyboardInterrupt:
        print ("") # skip over ^C
    except:
        mygps.terminate() # Call terminate to kill the thread
        raise
        
    mygps.terminate() # Call terminate to kill the thread

    print ("Application closing")


Closing Comments

GPSD does most of the hard work of reading the data from the hardware and converting it into a standard data stream. 
I've shown an example using a serial device connected to the 40 pin header. USB dongles can be found which should slot straight into a Pi and also work with the example code (you will need to use the serial /dev/ name instead of /dev/ttyAMA0)

GPS receivers also provide an additional feature for a Raspberry Pi because it also provides a clock source when your device is not connected to a real time clock or on a network. 

Thursday 26 January 2017

MQTT sensor setup with Raspberry Pi

Message Queue for IoT

So there's nothing new about MQTT. It's been around since the 90's, but despite all of this I've just discovered it and thought I'd share, even if it's just as a note to self about the setup. 
It stands for Message Queue Telemetry Transport and you can read the Wikipedia about it to dig into it a bit more.

It is simply a publish/subscribe message bus, which means something writes whilst other things can pickup and read the 'published' data. MQTT is a small, lightweight protocol suitable for IoT data and low bandwidth/resource communications.

A bus could look like the above and may have multiple devices connected. For example a sensor may be on one device reading temperature, whilst the other device may do something such as triggering a light to turn on or switch on the heating. Another mobile device could operate as a dashboard to see what's going on. 
So what's the big deal? It could all be easily built with some direct connections and some web pages. 

Well the great thing about the bus is it's flexibility to add or remove devices. In the above example the sensor on the Pi Zero could be replaced with and Arduino sensor. A new dashboard device could be introduced to also relay the information to your TV or email inbox with a summary. Other devices could be added to react to temperature readings and open/close a window or do other things. 
All these changes are possible without rewriting point-to-point code and allows reuse of information.

Setting up

The first thing to setup is the server. This is fairly straight forward

Install the server from the command line
> sudo apt-get install mosquitto 

Install client tools
> sudo apt-get install mosquitto-clients

This should install a running server and add tools for some testing

Run a publish command to write to the bus
> mosquitto_pub -t test -r -m "Hello World"
This writes to a topic called "test" the value "Hello World" and retains the message

Next read the topic
> mosquitto_sub -t test
You should see Hello World

Clear the topic
> mosquitto_pub -t test -r -n
This writes a null message to the topic to clear it

Typically messages are not published as retained messages and would only be on the bus for subscribers to read and then deleted. For this test a retention was used because the mosquitto_sub command wasn't running at the same time as the mosquitto_pub.

Using an EnviroHat

The Enviro pHat is a good Pi Zero board to use for a practical test of MQTT. The Python code is really straight forward to use and there are multiple sensors to collect data.

The Python code can be installed with the following command
> sudo apt-get install python-mosquitto

This isn't the most up-to-date mosquitto, but it's the easiest to install for a demo. Look at the Eclipse paho project because the latest mosquitto code has been merged into there.

Adding PaPiRus

To provide a readable display the PaPiRus e-ink display has been added, but isn't essential for a test


Example Code

Prerequisites for the code
For the PaPiRus setup follow my post to setup. Edit the configuration to set the screen size to 2.0
> sudo nano /etc/default/epd-fuse
Change EPD_SIZE and save
Restart with
> sudo service epd-fuse restart

The Enviro pHAT can be setup by following the instructions from Pimoroni.

In the example below, change the string in the connect_async to your MQTT server IP or server
name. I used the code reference for paho.

Code:

from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from envirophat import light, weather, motion
import time
from EPD import EPD
import mosquitto


WHITE = 1
BLACK = 0

def on_connect(client, userdata, rc):
        print("Connected with result code {}".format(rc))

def on_disconnect(client, userdata, rc):
        if rc != 0:
                print("Unexpected disconnection. Return {}".format(rc))
        else:
                print("Disconnected from server")

def write_text(draw, x, y, text, size):
        draw.text( (x, y) , text, font=font, fill=BLACK)

try:
        dis = EPD()
        dis.clear()
        text_size = 14

        client = mosquitto.Mosquitto()
        client.on_connect = on_connect
        client.on_disconnect = on_disconnect
        client.connect_async("mqtt.orbitalfruit")
        client.loop_start()

        font = ImageFont.truetype('/usr/share/fonts/truetype/freefont/FreeMono.ttf', text_size)
        image = Image.new('1', dis.size, 1)
        draw = ImageDraw.Draw(image)

        while True:
                l = 0
                rgb = light.rgb()
                temp = round(weather.temperature(),1)
                press = round(weather.pressure(),2)
                heading = motion.heading()
                lightstate = light.light()

                write_text(draw, 0, 0, "Temp: {}".format(temp), text_size)
                l += text_size
                write_text(draw, 0, l, "Pressure: {}".format(press), text_size)
                l += text_size
                write_text(draw, 0, l, "Colour R:{0},G:{1},B:{2}".format(rgb[0], rgb[1], rgb[2]), text_size)
                l += text_size
                write_text(draw, 0, l, "Heading: {}".format(heading), text_size)
                l += text_size
                write_text(draw, 0, l, "Light: {}".format(lightstate), text_size)

                dis.display(image)
                dis.partial_update()
                draw.rectangle([0,0,dis.width,dis.height], fill=1)

                client.publish("envirohat/temperature", temp)
                client.publish("envirohat/pressure", press)
                client.publish("envirohat/colour/red", rgb[0])
                client.publish("envirohat/colour/green", rgb[1])
                client.publish("envirohat/colour/blue", rgb[2])
                client.publish("envirohat/heading", heading)
                client.publish("envirohat/light", lightstate)
                time.sleep(10)

except KeyboardInterrupt:
        client.loop_stop()
        client.disconnect()

The client code publishes to the topic path "envirohat/". This is a useful way of grouping topics and forms a directory like structure.

Execute the code on the Pi Zero (note that it will need a network connection).

On your server you can type
> mosquitto_sub -t envirohat/#

The trailing # is a wildcard character to show all topics below the parent topic. This displays all values pushed to the bus by the Pi Zero.




Displaying to a Mobile Device

There are a few MQTT clients available across Andriod and Apple. 
MQTT Dashboard on Android provides a nice display. 
Configure the applications to point to the server and the topic of interest. I used 
envirohat/temperature 
envirohat/pressure
for testing



Take the Bus

This is just the basics, but the example creates something which can be used immediately for monitoring. Devices reading from the MQTT server can take actions and all the connecting devices can be built in a modular way.