Skip to content

Commit

Permalink
Merge pull request #43 from tiagocoutinho/gpio
Browse files Browse the repository at this point in the history
Add support for GPIO
  • Loading branch information
tiagocoutinho authored Sep 17, 2024
2 parents 44fd928 + ef76380 commit 35d8562
Show file tree
Hide file tree
Showing 14 changed files with 1,127 additions and 20 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

Human friendly interface to linux subsystems using python.

Provides python access to several linux subsystems like V4L2, input and MIDI.
Provides python access to several linux subsystems like V4L2, GPIO, Led, thermal,
input and MIDI.

There is experimental, undocumented, incomplete and unstable access to USB.

Expand Down
3 changes: 3 additions & 0 deletions docs/api/gpio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ⚡ GPIO API

::: linuxpy.gpio.device
11 changes: 10 additions & 1 deletion docs/develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ $ sudo addgroup video
$ sudo addgroup led
$ sudo adduser $USER input
$ sudo adduser $USER video
$ sudo adduser $USER led
```

(reboot if necessary for those changes to take effect)
Expand All @@ -56,10 +57,18 @@ Create a new rules file (ex: `/etc/udev/rules.d/80-device.rules`):
```
KERNEL=="event[0-9]*", SUBSYSTEM=="input", GROUP="input", MODE:="0660"
KERNEL=="uinput", SUBSYSTEM=="misc", GROUP="input", MODE:="0660"
SUBSYSTEM=="video4linux", MODE:="0666"
SUBSYSTEM=="video4linux", GROUP="video", MODE:="0660"
KERNEL=="uleds", GROUP="input", MODE:="0660"
SUBSYSTEM=="leds", ACTION=="add", RUN+="/bin/chmod -R g=u,o=u /sys%p"
SUBSYSTEM=="leds", ACTION=="change", ENV{TRIGGER}!="none", RUN+="/bin/chmod -R g=u,o=u /sys%p"
SUBSYSTEM=="gpio", GROUP="input", MODE:="0660"
```

Reload the rules:

```console
$ sudo udevadm control --reload-rules
$ sudo udevadm trigger
```

Finally, make sure all kernel modules are installed:
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ hide:

Human friendly interface to linux subsystems using python.

Provides python access to several linux subsystems like V4L2, input and MIDI.
Provides python access to several linux subsystems like V4L2, input, GPIO and MIDI.

There is experimental, undocumented, incomplete and unstable access to USB.

Need fine control over Webcams, MIDI devices, thermal sensors and cooling
Need fine control over Webcams, GPIO, MIDI devices, thermal sensors and cooling
devices, joysticks, gamepads, keyboards, mice or even the keyboard light on
your laptop?

Expand Down
14 changes: 14 additions & 0 deletions docs/user_guide/gpio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# ⚡ GPIO

Human friendly interface to linux GPIO handling.

Without further ado:

<div class="termy" data-ty-macos>
<span data-ty="input" data-ty-prompt="$">python</span>
<span data-ty="input" data-ty-prompt=">>>">from linuxpy.gpio import find</span>
<span data-ty="input" data-ty-prompt=">>>">with find() as gpio:</span>
<span data-ty="input" data-ty-prompt="..."> with gpio[1, 2, 5:8] as lines:</span>
<span data-ty="input" data-ty-prompt=">>>"> print(lines[:])</span>
<span data-ty>{1: 0, 2: 1, 5: 0, 6: 1, 7:0}</span>
</div>
47 changes: 32 additions & 15 deletions linuxpy/codegen/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ class {self.name}(enum.{self.klass}):


class CStruct:
def __init__(self, node, node_members, pack=False):
def __init__(self, node, name, node_members, pack=False):
self.node = node
self.node_members = node_members
self.pack = pack
self.name = node.get("name")
self.name = name
self.parent = None
self.fields = []
self.children = {}
Expand Down Expand Up @@ -215,7 +215,7 @@ def find_xml_base_type(etree, context, type_id):
type_id = node.get("type")


def get_structs(header_filename, xml_filename):
def get_structs(header_filename, xml_filename, decode_name):
etree = xml.etree.ElementTree.parse(xml_filename)
header_tag = etree.find(f"File[@name='{header_filename}']")
structs = {}
Expand All @@ -234,7 +234,10 @@ def get_structs(header_filename, xml_filename):
fields = (etree.find(f"*[@id='{member_id}']") for member_id in member_ids)
fields = [field for field in fields if field.tag not in {"Union", "Struct", "Unimplemented"}]
pack = int(node.get("align")) == 8
struct = CStruct(node, fields, pack)
name = node.get("name")
if name:
name = decode_name(name)
struct = CStruct(node, name, fields, pack)
structs[struct.id] = struct
for struct in structs.values():
if struct.context_id != "_1":
Expand Down Expand Up @@ -278,7 +281,7 @@ def cname_to_pyname(
return "".join(map(str.capitalize, name.split(splitby)))


def get_enums(header_filename, xml_filename, enums):
def get_enums(header_filename, xml_filename, enums, decode_name):
etree = xml.etree.ElementTree.parse(xml_filename)
header_tag = etree.find(f"File[@name='{header_filename}']")
structs = {}
Expand All @@ -288,7 +291,9 @@ def get_enums(header_filename, xml_filename, enums):
nodes = etree.findall(f"Enumeration[@file='{header_id}']")
for node in nodes:
cname = node.get("name")
py_name = cname_to_pyname(cname)
if not cname:
continue
py_name = cname_to_pyname(decode_name(cname))
prefix = cname.upper() + "_"
raw_names = [child.get("name") for child in node]
common_prefix = os.path.commonprefix(raw_names)
Expand All @@ -309,16 +314,28 @@ def get_enums(header_filename, xml_filename, enums):


def code_format(text, filename):
cmd = ["ruff", "check", "--fix", "--stdin-filename", str(filename)]
result = subprocess.run(cmd, capture_output=True, check=True, text=True, input=text)
fixed_text = result.stdout
try:
cmd = ["ruff", "check", "--fix", "--stdin-filename", str(filename)]
result = subprocess.run(cmd, capture_output=True, check=True, text=True, input=text)
fixed_text = result.stdout
except Exception as error:
print(repr(error))
fixed_text = text

try:
cmd = ["ruff", "format", "--stdin-filename", str(filename)]
result = subprocess.run(cmd, capture_output=True, check=True, text=True, input=fixed_text)
fixed_text = result.stdout
except Exception as error:
print(repr(error))
return fixed_text


cmd = ["ruff", "format", "--stdin-filename", str(filename)]
result = subprocess.run(cmd, capture_output=True, check=True, text=True, input=fixed_text)
return result.stdout
def nullf(x):
return x


def run(name, headers, template, macro_enums, output=None):
def run(name, headers, template, macro_enums, output=None, decode_struct_name=nullf, decode_enum_name=nullf):
cache = {}
temp_dir = tempfile.mkdtemp()
logging.info("Starting %s...", name)
Expand All @@ -330,10 +347,10 @@ def run(name, headers, template, macro_enums, output=None):
xml_filename = os.path.join(temp_dir, base_header + ".xml")
cmd = f"castxml --castxml-output=1.0.0 -o {xml_filename} {header}"
assert os.system(cmd) == 0
new_structs = get_structs(header, xml_filename)
new_structs = get_structs(header, xml_filename, decode_name=decode_struct_name)
structs += list(new_structs.values())

get_enums(header, xml_filename, macro_enums)
get_enums(header, xml_filename, macro_enums, decode_name=decode_enum_name)

structs_definition = "\n\n".join(struct.class_text for struct in structs if struct.parent is None)
structs_fields = "\n".join(struct.fields_text for struct in structs if struct.parent is None)
Expand Down
2 changes: 2 additions & 0 deletions linuxpy/codegen/cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging

from .gpio import main as gpio_main
from .input import main as input_run
from .magic import main as magic_run
from .usbfs import main as usbfs_run
Expand All @@ -10,6 +11,7 @@
def main():
logging.basicConfig(level="INFO")

gpio_main()
magic_run()
input_run()
video_main()
Expand Down
81 changes: 81 additions & 0 deletions linuxpy/codegen/gpio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#
# This file is part of the linuxpy project
#
# Copyright (c) 2024 Tiago Coutinho
# Distributed under the GPLv3 license. See LICENSE for more info.

import pathlib

from linuxpy.codegen.base import CEnum, run

HEADERS = [
"/usr/include/linux/gpio.h",
]


TEMPLATE = """\
#
# This file is part of the linuxpy project
#
# Copyright (c) 2024 Tiago Coutinho
# Distributed under the GPLv3 license. See LICENSE for more info.
# This file has been generated by {name}
# Date: {date}
# System: {system}
# Release: {release}
# Version: {version}
import enum
from linuxpy.ioctl import IOR as _IOR, IOW as _IOW, IOWR as _IOWR
from linuxpy.ctypes import u8, u16, u32, cuint, cint, cchar, culonglong
from linuxpy.ctypes import Struct, Union, POINTER, cvoidp
class GpioLineEvent(enum.IntEnum):
REQUESTED = 1
RELEASED = 2
CONFIG = 3
{enums_body}
{structs_body}
{iocs_body}"""


class IOC(CEnum):
def __init__(self):
def filter(name, value):
return name.endswith("_IOCTL")

super().__init__("IOC", ["GPIO_GET_", "GPIO_V2_"], filter=filter)

def add_item(self, name, value):
name = name.removesuffix("_IOCTL")
return super().add_item(name, value)


# macros from #define statements
MACRO_ENUMS = [
IOC(),
]


this_dir = pathlib.Path(__file__).parent


def decode_name(name: str) -> str:
return name.removeprefix("gpio_v2_").removeprefix("gpio_")


def main(output=this_dir.parent / "gpio" / "raw.py"):
run(__name__, HEADERS, TEMPLATE, MACRO_ENUMS, output=output, decode_enum_name=decode_name)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions linuxpy/gpio/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#
# This file is part of the linuxpy project
#
# Copyright (c) 2024 Tiago Coutinho
# Distributed under the GPLv3 license. See LICENSE for more info.
Loading

0 comments on commit 35d8562

Please sign in to comment.