pimoroni / inky

Combined library for V2/V3 Inky pHAT and Inky wHAT.
https://shop.pimoroni.com/?q=inky
MIT License
575 stars 121 forks source link

Port to gpiod for Bookworm/Pi 5 and beyond #182

Open Gadgetoid opened 9 months ago

Gadgetoid commented 9 months ago

Hey ho, we're in the process of transitioning the Inky library and examples to gpiod, virtual environments and the shiny new Pi 5.

As such, I'd really like your feedback!

Progress

Note: Most examples using GPIO have not been ported, with the exception of examples/7color/buttons.py.

Note: The simulator might be broken. I am removing conditional imports and custom ImportError trapping where I can, since this overcomplicates the codebase especially when we try to move to gpiod.

👉 Installation Instructions 👈

Grab this branch and install with the --unstable (local downloaded copy of the library) flag:

git clone https://github.com/pimoroni/inky -b gpiod
cd inky
./install.sh --unstable

A venv should be created for you, though this step will be skipped if you're already inside one (in theory).

If you get errors, make sure you activate that virtual environment. Your bash prompt should start with (pimoroni):

source ~/.virtualenvs/pimoroni/bin/activate

The installer should have added some new lines to /boot/firmware/config.txt so that SPI gets out of the way of the chip-select pin and the EEPROM i2c is enabled (for detecting your Inky board type)-

dtoverlay=spi0-0cs
dtoverlay=i2c1
dtoverlay=i2c1-pi5

Reboot your Pi, and see if anything breaks.

Things to look out for:

  1. Any errors writing /boot/firmware/config.txt and that the lines mentioned above are present
  2. Make sure /boot/config.txt is still a symlink to /boot/firmware/config.txt - I think sed breaks this horribly (Note: On my Pi it now seems like this is not a symlink on purpose?)
  3. Examples and such are copied to ~/Pimoroni
  4. No missing packages or heinous compile steps during install
  5. Your product actually works!

Inky wHAT (ssd1683)

Tested before the dtoverlay=spi0-0cs change, which worked, but this broke SSD1683 because it was using the default SPI chip-select behaviour. I have migrated everything over to use soft chip-select, though it might be worth seeing if we can give it over to the SPI driver on all counts and avoid needing to load that overlay.

Inky Impressions 5.7"

I've been having trouble with this, and I'm still suspicious but it seems to now be working-

PYTHONPATH=../../ python3 image.py --type 7colour --file images/inky-4.0-redchain.jpg

I have a test board with no EEPROM data, so it blurts out:

Detected None
Failed to detect an Inky board. Trying --type/--colour arguments instead...

/home/phil/Development/pi/inky/inky/inky_uc8159.py:242: UserWarning: SPI: Cannot disable chip-select!
  warnings.warn("SPI: Cannot disable chip-select!")
/home/phil/Development/pi/inky/inky/inky_uc8159.py:337: UserWarning: Busy Wait: Held high. Waiting for 1.00s
  warnings.warn(f"Busy Wait: Held high. Waiting for {timeout:0.2f}s")
/home/phil/Development/pi/inky/inky/inky_uc8159.py:337: UserWarning: Busy Wait: Held high. Waiting for 0.20s
  warnings.warn(f"Busy Wait: Held high. Waiting for {timeout:0.2f}s")
/home/phil/Development/pi/inky/inky/inky_uc8159.py:337: UserWarning: Busy Wait: Held high. Waiting for 32.00s
  warnings.warn(f"Busy Wait: Held high. Waiting for {timeout:0.2f}s")

This may have been another SPI chip-select issue.

Inky Impressions 4"

My 4" proto has no EEPROM data flashed, so I modified stripes.py as follows:

#!/usr/bin/env python3

# To simulate:
# from inky.mock import InkyMockImpression as Inky
#from inky.auto import auto
from inky.inky_uc8159 import Inky

#inky = auto(ask_user=True, verbose=True)
inky = Inky((640, 400))

for y in range(inky.height - 1):
    color = y // (inky.height // 7)
    for x in range(inky.width - 1):
        inky.set_pixel(x, y, color * 0x11)

inky.show()
# To simulate:
# inky.wait_for_window_close()
stonehippo commented 5 months ago

@Gadgetoid Tried this with an older Inky What (red) and it's sorta but not really working. Display updates (yay) but is offset and doesn't cover the full screen.

Any idea where to start on to get things working correctly?

IMG_3003

Gadgetoid commented 5 months ago

Ooof! That’s… interesting 😬 I can’t for the life of me remember if there were two variants of the WHAT- but I believe there were.

Poking through code (on my phone, eek) maybe yours is an O.G. rather than an SSD1683 WHAT? Should be possible to construct an Inky() instance with the correct (400, 300)? resolution.

What does the identify.py script say?

stonehippo commented 5 months ago

@Gadgetoid here's the output:

No display EEPROM detected,
you might have an old Inky board that doesn't have an EEPROM - eg: early Inky pHAT boards.

Try running examples with --colour <black/red/yellow>

Or writing your code using:

from inky.phat import InkyPHAT

display = InkyPHAT("<black/red/yellow>")

Checking the back of the board, there's no EEPROM. I've been using --type what --colour red with the other scripts.

I've also got an old school yellow PHAT that works as expected, so that's cool.

stonehippo commented 5 months ago

Tried --type whatssd1683 just to see what would happen. Answer: nothing at all.

stonehippo commented 5 months ago

@Gadgetoid Ok, interesting. I created an instance of InkyWHAT in the REPL and I can draw boxes correctly with .set_pixel without any issues. I've tried loading the what logo image with .set_image and things go off the rails. Which lead me to look at .set_image, which seems to be broken. Here's the implementation in the gpiod branch:


    def set_image(self, image):
        """Copy an image to the buffer.
        """
        image = image.resize((self.width, self.height))

        if not image.mode == "P":
            palette_image = Image.new("P", (1, 1))
            r, g, b = 0, 0, 0
            if self.colour == "red":
                r = 255
            if self.colour == "yellow":
                r = g = 255
            palette_image.putpalette([255, 255, 255, 0, 0, 0, r, g, b] + [0, 0, 0] * 252)
            image.load()
            image = image.im.convert("P", True, palette_image.im)

        canvas = Image.new("P", (self.rows, self.cols))
        width, height = image.size
        canvas.paste(image, (0, 0, width, height))
        self.buf = numpy.array(canvas, dtype=numpy.uint8).reshape((self.cols, self.rows))

That's all good, until it hits the new canvas. It looks like self.rows and self.cols are swapped. On the wHAT, this creates a canvas of (300,400) instead of (400, 300), which then gets the existing image pasted into it. That distorted image then gets put in the buffer.

To confirm that was the issue, I swapped self.rows and self.cols:

canvas = Image.new("P", (self.cols, self.rows))

The result: IMG_3012

IMG_3014

I haven't looked into whether this change breaks things on my pHAT or not yet. Frankly, it's a little weird to me that it's working anywhere.

Gadgetoid commented 5 months ago

I haven't looked into whether this change breaks things on my pHAT or not yet. Frankly, it's a little weird to me that it's working anywhere.

Looks like I updated this code as part of this branch, but it's as of yet untested.

I would expect it to break the pHAT if it was using the same driver. It may be that the cols/rows values for the displays need updating and the code tweaked to use them properly. Very much something I need to write a test for!

stonehippo commented 5 months ago

Yeah, you might not really need cols/rows at all (except maybe for backward compatibility with early versions). It looks like they're just the same values as the width and height for all displays?

Gadgetoid commented 4 months ago

Assuming the test tests what I think it does - I'm pretty rusty on these old Inky boards - then just removing the additional canvas and image pasting step seems to work. Afaik this step does nothing at best, since the image is already resized to the display width/height. Raised the new test and possible fix and a PR against this branch here: https://github.com/pimoroni/inky/pull/188

BryanH commented 4 months ago

@Gadgetoid

Make sure /boot/config.txt is still a symlink to /boot/firmware/config.txt

It isn't.

Here's the identify for my board:

Found: Yellow wHAT
Display: 400x300
Color: yellow
PCB Variant: 1.2
Display Variant: 2
Time: b'2023-02-01 09:29:03.0'

When I run the logo example following the above installation instructions, I get:

$ python3 logo.py

Inky pHAT/wHAT: Logo

Displays the Inky pHAT/wHAT logo.

Detected Yellow wHAT
/home/pi/.virtualenvs/pimoroni/lib/python3.11/site-packages/inky/inky.py:254: UserWarning: SPI: Cannot disable chip-select!
  warnings.warn("SPI: Cannot disable chip-select!")

My Inky shows the same display as in the comment by @stonehippo (although mine is yellow).

Gadgetoid commented 4 months ago

It isn't.

Yeah, they changed that recently-ish just to keep me on my toes :cry:

In that case, what's in your /boot/firmware/config.txt?

BryanH commented 4 months ago

@Gadgetoid Here you go!

# For more options and information see
# http://rptl.io/configtxt
# Some settings may impact device functionality. See link above for details

# Uncomment some or all of these to enable the optional hardware interfaces
dtparam=i2c_arm=on
#dtparam=i2s=on
dtparam=spi=on

# Enable audio (loads snd_bcm2835)
dtparam=audio=on

# Additional overlays and parameters are documented
# /boot/firmware/overlays/README

# Automatically load overlays for detected cameras
camera_auto_detect=1

# Automatically load overlays for detected DSI displays
display_auto_detect=1

# Automatically load initramfs files, if found
auto_initramfs=1

# Enable DRM VC4 V3D driver
dtoverlay=vc4-kms-v3d
max_framebuffers=2

# Don't have the firmware create an initial video= setting in cmdline.txt.
# Use the kernel's default instead.
disable_fw_kms_setup=1

# Run in 64-bit mode
arm_64bit=1

# Disable compensation for displays with overscan
disable_overscan=1

# Run as fast as firmware / board allows
arm_boost=1

[cm4]
# Enable host mode on the 2711 built-in XHCI USB controller.
# This line should be removed if the legacy DWC2 controller is required
# (e.g. for USB device mode) or if USB support is not required.
otg_mode=1

[all]

dtoverlay=i2c1
dtoverlay=i2c1-pi5
dtoverlay=spi0-0cs
Gadgetoid commented 4 months ago

Looks like I need a try/catch around the chip select disable call- since it isn’t owned by the SPI driver 😬

Gadgetoid commented 4 months ago

Oh hmm, wait, this warning is the result of such a try/catch.

Sorry I have completely lost the plot from juggling so much stuff that my memory was purged of any and all recollection of this issue.

More discussion and a potential fix should be detailed here. I'll get some install instructions added for the fix branch so it's easier for you to try - https://github.com/pimoroni/inky/pull/188

davidskeck commented 1 month ago

Hello, I tried installing this branch with the given instructions on the Pi 5, but I get the below error. Any ideas?

  Building wheel for spidev (pyproject.toml) ... error
  error: subprocess-exited-with-error

  × Building wheel for spidev (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [27 lines of output]
      /tmp/pip-build-env-cogozc2i/overlay/lib/python3.11/site-packages/setuptools/dist.py:447: SetuptoolsDeprecationWarning: Invalid dash-separated options
      !!

              ********************************************************************************
              Usage of dash-separated 'description-file' will not be supported in future
              versions. Please use the underscore name 'description_file' instead.

              By 2024-Sep-26, you need to update your project and remove deprecated calls
              or your builds will no longer be supported.

              See https://setuptools.pypa.io/en/latest/userguide/declarative_config.html for details.
              ********************************************************************************

      !!
        opt = self.warn_dash_deprecation(opt, section)
      running bdist_wheel
      running build
      running build_ext
      building 'spidev' extension
      creating build
      creating build/temp.linux-aarch64-cpython-311
      aarch64-linux-gnu-gcc -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -fPIC -I/home/david/pinky/.venv/include -I/usr/include/python3.11 -c spidev_module.c -o build/temp.linux-aarch64-cpython-311/spidev_module.o
      spidev_module.c:28:10: fatal error: Python.h: No such file or directory
         28 | #include <Python.h>
            |          ^~~~~~~~~~
      compilation terminated.
      error: command '/usr/bin/aarch64-linux-gnu-gcc' failed with exit code 1
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for spidev
Successfully built inky
Failed to build spidev
ERROR: Could not build wheels for spidev, which is required to install pyproject.toml-based projects
⚠ WARNING: ^^^ 😬 previous command did not exit cleanly!
Done!
davidskeck commented 1 month ago

Hello, I tried installing this branch with the given instructions on the Pi 5, but I get the below error. Any ideas?

  Building wheel for spidev (pyproject.toml) ... error
  error: subprocess-exited-with-error

  × Building wheel for spidev (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [27 lines of output]
      /tmp/pip-build-env-cogozc2i/overlay/lib/python3.11/site-packages/setuptools/dist.py:447: SetuptoolsDeprecationWarning: Invalid dash-separated options
      !!

              ********************************************************************************
              Usage of dash-separated 'description-file' will not be supported in future
              versions. Please use the underscore name 'description_file' instead.

              By 2024-Sep-26, you need to update your project and remove deprecated calls
              or your builds will no longer be supported.

              See https://setuptools.pypa.io/en/latest/userguide/declarative_config.html for details.
              ********************************************************************************

      !!
        opt = self.warn_dash_deprecation(opt, section)
      running bdist_wheel
      running build
      running build_ext
      building 'spidev' extension
      creating build
      creating build/temp.linux-aarch64-cpython-311
      aarch64-linux-gnu-gcc -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -fPIC -I/home/david/pinky/.venv/include -I/usr/include/python3.11 -c spidev_module.c -o build/temp.linux-aarch64-cpython-311/spidev_module.o
      spidev_module.c:28:10: fatal error: Python.h: No such file or directory
         28 | #include <Python.h>
            |          ^~~~~~~~~~
      compilation terminated.
      error: command '/usr/bin/aarch64-linux-gnu-gcc' failed with exit code 1
      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for spidev
Successfully built inky
Failed to build spidev
ERROR: Could not build wheels for spidev, which is required to install pyproject.toml-based projects
⚠ WARNING: ^^^ 😬 previous command did not exit cleanly!
Done!

aaand it looks like this fixes it... sudo apt-get install python3-dev

davidskeck commented 1 month ago

image any ideas on this one?