Skip to content
Advertisement

Python try_decorator to retry i2c connections

I am communicating with some hardware over an I2C bus.

Every so often, there is an exception as the bus is busy so I am currently handling this with a loop that retries, but there’s a lot of code repetition, I want to tidy it up.

So, I tried to write a decorator function to pass various functions to

As an example, this command initialises the bus: i2c = busio.I2C(board.SCL, board.SDA)

This is the decorator I have written:

def try_decorator(func, name):
    MAX_TRIES = 5
    i = 1
    n = name
    while True:
        try:
            func()
        except Exception as e:
            print(f'retry {i} for {n}')
            i += 1
            if i == MAX_TRIES:
                print(f'max retries of {MAX_TRIES} reached for {n}, with error: {e}')
                break
            continue
        break

And then I try to pass it the line mentioned above as a partial: i2c = try_decorator(partial(busio.I2C, board.SCL, board.SDA), 'i2c init') but I get an exception when I then try and pass the i2c to the next function: 'NoneType' object has no attribute 'try_lock' which I think means the partial isn’t actually initialising the bus properly. I also tried as a lambda and as a function but get similar results.

What can I do to fix this and keep my code as straightforward as possible?

Also, instead of passing a string as name as an arg (to track exceptions), I tried using func.name in the decorator when I tried as a function instead of a partial but it gives an error that seems as if it is trying to get the name of the I2C object.

Edit: I have updated my code as per BigBro’s answer to below but I still get an error when passing the i2c object to my next function (I don’t get this error if I run without the decorator).

def retry(f):
    MAX_TRIES = 5
    def wrapped(*args, **kwargs):
        for i in range(MAX_TRIES):
            try:
                res = f(*args, **kwargs)
                return res
            except IOError as e:
                print(f'retry {i} for {f.__name__}, with error: {e}')
        print(f'max retries of {MAX_TRIES} reached for {f.__name__}, with error: {e}')
    return wrapped

i2c = retry(busio.I2C(board.SCL, board.SDA))

drv = adafruit_drv2605.DRV2605(i2c)

'function' object has no attribute 'try_lock'

Advertisement

Answer

The simple answer is that you forgot to return the result of the function in try_decorator (and therefore return None). Replace func() with res = func() and then return res at the end.

However your function is not really a decorator, as it doesn’t actually return a function, and can’t be used as @try_decorator.

I would suggest something like:

import random

MAX_RETRIES = 10
def retry_decorator(f):
    def wrapped(*args, **kwargs):  # Allows arg to be passed to the function instead of using partial
        for i in range(MAX_RETRIES):
            try:
                res = f(*args, **kwargs)
                return res
            except Exception as e:
                print(f'Function {f.__name__} failed {i}/{MAX_RETRIES}: {e}')  # f.__name__ is what you're looking for
        print(f'Function {f.__name__} didn't work after {MAX_RETRIES} retries: {e}')
    return wrapped


@retry_decorator
def fail_randomly():
    i = random.randint(0, 10)
    if i < 7:
        raise RuntimeError('Random error')
    print('Sucess!')

fail_randomly()

output

Function fail_randomly failed 0/10: Random error
Function fail_randomly failed 1/10: Random error
Function fail_randomly failed 2/10: Random error
Function fail_randomly failed 3/10: Random error
Function fail_randomly failed 4/10: Random error
Sucess!

One last thing, I suggest being more specific in what exception you catch otherwise things might become hard to debug ;)

User contributions licensed under: CC BY-SA
9 People found this is helpful
Advertisement