vsergeev / python-periphery

A pure Python 2/3 library for peripheral I/O (GPIO, LED, PWM, SPI, I2C, MMIO, Serial) in Linux.
MIT License
519 stars 139 forks source link

Calling method directly doesn't alter character device #38

Closed LongLiveCHIEF closed 4 years ago

LongLiveCHIEF commented 4 years ago

I've been doing some experimenting with GPIO access via docker so I can prep docs compatible with arm64 versions of the official octoprint/octoprint docker image.

I've got a small test script that I'm using that will allow me to test permissions and devices for the docker container, however, I've run into a problem of sorts.

If I write a method that utilizes GPIO, and then immediately execute it, the python process thinks the character device state has been altered, but it's not actually changing.

However, if my method returns the object and then my script calls that, it works.

Here's the repo I'm working with, that has a tiny example: https://github.com/LongLiveCHIEF/docker-pi-gpio/tree/ec2fb5680078d081feaf5994fa7a42fc5113fe1e

i have a method called LED, which does this:

Calling approach 1 (this works)

def LED(chip, pin):
    led = GPIO(chip, pin, "out")
    return led

# then call it from tests/on.test.py
led = dockergpio.LED("/dev/gpiochip0", 17)
led.write(True)

Calling approach 2 (this does not work)

def ledOn(chip, pin):
    led = GPIO(chip, pin, "out")
    led.write(True)

# then call it from tests/gpio.test.py
dockergpio.ledOn("/dev/gpiochip0", 17)

No errors are thrown, it's just that the led lights up using the first approach, but does not light up using the 2nd. I'm not changing anything with the hardware setup or environment in between executing the two tests.

What's interesting, is that if I add print(led.read()) to the body of the second approach, both before and after writing the state, the first read prints False, but the second read returns True.

Why would the first approach work, but not the second? The only thing I can think of is that the second approach closes the file descriptor immediately after using it.

I haven't yet hooked up the scope for the second approach to do a capture trigger on a rising edge to see if the voltage actually changed for a small time.

I'm new to python, so forgive me if this is something with scope/instantiation that would be obvious to a seasoned python programmer.

vsergeev commented 4 years ago

In the second case, the GPIO object is destroyed as it exits the scope of ledOn(), which leads to the GPIO being closed (see https://github.com/vsergeev/python-periphery/blob/v2.0.1/periphery/gpio.py#L35). In the kernel, when a GPIO line handle file descriptor is closed, the GPIO is freed and ultimately the Raspberry Pi pinctrl driver handles this by setting the GPIO to an input.

In short, the character device GPIOs don't retain their state after the file descriptor is closed, so you need to keep the GPIO object around to keep that file descriptor opened and with it, its output state.

LongLiveCHIEF commented 4 years ago

I knew that the line's fd had to be held open by the process to maintain state, I just didn't realize that calling it the second way didn't hold the file descriptor open. Your description about the GPIO object being in scope makes total sense though, thank you!

vsergeev commented 4 years ago

Sounds good, no problem :+1: