From 168a874fec9d2f9dcfe05367c5e0c37880918ab1 Mon Sep 17 00:00:00 2001 From: Kenny Date: Mon, 12 Sep 2022 19:38:38 -0700 Subject: [PATCH 1/4] Fix permissions error for /sys/class/gpio/export --- gpio/__init__.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/gpio/__init__.py b/gpio/__init__.py index c6f37f7..6d51c1c 100644 --- a/gpio/__init__.py +++ b/gpio/__init__.py @@ -16,7 +16,9 @@ GPIO_ROOT = '/sys/class/gpio' GPIO_EXPORT = os.path.join(GPIO_ROOT, 'export') GPIO_UNEXPORT = os.path.join(GPIO_ROOT, 'unexport') -FMODE = 'w+' # w+ overwrites and truncates existing files +FMODE_SYS_WO = 'w' # /sys/class/gpio/export is not readable, even by root +FMODE_SYS_RW = 'w+' # w+ overwrites and truncates existing files +FMODE_BIN_RW = 'wb+' # Using unbuffered binary IO is ~ 3x faster than text IN, OUT = 'in', 'out' LOW, HIGH = 0, 1 @@ -46,12 +48,12 @@ def __init__(self, pin, direction=None, initial=LOW, active_low=None): if not os.path.exists(self.root): with _export_lock: - with open(GPIO_EXPORT, FMODE) as f: + with open(GPIO_EXPORT, FMODE_SYS_WO) as f: f.write(str(self.pin)) f.flush() # Using unbuffered binary IO is ~ 3x faster than text - self.value = open(os.path.join(self.root, 'value'), 'wb+', buffering=0) + self.value = open(os.path.join(self.root, 'value'), FMODE_BIN_RW, buffering=0) # I hate manually calling .setup()! self.setup(direction, initial, active_low) @@ -100,7 +102,7 @@ def get_direction(self): Returns: str: "in" or "out" ''' - with open(os.path.join(self.root, 'direction'), FMODE) as f: + with open(os.path.join(self.root, 'direction'), FMODE_SYS_RW) as f: return f.read().strip() def set_direction(self, mode): @@ -112,7 +114,7 @@ def set_direction(self, mode): if mode not in (IN, OUT, LOW, HIGH): raise ValueError("Unsupported pin mode {}".format(mode)) - with open(os.path.join(self.root, 'direction'), FMODE) as f: + with open(os.path.join(self.root, 'direction'), FMODE_SYS_RW) as f: f.write(str(mode)) f.flush() @@ -125,7 +127,7 @@ def set_active_low(self, active_low): if not isinstance(active_low, bool): raise ValueError("active_low must be True or False") - with open(os.path.join(self.root, 'active_low'), FMODE) as f: + with open(os.path.join(self.root, 'active_low'), FMODE_SYS_RW) as f: f.write('1' if active_low else '0') f.flush() @@ -168,7 +170,7 @@ def cleanup(self): if os.path.exists(self.root): with _export_lock: - with open(GPIO_UNEXPORT, FMODE) as f: + with open(GPIO_UNEXPORT, FMODE_SYS_WO) as f: f.write(str(self.pin)) f.flush() From 20653fa59c2bb94bc69a2b51911b418f0a3f6195 Mon Sep 17 00:00:00 2001 From: Kenny Date: Mon, 12 Sep 2022 19:51:36 -0700 Subject: [PATCH 2/4] Fix permissions error for /sys/class/gpio/*/value --- gpio/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gpio/__init__.py b/gpio/__init__.py index 6d51c1c..d055e60 100644 --- a/gpio/__init__.py +++ b/gpio/__init__.py @@ -2,6 +2,7 @@ __version__ = '1.0.0' from threading import Lock +from time import sleep try: from collections.abc import Iterable except ImportError: @@ -51,6 +52,8 @@ def __init__(self, pin, direction=None, initial=LOW, active_low=None): with open(GPIO_EXPORT, FMODE_SYS_WO) as f: f.write(str(self.pin)) f.flush() + # give the kernel a moment to build out the requested sysfs tree + sleep(.1) # Using unbuffered binary IO is ~ 3x faster than text self.value = open(os.path.join(self.root, 'value'), FMODE_BIN_RW, buffering=0) From f9991c3470af4572620f0168a7726e3157afc4ce Mon Sep 17 00:00:00 2001 From: Kenny Date: Mon, 12 Sep 2022 22:54:33 -0700 Subject: [PATCH 3/4] wait for gpio*/value file group initialization --- gpio/__init__.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gpio/__init__.py b/gpio/__init__.py index d055e60..aa3bfb4 100644 --- a/gpio/__init__.py +++ b/gpio/__init__.py @@ -2,7 +2,7 @@ __version__ = '1.0.0' from threading import Lock -from time import sleep +from time import sleep, time try: from collections.abc import Iterable except ImportError: @@ -52,8 +52,12 @@ def __init__(self, pin, direction=None, initial=LOW, active_low=None): with open(GPIO_EXPORT, FMODE_SYS_WO) as f: f.write(str(self.pin)) f.flush() - # give the kernel a moment to build out the requested sysfs tree - sleep(.1) + gpio_gid = os.stat(GPIO_ROOT).st_gid + value_path = os.path.join(self.root, 'value') + start = time() + # give udev a moment to update the group of newly created files + while os.stat(value_path).st_gid != gpio_gid and time() - start < .5: + sleep(.01) # Using unbuffered binary IO is ~ 3x faster than text self.value = open(os.path.join(self.root, 'value'), FMODE_BIN_RW, buffering=0) From 84789acf5811fc5e772021f7804e16b6d168a0d9 Mon Sep 17 00:00:00 2001 From: Kenny Date: Mon, 12 Sep 2022 23:00:25 -0700 Subject: [PATCH 4/4] Improve FMODE naming: SYS->STR --- gpio/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/gpio/__init__.py b/gpio/__init__.py index aa3bfb4..10cf9d3 100644 --- a/gpio/__init__.py +++ b/gpio/__init__.py @@ -17,8 +17,8 @@ GPIO_ROOT = '/sys/class/gpio' GPIO_EXPORT = os.path.join(GPIO_ROOT, 'export') GPIO_UNEXPORT = os.path.join(GPIO_ROOT, 'unexport') -FMODE_SYS_WO = 'w' # /sys/class/gpio/export is not readable, even by root -FMODE_SYS_RW = 'w+' # w+ overwrites and truncates existing files +FMODE_STR_WO = 'w' # /sys/class/gpio/export is not readable, even by root +FMODE_STR_RW = 'w+' # w+ overwrites and truncates existing files FMODE_BIN_RW = 'wb+' # Using unbuffered binary IO is ~ 3x faster than text IN, OUT = 'in', 'out' LOW, HIGH = 0, 1 @@ -49,7 +49,7 @@ def __init__(self, pin, direction=None, initial=LOW, active_low=None): if not os.path.exists(self.root): with _export_lock: - with open(GPIO_EXPORT, FMODE_SYS_WO) as f: + with open(GPIO_EXPORT, FMODE_STR_WO) as f: f.write(str(self.pin)) f.flush() gpio_gid = os.stat(GPIO_ROOT).st_gid @@ -109,7 +109,7 @@ def get_direction(self): Returns: str: "in" or "out" ''' - with open(os.path.join(self.root, 'direction'), FMODE_SYS_RW) as f: + with open(os.path.join(self.root, 'direction'), FMODE_STR_RW) as f: return f.read().strip() def set_direction(self, mode): @@ -121,7 +121,7 @@ def set_direction(self, mode): if mode not in (IN, OUT, LOW, HIGH): raise ValueError("Unsupported pin mode {}".format(mode)) - with open(os.path.join(self.root, 'direction'), FMODE_SYS_RW) as f: + with open(os.path.join(self.root, 'direction'), FMODE_STR_RW) as f: f.write(str(mode)) f.flush() @@ -134,7 +134,7 @@ def set_active_low(self, active_low): if not isinstance(active_low, bool): raise ValueError("active_low must be True or False") - with open(os.path.join(self.root, 'active_low'), FMODE_SYS_RW) as f: + with open(os.path.join(self.root, 'active_low'), FMODE_STR_RW) as f: f.write('1' if active_low else '0') f.flush() @@ -177,7 +177,7 @@ def cleanup(self): if os.path.exists(self.root): with _export_lock: - with open(GPIO_UNEXPORT, FMODE_SYS_WO) as f: + with open(GPIO_UNEXPORT, FMODE_STR_WO) as f: f.write(str(self.pin)) f.flush()