kivynonetypepyvisaobject-property

Kivy & pyvisa - How to make an ObjectProperty to None after a GPIB connection?


I'm facing a real blocking issue with kivy and pyvisa and I'm really lost about how to find a solution to this.

In the code below, I have an ObjectProperty called 'device' who's initialized to None. I want to use it to start a GPIB connection. When this one is closed, I want to set the device property to None again.

All the code below is a simple example to try to find a solution to this problem, but I'm facing actually this issue in a real application. One of the functions of this application is to choose which equipment to use in a list, and it is impossible for me to know in advance which one will be available or not, or even if the property 'device' will be connected in GPIB or in another protocol. This is why it is important for me to reset it to None.

Here is the code:

main.py

from kivy.app               import App
from kivy.uix.screenmanager import Screen
from kivy.properties        import ObjectProperty, BooleanProperty, StringProperty
from pyvisa                 import ResourceManager

class NoneScreen(Screen):

    # Properties
    address         = StringProperty('GPIB0::10::INSTR')
    isConnected     = BooleanProperty(False)
    device          = ObjectProperty(None)

    # Connect/Disconnect very basicaly to a GPIB device
    def connect(self):

        if not self.isConnected:
            print('Connect to', self.address)
            rm = ResourceManager()
            self.device = rm.open_resource(self.address)
            self.isConnected = True

        else:
            print('Disconnect from', self.address)
            self.device.close()
            self.isConnected = False

    # Test the state of the connection
    def testCon(self):
        try:
             self.device.query('*IDN?')
        except:
             print('You are not connected')
        else:
             print('You are connected :)')

    # Try something to make device properties None again
    def noneFct(self):
        self.device = None

# App
class MainApp(App):
    def build(self):
        ns = NoneScreen()
        return ns

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

Main.kv

<NoneScreen>:
    
    BoxLayout:
        orientation: 'vertical'

        Button:
            text: 'Print'
            on_release: print('self.device =', root.device)

        Button:
            text: 'Connect'
            on_release: root.connect()

        Button:
            text: 'Test Connexion'
            on_release: root.testCon()

        Button:
            text: 'Become None'
            on_release: root.noneFct()

So, here is my problem : when I connect and disconnect an equipment from the GPIB, all is going well BUT when I try to make 'device' return to None after disconnection I have this error :

self.device = None
Connect to GPIB0::10::INSTR
You are connected :)
self.device = GPIBInstrument at GPIB0::10::INSTR
Disconnect from GPIB0::10::INSTR
self.device = GPIBInstrument at GPIB0::10::INSTR
You are not connected
[INFO   ] [Base        ] Leaving application in progress...
 Traceback (most recent call last):
   File "C:\Users\Frederic\Desktop\debug\main.py", line 51, in <module>
     MainApp().run()
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\app.py", line 855, in run
     runTouchApp()
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\base.py", line 504, in runTouchApp
     EventLoop.window.mainloop()
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\core\window\window_sdl2.py", line 747, in mainloop
     self._mainloop()
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\core\window\window_sdl2.py", line 479, in _mainloop
     EventLoop.idle()
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\base.py", line 342, in idle
     self.dispatch_input()
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\base.py", line 327, in dispatch_input
     post_dispatch_input(*pop(0))
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\base.py", line 293, in post_dispatch_input
     wid.dispatch('on_touch_up', me)
   File "kivy\_event.pyx", line 707, in kivy._event.EventDispatcher.dispatch
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\uix\behaviors\button.py", line 179, in on_touch_up
     self.dispatch('on_release')
   File "kivy\_event.pyx", line 703, in kivy._event.EventDispatcher.dispatch
   File "kivy\_event.pyx", line 1214, in kivy._event.EventObservers.dispatch
   File "kivy\_event.pyx", line 1098, in kivy._event.EventObservers._dispatch
   File "C:\Users\Frederic\AppData\Local\Programs\Python\Python37\lib\site-packages\kivy\lang\builder.py", line 64, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "C:\Users\Frederic\Desktop\debug\main.kv", line 20, in <module>
     on_release: root.noneFct()
   File "C:\Users\Frederic\Desktop\debug\main.py", line 38, in noneFct
     self.device = None
   File "kivy\properties.pyx", line 497, in kivy.properties.Property.__set__
   File "kivy\properties.pyx", line 541, in kivy.properties.Property.set
   File "kivy\properties.pyx", line 532, in kivy.properties.Property.set
   File "kivy\properties.pyx", line 1001, in kivy.properties.ObjectProperty.check
   File "kivy\properties.pyx", line 570, in kivy.properties.Property.check
 ValueError: None is not allowed for NoneScreen.device

So, here is why it's going weird for me : if I try to do the same thing, without using kivy, in a python command line, it works !

>>> import pyvisa
>>> rm = pyvisa.ResourceManager()
>>> device = None
>>> print(device)
None
>>> device = rm.open_resource('GPIB0::10::INSTR')
>>> print(device)
GPIBInstrument at GPIB0::10::INSTR
>>> device.query('*IDN?')
'Agilent Technologies,33250A,0,2.01-1.01-1.00-03-2\n'
>>> device.close()
>>> print(device)
GPIBInstrument at GPIB0::10::INSTR
>>> device = None
>>> print(device)
None

So, I really don't understand why it doesn't work... Does anyone have any idea on how to fix this ?

N.B : This my first post here, so I hope all is clearly explained and that I didn't make a mistake ^^ Please let me know if it is the case !


Solution

  • https://kivy.org/doc/stable/api-kivy.properties.html#kivy.properties.Property

    None is a special case: you can set the default value of a Property to None, but you can’t set None to a property afterward. If you really want to do that, you must declare the Property with allownone=True:

    class MyObject(Widget):
    
        hello = ObjectProperty(None, allownone=True)
    
    # then later
    a = MyObject()
    a.hello = 'bleh' # working
    a.hello = None # working too, because allownone is True.
    

    Change

    device          = ObjectProperty(None)
    

    To

    device          = ObjectProperty(None, allownone=True)