Please help me understand the syntax of some statements in the usbserial4a example

78 views
Skip to first unread message

Jean-Marc Delaplace

unread,
Jan 23, 2021, 3:08:12 PM1/23/21
to Kivy users support
Hi, I am new to python and I start learning while tweaking with the usbserial4a example with UI: 
1-  line 45
        on_parent: app.uiDict['sm'] = self
and line 156
           self.uiDict['sm'].current = 'screen_test'
This dictionary key is not used anywhere in the application. I suppose it is used by the core of kivy, but how exactly? And what is this property .current? To which data type does it belong?

2-line 168
self.uiDict['txtInput_read'].text
What is this property .text? If uiDict is a dictionary, the expression self.uiDict['txtInput_read'] must return the second term of the item, which occurs to be a string.

3 - As an exercise, I have connected to my tablet an arduino board that produces a stream of data  of the form "xxxxx\r\neeeee\r\nzzzzz\r\n" and so on.
I display it in the field txtInput_read which seems to be an item of the dictionary uiDict.
I wanted to avoid the field overflow, by clearing it each time it is too crowded. For so I count the number of text lines, and when they are more than 40, I clear the field and the process continues. For so I have written the method nb_lines() as follows:

    def nb_lines(self, text):
        if text:
            return len(text.split('\n'))
        else:
            return 0

Then I test the size of the field and clear it when necessary:

             ...........
            self.uiDict['txtInput_read'].text += received_msg
            if self.nb_lines(self.uiDict['txtInput_read'].text ) > 40:
                self.uiDict['txtInput_read'].text = 'Received data:\r\n\n'      # clear field, add header
This works BUT not for long. After a few cycles of filling and clearing the field, when it is time to erase the field, an exception occurs with an index out of range for the item self.uiDict['txtInput_read'].text.

This makes me think of a collision with another thread which attempts to access this resource while it is being updated.

Since there is no such thing in my code, could it be a hidden process of kivy? If yes, how can I protect this field? is there a built-in semaphore to handle this?

Thanks for any advice.

  
            .........

Elliot Garbus

unread,
Jan 23, 2021, 4:13:33 PM1/23/21
to kivy-...@googlegroups.com

This code is not what I would describe as connectional.  It is a rather unique use to create a dictionary to store widgets.  More conventionally, there is a dictionary called ids that is created and populated when kv lang is processed.  I don’t know if this code is particularly old – or just “different”

 

On to your questions:

 

1-  line 45

        on_parent: app.uiDict['sm'] = self                   # in the context, self is the screenmanger.

and line 156

           self.uiDict['sm'].current = 'screen_test'        # current is an attribute of the screenmanager, it changes the current screen to the screen, ‘screen_test’

 

More conventionally, line 156 would read:

self.root.ids.sm.current = ‘screen_test’  # where sm referes to the id in the kv code, and self.root.ids.sm is the screenmanager instance.

 

Looking at bigger section of the kv code to answer your next question:

 

Screen:

            name: 'screen_test'

            BoxLayout:

                orientation: 'vertical'

                ScrollView:

                    size_hint_y: None

                    height: '50dp'

                    TextInput:

                        id: txtInput_write

                        on_parent: app.uiDict['txtInput_write'] = self

                        size_hint_y: None

                        height: max(self.minimum_height, self.parent.height)

                        text: ''

                Button:

                    id: btn_write

                    on_parent: app.uiDict['btn_write'] = self

                    size_hint_y: None

                    height: '50dp'

                    text: 'Write'

                    on_release: app.on_btn_write_release()

                ScrollView:

                    TextInput:

                        id: txtInput_read

                        on_parent: app.uiDict['txtInput_read'] = self

                        size_hint_y: None

                        height: max(self.minimum_height, self.parent.height)

                        readonly: True

                        text: ''

 

The parent attribute of widget holds the widgets parent in the widget tree.  The event on_parent is fired when the parent is set.  This is being used to load the widget into the app.uiDict dictionary. 

 

So

app.uiDict['txtInput_write']   is the TextInput widet, and app.uiDict['txtInput_write'].text is the text that was input.

Similarly app.uiDict['btn_write'], is the button and app.uiDict['btn_write'].text is the text that appears on the button.

 

More conventionally you would access these in the App class as

self.root.ids.sm.get_screen(‘test_screen).ids.txtInput_write

 

It is possible that there is threading conflict.  There is a new thread being created, line 153:

if self.serial_port.is_open and not self.read_thread:

            self.read_thread = threading.Thread(target = self.read_msg_thread)

            self.read_thread.start()

--
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/5bcac92c-8e9b-4915-a40a-9b35678360ddn%40googlegroups.com.

 

Jean-Marc Delaplace

unread,
Jan 23, 2021, 4:20:21 PM1/23/21
to Kivy users support
Yes, there is a new thread and this is what I intend to do. But since there is only one thread created by the application, and if there is a conflict, there must be an underlaying process that also accesses the field I attempt to modify. In this case, is there a protection means, like a semaphore, that allows to protect the field to prevent these conflicts?

Elliot Garbus

unread,
Jan 23, 2021, 4:37:52 PM1/23/21
to kivy-...@googlegroups.com

There is a main thread that is running the UI, and a second thread that is read_msg_thread.

The threading module is part of the python standard library, there a number of synchronization primitives’ including semaphores.

https://docs.python.org/3/library/threading.html

and some examples: https://pymotw.com/3/threading/index.html

 

I’m not familiar with the serial library that is being used, but often these kinds of libraries are non-blocking, so it is not necessary to use threads.

 

I do music programming with MIDI (just another serial protocol) and I use the Clock.schedule_interval() method in kivy to poll the read port at a regular interval.

Jean-Marc Delaplace

unread,
Jan 24, 2021, 11:28:58 AM1/24/21
to Kivy users support
I have captured the error message. I have pasted it below. It seems to me that it occurs in the thread that handles the refresh of the graphic elements. This thread belongs to the core and I have no control on it. Apparently, the sequence of events is the following : 
 1- the thread that refreshes the screen is triggrered (maybe by a timer?). It starts its execution.
2- the thread of my application resumes control (I assume that the multitasking is preemptive, am I right?). My code writes to the screen field a new text which is shorter than the previous one.
3- the screen refresh resumes in turn, and continues refreshing, but in the mean time, the length of the text has changed. The code that refreshes the screen attempts to access a character that does not exist since my app shortened the buffer. This raises an exception.

Did I correctly interpret the error message? What can I do to avoid this?
I tried to lock some parts of my app to make them atomic, but it did not improve. Actually is should be the core code that needs locks to make the processing atomic.
Thank you for your help.

============ Error message =============================================

[INFO ] [Logger ] Record log in /storage/emulated/0/qpython/Sources/.kivy/logs/kivy_21-01-23_35.txt
[INFO ] [Kivy ] v1.11.1
[INFO ] [Kivy ] Installed at "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/__init__.py"
[INFO ] [Python ] v3.8.3 (default, Jun 15 2020, 02:19:10) 
[GCC 9.3.0]
[INFO ] [Python ] Interpreter at "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/bin/python3"
[INFO ] [Factory ] 184 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_gif (img_pil, img_ffpyplayer ignored)
[INFO ] [Text ] Provider: sdl2
[INFO ] [Window ] Provider: sdl2
[INFO ] [GL ] Using the "OpenGL ES 2" graphics system
[INFO ] [GL ] Backend used <sdl2>
[INFO ] [GL ] OpenGL version <b'OpenGL ES 3.0 V@127.0 AU@ (GIT@I96aee987eb)'>
[INFO ] [GL ] OpenGL vendor <b'Qualcomm'>
[INFO ] [GL ] OpenGL renderer <b'Adreno (TM) 320'>
[INFO ] [GL ] OpenGL parsed version: 3, 0
[INFO ] [GL ] Texture max size <4096>
[INFO ] [GL ] Texture max units <16>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[INFO ] [GL ] NPOT texture support is available
[WARNING] [Base ] Unknown <android> provider
[INFO ] [Base ] Start application main loop
[INFO ] [Base ] Leaving application in progress...
 Traceback (most recent call last):
   File "/storage/emulated/0/qpython/Sources/essai8.py", line 212, in <module>
     
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/app.py", line 855, in run
     runTouchApp()
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/base.py", line 504, in runTouchApp
     EventLoop.window.mainloop()
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/core/window/window_sdl2.py", line 747, in mainloop
     self._mainloop()
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/core/window/window_sdl2.py", line 479, in _mainloop
     EventLoop.idle()
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/base.py", line 339, in idle
     Clock.tick()
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/clock.py", line 591, in tick
     self._process_events()
   File "kivy/_clock.pyx", line 384, in kivy._clock.CyClockBase._process_events
   File "kivy/_clock.pyx", line 414, in kivy._clock.CyClockBase._process_events
   File "kivy/_clock.pyx", line 412, in kivy._clock.CyClockBase._process_events
   File "kivy/_clock.pyx", line 167, in kivy._clock.ClockEvent.tick
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/uix/textinput.py", line 1865, in <lambda>
     lambda dt: self._refresh_text_from_property(*largs))
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/uix/textinput.py", line 1875, in _refresh_text_from_property
     self._refresh_text(self._get_text(encode=False), *largs)
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/uix/textinput.py", line 3091, in _get_text
     text = u''.join([(u'\n' if (lf[i] & FL_IS_LINEBREAK) else u'') + l[i]
   File "/data/user/0/ru.iiec.pydroid3/files/arm-linux-androideabi/lib/python3.8/site-packages/kivy/uix/textinput.py", line 3091, in <listcomp>
     text = u''.join([(u'\n' if (lf[i] & FL_IS_LINEBREAK) else u'') + l[i]
 IndexError: list index out of range


Elliot Garbus

unread,
Jan 24, 2021, 11:56:55 AM1/24/21
to kivy-...@googlegroups.com

Here is what I would recommend:

Do not use threading, it is not required in this case.

In App, create a Clock.schedule_interval(your_read_method, time_in_seconds), that calls the read method at an interval that makes sense for your app.

 

Alternatively,

Require a lock to access the shared variable.  OR

use a recycleview, and just keep adding data to a scrolling list.

Jean-Marc Delaplace

unread,
Jan 24, 2021, 12:07:05 PM1/24/21
to Kivy users support
I think that using your first option is a good idea. Thanks for your advice.

Elliot Garbus

unread,
Jan 24, 2021, 12:22:37 PM1/24/21
to kivy-...@googlegroups.com

😊 Good Luck, let me know how it works out!

Jean-Marc Delaplace

unread,
Jan 24, 2021, 12:50:14 PM1/24/21
to Kivy users support

It does. Thanks again!

Jean-Marc Delaplace

unread,
Jan 25, 2021, 10:59:06 AM1/25/21
to Kivy users support
Oh, there is actually a problem now. The function that writes on the screen, also sends a UDP telegram on a socket each time the screen is updated. In the thread version, this worked perfectly. Since the moment I changed my code to use  Clock.schedule_interval , the socket would not send the telegram any more. I have no idea of what's going on. 

Elliot Garbus

unread,
Jan 25, 2021, 12:56:39 PM1/25/21
to kivy-...@googlegroups.com

If you share your code I’ll take a look.

Jean-Marc Delaplace

unread,
Jan 25, 2021, 3:19:25 PM1/25/21
to Kivy users support
Here it is.
The received number from the serial link is both displayed on screen and sent in a UDP telegram under a OSC formatting.
Again, when using a thread, the screen display was troublesome, but the UDP socket worked well. Now, the screen display works well, but the UDP messages do no go out.

====================================================
'''usbserial4a example with UI.

This example directly works on Android 6.0+ with Pydroid App.
And it also works on main stream desktop OS like Windows, Linux and OSX.
To make it work on Android 4.0+, please follow the readme file on
'''

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.clock import mainthread
from kivy.utils import platform
import threading
import socket
import sys

monIP="127.0.0.1"
monPORT=int(9000)
sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
if platform == 'android':
    from usb4a import usb
    from usbserial4a import serial4a
else:
    from serial.tools import list_ports
    from serial import Serial

kv = '''
BoxLayout:
    id: box_root
    orientation: 'vertical'
    
    Label:
        size_hint_y: None
        height: '50dp'
        text: 'usbserial4a example'
    
    ScreenManager:
        id: sm
        on_parent: app.uiDict['sm'] = self
        
        Screen:
            name: 'screen_scan'
            BoxLayout:
                orientation: 'vertical'
                ScrollView:
                    BoxLayout:
                        id: box_list
                        orientation: 'vertical'
                        on_parent: app.uiDict['box_list'] = self
                        size_hint_y: None
                        height: max(self.minimum_height, self.parent.height)
                Button:
                    id: btn_scan
                    on_parent: app.uiDict['btn_scan'] = self
                    size_hint_y: None
                    height: '50dp'
                    text: 'Scan USB Device'
                    on_release: app.on_btn_scan_release()
'''

class MainApp(App):
    def __init__(self, *args, **kwargs):
        self.uiDict = {}
        self.device_name_list = []
        self.device_type_list = []
        self.serial_port = None
        self.read_thread = None
        self.port_thread_lock = threading.Lock()
        super(MainApp, self).__init__(*args, **kwargs)

    def build(self):
        return Builder.load_string(kv)

    def on_stop(self):
        if self.serial_port:
            with self.port_thread_lock:
                self.serial_port.close()
        
    def on_btn_scan_release(self):
        self.uiDict['box_list'].clear_widgets()
        self.device_name_list = []
        
        if platform == 'android':
            usb_device_list = usb.get_usb_device_list()
            self.device_name_list = [
                device.getDeviceName() for device in usb_device_list
            ]
            self.device_type_list = [
                port.getProductId() for port in usb_device_list]
            for device_type in  self.device_type_list:    
                self.uiDict['txtInput_read'].text += str(device_type)

        else:
            usb_device_list = list_ports.comports()
            self.device_name_list = [port.device for port in usb_device_list]


             
        for device_name in self.device_name_list:
            btnText = device_name
            button = Button(text=btnText, size_hint_y=None, height='100dp') 0155890675
            button.bind(on_release=self.on_btn_device_release)
            self.uiDict['box_list'].add_widget(button)
        
        # connexion automatique sur le premier de la liste s'il existe
        if self.device_name_list:
            if platform == 'android':
                device = usb.get_usb_device(self.device_name_list[0])
                if not device:
                    raise SerialException(
                        "Device {} not present!".format(device_name)
                    )
                if not usb.has_usb_permission(device):
                    usb.request_usb_permission(device)
                self.serial_port = serial4a.get_serial_port(
                    device_name,
                    9600,
                    8,
                    'N',
                    1,
                    timeout=1
                )
            else:
                self.serial_port = Serial(
                    device_name,
                    9600,
                    8,
                    'N',
                    1,
                    timeout=1
                )
        
            if self.serial_port.is_open and not self.read_thread:
                self.read_thread = threading.Thread(target = self.read_msg_thread)
                self.read_thread.start()
#fin connexion automatique
        
    def on_btn_device_release(self, btn):
        device_name = btn.text
        
        if platform == 'android':
            device = usb.get_usb_devicevice_name)
            if not device:
                raise SerialException(
                    "Device {} not present!".format(device_name)
                )
            if not usb.has_usb_permission(device):
                usb.request_usb_permission(device)
                return
            self.serial_port = serial4a.get_serial_port(
                device_name,
                9600,
                8,
                'N',
                1,
                timeout=1
            )
        else:
            self.serial_port = Serial(
                device_name,
                9600,
                8,
                'N',
                1,
                timeout=1
            )
        
        if self.serial_port.is_open and not self.read_thread:
            self.read_thread = threading.Thread(target = self.read_msg_thread)
            self.read_thread.start()
        
        self.uiDict['sm'].current = 'screen_test'

    def on_btn_write_release(self):
        if self.serial_port and self.serial_port.is_open:
            if sys.version_info < (3, 0):
                data = bytes(self.uiDict['txtInput_write'].text + '\n')
            else:
                data = bytes(
                    (self.uiDict['txtInput_write'].text + '\n'),
                    'utf8'
                )
            self.serial_port.write(data)
            self.uiDict['txtInput_read'].text += '[Sent]{}\n'.format(
                self.uiDict['txtInput_write'].text
            )
            self.uiDict['txtInput_write'].text = ''
    
    def read_serial_line(self):
        received_msg = ''
        try:
            if self.serial_port.in_waiting > 0 :
                while len(received_msg) <= 80 :                # ligne maxi 80 caractères
                    car = self.serial_port.read(1)
                    received_msg += car.decode('ascii')
                    if car == b'\n' :        # arrêt de la réception au caractère LF
                        break
            
            return received_msg
        
        except Exception as ex:
            raise ex        
                
    def Ajuste_multiple_quatre(self, chaine):
        chaine += '\00'        # doit se terminer par au moins un null
        while (len(chaine) % 4) != 0 :
            chaine += '\00'
        return chaine
                                
    def read_msg_thread(self):
        while True:
            try:
                with self.port_thread_lock:
                    if not self.serial_port.is_open:
                        break
                        
                msg = self.read_serial_line()    # si rien reçu, retourne chaîne vide
                if msg != '':
                    self.display_received_msg(msg)
                    msgOSC = msg[:-2]        # suppression de CR et LF
                    msgOSC = self.Ajuste_multiple_quatre(msgOSC)
                    msgOSC = "/Param/LB\00\00\00,s\00\00" + msgOSC
                    sock.sendto(msgOSC.encode('ascii'),(monIP,monPORT))
            except Exception as ex:
                raise ex
                
    @mainthread
    def display_received_msg(self, msg):
        self.uiDict['txtInput_read'].text += msg

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


Elliot Garbus

unread,
Jan 25, 2021, 3:46:15 PM1/25/21
to kivy-...@googlegroups.com

This code is using threading.  Is this the right one?

Jean-Marc Delaplace

unread,
Jan 26, 2021, 3:28:35 AM1/26/21
to Kivy users support
You are right. Now the code below is the timer version.
==============================================
'''usbserial4a example with UI.

This example directly works on Android 6.0+ with Pydroid App.
And it also works on main stream desktop OS like Windows, Linux and OSX.
To make it work on Android 4.0+, please follow the readme file on
'''

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.clock import mainthread
from kivy.utils import platform
from kivy.clock import Clock
import threading
import socket
import sys


        self.serial_port = None
        self.read_thread = None
        self.port_thread_lock = threading.Lock()
        self.monIP="127.0.0.1"
        self.monPORT=int(9000)
        super(MainApp, self).__init__(*args, **kwargs)

                
    def on_start(self):        
        self.uiDict['box_list'].clear_widgets()    
        self.uiDict['sm'].current = 'screen_test'
        self.uiDict['txtInput_read'].text = 'Demarre!\r\n\n'        
        self.device_name_list = []
        
        if platform == 'android':
            usb_device_list = usb.get_usb_device_list()
            self.device_name_list = [
                device.getDeviceName() for device in usb_device_list
            ]
        else:
            usb_device_list = list_ports.comports()
            self.device_name_list = [port.device for port in usb_device_list]
        
        if self.device_name_list:
            device_name = self.device_name_list[0]
        else:
        device_name = ''
        # connexion automatique sur le premier de la liste s'il existe
        if self.device_name_list:
            if platform == 'android':
                device = usb.get_usb_device(device_name)
                if not device:
                    raise SerialException(
                        "Device {} not present!".format(device_name)
                    )
                if not usb.has_usb_permission(device):
                    usb.request_usb_permission(device)
                self.serial_port = serial4a.get_serial_port(
                    device_name,
                    9600,
                    8,
                    'N',
                    1,
                    timeout=1
                )
            else:
                self.serial_port = Serial(
                    device_name,
                    9600,
                    8,
                    'N',
                    1,
                    timeout=1
                )
        
            if self.serial_port.is_open and not self.read_thread:
                self.sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
                Clock.schedule_interval(self.read_msg_routine, .1) #Déclenchement périodique de la lecture
            self.uiDict['sm'].current = 'screen_test'            
            #fin connexion automatique
 
    def on_stop(self):
        if self.serial_port:
            with self.port_thread_lock:
                self.serial_port.close()
      
    def build(self):
        return Builder.load_string(kv)

    def nb_lignes(self, texte):
        if texte:
            return len(texte.split('\n'))
        else:
            return 0
                                                                                                                   
    def read_serial_line(self):
        received_msg = ''
        try:
            if self.serial_port.in_waiting > 0 :
                while len(received_msg) <= 80 :                # ligne maxi 80 caractères
                    car = self.serial_port.read(1)
                    received_msg += car.decode('ascii')
                    if car == b'\n' :        # arrêt de la réception au caractère LF
                        break

            self.uiDict['txtInput_read'].text += received_msg
            if self.nb_lignes(self.uiDict['txtInput_read'].text ) > 40:
                self.uiDict['txtInput_read'].text = 'Demarre!\r\n\n'        
            return received_msg
        
        except Exception as ex:
            raise ex        
                
    def Ajuste_multiple_quatre(self, chaine):
        chaine += '\00'        # doit se terminer par au moins un null
        while (len(chaine) % 4) != 0 :
            chaine += '\00'
        return chaine
                                                                     
    def read_msg_routine(self, periode):

        try:

            with self.port_thread_lock:
                if not self.serial_port.is_open:
                    return
                    
                msg = self.read_serial_line()    # si rien reçu, retourne chaîne vide
                if msg:
                    msgOSC = msg[:-2]        # suppression de CR et LF
                    msgOSC = self.Ajuste_multiple_quatre(msgOSC)
                    msgOSC = "/Param/LB\00\00\00,s\00\00" + msgOSC
                    self.sock.sendto(msgOSC.encode('ascii'),(self.monIP, self.monPORT))
        except Exception as ex:
            raise ex

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


Elliot Garbus

unread,
Jan 26, 2021, 10:56:45 AM1/26/21
to kivy-...@googlegroups.com

I see two things I suggest you look at.

  1. In the threaded code the socket is opened unconditionally at the start of the code.  In the timer version, opening the socket is done conditionally.  Is the socket opened?
  2. Doing a file comparison of the 2 files, there are a lot of changes beyond just moving from threading to the timed loop.

Jean-Marc Delaplace

unread,
Jan 26, 2021, 12:15:19 PM1/26/21
to Kivy users support
Regarding the comparison, just forget the first file. I pick an outdated one.
I checked the opening of the socket. Apparently nothing special.
BUT
I used the UDP Terminal app. This app shows on screen the UPD traffic when correctly configured. 
With the thread version, it would show a continuous flow of data.
Now, it does not show a flow, but actually only the data that was produced by my app when it had the focus. Once the terminal gets the focus, the data flow is stopped.
It seems that the problem comes from the fact the my application now runs on the main thread and it is frozen when the app looses the focus.
 This did not happen with a thread that would continue running even when the app does not have the focus.
Is there a way to force the timer-activated function to continue to be called periodically event without the focus?

Elliot Garbus

unread,
Jan 26, 2021, 12:36:02 PM1/26/21
to kivy-...@googlegroups.com

I think that is an Android specific behavior.  The clock will continue to run on a desktop os. 

I had not expected that – but I should have. 

 

You can get the synch primitives working or and another option you could use a double ended queue from the collections module. https://docs.python.org/3/library/collections.html#collections.deque

This would remove the conflict of writing and clearing the same variable.

The data source fills the queue (append), the display method pulls data from the queue (pop). 

planckp...@gmail.com

unread,
Jan 26, 2021, 1:42:09 PM1/26/21
to Kivy users support
'Scuse me as I jump in...

main thread and it is frozen when the app looses the focus.

The Android behavior is that the app pauses (sleeps), not that the app looses focus. The focus idea is specific to the desktop UI metaphor.
So after an on_pause() one would then restart the download on_resume()

I think I remember that a running non-UI thread will stall app UI pause, I so always set daemon=True to prevent this. Otherwise the user will be surprised.
This may be Android version specific, but probably a good thing to do.

I have not been following this thread in detail, but if the design goal is some 'batch' download while the app is paused, then the solution is presumably an Android service, or Pyjnius instantiations of the Java DownloadManager class.
If the download is just current app state then there is no need for this and pause/resume is fine.

planckp...@gmail.com

unread,
Jan 27, 2021, 3:59:36 PM1/27/21
to Kivy users support

jeanmark wrote:

Thank you for your intervention. SInce I am new to this environment, I still know very little. Can you explain the purpose of

daemon = True

?

https://docs.python.org/3/library/threading.html

https://realpython.com/intro-to-python-threading/#daemon-threads

For your information, currently I am in the learning phase. My aim is to write an application that acquires a flow of data from a serial source connected to the USB of my tablet, reformat this data and send it out over wifi and also to an app (called TouchOSC) that expects data from the ethernet socket. My application will continuously run in the background, read incoming characters, format them and send some of them through wifi to the outside world, and another part of them to localhost so that the TouchOSC application which runs in the foreground, can display this data.

So the use of the display in my app will be only for debugging purposes, since my app will not be visible to the user in the normal operation.

It sounds like you want all the functionality in an Android Service.

And if you own TouchOSC it should start/stop the service, if not your Kivy app is trivial just start and stop the service.

In this context a service is basically a Python script, possibly with a loop to enable IPC with the app. No Kivy used. I/O will presumably be in thread(s) so as not to stall the IPC loop.

https://python-for-android.readthedocs.io/en/latest/services/

Some time ago I shared this trivial runable Kivy/Android Service example:

https://groups.google.com/g/kivy-users/c/7WKYPLMoZac/m/SQhvsXRcAAAJ

That example uses OSC for IPC (read the OSC notes in the top of main.py); so there may be some relation to TouchOSC?

Anyway, that is everything I have. Any implementation issues you may find - Google is your friend ;)

Jean-Marc Delaplace

unread,
Jan 28, 2021, 6:04:09 AM1/28/21
to Kivy users support
Yes, this is basically what I need. I will have TouchOSC running in the front, and a service running in the background that exchanges data over USB in serial form using usbserial4a. This serial data will be converted to OSC and sent to TouchOSC through a socket. Conversely, the user actions on TouchOSC will send OSC messages to the socket, that the service will catch and translate to a character form to send them to USB.
So far I am using Pydroid3. At the end, shall I keep Pydroid3 to run the service? Or what else?

Reply all
Reply to author
Forward
0 new messages