combining kivy with mqtt

827 views
Skip to first unread message

Nico De Ranter

unread,
Dec 5, 2021, 10:04:58 AM12/5/21
to kivy-...@googlegroups.com
Hi folks,

I'm having difficulties updating the source of an image from an MQTT callback function. Whenever I change the StringProperty the image is looking at the image will simply go black.  Updating the text from a label does not appear to be a problem.

For instance, in the small example below the display shows the button, the 1.png image and 2 labels displaying 5 and -4.  
When I press the button the image changes as expected. 
When an mqtt message arrives the label showing temp_max increases as expected, but the image goes black.
When I press the button again the image is again displayed properly.
The next mqtt message again turns the image black and increases temp_max.
Note the images used in this example all exist and are properly working e.g. swapping 3.jpg and 4.jpg still give the same result: the button still displays an image but the mqtt callback still displays a black image.

Any ideas what could be wrong here?  Is the mqtt thread (not sure whether this is a separate thread or not) messing things up? But then why is the label updated properly?

touch.kv:
#:kivy 2.0
#:import kivy kivy
#:import win kivy.core.window
FloatLayout:
    Image:
        size_hint: None, None
        size: 220,220
        pos_hint: { 'center_x': 0.4, 'center_y': 0.5 }
        allow_stretch: True
        source: app.weather_icon
    Label:
        pos_hint: { 'center_x': 0.8, 'center_y': 0.6 }
        #bold: True
        color: 1, 0.3, 0.3, 1
        font_size: 72
        text: '%d'%app.temp_max
    Label:
        color: 0.7, 0.7, 1, 1
        pos_hint: { 'center_x': 0.8, 'center_y': 0.3 }
        #bold: True
        font_size:56
        text: '%d'%app.temp_min
    Button:
        text: 'Hit me'
        on_press: app.kvcallback()
        size_hint_x: None
        height: 50

touch.py:
import paho.mqtt.client as mqtt
import kivy
from kivy.app import App
from kivy.clock import Clock
from kivy.properties import StringProperty, NumericProperty

class TouchApp(App):
    weather_icon=StringProperty('1.png')
    temp_max=NumericProperty(0.0)
    temp_min=NumericProperty(0.0)
    doit=False
    client=None 

    def build(self):
        self.temp_max=5.0
        self.temp_min=-4.0
        self._mqttConnect()
        return

    def _mqttConnect( self ):
        self.client=mqtt.Client( client_id='kivytest', userdata=None, protocol=mqtt.MQTTv311, transport="tcp")
        self.client.on_message=self.mqttcallback
        self.client.connect( '172.30.10.3', port=8883, keepalive=60 )
        self.client.subscribe ('#', 0)
        self.client.loop_start()
        return

    def mqttcallback(self, client, userdata, message):
        if self.doit:
            self.temp_max=self.temp_max+1
            self.weather_icon='3.jpg'
        else:
            # skip the first update
            self.doit=True
        return

    def kvcallback(self):
        print("kv callback called")
        self.weather_icon='4.jpg'
        return

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


--
Nico & Ann De Ranter - Van Aelst

Elliot Garbus

unread,
Dec 5, 2021, 11:15:07 AM12/5/21
to kivy-...@googlegroups.com

I am assuming you are having an issue because you have 2 event loop based systems “competing” with each other.

Kivy operates on an event loop.  Calling app.run() starts the event loop.  It looks like paho mqtt is also event loop based, this is how it is managing the callbacks.   I suspect what is happening is that the code is sitting in the mqtt event loop – not the kivy event loop, so the kivy event never fires. 

 

I took a quick look at the MQTT docs, and saw this section: https://www.eclipse.org/paho/index.php?page=clients/python/docs/index.php#external-event-loop-support

 

I expect you could use these calls to integrate MQTT into the kivy event loop.  You can use the Clock.schedule_interval() to schedule these calls.  See: https://kivy.org/doc/master/api-kivy.clock.html?highlight=clock#module-kivy.clock

 

FYI: The convention in python is to only use a return statement at the end of a method if you are returning a value.

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/CAJoVLP7bmLVbhyMLRzp9HM1D0edSVK%3DTPG3gGt2MYuAxXP7fCQ%40mail.gmail.com.

 

Nico De Ranter

unread,
Dec 6, 2021, 2:10:48 PM12/6/21
to kivy-...@googlegroups.com
Thanks, I'll give it a try.

Nico

Op zo 5 dec. 2021 om 17:15 schreef Elliot Garbus <elli...@cox.net>:

Nico De Ranter

unread,
Dec 8, 2021, 3:24:43 PM12/8/21
to kivy-...@googlegroups.com
Adding my solution to the thread in case anybody else is interested:

I had a look at the external event loop support in MQTT but judging by what I found it looked like a rather complicated and potentially wasteful solution (lots of clock interval calls even if nothing is happening).  So I figured all I needed to do was to somehow transfer data from the mqtt loop to the kivy loop.  I tried creating a custom event but calling it with dispatch executes the eventhandler immediately from within the mqtt loop so that didn't help.  I was able to make it work using Clock.schedule_once however:  inside the mqttcallback I call Clock.schedule_once providing the input from the callback as extra parameters:

    def mqttcallback(self, mqttclient, userdata, message):
            Clock.schedule_once(partial(self.clock_mqtt_callback, mqttclient, userdata, message ))

The clockmqttcallback function is then called 'as soon as possible' inside the Kivy loop updating the image as expected

    def clock_mqtt_callback(self, mqttclient, userdata, message, dt):
          self.temp_max=self.temp_max+1
          self.weather_icon='3.jpg'

The image is now updated as expected!

Nico

Op ma 6 dec. 2021 om 20:10 schreef Nico De Ranter <nico.d...@gmail.com>:

Elliot Garbus

unread,
Dec 8, 2021, 4:13:59 PM12/8/21
to kivy-...@googlegroups.com

I’m delighted to hear you got things working.  I would caution you that this seems like a rather fragile solution.  You are at high risk of dropping messages because your MQTT loop is not running – or locking up the GUI because you are not running the Kivy event loop.

 

I’d recommend you use the clock interval calls.  These will simple establish a schedule to poll the items in the Kivy event loop that would normally be polled in the MQTT event loop.

 

Of course this is your code – so it is your call.  If it is working…

Nico De Ranter

unread,
Dec 12, 2021, 8:30:46 AM12/12/21
to kivy-...@googlegroups.com
Actually, both threads seem to be well behaved.  I tried adding a busy loop (while True: i=i+1) to both the mqttcallback and kvcallback routine.  When I trigger the mqttcallback loop by sending an mqtt message, the app doesn't respond to mqtt messages anymore but it still responds to button presses in the UI and updates the UI.  The other way around when I trigger the kvcallback loop the UI stops responding but mqtt messages are still getting processed. So I'm sticking to my solution :-)

Nico

Op wo 8 dec. 2021 om 22:14 schreef Elliot Garbus <elli...@cox.net>:

Elliot Garbus

unread,
Dec 12, 2021, 8:46:58 AM12/12/21
to kivy-...@googlegroups.com

😎  Thanks for the follow up!

Reply all
Reply to author
Forward
0 new messages