A mutex class inside another class? How to make it work?

Continuing the discussion from Wanted: A good SPI mutex:

Given the following code:
Waveshare demo code for the 2.7" display using SPI calls.

#!/usr/bin/python
# -*- coding:utf-8 -*-
import sys
import os
picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic')
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
    sys.path.append(libdir)

import logging
from waveshare_epd import epd2in7
import time
from PIL import Image,ImageDraw,ImageFont
import traceback

from di_mutex import DI_Mutex
mutex = DI_Mutex

logging.basicConfig(level=logging.DEBUG)

try:
    logging.info("epd2in7 Demo")   
    epd = epd2in7.EPD()
    
    '''2Gray(Black and white) display'''
    logging.info("init and Clear")

    try:
        mutex.acquire()
        epd.init()
    finally:
        mutex.release()

    try:
        mutex.acquire()
        epd.Clear(0xFF)
    finally:
        mutex.release()
<snip rest of code that is, essentially, irrelevant.>

The di_mutex code used above:

# https://www.dexterindustries.com
#
# Copyright (c) 2019 Dexter Industries
# Modified 2024-07 by Jim Harris to include usage information
# Released under the MIT license (http://choosealicense.com/licenses/mit/).
# For more information see https://github.com/DexterInd/DI_Sensors/blob/master/LICENSE.md
#
# Generic Dexter Industries python mutex
#
# Usage:
# Insert and un-comment the code between the double lines.
#
# ============================
#from di_mutex import DI_Mutex
#
#spi_mutex = DI_Mutex("SPI")
#
#
#try:
#    spi_mutex.acquire()
#    # do protected SPI access stuff here
#finally:
#    spi_mutex.release()
# ============================

from __future__ import print_function
from __future__ import division

import time
import fcntl
import os
import atexit


class DI_Mutex(object):
    """ Dexter Industries mutex """

    def __init__(self, name, loop_time = 0.0001):
        """ Initialize """

        self.Filename = "/run/lock/DI_Mutex_" + name
        self.LoopTime = loop_time
        self.Handle = None

        try:
            open(self.Filename, 'w')
            if os.path.isfile(self.Filename):
                os.chmod(self.Filename, 0o777)
        except Exception as e:
            pass

        # Register the exit method
        atexit.register(self.__exit_cleanup__) # register the exit method

    def __exit_cleanup__(self):
        """ Called at exit to clean up """

        self.release()

    def acquire(self):
        """ Acquire the mutex """

        while True:
            try:
                self.Handle = open(self.Filename, 'w')
                # lock
                fcntl.lockf(self.Handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
                return
            except IOError: # already locked by a different process
                time.sleep(self.LoopTime)
            except Exception as e:
                print(e)

    def release(self):
        """ Release the mutex """

        if self.Handle is not None and self.Handle is not True:
            self.Handle.close()
            self.Handle = None
            time.sleep(self.LoopTime)

And finally, my sanity test code.
It essentially does nothing, but I used it to verify that the mutex code itself wasn’t failing.

#!/usr/bin/python3.7

import os
import sys
from di_mutex import DI_Mutex

mutex = DI_Mutex("SPI")


try:
    mutex.acquire()
    print("SPI mutex acquired")
    # do protected SPI access stuff here
    print("sys.path before insert = ", sys.path)
    sys.path.insert(1,"/home/pi/Test_Libraries/")
    print("sys.path after insert = ", sys.path)
    import easygopigo3 as easy
    import I2C_mutex
    print("str(easy) = ", str(easy))
    print("str(I2C_mutex) - ", str(I2C_mutex))
finally:
    mutex.release()
    print("SPI mutex released")

Notes:
The sanity test code, when run inside Thonny, works perfectly, however the demo code, modified to use the Dexter mutex, always fails as follows:

>>> %Run epd_2in7_test.py
INFO:root:epd2in7 Demo
INFO:root:init and Clear
Traceback (most recent call last):
  File "/home/pi/e-Paper/RaspberryPi_JetsonNano/python/examples/epd_2in7_test.py", line 29, in <module>
    mutex.acquire()
TypeError: acquire() missing 1 required positional argument: 'self'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/pi/e-Paper/RaspberryPi_JetsonNano/python/examples/epd_2in7_test.py", line 32, in <module>
    mutex.release()
TypeError: release() missing 1 required positional argument: 'self'
>>> 

Adding “self” as a parameter, “mutex.acquire|release(self)”, as a prefix, “self.mutex.acqire|release()”, or both “self.mutex.acquire|release(self)”:

try:
    logging.info("epd2in7 Demo")   
    epd = epd2in7.EPD()
    
    logging.info("init and Clear")

    try:
        mutex.acquire(self)
        epd.init()
    finally:
        mutex.release(self)

    try:
        mutex.acquire(self)
        epd.Clear(0xFF)
    finally:
        mutex.release(self)

generates the following error:

>>> %Run epd_2in7_test.py
INFO:root:epd2in7 Demo
INFO:root:init and Clear
Traceback (most recent call last):
  File "/home/pi/e-Paper/RaspberryPi_JetsonNano/python/examples/epd_2in7_test.py", line 29, in <module>
    mutex.acquire(self)
NameError: name 'self' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/pi/e-Paper/RaspberryPi_JetsonNano/python/examples/epd_2in7_test.py", line 32, in <module>
    mutex.release(self)
NameError: name 'self' is not defined
>>> 

I am sure it is something both obvious and stupid, but for the life of me I cannot figure out what’s going wrong.

Thanks!

1 Like

mutex=DI_Mutex(“SPI”)

To init the class, need (“SPI”) just like your sanity check code. Without the parens you are creating an alias for the Class definition. With the parens you are calling the init method of the class which instantiates the class object.

Don’t. Class method calls add the real self automagically. “Self” is only valid inside the Class.

1 Like

Modification:

#!/usr/bin/python3.7
# -*- coding:utf-8 -*-
import sys
import os
picdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'pic')
libdir = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'lib')
if os.path.exists(libdir):
    sys.path.append(libdir)

import logging
from waveshare_epd import epd2in7
import time
from PIL import Image,ImageDraw,ImageFont
import traceback

from di_mutex import DI_Mutex
mutex = DI_Mutex("SPI")

logging.basicConfig(level=logging.DEBUG)

Result:

>>> %Run epd_2in7_test.py
INFO:root:epd2in7 Demo
INFO:root:init and Clear
Traceback (most recent call last):
  File "/home/pi/e-Paper/RaspberryPi_JetsonNano/python/examples/epd_2in7_test.py", line 29, in <module>
    mutex.acquire(self)
NameError: name 'self' is not defined

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/pi/e-Paper/RaspberryPi_JetsonNano/python/examples/epd_2in7_test.py", line 32, in <module>
    mutex.release(self)
NameError: name 'self' is not defined
>>> 

I am still confused. . . I am looking at this and have no idea why this still happening.

Update:
I forgot to remove “self” from mutex.acquire|release()

It works now. Thanks!

Next steps:
Implement within the Dexter code too and see if these two things can coexist.

1 Like

Update:

They’re still not playing nice in the sandbox, so I have to find where else to wrap a mutex.

Are you sure it is the SPI?

From epdconfig.py (maybe not the one you are using?)

class RaspberryPi:
    # Pin definition
    RST_PIN  = 17
    DC_PIN   = 25
    CS_PIN   = 8
    BUSY_PIN = 24
    PWR_PIN  = 18
    MOSI_PIN = 10
    SCLK_PIN = 11

In my notes:

GoPiGo3 - Raspberry Pi Interface
 1:3v3: RPI+3v3
 2:5V: 5V supply
 3:GPIO2: SDA_3v3
 4:5V: 5V supply
 5:GPIO3: SCL_3v3
 8:GPIO14: RPI_TX   <<---- collision with "CS_PIN   = 8" ??
10:GPIO15: RPI_RX   <<--- collision with "MOSI_PIN = 10" ??
12:GPIO18: RPI_RESET
15:GPIO22: RPI_Shutdown (button)
16:GPIO23: RPI_RUNNING
18:GPIO24: RPI_SWDIO    <<----- Is this a collision with "PWR_PIN  = 18"
19:GPIO10: RPI_MOSI
21:GPIO9: RPI_MISO
22:GPIO25: RPI_SWDCLK
23:GPIO11:RPI_SCK
26:GPIO7: RPI_SS

1 Like

What is different from your prior posting?

1 Like

Viz.:

As noted there, Charlie and Charline behave differently, at least sometimes.

I don’t know why, but this is a very interesting data point that will need additional research.

1 Like

Maybe those are not pin # but GPIO “pin #”?
RST_PIN = GPIO17: pin 11: GoPiGo3 unused
DC_PIN = GPIO25: pin 22 : GoPiGo3 RPI_SWDCLK
CS_PIN = GPIO8: pin 24 : GoPiGo3 unused
BUSY_PIN = GPIO24: pin 18: GoPiGo3 RPI_SWDIO
MOSI_PIN = GPIO10: pin 19 : GoPiGo3 RPI_MOSI
PWR_PIN = GPIO18: pin 12 : GoPiGo3 RPI_RESET

1 Like

The big confusion comes from the fact that folks don’t use the same numbering scheme, and the fact that the Broadcom pin numbers bear no resemblance to the actual GPIO pin numbers.

1 Like