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>
This commit is contained in:
dissimulo
2026-05-06 01:32:39 -07:00
commit 030172f523
99 changed files with 4445 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
# Spirit level example
This examples emulates a circular spirit level, using the LCD to draw
the spirit level and the 3DoF motion sensor to detect orientation.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [MSA301 3DoF Motion Sensor Breakout](https://shop.pimoroni.com/products/msa301-3dof-motion-sensor-breakout)
- A Pimoroni [1.3" LCD Breakout](https://shop.pimoroni.com/products/1-3-spi-colour-lcd-240x240-breakout)
## Installation
Pop the breakouts into your Breakout Garden, and then run the `install.sh`
script in the root of this repository with `sudo ./install.sh` to automagically
install the libraries to run the I2C breakouts.
For this example you'll need to make sure some additional software is installed:
```
sudo apt install python3-pil
```
You'll need to clone and install the library for the 1.3" LCD Breakout
as follows:
```
git clone https://github.com/pimoroni/st7789-python
cd library
sudo python3 setup.py install
```
This example assumes that you have the LCD plugged into the front slot on the
Breakout Garden HAT, which should also work with the Breakout Garden Mini HAT.
To change it to the back slot, change `cs=ST7789.BG_SPI_CS_FRONT` to
`cs=ST7789.BG_SPI_CS_BACK` and `backlight=19` to backlight=18` on the line
where the LCD is set up.
## Running this example
To run this example, type `./spirit-level.py` in the terminal.
It's assumed that you have the MSA301 breakout and LCD breakout lying flat, so
your Raspberry Pi or Raspberry Pi Zero will be perpendicular to your flat surface.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1,107 @@
#!/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)