`import board` with MCP2221 fails with `OSError: open failed`
See original GitHub issueI spent a couple of nights trying to read data from a SCD30 connected to a Linux machine through a MCP2221 following this example, but I was getting a traceback during the import of the board
module. To run the MCP2221 I followed this article that explains how to install all the necessary packages and also how to blacklist and replace the native hid_mcp2221 Linux driver (all the post-install checks were successful).
Eventually I narrowed it down to a minimal set of instructions to reproduce the failures consistently.
The first error I got was
>>> import board
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.9/dist-packages/board.py", line 288, in <module>
raise NotImplementedError("Board not supported {}".format(board_id))
NotImplementedError: Board not supported GENERIC_LINUX_PC
I was able to fix this by restarting Python, setting the BLINKA_MCP2221
environment variable to 1
, and trying import board
again. However, I still got an error:
>>> import os; os.environ['BLINKA_MCP2221'] = '1'
>>> import board
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.9/dist-packages/board.py", line 197, in <module>
from adafruit_blinka.board.microchip_mcp2221 import *
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/board/microchip_mcp2221.py", line 2, in <module>
from adafruit_blinka.microcontroller.mcp2221 import pin
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/pin.py", line 2, in <module>
from .mcp2221 import mcp2221
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 386, in <module>
mcp2221 = MCP2221()
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 55, in __init__
self._reset()
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 131, in _reset
raise OSError("open failed")
OSError: open failed
>>>
If I try again immediately after, I get a different error:
>>> import board
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.9/dist-packages/board.py", line 197, in <module>
from adafruit_blinka.board.microchip_mcp2221 import *
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/board/microchip_mcp2221.py", line 2, in <module>
from adafruit_blinka.microcontroller.mcp2221 import pin
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/pin.py", line 2, in <module>
from .mcp2221 import mcp2221
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 386, in <module>
mcp2221 = MCP2221()
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 53, in __init__
self._hid.open(MCP2221.VID, MCP2221.PID)
File "hid.pyx", line 113, in hid.device.open
OSError: open failed
In both cases, the error comes from a self._hid.open(MCP2221.VID, MCP2221.PID)
, and it happens whenever I try to open a device that is already open. By looking at the code, the __init__
of the MCP2221
class does:
https://github.com/adafruit/Adafruit_Blinka/blob/1ac53c86297e869220ea41d8740f01852609dd65/src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py#L51-L55
and the _reset
method that it calls does (notice the other open
at line 125):
https://github.com/adafruit/Adafruit_Blinka/blob/1ac53c86297e869220ea41d8740f01852609dd65/src/adafruit_blinka/microcontroller/mcp2221/mcp2221.py#L119-L131
It seems that the first traceback is caused by the fact that the __init__
successfully opens the device (at line 53), but then calls self._reset()
at line 55, that tries to open the – already opened – device again (at line 125), causing the traceback. The second traceback can be explained by the fact that after the first error, the __init__
doesn’t clean up after itself and doesn’t call self._hid.close()
, so the device is still open when the self._hid.open(MCP2221.VID, MCP2221.PID)
at line 53 is executed. However after waiting for a while, the import board
fails again in the _reset()
method (maybe the device is automatically closed – perhaps after being garbage collected – and the open
at line 53 is successful again).
I verified that trying to open an already open device (like _reset
does after the __init__
, or after two consecutive import board
) results in an error:
>>> import hid; h = hid.device(); h.open(0x04D8, 0x00DD);
>>> h.open(0x04D8, 0x00DD)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "hid.pyx", line 113, in hid.device.open
OSError: open failed
I also noticed that the hid.device.close()
method sometimes returns immediately, other times it takes about ~16s. After a lot of trial and error, I found that trying to import board
makes it slow, and a way to make it faster again is to first open the device, then close Python without closing the device, causing a segfault. After the segfault and after restarting Python, .close()
will be faster again:
$ python3
Python 3.9.7 (default, Sep 10 2021, 14:59:43)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> # now .close() is fast
>>> import hid, time; h = hid.device(); h.open(0x04D8, 0x00DD); t = time.time(); h.close(); time.time() - t
0.0011868476867675781
>>> # try to import board, it will fail and make .close() slow
>>> import os; os.environ['BLINKA_MCP2221'] = '1'
>>> import board
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/lib/python3.9/dist-packages/board.py", line 197, in <module>
...
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 55, in __init__
self._reset()
File "/usr/local/lib/python3.9/dist-packages/adafruit_blinka/microcontroller/mcp2221/mcp2221.py", line 131, in _reset
raise OSError("open failed")
OSError: open failed
>>> # now the device should still be open, so the next line will fail
>>> import hid, time; h = hid.device(); h.open(0x04D8, 0x00DD); t = time.time(); h.close(); time.time() - t
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "hid.pyx", line 113, in hid.device.open
OSError: open failed
>>> # after a while it should work, but it will be slow now
>>> h = hid.device(); h.open(0x04D8, 0x00DD); t = time.time(); h.close(); time.time() - t
16.50315546989441
>>> # now I can exit Python (with ctrl+d), but the .close() will still be slow
>>>
$ python3
Python 3.9.7 (default, Sep 10 2021, 14:59:43)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> # .close() is still slow, even after reopening Python
>>> import hid, time; h = hid.device(); h.open(0x04D8, 0x00DD); t = time.time(); h.close(); time.time() - t
16.573604822158813
>>> # opening the device and quitting Python without closing it will cause a segfault
>>> h.open(0x04D8, 0x00DD)
>>>
python3: io.c:2115: handle_events: Assertion `ctx->pollfds_cnt >= internal_nfds' failed.
Aborted (core dumped)
$ python3
Python 3.9.7 (default, Sep 10 2021, 14:59:43)
[GCC 11.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> # now it will be fast again
>>> import hid, time; h = hid.device(); h.open(0x04D8, 0x00DD); t = time.time(); h.close(); time.time() - t
0.0013420581817626953
>>>
Apparently importing board
causes some issue with the MCP2221, that only gets reset after a crash. I think the module should ensure that the device is always closed correctly, both in case of error and in case of success after the program exits regularly. The former could be achieved by wrapping the call to self._reset()
in the __init__
in a try
/finally
that closes the device before letting the exception propagate, the latter by defining a __del__
(unreliable) and/or by using atexit
.
This should fix the slow .close()
and some other issues, but probably not the import error. To fix the import error, I eventually used this solution:
>>> import os; os.environ['BLINKA_MCP2221'] = '1'
>>> import os; os.environ['BLINKA_MCP2221_RESET_DELAY'] = '20'
>>> import board
>>>
python3: io.c:2115: handle_events: Assertion `ctx->pollfds_cnt >= internal_nfds' failed.
Aborted (core dumped)
This is the only way I found to successfully import board
, and after that I was able to run the example and read data from the SCD30 sensor (even though it still crashes upon exit).
There are still a few things that are not clear to me:
- Why is the call to
self._reset()
necessary in the__init__
and what does it do exactly? - Why does
_reset
open the (already opened) device again, and why does it work after adding a delay? - Can the delay be calculated dynamically, by making a few attempts at increasing time intervals?
- What exactly is altering the state of the MCP2221 and causing
.close()
to become slow? - Why does the linked article requires the native Linux driver to be removed/blacklisted?
Tl;DR:
- Trying to
import board
withBLINKA_MCP2221 == 1
(and importing theMCP2221
class) fails unless a 20s delay is added. - The
MCP2221
class/module leave the MCP2221 in an altered state in case of error or even when closing the program after a successful import.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:14 (12 by maintainers)
Top GitHub Comments
That’s one of the post install checks: https://learn.adafruit.com/circuitpython-libraries-on-any-computer-with-mcp2221/post-install-checks#check-that-mcp2221-can-be-found-3049740-7
Did you run that when setting things up initially?
Closing this issue. It’s getting off track.
@ezio-melotti I think guide info has generally been updated per all the previous discussion above.
@dalbabur Please open a new issue. This seems to be something specific to your setup. Just tested setting up a MCP2221 up on a Pi4 and it generally worked OK. Also, just to point out - the only thing a MCP2221 is adding that a Pi does not already have natively are the ADCs. Pi’s have I2C, SPI, UART, and tons of GPIO.
BASIC HID TEST
BASIC BLINKA TEST