diff --git a/README.md b/README.md index b47a31e..f6cfce2 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,49 @@ # Python Spidev -This project contains a python module for interfacing with SPI devices from -user space via the spidev linux kernel driver. +Python interface for the spidev Linux kernel driver for connecting to SPI +devices. All code is MIT licensed unless explicitly stated otherwise. ## Usage ```python -import spidev -spi = spidev.SpiDev() -spi.open_path(spidev_devicefile_path) -to_send = [0x01, 0x02, 0x03] -spi.xfer(to_send) +from spidev import SpiDev + +# bus=0, device=1. Same as path="/dev/spidev0.1" +with SpiDev(0, 1) as spi: + spi.write([0x01, 0x02, 0x03]) + +# or directly from a path and manually opening/closing the file: + +spi = SpiDev(path="/dev/myspidev") +spi.open() + +print(spi.read(64)) + +spi.close() ``` + ## Settings ```python import spidev -spi = spidev.SpiDev() -spi.open_path("/dev/spidev0.0") -# Settings (for example) -spi.max_speed_hz = 5000 -spi.mode = 0b01 +# setting options during initialization +spi = spidev.SpiDev(0, 0, mode=0b01, max_speed_hz=5000) -... +with spi: + spi.read() + +# changing options on the go +spi.mode = 0b11 + +with spi: + spi.read() ``` +Full list of supported options: + * `bits_per_word` * `cshigh` * `loop` - Set the "SPI_LOOP" flag to enable loopback mode @@ -48,55 +64,17 @@ spi.mode = 0b01 ## Methods - open_path(filesystem_path) - -Connects to the specified SPI device special file, following symbolic links if -appropriate (see note on deterministic SPI bus numbering in the Linux kernel -below for why this can be advantageous in some configurations). - - open(bus, device) - -Equivalent to calling `open_path("/dev/spidev.")`. n.b. **Either** -`open_path` or `open` should be used. - - readbytes(n) - -Read n bytes from SPI device. +The `SpiDev` class is similar to Python's [I/O Base +Classes](https://docs.python.org/3/library/io.html#i-o-base-classes), and so +are its methods. The most important ones are: - writebytes(list of values) - -Writes a list of values to SPI device. - - writebytes2(list of values) - -Similar to `writebytes` but accepts arbitrary large lists. If list size exceeds -buffer size (which is read from `/sys/module/spidev/parameters/bufsiz`), data -will be split into smaller chunks and sent in multiple operations. - -Also, `writebytes2` understands [buffer -protocol](https://docs.python.org/3/c-api/buffer.html) so it can accept numpy -byte arrays for example without need to convert them with `tolist()` first. -This offers much better performance where you need to transfer frames to -SPI-connected displays for instance. - - xfer(list of values[, speed_hz, delay_usec, bits_per_word]) - -Performs an SPI transaction. Chip-select should be released and reactivated -between blocks. Delay specifies the delay in usec between blocks. - - xfer2(list of values[, speed_hz, delay_usec, bits_per_word]) - -Performs an SPI transaction. Chip-select should be held active between blocks. - - xfer3(list of values[, speed_hz, delay_usec, bits_per_word]) - -Similar to `xfer2` but accepts arbitrary large lists. If list size exceeds -buffer size (which is read from `/sys/module/spidev/parameters/bufsiz`), data -will be split into smaller chunks and sent in multiple operations. - - close() - -Disconnects from the SPI device. +* `close()`: disconnects from the SPI device. +* `closed`: True if the SPI device is closed. +* `fileno()`: Underlying file descriptor. `ValueError` if closed. +* `readable()`: True if not closed. +* `read(n)`: Read `n` bytes from the SPI device. +* `writeable()`: True if not closed. +* `write(b)`: Write bytes to SPI device. ## The Linux kernel and SPI bus numbering and the role of udev @@ -146,7 +124,7 @@ bus number and SPI chip select number to user space in the form of a POSIX A user space program (usually 'udev') listens for kernel device creation events, and creates a file system "device node" for user space software to interact with. By convention, for spidev, the device nodes are named -/dev/spidev. is (where the *bus* is the Linux kernel's internal +`/dev/spidev.` is (where the *bus* is the Linux kernel's internal SPI bus number (see below) and the *device* number corresponds to the SPI controller "chip select" output pin that is connected to the SPI *device* 'chip select' input pin. @@ -172,22 +150,22 @@ symbolic links to allow identification of block storage devices e.g. see the output of `ls -alR /dev/disk`. `99-local-spi-example-udev.rules` included with py-spidev includes example udev -rules for creating stable symlink device paths (for use with `open_path`). +rules for creating stable symlink device paths (for use with SpiDev's `path` +argument). -e.g. the following Python code could be used to communicate with an SPI device +E.g. the following Python code could be used to communicate with an SPI device attached to chip-select line 0 of an individual FTDI FT232H USB to SPI adapter which has the USB serial number "1A8FG636": -``` -#!/usr/bin/env python3 +```python import spidev -spi = spidev.SpiDev() -spi.open_path("/dev/spi/by-path/usb-sernum-1A8FG636-cs-0") -# TODO: Useful stuff here -spi.close() +spi = spidev.SpiDev(path="/dev/spi/by-path/usb-sernum-1A8FG636-cs-0") +with spi: + # TODO: Useful stuff here + ... ``` In the more general case, the example udev file should be modified as diff --git a/pyproject.toml b/pyproject.toml index 638dd9c..3a8d531 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,18 @@ [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" + +[tool.mypy] +strict = true + +[tool.ruff.lint] +select = [ + "E", # pycodestyle + "F", # Pyflakes + "A", # flake8-builtins + "UP", # pyupgrade + "FA", # flake8-future-annotations + "B", # flake8-bugbear + "SIM", # flake8-simplify + "I", # isort +] diff --git a/spidev/__init__.py b/spidev/__init__.py new file mode 100644 index 0000000..e4fa517 --- /dev/null +++ b/spidev/__init__.py @@ -0,0 +1,7 @@ +"""TODO.""" + +from ._spi import SpiDev + +__version__ = "4.0.0" + +__all__ = ("SpiDev",) diff --git a/spidev/_spi.py b/spidev/_spi.py new file mode 100644 index 0000000..8864154 --- /dev/null +++ b/spidev/_spi.py @@ -0,0 +1,441 @@ +from __future__ import annotations + +from contextlib import suppress +from typing import TYPE_CHECKING, Any, Callable, Self, TypeVar, overload +from warnings import deprecated + +from . import _spimodule + +if TYPE_CHECKING: + from collections.abc import Buffer, Sequence + from os import PathLike + from types import TracebackType + + StrPath = str | PathLike[str] + T = TypeVar("T") + + +def try_convert(val: object, typename: Callable[[Any], T], varname: str = "Value") -> T: + """Try to convert `val` to `typename`. Raise a TypeError if conversion fails.""" + try: + return typename(val) + except (TypeError, ValueError) as err: + msg = f"{varname} must have type {typename}, but is {type(val)}" + raise TypeError(msg) from err + + +class SpiDev: + """Connect to a SPI device. + + Examples: + >>> SpiDev(0, 1) # connect to /dev/spidev0.1 + + >>> SpiDev(path='/dev/myspi') # connect to /dev/myspi + + """ + + def __init__( # noqa: PLR0913 + self, + bus: int | None = None, + device: int | None = None, + *, + path: StrPath | None = None, + mode: int | None = None, + bits_per_word: int | None = None, + max_speed_hz: int | None = None, + read0: bool | None = None, + ) -> None: + self._cmod = _spimodule.SpiDev(bus, device) + + self.bus = bus + self.device = device + if path and (bus or device): + raise ValueError( + "both path and bus/device number of SPI device are provided", + ) + self.path = path + + if mode is not None: + self.mode = mode + if bits_per_word is not None: + self.bits_per_word = bits_per_word + if max_speed_hz is not None: + self.max_speed_hz = max_speed_hz + if read0 is not None: + self.read0 = read0 + + # TODO: open() here? It's what the original implementation did + if (bus and device) or path: + self.open() + + def _resolve_path(self) -> str: + """Construct path from bus and device numbers or from given path. + + Returns: + str: Resolved path to the SPI device. + + Raises: + ValueError: if bus+device or path not set + + """ + if self.bus is not None and self.device is not None: + return f"/dev/spidev{self.bus:d}.{self.device:d}" + if self.path is not None: + return str(self.path) + raise ValueError("bus/device or path not set") + + @property + def closed(self) -> bool: + """Return True if the connection is not opened.""" + try: + self.fileno() + return True + except ValueError: + return False + + @property + def mode(self) -> int: + """SPI mode. + + A two bit pattern of clock polarity and phase [CPOL|CPHA], + min: 0b00 = 0, max: 0b11 = 3 + """ + return self._cmod.mode + + @mode.setter + def mode(self, value: int, /) -> None: + v = try_convert(value, int, "mode") + + # two bits max, thus value can only be 0-3 + if v not in range(4): + msg = f"mode {v} has more than two bits" + raise ValueError(msg) + + self._cmod.mode = v + + @property + def bits_per_word(self) -> int: + """Bits per word used in the xfer methods.""" + return self._cmod.bits_per_word + + @bits_per_word.setter + def bits_per_word(self, value: int, /) -> None: + v = try_convert(value, int, "bits_per_word") + + if v not in (8, 16, 32): + msg = f"bits_per_word must be 8, 16 or 32, is {v}" + raise ValueError(msg) + + self._cmod.bits_per_word = v + + @property + def max_speed_hz(self) -> int: + """Max speed (in Hertz).""" + return self._cmod.max_speed_hz + + @max_speed_hz.setter + def max_speed_hz(self, value: int, /) -> None: + self._cmod.max_speed_hz = try_convert(value, int, "max_speed_hz") + + @property + def read0(self) -> bool: + """Read 0 bytes after transfer to lower CS if cshigh is set.""" + return self._cmod.read0 + + @read0.setter + def read0(self, value: bool, /) -> None: + self._cmod.read0 = try_convert(value, bool, "read0") + + @property + def cshigh(self) -> bool: + return self._cmod.cshigh + + @cshigh.setter + def cshigh(self, value: bool, /) -> None: + self._cmod.cshigh = try_convert(value, bool, "cshigh") + + @property + def threewire(self) -> bool: + """SI/SO signals shared.""" + return self._cmod.threewire + + @threewire.setter + def threewire(self, value: bool, /) -> None: + self._cmod.threewire = try_convert(value, bool, "threewire") + + @property + def lsbfirst(self) -> bool: + return self._cmod.lsbfirst + + @lsbfirst.setter + def lsbfirst(self, value: bool, /) -> None: + self._cmod.lsbfirst = try_convert(value, bool, "lsbfirst") + + @property + def loop(self) -> bool: + """Sets the SPI_LOOP flag to enable loopback mode.""" + return self._cmod.loop + + @loop.setter + def loop(self, value: bool, /) -> None: + self._cmod.loop = try_convert(value, bool, "loop") + + @property + def no_cs(self) -> bool: + """Sets the SPI_NO_CS flag to disable use of the chip select.""" + return self._cmod.no_cs + + @no_cs.setter + def no_cs(self, value: bool, /) -> None: + self._cmod.no_cs = try_convert(value, bool, "no_cs") + + def close(self) -> None: + """Close the object from the interface.""" + self._cmod.close() + + def fileno(self) -> int: + """Return the file descriptor if it exists. + + Returns: + int: File descriptor number. + + Raises: + ValueError: if the connection is not open. + + """ + fd = self._cmod.fileno() + if fd < 0: + raise ValueError("I/O operation on closed file") + return fd + + @overload + def open(self) -> None: ... + + @overload + @deprecated("use SpiDev(bus, device).open()") + def open(self, bus: int, device: int) -> None: ... + + def open(self, bus: int | None = None, device: int | None = None) -> None: + """Connect to the SPI device special file. + + If bus and device are provided it opens "/dev/spidev". If + path is provided it opens the SPI device at given path. Symbolic links + are followed. + + Args: + bus: Bus number. + device: Device number. + + Raises: + ValueError: If bus/device or path is not provided. + + """ + if bus: + self.bus = bus + if device: + self.device = device + + # TODO: If Cmod's open() would return the fd we can lift a lot of logic + # to here. + self._cmod.open_path(self._resolve_path()) + + @deprecated("use SpiDev(path='...').open()") + def open_path(self, path: StrPath | None = None) -> None: + """Open SPI device at given path. + + Args: + path: Path to SPI device. + + Raises: + IOError + + """ + if path: + self.path = path + self._cmod.open_path(self._resolve_path()) + + def read(self, size: int = -1, /) -> list[int]: + """Read and return up to _size_ bytes. + + If size is omitted or negative, 1 byte is read. + + Returns: + list[int]: _size_ number of bytes. + + Raises: + OSError: If device is closed. + + """ + if not self.readable(): + raise OSError("device closed") + # TODO: IOBase.read(-1) means "read as much as + # possible". Can we mimic this behavior? + if size < 1: + size = 1 + return self._cmod.readbytes(size) + + def readable(self) -> bool: + """Return True if the SPI device is currently open.""" + return not self.closed + + @deprecated("use SpiDev().read()") + def readbytes(self, length: int) -> list[int]: + return self._cmod.readbytes(length) + + def writeable(self) -> bool: + """Return True if the SPI connection is currently open.""" + return not self.closed + + def write(self, b: Sequence[int] | Buffer, /) -> None: + """Write bytes to SPI device. + + Accepts arbitrary large lists. If list size exceeds buffer size (read + from /sys/module/spidev/parameters/bufsiz), data will be + split into smaller chunks and sent in multiple operations. + + Args: + b: Sequence of bytes or Buffer to write. + + Raises: + OSError: If device is closed. + + """ + if not self.writeable(): + raise OSError("device closed") + # TODO: return number of bytes written like RawIOBase does + self._cmod.writebytes2(b) + + @deprecated("use SpiDev().write()") + def writebytes(self, values: Sequence[int]) -> None: + self._cmod.writebytes(values) + + @deprecated("use SpiDev().write()") + def writebytes2(self, values: Sequence[int] | Buffer) -> None: + self._cmod.writebytes2(values) + + def xfer( + self, + values: Sequence[int], + speed_hz: int | None = None, + delay_usecs: int | None = None, + bits_per_word: int | None = None, + ) -> list[int]: + """Performs an SPI transaction. + + NOTE: Chip-select should be released and reactivated between blocks. + + Args: + values: Bytes to write. + speed_hz: Speed to use. + delay_usecs: Delay in microseconds between blocks. + bits_per_word: Bits per word. + + Returns: + TODO + + """ + return self._cmod.xfer(values, speed_hz, delay_usecs, bits_per_word) + + def xfer2( + self, + values: Sequence[int], + speed_hz: int | None = None, + delay_usecs: int | None = None, + bits_per_word: int | None = None, + ) -> list[int]: + """Performs an SPI transaction. + + NOTE: Chip-select should be held active between blocks. + + Args: + values: Bytes to write. + speed_hz: Speed to use. + delay_usecs: Delay in microseconds between blocks. + bits_per_word: Bits per word. + + Returns: + TODO + + """ + return self._cmod.xfer2(values, speed_hz, delay_usecs, bits_per_word) + + def xfer3( + self, + values: Sequence[int], + speed_hz: int | None = None, + delay_usecs: int | None = None, + bits_per_word: int | None = None, + ) -> tuple[int, ...]: + """Performs an SPI transaction. + + Accepts arbitrary large lists. If list size exceeds buffer size (read + from /sys/module/spidev/parameters/bufsiz), data will be split + into smaller chunks and sent in multiple operations. + + Args: + values: Bytes to write. + speed_hz: Speed to use. + delay_usecs: Delay in microseconds between blocks. + bits_per_word: Bits per word. + + Returns: + TODO + + """ + return self._cmod.xfer3(values, speed_hz, delay_usecs, bits_per_word) + + def __enter__(self) -> Self: + # TODO: make open() idempotent and raise IOError if opening fails + with suppress(ValueError): + self.open() + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: TracebackType | None, + ) -> None: + self._cmod.close() + + def __str__(self) -> str: + if self.bus is not None and self.device is not None: + # SpiDev(0, 1) + return f"{self.__class__.__name__}({self.bus}, {self.device})" + if self.path is not None: + # SpiDev('/dev/myspi') + return f"{self.__class__.__name__}({self.path!r})" + + # SpiDev() + return f"{self.__class__.__name__}()" + + def __repr__(self) -> str: + # e.g. SpiDev(bus=0, device=1, bits_per_word=8) + args = ", ".join( + f"{a}={getattr(self, a)!r}" + for a in ( + "bus", + "device", + "path", + "mode", + "bits_per_word", + "max_speed_hz", + "read0", + ) + if getattr(self, a) is not None + ) + return f"{self.__class__.__name__}({args})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, type(self)): + return False + + try: + return self._resolve_path() == other._resolve_path() + except ValueError: + # return False if one of the instances is uninitiated + return False + + def __del__(self) -> None: + del self._cmod diff --git a/spidev_module.c b/spidev/_spimodule.c similarity index 93% rename from spidev_module.c rename to spidev/_spimodule.c index 6793266..29b632b 100644 --- a/spidev_module.c +++ b/spidev/_spimodule.c @@ -1,5 +1,5 @@ /* - * spidev_module.c - Python bindings for Linux SPI access through spidev + * _cspi.c - Python bindings for Linux SPI access through spidev * * MIT License * @@ -1012,28 +1012,8 @@ SpiDev_set_mode(SpiDevObject *self, PyObject *val, void *closure) "Cannot delete attribute"); return -1; } -#if PY_MAJOR_VERSION < 3 - if (PyInt_Check(val)) { - mode = PyInt_AS_LONG(val); - } else -#endif - { - if (PyLong_Check(val)) { - mode = PyLong_AS_LONG(val); - } else { - PyErr_SetString(PyExc_TypeError, - "The mode attribute must be an integer"); - return -1; - } - } - - if ( mode > 3 ) { - PyErr_SetString(PyExc_TypeError, - "The mode attribute must be an integer" - "between 0 and 3."); - return -1; - } + mode = PyLong_AS_LONG(val); // clean and set CPHA and CPOL bits tmp = ( self->mode & ~(SPI_CPHA | SPI_CPOL) ) | mode ; @@ -1202,33 +1182,13 @@ SpiDev_get_bits_per_word(SpiDevObject *self, void *closure) static int SpiDev_set_bits_per_word(SpiDevObject *self, PyObject *val, void *closure) { - uint8_t bits; - if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } -#if PY_MAJOR_VERSION < 3 - if (PyInt_Check(val)) { - bits = PyInt_AS_LONG(val); - } else -#endif - { - if (PyLong_Check(val)) { - bits = PyLong_AS_LONG(val); - } else { - PyErr_SetString(PyExc_TypeError, - "The bits_per_word attribute must be an integer"); - return -1; - } - } - if (bits < 8 || bits > 32) { - PyErr_SetString(PyExc_TypeError, - "invalid bits_per_word (8 to 32)"); - return -1; - } + uint8_t bits = PyLong_AS_LONG(val); if (self->bits_per_word != bits) { if (ioctl(self->fd, SPI_IOC_WR_BITS_PER_WORD, &bits) == -1) { @@ -1251,27 +1211,14 @@ SpiDev_get_max_speed_hz(SpiDevObject *self, void *closure) static int SpiDev_set_max_speed_hz(SpiDevObject *self, PyObject *val, void *closure) { - uint32_t max_speed_hz; if (val == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete attribute"); return -1; } -#if PY_MAJOR_VERSION < 3 - if (PyInt_Check(val)) { - max_speed_hz = PyInt_AS_LONG(val); - } else -#endif - { - if (PyLong_Check(val)) { - max_speed_hz = PyLong_AS_LONG(val); - } else { - PyErr_SetString(PyExc_TypeError, - "The max_speed_hz attribute must be an integer"); - return -1; - } - } + + uint32_t max_speed_hz = PyLong_AS_LONG(val); if (self->max_speed_hz != max_speed_hz) { if (ioctl(self->fd, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed_hz) == -1) { @@ -1310,6 +1257,27 @@ SpiDev_set_read0(SpiDevObject *self, PyObject *val, void *closure) return 0; } +static PyObject * +SpiDev_get_settings(SpiDevObject *self, void *closure) +{ + PyObject* dict = PyDict_New(); + if (!dict) + return NULL; + + PyDict_SetItemString(dict, "mode", Py_BuildValue("i", (self->mode & (SPI_CPHA | SPI_CPOL)))); + PyDict_SetItemString(dict, "bits_per_word", Py_BuildValue("i", self->bits_per_word)); + PyDict_SetItemString(dict, "max_speed_hz", Py_BuildValue("i", self->max_speed_hz)); + PyDict_SetItemString(dict, "read0", (self->read0 == 1) ? Py_True : Py_False); + + PyDict_SetItemString(dict, "cshigh", (self->mode & SPI_CS_HIGH) ? Py_True : Py_False); + PyDict_SetItemString(dict, "threewire", (self->mode & SPI_3WIRE) ? Py_True : Py_False); + PyDict_SetItemString(dict, "lsbfirst", (self->mode & SPI_LSB_FIRST) ? Py_True : Py_False); + PyDict_SetItemString(dict, "loop", (self->mode & SPI_LOOP) ? Py_True : Py_False); + PyDict_SetItemString(dict, "no_cs", (self->mode & SPI_NO_CS) ? Py_True : Py_False); + + return dict; +} + static PyGetSetDef SpiDev_getset[] = { {"mode", (getter)SpiDev_get_mode, (setter)SpiDev_set_mode, "SPI mode as two bit pattern of \n" @@ -1386,27 +1354,7 @@ SpiDev_open_path(SpiDevObject *self, PyObject *args, PyObject *kwds) } -PyDoc_STRVAR(SpiDev_open_doc, - "open(bus, device)\n\n" - "Connects the object to the specified SPI device.\n" - "open(X,Y) will open /dev/spidev.\n"); - -static PyObject * -SpiDev_open(SpiDevObject *self, PyObject *args, PyObject *kwds) -{ - int bus, device; - char path[SPIDEV_MAXPATH]; - static char *kwlist[] = {"bus", "device", NULL}; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii:open", kwlist, &bus, &device)) - return NULL; - if (snprintf(path, SPIDEV_MAXPATH, "/dev/spidev%d.%d", bus, device) >= SPIDEV_MAXPATH) { - PyErr_SetString(PyExc_OverflowError, - "Bus and/or device number is invalid."); - return NULL; - } - return SpiDev_open_dev(self, path); -} - +// TODO: not necessary anymore? static int SpiDev_init(SpiDevObject *self, PyObject *args, PyObject *kwds) { @@ -1418,12 +1366,6 @@ SpiDev_init(SpiDevObject *self, PyObject *args, PyObject *kwds) kwlist, &bus, &client)) return -1; - if (bus >= 0) { - SpiDev_open(self, args, kwds); - if (PyErr_Occurred()) - return -1; - } - return 0; } @@ -1433,35 +1375,7 @@ PyDoc_STRVAR(SpiDevObjectType_doc, "Return a new SPI object that is (optionally) connected to the\n" "specified SPI device interface.\n"); -static -PyObject *SpiDev_enter(PyObject *self, PyObject *args) -{ - if (!PyArg_ParseTuple(args, "")) - return NULL; - - Py_INCREF(self); - return self; -} - -static -PyObject *SpiDev_exit(SpiDevObject *self, PyObject *args) -{ - - PyObject *exc_type = 0; - PyObject *exc_value = 0; - PyObject *traceback = 0; - if (!PyArg_UnpackTuple(args, "__exit__", 3, 3, &exc_type, &exc_value, - &traceback)) { - return 0; - } - - SpiDev_close(self); - Py_RETURN_FALSE; -} - static PyMethodDef SpiDev_methods[] = { - {"open", (PyCFunction)SpiDev_open, METH_VARARGS | METH_KEYWORDS, - SpiDev_open_doc}, {"open_path", (PyCFunction)SpiDev_open_path, METH_VARARGS | METH_KEYWORDS, SpiDev_open_path_doc}, {"close", (PyCFunction)SpiDev_close, METH_NOARGS, @@ -1480,10 +1394,6 @@ static PyMethodDef SpiDev_methods[] = { SpiDev_xfer2_doc}, {"xfer3", (PyCFunction)SpiDev_xfer3, METH_VARARGS, SpiDev_xfer3_doc}, - {"__enter__", (PyCFunction)SpiDev_enter, METH_VARARGS, - NULL}, - {"__exit__", (PyCFunction)SpiDev_exit, METH_VARARGS, - NULL}, {NULL}, }; diff --git a/spidev/_spimodule.pyi b/spidev/_spimodule.pyi new file mode 100644 index 0000000..e9680e1 --- /dev/null +++ b/spidev/_spimodule.pyi @@ -0,0 +1,47 @@ +# I generated this with ChatGPT and think it's mostly correct. It is not meant +# to be permanent and is their to help with uplifting most of the C module's +# code to a .py file while making sure the properties and method signature stay +# the same. +from collections.abc import Buffer, Sequence + +class SpiDev: + def __init__(self, bus: int | None = ..., client: int | None = ...) -> None: ... + def open_path(self, path: str) -> None: ... + def close(self) -> None: ... + def fileno(self) -> int: ... + def readbytes(self, length: int) -> list[int]: ... + def writebytes(self, values: Sequence[int]) -> None: ... + def writebytes2(self, values: Sequence[int] | Buffer) -> None: ... + def xfer( + self, + values: Sequence[int], + speed_hz: int | None = ..., + delay_usecs: int | None = ..., + bits_per_word: int | None = ..., + ) -> list[int]: ... + def xfer2( + self, + values: Sequence[int], + speed_hz: int | None = ..., + delay_usecs: int | None = ..., + bits_per_word: int | None = ..., + ) -> list[int]: ... + def xfer3( + self, + values: Sequence[int], + speed_hz: int | None = ..., + delay_usecs: int | None = ..., + bits_per_word: int | None = ..., + ) -> tuple[int, ...]: ... + + mode: int + cshigh: bool + threewire: bool + lsbfirst: bool + loop: bool + no_cs: bool + bits_per_word: int + max_speed_hz: int + read0: bool + +__version__: str