Repository: pimoroni/st7789-python
Branch: main
Commit: 2e0df2861ef6
Files: 32
Total size: 59.2 KB
Directory structure:
gitextract_u_1k7z4_/
├── .coveragerc
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ ├── build.yml
│ ├── qa.yml
│ └── test.yml
├── .gitignore
├── .stickler.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── ST7789.py
├── check.sh
├── examples/
│ ├── 320x240.py
│ ├── LICENSE.txt
│ ├── framerate.py
│ ├── gif.py
│ ├── image.py
│ ├── round.py
│ ├── scrolling-text.py
│ └── shapes.py
├── install.sh
├── pyproject.toml
├── requirements-dev.txt
├── st7789/
│ └── __init__.py
├── tests/
│ ├── conftest.py
│ ├── test_dimensions.py
│ ├── test_display.py
│ └── test_setup.py
├── tox.ini
└── uninstall.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .coveragerc
================================================
[run]
source = st7789
omit =
.tox/*
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
Thank you for opening an issue on an Pimoroni Python library repository. To
improve the speed of resolution please review the following guidelines and
common troubleshooting steps below before creating the issue:
- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use
the forums at http://forums.pimoroni.com to ask questions and troubleshoot why
something isn't working as expected. In many cases the problem is a common issue
that you will more quickly receive help from the forum community. GitHub issues
are meant for known defects in the code. If you don't know if there is a defect
in the code then start with troubleshooting on the forum first.
- **If following a tutorial or guide be sure you didn't miss a step.** Carefully
check all of the steps and commands to run have been followed. Consult the
forum if you're unsure or have questions about steps in a guide/tutorial.
- **For Python/Raspberry Pi projects check these very common issues to ensure they don't apply**:
- If you are receiving an **ImportError: No module named...** error then a
library the code depends on is not installed. Check the tutorial/guide or
README to ensure you have installed the necessary libraries. Usually the
missing library can be installed with the `pip` tool, but check the tutorial/guide
for the exact command.
- **Be sure you are supplying adequate power to the board.** Check the specs of
your board and power in an external power supply. In many cases just
plugging a board into your computer is not enough to power it and other
peripherals.
- **Double check all soldering joints and connections.** Flakey connections
cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints.
If you're sure this issue is a defect in the code and checked the steps above
please fill in the following fields to provide enough troubleshooting information.
You may delete the guideline and text above to just leave the following details:
- Platform/operating system (i.e. Raspberry Pi with Raspbian operating system,
Windows 32-bit, Windows 64-bit, Mac OSX 64-bit, etc.): **INSERT PLATFORM/OPERATING
SYSTEM HERE**
- Python version (run `python -version` or `python3 -version`): **INSERT PYTHON
VERSION HERE**
- Error message you are receiving, including any Python exception traces: **INSERT
ERROR MESAGE/EXCEPTION TRACES HERE***
- List the steps to reproduce the problem below (if possible attach code or commands
to run): **LIST REPRO STEPS BELOW**
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
Thank you for creating a pull request to contribute to Pimoroni's GitHub code!
Before you open the request please review the following guidelines and tips to
help it be more easily integrated:
- **Describe the scope of your change--i.e. what the change does and what parts
of the code were modified.** This will help us understand any risks of integrating
the code.
- **Describe any known limitations with your change.** For example if the change
doesn't apply to a supported platform of the library please mention it.
- **Please run any tests or examples that can exercise your modified code.** We
strive to not break users of the code and running tests/examples helps with this
process. You should install tox (`pip install tox`) and run it in the `library`
folder to execute the tests and run Python linting.
Thank you again for contributing! We will try to test and integrate the change
as soon as we can, but be aware we have many GitHub repositories to manage and
can't immediately respond to every request. There is no need to bump or check in
on a pull request (it will clutter the discussion of the request).
Also don't be worried if the request is closed or not integrated--sometimes the
priorities of Pimoroni's GitHub code (education, ease of use) might not match the
priorities of the pull request. Don't fret, the open source community thrives on
forks and GitHub makes it easy to keep your changes in a forked repo.
After reviewing the guidelines above you can delete this text from the pull request.
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
pull_request:
push:
branches:
- main
jobs:
test:
name: Python ${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.9', '3.10', '3.11']
env:
RELEASE_FILE: ${{ github.event.repository.name }}-${{ github.event.release.tag_name || github.sha }}-py${{ matrix.python }}
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Install Dependencies
run: |
make dev-deps
- name: Build Packages
run: |
make build
- name: Upload Packages
uses: actions/upload-artifact@v4
with:
name: ${{ env.RELEASE_FILE }}
path: dist/
================================================
FILE: .github/workflows/qa.yml
================================================
name: QA
on:
pull_request:
push:
branches:
- main
jobs:
test:
name: linting & spelling
runs-on: ubuntu-latest
env:
TERM: xterm-256color
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Set up Python '3,11'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install Dependencies
run: |
make dev-deps
- name: Run Quality Assurance
run: |
make qa
- name: Run Code Checks
run: |
make check
- name: Run Bash Code Checks
run: |
make shellcheck
================================================
FILE: .github/workflows/test.yml
================================================
name: Tests
on:
pull_request:
push:
branches:
- main
jobs:
test:
name: Python ${{ matrix.python }}
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.9', '3.10', '3.11']
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- name: Install Dependencies
run: |
make dev-deps
- name: Run Tests
run: |
make pytest
- name: Coverage
if: ${{ matrix.python == '3.9' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
python -m pip install coveralls
coveralls --service=github
================================================
FILE: .gitignore
================================================
build/
_build/
*.o
*.so
*.a
*.py[cod]
*.egg-info
dist/
__pycache__
.DS_Store
*.deb
*.dsc
*.build
*.changes
*.orig.*
packaging/*tar.xz
library/debian/
.coverage
.pytest_cache
.tox
================================================
FILE: .stickler.yml
================================================
---
linters:
flake8:
python: 3
max-line-length: 160
================================================
FILE: CHANGELOG.md
================================================
1.0.1
-----
* Add spidev and numpy dependencies.
1.0.0
-----
* Repackage to hatch/pyproject.toml
* Port to gpiod/gpiodevice
0.0.4
-----
* Add support for 320x240 2.0" LCD (Display HAT Mini)
* Add support for 240x135 1.14" LCD (@slabua)
* Rework numpy RGB888 to RGB565
* Support displaying numpy arrays (@zecktos)
0.0.3
-----
* Add support for RLCD
* Brought back `offset_left` and `offset_top` parameters
0.0.2
-----
* Fix for image retention
* Drop defunct parameters
0.0.1
-----
* Initial Release
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2014 Adafruit Industries
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: Makefile
================================================
LIBRARY_NAME := $(shell hatch project metadata name 2> /dev/null)
LIBRARY_VERSION := $(shell hatch version 2> /dev/null)
.PHONY: usage install uninstall check pytest qa build-deps check tag wheel sdist clean dist testdeploy deploy
usage:
ifdef LIBRARY_NAME
@echo "Library: ${LIBRARY_NAME}"
@echo "Version: ${LIBRARY_VERSION}\n"
else
@echo "WARNING: You should 'make dev-deps'\n"
endif
@echo "Usage: make <target>, where target is one of:\n"
@echo "install: install the library locally from source"
@echo "uninstall: uninstall the local library"
@echo "dev-deps: install Python dev dependencies"
@echo "check: perform basic integrity checks on the codebase"
@echo "qa: run linting and package QA"
@echo "pytest: run Python test fixtures"
@echo "clean: clean Python build and dist directories"
@echo "build: build Python distribution files"
@echo "testdeploy: build and upload to test PyPi"
@echo "deploy: build and upload to PyPi"
@echo "tag: tag the repository with the current version\n"
version:
@hatch version
install:
./install.sh --unstable
uninstall:
./uninstall.sh
dev-deps:
python3 -m pip install -r requirements-dev.txt
sudo apt install dos2unix shellcheck
check:
@bash check.sh
shellcheck:
shellcheck *.sh
qa:
tox -e qa
pytest:
tox -e py
nopost:
@bash check.sh --nopost
tag: version
git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}"
build: check
@hatch build
clean:
-rm -r dist
testdeploy: build
twine upload --repository testpypi dist/*
deploy: nopost build
twine upload dist/*
================================================
FILE: README.md
================================================
# Python ST7789
[](https://github.com/pimoroni/st7789-python/actions/workflows/test.yml)
[](https://coveralls.io/github/pimoroni/st7789-python?branch=main)
[](https://pypi.python.org/pypi/st7789)
[](https://pypi.python.org/pypi/st7789)
Python library to control an ST7789 TFT LCD display
Designed specifically to work with a ST7789 based 240x240 pixel TFT SPI display. (Specifically the [1.3" SPI LCD from Pimoroni](https://shop.pimoroni.com/products/1-3-spi-colour-lcd-240x240-breakout)).

# Installation
Make sure you have the following dependencies:
````
sudo apt-get update
sudo apt-get install python-rpi.gpio python-spidev python-pip python-pil python-numpy
````
Install this library by running:
````
sudo pip install st7789
````
You might also need to enable I2C and SPI in raspi-config. See example of usage in the examples folder.
# Licensing & History
This library is a modification of a modification of code originally written by Tony DiCola for Adafruit Industries, and modified to work with the ST7735 by Clement Skau.
To create this ST7789 driver, it has been hard-forked from st7735-python which was originally modified by Pimoroni to include support for their 160x80 SPI LCD breakout.
## Modifications include:
* PIL/Pillow has been removed from the underlying display driver to separate concerns- you should create your own PIL image and display it using `display(image)`
* `width`, `height`, `rotation`, `invert`, `offset_left` and `offset_top` parameters can be passed into `__init__` for alternate displays
* `Adafruit_GPIO` has been replaced with `RPi.GPIO` and `spidev` to closely align with our other software (IE: Raspberry Pi only)
* Test fixtures have been added to keep this library stable
Pimoroni invests time and resources forking and modifying this open source code, please support Pimoroni and open-source software by purchasing products from us, too!
Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit!
Modified from 'Modified from 'Adafruit Python ILI9341' written by Tony DiCola for Adafruit Industries.' written by Clement Skau.
MIT license, all text above must be included in any redistribution
================================================
FILE: ST7789.py
================================================
from warnings import warn
from st7789 import * # noqa F403
warn(
'Using "import ST7789" is deprecated. Please "import st7789" (all lowercase)!',
DeprecationWarning,
stacklevel=2,
)
================================================
FILE: check.sh
================================================
#!/bin/bash
# This script handles some basic QA checks on the source
NOPOST=$1
LIBRARY_NAME=$(hatch project metadata name)
LIBRARY_VERSION=$(hatch version | awk -F "." '{print $1"."$2"."$3}')
POST_VERSION=$(hatch version | awk -F "." '{print substr($4,0,length($4))}')
TERM=${TERM:="xterm-256color"}
success() {
echo -e "$(tput setaf 2)$1$(tput sgr0)"
}
inform() {
echo -e "$(tput setaf 6)$1$(tput sgr0)"
}
warning() {
echo -e "$(tput setaf 1)$1$(tput sgr0)"
}
while [[ $# -gt 0 ]]; do
K="$1"
case $K in
-p|--nopost)
NOPOST=true
shift
;;
*)
if [[ $1 == -* ]]; then
printf "Unrecognised option: %s\n" "$1";
exit 1
fi
POSITIONAL_ARGS+=("$1")
shift
esac
done
inform "Checking $LIBRARY_NAME $LIBRARY_VERSION\n"
inform "Checking for trailing whitespace..."
if grep -IUrn --color "[[:blank:]]$" --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO; then
warning "Trailing whitespace found!"
exit 1
else
success "No trailing whitespace found."
fi
printf "\n"
inform "Checking for DOS line-endings..."
if grep -lIUrn --color $'\r' --exclude-dir=dist --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile; then
warning "DOS line-endings found!"
exit 1
else
success "No DOS line-endings found."
fi
printf "\n"
inform "Checking CHANGELOG.md..."
if ! grep "^${LIBRARY_VERSION}" CHANGELOG.md > /dev/null 2>&1; then
warning "Changes missing for version ${LIBRARY_VERSION}! Please update CHANGELOG.md."
exit 1
else
success "Changes found for version ${LIBRARY_VERSION}."
fi
printf "\n"
inform "Checking for git tag ${LIBRARY_VERSION}..."
if ! git tag -l | grep -E "${LIBRARY_VERSION}$"; then
warning "Missing git tag for version ${LIBRARY_VERSION}"
fi
printf "\n"
if [[ $NOPOST ]]; then
inform "Checking for .postN on library version..."
if [[ "$POST_VERSION" != "" ]]; then
warning "Found .$POST_VERSION on library version."
inform "Please only use these for testpypi releases."
exit 1
else
success "OK"
fi
fi
================================================
FILE: examples/320x240.py
================================================
#!/usr/bin/env python3
import time
from PIL import Image, ImageDraw
from st7789 import ST7789
# Buttons
BUTTON_A = 5
BUTTON_B = 6
BUTTON_X = 16
BUTTON_Y = 24
# Onboard RGB LED
LED_R = 17
LED_G = 27
LED_B = 22
# General
SPI_PORT = 0
SPI_CS = 1
SPI_DC = 9
BACKLIGHT = 13
# Screen dimensions
WIDTH = 320
HEIGHT = 240
buffer = Image.new("RGB", (WIDTH, HEIGHT))
draw = ImageDraw.Draw(buffer)
draw.rectangle((0, 0, 50, 50), (255, 0, 0))
draw.rectangle((320 - 50, 0, 320, 50), (0, 255, 0))
draw.rectangle((0, 240 - 50, 50, 240), (0, 0, 255))
draw.rectangle((320 - 50, 240 - 50, 320, 240), (255, 255, 0))
display = ST7789(
port=SPI_PORT,
cs=SPI_CS,
dc=SPI_DC,
backlight=BACKLIGHT,
width=WIDTH,
height=HEIGHT,
rotation=180,
spi_speed_hz=60 * 1000 * 1000,
)
while True:
display.display(buffer)
time.sleep(1.0 / 60)
================================================
FILE: examples/LICENSE.txt
================================================
Examples originally modified from Adafruit_Python_ILI9341
https://github.com/adafruit/Adafruit_Python_ILI9341
Copyright (c 2014 Adafruit Industries
Author: Tony DiCola
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: examples/framerate.py
================================================
#!/usr/bin/env python3
import math
import sys
import time
from PIL import Image, ImageDraw
import st7789
# Higher SPI bus speed = higher framerate
try:
SPI_SPEED_MHZ = int(sys.argv[1])
except ValueError:
sys.exit(1)
except IndexError:
SPI_SPEED_MHZ = 80
try:
display_type = sys.argv[2]
except IndexError:
display_type = "square"
print(
f"""
framerate.py - Test LCD framerate.
If you're using Breakout Garden, plug the 1.3" LCD (SPI)
breakout into the front slot.
Usage: {sys.argv[0]} <spi_speed_mhz> <display_type>
Where <display_type> is one of:
* square - 240x240 1.3" Square LCD
* round - 240x240 1.3" Round LCD (applies an offset)
* rect - 240x135 1.14" Rectangular LCD (applies an offset)
* dhmini - 320x240 2.0" Rectangular LCD
Running at: {SPI_SPEED_MHZ}MHz on a {display_type} display.
"""
)
try:
width, height, rotation, backlight, offset_left, offset_top = {
"square": (240, 240, 90, 19, 0, 0),
"round": (240, 240, 90, 19, 40, 0),
"rect": (240, 135, 0, 19, 40, 53),
"dhmini": (320, 240, 180, 13, 0, 0),
}[display_type]
except IndexError:
raise RuntimeError(f"Unsupported display type: {display_type}")
# Create ST7789 LCD display class.
disp = st7789.ST7789(
width=width,
height=height,
rotation=rotation,
port=0,
cs=st7789.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT
dc=9,
backlight=19, # Breakout Garden: 18 for back slot, 19 for front slot.
# NOTE: Change this to 13 for Pirate Audio boards
spi_speed_hz=SPI_SPEED_MHZ * 1000000,
offset_left=offset_left,
offset_top=offset_top,
)
WIDTH = disp.width
HEIGHT = disp.height
STEPS = WIDTH * 2
images = []
for step in range(STEPS):
image = Image.new("RGB", (WIDTH, HEIGHT), (0, 0, 128))
draw = ImageDraw.Draw(image)
if step % 2 == 0:
draw.rectangle((WIDTH / 2, int(HEIGHT / 2), WIDTH, HEIGHT), (0, 128, 0))
else:
draw.rectangle((0, 0, WIDTH / 2 - 1, int(HEIGHT / 2) - 1), (0, 128, 0))
f = math.sin((float(step) / STEPS) * math.pi)
offset_left = int(f * WIDTH)
draw.ellipse((offset_left, 35, offset_left + 10, 45), (255, 0, 0))
images.append(image)
count = 0
time_start = time.time()
while True:
disp.display(images[count % len(images)])
count += 1
time_current = time.time() - time_start
if count % 120 == 0:
print(
f"Time: {time_current:8.3f}, Frames: {count:6d}, FPS: {count / time_current:8.3f}"
)
================================================
FILE: examples/gif.py
================================================
#!/usr/bin/env python3
import sys
import time
from PIL import Image
import st7789
print(
"""
gif.py - Display a gif on the LCD.
If you're using Breakout Garden, plug the 1.3" LCD (SPI)
breakout into the front slot.
"""
)
if len(sys.argv) < 2:
print(
f"""Usage: {sys.argv[0]} <gif_file> <display_type>
Where <gif_file> is a .gif file.
Hint: {sys.argv[0]} deployrainbows.gif
And <display_type> is one of:
* square - 240x240 1.3" Square LCD
* round - 240x240 1.3" Round LCD (applies an offset)
* rect - 240x135 1.14" Rectangular LCD (applies an offset)
* dhmini - 320x240 2.0" Display HAT Mini
"""
)
sys.exit(1)
image_file = sys.argv[1]
try:
display_type = sys.argv[2]
except IndexError:
display_type = "square"
# Create ST7789 LCD display class.
if display_type in ("square", "rect", "round"):
disp = st7789.ST7789(
height=135 if display_type == "rect" else 240,
rotation=0 if display_type == "rect" else 90,
port=0,
cs=st7789.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT
dc=9,
backlight=19, # Breakout Garden: 18 for back slot, 19 for front slot.
# NOTE: Change this to 13 for Pirate Audio boards
spi_speed_hz=80 * 1000 * 1000,
offset_left=0 if display_type == "square" else 40,
offset_top=53 if display_type == "rect" else 0,
)
elif display_type == "dhmini":
disp = st7789.ST7789(
height=240,
width=320,
rotation=180,
port=0,
cs=1,
dc=9,
backlight=13,
spi_speed_hz=60 * 1000 * 1000,
offset_left=0,
offset_top=0,
)
else:
print("Invalid display type!")
# Initialize display.
disp.begin()
width = disp.width
height = disp.height
# Load an image.
print(f"Loading gif: {image_file}...")
image = Image.open(image_file)
print("Drawing gif, press Ctrl+C to exit!")
frame = 0
while True:
try:
image.seek(frame)
disp.display(image.resize((width, height)))
frame += 1
time.sleep(0.05)
except EOFError:
frame = 0
================================================
FILE: examples/image.py
================================================
#!/usr/bin/env python3
import sys
from PIL import Image
import st7789
print(
"""
image.py - Display an image on the LCD.
If you're using Breakout Garden, plug the 1.3" LCD (SPI)
breakout into the front slot.
"""
)
if len(sys.argv) < 2:
print(
f"""Usage: {sys.argv[0]} <image_file> <display_type>
Where <display_type> is one of:
* square - 240x240 1.3" Square LCD
* round - 240x240 1.3" Round LCD (applies an offset)
* rect - 240x135 1.14" Rectangular LCD (applies an offset)
* dhmini - 320x240 2.0" Display HAT Mini
"""
)
sys.exit(1)
image_file = sys.argv[1]
try:
display_type = sys.argv[2]
except IndexError:
display_type = "square"
# Create ST7789 LCD display class.
if display_type in ("square", "rect", "round"):
disp = st7789.ST7789(
height=135 if display_type == "rect" else 240,
rotation=0 if display_type == "rect" else 90,
port=0,
cs=st7789.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT
dc=9,
backlight=19, # Breakout Garden: 18 for back slot, 19 for front slot.
# NOTE: Change this to 13 for Pirate Audio boards
spi_speed_hz=80 * 1000 * 1000,
offset_left=0 if display_type == "square" else 40,
offset_top=53 if display_type == "rect" else 0,
)
elif display_type == "dhmini":
disp = st7789.ST7789(
height=240,
width=320,
rotation=180,
port=0,
cs=1,
dc=9,
backlight=13,
spi_speed_hz=60 * 1000 * 1000,
offset_left=0,
offset_top=0,
)
else:
print("Invalid display type!")
WIDTH = disp.width
HEIGHT = disp.height
# Initialize display.
disp.begin()
# Load an image.
print(f"Loading image: {image_file}...")
image = Image.open(image_file)
# Resize the image
image = image.resize((WIDTH, HEIGHT))
# Draw the image on the display hardware.
print("Drawing image")
disp.display(image)
================================================
FILE: examples/round.py
================================================
#!/usr/bin/env python3
import colorsys
import math
import sys
import time
from PIL import Image, ImageDraw
import st7789
print(
f"""
round.py - Shiny shiny round LCD!
If you're using Breakout Garden, plug a 1.3" ROUND LCD
(SPI) breakout into the front slot.
Usage: {sys.argv[0]} <style>
Where style is one of:
* dots - swirly swooshy dots
* lines - 3D depth effect lines
"""
)
try:
style = sys.argv[1]
except IndexError:
style = "dots"
# Create ST7789 LCD display class.
disp = st7789.ST7789(
port=0,
cs=st7789.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT
dc=9,
backlight=19, # Breakout Garden: 18 for back slot, 19 for front slot.
# NOTE: Change this to 13 for Pirate Audio boards
rotation=90,
spi_speed_hz=80 * 1000 * 1000,
offset_left=40,
)
# Initialize display.
disp.begin()
RADIUS = disp.width // 2
img = Image.new("RGB", (disp.width, disp.height), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
while True:
t = time.time()
draw.rectangle((0, 0, disp.width, disp.height), (0, 0, 0))
angle = t % (math.pi * 2)
prev_x = RADIUS
prev_y = RADIUS
steps = 100.0
angle_step = 1.0
if style == "lines":
steps *= 5
angle_step = 0.1
for step in range(int(steps)):
angle += angle_step
distance = RADIUS / steps * step
distance += step * 0.2
r, g, b = [
int(c * 255)
for c in colorsys.hsv_to_rgb((t / 10.0) + distance / 120.0, 1.0, 1.0)
]
x = RADIUS + int(distance * math.cos(angle))
y = RADIUS + int(distance * math.sin(angle))
line = ((math.sin(t + angle) + 1) / 2.0) * 10
if style == "lines":
draw.line(
(prev_x + line, prev_y + line, x - line, y - line), fill=(r, g, b)
)
else:
line += 1
draw.ellipse(
(x - line, y - line, x + (line * 2), y + (line * 2)), fill=(r, g, b)
)
prev_x = x
prev_y = y
disp.display(img)
================================================
FILE: examples/scrolling-text.py
================================================
#!/usr/bin/env python3
import sys
import time
from PIL import Image, ImageDraw, ImageFont
import st7789
MESSAGE = "Hello World! How are you today?"
print(
f"""
scrolling-test.py - Display scrolling text.
If you're using Breakout Garden, plug the 1.3" LCD (SPI)
breakout into the front slot.
Usage: {sys.argv[0]} "<message>" <display_type>
Where <display_type> is one of:
* square - 240x240 1.3" Square LCD
* round - 240x240 1.3" Round LCD (applies an offset)
* rect - 240x135 1.14" Rectangular LCD (applies an offset)
* dhmini - 320x240 2.0" Display HAT Mini
"""
)
try:
MESSAGE = sys.argv[1]
except IndexError:
pass
try:
display_type = sys.argv[2]
except IndexError:
display_type = "square"
# Create ST7789 LCD display class.
if display_type in ("square", "rect", "round"):
disp = st7789.ST7789(
height=135 if display_type == "rect" else 240,
rotation=0 if display_type == "rect" else 90,
port=0,
cs=st7789.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT
dc=9,
backlight=19, # Breakout Garden: 18 for back slot, 19 for front slot.
# NOTE: Change this to 13 for Pirate Audio boards
spi_speed_hz=80 * 1000 * 1000,
offset_left=0 if display_type == "square" else 40,
offset_top=53 if display_type == "rect" else 0,
)
elif display_type == "dhmini":
disp = st7789.ST7789(
height=240,
width=320,
rotation=180,
port=0,
cs=1,
dc=9,
backlight=13,
spi_speed_hz=60 * 1000 * 1000,
offset_left=0,
offset_top=0,
)
else:
print("Invalid display type!")
# Initialize display.
disp.begin()
WIDTH = disp.width
HEIGHT = disp.height
img = Image.new("RGB", (WIDTH, HEIGHT), color=(0, 0, 0))
draw = ImageDraw.Draw(img)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 30)
size_x, size_y = draw.textsize(MESSAGE, font)
text_x = disp.width
text_y = (disp.height - size_y) // 2
t_start = time.time()
while True:
x = (time.time() - t_start) * 100
x %= size_x + disp.width
draw.rectangle((0, 0, disp.width, disp.height), (0, 0, 0))
draw.text((int(text_x - x), text_y), MESSAGE, font=font, fill=(255, 255, 255))
disp.display(img)
================================================
FILE: examples/shapes.py
================================================
#!/usr/bin/env python3
import sys
from PIL import Image, ImageDraw, ImageFont
import st7789
print(
f"""
shapes.py - Display test shapes on the LCD using PIL.
If you're using Breakout Garden, plug the 1.3" LCD (SPI)
breakout into the front slot.
Usage: {sys.argv[0]} <display_type>
Where <display_type> is one of:
* square - 240x240 1.3" Square LCD
* round - 240x240 1.3" Round LCD (applies an offset)
* rect - 240x135 1.14" Rectangular LCD (applies an offset)
* dhmini - 320x240 2.0" Display HAT Mini
"""
)
try:
display_type = sys.argv[1]
except IndexError:
display_type = "square"
# Create ST7789 LCD display class.
if display_type in ("square", "rect", "round"):
disp = st7789.ST7789(
height=135 if display_type == "rect" else 240,
rotation=0 if display_type == "rect" else 90,
port=0,
cs=st7789.BG_SPI_CS_FRONT, # BG_SPI_CS_BACK or BG_SPI_CS_FRONT
dc=9,
backlight=19, # Breakout Garden: 18 for back slot, 19 for front slot.
# NOTE: Change this to 13 for Pirate Audio boards
spi_speed_hz=80 * 1000 * 1000,
offset_left=0 if display_type == "square" else 40,
offset_top=53 if display_type == "rect" else 0,
)
elif display_type == "dhmini":
disp = st7789.ST7789(
height=240,
width=320,
rotation=180,
port=0,
cs=1,
dc=9,
backlight=13,
spi_speed_hz=60 * 1000 * 1000,
offset_left=0,
offset_top=0,
)
else:
print("Invalid display type!")
# Initialize display.
disp.begin()
WIDTH = disp.width
HEIGHT = disp.height
# Clear the display to a red background.
# Can pass any tuple of red, green, blue values (from 0 to 255 each).
# Get a PIL Draw object to start drawing on the display buffer.
img = Image.new("RGB", (WIDTH, HEIGHT), color=(255, 0, 0))
draw = ImageDraw.Draw(img)
# Draw a purple rectangle with yellow outline.
draw.rectangle(
(10, 10, WIDTH - 10, HEIGHT - 10), outline=(255, 255, 0), fill=(255, 0, 255)
)
# Draw some shapes.
# Draw a blue ellipse with a green outline.
draw.ellipse((10, 10, WIDTH - 10, HEIGHT - 10), outline=(0, 255, 0), fill=(0, 0, 255))
# Draw a white X.
draw.line((10, 10, WIDTH - 10, HEIGHT - 10), fill=(255, 255, 255))
draw.line((10, HEIGHT - 10, WIDTH - 10, 10), fill=(255, 255, 255))
# Draw a cyan triangle with a black outline.
draw.polygon(
[(WIDTH / 2, 10), (WIDTH - 10, HEIGHT - 10), (10, HEIGHT - 10)],
outline=(0, 0, 0),
fill=(0, 255, 255),
)
# Load default font.
font = ImageFont.load_default()
# Alternatively load a TTF font.
# Some other nice fonts to try: http://www.dafont.com/bitmap.php
# font = ImageFont.truetype('Minecraftia.ttf', 16)
# Define a function to create rotated text. Unfortunately PIL doesn't have good
# native support for rotated fonts, but this function can be used to make a
# text image and rotate it so it's easy to paste in the buffer.
def draw_rotated_text(image, text, position, angle, font, fill=(255, 255, 255)):
# Get rendered font width and height.
draw = ImageDraw.Draw(image)
width, height = draw.textsize(text, font=font)
# Create a new image with transparent background to store the text.
textimage = Image.new("RGBA", (width, height), (0, 0, 0, 0))
# Render the text.
textdraw = ImageDraw.Draw(textimage)
textdraw.text((0, 0), text, font=font, fill=fill)
# Rotate the text image.
rotated = textimage.rotate(angle, expand=1)
# Paste the text into the image, using it as a mask for transparency.
image.paste(rotated, position, rotated)
# Write two lines of white text on the buffer, rotated 90 degrees counter clockwise.
draw_rotated_text(img, "Hello World!", (0, 0), 90, font, fill=(255, 255, 255))
draw_rotated_text(
img, "This is a line of text.", (10, HEIGHT - 10), 0, font, fill=(255, 255, 255)
)
# Write buffer to display hardware, must be called to make things visible on the
# display!
disp.display(img)
================================================
FILE: install.sh
================================================
#!/bin/bash
LIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}')
CONFIG_FILE=config.txt
CONFIG_DIR="/boot/firmware"
DATESTAMP=$(date "+%Y-%m-%d-%H-%M-%S")
CONFIG_BACKUP=false
APT_HAS_UPDATED=false
RESOURCES_TOP_DIR="$HOME/Pimoroni"
VENV_BASH_SNIPPET="$RESOURCES_TOP_DIR/auto_venv.sh"
VENV_DIR="$HOME/.virtualenvs/pimoroni"
USAGE="./install.sh (--unstable)"
POSITIONAL_ARGS=()
FORCE=false
UNSTABLE=false
PYTHON="python"
CMD_ERRORS=false
user_check() {
if [ "$(id -u)" -eq 0 ]; then
fatal "Script should not be run as root. Try './install.sh'\n"
fi
}
confirm() {
if $FORCE; then
true
else
read -r -p "$1 [y/N] " response < /dev/tty
if [[ $response =~ ^(yes|y|Y)$ ]]; then
true
else
false
fi
fi
}
success() {
echo -e "$(tput setaf 2)$1$(tput sgr0)"
}
inform() {
echo -e "$(tput setaf 6)$1$(tput sgr0)"
}
warning() {
echo -e "$(tput setaf 1)⚠ WARNING:$(tput sgr0) $1"
}
fatal() {
echo -e "$(tput setaf 1)⚠ FATAL:$(tput sgr0) $1"
exit 1
}
find_config() {
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then
CONFIG_DIR="/boot"
if [ ! -f "$CONFIG_DIR/$CONFIG_FILE" ]; then
fatal "Could not find $CONFIG_FILE!"
fi
fi
inform "Using $CONFIG_FILE in $CONFIG_DIR"
}
venv_bash_snippet() {
inform "Checking for $VENV_BASH_SNIPPET\n"
if [ ! -f "$VENV_BASH_SNIPPET" ]; then
inform "Creating $VENV_BASH_SNIPPET\n"
mkdir -p "$RESOURCES_TOP_DIR"
cat << EOF > "$VENV_BASH_SNIPPET"
# Add "source $VENV_BASH_SNIPPET" to your ~/.bashrc to activate
# the Pimoroni virtual environment automagically!
VENV_DIR="$VENV_DIR"
if [ ! -f \$VENV_DIR/bin/activate ]; then
printf "Creating user Python environment in \$VENV_DIR, please wait...\n"
mkdir -p \$VENV_DIR
python3 -m venv --system-site-packages \$VENV_DIR
fi
printf " ↓ ↓ ↓ ↓ Hello, we've activated a Python venv for you. To exit, type \"deactivate\".\n"
source \$VENV_DIR/bin/activate
EOF
fi
}
venv_check() {
PYTHON_BIN=$(which "$PYTHON")
if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then
printf "This script should be run in a virtual Python environment.\n"
if confirm "Would you like us to create and/or use a default one?"; then
printf "\n"
if [ ! -f "$VENV_DIR/bin/activate" ]; then
inform "Creating a new virtual Python environment in $VENV_DIR, please wait...\n"
mkdir -p "$VENV_DIR"
/usr/bin/python3 -m venv "$VENV_DIR" --system-site-packages
venv_bash_snippet
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
else
inform "Activating existing virtual Python environment in $VENV_DIR\n"
printf "source \"%s/bin/activate\"\n" "$VENV_DIR"
# shellcheck disable=SC1091
source "$VENV_DIR/bin/activate"
fi
else
printf "\n"
fatal "Please create and/or activate a virtual Python environment and try again!\n"
fi
fi
printf "\n"
}
check_for_error() {
if [ $? -ne 0 ]; then
CMD_ERRORS=true
warning "^^^ 😬 previous command did not exit cleanly!"
fi
}
function do_config_backup {
if [ ! $CONFIG_BACKUP == true ]; then
CONFIG_BACKUP=true
FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt"
inform "Backing up $CONFIG_DIR/$CONFIG_FILE to $CONFIG_DIR/$FILENAME\n"
sudo cp "$CONFIG_DIR/$CONFIG_FILE" "$CONFIG_DIR/$FILENAME"
mkdir -p "$RESOURCES_TOP_DIR/config-backups/"
cp $CONFIG_DIR/$CONFIG_FILE "$RESOURCES_TOP_DIR/config-backups/$FILENAME"
if [ -f "$UNINSTALLER" ]; then
echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG_DIR/$CONFIG_FILE" >> "$UNINSTALLER"
fi
fi
}
function apt_pkg_install {
PACKAGES_NEEDED=()
PACKAGES_IN=("$@")
# Check the list of packages and only run update/install if we need to
for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do
PACKAGE="${PACKAGES_IN[$i]}"
if [ "$PACKAGE" == "" ]; then continue; fi
printf "Checking for %s\n" "$PACKAGE"
dpkg -L "$PACKAGE" > /dev/null 2>&1
if [ "$?" == "1" ]; then
PACKAGES_NEEDED+=("$PACKAGE")
fi
done
PACKAGES="${PACKAGES_NEEDED[*]}"
if ! [ "$PACKAGES" == "" ]; then
printf "\n"
inform "Installing missing packages: $PACKAGES"
if [ ! $APT_HAS_UPDATED ]; then
sudo apt update
APT_HAS_UPDATED=true
fi
# shellcheck disable=SC2086
sudo apt install -y $PACKAGES
check_for_error
if [ -f "$UNINSTALLER" ]; then
echo "apt uninstall -y $PACKAGES" >> "$UNINSTALLER"
fi
fi
}
function pip_pkg_install {
# A null Keyring prevents pip stalling in the background
PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring $PYTHON -m pip install --upgrade "$@"
check_for_error
}
while [[ $# -gt 0 ]]; do
K="$1"
case $K in
-u|--unstable)
UNSTABLE=true
shift
;;
-f|--force)
FORCE=true
shift
;;
-p|--python)
PYTHON=$2
shift
shift
;;
*)
if [[ $1 == -* ]]; then
printf "Unrecognised option: %s\n" "$1";
printf "Usage: %s\n" "$USAGE";
exit 1
fi
POSITIONAL_ARGS+=("$1")
shift
esac
done
printf "Installing %s...\n\n" "$LIBRARY_NAME"
user_check
venv_check
if [ ! -f "$(which "$PYTHON")" ]; then
fatal "Python path %s not found!\n" "$PYTHON"
fi
PYTHON_VER=$($PYTHON --version)
inform "Checking Dependencies. Please wait..."
# Install toml and try to read pyproject.toml into bash variables
pip_pkg_install toml
CONFIG_VARS=$(
$PYTHON - <<EOF
import toml
config = toml.load("pyproject.toml")
github_url = config['project']['urls']['GitHub']
p = dict(config['tool']['pimoroni'])
# Convert list config entries into bash arrays
for k, v in p.items():
v = "'\n\t'".join(v)
p[k] = f"('{v}')"
print(f'GITHUB_URL="{github_url}"')
print("""
APT_PACKAGES={apt_packages}
SETUP_CMDS={commands}
CONFIG_TXT={configtxt}
""".format(**p))
EOF
)
# shellcheck disable=SC2181 # Inlining the above command would be messy
if [ $? -ne 0 ]; then
# This is bad, this should not happen in production!
fatal "Error parsing configuration...\n"
fi
eval "$CONFIG_VARS"
RESOURCES_DIR=$RESOURCES_TOP_DIR/$LIBRARY_NAME
UNINSTALLER=$RESOURCES_DIR/uninstall.sh
RES_DIR_OWNER=$(stat -c "%U" "$RESOURCES_TOP_DIR")
# Previous install.sh scripts were run as root with sudo, which caused
# the ~/Pimoroni dir to be created with root ownership. Try and fix it.
if [[ "$RES_DIR_OWNER" == "root" ]]; then
warning "\n\nFixing $RESOURCES_TOP_DIR permissions!\n\n"
sudo chown -R "$USER:$USER" "$RESOURCES_TOP_DIR"
fi
mkdir -p "$RESOURCES_DIR"
# Create a stub uninstaller file, we'll try to add the inverse of every
# install command run to here, though it's not complete.
cat << EOF > "$UNINSTALLER"
printf "It's recommended you run these steps manually.\n"
printf "If you want to run the full script, open it in\n"
printf "an editor and remove 'exit 1' from below.\n"
exit 1
source $VIRTUAL_ENV/bin/activate
EOF
printf "\n"
inform "Installing for $PYTHON_VER...\n"
# Install apt packages from pyproject.toml / tool.pimoroni.apt_packages
apt_pkg_install "${APT_PACKAGES[@]}"
printf "\n"
if $UNSTABLE; then
warning "Installing unstable library from source.\n"
pip_pkg_install .
else
inform "Installing stable library from pypi.\n"
pip_pkg_install "$LIBRARY_NAME"
fi
# shellcheck disable=SC2181 # One of two commands run, depending on --unstable flag
if [ $? -eq 0 ]; then
success "Done!\n"
echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> "$UNINSTALLER"
fi
find_config
printf "\n"
# Run the setup commands from pyproject.toml / tool.pimoroni.commands
inform "Running setup commands...\n"
for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do
CMD="${SETUP_CMDS[$i]}"
# Attempt to catch anything that touches config.txt and trigger a backup
if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG_DIR/$CONFIG_FILE"* ]] || [[ "$CMD" == *"\$CONFIG_DIR/\$CONFIG_FILE"* ]]; then
do_config_backup
fi
if [[ ! "$CMD" == printf* ]]; then
printf "Running: \"%s\"\n" "$CMD"
fi
eval "$CMD"
check_for_error
done
printf "\n"
# Add the config.txt entries from pyproject.toml / tool.pimoroni.configtxt
for ((i = 0; i < ${#CONFIG_TXT[@]}; i++)); do
CONFIG_LINE="${CONFIG_TXT[$i]}"
if ! [ "$CONFIG_LINE" == "" ]; then
do_config_backup
inform "Adding $CONFIG_LINE to $CONFIG_DIR/$CONFIG_FILE"
sudo sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG_DIR/$CONFIG_FILE
if ! grep -q "^$CONFIG_LINE" $CONFIG_DIR/$CONFIG_FILE; then
printf "%s \n" "$CONFIG_LINE" | sudo tee --append $CONFIG_DIR/$CONFIG_FILE
fi
fi
done
printf "\n"
# Just a straight copy of the examples/ dir into ~/Pimoroni/board/examples
if [ -d "examples" ]; then
if confirm "Would you like to copy examples to $RESOURCES_DIR?"; then
inform "Copying examples to $RESOURCES_DIR"
cp -r examples/ "$RESOURCES_DIR"
echo "rm -r $RESOURCES_DIR" >> "$UNINSTALLER"
success "Done!"
fi
fi
printf "\n"
# Use pdoc to generate basic documentation from the installed module
if confirm "Would you like to generate documentation?"; then
inform "Installing pdoc. Please wait..."
pip_pkg_install pdoc
inform "Generating documentation.\n"
if $PYTHON -m pdoc "$LIBRARY_NAME" -o "$RESOURCES_DIR/docs" > /dev/null; then
inform "Documentation saved to $RESOURCES_DIR/docs"
success "Done!"
else
warning "Error: Failed to generate documentation."
fi
fi
printf "\n"
if [ "$CMD_ERRORS" = true ]; then
warning "One or more setup commands appear to have failed."
printf "This might prevent things from working properly.\n"
printf "Make sure your OS is up to date and try re-running this installer.\n"
printf "If things still don't work, report this or find help at %s.\n\n" "$GITHUB_URL"
else
success "\nAll done!"
fi
printf "If this is your first time installing you should reboot for hardware changes to take effect.\n"
printf "Find uninstall steps in %s\n\n" "$UNINSTALLER"
if [ "$CMD_ERRORS" = true ]; then
exit 1
else
exit 0
fi
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["hatchling", "hatch-fancy-pypi-readme"]
build-backend = "hatchling.build"
[project]
name = "st7789"
dynamic = ["version", "readme"]
description = "Driver for ST7789-based TFT LCD displays."
license = {file = "LICENSE"}
requires-python = ">= 3.7"
authors = [
{ name = "Philip Howard", email = "phil@pimoroni.com" },
]
maintainers = [
{ name = "Philip Howard", email = "phil@pimoroni.com" },
]
keywords = [
"Pi",
"Raspberry",
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX :: Linux",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Software Development",
"Topic :: Software Development :: Libraries",
"Topic :: System :: Hardware",
]
dependencies = [
"gpiod",
"gpiodevice >= 0.0.4",
"numpy >= 1.26.4",
"spidev >= 3.6"
]
[project.urls]
GitHub = "https://www.github.com/pimoroni/st7789-python"
Homepage = "https://www.pimoroni.com"
[tool.hatch.version]
path = "st7789/__init__.py"
[tool.hatch.build]
include = [
"st7789",
"ST7789.py",
"README.md",
"CHANGELOG.md",
"LICENSE"
]
[tool.hatch.build.targets.sdist]
include = [
"*"
]
exclude = [
".*",
"dist"
]
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
fragments = [
{ path = "README.md" },
{ text = "\n" },
{ path = "CHANGELOG.md" }
]
[tool.ruff]
exclude = [
'.tox',
'.egg',
'.git',
'__pycache__',
'build',
'dist'
]
line-length = 200
[tool.codespell]
skip = """
./.tox,\
./.egg,\
./.git,\
./__pycache__,\
./build,\
./dist.\
"""
[tool.isort]
line_length = 200
[tool.check-manifest]
ignore = [
'.stickler.yml',
'boilerplate.md',
'check.sh',
'install.sh',
'uninstall.sh',
'Makefile',
'tox.ini',
'tests/*',
'examples/*',
'.coveragerc',
'requirements-dev.txt'
]
[tool.pimoroni]
apt_packages = []
configtxt = []
commands = []
================================================
FILE: requirements-dev.txt
================================================
check-manifest
ruff
codespell
isort
twine
hatch
hatch-fancy-pypi-readme
tox
pdoc
================================================
FILE: st7789/__init__.py
================================================
# Copyright (c) 2014 Adafruit Industries
# Author: Tony DiCola
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import numbers
import time
import gpiod
import gpiodevice
import numpy
import spidev
from gpiod.line import Direction, Value
__version__ = "1.0.1"
OUTL = gpiod.LineSettings(direction=Direction.OUTPUT, output_value=Value.INACTIVE)
BG_SPI_CS_BACK = 0
BG_SPI_CS_FRONT = 1
SPI_CLOCK_HZ = 16000000
ST7789_NOP = 0x00
ST7789_SWRESET = 0x01
ST7789_RDDID = 0x04
ST7789_RDDST = 0x09
ST7789_SLPIN = 0x10
ST7789_SLPOUT = 0x11
ST7789_PTLON = 0x12
ST7789_NORON = 0x13
ST7789_INVOFF = 0x20
ST7789_INVON = 0x21
ST7789_DISPOFF = 0x28
ST7789_DISPON = 0x29
ST7789_CASET = 0x2A
ST7789_RASET = 0x2B
ST7789_RAMWR = 0x2C
ST7789_RAMRD = 0x2E
ST7789_PTLAR = 0x30
ST7789_MADCTL = 0x36
ST7789_COLMOD = 0x3A
ST7789_FRMCTR1 = 0xB1
ST7789_FRMCTR2 = 0xB2
ST7789_FRMCTR3 = 0xB3
ST7789_INVCTR = 0xB4
ST7789_DISSET5 = 0xB6
ST7789_GCTRL = 0xB7
ST7789_GTADJ = 0xB8
ST7789_VCOMS = 0xBB
ST7789_LCMCTRL = 0xC0
ST7789_IDSET = 0xC1
ST7789_VDVVRHEN = 0xC2
ST7789_VRHS = 0xC3
ST7789_VDVS = 0xC4
ST7789_VMCTR1 = 0xC5
ST7789_FRCTRL2 = 0xC6
ST7789_CABCCTRL = 0xC7
ST7789_RDID1 = 0xDA
ST7789_RDID2 = 0xDB
ST7789_RDID3 = 0xDC
ST7789_RDID4 = 0xDD
ST7789_GMCTRP1 = 0xE0
ST7789_GMCTRN1 = 0xE1
ST7789_PWCTR6 = 0xFC
class ST7789(object):
"""Representation of an ST7789 TFT LCD."""
def __init__(
self,
port,
cs,
dc,
backlight=None,
rst=None,
width=240,
height=240,
rotation=90,
invert=True,
spi_speed_hz=4000000,
offset_left=0,
offset_top=0,
):
"""Create an instance of the display using SPI communication.
Must provide the GPIO pin number for the D/C pin and the SPI driver.
Can optionally provide the GPIO pin number for the reset pin as the rst parameter.
:param port: SPI port number
:param cs: SPI chip-select number (0 or 1 for BCM
:param backlight: Pin for controlling backlight
:param rst: Reset pin for ST7789
:param width: Width of display connected to ST7789
:param height: Height of display connected to ST7789
:param rotation: Rotation of display connected to ST7789
:param invert: Invert display
:param spi_speed_hz: SPI speed (in Hz)
"""
if rotation not in [0, 90, 180, 270]:
raise ValueError(f"Invalid rotation {rotation}")
if width != height and rotation in [90, 270]:
raise ValueError(
f"Invalid rotation {rotation} for {width}x{height} resolution"
)
gpiodevice.friendly_errors = True
self._spi = spidev.SpiDev(port, cs)
self._spi.mode = 0
self._spi.lsbfirst = False
self._spi.max_speed_hz = spi_speed_hz
self._dc = dc
self._rst = rst
self._width = width
self._height = height
self._rotation = rotation
self._invert = invert
self._offset_left = offset_left
self._offset_top = offset_top
# Set up DC pin if a lines/offset tuple is not supplied
if isinstance(dc, int):
self._dc = ST7789.get_dc_pin(dc)
# Setup backlight as output (if provided).
if backlight is not None:
if isinstance(backlight, int):
self._bl = ST7789.get_bl_pin(backlight)
else:
self._bl = backlight
self.set_pin(self._bl, False)
time.sleep(0.1)
self.set_pin(self._bl, True)
# Set up and call reset (if provided)
if rst is not None:
# Set up RESET pin if a lines/offset tuple is not supplied
if isinstance(rst, int):
self._rst = ST7789.get_rst_pin(rst)
self.reset()
self._init()
def set_pin(self, pin, state):
lines, offset = pin
lines.set_value(offset, Value.ACTIVE if state else Value.INACTIVE)
def send(self, data, is_data=True, chunk_size=4096):
"""Write a byte or array of bytes to the display. Is_data parameter
controls if byte should be interpreted as display data (True) or command
data (False). Chunk_size is an optional size of bytes to write in a
single SPI transaction, with a default of 4096.
"""
# Set DC low for command, high for data.
self.set_pin(self._dc, is_data)
# Convert scalar argument to list so either can be passed as parameter.
if isinstance(data, numbers.Number):
data = [data & 0xFF]
# Write data a chunk at a time.
for start in range(0, len(data), chunk_size):
end = min(start + chunk_size, len(data))
self._spi.xfer(data[start:end])
def set_backlight(self, value):
"""Set the backlight on/off."""
if self._bl is not None:
self.set_pin(self._bl, value)
@property
def width(self):
return (
self._width
if self._rotation == 0 or self._rotation == 180
else self._height
)
@property
def height(self):
return (
self._height
if self._rotation == 0 or self._rotation == 180
else self._width
)
def command(self, data):
"""Write a byte or array of bytes to the display as command data."""
self.send(data, False)
def data(self, data):
"""Write a byte or array of bytes to the display as display data."""
self.send(data, True)
def reset(self):
"""Reset the display, if reset pin is connected."""
if self._rst is not None:
self.set_pin(self._rst, True)
time.sleep(0.500)
self.set_pin(self._rst, False)
time.sleep(0.500)
self.set_pin(self._rst, True)
time.sleep(0.500)
def _init(self):
# Initialize the display.
self.command(ST7789_SWRESET) # Software reset
time.sleep(0.150) # delay 150 ms
self.command(ST7789_MADCTL)
self.data(0x70)
self.command(ST7789_FRMCTR2) # Frame rate ctrl - idle mode
self.data(0x0C)
self.data(0x0C)
self.data(0x00)
self.data(0x33)
self.data(0x33)
self.command(ST7789_COLMOD)
self.data(0x05)
self.command(ST7789_GCTRL)
self.data(0x14)
self.command(ST7789_VCOMS)
self.data(0x37)
self.command(ST7789_LCMCTRL) # Power control
self.data(0x2C)
self.command(ST7789_VDVVRHEN) # Power control
self.data(0x01)
self.command(ST7789_VRHS) # Power control
self.data(0x12)
self.command(ST7789_VDVS) # Power control
self.data(0x20)
self.command(0xD0)
self.data(0xA4)
self.data(0xA1)
self.command(ST7789_FRCTRL2)
self.data(0x0F)
self.command(ST7789_GMCTRP1) # Set Gamma
self.data(0xD0)
self.data(0x04)
self.data(0x0D)
self.data(0x11)
self.data(0x13)
self.data(0x2B)
self.data(0x3F)
self.data(0x54)
self.data(0x4C)
self.data(0x18)
self.data(0x0D)
self.data(0x0B)
self.data(0x1F)
self.data(0x23)
self.command(ST7789_GMCTRN1) # Set Gamma
self.data(0xD0)
self.data(0x04)
self.data(0x0C)
self.data(0x11)
self.data(0x13)
self.data(0x2C)
self.data(0x3F)
self.data(0x44)
self.data(0x51)
self.data(0x2F)
self.data(0x1F)
self.data(0x1F)
self.data(0x20)
self.data(0x23)
if self._invert:
self.command(ST7789_INVON) # Invert display
else:
self.command(ST7789_INVOFF) # Don't invert display
self.command(ST7789_SLPOUT)
self.command(ST7789_DISPON) # Display on
time.sleep(0.100) # 100 ms
def begin(self):
"""Set up the display
Deprecated. Included in __init__.
"""
pass
def set_window(self, x0=0, y0=0, x1=None, y1=None):
"""Set the pixel address window for proceeding drawing commands. x0 and
x1 should define the minimum and maximum x pixel bounds. y0 and y1
should define the minimum and maximum y pixel bound. If no parameters
are specified the default will be to update the entire display from 0,0
to width-1,height-1.
"""
if x1 is None:
x1 = self._width - 1
if y1 is None:
y1 = self._height - 1
y0 += self._offset_top
y1 += self._offset_top
x0 += self._offset_left
x1 += self._offset_left
self.command(ST7789_CASET) # Column addr set
self.data(x0 >> 8)
self.data(x0 & 0xFF) # XSTART
self.data(x1 >> 8)
self.data(x1 & 0xFF) # XEND
self.command(ST7789_RASET) # Row addr set
self.data(y0 >> 8)
self.data(y0 & 0xFF) # YSTART
self.data(y1 >> 8)
self.data(y1 & 0xFF) # YEND
self.command(ST7789_RAMWR) # write to RAM
def display(self, image):
"""Write the provided image to the hardware.
:param image: Should be RGB format and the same dimensions as the display hardware.
"""
# Set address bounds to entire display.
self.set_window()
# Convert image to 16bit RGB565 format and
# flatten into bytes.
pixelbytes = self.image_to_data(image, self._rotation)
# Write data to hardware.
for i in range(0, len(pixelbytes), 4096):
self.data(pixelbytes[i : i + 4096])
def image_to_data(self, image, rotation=0):
if not isinstance(image, numpy.ndarray):
image = numpy.array(image.convert("RGB"))
# Rotate the image
pb = numpy.rot90(image, rotation // 90).astype("uint16")
# Mask and shift the 888 RGB into 565 RGB
red = (pb[..., [0]] & 0xF8) << 8
green = (pb[..., [1]] & 0xFC) << 3
blue = (pb[..., [2]] & 0xF8) >> 3
# Stick 'em together
result = red | green | blue
# Output the raw bytes
return result.byteswap().tobytes()
@staticmethod
def get_bl_pin(pin):
return gpiodevice.get_pin(pin, "st7789-bl", OUTL)
@staticmethod
def get_rst_pin(pin):
return gpiodevice.get_pin(pin, "st7789-rst", OUTL)
@staticmethod
def get_dc_pin(pin):
return gpiodevice.get_pin(pin, "st7789-dc", OUTL)
================================================
FILE: tests/conftest.py
================================================
import sys
import mock
import pytest
@pytest.fixture(scope="function", autouse=False)
def st7789():
import st7789
yield st7789
del sys.modules["st7789"]
@pytest.fixture(scope="function", autouse=False)
def gpiod():
"""Mock gpiod module."""
sys.modules["gpiod"] = mock.MagicMock()
sys.modules["gpiod.line"] = mock.MagicMock()
yield sys.modules["gpiod"]
del sys.modules["gpiod"]
@pytest.fixture(scope="function", autouse=False)
def gpiodevice():
"""Mock gpiodevice module."""
sys.modules["gpiodevice"] = mock.MagicMock()
sys.modules["gpiodevice"].get_pin.return_value = (mock.Mock(), 0)
yield sys.modules["gpiodevice"]
del sys.modules["gpiodevice"]
@pytest.fixture(scope="function", autouse=False)
def spidev():
"""Mock spidev module."""
spidev = mock.MagicMock()
sys.modules["spidev"] = spidev
yield spidev
del sys.modules["spidev"]
@pytest.fixture(scope="function", autouse=False)
def numpy():
"""Mock numpy module."""
numpy = mock.MagicMock()
sys.modules["numpy"] = numpy
yield numpy
del sys.modules["numpy"]
================================================
FILE: tests/test_dimensions.py
================================================
def test_240_240(gpiodevice, gpiod, spidev, numpy, st7789):
display = st7789.ST7789(port=0, cs=0, dc=24, width=240, height=240, rotation=0)
assert display.width == 240
assert display.height == 240
def test_240_135(gpiodevice, gpiod, spidev, numpy, st7789):
display = st7789.ST7789(port=0, cs=0, dc=24, width=240, height=135, rotation=0)
assert display.width == 240
assert display.height == 135
def test_320_240(gpiodevice, gpiod, spidev, numpy, st7789):
display = st7789.ST7789(port=0, cs=0, dc=24, width=320, height=240, rotation=0)
assert display.width == 320
assert display.height == 240
================================================
FILE: tests/test_display.py
================================================
def test_display_pil_image(gpiodevice, gpiod, spidev, st7789):
from PIL import Image
display = st7789.ST7789(port=0, cs=0, dc=24)
image = Image.new("RGB", (display.width, display.width))
display.display(image)
def test_display_numpy_array(gpiodevice, gpiod, spidev, st7789):
import numpy
display = st7789.ST7789(port=0, cs=0, dc=24)
image = numpy.empty((display.width, display.height, 3))
display.display(image)
================================================
FILE: tests/test_setup.py
================================================
import pytest
def test_setup(gpiodevice, gpiod, spidev, numpy, st7789):
display = st7789.ST7789(port=0, cs=0, dc=24)
del display
def test_backlight(gpiodevice, gpiod, spidev, numpy, st7789):
display = st7789.ST7789(port=0, cs=0, dc=24, backlight=19)
display.set_backlight(1)
def test_reset(gpiodevice, gpiod, spidev, numpy, st7789):
display = st7789.ST7789(port=0, cs=0, dc=24, rst=19)
display.reset()
def test_unsupported_rotation_320_x_240_90(gpiodevice, gpiod, spidev, numpy, st7789):
with pytest.raises(ValueError):
display = st7789.ST7789(port=0, cs=0, dc=24, width=320, height=240, rotation=90)
del display
def test_unsupported_rotation_320_x_240_270(gpiodevice, gpiod, spidev, numpy, st7789):
with pytest.raises(ValueError):
display = st7789.ST7789(
port=0, cs=0, dc=24, width=320, height=240, rotation=270
)
del display
================================================
FILE: tox.ini
================================================
[tox]
envlist = py,qa
skip_missing_interpreters = True
isolated_build = true
minversion = 4.0.0
[testenv]
commands =
coverage run -m pytest -v -r wsx
coverage report
deps =
mock
pytest>=3.1
pytest-cov
build
numpy
pillow
[testenv:qa]
commands =
check-manifest
python -m build --no-isolation
python -m twine check dist/*
isort --check .
ruff check .
codespell .
deps =
check-manifest
ruff
codespell
isort
twine
build
hatch
hatch-fancy-pypi-readme
================================================
FILE: uninstall.sh
================================================
#!/bin/bash
FORCE=false
LIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F" = " '{print substr($2,2,length($2)-2)}')
RESOURCES_DIR=$HOME/Pimoroni/$LIBRARY_NAME
PYTHON="python"
venv_check() {
PYTHON_BIN=$(which $PYTHON)
if [[ $VIRTUAL_ENV == "" ]] || [[ $PYTHON_BIN != $VIRTUAL_ENV* ]]; then
printf "This script should be run in a virtual Python environment.\n"
exit 1
fi
}
user_check() {
if [ "$(id -u)" -eq 0 ]; then
printf "Script should not be run as root. Try './uninstall.sh'\n"
exit 1
fi
}
confirm() {
if $FORCE; then
true
else
read -r -p "$1 [y/N] " response < /dev/tty
if [[ $response =~ ^(yes|y|Y)$ ]]; then
true
else
false
fi
fi
}
prompt() {
read -r -p "$1 [y/N] " response < /dev/tty
if [[ $response =~ ^(yes|y|Y)$ ]]; then
true
else
false
fi
}
success() {
echo -e "$(tput setaf 2)$1$(tput sgr0)"
}
inform() {
echo -e "$(tput setaf 6)$1$(tput sgr0)"
}
warning() {
echo -e "$(tput setaf 1)$1$(tput sgr0)"
}
printf "%s Python Library: Uninstaller\n\n" "$LIBRARY_NAME"
user_check
venv_check
printf "Uninstalling for Python 3...\n"
$PYTHON -m pip uninstall "$LIBRARY_NAME"
if [ -d "$RESOURCES_DIR" ]; then
if confirm "Would you like to delete $RESOURCES_DIR?"; then
rm -r "$RESOURCES_DIR"
fi
fi
printf "Done!\n"
gitextract_u_1k7z4_/ ├── .coveragerc ├── .github/ │ ├── ISSUE_TEMPLATE.md │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ ├── build.yml │ ├── qa.yml │ └── test.yml ├── .gitignore ├── .stickler.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── ST7789.py ├── check.sh ├── examples/ │ ├── 320x240.py │ ├── LICENSE.txt │ ├── framerate.py │ ├── gif.py │ ├── image.py │ ├── round.py │ ├── scrolling-text.py │ └── shapes.py ├── install.sh ├── pyproject.toml ├── requirements-dev.txt ├── st7789/ │ └── __init__.py ├── tests/ │ ├── conftest.py │ ├── test_dimensions.py │ ├── test_display.py │ └── test_setup.py ├── tox.ini └── uninstall.sh
SYMBOL INDEX (34 symbols across 6 files)
FILE: examples/shapes.py
function draw_rotated_text (line 109) | def draw_rotated_text(image, text, position, angle, font, fill=(255, 255...
FILE: st7789/__init__.py
class ST7789 (line 93) | class ST7789(object):
method __init__ (line 96) | def __init__(
method set_pin (line 176) | def set_pin(self, pin, state):
method send (line 180) | def send(self, data, is_data=True, chunk_size=4096):
method set_backlight (line 196) | def set_backlight(self, value):
method width (line 202) | def width(self):
method height (line 210) | def height(self):
method command (line 217) | def command(self, data):
method data (line 221) | def data(self, data):
method reset (line 225) | def reset(self):
method _init (line 235) | def _init(self):
method begin (line 321) | def begin(self):
method set_window (line 329) | def set_window(self, x0=0, y0=0, x1=None, y1=None):
method display (line 360) | def display(self, image):
method image_to_data (line 377) | def image_to_data(self, image, rotation=0):
method get_bl_pin (line 396) | def get_bl_pin(pin):
method get_rst_pin (line 400) | def get_rst_pin(pin):
method get_dc_pin (line 404) | def get_dc_pin(pin):
FILE: tests/conftest.py
function st7789 (line 8) | def st7789():
function gpiod (line 16) | def gpiod():
function gpiodevice (line 25) | def gpiodevice():
function spidev (line 34) | def spidev():
function numpy (line 43) | def numpy():
FILE: tests/test_dimensions.py
function test_240_240 (line 1) | def test_240_240(gpiodevice, gpiod, spidev, numpy, st7789):
function test_240_135 (line 7) | def test_240_135(gpiodevice, gpiod, spidev, numpy, st7789):
function test_320_240 (line 13) | def test_320_240(gpiodevice, gpiod, spidev, numpy, st7789):
FILE: tests/test_display.py
function test_display_pil_image (line 1) | def test_display_pil_image(gpiodevice, gpiod, spidev, st7789):
function test_display_numpy_array (line 10) | def test_display_numpy_array(gpiodevice, gpiod, spidev, st7789):
FILE: tests/test_setup.py
function test_setup (line 4) | def test_setup(gpiodevice, gpiod, spidev, numpy, st7789):
function test_backlight (line 9) | def test_backlight(gpiodevice, gpiod, spidev, numpy, st7789):
function test_reset (line 14) | def test_reset(gpiodevice, gpiod, spidev, numpy, st7789):
function test_unsupported_rotation_320_x_240_90 (line 19) | def test_unsupported_rotation_320_x_240_90(gpiodevice, gpiod, spidev, nu...
function test_unsupported_rotation_320_x_240_270 (line 25) | def test_unsupported_rotation_320_x_240_270(gpiodevice, gpiod, spidev, n...
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (66K chars).
[
{
"path": ".coveragerc",
"chars": 40,
"preview": "[run]\nsource = st7789\nomit =\n .tox/*\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 2666,
"preview": "Thank you for opening an issue on an Pimoroni Python library repository. To\nimprove the speed of resolution please revi"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 1540,
"preview": "Thank you for creating a pull request to contribute to Pimoroni's GitHub code!\nBefore you open the request please review"
},
{
"path": ".github/workflows/build.yml",
"chars": 868,
"preview": "name: Build\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n test:\n name: Python ${{ matrix.python }}"
},
{
"path": ".github/workflows/qa.yml",
"chars": 656,
"preview": "name: QA\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n test:\n name: linting & spelling\n runs-on"
},
{
"path": ".github/workflows/test.yml",
"chars": 802,
"preview": "name: Tests\n\non:\n pull_request:\n push:\n branches:\n - main\n\njobs:\n test:\n name: Python ${{ matrix.python }}"
},
{
"path": ".gitignore",
"chars": 179,
"preview": "build/\n_build/\n*.o\n*.so\n*.a\n*.py[cod]\n*.egg-info\ndist/\n__pycache__\n.DS_Store\n*.deb\n*.dsc\n*.build\n*.changes\n*.orig.*\npack"
},
{
"path": ".stickler.yml",
"chars": 72,
"preview": "---\nlinters:\n flake8:\n python: 3\n max-line-length: 160\n"
},
{
"path": "CHANGELOG.md",
"chars": 510,
"preview": "1.0.1\n-----\n\n* Add spidev and numpy dependencies.\n\n1.0.0\n-----\n\n* Repackage to hatch/pyproject.toml\n* Port to gpiod/gpio"
},
{
"path": "LICENSE",
"chars": 1086,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2014 Adafruit Industries\n\nPermission is hereby granted, free of charge, to any pers"
},
{
"path": "Makefile",
"chars": 1615,
"preview": "LIBRARY_NAME := $(shell hatch project metadata name 2> /dev/null)\nLIBRARY_VERSION := $(shell hatch version 2> /dev/null)"
},
{
"path": "README.md",
"chars": 2776,
"preview": "# Python ST7789\n\n[-2)}')\nCONFIG_FILE="
},
{
"path": "pyproject.toml",
"chars": 2291,
"preview": "[build-system]\nrequires = [\"hatchling\", \"hatch-fancy-pypi-readme\"]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \""
},
{
"path": "requirements-dev.txt",
"chars": 81,
"preview": "check-manifest\nruff\ncodespell\nisort\ntwine\nhatch\nhatch-fancy-pypi-readme\ntox\npdoc\n"
},
{
"path": "st7789/__init__.py",
"chars": 11614,
"preview": "# Copyright (c) 2014 Adafruit Industries\n# Author: Tony DiCola\n#\n# Permission is hereby granted, free of charge, to any "
},
{
"path": "tests/conftest.py",
"chars": 1114,
"preview": "import sys\n\nimport mock\nimport pytest\n\n\n@pytest.fixture(scope=\"function\", autouse=False)\ndef st7789():\n import st7789"
},
{
"path": "tests/test_dimensions.py",
"chars": 631,
"preview": "def test_240_240(gpiodevice, gpiod, spidev, numpy, st7789):\n display = st7789.ST7789(port=0, cs=0, dc=24, width=240, "
},
{
"path": "tests/test_display.py",
"chars": 450,
"preview": "def test_display_pil_image(gpiodevice, gpiod, spidev, st7789):\n from PIL import Image\n\n display = st7789.ST7789(po"
},
{
"path": "tests/test_setup.py",
"chars": 922,
"preview": "import pytest\n\n\ndef test_setup(gpiodevice, gpiod, spidev, numpy, st7789):\n display = st7789.ST7789(port=0, cs=0, dc=2"
},
{
"path": "tox.ini",
"chars": 470,
"preview": "[tox]\nenvlist = py,qa\nskip_missing_interpreters = True\nisolated_build = true\nminversion = 4.0.0\n\n[testenv]\ncommands =\n\tc"
},
{
"path": "uninstall.sh",
"chars": 1283,
"preview": "#!/bin/bash\n\nFORCE=false\nLIBRARY_NAME=$(grep -m 1 name pyproject.toml | awk -F\" = \" '{print substr($2,2,length($2)-2)}')"
}
]
About this extraction
This page contains the full source code of the pimoroni/st7789-python GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (59.2 KB), approximately 18.3k tokens, and a symbol index with 34 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.