Single board computer projects

Pitoucon: Raspi touch panel with Kivy and MQTT

last updated: 11/03/18

pitoucon_weather

My Mechanical Ventilation Heat Recovery (MVHR) system from Paul was updated with a Teensy and a Raspberry Pi (see Piventi and is now sending MQTT messages:

piventi mqtt message

To see this messages and manipulate the air flow I needed a touch panel. I bought a 3.5" PiTFT from Adafruit and use it with a Raspberry Pi 3. The Pi is connected over Wifi an is also getting data from a weather station.

To draw the graphical screen and use the touch function of the screen I use Kivy on the console (without X).

pitoucon_screen

For MQTT I use the Paho library:

    sudo pip3 install paho-mqtt

Install Kivy

After some trial and error this link helped to install kivy on a raspi3 (raspbian nov/17):

    sudo apt-get update
    sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
       pkg-config libgl1-mesa-dev libgles2-mesa-dev \
       python-setuptools libgstreamer1.0-dev git-core \
       gstreamer1.0-plugins-{bad,base,good,ugly} \
       gstreamer1.0-{omx,alsa} python-dev libmtdev-dev \
       xclip xsel
       sudo pip3 install -U Cython==0.27.3
       sudo pip3 install git+https://github.com/kivy/kivy.git@master

Tested with a minimal python program:

    from kivy.app import App

    class pitouconApp(App):
        pass

    if __name__ == '__main__':
        pitouconApp().run()

With the following pitoucon.kv program (same folder):

Screen:
    Label:
        text: 'Hallo'

Software

To understand the Kivy logic was not so easy, but finally the python software worked. A switch on gpio 3 is used to toggle the backlight. The switch is polled every half of a second in a callback function that is initiated by an event (see https://kivy.org/docs/guide/events.html). Another event every 10 minutes sends an alive message.

I wanted to use the Screenmanager with more screens, but finally one screen sufficed. The pitoucon.kv-file is in the download section. Here is the python code:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-

    from kivy.app import App
    from kivy.clock import Clock
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.uix.boxlayout import BoxLayout
    from kivy.properties import ObjectProperty, StringProperty
    import paho.mqtt.client as mqtt
    import RPi.GPIO as GPIO
    import json
    import time, datetime

    class ScreenManagement(ScreenManager):
        pass

    class Main(Screen):
        inoutflow = StringProperty()
        freshexh = StringProperty()
        tacho = StringProperty()
        co2 = StringProperty()
        def setVent(self,percent):
            print('button state is: ', percent)
            message = "{\"flow-rate\":\"" + str(percent) + "\"}"
            mqttc.publish('weigus/attic/attic/ventilation',message)

    class Setup(Screen):
        pass

    class pitouconApp(App):
        def build(self):
            global SM #ScreenManager
            global s
            SM = self.root
            s = SM.get_screen('main')

        def on_start(self):
            global mqttc
            global switch_pin
            switch_pin = 3
            global switch_old
            switch_old = 1
            GPIO.setmode(GPIO.BCM)
            GPIO.setwarnings(False)
            GPIO.setup(switch_pin, GPIO.IN)
            clientID   = "pitoucon"
            brokerIP   = "192.168.1.111"
            brokerPort = 1883
            topic      = "mytopic"
            # Callback if CONNACK response from the server.
            def onConnect(client, userdata, flags, rc):
                print("Connected with result code " + str(rc))
                mqttc.subscribe(topic, 0)  # Subscribe (topic name, QoS)
            # Callback that is executed when we disconnect from the broker.
            def onDisconnect(client, userdata, message):
                print("Disconnected from the broker.")
            # Callback that is executed when subscribing to a topic
            def onSubscribe(client, userdata, mid, granted_qos):
                print('Subscribed on topic.')
            # Callback that is executed when unsubscribing to a topic
            def onUnsubscribe(client, userdata, mid, granted_qos):
                print('Unsubscribed on topic.')
            # Callback that is executed when a message is received.
            def onMessage(client, userdata, message):
                io=message.payload.decode("utf-8")
                if (io[2:6] != "flow") and (io[2:7] != "alive"):
                    try:
                        ioj=json.loads(io)
                        inflowTxt = ioj["inflow"]
                        outflowTxt = ioj["outflow"]
                        inOutTxt = "Temp   Hum      \n" + inflowTxt + "  IN \n\n" + outflowTxt + "  OUT"
                        s.inoutflow = inOutTxt
                        freshTxt = ioj["fresh"]
                        exhaustTxt = ioj["exhaust"]
                        freExTxt = "        Temp   Hum\nFRESH   " + freshTxt + "\n\nEXHAUST " + exhaustTxt
                        s.freshexh = freExTxt
                        tinTxt = ioj["tacho-in"]
                        if tinTxt[2]  == "%":
                            tinTxt = tinTxt[0:2]
                        else:
                            tinTxt = tinTxt[0:3]
                        toutTxt = ioj["tacho-out"]
                        if toutTxt[2]  == "%":
                            toutTxt = toutTxt[0:2]
                        else:
                            toutTxt = toutTxt[0:3]
                        tachoTxt = "Tacho I/O : " + tinTxt + "% " + toutTxt + "%"
                        s.tacho = tachoTxt
                        co2Txt = ioj["co2"]
                        co2Txt = "CO: " + co2Txt
                        s.co2 = co2Txt
                    except:
                       print("json error")
                else:
                    pass
            # Callback every 500ms to poll the backlight switch and act accordingly
            def poll_switch(dt):
                global switch_old
                switch = GPIO.input(switch_pin)
                if (switch != switch_old):
                    if switch:
                        with open("/sys/class/backlight/soc:backlight/brightness", "w") as f:
                            f.write('0')
                    else:
                        with open("/sys/class/backlight/soc:backlight/brightness", "w") as f:
                            f.write('1')
                switch_old = switch
            def send_alive(dt):
                samessage = datetime.datetime.now().strftime('%Y/%m/%d %H:%M:%S')
                samessage = "{\"alive\":\"" + samessage + "\"}"
                mqttc.publish('weigus/attic/attic/ventilation',samessage)

            mqttc = mqtt.Client(client_id=clientID, clean_session=True)
            mqttc.on_connect      = onConnect   # define the callback functions
            mqttc.on_disconnect   = onDisconnect
            mqttc.on_subscribe    = onSubscribe
            mqttc.on_unsubscribe  = onUnsubscribe
            mqttc.on_message      = onMessage
            mqttc.connect(brokerIP, brokerPort, keepalive=60, bind_address="")
            mqttc.loop_start() # start loop to process callbacks! (new thread!)
            event = Clock.schedule_interval(poll_switch, 1 / 2.) # poll switch 500ms
            event = Clock.schedule_interval(send_alive, 600) # send alive 10 min.
    if __name__ == "__main__":
        pitouconApp().run()

Housing

For the housing I searched for PiTFT on thingiverse and found the OctoPrint housing. But there exist 2 versions of the 3.5" PiTFT with different dimensions. I got Version with the PID 2097 and not the newer PID 2441. So a second search gave me the Touch Pi housing from adafruit. I changed the bottom stl, so a raspi3 could be mounted and added blocks to fix it on the wall. Fortunately there is enough space and the possibility to add a switch for the backlight was given.

pitoucon<em>housing</em>bottom pitoucon<em>housing</em>top pitoucon<em>housing</em>ext pitoucon<em>housing</em>freecad

Further problems and changings

Prevent Raspi dropping WiFi

My Pi became randomly inaccessible over WiFi. In /var/log/syslog I found:
{TIMESTAMP} raspberrypi dhcpcd[{PID}]: wlan0: carrier lost
It turned out the problem was that the Pi WiFi controller has power_save on by default.

Command to read the current power saving mode (Stretch):

    sudo iw wlan0 get power_save

Command to power_save off:

    sudo iw wlan0 set power_save off

To make this permanent add the following line to /etc/rc.local:

/sbin/iw dev wlan0 set power_save off

Pi shutdown

I added a bushbutton (Pin 40 (GPIO21) to Ground) to my pitoucon. When the button is pressed for less than 3 seconds, my Pi reboots. If pressed for more than 3 seconds it shuts down.

The code is on github.com/gilyes/pi-shutdown.

I added the following line to /etc/rc.local:

sudo python pishutdown.py &

If you use pin 5 (GPIO3) and it is pressed while shut down, the Pi restarts. This is not possible with pin 40.

Downloads