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

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
__pycache__/
*.pyc
.DS_Store

62
README.md Normal file
View File

@@ -0,0 +1,62 @@
# matrix-pi
Backup of the Pimoroni LTP-305G LED matrix display setup running on `matrixpi`
under the `dissimulo` user. Two LTP-305G modules are wired to the I2C bus and
display the current time as a clock — hour on the left module (I2C `0x61`),
minute on the right (I2C `0x63`).
## Hardware
- Raspberry Pi (running Debian Bookworm, kernel 6.6.x)
- 2 × Pimoroni LTP-305G breakouts on I2C bus 1
- `0x61` — hour digits
- `0x63` — minute digits
## What's in here
| Path | Purpose |
| --- | --- |
| `systemd/` | The two systemd units that run the clock at boot |
| `deployed/` | Mirror of the on-disk script tree the units invoke (includes the local `%I` 12-hour customization to `clock.py`) |
| `vendor/ltp305-python/` | Vendored copy of the Pimoroni `ltp305` Python library (offline install fallback) |
| `vendor/breakout-garden/` | Vendored copy of Pimoroni's Breakout Garden tooling (used for I2C autodetect) |
| `config/boot-firmware-config.txt.snippet` | The `/boot/firmware/config.txt` lines that enable I2C |
| `install.sh` | Idempotent restore script |
| `docs/inventory.md` | Source-of-truth inventory of every LTP-305G-related file on the live system |
## Restoring on a fresh Pi
1. Flash Raspberry Pi OS (Bookworm or later), create user `dissimulo`.
2. Ensure I2C is enabled — either via `raspi-config` or by appending the line
from `config/boot-firmware-config.txt.snippet` to `/boot/firmware/config.txt`
and rebooting.
3. Wire the two LTP-305G breakouts; confirm with `i2cdetect -y 1` that they
appear at `0x61` and `0x63`. (Install `i2c-tools` if missing.)
4. From a clone of this repo, run:
```
sudo ./install.sh
```
This installs the `ltp305` Python package from `vendor/`, drops the
customized scripts into `/home/dissimulo/Pimoroni/ltp305/`, and enables both
systemd units.
5. Verify:
```
systemctl status matrix0x61.service matrix0x63.service
```
## Local customization vs. upstream
The deployed `clock.py` differs from the upstream Pimoroni `ltp305-python`
example by one line — the `hour` format is `%I` (12-hour) instead of `%H`
(24-hour). This is preserved in `deployed/home/dissimulo/Pimoroni/ltp305/examples/clock.py`.
## Upstream provenance
Vendored sources were captured from these commits:
- `pimoroni/ltp305-python` @ `bf859fe6cddf8cffcb12d62bad8f21e0bbb9fc42`
- `pimoroni/breakout-garden` @ `9c331458ee8c4732dbc783a6409ef53d671d321b`

View File

@@ -0,0 +1,3 @@
dtparam=i2c_arm=on
dtoverlay=vc4-kms-v3d
dtoverlay=dwc2,dr_mode=host

View File

@@ -0,0 +1,126 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Python: package ltp305</title>
</head><body>
<table class="heading">
<tr class="heading-text decor">
<td class="title">&nbsp;<br><strong class="title">ltp305</strong> (version 0.0.1)</td>
<td class="extra"><a href=".">index</a><br><a href="file:/usr/local/lib/python3.11/dist-packages/ltp305/__init__.py">/usr/local/lib/python3.11/dist-packages/ltp305/__init__.py</a></td></tr></table>
<p></p>
<p>
<table class="section">
<tr class="decor pkg-content-decor heading-text">
<td class="section-title" colspan=3>&nbsp;<br><strong class="bigsection">Package Contents</strong></td></tr>
<tr><td class="decor pkg-content-decor"><span class="code">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></td><td>&nbsp;</td>
<td class="singlecolumn"><table><tr><td class="multicolumn"><a href="ltp305.font.html">font</a><br>
</td><td class="multicolumn"></td><td class="multicolumn"></td><td class="multicolumn"></td></tr></table></td></tr></table><p>
<table class="section">
<tr class="decor index-decor heading-text">
<td class="section-title" colspan=3>&nbsp;<br><strong class="bigsection">Classes</strong></td></tr>
<tr><td class="decor index-decor"><span class="code">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></td><td>&nbsp;</td>
<td class="singlecolumn"><dl>
<dt class="heading-text"><a href="builtins.html#object">builtins.object</a>
</dt><dd>
<dl>
<dt class="heading-text"><a href="ltp305.html#LTP305">LTP305</a>
</dt></dl>
</dd>
</dl>
<p>
<table class="section">
<tr class="decor title-decor heading-text">
<td class="section-title" colspan=3>&nbsp;<br><a name="LTP305">class <strong>LTP305</strong></a>(<a href="builtins.html#object">builtins.object</a>)</td></tr>
<tr><td class="decor title-decor" rowspan=2><span class="code">&nbsp;&nbsp;&nbsp;</span></td>
<td class="decor title-decor" colspan=2><span class="code"><a href="#LTP305">LTP305</a>(address=97,&nbsp;brightness=0.5)<br>
&nbsp;<br>
_buf_matrix_left&nbsp;=&nbsp;[<br>
#&nbsp;Row&nbsp;&nbsp;7654321<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b01111111,&nbsp;&nbsp;#&nbsp;col&nbsp;1,&nbsp;bottom&nbsp;=&nbsp;msb<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b01111111,&nbsp;&nbsp;#&nbsp;col&nbsp;2<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b01111111,&nbsp;&nbsp;#&nbsp;col&nbsp;3<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b01111111,&nbsp;&nbsp;#&nbsp;col&nbsp;4<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b01111111,&nbsp;&nbsp;#&nbsp;col&nbsp;5<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00000000,<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00000000,<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b01000000&nbsp;&nbsp;&nbsp;#&nbsp;bit&nbsp;7&nbsp;=&nbsp;decimal&nbsp;dot<br>
]<br>
&nbsp;<br>
_buf_matrix_right&nbsp;=&nbsp;[<br>
#&nbsp;Col&nbsp;&nbsp;&nbsp;&nbsp;12345<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00011111,&nbsp;&nbsp;#&nbsp;row&nbsp;1<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00011111,&nbsp;&nbsp;#&nbsp;row&nbsp;2<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00011111,&nbsp;&nbsp;#&nbsp;row&nbsp;3<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00011111,&nbsp;&nbsp;#&nbsp;row&nbsp;4<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00011111,&nbsp;&nbsp;#&nbsp;row&nbsp;5<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00011111,&nbsp;&nbsp;#&nbsp;row&nbsp;6<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b10011111,&nbsp;&nbsp;#&nbsp;row&nbsp;7&nbsp;+&nbsp;bit&nbsp;8&nbsp;=&nbsp;decimal&nbsp;dot<br>
&nbsp;&nbsp;&nbsp;&nbsp;0b00000000<br>
]<br>&nbsp;</span></td></tr>
<tr><td>&nbsp;</td>
<td class="singlecolumn">Methods defined here:<br>
<dl><dt><a name="LTP305-__init__"><strong>__init__</strong></a>(self, address=97, brightness=0.5)</dt><dd><span class="code"><a href="#LTP305">LTP305</a>&nbsp;5x7&nbsp;x&nbsp;2&nbsp;Matrix&nbsp;Driver<br>
&nbsp;<br>
:param&nbsp;address:&nbsp;i2c&nbsp;address,&nbsp;one&nbsp;of&nbsp;0x61,&nbsp;0x62&nbsp;or&nbsp;0x63&nbsp;(default&nbsp;0x61)<br>
:param&nbsp;brightness:&nbsp;LED&nbsp;brightness&nbsp;from&nbsp;0.0&nbsp;to&nbsp;1.0&nbsp;(default&nbsp;0.5)</span></dd></dl>
<dl><dt><a name="LTP305-clear"><strong>clear</strong></a>(self)</dt><dd><span class="code">Clear&nbsp;both&nbsp;LED&nbsp;matrices.<br>
&nbsp;<br>
Must&nbsp;call&nbsp;.<a href="#LTP305-show">show</a>()&nbsp;to&nbsp;display&nbsp;changes.</span></dd></dl>
<dl><dt><a name="LTP305-get_shape"><strong>get_shape</strong></a>(self)</dt><dd><span class="code">Set&nbsp;the&nbsp;width/height&nbsp;of&nbsp;the&nbsp;display.</span></dd></dl>
<dl><dt><a name="LTP305-set_brightness"><strong>set_brightness</strong></a>(self, brightness, update=False)</dt><dd><span class="code">Set&nbsp;brightness&nbsp;of&nbsp;both&nbsp;LED&nbsp;matrices.<br>
&nbsp;<br>
:param&nbsp;brightnes:&nbsp;LED&nbsp;brightness&nbsp;from&nbsp;0.0&nbsp;to&nbsp;1.0<br>
:param&nbsp;update:&nbsp;Push&nbsp;change&nbsp;to&nbsp;display&nbsp;immediately&nbsp;(otherwise&nbsp;you&nbsp;must&nbsp;call&nbsp;.<a href="#LTP305-show">show</a>())</span></dd></dl>
<dl><dt><a name="LTP305-set_character"><strong>set_character</strong></a>(self, x, char)</dt><dd><span class="code">Set&nbsp;a&nbsp;single&nbsp;character.<br>
&nbsp;<br>
:param&nbsp;x:&nbsp;x&nbsp;position,&nbsp;0&nbsp;for&nbsp;left,&nbsp;5&nbsp;for&nbsp;right,&nbsp;or&nbsp;in&nbsp;between&nbsp;if&nbsp;you&nbsp;fancy<br>
:param&nbsp;char:&nbsp;string&nbsp;character&nbsp;or&nbsp;char&nbsp;ordinal</span></dd></dl>
<dl><dt><a name="LTP305-set_decimal"><strong>set_decimal</strong></a>(self, left=None, right=None)</dt><dd><span class="code">Set&nbsp;decimal&nbsp;of&nbsp;left&nbsp;and/or&nbsp;right&nbsp;matrix.<br>
&nbsp;<br>
:param&nbsp;left:&nbsp;State&nbsp;of&nbsp;left&nbsp;decimal&nbsp;dot<br>
:param&nbsp;right:&nbsp;State&nbsp;of&nbsp;right&nbsp;decimal&nbsp;dot</span></dd></dl>
<dl><dt><a name="LTP305-set_image"><strong>set_image</strong></a>(self, image, offset_x=0, offset_y=0, wrap=False, bg=0)</dt><dd><span class="code">Set&nbsp;a&nbsp;PIL&nbsp;image&nbsp;to&nbsp;the&nbsp;display&nbsp;buffer.</span></dd></dl>
<dl><dt><a name="LTP305-set_pixel"><strong>set_pixel</strong></a>(self, x, y, c)</dt><dd><span class="code">Set&nbsp;a&nbsp;single&nbsp;pixel&nbsp;on&nbsp;the&nbsp;matrix.<br>
&nbsp;<br>
:param&nbsp;x:&nbsp;x&nbsp;position&nbsp;from&nbsp;0&nbsp;to&nbsp;9&nbsp;(0-4&nbsp;on&nbsp;left&nbsp;matrix,&nbsp;5-9&nbsp;on&nbsp;right)<br>
:param&nbsp;y:&nbsp;y&nbsp;position<br>
:param&nbsp;c:&nbsp;state&nbsp;on/off</span></dd></dl>
<dl><dt><a name="LTP305-show"><strong>show</strong></a>(self)</dt><dd><span class="code">Update&nbsp;the&nbsp;LED&nbsp;matrixes&nbsp;from&nbsp;the&nbsp;buffer.</span></dd></dl>
<hr>
Data descriptors defined here:<br>
<dl><dt><strong>__dict__</strong></dt>
<dd><span class="code">dictionary&nbsp;for&nbsp;instance&nbsp;variables&nbsp;(if&nbsp;defined)</span></dd>
</dl>
<dl><dt><strong>__weakref__</strong></dt>
<dd><span class="code">list&nbsp;of&nbsp;weak&nbsp;references&nbsp;to&nbsp;the&nbsp;object&nbsp;(if&nbsp;defined)</span></dd>
</dl>
</td></tr></table></td></tr></table><p>
<table class="section">
<tr class="decor data-decor heading-text">
<td class="section-title" colspan=3>&nbsp;<br><strong class="bigsection">Data</strong></td></tr>
<tr><td class="decor data-decor"><span class="code">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span></td><td>&nbsp;</td>
<td class="singlecolumn"><strong>CMD_BRIGHTNESS</strong> = 25<br>
<strong>CMD_MATRIX_L</strong> = 14<br>
<strong>CMD_MATRIX_R</strong> = 1<br>
<strong>CMD_MODE</strong> = 0<br>
<strong>CMD_OPTIONS</strong> = 13<br>
<strong>CMD_UPDATE</strong> = 12<br>
<strong>MODE</strong> = 24<br>
<strong>OPTS</strong> = 14<br>
<strong>font</strong> = {32: [0, 0, 0, 0, 0], 33: [0, 0, 95, 0, 0], 34: [0, 7, 0, 7, 0], 35: [20, 127, 20, 127, 20], 36: [36, 42, 127, 42, 18], 37: [35, 19, 8, 100, 98], 38: [54, 73, 85, 34, 80], 39: [0, 5, 3, 0, 0], 40: [0, 28, 34, 65, 0], 41: [0, 65, 34, 28, 0], ...}</td></tr></table>
</body></html>

View File

@@ -0,0 +1,43 @@
import time
import math
import random
from ltp305 import LTP305
print("""bargraph.py - A basic graph example
Displays an animated rising/falling bar on the left matrix,
and a scrolling bar graph on the right.
Press Ctrl+C to exit!
""")
display = LTP305()
width, height = display.get_shape()
values = [0, 0, 0, 0, 0]
while True:
# Add a new random value to our list and prune the list to visible values
values.insert(0, random.randint(0, height))
values = values[:width]
# Animate a value from 0 to height + 1
value = (math.sin(time.time() * math.pi) + 1) / 2.0
value *= height + 1
value = math.floor(value)
for y in range(height):
y = height - 1 - y
for x in range(width // 2):
# Left
display.set_pixel(x, y, value <= y)
# Right
display.set_pixel(x + (width // 2), y, values[x] <= y)
time.sleep(1.0 / height)
display.show()

View File

@@ -0,0 +1,50 @@
import sys
import time
from ltp305 import LTP305
print("""clock.py - clock segment example.
Usage: python3 clock.py <hour/minute/second> <i2c-address>
This simple clock example will display the hour, minute or second on a single matrix display breakout.
Press Ctrl+C to exit.
""")
fmt = "%M"
address = 0x61
available = {
"hour": "%I",
"minute": "%M",
"second": "%S"
}
if len(sys.argv) > 1:
try:
fmt = available[sys.argv[1]]
except KeyError:
raise ValueError("{} is not supported!".format(sys.argv[1]))
if len(sys.argv) > 2:
address = int(sys.argv[2], 16)
if address not in [0x61, 0x62, 0x63]:
raise ValueError("Invalid i2c address: 0x{:02x}. Run `i2c-detect -y 1` to discover breakouts.".format(address))
try:
display = LTP305(address=address)
display.clear()
display.show()
except OSError:
raise OSError("Unable to find LTP305 on i2c address: 0x{:02x}. Run `i2c-detect -y 1` to discover breakouts".format(address))
while True:
minute = time.strftime(fmt)
left, right = minute
display.set_character(0, left)
display.set_character(5, right)
display.show()
time.sleep(1.0 / 60)

View File

@@ -0,0 +1,49 @@
import math
import time
import random
print("""eyes.py - They're watching you!
Press Ctrl+C to exit!
""")
from ltp305 import LTP305
display = LTP305()
def eye(x, y):
display.set_pixel(x, y, True)
display.set_pixel(x, y + 1, True)
display.set_pixel(x + 1, y, True)
display.set_pixel(x + 1, y + 1, True)
while True:
t = time.time() * math.pi
# Get eye x and y positions in the range 0.0 to 1.0
# You can plug in your own 0.0 to 1.0 values here
x = (math.sin(t / 2) + 1) / 2
y = (math.sin(t / 4) + 1) / 2
# Multiply them up to display coords and convert to itn
x = math.floor(x * 4)
y = math.floor(y * 6)
# Clear the display
display.clear()
# Blink occasionally
if not random.randint(0, 20) == 2:
# Draw the eyes if not blinking
eye(x, y)
eye(x + 5, y)
# Update the display at 10FPS
# This gives our crude blink code time to not look like a random flicker
display.show()
time.sleep(1.0 / 10)

View File

@@ -0,0 +1,5 @@
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
rm -r /home/dissimulo/Pimoroni/ltp305

41
docs/inventory.md Normal file
View File

@@ -0,0 +1,41 @@
# LTP-305G live-system inventory
Snapshot of every LTP-305G-related path on `matrixpi` at the time this repo
was created. Use this to spot drift if anything is changed on the live system
without being committed back here.
## Systemd units (root-owned)
- `/etc/systemd/system/matrix0x61.service` — runs `clock.py hour 0x61`
- `/etc/systemd/system/matrix0x63.service` — runs `clock.py minute 0x63`
Both have `WantedBy=multi-user.target` and are `enabled`.
## Deployed scripts (under `dissimulo`)
- `/home/dissimulo/Pimoroni/ltp305/`
- `docs.html`
- `uninstall.sh`
- `examples/bargraph.py`
- `examples/clock.py`**locally modified**: `"hour": "%I"` (12-hour) instead of upstream `"%H"`
- `examples/eyes.py`
## Upstream library checkouts
- `/home/dissimulo/ltp305-python/` — clone of `pimoroni/ltp305-python`, used at install time
- `/home/dissimulo/breakout-garden/` — clone of `pimoroni/breakout-garden`, used for I2C autodetect
## Installed Python package
- `/usr/local/lib/python3.11/dist-packages/ltp305/__init__.py`
- `/usr/local/lib/python3.11/dist-packages/ltp305-0.0.1.dist-info/`
## Boot configuration
- `/boot/firmware/config.txt` contains `dtparam=i2c_arm=on` enabling the I2C bus the modules sit on.
## I2C bus state
`i2cdetect` is not currently installed on this system. Once `i2c-tools` is
installed, expect `0x61` and `0x63` to appear on bus 1 when both LTP-305G
breakouts are connected and powered.

39
install.sh Executable file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Restore the LTP-305G matrix clock setup on a fresh Pi.
# Run as root (or with sudo). Idempotent.
set -euo pipefail
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET_USER="${TARGET_USER:-dissimulo}"
TARGET_HOME="$(getent passwd "$TARGET_USER" | cut -d: -f6)"
if [[ -z "$TARGET_HOME" ]]; then
echo "User '$TARGET_USER' not found. Create it first or set TARGET_USER." >&2
exit 1
fi
if [[ $EUID -ne 0 ]]; then
echo "Run as root (sudo)." >&2
exit 1
fi
echo "==> Installing ltp305 Python package from vendored source"
pip3 install --break-system-packages "$REPO_DIR/vendor/ltp305-python/library"
echo "==> Deploying script tree to $TARGET_HOME/Pimoroni/ltp305"
mkdir -p "$TARGET_HOME/Pimoroni"
cp -r "$REPO_DIR/deployed/home/dissimulo/Pimoroni/ltp305" "$TARGET_HOME/Pimoroni/"
chown -R "$TARGET_USER:$TARGET_USER" "$TARGET_HOME/Pimoroni"
echo "==> Installing systemd units"
install -m 0644 "$REPO_DIR/systemd/matrix0x61.service" /etc/systemd/system/matrix0x61.service
install -m 0644 "$REPO_DIR/systemd/matrix0x63.service" /etc/systemd/system/matrix0x63.service
echo "==> Reloading systemd and enabling units"
systemctl daemon-reload
systemctl enable --now matrix0x61.service matrix0x63.service
echo
echo "Done. Status:"
systemctl --no-pager status matrix0x61.service matrix0x63.service || true

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Matrix LED Clock 0x61
[Service]
ExecStart=python3 /home/dissimulo/Pimoroni/ltp305/examples/clock.py hour 0x61
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Matrix LED Clock 0x63
[Service]
ExecStart=python3 /home/dissimulo/Pimoroni/ltp305/examples/clock.py minute 0x63
[Install]
WantedBy=multi-user.target

21
vendor/breakout-garden/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Pimoroni Ltd.
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.

29
vendor/breakout-garden/README.md vendored Normal file
View File

@@ -0,0 +1,29 @@
# Breakout Garden
Software and examples for the Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi),
a simple way to build projects using [breakouts](https://shop.pimoroni.com/collections/pimoroni-breakouts), with
absolutely no soldering required.
## Installing
Breakout Garden doesn't require any software of its own, but each breakout you use will need a Python library.
We've created a handy script to get you started:
1. Plug in all of your Breakout Garden-compatible breakouts
2. Run `wget https://github.com/pimoroni/breakout-garden/archive/master.zip` to download software.
3. Run `unzip master.zip` to unzip the software.
4. Run `cd breakout-garden-master` to enter code folder.
5. Run `sudo ./install.sh`
6. Step through the install process
7. Enjoy!
## Examples
We've put together a few fun [examples](examples/) to show what's possible with Breakout Garden. Each example
has its own README telling you about it, which breakouts are required, and about how to use it.
## Bugs, Issues and Problems
If you have trouble with your Breakout Garden try our support forums: https://forums.pimoroni.com/

123
vendor/breakout-garden/autodetect.py vendored Executable file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env python
import smbus
import sys
I2C_BUS = 1
DEBUG = False
install_mode = False
if len(sys.argv) > 1:
if "--install" in sys.argv:
install_mode = True
try:
bus = smbus.SMBus(I2C_BUS)
except IOError:
print("Unable to access /dev/i2c-{}, please ensure i2c is enabled!".format(I2C_BUS))
sys.exit()
def check_chip_id(i2c_addr, chip_ids):
if len(chip_ids) == 0:
return True
for register in chip_ids:
value, size, negate_match = chip_ids[register]
reg, reg_size = register
if reg_size == 1:
if size == 1:
read = bus.read_byte_data(i2c_addr, reg)
elif size == 2:
read = bus.read_word_data(i2c_addr, reg)
else:
raise RuntimeError("Unsupported Chip ID size: {} byte(s)".format(size))
if negate_match and value != read:
return True
elif value == read:
return True
else:
# TODO: Support 16-bit registers
raise RuntimeError("Unsupported register size: {} byte(s)".format(size))
return False
def get_device(line):
parts=[x.strip() for x in line.split(":")]
i2c_addr = int(parts[0][0:4], 16)
chip_ids = {}
if(len(parts[0]) > 4):
register_map = parts[0][5:-1].split(',')
for mapping in register_map:
if '!=' in mapping:
register, value = [(int(x, 16), (len(x) - 2) // 2) for x in mapping.split('!=')]
chip_ids[register] = value[0], value[1], True
else:
register, value = [(int(x, 16), (len(x) - 2) // 2) for x in mapping.split('=')]
chip_ids[register] = value[0], value[1], False
return i2c_addr, parts[1], parts[2], parts[3], chip_ids
devices = [get_device(line) for line in open("breakouts.config").read().strip().split("\n")]
addresses = set([device[0] for device in devices])
def identify(find_i2c_addr):
try:
bus.read_byte_data(find_i2c_addr, 0x00)
except IOError as e:
pass
for i2c_addr, library, module, name, chip_ids in devices:
if i2c_addr == find_i2c_addr and check_chip_id(i2c_addr, chip_ids):
installed = True
try:
__import__(module)
except ImportError:
installed = False
return installed, library, name
return None, None, None
found_addr = []
found_devices = {}
for i2c_addr in addresses:
try:
bus.read_byte_data(i2c_addr, 0x00)
if DEBUG: print("Found device on: {:02x}".format(i2c_addr))
found_addr.append(i2c_addr)
installed, library, name = identify(i2c_addr)
if installed is None:
continue
if name not in found_devices:
found_devices[name] = [installed, library, [i2c_addr]]
else:
found_devices[name][2].append(i2c_addr)
except IOError as e:
if DEBUG: print("IOError reading: {:02x}".format(i2c_addr))
continue
for name in found_devices:
installed, library, i2c_addresses = found_devices[name]
format_string = ""
if install_mode:
format_string = "{name}|{library}|{installed}"
else:
format_string = "{i2c_addresses}: {name} ({library} {installed})"
print(format_string.format(
i2c_addresses = ",".join(["0x{:02x}".format(i2c_addr) for i2c_addr in i2c_addresses]),
name = name,
library = library,
installed = "installed" if installed else "required"
))

31
vendor/breakout-garden/breakouts.config vendored Normal file
View File

@@ -0,0 +1,31 @@
0x0a: trackball-python: trackball: rgb illuminated mini trackball
0x0b: trackball-python: trackball: rgb illuminated mini trackball
0x3c: sh1106-python: luma.oled: 1.12" mono OLED
0x3d: sh1106-python: luma.oled: 1.12" mono OLED
0x1d: lsm303d-python: lsm303d: lsm303d 6dof motion sensor
0x1e: lsm303d-python: lsm303d: lsm303d 6dof motion sensor
0x23: ltr559-python: ltr559: ltr559 proximity sensor
0x38: bh1745-python: bh1745: bh1745 colour sensor
0x39: bh1745-python: bh1745: bh1745 colour sensor
0x29: vl53l1x-python: VL53L1X: vl53l1x time of flight
0x33: mlx90640-library: mlx90460: mlx90640 thermal camera
0x48: ads1015-python: ads1015: ads1015 ADC
0x49: as7262-python: as7262: as7262 spectrometer
0x57: max30105-python: max30105: max30105 heart rate, oximeter, smoke sensor
0x58: sgp30-python: sgp30: sgp30 TVOC and CO2 sensor
0x60: drv8830-python: drv8830: drv8830 H-bridge motor driver
0x66: mcp9600-python: mcp9600: mcp9600 thermocouple temperature sensor
0x67: mcp9600-python: mcp9600: mcp9600 thermocouple temperature sensor
0x68: icm20948-python: icm20948: icm20948 9dof motion sensor
0x69: icm20948-python: icm20948: icm20948 9dof motion sensor
0x74: rgbmatrix5x5-python: rgbmatrix5x5: rgb 5x5 led matrix
0x75: matrix11x7-python: matrix11x7: white 11x7 led matrix
0x76[0xd0=0x61]: bme680-python: bme680: bme680 weather sensor
0x76[0xd0=0x58]: bmp280-python: bmp280: bmp280 temperature and pressure sensor
0x77[0xd0=0x61]: bme680-python: bme680: bme680 weather sensor
0x77[0xd0=0x59]: bmp280-python: bmp280: bmp280 temperature and pressure sensor
0x5a: drv2605-python: drv2605: drv2605 haptic driver
0x26: msa301-python: msa301: msa3013-axis accelerometer
0x10[0x0c=0x0026]: veml6075-python: veml6075: veml6075 uva and uvb sensor
0x10[0x0c!=0x0026]: pa1010d-python: pa1010d: pa1010d GPS
0x52: rv3028-python: rv3028: rv3028 real-time-clock breakout

View File

@@ -0,0 +1,3 @@
# BME680 - Temperature, Pressure, Humidity And Gas Sensor
https://github.com/pimoroni/bme680

View File

@@ -0,0 +1,3 @@
# LTR559 - Proximity And Light Presence Sensor
https://github.com/pimoroni/ltr559-python

View File

@@ -0,0 +1,3 @@
# MLX90640 - 32x24 Pixel Thermal Camera
https://github.com/pimoroni/mlx90640-library

View File

@@ -0,0 +1,3 @@
# SH1106 - 1.12" Mono OLED
https://github.com/pimoroni/sh1106-python

View File

@@ -0,0 +1,3 @@
# VL51L1X - Time Of Flight Distance Sensor
https://github.com/pimoroni/vl53l1x-python

View File

@@ -0,0 +1,6 @@
# Breakout Garden examples
These example programs demonstrate the sort of thing that's possible by combining
two or more Pimoroni breakouts on Breakout Garden.
Each example folder has its own README that you should check before running the example.

View File

@@ -0,0 +1,26 @@
# Colour control example
Use the trackball and switch to control the hue and brightness of the trackball's RGBW LEDs
and the 5x5 RGB matrix's LEDs.
Scroll up to increase brightness and left/right to change hue. Click to turn on/off.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [Trackball Breakout](https://shop.pimoroni.com/products/trackball-breakout)
- A Pimoroni [5x5 RGB Matrix Breakout](https://shop.pimoroni.com/products/5x5-rgb-matrix-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.
## Running this example
To run this example, type `./colour-control.py` in the terminal.

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python3
import time
import colorsys
import atexit
from trackball import TrackBall
from rgbmatrix5x5 import RGBMatrix5x5
print("""This Pimoroni Breakout Garden example requires an
Trackball Breakout and a 5x5 RGB Matrix Breakout.
Use the trackball and switch to control the hue and
brightness of the trackball's RGBW LEDs.
Scroll up to increase brightness and left/right
to change hue. Click to turn on/off.
Press Ctrl+C to exit.
""")
# Set up the trackball
trackball = TrackBall(interrupt_pin=4)
@atexit.register
def clear_trackball():
trackball.set_rgbw(0, 0, 0, 0)
# Set up the 5x5 RGB matrix
rgbmatrix5x5 = RGBMatrix5x5()
rgbmatrix5x5.set_clear_on_exit()
rgbmatrix5x5.set_brightness(0.8)
x = 0
y = 50.0
toggled = False
while True:
up, down, left, right, switch, state = trackball.read()
# Update x and y vals based on movement
y += up
y -= down
x += right / 10.0
x -= left / 10.0
# Clamp to min of 0 and max of 100
x %= 100
y = max(0, min(y, 100))
# Calculate hue and brightness
h = x / 100.0
v = y / 100.0
# Prevents button from retriggering
debounce = 0.5
# Change toggled state if switch is pressed
if state and not toggled:
toggled = True
time.sleep(debounce)
elif state and toggled:
toggled = False
time.sleep(debounce)
# Set brightness to zero if switch toggled
if toggled:
v = 0
# Calculate RGB vals
w = 0
r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(h, 1.0, v)]
# Set LEDs
trackball.set_rgbw(r, g, b, w)
rgbmatrix5x5.set_all(r, g, b)
rgbmatrix5x5.show()
time.sleep(0.01)

View File

@@ -0,0 +1,44 @@
# Distance example
This example, the Park-O-Matic 6000, is a mockup of a car reversing
indicator.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [VL53L1X Time of Flight Sensor Breakout](https://shop.pimoroni.com/products/vl53l1x-breakout)
- A Pimoroni [1.12" OLED Breakout (SPI)](https://shop.pimoroni.com/products/1-12-oled-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.12" OLED Breakout (SPI)
as follows:
```
git clone https://github.com/pimoroni/sh1106-python
sudo ./install.sh
```
This example assumes that you have the OLED 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 `device=1` to `device=0` on the line
where the OLED is set up.
## Running this example
To run this example, type `./distance.py` in the terminal.
You can change the `threshold` value (in cm) to change the threshold at which the warning
indicator starts flashing.

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
import os
import time
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
import VL53L1X
from luma.core.interface.serial import spi
from luma.core.render import canvas
from luma.oled.device import sh1106
print("""This Pimoroni Breakout Garden example requires an
VL53L1X Time of Flight Sensor Breakout and a 1.12" OLED Breakout (SPI).
The Park-O-Matic 6000 is a car reversing indicator mockup!
Press Ctrl+C a couple times to exit.
""")
# Set up OLED
oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)
# Set up VL53L1X Time of Flight sensor
tof = VL53L1X.VL53L1X(i2c_bus=1, i2c_address=0x29)
tof.open() # Initialise the I2C bus and configure the sensor
tof.start_ranging(3) # Start ranging, 1 = Short Range, 2 = Medium Range, 3 = Long Range
# Load fonts
rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto-Regular.ttf'))
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto-Black.ttf'))
rr_24 = ImageFont.truetype(rr_path, 24)
rb_20 = ImageFont.truetype(rb_path, 20)
rr_12 = ImageFont.truetype(rr_path, 12)
i = 0
threshold = 20 # Threshold at which the warning indicator flashes, in cm
# Main loop
while True:
i += 1
background = Image.open("images/distance.png").convert(oled.mode) # Load the artwork
draw = ImageDraw.ImageDraw(background)
# Measure the distance and write to the right place on the display
cm = tof.get_distance() // 10
cm += 1
pos = 80 - (rb_20.getsize(str(cm))[0] / 2)
draw.text((pos, 30), "{}".format(cm), fill="white", font=rb_20)
# Flash the warning indicator if the distance is below the threshold
if cm > threshold or i % cm == 1:
draw.rectangle([(76, 66), (115, 110)], fill="black")
# Display on the OLED
oled.display(background)
time.sleep(0.05)

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,22 @@
# Haptic trackball example
This example demonstrates how to generate haptic feedback
as the trackball is scrolled/pressed.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [DRV2605L Haptic Breakout](https://shop.pimoroni.com/products/drv2605l-linear-actuator-haptic-breakout)
- A Pimoroni [Trackball Breakout](https://shop.pimoroni.com/products/trackball-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.
## Running this example
To run this example, type `./haptic-trackball.py` in the terminal.

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
import time
from trackball import TrackBall
from drv2605 import DRV2605
print("""This Pimoroni Breakout Garden example requires a
DRV2605L Haptic Breakout and a Trackball Breakout.
This example demonstrates how to generate haptic feedback
as the trackball is scrolled/pressed.
Press Ctrl+C to exit.
""")
# Set up Trackball Breakout.
trackball = TrackBall(interrupt_pin=4)
drv2605 = DRV2605()
x = 0
y = 0
delta_x = 0
delta_y = 0
last_state = 0
# Set up Haptic Breakout.
drv2605.reset()
drv2605.set_realtime_data_format('Unsigned')
drv2605.set_feedback_mode('LRA')
drv2605.set_mode('Real-time Playback')
drv2605.go()
try:
while True:
# Get positional values from trackball.
up, down, left, right, switch, state = trackball.read()
y += up
y -= down
x += right
x -= left
delta_x += right
delta_x -= left
delta_y += up
delta_y -= down
x = max(0, min(x, 255))
y = max(0, min(y, 255))
# Generate a longer click when trackball is pressed.
if state != last_state:
drv2605.set_realtime_input(255)
time.sleep(0.01)
drv2605.set_realtime_input(0)
last_state = state
# Generate shorter clicks when trackball is scrolled.
if abs(delta_x) > 2:
drv2605.set_realtime_input(255)
time.sleep(0.005)
drv2605.set_realtime_input(0)
delta_x = 0
elif abs(delta_y) > 2:
drv2605.set_realtime_input(255)
time.sleep(0.005)
drv2605.set_realtime_input(0)
delta_y = 0
time.sleep(0.001)
except KeyboardInterrupt:
pass

View File

@@ -0,0 +1,57 @@
# Heartbeat example
This example, the Pulse-O-Matic 6000, is a heartbeat plotter and BPM display.
## Important!
**This code should not be used for medical diagnosis, as the basis for a real smoke
or fire detector, or in life-critical situations. It's for fun/novelty use only,
so bear that in mind while using it.**
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden HAT](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [MAX30105 Breakout - Heart Rate, Oximeter, Smoke Sensor](https://shop.pimoroni.com/products/max30105-breakout-heart-rate-oximeter-smoke-sensor)
- A Pimoroni [1.12" OLED Breakout (SPI)](https://shop.pimoroni.com/products/1-12-oled-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.12" OLED Breakout (SPI)
as follows:
```
git clone https://github.com/pimoroni/sh1106-python
sudo ./install.sh
```
This example assumes that you have the OLED 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 `device=1` to `device=0` on the line
where the OLED is set up.
## Running this example
To run this example, type `./heartbeat.py` in the terminal.
It's best to hold the sensor against your fingertip (the fleshy side)
using a piece of wire or a rubber band looped through the mounting
holes on the breakout, as the sensor is very sensitive to small
movements and it's hard to hold your finger against the sensor with
even pressure.
If you're using your MAX30105 Breakout with Breakout Garden, then
we'd recommend using one of our
[Breakout Garden Extender Kits](https://shop.pimoroni.com/products/breakout-garden-extender-kit)
with some [female to female jumper jerky](https://shop.pimoroni.com/products/jumper-jerky?variant=348491271).

View File

@@ -0,0 +1,46 @@
Digitized data copyright (c) 2010 Google Corporation
with Reserved Font Arimo, Tinos and Cousine.
Copyright (c) 2012 Red Hat, Inc.
with Reserved Font Name Liberation.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright statement(s).
"Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# NOTE! This code should not be used for medical diagnosis. It's
# for fun/novelty use only, so bear that in mind while using it.
import sys
import time
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from threading import Thread
from luma.core.interface.serial import spi
from luma.oled.device import sh1106
from max30105 import MAX30105, HeartRate
print("""
NOTE! This code should not be used for medical diagnosis. It's
for fun/novelty use only, so bear that in mind while using it.
This Pimoroni Breakout Garden example requires a
MAX30105 Breakout and a 1.12" OLED Breakout (SPI).
The Pulse-O-Matic 6000 is a heartbeat plotter and BPM display.
Press Ctrl+C a couple times to exit.
""")
# Set up OLED
oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)
# Load fonts
lsb_18 = ImageFont.truetype("fonts/LiberationSans-Bold.ttf", 18)
lsr_12 = ImageFont.truetype("fonts/LiberationSans-Regular.ttf", 12)
# Set up MAX30105 Breakout
max30105 = MAX30105()
max30105.setup(leds_enable=2)
max30105.set_led_pulse_amplitude(1, 0.2)
max30105.set_led_pulse_amplitude(2, 12.5)
max30105.set_led_pulse_amplitude(3, 0)
max30105.set_slot_mode(1, 'red')
max30105.set_slot_mode(2, 'ir')
max30105.set_slot_mode(3, 'off')
max30105.set_slot_mode(4, 'off')
hr = HeartRate(max30105)
data = []
running = True
bpm = 0
bpm_avg = 0
beat_detected = False
beat_status = False
def sample():
"""Function to thread heartbeat values separately to
OLED drawing"""
global bpm, bpm_avg, beat_detected, beat_status
average_over = 5
bpm_vals = [0 for x in range(average_over)]
last_beat = time.time()
while running:
t = time.time()
samples = max30105.get_samples()
if samples is not None:
for i in range(0, len(samples), 2):
ir = samples[i + 1]
beat_detected = hr.check_for_beat(ir)
if beat_detected:
beat_status = True
delta = t - last_beat
last_beat = t
bpm = 60 / delta
bpm_vals = bpm_vals[1:] + [bpm]
bpm_avg = sum(bpm_vals) / average_over
d = hr.low_pass_fir(ir & 0xff)
data.append(d)
if len(data) > 128:
data.pop(0)
time.sleep(0.05)
# The thread to measure acclerometer values
t = Thread(target=sample)
t.start()
# The main loop that draws values to the OLED
while True:
try:
img = Image.open("images/heartbeat.png").convert(oled.mode)
draw = ImageDraw.Draw(img)
# Draw the heartbeat trace
vals = data
new_vals = [x / float((max(vals) - min(vals))) * 32 for x in vals]
for i in range(1, len(new_vals)):
draw.line([(i - 1, 80 - new_vals[i - 1]), (i, 80 - new_vals[i])],
fill="white")
# Draw the Pulse-O-Matic "branding"
draw.rectangle([(0, 0), (128, 20)], fill="black")
if bpm_avg > 40:
draw.text((0, 1), "BPM: {:.2f}".format(bpm_avg), fill="white",
font=lsb_18)
else:
draw.text((0, 1), "BPM: --.--", fill="white", font=lsb_18)
if beat_status:
draw.text((115, 1), u"\u2665", fill="white", font=lsb_18)
beat_status = False
draw.line([(0, 20), (128, 20)], fill="white")
draw.rectangle([(0, 108), (128, 128)], fill="black")
draw.text((0, 110), "Pulse-O-Matic 6000", fill="white", font=lsr_12)
draw.line([(0, 108), (128, 108)], fill="white")
# Display on the OLED
oled.display(img)
except ZeroDivisionError:
pass
except KeyboardInterrupt:
running = False
sys.exit(0)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,34 @@
# Nightlight example
A simple little example of how to make a nightlight with the LTR-559 and 5x5
RGB matrix breakouts. It can be toggled on or off by tapping the sensor, or
triggered automatically when it gets dark.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [LTR-559 Light & Proximity Sensor Breakout](https://shop.pimoroni.com/products/ltr-559-light-proximity-sensor-breakout)
- A Pimoroni [5x5 RGB Matrix Breakout](https://shop.pimoroni.com/products/5x5-rgb-matrix-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 all of the required libraries.
## Running this example
To run this example, type `./nightlight.py` in the terminal.
You can change the RGB values of the `colour` variable to change the colour
of the light to whatever you wish. If you want the light and proximity
thresholds to be more or less sensitive, then you can change the values of
the `light_threshold` and `prox_threshold` variables.
## Notes
It's probably best to have the sensor and matrix breakouts on either side
of your Breakout Garden HAT, so that they're spaced apart and the LTR-559
won't be affected by the light from the matrix.

View File

@@ -0,0 +1,103 @@
#!/usr/bin/env python3
import time
from ltr559 import LTR559
from rgbmatrix5x5 import RGBMatrix5x5
print("""This Pimoroni Breakout Garden example requires an
LTR-559 Light and Proximity Breakout and a 5x5 RGB Matrix Breakout.
This example creates a little nightlight that can be toggled on or
off by tapping the proximity sensor with your finger, or triggered
automatically when it's dark.
Press Ctrl+C to exit.
""")
# Set up the LTR-559 sensor
ltr559 = LTR559()
# Set up the 5x5 RGB matrix
rgbmatrix5x5 = RGBMatrix5x5()
rgbmatrix5x5.set_clear_on_exit()
rgbmatrix5x5.set_brightness(0.8)
# Initial variables to keep track of state of light
state = False
last_state = False
toggled = False
light_threshold = 100 # Low-light trigger level
prox_threshold = 1000 # Proximity trigger level
colour = (255, 165, 0) # Orange-ish
# Function to toggle the RGB matrix on or off depending on state
def toggle_matrix():
global state, last_state
if state is True and last_state is False:
rgbmatrix5x5.set_all(*colour)
rgbmatrix5x5.show()
elif state is False and last_state is True:
rgbmatrix5x5.clear()
rgbmatrix5x5.show()
last_state = state
# Read the sensor once, as the first values are always squiffy
ltr559.update_sensor()
lux = ltr559.get_lux()
prox =ltr559. get_proximity()
time.sleep(1)
try:
while True:
# Read the light and proximity sensor
ltr559.update_sensor()
lux = ltr559.get_lux()
prox = ltr559.get_proximity()
# If it's dark and the light isn't toggled on, turn on
if lux < light_threshold and not toggled:
state = True
if state != last_state:
print("It's dark! Turning light ON")
toggle_matrix()
# If it's light and the light isn't on, turn off
elif lux >= light_threshold and not toggled:
state = False
if state != last_state:
print("It's light! Turning light OFF")
toggle_matrix()
# If there's a tap on the sensor
if prox > prox_threshold:
# Toggle it off if it's currently on
if toggled:
state = False
toggled = False
if state != last_state:
print("Toggling light OFF")
toggle_matrix()
# Toggle it on if it's currently off
else:
state = True
toggled = True
if state != last_state:
print("Toggling light ON")
toggle_matrix()
# Wait a short while to prevent the on/off switch
# from immediately re-triggering
time.sleep(0.5)
elif prox < prox_threshold and lux >= light_threshold:
state = False
time.sleep(0.05)
except KeyboardInterrupt:
pass

View File

@@ -0,0 +1,28 @@
# Rainbow compass example
Calculates and displays compass heading as an
RGB colour around the hue wheel, with North being red, South cyan,
East green, and West purple, approximately.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [LSM303D Motion Sensor Breakout](https://shop.pimoroni.com/products/lsm303d-6dof-motion-sensor-breakout)
- A Pimoroni [5x5 RGB Matrix Breakout](https://shop.pimoroni.com/products/5x5-rgb-matrix-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 all of the required libraries.
## Running this example
To run this example, type `./rainbow-compass.py` in the terminal, and then
walk through the calibration steps.
Depending on the orientation of you LSM303D breakout, you can change the
line that says `Y = 2` in the `raw_heading` function to e.g. `Y = 1` if
you have the breakout flat rather than vertical.

View File

@@ -0,0 +1,100 @@
#!/usr/bin/env python3
import time
import math
import colorsys
from lsm303d import LSM303D
from rgbmatrix5x5 import RGBMatrix5x5
print("""This Pimoroni Breakout Garden example requires an LSM303D
Motion Sensor Breakout and a 5x5 RGB Matrix Breakout.
The Rainbow Compass calculates and displays compass heading as an
RGB colour around the hue wheel, with North being red, South cyan,
East green, and West purple, appromximately.
Press Cyrl-+C to exit.
""")
def raw_heading(minimums, maximums, zero=0):
"""Return a raw compass heading calculated from the magnetometer data."""
X = 0
Y = 2 # Change to 1 if you have the breakout flat
# The range over which values will be calculated, i.e. -1 to +1
mag_range = 2
# Get the magnetometer's values
mag = list(lsm.magnetometer())
# Scale and shift values
for i in range(len(mag)):
mag[i] = ((mag_range / (maximums[i] - minimums[i])) * mag[i]) - \
(mag_range / 2.0)
# Calculate the heading from the vector
heading = math.atan2(mag[Y], mag[X])
if heading < 0:
heading += (2 * math.pi)
# Convert radian value to degrees
heading_degrees = (round(math.degrees(heading), 2) - zero) % 360
return heading_degrees
lsm = LSM303D(0x1d) # Change to 0x1e if you have soldered the address jumper
# Set up the 5x5 RGB matrix
rgbmatrix5x5 = RGBMatrix5x5()
rgbmatrix5x5.set_clear_on_exit()
rgbmatrix5x5.set_brightness(0.8)
# Python 2/3 compatibility
try:
input = raw_input
except NameError:
pass
input("Lay your LSM303D in Breakout Garden flat (LSM303D vertical), \n\
press a key to start, then rotate it 360 degrees, keeping it flat...\n")
# Variables to govern calibration time
t_start = time.time()
t_elapsed = 0
calibration_time = 30
# Initial values for mins and maxs
minimums = list(lsm.magnetometer())
maximums = list(lsm.magnetometer())
# Run calibration until time limit is reached
while t_elapsed < calibration_time:
mag = lsm.magnetometer()
for i in range(len(mag)):
if mag[i] < minimums[i]: # Set new min
minimums[i] = mag[i]
if mag[i] > maximums[i]: # Set new max
maximums[i] = mag[i]
t_elapsed = time.time() - t_start
input("Calibration complete!\n\nIf you want to set a zero (North) point, \n\
then turn your breakout to that point and press a key...\n")
# Zero point for the compass
zero = raw_heading(minimums, maximums)
input("Press a key to begin readings!\n")
# Begin compass readings and display on RGB matrix
while True:
rh = raw_heading(minimums, maximums, zero=zero)
hue = rh / 360.0 # Hue is based on compass heading
r, g, b = [int(c * 255) for c in colorsys.hsv_to_rgb(hue, 1.0, 1.0)]
rgbmatrix5x5.set_all(r, g, b) # Set whole matrix to calculated RGB
rgbmatrix5x5.show()
print("compass heading: {:0.0f} degrees".format(rh))
time.sleep(0.1)

View File

@@ -0,0 +1,49 @@
# Seismograph example
The Dino-Detect v1.2 beta is a dino stomp detector. It's a
UNIX system, I know this.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [LSM303D 6DoF Sensor Breakout](https://shop.pimoroni.com/products/lsm303d-6dof-motion-sensor-breakout)
- A Pimoroni [1.12" OLED Breakout (SPI)](https://shop.pimoroni.com/products/1-12-oled-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.12" OLED Breakout (SPI)
as follows:
```
git clone https://github.com/pimoroni/sh1106-python
sudo ./install.sh
```
This example assumes that you have the OLED 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 `device=1` to `device=0` on the line
where the OLED is set up.
## Running this example
To run this example, type `./seismograph.py` in the terminal.
Note that it takes a baseline reading initially to zero out the axes,
and then calculates subsequent readings against the baseline, so make
sure that your Breakout Garden is sitting still when you start the
program.
The `sensitivity` variable can be changed to make the seismograph more or
less sensitive to dino stomps.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,122 @@
#!/usr/bin/env python3
import os
import time
import sys
try:
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
except ImportError:
print("""This example requires PIL.
Install with: sudo apt install python{v}-pil
""".format(v="" if sys.version_info.major == 2 else sys.version_info.major))
sys.exit(1)
from lsm303d import LSM303D
from threading import Thread
from luma.core.interface.serial import spi
from luma.core.render import canvas
from luma.oled.device import sh1106
print("""This Pimoroni Breakout Garden example requires an
LSM303D 6DoF Breakout and a 1.12" OLED Breakout (SPI).
The Dino-Detect v1.2 beta is a dino stomp detector. It's a
UNIX system, I know this.
Press Ctrl+C to exit.
""")
# Set up OLED
oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)
# Load fonts
rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto-Regular.ttf'))
print(rr_path)
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto-Black.ttf'))
rr_24 = ImageFont.truetype(rr_path, 24)
rb_20 = ImageFont.truetype(rb_path, 20)
rr_12 = ImageFont.truetype(rr_path, 12)
# Set up LSM303D motion sensor
lsm = LSM303D(0x1d)
samples = []
points = []
sx, sy, sz = lsm.accelerometer() # Starting values to zero out accelerometer
sensitivity = 5 # Value from 1 to 10. Determines twitchiness of needle
# Function to thread accelerometer values separately to OLED drawing
def sample():
while True:
x, y, z = lsm.accelerometer()
x -= sx
y -= sy
z -= sz
v = y # Change this axis depending on orientation of breakout
# Scale up or down depending on sensitivity required
if v < 0:
v *= (100 * sensitivity)
else:
v *= (40 * sensitivity)
# Only keep 96 most recent values in list
points.append(v)
if len(points) > 96:
points.pop(0)
time.sleep(0.05)
# The thread to measure acclerometer values
t = Thread(target=sample)
t.daemon = True
t.start()
# Wait for at least one data oint
while len(points) == 0:
pass
# The main loop that draws values to the OLED
while True:
background = Image.open("images/seismograph.png").convert(oled.mode)
draw = ImageDraw.ImageDraw(background)
draw.line([(128, 64), (96, 64 + points[-1])], fill="white")
draw.line([(128, 63), (96, 64 + points[-1])], fill="white")
draw.line([(128, 65), (96, 64 + points[-1])], fill="white")
# Draw the seismograph trace
for i in range(1, len(points)):
draw.line([(i - 1, 64 + points[i - 1]), (i, 64 + points[i])], fill="white")
# Draw the Dino-Detect "branding"
draw.rectangle([(0, 0), (128, 20)], fill="black")
draw.text((0, 1), "AUS (A UNIX System)", fill="white", font=rr_12)
draw.line([(0, 20), (128, 20)], fill="white")
draw.rectangle([(0, 108), (128, 128)], fill="black")
draw.text((0, 110), "Dino-Detect v1.2 BETA", fill="white", font=rr_12)
draw.line([(0, 108), (128, 108)], fill="white")
# Display on the OLED
oled.display(background)

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)

View File

@@ -0,0 +1,49 @@
# UV warning example
This example uses a VEML6075 UVA/B sensor, a 5x5 RGB matrix, and a 1.2" OLED breakout
to display average UV index as a traffic light warning on the RGB matrix, with green
being a low UV index, yellow/orange being moderate, and red high or extreme. The numerical
values and a descriptive warning are displayed on the OLED.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [VEML6075 UVA/B Sensor Breakout Breakout](https://shop.pimoroni.com/products/veml6075-uva-b-sensor-breakout)
- A Pimoroni [5x5 RGB Matrix Breakout](https://shop.pimoroni.com/products/5x5-rgb-matrix-breakout)
- A Pimoroni [1.12" OLED Breakout (SPI)](https://shop.pimoroni.com/products/1-12-oled-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.12" OLED Breakout (SPI)
as follows:
```
git clone https://github.com/pimoroni/sh1106-python
sudo ./install.sh
```
This example assumes that you have the OLED 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 `device=1` to `device=0` on the line
where the OLED is set up.
## Running this example
To run this example, type `./uv-warning.py` in the terminal
## Notes
You might want rotate your Pi and Breakout Garden so that the UV sensor is facing
upwards at a better angle to detect UV light.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
import os
import sys
import time
import veml6075
import smbus
import colorsys
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
from luma.core.interface.serial import spi
from luma.core.render import canvas
from luma.oled.device import sh1106
from rgbmatrix5x5 import RGBMatrix5x5
print("""
This Pimoroni Breakout Garden example requires a VEML6075 UV
Breakout, a 5x5 RGB Matrix Breakout, and a 1.12" OLED Breakout (SPI).
The UV-O-Meter 3000 displays UV levels visually and in text.
Press Ctrl+C a couple times to exit.
""")
# Map UV index to a descriptive level
def uv_to_description(avg_uv_index):
if avg_uv_index < 3:
return "LOW"
elif 3 <= avg_uv_index < 6:
return "MEDIUM"
elif 6 <= avg_uv_index < 8:
return "HIGH"
elif 8 <= avg_uv_index < 11:
return "V. HIGH"
elif avg_uv_index > 11:
return "EXTREME"
else:
return ""
bus = smbus.SMBus(1)
# Set up UV sensor
uv_sensor = veml6075.VEML6075(i2c_dev=bus)
uv_sensor.set_shutdown(False)
uv_sensor.set_high_dynamic_range(False)
uv_sensor.set_integration_time('100ms')
# Set up RGB matrix
rgbmatrix5x5 = RGBMatrix5x5()
rgbmatrix5x5.set_clear_on_exit()
rgbmatrix5x5.set_brightness(1.0)
# Set up OLED
oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)
# Load fonts
rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto-Regular.ttf'))
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts', 'Roboto-Black.ttf'))
rb_15 = ImageFont.truetype(rb_path, 15)
rr_15 = ImageFont.truetype(rr_path, 15)
while True:
try:
img = Image.open("images/uv-warning.png").convert(oled.mode)
draw = ImageDraw.Draw(img)
draw.rectangle([(0, 0), (128, 128)], fill="black")
# Get UV data and calculate indices
uva, uvb = uv_sensor.get_measurements()
uv_comp1, uv_comp2 = uv_sensor.get_comparitor_readings()
uv_indices = uv_sensor.convert_to_index(uva, uvb, uv_comp1, uv_comp2)
uva_index, uvb_index, avg_uv_index = uv_indices
# Draw UV data to OLED
draw.text((0, 0), "UV-O-Meter 3000", fill="white", font=rb_15)
draw.text((0, 30), "UVA index: {:05.02f}".format(uva_index), fill="white", font=rr_15)
draw.text((0, 48), "UVB index: {:05.02f}".format(uvb_index), fill="white", font=rr_15)
draw.text((0, 66), "Avg. index: {:05.02f}".format(avg_uv_index), fill="white", font=rr_15)
draw.text((0, 100), "Level: {}".format(uv_to_description(avg_uv_index)), fill="white", font=rb_15)
# Map avg. UV index to colour, from green to red
hue = (10.001 - min(10, avg_uv_index)) / 30
sat = 1.0
val = 1.0
r, g, b = [int(255 * c) for c in colorsys.hsv_to_rgb(hue, sat, val)]
# Light RGB matrix with calculated colour
rgbmatrix5x5.set_all(r, g, b)
rgbmatrix5x5.show()
oled.display(img)
# Also print current UV index
print ('Avg. UV index: {}'.format(avg_uv_index))
except KeyboardInterrupt:
sys.exit(0)

View File

@@ -0,0 +1,53 @@
# Weather example
This example turns your Breakout Garden into a mini weather display
combining indoor temperature and pressure data with a weather icon
indicating the current local weather conditions.
## Pre-requisites
This example requires:
- A Pimoroni [Breakout Garden](https://shop.pimoroni.com/products/breakout-garden-hat-i2c-spi)
- A Pimoroni [BME680 Breakout](https://shop.pimoroni.com/products/bme680-breakout)
- A Pimoroni [1.12" OLED Breakout (SPI)](https://shop.pimoroni.com/products/1-12-oled-breakout)
You'll need the requests (`sudo pip install requests`), geocoder (`sudo pip install geocoder`),
and BeautifulSoup4 (`sudo pip install beautifulsoup4`) libraries to query the Dark Sky weather page.
## 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-lxml python3-pil
sudo pip3 install requests geocoder beautifulsoup4
```
You'll need to clone and install the library for the 1.12" OLED Breakout (SPI)
as follows:
```
git clone https://github.com/pimoroni/sh1106-python
sudo ./install.sh
```
This example assumes that you have the OLED 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 `device=1` to `device=0` on the line
where the OLED is set up.
## Running this example
To run this example, type `./weather.py` in the terminal
## Notes
This example uses Sheffield as the default location, so you'll need to specify your city and
country code at the top of the file, changing the variables called `CITY` and `COUNTRYCODE`
to your current location.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -0,0 +1,218 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import time
import datetime
import glob
import logging
import sys
try:
import requests
import geocoder
import lxml
from bs4 import BeautifulSoup
from PIL import Image
from PIL import ImageFont
from PIL import ImageDraw
except ImportError:
print("""
This script requires several modules to run correctly.
Install with:
sudo pip install requests geocoder beautifulsoup4
sudo apt install python{v}-lxml python{v}-pil
""".format(v="" if sys.version_info.major == 2 else sys.version_info.major))
sys.exit(1)
import bme680
from luma.core.interface.serial import spi
from luma.core.error import DeviceNotFoundError
from luma.oled.device import sh1106
TEMPERATURE_UPDATE_INTERVAL = 0.1 # in seconds
# Default to Sheffield-on-Sea for location
CITY = "Sheffield"
COUNTRYCODE = "GB"
# Used to calibrate the sensor
TEMP_OFFSET = 0.0
logging.basicConfig(level=os.environ.get("LOGLEVEL", "WARNING"))
print("""This Pimoroni Breakout Garden example requires a
BME680 Environmental Sensor Breakout and a 1.12" OLED Breakout.
This example turns your Breakout Garden into a mini weather display
combining indoor temperature and pressure data with a weather icon
indicating the current local weather conditions.
Press Ctrl+C a couple times to exit.
""")
# Convert a city name and country code to latitude and longitude
def get_coords(address):
g = geocoder.arcgis(address)
coords = g.latlng
logging.info("Location coordinates: %s", coords)
return coords
# Query Dark Sky (https://darksky.net/) to scrape current weather data
def get_weather(coords):
weather = {}
try:
res = requests.get("https://darksky.net/forecast/{}/uk212/en".format(","
.join([str(c) for c in coords])))
if res.status_code == 200:
soup = BeautifulSoup(res.content, "lxml")
curr = soup.find("span", "currently")
if curr:
img_name = curr.img["alt"].split()[0]
logging.info("Weather summary: %s", img_name)
weather["summary"] = img_name
except requests.exceptions.RequestException as e:
logging.error("Could not get weather data from DarkSky: {}".format(e))
pass
return weather
# This maps the weather summary from Dark Sky
# to the appropriate weather icons
icon_map = {
"snow": ["snow", "sleet"],
"rain": ["rain"],
"cloud": ["fog", "cloudy", "partly-cloudy-day", "partly-cloudy-night"],
"sun": ["clear-day", "clear-night"],
"storm": [],
"wind": ["wind"]
}
# Pre-load icons into a dictionary with PIL
icons = {}
for icon in glob.glob("icons/*.png"):
icon_name = icon.split("/")[1].replace(".png", "")
icon_image = Image.open(icon)
icons[icon_name] = icon_image
location_string = "{city}, {countrycode}".format(city=CITY,
countrycode=COUNTRYCODE)
coords = get_coords(location_string)
def get_weather_icon(weather):
if weather:
summary = weather["summary"]
for icon in icon_map:
if summary in icon_map[icon]:
logging.info("Weather icon: %s", icon)
return icons[icon]
logging.error("Could not determine icon for weather")
return None
else:
logging.error("No weather information provided to get icon")
return None
# Get initial weather data for the given location
weather_icon = get_weather_icon(get_weather(coords))
# Set up OLED
oled = sh1106(spi(port=0, device=1, gpio_DC=9), rotate=2, height=128, width=128)
# Set up BME680 sensor
sensor = bme680.BME680()
sensor.set_humidity_oversample(bme680.OS_2X)
sensor.set_pressure_oversample(bme680.OS_4X)
sensor.set_temperature_oversample(bme680.OS_8X)
sensor.set_filter(bme680.FILTER_SIZE_3)
sensor.set_temp_offset(TEMP_OFFSET)
# Load fonts
rr_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts',
'Roboto-Regular.ttf'))
rb_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'fonts',
'Roboto-Black.ttf'))
rr_24 = ImageFont.truetype(rr_path, 24)
rb_20 = ImageFont.truetype(rb_path, 20)
rr_12 = ImageFont.truetype(rr_path, 12)
# Fetch sensor dating first so that device settings take effect
sensor.get_sensor_data()
# Initial values
low_temp = sensor.data.temperature
high_temp = sensor.data.temperature
curr_date = datetime.date.today().day
last_checked = time.time()
# Main loop
while True:
# Limit calls to Dark Sky to 1 per minute
if time.time() - last_checked > 60:
weather_icon = get_weather_icon(get_weather(coords))
last_checked = time.time()
# Load in the background image
background = Image.open("images/weather.png").convert(oled.mode)
# Place the weather icon and draw the background
if weather_icon:
background.paste(weather_icon, (10, 46))
draw = ImageDraw.ImageDraw(background)
# Gets temp. and press. and keeps track of daily min and max temp
if sensor.get_sensor_data():
temp = sensor.data.temperature
press = sensor.data.pressure
if datetime.datetime.today().day == curr_date:
if temp < low_temp:
low_temp = temp
elif temp > high_temp:
high_temp = temp
else:
curr_date = datetime.datetime.today().day
low_temp = temp
high_temp = temp
# Write temp. and press. to image
draw.text((8, 22), "{0:4.0f}".format(press),
fill="white", font=rb_20)
draw.text((86, 12), u"{0:2.0f}°".format(temp),
fill="white", font=rb_20)
# Write min and max temp. to image
draw.text((80, 0), u"max: {0:2.0f}°".format(high_temp),
fill="white", font=rr_12)
draw.text((80, 110), u"min: {0:2.0f}°".format(low_temp),
fill="white", font=rr_12)
# Write the 24h time and blink the separator every second
if int(time.time()) % 2 == 0:
draw.text((4, 98), datetime.datetime.now().strftime("%H:%M"),
fill="white", font=rr_24)
else:
draw.text((4, 98), datetime.datetime.now().strftime("%H %M"),
fill="white", font=rr_24)
# These lines display the temp. on the thermometer image
draw.rectangle([(97, 43), (100, 86)], fill="black")
temp_offset = 86 - ((86 - 43) * ((temp - 20) / (32 - 20)))
draw.rectangle([(97, temp_offset), (100, 86)], fill="white")
# Display the completed image on the OLED
oled.display(background)
time.sleep(TEMPERATURE_UPDATE_INTERVAL)

57
vendor/breakout-garden/install-legacy.sh vendored Executable file
View File

@@ -0,0 +1,57 @@
#!/bin/bash
printf "Breakout Garden: Auto Installer\n\n"
if [ $(id -u) -ne 0 ]; then
printf "Script must be run as root. Try 'sudo ./install.sh'\n"
exit 1
fi
printf "Please plug the breakouts you would like to install into your Breakout Garden\n\n"
read -p "Press enter to continue..."
printf "\n"
WORKING_DIR=`pwd`
TMP_DIR="/tmp/breakout-garden"
results=`python autodetect.py`
found=`echo "$results" | wc -l`
if [[ "$found" -eq "0" ]] || [[ "$results" == "" ]]; then
printf "Sorry, I couldn't find any breakouts!\n"
exit 1
fi
printf "Found $found breadkout(s):\n"
echo "$results" | while read line; do
printf "$line\n"
done
printf "\n"
read -p "Press enter to continue..."
if [[ ! -d "$TMP_DIR" ]]; then
mkdir "$TMP_DIR"
fi
python autodetect.py --install | while read line; do
printf "Installing $line\n"
git_dir="$line"
cd $TMP_DIR
if [[ ! -d "$git_dir/.git" ]]; then
rm -f $git_dir
git clone https://github.com/pimoroni/$line $git_dir
fi
cd $git_dir
git pull origin master
if [[ -f "install.sh" ]]; then
./install.sh
else
printf "Warning: No install.sh found for $line. Skipping!";
fi
cd $WORKING_DIR
done
printf "\nBreakout Garden setup complete! Enjoy your breakouts.\n"

313
vendor/breakout-garden/install.sh vendored Executable file
View File

@@ -0,0 +1,313 @@
#!/bin/bash
WORKING_DIR=`pwd`
TMP_DIR="/tmp/breakout-garden"
LOG_FILE="$TMP_DIR/install.log"
ACTION="install"
VERBOSE=""
FORCE=""
DEVICES=()
STATUSES=()
REPOS=()
PADDING=5
success() {
printf "$(tput setaf 2)$1$(tput setaf 7)"
}
inform() {
printf "$(tput setaf 6)$1$(tput setaf 7)"
}
warning() {
printf "$(tput setaf 1)$1$(tput setaf 7)"
}
if [ $(id -u) -ne 0 ]; then
printf "Breakout Garden: Installer\n\n"
inform "Script must be run as root. Try 'sudo ./install.sh'\n"
exit 1
fi
if [ ! -c "/dev/i2c-1" ]; then
raspi-config nonint do_i2c 0
STATUS=$?
if [ $STATUS -eq 0 ]; then
inform "\nBreakout Garden requires I2C. We've enabled it for you.\n"
else
warning "\nWarning, Breakout Garden requires I2C but we couldn't enable it.\n"
printf "\nPlease try 'curl https://get.pimoroni.com/i2c | bash' to enable I2C first.\n"
exit 1
fi
sleep 0.1
fi
if [ ! -d "$TMP_DIR" ]; then
mkdir $TMP_DIR
fi
while getopts "uvf" option; do
case $option in
u ) ACTION="uninstall";VERBOSE="true";;
v ) VERBOSE="true";;
f ) FORCE="true";;
\? ) printf "Invalid option: -$OPTARG\n"; exit 1;;
esac
done
DETECTED=`python autodetect.py --install`
COUNT=`echo -e "$DETECTED" | wc -l`
if [[ "$COUNT" -eq "0" ]] || [[ "$DETECTED" == "" ]]; then
printf "Sorry, I couldn't find any breakouts!\n"
exit 1
fi
if [[ -f "/usr/bin/python3" ]]; then
DETECTED3=`python3 autodetect.py --install`
fi
array_index () {
local -n array=$1
string=$2
for i in "${!array[@]}"; do
if [[ "${array[$i]}" = "$string" ]]; then
return $i
fi
done
return -1
}
check_status () {
index=0
# Iterate through Python3 detected packages
# and build out arrays of devices, statuses and Git repos
while read line; do
IFS='|' read -r -a package <<< "$line"
package_name=${package[0]}
package_library=${package[1]}
package_status=${package[2]}
DEVICES[$index]=$package_name
STATUSES[$index]=$package_status
REPOS[$index]=$package_library
index=$(($index+1))
done < <(echo -e "$DETECTED")
# Iterate through Python3 detected packages
# and update status to required if they are missing
if [[ ! "$DETECTED3" == "" ]]; then
while read line; do
IFS='|' read -r -a package <<< "$line"
package_name=${package[0]}
package_library=${package[1]}
package_status=${package[2]}
# Find the index of the package in our original array
# that we produced in the first loop above
array_index DEVICES "$package_name"
index=$?
if [[ ! "$index" == "-1" ]] && [[ "$package_status" == "required" ]]; then
STATUSES[$index]=$package_status
fi
done < <(echo -e "$DETECTED3")
fi
}
do_uninstall () {
index=$1
package_name=${DEVICES[$index]}
package_library=${REPOS[$index]}
git_dir=$package_library
cd $TMP_DIR
if [[ ! -d "$git_dir/.git" ]]; then
if [[ ! "$VERBOSE" = "" ]]; then
rm -f $git_dir > $LOG_FILE
git clone https://github.com/pimoroni/$package_library $git_dir
else
rm -f $git_dir > $LOG_FILE 2>&1
git clone https://github.com/pimoroni/$package_library $git_dir > $LOG_FILE 2>&1
fi
fi
cd $git_dir
git pull origin master > $LOG_FILE 2>&1
if [[ -f "uninstall.sh" ]]; then
if [[ ! "$VERBOSE" == "" ]]; then
./uninstall.sh
else
./uninstall.sh > $LOG_FILE 2>&1
fi
else
if [[ ! "$VERBOSE" == "" ]]; then
echo "Warning: No uninstall.sh found for $package_name."
else
echo "Warning: No uninstall.sh found for $package_name." > $LOG_FILE 2>&1
fi
STATUSES[$index]="error"
return 1
fi
cd $TMP_DIR
rm -r $git_dir
cd $WORKING_DIR
STATUSES[$index]="uninstalled"
}
do_install () {
index=$1
package_name=${DEVICES[$index]}
package_library=${REPOS[$index]}
git_dir=$package_library
cd $TMP_DIR
if [[ ! -d "$git_dir/.git" ]]; then
if [[ ! "$VERBOSE" == "" ]]; then
rm -f $git_dir > $LOG_FILE
git clone https://github.com/pimoroni/$package_library $git_dir
else
rm -f $git_dir > $LOG_FILE 2>&1
git clone https://github.com/pimoroni/$package_library $git_dir > $LOG_FILE 2>&1
fi
fi
cd $git_dir
git pull origin master > $LOG_FILE 2>&1
if [[ -f "install.sh" ]]; then
if [[ ! "$VERBOSE" == "" ]]; then
"./install.sh"
else
"./install.sh" > $LOG_FILE 2>&1
fi
else
echo "Warning: No install.sh found for $package_name." > $LOG_FILE 2>&1
STATUSES[$index]="error"
return 1
fi
cd $WORKING_DIR
STATUSES[$index]="installed"
}
display () {
if [[ "$VERBOSE" == "" ]]; then
lines=$(tput lines)
lines=$(($lines-$COUNT-$PADDING))
tput cup $lines 0
fi
for ((i = 0; i < $COUNT; i++)); do
ITEM=${DEVICES[$i]}
STATUS=${STATUSES[$i]}
if [[ "$VERBOSE" == "" ]] || [[ "" == "$1" ]] || [[ "$i" == "$1" ]]; then
printf "%-30s %s" "$ITEM:" " "
case $STATUS in
"error"*)
warning "Error! ";;
"required"*)
warning "Required ";;
"installed"*)
success "Installed ";;
"uninstalled"*)
success "Uninstalled ";;
"uninstalling"*)
inform "Uninstalling...";;
"installing"*)
inform "Installing... ";;
"reinstalling"*)
inform "Reinstalling...";;
esac
printf "\n"
fi
done
}
check_status
installs_required=0
uninstalls_required=0
printf "\n"
printf "Breakout Garden: Installer. ($COUNT breakout(s) found) \n\n"
for ((y = 0; y < $COUNT; y++)); do
if [[ "$VERBOSE" == "" ]]; then
printf "\n"
fi
if [[ "${STATUSES[$y]}" == "required" ]] || [[ ! "$FORCE" == "" ]]; then
installs_required=$(($installs_required+1))
fi
if [[ "$ACTION" == "uninstall" ]] && [[ "${STATUSES[$y]}" == "installed" ]]; then
uninstalls_required=$(($uninstalls_required+1))
fi
done
if [[ "$VERBOSE" == "" ]]; then
printf "\n\n\n\n"
fi
display
printf "\n"
if [[ "$ACTION" == "install" ]]; then
if [[ "$installs_required" == "0" ]]; then
read -p "Nothing to do! Press enter to quit..."
else
action_text="Installing"
if [[ ! "$FORCE" == "" ]]; then
forced_mode=" (forced)"
action_text="Reinstalling"
fi
read -p "$action_text $installs_required module(s)$forced_mode. Enter to continue (Ctrl+C to cancel)..."
for ((y = 0; y < $COUNT; y++)); do
STATUS=${STATUSES[$y]}
if [[ ! "$STATUS" == "installed" ]] || [[ ! "$FORCE" == "" ]]; then
if [[ ! "$FORCE" == "" ]]; then
STATUSES[$y]="reinstalling"
else
STATUSES[$y]="installing"
fi
display $y
do_install $y
display $y
fi
done
fi
fi
if [[ "$ACTION" == "uninstall" ]]; then
if [[ "$uninstalls_required" == "0" ]]; then
read -p "Nothing to do! Press enter to quit..."
else
read -p "Removing $uninstalls_required module(s). Enter to continue (Ctrl+C to cancel)..."
for ((y = 0; y < $COUNT; y++)); do
STATUS=${STATUSES[$y]}
if [[ "$STATUS" == "installed" ]]; then
STATUSES[$y]="uninstalling"
display $y
do_uninstall $y
display $y
fi
done
fi
fi
printf "\n\n"
for ((y = 0; y < $COUNT; y++)); do
STATUS=${STATUSES[$y]}
if [[ "$STATUS" == "error" ]]; then
warning "Errors occured during $ACTION. For more info see $LOG_FILE"
printf "\n"
break;
fi
done

View File

@@ -0,0 +1,37 @@
name: Python Tests
on:
pull_request:
push:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python: [2.7, 3.5, 3.7, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python }}
- name: Install Dependencies
run: |
python -m pip install --upgrade setuptools tox
- name: Run Tests
working-directory: library
run: |
tox -e py
- name: Coverage
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
working-directory: library
run: |
python -m pip install coveralls
coveralls --service=github
if: ${{ matrix.python == '3.9' }}

20
vendor/ltp305-python/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
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

5
vendor/ltp305-python/.stickler.yml vendored Normal file
View File

@@ -0,0 +1,5 @@
---
linters:
flake8:
python: 3
max-line-length: 160

21
vendor/ltp305-python/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Pimoroni Ltd.
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.

70
vendor/ltp305-python/Makefile vendored Normal file
View File

@@ -0,0 +1,70 @@
LIBRARY_VERSION=$(shell grep version library/setup.cfg | awk -F" = " '{print $$2}')
LIBRARY_NAME=$(shell grep name library/setup.cfg | awk -F" = " '{print $$2}')
.PHONY: usage install uninstall
usage:
@echo "Library: ${LIBRARY_NAME}"
@echo "Version: ${LIBRARY_VERSION}\n"
@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 "check: peform basic integrity checks on the codebase"
@echo "python-readme: generate library/README.md from README.md + library/CHANGELOG.txt"
@echo "python-wheels: build python .whl files for distribution"
@echo "python-sdist: build python source distribution"
@echo "python-clean: clean python build and dist directories"
@echo "python-dist: build all python distribution files"
@echo "python-testdeploy: build all and deploy to test PyPi"
@echo "tag: tag the repository with the current version"
install:
./install.sh
uninstall:
./uninstall.sh
check:
@echo "Checking for trailing whitespace"
@! grep -IUrn --color "[[:blank:]]$$" --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=PKG-INFO
@echo "Checking for DOS line-endings"
@! grep -IUrn --color "
" --exclude-dir=sphinx --exclude-dir=.tox --exclude-dir=.git --exclude=Makefile
@echo "Checking library/CHANGELOG.txt"
@cat library/CHANGELOG.txt | grep ^${LIBRARY_VERSION}
@echo "Checking library/${LIBRARY_NAME}/__init__.py"
@cat library/${LIBRARY_NAME}/__init__.py | grep "^__version__ = '${LIBRARY_VERSION}'"
tag:
git tag -a "v${LIBRARY_VERSION}" -m "Version ${LIBRARY_VERSION}"
python-readme: library/README.md
python-license: library/LICENSE.txt
library/README.md: README.md library/CHANGELOG.txt
cp README.md library/README.md
printf "\n# Changelog\n" >> library/README.md
cat library/CHANGELOG.txt >> library/README.md
library/LICENSE.txt: LICENSE
cp LICENSE library/LICENSE.txt
python-wheels: python-readme python-license
cd library; python3 setup.py bdist_wheel
cd library; python setup.py bdist_wheel
python-sdist: python-readme python-license
cd library; python setup.py sdist
python-clean:
-rm -r library/dist
-rm -r library/build
-rm -r library/*.egg-info
python-dist: python-clean python-wheels python-sdist
ls library/dist
python-testdeploy: python-dist
twine upload --repository-url https://test.pypi.org/legacy/ library/dist/*
python-deploy: check python-dist

27
vendor/ltp305-python/README.md vendored Normal file
View File

@@ -0,0 +1,27 @@
# LTP305 - Breakout Garden dual 5x7 LED matrix driver
[![Build Status](https://travis-ci.com/pimoroni/ltp305-python.svg?branch=master)](https://travis-ci.com/pimoroni/ltp305-python)
[![Coverage Status](https://coveralls.io/repos/github/pimoroni/ltp305-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/ltp305-python?branch=master)
[![PyPi Package](https://img.shields.io/pypi/v/ltp305.svg)](https://pypi.python.org/pypi/ltp305)
[![Python Versions](https://img.shields.io/pypi/pyversions/ltp305.svg)](https://pypi.python.org/pypi/ltp305)
# Pre-requisites
You must enable i2c:
* i2c: `sudo raspi-config nonint do_i2c 0`
You can optionally run `sudo raspi-config` or the graphical Raspberry Pi Configuration UI to enable interfaces.
# Installing
Stable library from PyPi:
* Just run `sudo pip3 install ltp305`
Latest/development library from GitHub:
* `git clone https://github.com/pimoroni/ltp305-python`
* `cd ltp305-python`
* `sudo ./install.sh`

128
vendor/ltp305-python/REFERENCE.md vendored Normal file
View File

@@ -0,0 +1,128 @@
# Pimoroni LTP305 LED Matrices Breakout <!-- omit in toc -->
The LTP305 breakout includes two LTP305 displays and an i2c dual matrix driver to drive them.
- [Getting Started](#getting-started)
- [Pre-requisites](#pre-requisites)
- [Python 3 & pip](#python-3--pip)
- [Enabling i2c](#enabling-i2c)
- [Installing the library](#installing-the-library)
- [Reference](#reference)
- [set_pixel](#set_pixel)
- [set_decimal](#set_decimal)
- [set_character](#set_character)
- [set_image](#set_image)
- [get_shape](#get_shape)
- [clear](#clear)
- [show](#show)
## Getting Started
You'll need to install the LTP305 software library and enable i2c on your Raspberry Pi.
### Pre-requisites
#### Python 3 & pip
You should use Python 3, which may need installing on your Pi:
```
sudo apt update
sudo apt install python3 python3-pip
```
#### Enabling i2c
You can use `sudo raspi-config` on the command line, the GUI Raspberry Pi Configuration app from the Pi desktop menu, or use the following command to enable i2c:
```
sudo raspi-config nonint do_i2c 0
```
### Installing the library
```python
python3 -m pip install ltp305
```
## Reference
In all cases you'll want to create an instance of the LTP305 class with the appropriate i2c address for your device.
The following addresses are available:
* `0x61` - Default address
* `0x62` - Solder bridge applied to Addr+1
* `0x63` - Cut Addr+2
```python
from ltp305 import LTP305
display = LTP305(address=0x61)
```
### set_pixel
Set a single pixel. Treats both matrices as a single display that's 10 pixels wide and 7 pixels high.
```python
display.set_pixel(0, 0, 1)
display.show()
```
### set_decimal
Set the decimal dot on one or both matrix displays:
```python
display.set_decimal(left=True, right=False) # Just the left
display.set_decimal(left=False, right=True) # Just the right
display.set_decimal(left=True, right=True) # Both
display.show()
```
### set_character
The library includes a font with a range of ASCII and Unicode characters, each matrix can display a single character.
```python
display.set_character(0, "a")
display.set_character(5, "b")
display.show()
```
### set_image
Set a 1-bit PIL image to the display.
```python
from PIL import Image, ImageDraw
image = Image.new("1", display.get_shape())
draw = ImageDraw.draw(image)
draw.rectangle((0, 0, 4, 6), 1)
display.set_image(image)
display.show()
```
### get_shape
Return the width and height of the display:
```python
width, height = display.get_shape()
```
### clear
Clear the display. Sets all pixels in the buffer to 0 (off). You must call `show()` to update the display.
```python
display.clear()
```
### show
Once you've finished setting pixels or drawing characters into the buffer, you must call `show` to update the matrices with the buffer:
```python
display.show()
```

View File

@@ -0,0 +1,43 @@
import time
import math
import random
from ltp305 import LTP305
print("""bargraph.py - A basic graph example
Displays an animated rising/falling bar on the left matrix,
and a scrolling bar graph on the right.
Press Ctrl+C to exit!
""")
display = LTP305()
width, height = display.get_shape()
values = [0, 0, 0, 0, 0]
while True:
# Add a new random value to our list and prune the list to visible values
values.insert(0, random.randint(0, height))
values = values[:width]
# Animate a value from 0 to height + 1
value = (math.sin(time.time() * math.pi) + 1) / 2.0
value *= height + 1
value = math.floor(value)
for y in range(height):
y = height - 1 - y
for x in range(width // 2):
# Left
display.set_pixel(x, y, value <= y)
# Right
display.set_pixel(x + (width // 2), y, values[x] <= y)
time.sleep(1.0 / height)
display.show()

50
vendor/ltp305-python/examples/clock.py vendored Normal file
View File

@@ -0,0 +1,50 @@
import sys
import time
from ltp305 import LTP305
print("""clock.py - clock segment example.
Usage: python3 clock.py <hour/minute/second> <i2c-address>
This simple clock example will display the hour, minute or second on a single matrix display breakout.
Press Ctrl+C to exit.
""")
fmt = "%M"
address = 0x61
available = {
"hour": "%H",
"minute": "%M",
"second": "%S"
}
if len(sys.argv) > 1:
try:
fmt = available[sys.argv[1]]
except KeyError:
raise ValueError("{} is not supported!".format(sys.argv[1]))
if len(sys.argv) > 2:
address = int(sys.argv[2], 16)
if address not in [0x61, 0x62, 0x63]:
raise ValueError("Invalid i2c address: 0x{:02x}. Run `i2c-detect -y 1` to discover breakouts.".format(address))
try:
display = LTP305(address=address)
display.clear()
display.show()
except OSError:
raise OSError("Unable to find LTP305 on i2c address: 0x{:02x}. Run `i2c-detect -y 1` to discover breakouts".format(address))
while True:
minute = time.strftime(fmt)
left, right = minute
display.set_character(0, left)
display.set_character(5, right)
display.show()
time.sleep(1.0 / 60)

49
vendor/ltp305-python/examples/eyes.py vendored Normal file
View File

@@ -0,0 +1,49 @@
import math
import time
import random
print("""eyes.py - They're watching you!
Press Ctrl+C to exit!
""")
from ltp305 import LTP305
display = LTP305()
def eye(x, y):
display.set_pixel(x, y, True)
display.set_pixel(x, y + 1, True)
display.set_pixel(x + 1, y, True)
display.set_pixel(x + 1, y + 1, True)
while True:
t = time.time() * math.pi
# Get eye x and y positions in the range 0.0 to 1.0
# You can plug in your own 0.0 to 1.0 values here
x = (math.sin(t / 2) + 1) / 2
y = (math.sin(t / 4) + 1) / 2
# Multiply them up to display coords and convert to itn
x = math.floor(x * 4)
y = math.floor(y * 6)
# Clear the display
display.clear()
# Blink occasionally
if not random.randint(0, 20) == 2:
# Draw the eyes if not blinking
eye(x, y)
eye(x + 5, y)
# Update the display at 10FPS
# This gives our crude blink code time to not look like a random flicker
display.show()
time.sleep(1.0 / 10)

254
vendor/ltp305-python/install-bullseye.sh vendored Executable file
View File

@@ -0,0 +1,254 @@
#!/bin/bash
CONFIG=/boot/config.txt
DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"`
CONFIG_BACKUP=false
APT_HAS_UPDATED=false
USER_HOME=/home/$SUDO_USER
RESOURCES_TOP_DIR=$USER_HOME/Pimoroni
WD=`pwd`
USAGE="sudo ./install.sh (--unstable)"
POSITIONAL_ARGS=()
UNSTABLE=false
PYTHON="/usr/bin/python3"
CODENAME=`lsb_release -sc`
distro_check() {
if [[ $CODENAME != "bullseye" ]]; then
printf "This installer is for Raspberry Pi OS: Bullseye only, current distro: $CODENAME\n"
exit 1
fi
}
user_check() {
if [ $(id -u) -ne 0 ]; then
printf "Script must be run as root. Try 'sudo ./install.sh'\n"
exit 1
fi
}
confirm() {
if [ "$FORCE" == '-y' ]; 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)"
}
function do_config_backup {
if [ ! $CONFIG_BACKUP == true ]; then
CONFIG_BACKUP=true
FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt"
inform "Backing up $CONFIG to /boot/$FILENAME\n"
cp $CONFIG /boot/$FILENAME
mkdir -p $RESOURCES_TOP_DIR/config-backups/
cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME
if [ -f "$UNINSTALLER" ]; then
echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER
fi
fi
}
function apt_pkg_install {
PACKAGES=()
PACKAGES_IN=("$@")
for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do
PACKAGE="${PACKAGES_IN[$i]}"
if [ "$PACKAGE" == "" ]; then continue; fi
printf "Checking for $PACKAGE\n"
dpkg -L $PACKAGE > /dev/null 2>&1
if [ "$?" == "1" ]; then
PACKAGES+=("$PACKAGE")
fi
done
PACKAGES="${PACKAGES[@]}"
if ! [ "$PACKAGES" == "" ]; then
echo "Installing missing packages: $PACKAGES"
if [ ! $APT_HAS_UPDATED ]; then
apt update
APT_HAS_UPDATED=true
fi
apt install -y $PACKAGES
if [ -f "$UNINSTALLER" ]; then
echo "apt uninstall -y $PACKAGES"
fi
fi
}
while [[ $# -gt 0 ]]; do
K="$1"
case $K in
-u|--unstable)
UNSTABLE=true
shift
;;
-p|--python)
PYTHON=$2
shift
shift
;;
*)
if [[ $1 == -* ]]; then
printf "Unrecognised option: $1\n";
printf "Usage: $USAGE\n";
exit 1
fi
POSITIONAL_ARGS+=("$1")
shift
esac
done
distro_check
user_check
if [ ! -f "$PYTHON" ]; then
printf "Python path $PYTHON not found!\n"
exit 1
fi
PYTHON_VER=`$PYTHON --version`
inform "Installing. Please wait..."
$PYTHON -m pip install --upgrade configparser
CONFIG_VARS=`$PYTHON - <<EOF
from configparser import ConfigParser
c = ConfigParser()
c.read('library/setup.cfg')
p = dict(c['pimoroni'])
# Convert multi-line config entries into bash arrays
for k in p.keys():
fmt = '"{}"'
if '\n' in p[k]:
p[k] = "'\n\t'".join(p[k].split('\n')[1:])
fmt = "('{}')"
p[k] = fmt.format(p[k])
print("""
LIBRARY_NAME="{name}"
LIBRARY_VERSION="{version}"
""".format(**c['metadata']))
print("""
PY3_DEPS={py3deps}
PY2_DEPS={py2deps}
SETUP_CMDS={commands}
CONFIG_TXT={configtxt}
""".format(**p))
EOF`
if [ $? -ne 0 ]; then
warning "Error parsing configuration...\n"
exit 1
fi
eval $CONFIG_VARS
RESOURCES_DIR=$RESOURCES_TOP_DIR/$LIBRARY_NAME
UNINSTALLER=$RESOURCES_DIR/uninstall.sh
mkdir -p $RESOURCES_DIR
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
EOF
printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n"
if $UNSTABLE; then
warning "Installing unstable library from source.\n\n"
else
printf "Installing stable library from pypi.\n\n"
fi
cd library
printf "Installing for $PYTHON_VER...\n"
apt_pkg_install "${PY3_DEPS[@]}"
if $UNSTABLE; then
$PYTHON setup.py install > /dev/null
else
$PYTHON -m pip install --upgrade $LIBRARY_NAME
fi
if [ $? -eq 0 ]; then
success "Done!\n"
echo "$PYTHON -m pip uninstall $LIBRARY_NAME" >> $UNINSTALLER
fi
cd $WD
for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do
CMD="${SETUP_CMDS[$i]}"
# Attempt to catch anything that touches /boot/config.txt and trigger a backup
if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then
do_config_backup
fi
eval $CMD
done
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\n"
sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG
if ! grep -q "^$CONFIG_LINE" $CONFIG; then
printf "$CONFIG_LINE\n" >> $CONFIG
fi
fi
done
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"
if [ -f "/usr/bin/pydoc" ]; then
printf "Generating documentation.\n"
pydoc -w $LIBRARY_NAME > /dev/null
if [ -f "$LIBRARY_NAME.html" ]; then
cp $LIBRARY_NAME.html $RESOURCES_DIR/docs.html
rm -f $LIBRARY_NAME.html
inform "Documentation saved to $RESOURCES_DIR/docs.html"
success "Done!"
else
warning "Error: Failed to generate documentation."
fi
fi
success "\nAll done!"
inform "If this is your first time installing you should reboot for hardware changes to take effect.\n"
inform "Find uninstall steps in $UNINSTALLER\n"

251
vendor/ltp305-python/install.sh vendored Executable file
View File

@@ -0,0 +1,251 @@
#!/bin/bash
CONFIG=/boot/config.txt
DATESTAMP=`date "+%Y-%m-%d-%H-%M-%S"`
CONFIG_BACKUP=false
APT_HAS_UPDATED=false
USER_HOME=/home/$SUDO_USER
RESOURCES_TOP_DIR=$USER_HOME/Pimoroni
WD=`pwd`
USAGE="sudo ./install.sh (--unstable)"
POSITIONAL_ARGS=()
UNSTABLE=false
CODENAME=`lsb_release -sc`
if [[ $CODENAME == "bullseye" ]]; then
bash ./install-bullseye.sh
exit $?
fi
user_check() {
if [ $(id -u) -ne 0 ]; then
printf "Script must be run as root. Try 'sudo ./install.sh'\n"
exit 1
fi
}
confirm() {
if [ "$FORCE" == '-y' ]; 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)"
}
function do_config_backup {
if [ ! $CONFIG_BACKUP == true ]; then
CONFIG_BACKUP=true
FILENAME="config.preinstall-$LIBRARY_NAME-$DATESTAMP.txt"
inform "Backing up $CONFIG to /boot/$FILENAME\n"
cp $CONFIG /boot/$FILENAME
mkdir -p $RESOURCES_TOP_DIR/config-backups/
cp $CONFIG $RESOURCES_TOP_DIR/config-backups/$FILENAME
if [ -f "$UNINSTALLER" ]; then
echo "cp $RESOURCES_TOP_DIR/config-backups/$FILENAME $CONFIG" >> $UNINSTALLER
fi
fi
}
function apt_pkg_install {
PACKAGES=()
PACKAGES_IN=("$@")
for ((i = 0; i < ${#PACKAGES_IN[@]}; i++)); do
PACKAGE="${PACKAGES_IN[$i]}"
if [ "$PACKAGE" == "" ]; then continue; fi
printf "Checking for $PACKAGE\n"
dpkg -L $PACKAGE > /dev/null 2>&1
if [ "$?" == "1" ]; then
PACKAGES+=("$PACKAGE")
fi
done
PACKAGES="${PACKAGES[@]}"
if ! [ "$PACKAGES" == "" ]; then
echo "Installing missing packages: $PACKAGES"
if [ ! $APT_HAS_UPDATED ]; then
apt update
APT_HAS_UPDATED=true
fi
apt install -y $PACKAGES
if [ -f "$UNINSTALLER" ]; then
echo "apt uninstall -y $PACKAGES"
fi
fi
}
while [[ $# -gt 0 ]]; do
K="$1"
case $K in
-u|--unstable)
UNSTABLE=true
shift
;;
*)
if [[ $1 == -* ]]; then
printf "Unrecognised option: $1\n";
printf "Usage: $USAGE\n";
exit 1
fi
POSITIONAL_ARGS+=("$1")
shift
esac
done
user_check
apt_pkg_install python-configparser
CONFIG_VARS=`python - <<EOF
from configparser import ConfigParser
c = ConfigParser()
c.read('library/setup.cfg')
p = dict(c['pimoroni'])
# Convert multi-line config entries into bash arrays
for k in p.keys():
fmt = '"{}"'
if '\n' in p[k]:
p[k] = "'\n\t'".join(p[k].split('\n')[1:])
fmt = "('{}')"
p[k] = fmt.format(p[k])
print("""
LIBRARY_NAME="{name}"
LIBRARY_VERSION="{version}"
""".format(**c['metadata']))
print("""
PY3_DEPS={py3deps}
PY2_DEPS={py2deps}
SETUP_CMDS={commands}
CONFIG_TXT={configtxt}
""".format(**p))
EOF`
if [ $? -ne 0 ]; then
warning "Error parsing configuration...\n"
exit 1
fi
eval $CONFIG_VARS
RESOURCES_DIR=$RESOURCES_TOP_DIR/$LIBRARY_NAME
UNINSTALLER=$RESOURCES_DIR/uninstall.sh
mkdir -p $RESOURCES_DIR
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
EOF
printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Installer\n\n"
if $UNSTABLE; then
warning "Installing unstable library from source.\n\n"
else
printf "Installing stable library from pypi.\n\n"
fi
cd library
printf "Installing for Python 2..\n"
apt_pkg_install "${PY2_DEPS[@]}"
if $UNSTABLE; then
python setup.py install > /dev/null
else
pip install --upgrade $LIBRARY_NAME
fi
if [ $? -eq 0 ]; then
success "Done!\n"
echo "pip uninstall $LIBRARY_NAME" >> $UNINSTALLER
fi
if [ -f "/usr/bin/python3" ]; then
printf "Installing for Python 3..\n"
apt_pkg_install "${PY3_DEPS[@]}"
if $UNSTABLE; then
python3 setup.py install > /dev/null
else
pip3 install --upgrade $LIBRARY_NAME
fi
if [ $? -eq 0 ]; then
success "Done!\n"
echo "pip3 uninstall $LIBRARY_NAME" >> $UNINSTALLER
fi
fi
cd $WD
for ((i = 0; i < ${#SETUP_CMDS[@]}; i++)); do
CMD="${SETUP_CMDS[$i]}"
# Attempt to catch anything that touches /boot/config.txt and trigger a backup
if [[ "$CMD" == *"raspi-config"* ]] || [[ "$CMD" == *"$CONFIG"* ]] || [[ "$CMD" == *"\$CONFIG"* ]]; then
do_config_backup
fi
eval $CMD
done
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\n"
sed -i "s/^#$CONFIG_LINE/$CONFIG_LINE/" $CONFIG
if ! grep -q "^$CONFIG_LINE" $CONFIG; then
printf "$CONFIG_LINE\n" >> $CONFIG
fi
fi
done
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"
if [ -f "/usr/bin/pydoc" ]; then
printf "Generating documentation.\n"
pydoc -w $LIBRARY_NAME > /dev/null
if [ -f "$LIBRARY_NAME.html" ]; then
cp $LIBRARY_NAME.html $RESOURCES_DIR/docs.html
rm -f $LIBRARY_NAME.html
inform "Documentation saved to $RESOURCES_DIR/docs.html"
success "Done!"
else
warning "Error: Failed to generate documentation."
fi
fi
success "\nAll done!"
inform "If this is your first time installing you should reboot for hardware changes to take effect.\n"
inform "Find uninstall steps in $UNINSTALLER\n"

View File

@@ -0,0 +1,4 @@
[run]
source = ltp305
omit =
.tox/*

View File

@@ -0,0 +1,4 @@
0.0.1
-----
* Initial Release

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Pimoroni Ltd.
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.

View File

@@ -0,0 +1,5 @@
include CHANGELOG.txt
include LICENSE.txt
include README.md
include setup.py
recursive-include ltp305 *.py

33
vendor/ltp305-python/library/README.md vendored Normal file
View File

@@ -0,0 +1,33 @@
# LTP305 - Breakout Garden dual 5x7 LED matrix driver
[![Build Status](https://travis-ci.com/pimoroni/ltp305-python.svg?branch=master)](https://travis-ci.com/pimoroni/ltp305-python)
[![Coverage Status](https://coveralls.io/repos/github/pimoroni/ltp305-python/badge.svg?branch=master)](https://coveralls.io/github/pimoroni/ltp305-python?branch=master)
[![PyPi Package](https://img.shields.io/pypi/v/ltp305.svg)](https://pypi.python.org/pypi/ltp305)
[![Python Versions](https://img.shields.io/pypi/pyversions/ltp305.svg)](https://pypi.python.org/pypi/ltp305)
# Pre-requisites
You must enable i2c:
* i2c: `sudo raspi-config nonint do_i2c 0`
You can optionally run `sudo raspi-config` or the graphical Raspberry Pi Configuration UI to enable interfaces.
# Installing
Stable library from PyPi:
* Just run `sudo pip3 install ltp305`
Latest/development library from GitHub:
* `git clone https://github.com/pimoroni/ltp305-python`
* `cd ltp305-python`
* `sudo ./install.sh`
# Changelog
0.0.1
-----
* Initial Release

View File

@@ -0,0 +1,205 @@
import smbus
from .font import font
__version__ = '0.0.1'
MODE = 0b00011000
OPTS = 0b00001110 # 1110 = 35mA, 0000 = 40mA
CMD_BRIGHTNESS = 0x19
CMD_MODE = 0x00
CMD_UPDATE = 0x0C
CMD_OPTIONS = 0x0D
CMD_MATRIX_L = 0x0E
CMD_MATRIX_R = 0x01
class LTP305:
"""
_buf_matrix_left = [
# Row 7654321
0b01111111, # col 1, bottom = msb
0b01111111, # col 2
0b01111111, # col 3
0b01111111, # col 4
0b01111111, # col 5
0b00000000,
0b00000000,
0b01000000 # bit 7 = decimal dot
]
_buf_matrix_right = [
# Col 12345
0b00011111, # row 1
0b00011111, # row 2
0b00011111, # row 3
0b00011111, # row 4
0b00011111, # row 5
0b00011111, # row 6
0b10011111, # row 7 + bit 8 = decimal dot
0b00000000
]
"""
def __init__(self, address=0x61, brightness=0.5):
"""LTP305 5x7 x 2 Matrix Driver
:param address: i2c address, one of 0x61, 0x62 or 0x63 (default 0x61)
:param brightness: LED brightness from 0.0 to 1.0 (default 0.5)
"""
self.bus = smbus.SMBus(1)
self.address = address
self.set_brightness(brightness)
self.clear()
def clear(self):
"""Clear both LED matrices.
Must call .show() to display changes.
"""
self._buf_matrix_left = [0 for _ in range(8)]
self._buf_matrix_right = [0 for _ in range(8)]
def set_brightness(self, brightness, update=False):
"""Set brightness of both LED matrices.
:param brightnes: LED brightness from 0.0 to 1.0
:param update: Push change to display immediately (otherwise you must call .show())
"""
self._brightness = int(brightness * 127.0)
self._brightness = min(127, max(0, self._brightness))
if update:
self.bus.write_byte_data(self.address, CMD_BRIGHTNESS, self._brightness)
def set_decimal(self, left=None, right=None):
"""Set decimal of left and/or right matrix.
:param left: State of left decimal dot
:param right: State of right decimal dot
"""
if left is not None:
if left:
self._buf_matrix_left[7] |= 0b01000000
else:
self._buf_matrix_left[7] &= 0b10111111
if right is not None:
if right:
self._buf_matrix_right[6] |= 0b10000000
else:
self._buf_matrix_right[6] &= 0b01111111
def set_pixel(self, x, y, c):
"""Set a single pixel on the matrix.
:param x: x position from 0 to 9 (0-4 on left matrix, 5-9 on right)
:param y: y position
:param c: state on/off
"""
if x < 5: # Left Matrix
if c:
self._buf_matrix_left[x] |= (0b1 << y)
else:
self._buf_matrix_left[x] &= ~(0b1 << y)
else: # Right Matrix
x -= 5
if c:
self._buf_matrix_right[y] |= (0b1 << x)
else:
self._buf_matrix_right[y] &= ~(0b1 << x)
def set_character(self, x, char):
"""Set a single character.
:param x: x position, 0 for left, 5 for right, or in between if you fancy
:param char: string character or char ordinal
"""
if type(char) is not int:
char = ord(char)
char = font[char]
for cx in range(5):
for cy in range(8):
c = char[cx] & (0b1 << cy)
self.set_pixel(x + cx, cy, c)
def get_shape(self):
"""Set the width/height of the display."""
return 10, 7
def set_image(self, image, offset_x=0, offset_y=0, wrap=False, bg=0):
"""Set a PIL image to the display buffer."""
image_width, image_height = image.size
if image.mode != "1":
image = image.convert("1")
display_width, display_height = self.get_shape()
for y in range(display_height):
for x in range(display_width):
p = bg
i_x = x + offset_x
i_y = y + offset_y
if wrap:
while i_x >= image_width:
i_x -= image_width
while i_y >= image_height:
i_y -= image_height
if i_x < image_width and i_y < image_height:
p = image.getpixel((i_x, i_y))
self.set_pixel(x, y, p)
def show(self):
"""Update the LED matrixes from the buffer."""
self.bus.write_i2c_block_data(self.address, CMD_MATRIX_L, self._buf_matrix_left)
self.bus.write_i2c_block_data(self.address, CMD_MATRIX_R, self._buf_matrix_right)
self.bus.write_byte_data(self.address, CMD_MODE, MODE)
self.bus.write_byte_data(self.address, CMD_OPTIONS, OPTS)
self.bus.write_byte_data(self.address, CMD_BRIGHTNESS, self._brightness)
self.bus.write_byte_data(self.address, CMD_UPDATE, 0x01)
if __name__ == "__main__":
import time
matrix = LTP305(0x61)
delay = 1.0 / 72 * 4
c = True
while True:
for n in range(10):
matrix.set_character(0, str(n))
matrix.set_character(5, 'abcdefghij'[n])
matrix.show()
time.sleep(0.1)
matrix.set_character(0, "=")
matrix.set_character(5, ")")
matrix.show()
time.sleep(1.0)
matrix.clear()
matrix.show()
time.sleep(0.5)
for _ in range(2):
for y in range(7):
for x in range(10):
matrix.set_pixel(x, y, c)
matrix.show()
time.sleep(delay)
matrix.set_decimal(left=c)
matrix.show()
time.sleep(delay)
matrix.set_decimal(right=c)
matrix.show()
time.sleep(delay)
c = not c

View File

@@ -0,0 +1,408 @@
# -*- coding: utf-8 -*-
tinynumbers = [
[0b11111, 0b11111],
[0b11111],
[0b10111, 0b11101],
[0b10101, 0b11111],
[0b11110, 0b00011],
[0b11101, 0b10111],
[0b11111, 0b00111],
[0b10000, 0b11111],
[0b10101, 0b10101],
[0b11100, 0b11111]
]
# add japanese
font = {
32: [0x00, 0x00, 0x00, 0x00, 0x00], # (space)
33: [0x00, 0x00, 0x5f, 0x00, 0x00], # !
34: [0x00, 0x07, 0x00, 0x07, 0x00], # "
35: [0x14, 0x7f, 0x14, 0x7f, 0x14], # #
36: [0x24, 0x2a, 0x7f, 0x2a, 0x12], # $
37: [0x23, 0x13, 0x08, 0x64, 0x62], # %
38: [0x36, 0x49, 0x55, 0x22, 0x50], # &
39: [0x00, 0x05, 0x03, 0x00, 0x00], # '
40: [0x00, 0x1c, 0x22, 0x41, 0x00], # (
41: [0x00, 0x41, 0x22, 0x1c, 0x00], # )
42: [0x08, 0x2a, 0x1c, 0x2a, 0x08], # *
43: [0x08, 0x08, 0x3e, 0x08, 0x08], # +
44: [0x00, 0x50, 0x30, 0x00, 0x00], # ,
45: [0x08, 0x08, 0x08, 0x08, 0x08], # -
46: [0x00, 0x60, 0x60, 0x00, 0x00], # .
47: [0x20, 0x10, 0x08, 0x04, 0x02], # /
48: [0x3e, 0x51, 0x49, 0x45, 0x3e], # 0
49: [0x00, 0x42, 0x7f, 0x40, 0x00], # 1
50: [0x42, 0x61, 0x51, 0x49, 0x46], # 2
51: [0x21, 0x41, 0x45, 0x4b, 0x31], # 3
52: [0x18, 0x14, 0x12, 0x7f, 0x10], # 4
53: [0x27, 0x45, 0x45, 0x45, 0x39], # 5
54: [0x3c, 0x4a, 0x49, 0x49, 0x30], # 6
55: [0x01, 0x71, 0x09, 0x05, 0x03], # 7
56: [0x36, 0x49, 0x49, 0x49, 0x36], # 8
57: [0x06, 0x49, 0x49, 0x29, 0x1e], # 9
58: [0x00, 0x36, 0x36, 0x00, 0x00], # :
59: [0x00, 0x56, 0x36, 0x00, 0x00], # ;
60: [0x00, 0x08, 0x14, 0x22, 0x41], # <
61: [0x14, 0x14, 0x14, 0x14, 0x14], # =
62: [0x41, 0x22, 0x14, 0x08, 0x00], # >
63: [0x02, 0x01, 0x51, 0x09, 0x06], # ?
64: [0x32, 0x49, 0x79, 0x41, 0x3e], # @
65: [0x7e, 0x11, 0x11, 0x11, 0x7e], # A
66: [0x7f, 0x49, 0x49, 0x49, 0x36], # B
67: [0x3e, 0x41, 0x41, 0x41, 0x22], # C
68: [0x7f, 0x41, 0x41, 0x22, 0x1c], # D
69: [0x7f, 0x49, 0x49, 0x49, 0x41], # E
70: [0x7f, 0x09, 0x09, 0x01, 0x01], # F
71: [0x3e, 0x41, 0x41, 0x51, 0x32], # G
72: [0x7f, 0x08, 0x08, 0x08, 0x7f], # H
73: [0x00, 0x41, 0x7f, 0x41, 0x00], # I
74: [0x20, 0x40, 0x41, 0x3f, 0x01], # J
75: [0x7f, 0x08, 0x14, 0x22, 0x41], # K
76: [0x7f, 0x40, 0x40, 0x40, 0x40], # L
77: [0x7f, 0x02, 0x04, 0x02, 0x7f], # M
78: [0x7f, 0x04, 0x08, 0x10, 0x7f], # N
79: [0x3e, 0x41, 0x41, 0x41, 0x3e], # O
80: [0x7f, 0x09, 0x09, 0x09, 0x06], # P
81: [0x3e, 0x41, 0x51, 0x21, 0x5e], # Q
82: [0x7f, 0x09, 0x19, 0x29, 0x46], # R
83: [0x46, 0x49, 0x49, 0x49, 0x31], # S
84: [0x01, 0x01, 0x7f, 0x01, 0x01], # T
85: [0x3f, 0x40, 0x40, 0x40, 0x3f], # U
86: [0x1f, 0x20, 0x40, 0x20, 0x1f], # V
87: [0x7f, 0x20, 0x18, 0x20, 0x7f], # W
88: [0x63, 0x14, 0x08, 0x14, 0x63], # X
89: [0x03, 0x04, 0x78, 0x04, 0x03], # Y
90: [0x61, 0x51, 0x49, 0x45, 0x43], # Z
91: [0x00, 0x00, 0x7f, 0x41, 0x41], # [
92: [0x02, 0x04, 0x08, 0x10, 0x20], # \
93: [0x41, 0x41, 0x7f, 0x00, 0x00], # ]
94: [0x04, 0x02, 0x01, 0x02, 0x04], # ^
95: [0x40, 0x40, 0x40, 0x40, 0x40], # _
96: [0x00, 0x01, 0x02, 0x04, 0x00], # `
97: [0x20, 0x54, 0x54, 0x54, 0x78], # a
98: [0x7f, 0x48, 0x44, 0x44, 0x38], # b
99: [0x38, 0x44, 0x44, 0x44, 0x20], # c
100: [0x38, 0x44, 0x44, 0x48, 0x7f], # d
101: [0x38, 0x54, 0x54, 0x54, 0x18], # e
102: [0x08, 0x7e, 0x09, 0x01, 0x02], # f
103: [0x08, 0x14, 0x54, 0x54, 0x3c], # g
104: [0x7f, 0x08, 0x04, 0x04, 0x78], # h
105: [0x00, 0x44, 0x7d, 0x40, 0x00], # i
106: [0x20, 0x40, 0x44, 0x3d, 0x00], # j
107: [0x00, 0x7f, 0x10, 0x28, 0x44], # k
108: [0x00, 0x41, 0x7f, 0x40, 0x00], # l
109: [0x7c, 0x04, 0x18, 0x04, 0x78], # m
110: [0x7c, 0x08, 0x04, 0x04, 0x78], # n
111: [0x38, 0x44, 0x44, 0x44, 0x38], # o
112: [0x7c, 0x14, 0x14, 0x14, 0x08], # p
113: [0x08, 0x14, 0x14, 0x18, 0x7c], # q
114: [0x7c, 0x08, 0x04, 0x04, 0x08], # r
115: [0x48, 0x54, 0x54, 0x54, 0x20], # s
116: [0x04, 0x3f, 0x44, 0x40, 0x20], # t
117: [0x3c, 0x40, 0x40, 0x20, 0x7c], # u
118: [0x1c, 0x20, 0x40, 0x20, 0x1c], # v
119: [0x3c, 0x40, 0x30, 0x40, 0x3c], # w
120: [0x44, 0x28, 0x10, 0x28, 0x44], # x
121: [0x0c, 0x50, 0x50, 0x50, 0x3c], # y
122: [0x44, 0x64, 0x54, 0x4c, 0x44], # z
123: [0x00, 0x08, 0x36, 0x41, 0x00], # {
124: [0x00, 0x00, 0x7f, 0x00, 0x00], # |
125: [0x00, 0x41, 0x36, 0x08, 0x00], # }
126: [0x08, 0x08, 0x2a, 0x1c, 0x08], # ~
8221: [0x00, 0x07, 0x00, 0x07, 0x00], # ”
8592: [0x08, 0x1C, 0x2A, 0x08, 0x08], # ←
8593: [0x08, 0x04, 0x7E, 0x04, 0x08], # ↑
8594: [0x08, 0x08, 0x2A, 0x1C, 0x08], # →
8595: [0x08, 0x10, 0x3F, 0x10, 0x08], # ↓
9472: [0x08, 0x08, 0x08, 0x08, 0x08], # ─
9474: [0x00, 0x00, 0x7F, 0x00, 0x00], # │
9484: [0x00, 0x00, 0x78, 0x08, 0x08], # ┌
9488: [0x08, 0x08, 0x78, 0x00, 0x00], # ┐
9492: [0x00, 0x00, 0x0F, 0x08, 0x08], # └
9496: [0x08, 0x08, 0x0F, 0x00, 0x00], # ┘
9500: [0x00, 0x00, 0x7F, 0x08, 0x08], # ├
9508: [0x08, 0x08, 0x7F, 0x00, 0x00], # ┤
9516: [0x08, 0x08, 0x78, 0x08, 0x08], # ┬
9524: [0x08, 0x08, 0x0F, 0x08, 0x08], # ┴
9532: [0x08, 0x08, 0x7F, 0x08, 0x08], # ┼
9632: [0x7F, 0x7F, 0x7F, 0x7F, 0x7F], # ■
9633: [0x7F, 0x41, 0x41, 0x41, 0x7F], # □
9650: [0x10, 0x1C, 0x1E, 0x1C, 0x10], # ▲
9651: [0x10, 0x1C, 0x12, 0x1C, 0x10], # △
9660: [0x04, 0x1C, 0x3C, 0x1C, 0x04], # ▼
9661: [0x04, 0x1C, 0x24, 0x1C, 0x04], # ▽
9670: [0x08, 0x1C, 0x3E, 0x1C, 0x08], # ◆
9675: [0x1C, 0x22, 0x22, 0x22, 0x1C], # ○
9679: [0x1C, 0x3E, 0x3E, 0x3E, 0x1C], # ●
9733: [0x64, 0x3E, 0x1F, 0x3E, 0x64], # ★
12288: [0x00, 0x00, 0x00, 0x00, 0x00], #
12289: [0x10, 0x20, 0x40, 0x00, 0x00], # 、
12290: [0x70, 0x50, 0x70, 0x00, 0x00], # 。
12300: [0x00, 0x0F, 0x01, 0x01, 0x01], # 「
12301: [0x40, 0x40, 0x40, 0x78, 0x00], # 」
12316: [0x02, 0x01, 0x02, 0x04, 0x02], # 〜
12353: [0x28, 0x58, 0x3C, 0x68, 0x00], # ぁ
12354: [0x32, 0x7A, 0x2F, 0x1A, 0x72], # あ
12355: [0x38, 0x40, 0x08, 0x30, 0x00], # ぃ
12356: [0x3E, 0x40, 0x20, 0x00, 0x1C], # い
12357: [0x10, 0x0C, 0x4C, 0x30, 0x00], # ぅ
12358: [0x04, 0x05, 0x45, 0x25, 0x18], # う
12359: [0x48, 0x2C, 0x5C, 0x48, 0x00], # ぇ
12360: [0x44, 0x24, 0x35, 0x4D, 0x44], # え
12361: [0x68, 0x7C, 0x10, 0x64, 0x00], # ぉ
12362: [0x32, 0x7F, 0x0A, 0x48, 0x32], # お
12363: [0x34, 0x0F, 0x44, 0x38, 0x06], # か
12365: [0x22, 0x4A, 0x4F, 0x5A, 0x08], # き
12367: [0x00, 0x18, 0x24, 0x43, 0x00], # く
12369: [0x3E, 0x00, 0x44, 0x3F, 0x04], # け
12371: [0x22, 0x52, 0x42, 0x42, 0x44], # こ
12373: [0x24, 0x44, 0x47, 0x5C, 0x12], # さ
12375: [0x3F, 0x40, 0x40, 0x20, 0x10], # し
12377: [0x02, 0x0A, 0x56, 0x3F, 0x02], # す
12379: [0x04, 0x3E, 0x44, 0x5F, 0x44], # せ
12381: [0x08, 0x2D, 0x5B, 0x45, 0x44], # そ
12383: [0x72, 0x0F, 0x22, 0x4A, 0x48], # た
12385: [0x0A, 0x0E, 0x4B, 0x4A, 0x32], # ち
12387: [0x08, 0x48, 0x48, 0x30, 0x00], # っ
12388: [0x02, 0x02, 0x42, 0x22, 0x1C], # つ
12390: [0x02, 0x32, 0x4A, 0x46, 0x42], # て
12392: [0x30, 0x4B, 0x44, 0x44, 0x42], # と
12394: [0x0A, 0x27, 0x52, 0x38, 0x26], # な
12395: [0x3E, 0x00, 0x22, 0x42, 0x42], # に
12396: [0x38, 0x6F, 0x1C, 0x67, 0x78], # ぬ
12397: [0x12, 0x7F, 0x0A, 0x64, 0x78], # ね
12398: [0x3C, 0x62, 0x1E, 0x44, 0x38], # の
12399: [0x3E, 0x00, 0x32, 0x7F, 0x22], # は
12402: [0x04, 0x3E, 0x40, 0x47, 0x3C], # ひ
12405: [0x30, 0x05, 0x7A, 0x00, 0x38], # ふ
12411: [0x3E, 0x00, 0x35, 0x7F, 0x25], # ほ
12414: [0x2A, 0x5A, 0x7F, 0x2A, 0x4A], # ま
12415: [0x71, 0x3F, 0x08, 0x7E, 0x08], # み
12416: [0x1A, 0x3F, 0x42, 0x40, 0x26], # む
12417: [0x38, 0x4E, 0x34, 0x1F, 0x78], # め
12418: [0x14, 0x3F, 0x54, 0x40, 0x20], # も
12419: [0x1C, 0x68, 0x0C, 0x30, 0x00], # ゃ
12420: [0x04, 0x7F, 0x04, 0x2E, 0x18], # や
12421: [0x18, 0x50, 0x3C, 0x18, 0x00], # ゅ
12422: [0x1E, 0x44, 0x3F, 0x12, 0x0C], # ゆ
12423: [0x60, 0x7C, 0x28, 0x40, 0x00], # ょ
12424: [0x20, 0x50, 0x3F, 0x24, 0x44], # よ
12425: [0x10, 0x0D, 0x49, 0x4A, 0x30], # ら
12426: [0x0E, 0x00, 0x40, 0x21, 0x1E], # り
12427: [0x20, 0x55, 0x65, 0x4B, 0x30], # る
12428: [0x34, 0x7F, 0x04, 0x3E, 0x40], # れ
12429: [0x10, 0x49, 0x4D, 0x4B, 0x30], # ろ
12431: [0x24, 0x7F, 0x0A, 0x44, 0x38], # わ
12434: [0x22, 0x5F, 0x4A, 0x56, 0x42], # を
12435: [0x70, 0x0F, 0x30, 0x40, 0x20], # ん
12443: [0x02, 0x04, 0x01, 0x02, 0x00], # ゛
12444: [0x07, 0x05, 0x07, 0x00, 0x00], # ゜
12449: [0x44, 0x44, 0x3C, 0x14, 0x0C], # ァ
12450: [0x42, 0x42, 0x3A, 0x12, 0x0E], # ア
12451: [0x20, 0x10, 0x78, 0x04, 0x00], # ィ
12452: [0x10, 0x08, 0x04, 0x7E, 0x01], # イ
12453: [0x18, 0x08, 0x4C, 0x48, 0x38], # ゥ
12454: [0x0E, 0x42, 0x43, 0x22, 0x1E], # ウ
12455: [0x48, 0x48, 0x78, 0x48, 0x48], # ェ
12456: [0x42, 0x42, 0x7E, 0x42, 0x42], # エ
12457: [0x48, 0x38, 0x08, 0x7C, 0x08], # ォ
12458: [0x22, 0x12, 0x0A, 0x7F, 0x02], # オ
12459: [0x42, 0x3F, 0x02, 0x02, 0x7E], # カ
12461: [0x0A, 0x0A, 0x7F, 0x0A, 0x0A], # キ
12463: [0x08, 0x06, 0x42, 0x22, 0x1E], # ク
12465: [0x0F, 0x42, 0x42, 0x3E, 0x02], # ケ
12467: [0x42, 0x42, 0x42, 0x42, 0x7E], # コ
12469: [0x02, 0x4F, 0x42, 0x3F, 0x02], # サ
12471: [0x4A, 0x4A, 0x40, 0x20, 0x1C], # シ
12473: [0x42, 0x22, 0x12, 0x2A, 0x46], # ス
12475: [0x02, 0x3F, 0x42, 0x42, 0x4E], # セ
12477: [0x06, 0x48, 0x40, 0x30, 0x0E], # ソ
12479: [0x08, 0x4E, 0x52, 0x32, 0x0E], # タ
12481: [0x08, 0x4A, 0x4A, 0x3F, 0x09], # チ
12483: [0x18, 0x40, 0x58, 0x40, 0x30], # ッ
12484: [0x0E, 0x40, 0x4E, 0x20, 0x1E], # ツ
12486: [0x04, 0x45, 0x45, 0x3D, 0x04], # テ
12488: [0x00, 0x7F, 0x08, 0x10, 0x10], # ト
12490: [0x44, 0x44, 0x44, 0x3F, 0x04], # ナ
12491: [0x40, 0x42, 0x42, 0x42, 0x40], # ニ
12492: [0x42, 0x4A, 0x2A, 0x12, 0x2E], # ヌ
12493: [0x22, 0x12, 0x73, 0x0A, 0x16], # ネ
12494: [0x40, 0x40, 0x20, 0x10, 0x0E], #
12495: [0x70, 0x0E, 0x00, 0x07, 0x78], # ハ
12498: [0x3F, 0x44, 0x44, 0x44, 0x44], # ヒ
12501: [0x02, 0x42, 0x42, 0x22, 0x1E], # フ
12504: [0x08, 0x04, 0x02, 0x0C, 0x30], # ヘ
12507: [0x32, 0x02, 0x7F, 0x02, 0x32], # ホ
12510: [0x02, 0x12, 0x22, 0x52, 0x0E], # マ
12511: [0x20, 0x2A, 0x2A, 0x2A, 0x40], # ミ
12512: [0x30, 0x2C, 0x22, 0x28, 0x70], # ム
12513: [0x40, 0x44, 0x28, 0x10, 0x2E], # メ
12514: [0x0A, 0x0A, 0x3E, 0x4A, 0x4A], # モ
12515: [0x08, 0x7C, 0x08, 0x28, 0x18], # ャ
12516: [0x04, 0x7F, 0x04, 0x14, 0x0C], # ヤ
12517: [0x48, 0x48, 0x48, 0x78, 0x40], # ュ
12518: [0x42, 0x42, 0x42, 0x7E, 0x40], # ユ
12519: [0x54, 0x54, 0x54, 0x7C, 0x00], # ョ
12520: [0x4A, 0x4A, 0x4A, 0x4A, 0x7E], # ヨ
12521: [0x04, 0x45, 0x45, 0x25, 0x1C], # ラ
12522: [0x0E, 0x40, 0x40, 0x20, 0x1E], # リ
12523: [0x7E, 0x00, 0x7E, 0x40, 0x38], # ル
12524: [0x7E, 0x40, 0x40, 0x20, 0x10], # レ
12525: [0x7E, 0x42, 0x42, 0x42, 0x7E], # ロ
12527: [0x0E, 0x42, 0x42, 0x22, 0x1E], # ワ
12530: [0x0A, 0x4A, 0x4A, 0x2A, 0x1E], # ヲ
12531: [0x42, 0x42, 0x40, 0x20, 0x1C], # ン
12539: [0x00, 0x00, 0x08, 0x00, 0x00], # ・
12540: [0x04, 0x08, 0x08, 0x08, 0x08], # ー
65281: [0x00, 0x00, 0x5F, 0x00, 0x00], #
65283: [0x14, 0x7F, 0x14, 0x7F, 0x14], #
65284: [0x24, 0x2A, 0x7F, 0x2A, 0x12], #
65285: [0x23, 0x13, 0x08, 0x64, 0x62], #
65286: [0x36, 0x49, 0x56, 0x20, 0x50], #
65288: [0x00, 0x1C, 0x22, 0x41, 0x00], #
65289: [0x00, 0x41, 0x22, 0x1C, 0x00], #
65290: [0x2A, 0x1C, 0x3E, 0x1C, 0x2A], #
65291: [0x08, 0x08, 0x3E, 0x08, 0x08], #
65292: [0x00, 0x10, 0x30, 0x00, 0x00], #
65294: [0x00, 0x60, 0x60, 0x00, 0x00], #
65295: [0x20, 0x10, 0x08, 0x04, 0x02], #
65296: [0x3E, 0x51, 0x49, 0x45, 0x3E], #
65297: [0x42, 0x42, 0x7F, 0x40, 0x40], #
65298: [0x42, 0x61, 0x51, 0x49, 0x46], #
65299: [0x22, 0x41, 0x49, 0x49, 0x36], #
65300: [0x38, 0x24, 0x22, 0x7F, 0x20], #
65301: [0x2F, 0x45, 0x45, 0x45, 0x39], #
65302: [0x3C, 0x4A, 0x49, 0x49, 0x30], #
65303: [0x03, 0x01, 0x79, 0x05, 0x03], #
65304: [0x36, 0x49, 0x49, 0x49, 0x36], #
65305: [0x06, 0x49, 0x49, 0x49, 0x3E], #
65306: [0x00, 0x00, 0x12, 0x00, 0x00], #
65307: [0x00, 0x40, 0x32, 0x00, 0x00], #
65308: [0x08, 0x14, 0x22, 0x41, 0x00], #
65309: [0x14, 0x14, 0x14, 0x14, 0x14], #
65310: [0x00, 0x41, 0x22, 0x14, 0x08], #
65311: [0x02, 0x01, 0x51, 0x09, 0x06], #
65312: [0x1C, 0x22, 0x49, 0x55, 0x4E], #
65313: [0x7C, 0x12, 0x11, 0x12, 0x7C], #
65314: [0x7F, 0x49, 0x49, 0x49, 0x36], #
65315: [0x3E, 0x41, 0x41, 0x41, 0x22], #
65316: [0x7F, 0x41, 0x41, 0x22, 0x1C], #
65317: [0x7F, 0x49, 0x49, 0x49, 0x41], #
65318: [0x7F, 0x09, 0x09, 0x09, 0x01], #
65319: [0x3E, 0x41, 0x49, 0x49, 0x79], #
65320: [0x7F, 0x08, 0x08, 0x08, 0x7F], #
65321: [0x00, 0x41, 0x7F, 0x41, 0x00], #
65322: [0x20, 0x40, 0x41, 0x3F, 0x01], #
65323: [0x7F, 0x08, 0x14, 0x22, 0x41], #
65324: [0x7F, 0x40, 0x40, 0x40, 0x40], #
65325: [0x7F, 0x02, 0x04, 0x02, 0x7F], #
65326: [0x7F, 0x02, 0x04, 0x08, 0x7F], #
65327: [0x3E, 0x41, 0x41, 0x41, 0x3E], #
65328: [0x7F, 0x09, 0x09, 0x09, 0x06], #
65329: [0x3E, 0x41, 0x51, 0x21, 0x5E], #
65330: [0x7F, 0x09, 0x19, 0x29, 0x46], #
65331: [0x26, 0x49, 0x49, 0x49, 0x32], #
65332: [0x01, 0x01, 0x7F, 0x01, 0x01], #
65333: [0x3F, 0x40, 0x40, 0x40, 0x3F], #
65334: [0x07, 0x18, 0x60, 0x18, 0x07], #
65335: [0x7F, 0x20, 0x10, 0x20, 0x7F], #
65336: [0x63, 0x14, 0x08, 0x14, 0x63], #
65337: [0x03, 0x04, 0x78, 0x04, 0x03], #
65338: [0x61, 0x51, 0x49, 0x45, 0x43], #
65339: [0x00, 0x7F, 0x41, 0x41, 0x00], #
65340: [0x02, 0x04, 0x08, 0x10, 0x20], #
65341: [0x00, 0x41, 0x41, 0x7F, 0x00], #
65342: [0x04, 0x02, 0x01, 0x02, 0x04], #
65343: [0x40, 0x40, 0x40, 0x40, 0x40], # _
65344: [0x00, 0x01, 0x02, 0x00, 0x00], #
65345: [0x24, 0x54, 0x54, 0x54, 0x78], #
65346: [0x7F, 0x44, 0x44, 0x44, 0x38], #
65347: [0x38, 0x44, 0x44, 0x44, 0x44], #
65348: [0x38, 0x44, 0x44, 0x44, 0x7F], #
65349: [0x38, 0x54, 0x54, 0x54, 0x18], #
65350: [0x08, 0x08, 0x7E, 0x09, 0x09], #
65351: [0x0C, 0x52, 0x52, 0x52, 0x3E], #
65352: [0x7F, 0x08, 0x04, 0x04, 0x78], #
65353: [0x00, 0x44, 0x7D, 0x40, 0x00], #
65354: [0x20, 0x40, 0x40, 0x44, 0x3D], #
65355: [0x7F, 0x20, 0x10, 0x28, 0x44], #
65356: [0x00, 0x41, 0x7F, 0x40, 0x00], #
65357: [0x7C, 0x04, 0x7C, 0x04, 0x78], #
65358: [0x7C, 0x04, 0x04, 0x04, 0x78], #
65359: [0x38, 0x44, 0x44, 0x44, 0x38], #
65360: [0x7E, 0x12, 0x12, 0x12, 0x0C], #
65361: [0x0C, 0x12, 0x12, 0x12, 0x7E], #
65362: [0x7C, 0x08, 0x04, 0x04, 0x08], #
65363: [0x48, 0x54, 0x54, 0x54, 0x24], #
65364: [0x04, 0x04, 0x3F, 0x44, 0x44], #
65365: [0x3C, 0x40, 0x40, 0x40, 0x7C], #
65366: [0x1C, 0x20, 0x40, 0x20, 0x1C], #
65367: [0x3C, 0x40, 0x38, 0x40, 0x3C], #
65368: [0x44, 0x28, 0x10, 0x28, 0x44], #
65369: [0x0E, 0x50, 0x50, 0x50, 0x3E], #
65370: [0x44, 0x64, 0x54, 0x4C, 0x44], #
65371: [0x00, 0x08, 0x36, 0x41, 0x41], #
65372: [0x00, 0x00, 0x77, 0x00, 0x00], #
65373: [0x41, 0x41, 0x36, 0x08, 0x00], #
65374: [0x08, 0x04, 0x08, 0x10, 0x08], #
65383: [0x44, 0x44, 0x3C, 0x14, 0x0C], # ァ
65384: [0x42, 0x42, 0x3A, 0x12, 0x0E], # ィ
65385: [0x20, 0x10, 0x78, 0x04, 0x00], # ゥ
65386: [0x10, 0x08, 0x04, 0x7E, 0x01], # ェ
65387: [0x18, 0x08, 0x4C, 0x48, 0x38], # ォ
65388: [0x0E, 0x42, 0x43, 0x22, 0x1E], # ャ
65389: [0x48, 0x48, 0x78, 0x48, 0x48], # ュ
65390: [0x42, 0x42, 0x7E, 0x42, 0x42], # ョ
65391: [0x48, 0x38, 0x08, 0x7C, 0x08], # ッ
65392: [0x22, 0x12, 0x0A, 0x7F, 0x02], # ー
65393: [0x42, 0x3F, 0x02, 0x02, 0x7E], # ア
65394: [0x0A, 0x0A, 0x7F, 0x0A, 0x0A], # イ
65395: [0x08, 0x06, 0x42, 0x22, 0x1E], # ウ
65396: [0x0F, 0x42, 0x42, 0x3E, 0x02], # エ
65397: [0x42, 0x42, 0x42, 0x42, 0x7E], # オ
65398: [0x02, 0x4F, 0x42, 0x3F, 0x02], # カ
65399: [0x4A, 0x4A, 0x40, 0x20, 0x1C], # キ
65400: [0x42, 0x22, 0x12, 0x2A, 0x46], # ク
65401: [0x02, 0x3F, 0x42, 0x42, 0x4E], # ケ
65402: [0x06, 0x48, 0x40, 0x30, 0x0E], # コ
65403: [0x08, 0x4E, 0x52, 0x32, 0x0E], # サ
65404: [0x08, 0x4A, 0x4A, 0x3F, 0x09], # シ
65405: [0x18, 0x40, 0x58, 0x40, 0x30], # ス
65406: [0x0E, 0x40, 0x4E, 0x20, 0x1E], # セ
65407: [0x04, 0x45, 0x45, 0x3D, 0x04], # ソ
65408: [0x00, 0x7F, 0x08, 0x10, 0x10], # タ
65409: [0x44, 0x44, 0x44, 0x3F, 0x04], # チ
65410: [0x40, 0x42, 0x42, 0x42, 0x40], # ツ
65411: [0x42, 0x4A, 0x2A, 0x12, 0x2E], # テ
65412: [0x22, 0x12, 0x73, 0x0A, 0x16], # ト
65413: [0x40, 0x40, 0x20, 0x10, 0x0E], # ナ
65414: [0x70, 0x0E, 0x00, 0x07, 0x78], # ニ
65415: [0x3F, 0x44, 0x44, 0x44, 0x44], # ヌ
65416: [0x02, 0x42, 0x42, 0x22, 0x1E], # ネ
65417: [0x08, 0x04, 0x02, 0x0C, 0x30], # ノ
65418: [0x32, 0x02, 0x7F, 0x02, 0x32], # ハ
65419: [0x02, 0x12, 0x22, 0x52, 0x0E], # ヒ
65420: [0x20, 0x2A, 0x2A, 0x2A, 0x40], # フ
65421: [0x30, 0x2C, 0x22, 0x28, 0x70], # ヘ
65422: [0x40, 0x44, 0x28, 0x10, 0x2E], # ホ
65423: [0x0A, 0x0A, 0x3E, 0x4A, 0x4A], # マ
65424: [0x08, 0x7C, 0x08, 0x28, 0x18], # ミ
65425: [0x04, 0x7F, 0x04, 0x14, 0x0C], # ム
65426: [0x48, 0x48, 0x48, 0x78, 0x40], # メ
65427: [0x42, 0x42, 0x42, 0x7E, 0x40], # モ
65428: [0x54, 0x54, 0x54, 0x7C, 0x00], # ヤ
65429: [0x4A, 0x4A, 0x4A, 0x4A, 0x7E], # ユ
65430: [0x04, 0x45, 0x45, 0x25, 0x1C], # ヨ
65431: [0x0E, 0x40, 0x40, 0x20, 0x1E], # ラ
65432: [0x7E, 0x00, 0x7E, 0x40, 0x38], # リ
65433: [0x7E, 0x40, 0x40, 0x20, 0x10], # ル
65434: [0x7E, 0x42, 0x42, 0x42, 0x7E], # レ
65435: [0x0E, 0x42, 0x42, 0x22, 0x1E], # ロ
65436: [0x0A, 0x4A, 0x4A, 0x2A, 0x1E], # ワ
65437: [0x42, 0x42, 0x40, 0x20, 0x1C], # ン
65438: [0x02, 0x04, 0x01, 0x02, 0x00], # ゙
65439: [0x07, 0x05, 0x07, 0x00, 0x00], # ゚
}

View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=40.8.0", "wheel"]
build-backend = "setuptools.build_meta"

49
vendor/ltp305-python/library/setup.cfg vendored Normal file
View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
[metadata]
name = ltp305
version = 0.0.1
author = Philip Howard
author_email = phil@pimoroni.com
description = Python library for dual LTP305 LED matrix breakout
long_description = file: README.md
long_description_content_type = text/markdown
keywords = Raspberry Pi
url = https://www.pimoroni.com
project_urls =
GitHub=https://www.github.com/pimoroni/ltp305-python
license = MIT
# This includes the license file(s) in the wheel.
# https://wheel.readthedocs.io/en/stable/user_guide.html#including-license-files-in-the-generated-wheel-file
license_files = LICENSE.txt
classifiers =
Development Status :: 4 - Beta
Operating System :: POSIX :: Linux
License :: OSI Approved :: MIT License
Intended Audience :: Developers
Programming Language :: Python :: 2.7
Programming Language :: Python :: 3
Topic :: Software Development
Topic :: Software Development :: Libraries
Topic :: System :: Hardware
[options]
python_requires = >= 2.7
packages = ltp305
install_requires = smbus2
[flake8]
exclude =
.tox,
.eggs,
.git,
__pycache__,
build,
dist
ignore =
E501
[pimoroni]
py2deps =
py3deps =
configtxt =
commands =

33
vendor/ltp305-python/library/setup.py vendored Executable file
View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Copyright (c) 2016 Pimoroni
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.
"""
from setuptools import setup, __version__
from pkg_resources import parse_version
minimum_version = parse_version('30.4.0')
if parse_version(__version__) < minimum_version:
raise RuntimeError("Package setuptools must be at least version {}".format(minimum_version))
setup()

View File

@@ -0,0 +1,22 @@
import sys
import mock
import pytest
@pytest.fixture(scope='function', autouse=True)
def cleanup():
"""Force module reimport between tests."""
yield None
try:
del sys.modules['ltp305']
except KeyError:
pass
@pytest.fixture(scope='function', autouse=False)
def smbus():
"""Mock smbus module."""
smbus = mock.MagicMock()
sys.modules['smbus'] = smbus
yield smbus
del sys.modules['smbus']

View File

@@ -0,0 +1,51 @@
import mock
def test_set_pixel(smbus):
from ltp305 import LTP305, CMD_MATRIX_L, CMD_MATRIX_R
display = LTP305()
display.set_pixel(0, 0, 1)
display.set_pixel(5, 0, 1)
display.show()
smbus.SMBus(1).write_i2c_block_data.assert_has_calls((
mock.call(display.address, CMD_MATRIX_L, [1, 0, 0, 0, 0, 0, 0, 0]),
mock.call(display.address, CMD_MATRIX_R, [1, 0, 0, 0, 0, 0, 0, 0])
))
smbus.reset_mock()
display.set_pixel(0, 0, 0)
display.set_pixel(5, 0, 0)
display.show()
smbus.SMBus(1).write_i2c_block_data.assert_has_calls((
mock.call(display.address, CMD_MATRIX_L, [0, 0, 0, 0, 0, 0, 0, 0]),
mock.call(display.address, CMD_MATRIX_R, [0, 0, 0, 0, 0, 0, 0, 0])
))
def test_set_decimal(smbus):
from ltp305 import LTP305, CMD_MATRIX_L, CMD_MATRIX_R
display = LTP305()
display.set_decimal(left=True, right=True)
display.show()
smbus.SMBus(1).write_i2c_block_data.assert_has_calls((
mock.call(display.address, CMD_MATRIX_L, [0, 0, 0, 0, 0, 0, 0, 0b01000000]),
mock.call(display.address, CMD_MATRIX_R, [0, 0, 0, 0, 0, 0, 0b10000000, 0])
))
smbus.reset_mock()
display.set_decimal(left=False, right=False)
display.show()
smbus.SMBus(1).write_i2c_block_data.assert_has_calls((
mock.call(display.address, CMD_MATRIX_L, [0, 0, 0, 0, 0, 0, 0, 0]),
mock.call(display.address, CMD_MATRIX_R, [0, 0, 0, 0, 0, 0, 0, 0])
))
def test_set_character(smbus):
from ltp305 import LTP305, CMD_MATRIX_L, CMD_MATRIX_R
display = LTP305()
display.set_character(0, "A")
display.set_character(5, "B")
display.show()
smbus.SMBus(1).write_i2c_block_data.assert_has_calls((
mock.call(display.address, CMD_MATRIX_L, [126, 17, 17, 17, 126, 0, 0, 0]),
mock.call(display.address, CMD_MATRIX_R, [15, 17, 17, 15, 17, 17, 15, 0])
))

View File

@@ -0,0 +1,5 @@
def test_setup(smbus):
import ltp305
display = ltp305.LTP305()
smbus.SMBus.assert_called_with(1)
del display

25
vendor/ltp305-python/library/tox.ini vendored Normal file
View File

@@ -0,0 +1,25 @@
[tox]
envlist = py{27,35,37,39},qa
skip_missing_interpreters = True
[testenv]
commands =
python setup.py install
coverage run -m py.test -v -r wsx
coverage report -m
deps =
mock
pytest>=3.1
pytest-cov
[testenv:qa]
commands =
check-manifest --ignore tox.ini,tests/*,.coveragerc
python setup.py sdist bdist_wheel
twine check dist/*
flake8 --ignore E501
deps =
check-manifest
flake8
twine

25
vendor/ltp305-python/uninstall.sh vendored Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
LIBRARY_VERSION=`cat library/setup.cfg | grep version | awk -F" = " '{print $2}'`
LIBRARY_NAME=`cat library/setup.cfg | grep name | awk -F" = " '{print $2}'`
printf "$LIBRARY_NAME $LIBRARY_VERSION Python Library: Uninstaller\n\n"
if [ $(id -u) -ne 0 ]; then
printf "Script must be run as root. Try 'sudo ./uninstall.sh'\n"
exit 1
fi
cd library
printf "Unnstalling for Python 2..\n"
pip uninstall $LIBRARY_NAME
if [ -f "/usr/bin/pip3" ]; then
printf "Uninstalling for Python 3..\n"
pip3 uninstall $LIBRARY_NAME
fi
cd ..
printf "Done!\n"