robotpy/robotpy-wpilib

[BUG]: Broken Implementation of WPILib SPI

echap1 opened this issue · 7 comments

Problem description

When trying to use wpilib SPI to read data into a buffer, a segmentation fault occurs inside of the wpilib.SPI.read method. I believe that the bindings are written improperly for this class. The signature of this function is def read(self, initiate: bool, dataReceived: buffer) -> int, and it is supposed to read data from the bus into the dataRecieved buffer. This is how it works in C++ wpilib, except a size parameter that is mentioned in the docstring but not present for some reason. However, this approach doesn't work in python because you can't pass pointers like you do in C++, and I think this function and functions like it (wpilib.SPI.transaction and others) should instead return the buffer.

Operating System

Linux, RoboRIO

Installed Python Packages

attrs                    21.4.0
bcrypt                   3.2.0
CacheControl             0.12.10
cachy                    0.3.0
certifi                  2021.10.8
cffi                     1.15.0
charset-normalizer       2.0.12
cleo                     0.8.1
click                    8.0.4
clikit                   0.6.2
crashtest                0.3.1
cryptography             36.0.1
distlib                  0.3.4
fast_unit                0.5.0
filelock                 3.4.2
html5lib                 1.1
idna                     3.3
importlib-metadata       4.11.0
iniconfig                1.1.1
jeepney                  0.7.1
keyring                  23.5.0
lockfile                 0.12.2
maturin                  0.12.9
msgpack                  1.0.3
numpy                    1.22.2
packaging                21.3
paramiko                 2.9.2
pastel                   0.2.1
pexpect                  4.8.0
Pint                     0.18
pip                      21.3.1
pkginfo                  1.8.2
platformdirs             2.5.0
pluggy                   1.0.0
poetry                   1.1.13
poetry-core              1.0.7
ptyprocess               0.7.0
py                       1.11.0
pycparser                2.21
pydantic                 1.9.0
pyfrc                    2022.0.2
pylev                    1.4.0
PyNaCl                   1.5.0
pynetconsole             2.0.2
pynetworktables          2021.0.0
pyntcore                 2022.4.1.0
pyparsing                3.0.7
pytest                   7.0.1
requests                 2.27.1
requests-toolbelt        0.9.1
robotpy                  2022.0.56
robotpy-commands-v2      2022.4.1.0
robotpy-ctre             2022.1.0
robotpy-hal              2022.4.1.0
robotpy-halsim-gui       2022.4.1.0
robotpy-installer        2022.1.1
robotpy-rev              2022.0.1
robotpy-toolkit-7407     0.3.3
robotpy-wpilib-utilities 2022.0.2
robotpy-wpimath          2022.4.1.0
robotpy-wpiutil          2022.4.1.1
SecretStorage            3.3.1
setuptools               58.3.0
shellingham              1.4.0
six                      1.16.0
toml                     0.10.2
tomli                    2.0.1
tomlkit                  0.9.2
typing_extensions        4.1.1
urllib3                  1.26.8
virtualenv               20.13.1
webencodings             0.5.1
wheel                    0.37.0
wpilib                   2022.4.1.0
zipp                     3.7.0

Reproducible example code

# Set up SPI bus
camera = wpilib.SPI(wpilib.SPI.Port.kOnboardCS0)
camera.setClockRate(CLOCK_RATE)
camera.setClockActiveHigh()

# Create buffer (also tried empty buffer)
recv_data = bytes([0] * 100000)

# Initiate read
self.left_cam.read(True, recv_data)  # ERROR HERE

print(recv_data)

bytes are readonly in python. I believe if you use a bytearray instead then it will work.

It should give a good error message when you pass in a bytes... what error message do you get when you do that? We can try to make the error better in the future.

I will try that today. I get no type error when invoking the function (but I do when trying to pass something like [] instead of bytes), it just gives me a segmentation fault with a traceback to the read function. Is there any way I can specify the size of the read? Will it just be the size of my bytearray?

Yes, it should be the size of your bytearray.

I'll see if I can take a look tonight.

The function returns the size of my bytearray, so I think you're right. However, when I try to initiate a read the bytearray is unmodified and the function doesn't error even when no SPI device is connected. Is this intended behavior?

The binding looks right to me. Here's what robotpy-build autogenerates to bind the function:

.def("read", [](frc::SPI * __that,bool initiate, const py::buffer& dataReceived) {
          int size = 0;
          auto __dataReceived = dataReceived.request(true);
          size = __dataReceived.size * __dataReceived.itemsize;
          auto __ret =__that->Read(initiate, (uint8_t*)__dataReceived.ptr, size );
          return __ret;
        },

Looking at SPI::Read it looks like the return value should be ... something undocumented. According to HAL, it looks like the return value should be whatever ioctl is going to return: ... which is likely going to be the length of data written?

Also, my understanding is that many SPI devices require you to write something in order to read it, which is why an API like transaction exists. So maybe you're just not telling the device to send any data, so it isn't.

I'm going to close this as I don't believe there's a bug here, just the error message needs improvement.