Files
drunkendotfiles/vendor/breakout-garden/examples/spirit-level/spirit-level.py
dissimulo 030172f523 Initial backup of LTP-305G matrix clock setup on matrixpi
Captures everything needed to redeploy the two-display clock (hour on I2C
0x61, minute on I2C 0x63) on a fresh Pi:

- Both systemd units (matrix0x61.service, matrix0x63.service)
- Deployed Pimoroni script tree, including the local %I (12-hour) clock
  customization
- Vendored upstream sources (ltp305-python, breakout-garden) so restore is
  fully offline-capable
- Boot config snippet enabling I2C
- install.sh that wires it all back up idempotently
- Inventory doc cross-referencing every live-system path

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 01:32:39 -07:00

108 lines
2.8 KiB
Python
Executable File

#!/usr/bin/env python3
from PIL import Image, ImageDraw, ImageFont, ImageFilter
import math
import msa301
import ST7789 as ST7789
print("""This Pimoroni Breakout Garden example requires an
MSA301 3DoF Motion Sensor Breakout and a 1.3" LCD Breakout.
This examples emulates a circular spirit level, using the
LCD to draw the spirit level and the accelerometer to
detect orientation.
Press Ctrl+C to exit.
""")
# Set up LCD
disp = ST7789.ST7789(
port=0,
cs=ST7789.BG_SPI_CS_FRONT,
dc=9,
backlight=19,
spi_speed_hz=80 * 1000 * 1000
)
WIDTH = disp.width
HEIGHT = disp.height
disp.begin()
# Load the image assets
level = Image.open("images/spirit-level.png").convert("RGBA")
bubble = Image.open("images/spirit-level-bubble.png").convert("RGBA")
crosshair_black = Image.open("images/spirit-level-crosshair-black.png").convert("RGBA")
crosshair_red = Image.open("images/spirit-level-crosshair-red.png").convert("RGBA")
# Sizes/coordinates of things
bubble_dia = 64
bubble_rad = bubble_dia / 2
circle_dia = 190
circle_rad = circle_dia / 2
border = (WIDTH - circle_dia) / 2
centre_x = WIDTH / 2
centre_y = HEIGHT / 2
# Set up MSA301
accel = msa301.MSA301()
accel.set_power_mode('normal')
x_vals = []
y_vals = []
smooth = 3
while True:
# Read MSA301 values
x, y, z = accel.get_measurements()
# z = x-axis, y = y-axis
bubble_centre_x = (2 - (z + 1)) * (circle_dia / 2) + border
bubble_centre_y = (y + 1) * (circle_dia / 2) + border
# Work out vector length to check if outside circle
delta_x = bubble_centre_x - centre_x
delta_y = bubble_centre_y - centre_y
vector_length = math.sqrt(delta_x ** 2 + delta_y ** 2)
# If outside circle, scale position back down relatively
if vector_length > circle_rad - bubble_rad:
scale = (circle_rad - bubble_rad) / vector_length
bubble_centre_x = centre_x + delta_x * scale
bubble_centre_y = centre_y + delta_y * scale
# Average x and y values to smooth jitter
x_vals.append(bubble_centre_x)
if len(x_vals) > smooth:
x_vals = x_vals[1:]
bubble_centre_x = int(sum(x_vals) / len(x_vals))
y_vals.append(bubble_centre_y)
if len(y_vals) > smooth:
y_vals = y_vals[1:]
bubble_centre_y = int(sum(y_vals) / len(y_vals))
# Use red crosshair if bubble is close to centre
if (-0.05 < z < 0.05) and (-0.05 < y < 0.05):
crosshair = crosshair_red
else:
crosshair = crosshair_black
# Construct image
image = Image.new('RGBA', (WIDTH, HEIGHT), color=(0, 0, 0, 0))
image.paste(level, (0, 0))
image.paste(bubble, (int(bubble_centre_x - (bubble_dia / 2)), int(bubble_centre_y - (bubble_dia / 2))), mask=bubble)
image.paste(crosshair, (0, 0), mask=crosshair)
# Display on LCD
disp.display(image)