Compare commits
14 commits
bd68c1cd16
...
62ab21f4dc
| Author | SHA1 | Date | |
|---|---|---|---|
| 62ab21f4dc | |||
|
|
0f50d831a9 | ||
|
|
d00b87c851 | ||
|
|
3ca8f9a715 | ||
|
|
b5eb25620a | ||
|
|
2c5449756e | ||
|
|
d453e1f1e1 | ||
|
|
c8035738b4 | ||
|
|
329a3c9c3f | ||
|
|
23a14e88b8 | ||
|
|
726c835078 | ||
|
|
f9495e0736 | ||
|
|
16497b25c0 | ||
|
|
b75b68d8dd |
25 changed files with 1316 additions and 69 deletions
|
|
@ -4,47 +4,45 @@
|
|||
/*
|
||||
SD card read/write
|
||||
|
||||
This example shows how to read and write data to and from an SD card file
|
||||
The circuit:
|
||||
SD card attached to SPI bus as follows:
|
||||
** MISO - pin 20
|
||||
** MOSI - pin 19
|
||||
** CS - pin 23
|
||||
** SCK - pin 18
|
||||
This example shows how to read and write data to and from an SD card file
|
||||
The circuit:
|
||||
* SD card attached to SPI0 bus as follows:
|
||||
** MOSI - pin 19
|
||||
** MISO - pin 20
|
||||
** CLK - pin 18
|
||||
|
||||
created Nov 2010
|
||||
by David A. Mellis
|
||||
modified 9 Apr 2012
|
||||
by Tom Igoe
|
||||
created Nov 2010
|
||||
by David A. Mellis
|
||||
modified 9 Apr 2012
|
||||
by Tom Igoe
|
||||
modified 14 Feb 2023
|
||||
by Liz Clark
|
||||
|
||||
This example code is in the public domain.
|
||||
This example code is in the public domain.
|
||||
|
||||
*/
|
||||
*/
|
||||
|
||||
// SPI0 pins for Metro RP2040
|
||||
// Pins connected to onboard SD card slot
|
||||
const int _MISO = 20;
|
||||
const int _MOSI = 19;
|
||||
const int _CS = 23;
|
||||
const int _SCK = 18;
|
||||
#include "SdFat.h"
|
||||
SdFat sd;
|
||||
|
||||
#include <SPI.h>
|
||||
#include <SD.h>
|
||||
#define SD_FAT_TYPE 1
|
||||
|
||||
File myFile;
|
||||
// default CS pin is 23 for Metro RP2040
|
||||
#define SD_CS_PIN 23
|
||||
|
||||
File32 myFile;
|
||||
|
||||
void setup() {
|
||||
// Open serial communications and wait for port to open:
|
||||
Serial.begin(115200);
|
||||
while (!Serial) {
|
||||
; // wait for serial port to connect. Needed for native USB port only
|
||||
}
|
||||
|
||||
|
||||
Serial.print("Initializing SD card...");
|
||||
|
||||
// Ensure the SPI pinout the SD card is connected to is configured properly
|
||||
SPI.setRX(_MISO);
|
||||
SPI.setTX(_MOSI);
|
||||
SPI.setSCK(_SCK);
|
||||
|
||||
if (!SD.begin(_CS)) {
|
||||
if (!sd.begin(SD_CS_PIN)) {
|
||||
Serial.println("initialization failed!");
|
||||
return;
|
||||
}
|
||||
|
|
@ -52,11 +50,12 @@ void setup() {
|
|||
|
||||
// open the file. note that only one file can be open at a time,
|
||||
// so you have to close this one before opening another.
|
||||
myFile = SD.open("test.txt", FILE_WRITE);
|
||||
myFile.open("test.txt", FILE_WRITE);
|
||||
|
||||
// if the file opened okay, write to it:
|
||||
if (myFile) {
|
||||
Serial.print("Writing to test.txt...");
|
||||
myFile.println("testing 1, 2, 3.");
|
||||
myFile.println("hello metro rp2040!");
|
||||
// close the file:
|
||||
myFile.close();
|
||||
|
|
@ -67,7 +66,7 @@ void setup() {
|
|||
}
|
||||
|
||||
// re-open the file for reading:
|
||||
myFile = SD.open("test.txt");
|
||||
myFile.open("test.txt");
|
||||
if (myFile) {
|
||||
Serial.println("test.txt:");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
||||
# SPDX-FileCopyrightText: 2019, 2023 Kattni Rembor for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""Simple rainbow swirl example for 3W LED"""
|
||||
import time
|
||||
import pwmio
|
||||
import board
|
||||
from rainbowio import colorwheel
|
||||
import digitalio
|
||||
|
||||
enable = digitalio.DigitalInOut(board.D10)
|
||||
enable.direction = digitalio.Direction.OUTPUT
|
||||
enable.value = True
|
||||
|
||||
|
||||
def colorwheel(pos):
|
||||
if pos < 0 or pos > 255:
|
||||
return 0, 0, 0
|
||||
if pos < 85:
|
||||
return int(255 - pos * 3), int(pos * 3), 0
|
||||
if pos < 170:
|
||||
pos -= 85
|
||||
return 0, int(255 - pos * 3), int(pos * 3)
|
||||
pos -= 170
|
||||
return int(pos * 3), 0, int(255 - pos * 3)
|
||||
|
||||
|
||||
red = pwmio.PWMOut(board.D11, duty_cycle=0, frequency=20000)
|
||||
green = pwmio.PWMOut(board.D12, duty_cycle=0, frequency=20000)
|
||||
blue = pwmio.PWMOut(board.D13, duty_cycle=0, frequency=20000)
|
||||
|
|
@ -22,3 +35,4 @@ while True:
|
|||
red.duty_cycle = int(r * 65536 / 256)
|
||||
green.duty_cycle = int(g * 65536 / 256)
|
||||
blue.duty_cycle = int(b * 65536 / 256)
|
||||
time.sleep(0.05)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# SPDX-FileCopyrightText: 2019 Kattni Rembor for Adafruit Industries
|
||||
# SPDX-FileCopyrightText: 2019, 2023 Kattni Rembor for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
|
|
@ -6,16 +6,23 @@
|
|||
# This example only works on Feathers that have analog audio out!
|
||||
import digitalio
|
||||
import board
|
||||
import audioio
|
||||
import audiocore
|
||||
|
||||
try:
|
||||
from audioio import AudioOut
|
||||
except ImportError:
|
||||
try:
|
||||
from audiopwmio import PWMAudioOut as AudioOut
|
||||
except ImportError:
|
||||
pass # not always supported by every board!
|
||||
|
||||
WAV_FILE_NAME = "StreetChicken.wav" # Change to the name of your wav file!
|
||||
|
||||
enable = digitalio.DigitalInOut(board.D10)
|
||||
enable.direction = digitalio.Direction.OUTPUT
|
||||
enable.value = True
|
||||
|
||||
with audioio.AudioOut(board.A0) as audio: # Speaker connector
|
||||
with AudioOut(board.A0) as audio: # Speaker connector
|
||||
wave_file = open(WAV_FILE_NAME, "rb")
|
||||
wave = audiocore.WaveFile(wave_file)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
# Ambient Machine inspired by Yuri Suzuki https://www.yurisuzuki.com/projects/the-ambient-machine
|
||||
import os
|
||||
import gc
|
||||
import board
|
||||
import busio
|
||||
import audiocore
|
||||
|
|
@ -26,8 +27,6 @@ for p in (8,9,10,11,12,4,3,2,1,0):
|
|||
pin.switch_to_input(pull=Pull.UP)
|
||||
switches.append(Debouncer(pin))
|
||||
|
||||
switch_states = [False] * 20 # list of switch states
|
||||
|
||||
wav_files = []
|
||||
|
||||
for filename in os.listdir('/samples/'): # on board flash
|
||||
|
|
@ -36,62 +35,57 @@ for filename in os.listdir('/samples/'): # on board flash
|
|||
print(filename)
|
||||
|
||||
wav_files.sort() # put in alphabetical/numberical order
|
||||
|
||||
gc.collect()
|
||||
# Metro M7 pins for the I2S amp:
|
||||
lck_pin, bck_pin, dat_pin = board.D9, board.D10, board.D12
|
||||
|
||||
audio = audiobusio.I2SOut(bit_clock=bck_pin, word_select=lck_pin, data=dat_pin)
|
||||
mixer = audiomixer.Mixer(voice_count=len(wav_files), sample_rate=22050, channel_count=1,
|
||||
bits_per_sample=16, samples_signed=True, buffer_size=8192)
|
||||
bits_per_sample=16, samples_signed=True, buffer_size=2048)
|
||||
audio.play(mixer)
|
||||
|
||||
for i in range(10): # start playing all wavs on loop w levels down
|
||||
wave = audiocore.WaveFile(open(wav_files[i], "rb"))
|
||||
wave = audiocore.WaveFile(wav_files[i], bytearray(1024))
|
||||
mixer.voice[i].play(wave, loop=True)
|
||||
mixer.voice[i].level = 0.0
|
||||
|
||||
LOW_VOL = 0.2
|
||||
HIGH_VOL = 0.5
|
||||
|
||||
while True:
|
||||
for i in range(len(switches)):
|
||||
switches[i].update()
|
||||
if i < 5: # first row plays five samples
|
||||
switch_row = i // 5
|
||||
if switch_row == 0: # first row plays five samples
|
||||
if switches[i].fell:
|
||||
if switch_states[i+5] is True: # check volume switch
|
||||
mixer.voice[i].level = 0.4 # if up
|
||||
if switches[i+5].value is False: # check vol switch (pull-down, so 'False' is 'on')
|
||||
mixer.voice[i].level = HIGH_VOL # if up
|
||||
else:
|
||||
mixer.voice[i].level = 0.2 # if down
|
||||
switch_states[i] = not switch_states[i]
|
||||
mixer.voice[i].level = LOW_VOL # if down
|
||||
if switches[i].rose:
|
||||
mixer.voice[i].level = 0.0
|
||||
switch_states[i] = not switch_states[i]
|
||||
|
||||
elif 4 < i < 10: # second row adjusts volume of first row
|
||||
if switch_row == 1: # second row adjusts volume of first row
|
||||
if switches[i].fell:
|
||||
if switch_states[i-5] is True: # raise volume if it is on
|
||||
mixer.voice[i-5].level = 0.4
|
||||
switch_states[i] = not switch_states[i]
|
||||
if switches[i-5].value is False: # raise volume if it is on
|
||||
mixer.voice[i-5].level = HIGH_VOL
|
||||
if switches[i].rose:
|
||||
if switch_states[i-5] is True: # lower volume if it is on
|
||||
mixer.voice[i-5].level = 0.2
|
||||
switch_states[i] = not switch_states[i]
|
||||
if switches[i-5].value is False: # lower volume if it is on
|
||||
mixer.voice[i-5].level = LOW_VOL
|
||||
|
||||
elif 9 < i < 15: # third row plays five different samples
|
||||
if switch_row == 2: # third row plays five different samples
|
||||
if switches[i].fell:
|
||||
if switch_states[i+5] is True:
|
||||
mixer.voice[i-5].level = 0.4
|
||||
if switches[i+5].value is False:
|
||||
mixer.voice[i-5].level = HIGH_VOL
|
||||
else:
|
||||
mixer.voice[i-5].level = 0.2
|
||||
switch_states[i] = not switch_states[i]
|
||||
mixer.voice[i-5].level = LOW_VOL
|
||||
if switches[i].rose:
|
||||
mixer.voice[i-5].level = 0.0
|
||||
switch_states[i] = not switch_states[i]
|
||||
|
||||
elif 14 < i < 20: # fourth row adjust volumes of third row
|
||||
if switch_row == 3: # fourth row adjust volumes of third row
|
||||
if switches[i].fell:
|
||||
if switch_states[i-5] is True:
|
||||
mixer.voice[i-10].level = 0.4
|
||||
switch_states[i] = not switch_states[i]
|
||||
if switches[i-5].value is False:
|
||||
mixer.voice[i-10].level = HIGH_VOL
|
||||
if switches[i].rose:
|
||||
if switch_states[i-5] is True:
|
||||
mixer.voice[i-10].level = 0.2
|
||||
switch_states[i] = not switch_states[i]
|
||||
if switches[i-5].value is False:
|
||||
mixer.voice[i-10].level = LOW_VOL
|
||||
|
|
|
|||
|
|
@ -139,9 +139,6 @@ class WrappedTextDisplay:
|
|||
|
||||
def refresh(self):
|
||||
text = '\n'.join(self.lines[self.line_offset : self.line_offset + max_lines])
|
||||
# Work around https://github.com/adafruit/Adafruit_CircuitPython_Display_Text/issues/183
|
||||
while '\n\n' in text:
|
||||
text = text.replace('\n\n', '\n \n')
|
||||
terminal.text = text
|
||||
board.DISPLAY.refresh()
|
||||
wrapped_text_display = WrappedTextDisplay()
|
||||
|
|
|
|||
82
ESP32S3_Linux/Dockerfile
Normal file
82
ESP32S3_Linux/Dockerfile
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Dockerfile port of https://gist.github.com/jcmvbkbc/316e6da728021c8ff670a24e674a35e6
|
||||
# wifi details http://wiki.osll.ru/doku.php/etc:users:jcmvbkbc:linux-xtensa:esp32s3wifi
|
||||
|
||||
# we need python 3.10 not 3.11
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get -y install gperf bison flex texinfo help2man gawk libtool-bin git unzip ncurses-dev rsync zlib1g zlib1g-dev xz-utils cmake wget bzip2 g++ python3 python3-dev python3-pip cpio bc virtualenv libusb-1.0 && \
|
||||
ln -s /usr/bin/python3 /usr/bin/python
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# install autoconf 2.71
|
||||
RUN wget https://ftp.gnu.org/gnu/autoconf/autoconf-2.71.tar.xz && \
|
||||
tar -xf autoconf-2.71.tar.xz && \
|
||||
cd autoconf-2.71 && \
|
||||
./configure --prefix=`pwd`/root && \
|
||||
make && \
|
||||
make install
|
||||
ENV PATH="$PATH:/app/autoconf-2.71/root/bin"
|
||||
|
||||
# dynconfig
|
||||
RUN git clone https://github.com/jcmvbkbc/xtensa-dynconfig -b original --depth=1 && \
|
||||
git clone https://github.com/jcmvbkbc/config-esp32s3 esp32s3 --depth=1 && \
|
||||
make -C xtensa-dynconfig ORIG=1 CONF_DIR=`pwd` esp32s3.so
|
||||
ENV XTENSA_GNU_CONFIG="/app/xtensa-dynconfig/esp32s3.so"
|
||||
|
||||
# ct-ng cannot run as root, we'll just do everything else as a user
|
||||
RUN useradd -d /app/build -u 3232 esp32 && mkdir build && chown esp32:esp32 build
|
||||
USER esp32
|
||||
|
||||
# toolchain
|
||||
RUN cd build && \
|
||||
git clone https://github.com/jcmvbkbc/crosstool-NG.git -b xtensa-fdpic --depth=1 && \
|
||||
cd crosstool-NG && \
|
||||
./bootstrap && \
|
||||
./configure --enable-local && \
|
||||
make && \
|
||||
./ct-ng xtensa-esp32s3-linux-uclibcfdpic && \
|
||||
CT_PREFIX=`pwd`/builds ./ct-ng build || echo "Completed" # the complete ct-ng build fails but we still get what we wanted!
|
||||
RUN [ -e build/crosstool-NG/builds/xtensa-esp32s3-linux-uclibcfdpic/bin/xtensa-esp32s3-linux-uclibcfdpic-gcc ] || exit 1
|
||||
|
||||
# kernel and rootfs
|
||||
RUN cd build && \
|
||||
git clone https://github.com/jcmvbkbc/buildroot -b xtensa-2023.02-fdpic --depth=1 && \
|
||||
make -C buildroot O=`pwd`/build-xtensa-2023.02-fdpic-esp32s3 esp32s3wifi_defconfig && \
|
||||
buildroot/utils/config --file build-xtensa-2023.02-fdpic-esp32s3/.config --set-str TOOLCHAIN_EXTERNAL_PATH `pwd`/crosstool-NG/builds/xtensa-esp32s3-linux-uclibcfdpic && \
|
||||
buildroot/utils/config --file build-xtensa-2023.02-fdpic-esp32s3/.config --set-str TOOLCHAIN_EXTERNAL_PREFIX '$(ARCH)-esp32s3-linux-uclibcfdpic' && \
|
||||
buildroot/utils/config --file build-xtensa-2023.02-fdpic-esp32s3/.config --set-str TOOLCHAIN_EXTERNAL_CUSTOM_PREFIX '$(ARCH)-esp32s3-linux-uclibcfdpic' && \
|
||||
make -C buildroot O=`pwd`/build-xtensa-2023.02-fdpic-esp32s3
|
||||
RUN [ -f build/build-xtensa-2023.02-fdpic-esp32s3/images/xipImage -a -f build/build-xtensa-2023.02-fdpic-esp32s3/images/rootfs.cramfs ] || exit 1
|
||||
|
||||
|
||||
# bootloader
|
||||
ENV IDF_PATH="/app/build/esp-hosted/esp_hosted_ng/esp/esp_driver/esp-idf"
|
||||
RUN cd build && \
|
||||
git clone https://github.com/jcmvbkbc/esp-hosted -b shmem --depth=1 && \
|
||||
cd esp-hosted/esp_hosted_ng/esp/esp_driver && cmake . && \
|
||||
cd esp-idf && . ./export.sh && \
|
||||
cd ../network_adapter && idf.py set-target esp32s3 && \
|
||||
cp sdkconfig.defaults.esp32s3 sdkconfig && idf.py build
|
||||
|
||||
|
||||
# move files over
|
||||
RUN cd build && mkdir release && \
|
||||
cp esp-hosted/esp_hosted_ng/esp/esp_driver/network_adapter/build/bootloader/bootloader.bin release && \
|
||||
cp esp-hosted/esp_hosted_ng/esp/esp_driver/network_adapter/build/partition_table/partition-table.bin release && \
|
||||
cp esp-hosted/esp_hosted_ng/esp/esp_driver/network_adapter/build/network_adapter.bin release && \
|
||||
cp build-xtensa-2023.02-fdpic-esp32s3/images/xipImage release && \
|
||||
cp build-xtensa-2023.02-fdpic-esp32s3/images/rootfs.cramfs release
|
||||
|
||||
# keep docker running so we can debug/rebuild :)
|
||||
USER root
|
||||
ENTRYPOINT ["tail", "-f", "/dev/null"]
|
||||
|
||||
|
||||
# grab the files with `docker cp CONTAINER_NAME:/app/build/release/\* .`
|
||||
# now you can burn the files from the 'release' folder with:
|
||||
# python esptool.py --chip esp32s3 -p /dev/ttyUSB0 -b 921600 --before=default_reset --after=hard_reset write_flash 0x0 bootloader.bin 0x10000 network_adapter.bin 0x8000 partition-table.bin
|
||||
# next we can burn in the kernel and filesys with parttool, which is part of esp-idf
|
||||
# parttool.py write_partition --partition-name linux --input xipImage
|
||||
# parttool.py write_partition --partition-name rootfs --input rootfs.cramfs
|
||||
23
Matrix_Portal_S3_Message_Board/code.py
Executable file
23
Matrix_Portal_S3_Message_Board/code.py
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
from adafruit_matrixportal.matrix import Matrix
|
||||
from messageboard import MessageBoard
|
||||
from messageboard.fontpool import FontPool
|
||||
from messageboard.message import Message
|
||||
|
||||
matrix = Matrix(width=128, height=16, bit_depth=5)
|
||||
messageboard = MessageBoard(matrix)
|
||||
messageboard.set_background("images/background.bmp")
|
||||
fontpool = FontPool()
|
||||
fontpool.add_font("arial", "fonts/Arial-10.pcf")
|
||||
|
||||
while True:
|
||||
message = Message(fontpool.find_font("arial"), mask_color=0xFF00FF, opacity=0.8)
|
||||
message.add_image("images/maskedstar.bmp")
|
||||
message.add_text("Hello World!", color=0xFFFF00, x_offset=2, y_offset=2)
|
||||
messageboard.animate(message, "Scroll", "in_from_right")
|
||||
time.sleep(1)
|
||||
messageboard.animate(message, "Scroll", "out_to_left")
|
||||
81
Matrix_Portal_S3_Message_Board/demo.py
Executable file
81
Matrix_Portal_S3_Message_Board/demo.py
Executable file
|
|
@ -0,0 +1,81 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
from adafruit_matrixportal.matrix import Matrix
|
||||
from messageboard import MessageBoard
|
||||
from messageboard.fontpool import FontPool
|
||||
from messageboard.message import Message
|
||||
|
||||
matrix = Matrix(width=128, height=16, bit_depth=5)
|
||||
messageboard = MessageBoard(matrix)
|
||||
messageboard.set_background("images/background.bmp")
|
||||
|
||||
fontpool = FontPool()
|
||||
fontpool.add_font("arial", "fonts/Arial-10.pcf")
|
||||
fontpool.add_font("comic", "fonts/Comic-10.pcf")
|
||||
fontpool.add_font("dejavu", "fonts/DejaVuSans-10.pcf")
|
||||
|
||||
message = Message(fontpool.find_font("terminal"), opacity=0.8)
|
||||
message.add_image("images/maskedstar.bmp")
|
||||
message.add_text("Hello World!", color=0xFFFF00, x_offset=2, y_offset=2)
|
||||
|
||||
message1 = Message(fontpool.find_font("dejavu"))
|
||||
|
||||
message2 = Message(fontpool.find_font("comic"), mask_color=0x00FF00)
|
||||
print("add blinka")
|
||||
message2.add_image("images/maskedblinka.bmp")
|
||||
print("add text")
|
||||
message2.add_text("CircuitPython", color=0xFFFF00, y_offset=-2)
|
||||
|
||||
message3 = Message(fontpool.find_font("dejavu"))
|
||||
message3.add_text("circuitpython.com", color=0xFF0000)
|
||||
|
||||
message4 = Message(fontpool.find_font("arial"))
|
||||
message4.add_text("Buy Electronics", color=0xFFFFFF)
|
||||
|
||||
while True:
|
||||
message1.clear()
|
||||
message1.add_text("Scroll Text In", color=0xFF0000)
|
||||
|
||||
messageboard.animate(message1, "Scroll", "in_from_left")
|
||||
time.sleep(1)
|
||||
message1.clear()
|
||||
message1.add_text("Change Messages")
|
||||
messageboard.animate(message1, "Static", "show")
|
||||
time.sleep(1)
|
||||
message1.clear()
|
||||
message1.add_text("And Scroll Out")
|
||||
|
||||
messageboard.animate(message1, "Static", "show")
|
||||
messageboard.animate(message1, "Scroll", "out_to_right")
|
||||
time.sleep(1)
|
||||
|
||||
message1.clear()
|
||||
message1.add_text("Or more effects like looping ", color=0xFFFF00)
|
||||
messageboard.animate(
|
||||
message1, "Split", "in_vertically"
|
||||
) # Split never completely joins
|
||||
messageboard.animate(
|
||||
message1, "Loop", "left"
|
||||
) # Text too high (probably from split)
|
||||
messageboard.animate(
|
||||
message1, "Static", "flash", count=3
|
||||
) # Flashes in weird positions
|
||||
|
||||
messageboard.animate(message1, "Split", "out_vertically")
|
||||
time.sleep(1)
|
||||
|
||||
messageboard.animate(message2, "Static", "fade_in")
|
||||
time.sleep(1)
|
||||
messageboard.animate(message2, "Static", "fade_out")
|
||||
|
||||
messageboard.set_background(0x00FF00)
|
||||
messageboard.animate(message3, "Scroll", "in_from_top")
|
||||
time.sleep(1)
|
||||
messageboard.animate(message3, "Scroll", "out_to_bottom")
|
||||
messageboard.set_background("images/background.bmp")
|
||||
|
||||
messageboard.animate(message4, "Scroll", "in_from_right")
|
||||
time.sleep(1)
|
||||
BIN
Matrix_Portal_S3_Message_Board/fonts/Arial-10.pcf
Executable file
BIN
Matrix_Portal_S3_Message_Board/fonts/Arial-10.pcf
Executable file
Binary file not shown.
BIN
Matrix_Portal_S3_Message_Board/fonts/Comic-10.pcf
Executable file
BIN
Matrix_Portal_S3_Message_Board/fonts/Comic-10.pcf
Executable file
Binary file not shown.
BIN
Matrix_Portal_S3_Message_Board/fonts/DejaVuSans-10.pcf
Executable file
BIN
Matrix_Portal_S3_Message_Board/fonts/DejaVuSans-10.pcf
Executable file
Binary file not shown.
BIN
Matrix_Portal_S3_Message_Board/images/adafruit_star.bmp
Executable file
BIN
Matrix_Portal_S3_Message_Board/images/adafruit_star.bmp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 824 B |
BIN
Matrix_Portal_S3_Message_Board/images/background.bmp
Executable file
BIN
Matrix_Portal_S3_Message_Board/images/background.bmp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
BIN
Matrix_Portal_S3_Message_Board/images/logo.bmp
Executable file
BIN
Matrix_Portal_S3_Message_Board/images/logo.bmp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 696 B |
BIN
Matrix_Portal_S3_Message_Board/images/maskedblinka.bmp
Executable file
BIN
Matrix_Portal_S3_Message_Board/images/maskedblinka.bmp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 824 B |
BIN
Matrix_Portal_S3_Message_Board/images/maskedstar.bmp
Executable file
BIN
Matrix_Portal_S3_Message_Board/images/maskedstar.bmp
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 824 B |
158
Matrix_Portal_S3_Message_Board/lib/messageboard/__init__.py
Executable file
158
Matrix_Portal_S3_Message_Board/lib/messageboard/__init__.py
Executable file
|
|
@ -0,0 +1,158 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import bitmaptools
|
||||
import displayio
|
||||
import adafruit_imageload
|
||||
from .doublebuffer import DoubleBuffer
|
||||
from .message import Message
|
||||
|
||||
|
||||
class MessageBoard:
|
||||
def __init__(self, matrix):
|
||||
self.fonts = {}
|
||||
self.display = matrix.display
|
||||
self._buffer_width = self.display.width * 2
|
||||
self._buffer_height = self.display.height * 2
|
||||
self._dbl_buf = DoubleBuffer(
|
||||
self.display, self._buffer_width, self._buffer_height
|
||||
)
|
||||
self._background = None
|
||||
self.set_background() # Set to black
|
||||
self._position = (0, 0)
|
||||
|
||||
def set_background(self, file_or_color=0x000000):
|
||||
"""The background image to a bitmap file."""
|
||||
if isinstance(file_or_color, str): # its a filenme:
|
||||
background, bg_shader = adafruit_imageload.load(file_or_color)
|
||||
self._dbl_buf.shader = bg_shader
|
||||
self._background = background
|
||||
elif isinstance(file_or_color, int):
|
||||
# Make a background color fill
|
||||
bg_shader = displayio.ColorConverter(
|
||||
input_colorspace=displayio.Colorspace.RGB565
|
||||
)
|
||||
background = displayio.Bitmap(
|
||||
self.display.width, self.display.height, 65535
|
||||
)
|
||||
background.fill(displayio.ColorConverter().convert(file_or_color))
|
||||
self._dbl_buf.shader = bg_shader
|
||||
self._background = background
|
||||
else:
|
||||
raise RuntimeError("Unknown type of background")
|
||||
|
||||
def animate(self, message, animation_class, animation_function, **kwargs):
|
||||
anim_class = __import__(
|
||||
f"{self.__module__}.animations.{animation_class.lower()}"
|
||||
)
|
||||
anim_class = getattr(anim_class, "animations")
|
||||
anim_class = getattr(anim_class, animation_class.lower())
|
||||
anim_class = getattr(anim_class, animation_class)
|
||||
animation = anim_class(
|
||||
self.display, self._draw, self._position
|
||||
) # Instantiate the class
|
||||
# Call the animation function and pass kwargs along with the message (positional)
|
||||
anim_func = getattr(animation, animation_function)
|
||||
anim_func(message, **kwargs)
|
||||
|
||||
def _draw(
|
||||
self,
|
||||
image,
|
||||
x,
|
||||
y,
|
||||
opacity=None,
|
||||
mask_color=0xFF00FF,
|
||||
blendmode=bitmaptools.BlendMode.Normal,
|
||||
post_draw_position=None,
|
||||
):
|
||||
"""Draws a message to the buffer taking its current settings into account.
|
||||
It also sets the current position and performs a swap.
|
||||
"""
|
||||
self._position = (x, y)
|
||||
buffer_x_offset = self._buffer_width - self.display.width
|
||||
buffer_y_offset = self._buffer_height - self.display.height
|
||||
|
||||
# Image can be a message in which case its properties will be used
|
||||
if isinstance(image, Message):
|
||||
if opacity is None:
|
||||
opacity = image.opacity
|
||||
mask_color = image.mask_color
|
||||
blendmode = image.blendmode
|
||||
image = image.buffer
|
||||
if opacity is None:
|
||||
opacity = 1.0
|
||||
|
||||
if mask_color > 65535:
|
||||
mask_color = displayio.ColorConverter().convert(mask_color)
|
||||
|
||||
# Blit the background
|
||||
bitmaptools.blit(
|
||||
self._dbl_buf.active_buffer,
|
||||
self._background,
|
||||
buffer_x_offset,
|
||||
buffer_y_offset,
|
||||
)
|
||||
|
||||
# If the image is wider than the display buffer, we need to shrink it
|
||||
if x + buffer_x_offset < 0:
|
||||
new_image = displayio.Bitmap(
|
||||
image.width - self.display.width, image.height, 65535
|
||||
)
|
||||
bitmaptools.blit(
|
||||
new_image,
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
x1=self.display.width,
|
||||
y1=0,
|
||||
x2=image.width,
|
||||
y2=image.height,
|
||||
)
|
||||
x += self.display.width
|
||||
image = new_image
|
||||
|
||||
# If the image is taller than the display buffer, we need to shrink it
|
||||
if y + buffer_y_offset < 0:
|
||||
new_image = displayio.Bitmap(
|
||||
image.width, image.height - self.display.height, 65535
|
||||
)
|
||||
bitmaptools.blit(
|
||||
new_image,
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
x1=0,
|
||||
y1=self.display.height,
|
||||
x2=image.width,
|
||||
y2=image.height,
|
||||
)
|
||||
y += self.display.height
|
||||
image = new_image
|
||||
|
||||
# Clear the foreground buffer
|
||||
foreground_buffer = displayio.Bitmap(
|
||||
self._buffer_width, self._buffer_height, 65535
|
||||
)
|
||||
foreground_buffer.fill(mask_color)
|
||||
|
||||
bitmaptools.blit(
|
||||
foreground_buffer, image, x + buffer_x_offset, y + buffer_y_offset
|
||||
)
|
||||
|
||||
# Blend the foreground buffer into the main buffer
|
||||
bitmaptools.alphablend(
|
||||
self._dbl_buf.active_buffer,
|
||||
self._dbl_buf.active_buffer,
|
||||
foreground_buffer,
|
||||
displayio.Colorspace.RGB565,
|
||||
1.0,
|
||||
opacity,
|
||||
blendmode=blendmode,
|
||||
skip_source2_index=mask_color,
|
||||
)
|
||||
self._dbl_buf.show()
|
||||
|
||||
# Allow for an override of the position after drawing (needed for split effects)
|
||||
if post_draw_position is not None and isinstance(post_draw_position, tuple):
|
||||
self._position = post_draw_position
|
||||
24
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/__init__.py
Executable file
24
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/__init__.py
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class Animation:
|
||||
def __init__(self, display, draw_callback, starting_position=(0, 0)):
|
||||
self._display = display
|
||||
self._position = starting_position
|
||||
self._draw = draw_callback
|
||||
|
||||
@staticmethod
|
||||
def _wait(start_time, duration):
|
||||
"""Uses time.monotonic() to wait from the start time for a specified duration"""
|
||||
while time.monotonic() < (start_time + duration):
|
||||
pass
|
||||
return time.monotonic()
|
||||
|
||||
def _get_centered_position(self, message):
|
||||
return int(self._display.width / 2 - message.buffer.width / 2), int(
|
||||
self._display.height / 2 - message.buffer.height / 2
|
||||
)
|
||||
146
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/loop.py
Executable file
146
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/loop.py
Executable file
|
|
@ -0,0 +1,146 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
import displayio
|
||||
import bitmaptools
|
||||
from . import Animation
|
||||
|
||||
|
||||
class Loop(Animation):
|
||||
def _create_loop_image(self, image, x_offset, y_offset, mask_color):
|
||||
"""Attach a copy of an image by a certain offset so it can be looped."""
|
||||
if 0 < x_offset < self._display.width:
|
||||
x_offset = self._display.width
|
||||
if 0 < y_offset < self._display.height:
|
||||
y_offset = self._display.height
|
||||
|
||||
loop_image = displayio.Bitmap(
|
||||
image.width + x_offset, image.height + y_offset, 65535
|
||||
)
|
||||
loop_image.fill(mask_color)
|
||||
bitmaptools.blit(loop_image, image, 0, 0)
|
||||
bitmaptools.blit(loop_image, image, x_offset, y_offset)
|
||||
|
||||
return loop_image
|
||||
|
||||
def left(self, message, duration=1, count=1):
|
||||
"""Loop a message towards the left side of the display over a certain period of time by a
|
||||
certain number of times. The message will re-enter from the right and end up back a the
|
||||
starting position.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float count: (optional) The number of times to loop. (default=1)
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
distance = max(message.buffer.width, self._display.width)
|
||||
loop_image = self._create_loop_image(
|
||||
message.buffer, distance, 0, message.mask_color
|
||||
)
|
||||
for _ in range(count):
|
||||
for _ in range(distance):
|
||||
start_time = time.monotonic()
|
||||
current_x -= 1
|
||||
if current_x < 0 - message.buffer.width:
|
||||
current_x += distance
|
||||
self._draw(
|
||||
loop_image,
|
||||
current_x,
|
||||
current_y,
|
||||
message.opacity,
|
||||
)
|
||||
self._wait(start_time, duration / distance / count)
|
||||
|
||||
def right(self, message, duration=1, count=1):
|
||||
"""Loop a message towards the right side of the display over a certain period of time by a
|
||||
certain number of times. The message will re-enter from the left and end up back a the
|
||||
starting position.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float count: (optional) The number of times to loop. (default=1)
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
distance = max(message.buffer.width, self._display.width)
|
||||
loop_image = self._create_loop_image(
|
||||
message.buffer, distance, 0, message.mask_color
|
||||
)
|
||||
for _ in range(count):
|
||||
for _ in range(distance):
|
||||
start_time = time.monotonic()
|
||||
current_x += 1
|
||||
if current_x > 0:
|
||||
current_x -= distance
|
||||
self._draw(
|
||||
loop_image,
|
||||
current_x,
|
||||
current_y,
|
||||
message.opacity,
|
||||
)
|
||||
self._wait(start_time, duration / distance / count)
|
||||
|
||||
def up(self, message, duration=0.5, count=1):
|
||||
"""Loop a message towards the top side of the display over a certain period of time by a
|
||||
certain number of times. The message will re-enter from the bottom and end up back a the
|
||||
starting position.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float count: (optional) The number of times to loop. (default=1)
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
distance = max(message.buffer.height, self._display.height)
|
||||
loop_image = self._create_loop_image(
|
||||
message.buffer, 0, distance, message.mask_color
|
||||
)
|
||||
for _ in range(count):
|
||||
for _ in range(distance):
|
||||
start_time = time.monotonic()
|
||||
current_y -= 1
|
||||
if current_y < 0 - message.buffer.height:
|
||||
current_y += distance
|
||||
self._draw(
|
||||
loop_image,
|
||||
current_x,
|
||||
current_y,
|
||||
message.opacity,
|
||||
)
|
||||
self._wait(start_time, duration / distance / count)
|
||||
|
||||
def down(self, message, duration=0.5, count=1):
|
||||
"""Loop a message towards the bottom side of the display over a certain period of time by a
|
||||
certain number of times. The message will re-enter from the top and end up back a the
|
||||
starting position.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float count: (optional) The number of times to loop. (default=1)
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
distance = max(message.buffer.height, self._display.height)
|
||||
loop_image = self._create_loop_image(
|
||||
message.buffer, 0, distance, message.mask_color
|
||||
)
|
||||
for _ in range(count):
|
||||
for _ in range(distance):
|
||||
start_time = time.monotonic()
|
||||
current_y += 1
|
||||
if current_y > 0:
|
||||
current_y -= distance
|
||||
self._draw(
|
||||
loop_image,
|
||||
current_x,
|
||||
current_y,
|
||||
message.opacity,
|
||||
)
|
||||
self._wait(start_time, duration / distance / count)
|
||||
170
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/scroll.py
Executable file
170
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/scroll.py
Executable file
|
|
@ -0,0 +1,170 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
from . import Animation
|
||||
|
||||
|
||||
class Scroll(Animation):
|
||||
def scroll_from_to(self, message, duration, start_x, start_y, end_x, end_y):
|
||||
"""
|
||||
Scroll the message from one position to another over a certain period of
|
||||
time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: The period of time to perform the animation over in seconds.
|
||||
:param int start_x: The Starting X Position
|
||||
:param int start_yx: The Starting Y Position
|
||||
:param int end_x: The Ending X Position
|
||||
:param int end_y: The Ending Y Position
|
||||
:type message: Message
|
||||
"""
|
||||
steps = max(abs(end_x - start_x), abs(end_y - start_y))
|
||||
if not steps:
|
||||
return
|
||||
increment_x = (end_x - start_x) / steps
|
||||
increment_y = (end_y - start_y) / steps
|
||||
for i in range(steps + 1):
|
||||
start_time = time.monotonic()
|
||||
current_x = start_x + round(i * increment_x)
|
||||
current_y = start_y + round(i * increment_y)
|
||||
self._draw(message, current_x, current_y)
|
||||
if i <= steps:
|
||||
self._wait(start_time, duration / steps)
|
||||
|
||||
def out_to_left(self, message, duration=1):
|
||||
"""Scroll a message off the display from its current position towards the left
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
self.scroll_from_to(
|
||||
message, duration, current_x, current_y, 0 - message.buffer.width, current_y
|
||||
)
|
||||
|
||||
def in_from_left(self, message, duration=1, x=0):
|
||||
"""Scroll a message in from the left side of the display over a certain period of
|
||||
time. The final position is centered.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:param int x: (optional) The amount of x-offset from the center position (default=0)
|
||||
:type message: Message
|
||||
"""
|
||||
center_x, center_y = self._get_centered_position(message)
|
||||
self.scroll_from_to(
|
||||
message,
|
||||
duration,
|
||||
0 - message.buffer.width,
|
||||
center_y,
|
||||
center_x + x,
|
||||
center_y,
|
||||
)
|
||||
|
||||
def in_from_right(self, message, duration=1, x=0):
|
||||
"""Scroll a message in from the right side of the display over a certain period of
|
||||
time. The final position is centered.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:param int x: (optional) The amount of x-offset from the center position (default=0)
|
||||
:type message: Message
|
||||
"""
|
||||
center_x, center_y = self._get_centered_position(message)
|
||||
self.scroll_from_to(
|
||||
message, duration, self._display.width - 1, center_y, center_x + x, center_y
|
||||
)
|
||||
|
||||
def in_from_top(self, message, duration=1, y=0):
|
||||
"""Scroll a message in from the top side of the display over a certain period of
|
||||
time. The final position is centered.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:param int y: (optional) The amount of y-offset from the center position (default=0)
|
||||
:type message: Message
|
||||
"""
|
||||
center_x, center_y = self._get_centered_position(message)
|
||||
self.scroll_from_to(
|
||||
message,
|
||||
duration,
|
||||
center_x,
|
||||
0 - message.buffer.height,
|
||||
center_x,
|
||||
center_y + y,
|
||||
)
|
||||
|
||||
def in_from_bottom(self, message, duration=1, y=0):
|
||||
"""Scroll a message in from the bottom side of the display over a certain period of
|
||||
time. The final position is centered.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:param int y: (optional) The amount of y-offset from the center position (default=0)
|
||||
:type message: Message
|
||||
"""
|
||||
center_x, center_y = self._get_centered_position(message)
|
||||
self.scroll_from_to(
|
||||
message,
|
||||
duration,
|
||||
center_x,
|
||||
self._display.height - 1,
|
||||
center_x,
|
||||
center_y + y,
|
||||
)
|
||||
|
||||
def out_to_right(self, message, duration=1):
|
||||
"""Scroll a message off the display from its current position towards the right
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
self.scroll_from_to(
|
||||
message, duration, current_x, current_y, self._display.width - 1, current_y
|
||||
)
|
||||
|
||||
def out_to_top(self, message, duration=1):
|
||||
"""Scroll a message off the display from its current position towards the top
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
self.scroll_from_to(
|
||||
message,
|
||||
duration,
|
||||
current_x,
|
||||
current_y,
|
||||
current_x,
|
||||
0 - message.buffer.height,
|
||||
)
|
||||
|
||||
def out_to_bottom(self, message, duration=1):
|
||||
"""Scroll a message off the display from its current position towards the bottom
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over in seconds. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
self.scroll_from_to(
|
||||
message, duration, current_x, current_y, current_x, self._display.height - 1
|
||||
)
|
||||
222
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/split.py
Executable file
222
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/split.py
Executable file
|
|
@ -0,0 +1,222 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
import displayio
|
||||
import bitmaptools
|
||||
from . import Animation
|
||||
|
||||
|
||||
class Split(Animation):
|
||||
def out_horizontally(self, message, duration=0.5):
|
||||
"""Show the effect of a message splitting horizontally
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=0.5)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
image = message.buffer
|
||||
left_image = displayio.Bitmap(image.width // 2, image.height, 65535)
|
||||
bitmaptools.blit(
|
||||
left_image, image, 0, 0, x1=0, y1=0, x2=image.width // 2, y2=image.height
|
||||
)
|
||||
|
||||
right_image = displayio.Bitmap(image.width // 2, image.height, 65535)
|
||||
bitmaptools.blit(
|
||||
right_image,
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
x1=image.width // 2,
|
||||
y1=0,
|
||||
x2=image.width,
|
||||
y2=image.height,
|
||||
)
|
||||
|
||||
distance = self._display.width // 2
|
||||
for i in range(distance + 1):
|
||||
start_time = time.monotonic()
|
||||
effect_buffer = displayio.Bitmap(
|
||||
self._display.width + image.width, image.height, 65535
|
||||
)
|
||||
effect_buffer.fill(message.mask_color)
|
||||
bitmaptools.blit(effect_buffer, left_image, distance - i, 0)
|
||||
bitmaptools.blit(
|
||||
effect_buffer, right_image, distance + image.width // 2 + i, 0
|
||||
)
|
||||
|
||||
self._draw(
|
||||
effect_buffer,
|
||||
current_x - self._display.width // 2,
|
||||
current_y,
|
||||
message.opacity,
|
||||
post_draw_position=(current_x - self._display.width // 2, current_y),
|
||||
)
|
||||
self._wait(start_time, duration / distance)
|
||||
|
||||
def out_vertically(self, message, duration=0.5):
|
||||
"""Show the effect of a message splitting vertically
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=0.5)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x, current_y = self._position
|
||||
image = message.buffer
|
||||
|
||||
top_image = displayio.Bitmap(image.width, image.height // 2, 65535)
|
||||
bitmaptools.blit(
|
||||
top_image, image, 0, 0, x1=0, y1=0, x2=image.width, y2=image.height // 2
|
||||
)
|
||||
|
||||
bottom_image = displayio.Bitmap(image.width, image.height // 2, 65535)
|
||||
bitmaptools.blit(
|
||||
bottom_image,
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
x1=0,
|
||||
y1=image.height // 2,
|
||||
x2=image.width,
|
||||
y2=image.height,
|
||||
)
|
||||
|
||||
distance = self._display.height // 2
|
||||
effect_buffer_width = self._display.width
|
||||
if current_x < 0:
|
||||
effect_buffer_width -= current_x
|
||||
for i in range(distance + 1):
|
||||
start_time = time.monotonic()
|
||||
effect_buffer = displayio.Bitmap(
|
||||
effect_buffer_width, self._display.height + image.height, 65535
|
||||
)
|
||||
effect_buffer.fill(message.mask_color)
|
||||
bitmaptools.blit(effect_buffer, top_image, 0, distance - i)
|
||||
bitmaptools.blit(
|
||||
effect_buffer, bottom_image, 0, distance + image.height // 2 + i + 1
|
||||
)
|
||||
|
||||
self._draw(
|
||||
effect_buffer,
|
||||
current_x,
|
||||
current_y - self._display.height // 2,
|
||||
message.opacity,
|
||||
post_draw_position=(current_x, current_y - self._display.height // 2),
|
||||
)
|
||||
self._wait(start_time, duration / distance)
|
||||
|
||||
def in_horizontally(self, message, duration=0.5):
|
||||
"""Show the effect of a split message joining horizontally
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=0.5)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x = int(self._display.width / 2 - message.buffer.width / 2)
|
||||
current_y = int(self._display.height / 2 - message.buffer.height / 2)
|
||||
image = message.buffer
|
||||
left_image = displayio.Bitmap(image.width // 2, image.height, 65535)
|
||||
bitmaptools.blit(
|
||||
left_image, image, 0, 0, x1=0, y1=0, x2=image.width // 2, y2=image.height
|
||||
)
|
||||
|
||||
right_image = displayio.Bitmap(image.width // 2, image.height, 65535)
|
||||
bitmaptools.blit(
|
||||
right_image,
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
x1=image.width // 2,
|
||||
y1=0,
|
||||
x2=image.width,
|
||||
y2=image.height,
|
||||
)
|
||||
|
||||
distance = self._display.width // 2
|
||||
effect_buffer = displayio.Bitmap(
|
||||
self._display.width + image.width, image.height, 65535
|
||||
)
|
||||
effect_buffer.fill(message.mask_color)
|
||||
for i in range(distance + 1):
|
||||
start_time = time.monotonic()
|
||||
bitmaptools.blit(effect_buffer, left_image, i, 0)
|
||||
bitmaptools.blit(
|
||||
effect_buffer,
|
||||
right_image,
|
||||
self._display.width + image.width // 2 - i + 1,
|
||||
0,
|
||||
)
|
||||
self._draw(
|
||||
effect_buffer,
|
||||
current_x - self._display.width // 2,
|
||||
current_y,
|
||||
message.opacity,
|
||||
post_draw_position=(current_x, current_y),
|
||||
)
|
||||
self._wait(start_time, duration / distance)
|
||||
|
||||
def in_vertically(self, message, duration=0.5):
|
||||
"""Show the effect of a split message joining vertically
|
||||
over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=0.5)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x = int(self._display.width / 2 - message.buffer.width / 2)
|
||||
current_y = int(self._display.height / 2 - message.buffer.height / 2)
|
||||
|
||||
image = message.buffer
|
||||
top_image = displayio.Bitmap(image.width, image.height // 2, 65535)
|
||||
bitmaptools.blit(
|
||||
top_image, image, 0, 0, x1=0, y1=0, x2=image.width, y2=image.height // 2
|
||||
)
|
||||
|
||||
bottom_image = displayio.Bitmap(image.width, image.height // 2, 65535)
|
||||
bitmaptools.blit(
|
||||
bottom_image,
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
x1=0,
|
||||
y1=image.height // 2,
|
||||
x2=image.width,
|
||||
y2=image.height,
|
||||
)
|
||||
|
||||
distance = self._display.height // 2
|
||||
effect_buffer_width = self._display.width
|
||||
if current_x < 0:
|
||||
effect_buffer_width -= current_x
|
||||
|
||||
effect_buffer = displayio.Bitmap(
|
||||
effect_buffer_width, self._display.height + image.height, 65535
|
||||
)
|
||||
effect_buffer.fill(message.mask_color)
|
||||
for i in range(distance + 1):
|
||||
start_time = time.monotonic()
|
||||
bitmaptools.blit(effect_buffer, top_image, 0, i + 1)
|
||||
bitmaptools.blit(
|
||||
effect_buffer,
|
||||
bottom_image,
|
||||
0,
|
||||
self._display.height + image.height // 2 - i + 1,
|
||||
)
|
||||
|
||||
self._draw(
|
||||
effect_buffer,
|
||||
current_x,
|
||||
current_y - self._display.height // 2,
|
||||
message.opacity,
|
||||
post_draw_position=(current_x, current_y),
|
||||
)
|
||||
self._wait(start_time, duration / distance)
|
||||
101
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/static.py
Executable file
101
Matrix_Portal_S3_Message_Board/lib/messageboard/animations/static.py
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import time
|
||||
from . import Animation
|
||||
|
||||
|
||||
class Static(Animation):
|
||||
def show(self, message):
|
||||
"""Show the message at its current position.
|
||||
|
||||
:param message: The message to show.
|
||||
:type message: Message
|
||||
"""
|
||||
x, y = self._position
|
||||
self._draw(message, x, y)
|
||||
|
||||
def hide(self, message):
|
||||
"""Hide the message at its current position.
|
||||
|
||||
:param message: The message to hide.
|
||||
:type message: Message
|
||||
"""
|
||||
x, y = self._position
|
||||
self._draw(message, x, y, opacity=0)
|
||||
|
||||
def blink(self, message, count=3, duration=1):
|
||||
"""Blink the foreground on and off a centain number of
|
||||
times over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float count: (optional) The number of times to blink. (default=3)
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
delay = duration / count / 2
|
||||
for _ in range(count):
|
||||
start_time = time.monotonic()
|
||||
self.hide(message)
|
||||
start_time = self._wait(start_time, delay)
|
||||
self.show(message)
|
||||
self._wait(start_time, delay)
|
||||
|
||||
def flash(self, message, count=3, duration=1):
|
||||
"""Fade the foreground in and out a centain number of
|
||||
times over a certain period of time.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float count: (optional) The number of times to flash. (default=3)
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:type message: Message
|
||||
"""
|
||||
delay = duration / count / 2
|
||||
steps = 50 // count
|
||||
for _ in range(count):
|
||||
self.fade_out(message, duration=delay, steps=steps)
|
||||
self.fade_in(message, duration=delay, steps=steps)
|
||||
|
||||
def fade_in(self, message, duration=1, steps=50):
|
||||
"""Fade the foreground in over a certain period of time
|
||||
by a certain number of steps. More steps is smoother, but too high
|
||||
of a number may slow down the animation too much.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:param float steps: (optional) The number of steps to perform the animation. (default=50)
|
||||
:type message: Message
|
||||
"""
|
||||
current_x = int(self._display.width / 2 - message.buffer.width / 2)
|
||||
current_y = int(self._display.height / 2 - message.buffer.height / 2)
|
||||
delay = duration / (steps + 1)
|
||||
for opacity in range(steps + 1):
|
||||
start_time = time.monotonic()
|
||||
self._draw(message, current_x, current_y, opacity=opacity / steps)
|
||||
self._wait(start_time, delay)
|
||||
|
||||
def fade_out(self, message, duration=1, steps=50):
|
||||
"""Fade the foreground out over a certain period of time
|
||||
by a certain number of steps. More steps is smoother, but too high
|
||||
of a number may slow down the animation too much.
|
||||
|
||||
:param message: The message to animate.
|
||||
:param float duration: (optional) The period of time to perform the animation
|
||||
over. (default=1)
|
||||
:param float steps: (optional) The number of steps to perform the animation. (default=50)
|
||||
:type message: Message
|
||||
"""
|
||||
delay = duration / (steps + 1)
|
||||
for opacity in range(steps + 1):
|
||||
start_time = time.monotonic()
|
||||
self._draw(
|
||||
message,
|
||||
self._position[0],
|
||||
self._position[1],
|
||||
opacity=(steps - opacity) / steps,
|
||||
)
|
||||
self._wait(start_time, delay)
|
||||
58
Matrix_Portal_S3_Message_Board/lib/messageboard/doublebuffer.py
Executable file
58
Matrix_Portal_S3_Message_Board/lib/messageboard/doublebuffer.py
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import displayio
|
||||
|
||||
|
||||
class DoubleBuffer:
|
||||
def __init__(self, display, width, height, shader=None, bit_depth=16):
|
||||
self._buffer_group = (displayio.Group(), displayio.Group())
|
||||
self._buffer = (
|
||||
displayio.Bitmap(width, height, 2**bit_depth - 1),
|
||||
displayio.Bitmap(width, height, 2**bit_depth - 1),
|
||||
)
|
||||
self._x_offset = display.width - width
|
||||
self._y_offset = display.height - height
|
||||
self.display = display
|
||||
self._active_buffer = 0 # The buffer we are updating
|
||||
|
||||
if shader is None:
|
||||
shader = displayio.ColorConverter()
|
||||
|
||||
buffer0_sprite = displayio.TileGrid(
|
||||
self._buffer[0],
|
||||
pixel_shader=shader,
|
||||
x=self._x_offset,
|
||||
y=self._y_offset,
|
||||
)
|
||||
self._buffer_group[0].append(buffer0_sprite)
|
||||
|
||||
buffer1_sprite = displayio.TileGrid(
|
||||
self._buffer[1],
|
||||
pixel_shader=shader,
|
||||
x=self._x_offset,
|
||||
y=self._y_offset,
|
||||
)
|
||||
self._buffer_group[1].append(buffer1_sprite)
|
||||
|
||||
def show(self, swap=True):
|
||||
self.display.show(self._buffer_group[self._active_buffer])
|
||||
if swap:
|
||||
self.swap()
|
||||
|
||||
def swap(self):
|
||||
self._active_buffer = 0 if self._active_buffer else 1
|
||||
|
||||
@property
|
||||
def active_buffer(self):
|
||||
return self._buffer[self._active_buffer]
|
||||
|
||||
@property
|
||||
def shader(self):
|
||||
return self._buffer_group[0][0].pixel_shader
|
||||
|
||||
@shader.setter
|
||||
def shader(self, shader):
|
||||
self._buffer_group[0][0].pixel_shader = shader
|
||||
self._buffer_group[1][0].pixel_shader = shader
|
||||
27
Matrix_Portal_S3_Message_Board/lib/messageboard/fontpool.py
Executable file
27
Matrix_Portal_S3_Message_Board/lib/messageboard/fontpool.py
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import terminalio
|
||||
from adafruit_bitmap_font import bitmap_font
|
||||
|
||||
|
||||
class FontPool:
|
||||
def __init__(self):
|
||||
"""Create a pool of fonts for reuse to avoid loading duplicates"""
|
||||
self._fonts = {}
|
||||
self.add_font("terminal")
|
||||
|
||||
def add_font(self, name, file=None):
|
||||
if name in self._fonts:
|
||||
return
|
||||
if name == "terminal":
|
||||
font = terminalio.FONT
|
||||
else:
|
||||
font = bitmap_font.load_font(file)
|
||||
self._fonts[name] = font
|
||||
|
||||
def find_font(self, name):
|
||||
if name in self._fonts:
|
||||
return self._fonts[name]
|
||||
return None
|
||||
144
Matrix_Portal_S3_Message_Board/lib/messageboard/message.py
Executable file
144
Matrix_Portal_S3_Message_Board/lib/messageboard/message.py
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
# SPDX-FileCopyrightText: 2023 Melissa LeBlanc-Williams for Adafruit Industries
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
import bitmaptools
|
||||
import displayio
|
||||
import adafruit_imageload
|
||||
from adafruit_display_text import bitmap_label
|
||||
|
||||
|
||||
class Message:
|
||||
def __init__(
|
||||
self,
|
||||
font,
|
||||
opacity=1.0,
|
||||
mask_color=0xFF00FF,
|
||||
blendmode=bitmaptools.BlendMode.Normal,
|
||||
):
|
||||
self._current_font = font
|
||||
self._current_color = 0xFF0000
|
||||
self._buffer = displayio.Bitmap(0, 0, 65535)
|
||||
self._cursor = [0, 0]
|
||||
self.opacity = opacity
|
||||
self._blendmode = blendmode
|
||||
self._mask_color = 0
|
||||
self.mask_color = mask_color
|
||||
self._width = 0
|
||||
self._height = 0
|
||||
|
||||
def _enlarge_buffer(self, width, height):
|
||||
"""Resize the message buffer to grow as necessary"""
|
||||
new_width = self._width
|
||||
if self._cursor[0] + width >= self._width:
|
||||
new_width = self._cursor[0] + width
|
||||
|
||||
new_height = self._height
|
||||
if self._cursor[1] + height >= self._height:
|
||||
new_height = self._cursor[1] + height
|
||||
|
||||
if new_width > self._width or new_height > self._height:
|
||||
new_buffer = displayio.Bitmap(new_width, new_height, 65535)
|
||||
if self._mask_color is not None:
|
||||
bitmaptools.fill_region(
|
||||
new_buffer, 0, 0, new_width, new_height, self._mask_color
|
||||
)
|
||||
bitmaptools.blit(new_buffer, self._buffer, 0, 0)
|
||||
self._buffer = new_buffer
|
||||
self._width = new_width
|
||||
self._height = new_height
|
||||
|
||||
def _add_bitmap(self, bitmap, x_offset=0, y_offset=0):
|
||||
new_width, new_height = (
|
||||
self._cursor[0] + bitmap.width + x_offset,
|
||||
self._cursor[1] + bitmap.height + y_offset,
|
||||
)
|
||||
# Resize the buffer if necessary
|
||||
self._enlarge_buffer(new_width, new_height)
|
||||
# Blit the image into the buffer
|
||||
source_left, source_top = 0, 0
|
||||
if self._cursor[0] + x_offset < 0:
|
||||
source_left = 0 - (self._cursor[0] + x_offset)
|
||||
x_offset = 0
|
||||
if self._cursor[1] + y_offset < 0:
|
||||
source_top = 0 - (self._cursor[1] + y_offset)
|
||||
y_offset = 0
|
||||
bitmaptools.blit(
|
||||
self._buffer,
|
||||
bitmap,
|
||||
self._cursor[0] + x_offset,
|
||||
self._cursor[1] + y_offset,
|
||||
x1=source_left,
|
||||
y1=source_top,
|
||||
)
|
||||
# Move the cursor
|
||||
self._cursor[0] += bitmap.width + x_offset
|
||||
|
||||
def add_text(
|
||||
self,
|
||||
text,
|
||||
color=None,
|
||||
font=None,
|
||||
x_offset=0,
|
||||
y_offset=0,
|
||||
):
|
||||
if font is None:
|
||||
font = self._current_font
|
||||
if color is None:
|
||||
color = self._current_color
|
||||
color_565value = displayio.ColorConverter().convert(color)
|
||||
# Create a bitmap label and add it to the buffer
|
||||
bmp_label = bitmap_label.Label(font, text=text)
|
||||
color_overlay = displayio.Bitmap(
|
||||
bmp_label.bitmap.width, bmp_label.bitmap.height, 65535
|
||||
)
|
||||
color_overlay.fill(color_565value)
|
||||
mask_overlay = displayio.Bitmap(
|
||||
bmp_label.bitmap.width, bmp_label.bitmap.height, 65535
|
||||
)
|
||||
mask_overlay.fill(self._mask_color)
|
||||
bitmaptools.blit(color_overlay, bmp_label.bitmap, 0, 0, skip_source_index=1)
|
||||
bitmaptools.blit(
|
||||
color_overlay, mask_overlay, 0, 0, skip_dest_index=color_565value
|
||||
)
|
||||
bmp_label = None
|
||||
|
||||
self._add_bitmap(color_overlay, x_offset, y_offset)
|
||||
|
||||
def add_image(self, image, x_offset=0, y_offset=0):
|
||||
# Load the image with imageload and add it to the buffer
|
||||
bmp_image, _ = adafruit_imageload.load(image)
|
||||
self._add_bitmap(bmp_image, x_offset, y_offset)
|
||||
|
||||
def clear(self):
|
||||
"""Clear the canvas content, but retain all of the style settings"""
|
||||
self._buffer = displayio.Bitmap(0, 0, 65535)
|
||||
self._cursor = [0, 0]
|
||||
self._width = 0
|
||||
self._height = 0
|
||||
|
||||
@property
|
||||
def buffer(self):
|
||||
"""Return the current buffer"""
|
||||
if self._width == 0 or self._height == 0:
|
||||
raise RuntimeError("No content in the message")
|
||||
return self._buffer
|
||||
|
||||
@property
|
||||
def mask_color(self):
|
||||
"""Get or Set the mask color"""
|
||||
return self._mask_color
|
||||
|
||||
@mask_color.setter
|
||||
def mask_color(self, value):
|
||||
self._mask_color = displayio.ColorConverter().convert(value)
|
||||
|
||||
@property
|
||||
def blendmode(self):
|
||||
"""Get or Set the blendmode"""
|
||||
return self._blendmode
|
||||
|
||||
@blendmode.setter
|
||||
def blendmode(self, value):
|
||||
if value in bitmaptools.BlendMode:
|
||||
self._blendmode = value
|
||||
Loading…
Reference in a new issue