1
0
mirror of https://github.com/danbee/unicorn synced 2026-06-21 15:12:22 +00:00

Initial commit.

This commit is contained in:
Phil Howard 2023-05-09 11:37:05 +01:00
commit e8a8cd6891
131 changed files with 31352 additions and 0 deletions

167
.github/workflows/micropython.yml vendored Normal file
View File

@ -0,0 +1,167 @@
name: MicroPython Firmware
on:
push:
pull_request:
release:
types: [created]
env:
MICROPYTHON_VERSION: v1.20.0
PIMORONI_PICO_VERSION: v1.20.1
jobs:
deps:
runs-on: ubuntu-20.04
name: Dependencies
steps:
- name: Workspace Cache
id: cache
uses: actions/cache@v3
with:
path: ${{runner.workspace}}
key: workspace-micropython-${{env.MICROPYTHON_VERSION}}
restore-keys: |
workspace-micropython-${{env.MICROPYTHON_VERSION}}
# Check out MicroPython
- name: Checkout MicroPython
if: steps.cache.outputs.cache-hit != 'true'
uses: actions/checkout@v3
with:
repository: micropython/micropython
ref: ${{env.MICROPYTHON_VERSION}}
submodules: false # MicroPython submodules are hideously broken
path: micropython
- name: Fetch base MicroPython submodules
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: micropython
run: git submodule update --init
- name: Fetch Pico SDK submodules
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: micropython/lib/pico-sdk
run: git submodule update --init
- name: Build mpy-cross
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
working-directory: micropython/mpy-cross
run: make
build:
needs: deps
name: Build ${{matrix.name}}
runs-on: ubuntu-20.04
strategy:
matrix:
include:
- name: Cosmic Unicorn
shortname: cosmic_unicorn
board: PIMORONI_COSMIC_UNICORN
# 0x10000000 + (2*1024*1024) - (848*1024)
dir2uf2_fs_start: 269664256
dir2uf2_fs_size: 868352
- name: Galactic Unicorn
shortname: galactic_unicorn
board: PIMORONI_GALACTIC_UNICORN
# 0x10000000 + (2*1024*1024) - (848*1024)
dir2uf2_fs_start: 269664256
dir2uf2_fs_size: 868352
env:
RELEASE_FILE: pimoroni-${{matrix.shortname}}-${{github.event.release.tag_name || github.sha}}-micropython.uf2
FIRMWARE_DIR: "$GITHUB_WORKSPACE/unicorn/firmware"
BOARD_DIR: "$GITHUB_WORKSPACE/unicorn/firmware/${{matrix.board}}"
steps:
- name: Compiler Cache
uses: actions/cache@v3
with:
path: /home/runner/.ccache
key: ccache-micropython-${{matrix.shortname}}-${{github.ref}}-${{github.sha}}
restore-keys: |
ccache-micropython-${{matrix.shortname}}-${{github.ref}}
ccache-micropython-${{matrix.shortname}}-
- name: Workspace Cache
uses: actions/cache@v3
with:
path: ${{runner.workspace}}
key: workspace-micropython-${{env.MICROPYTHON_VERSION}}
restore-keys: |
workspace-micropython-${{env.MICROPYTHON_VERSION}}
- uses: actions/checkout@v3
with:
submodules: true
path: unicorn
# Check out Pimoroni Pico
- uses: actions/checkout@v3
with:
repository: pimoroni/pimoroni-pico
ref: ${{env.PIMORONI_PICO_VERSION}}
submodules: true
path: pimoroni-pico
# Check out dir2u2f
- uses: actions/checkout@v3
with:
repository: gadgetoid/dir2uf2
ref: v0.0.1
path: dir2uf2
# HACK: Patch startup overclock into Pico SDK
- name: "HACK: Startup Overclock Patch"
shell: bash
working-directory: micropython/lib/pico-sdk
run: |
git apply "${{env.FIRMWARE_DIR}}/startup_overclock.patch"
# Install apt packages
- name: Install CCache & Compiler
shell: bash
run:
sudo apt update && sudo apt install ccache gcc-arm-none-eabi
# Build firmware
- name: Configure MicroPython
shell: bash
working-directory: micropython/ports/rp2
run: |
cmake -S . -B build -DPICO_BUILD_DOCS=0 -DUSER_C_MODULES=${{env.BOARD_DIR}}/micropython.cmake -DMICROPY_BOARD_DIR=${{env.BOARD_DIR}} -DMICROPY_BOARD=${{env.BOARD}} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
- name: Build MicroPython
shell: bash
working-directory: micropython/ports/rp2
run: |
ccache --zero-stats || true
cmake --build build -j 2
ccache --show-stats || true
- name: Rename .uf2 for artifact
shell: bash
working-directory: micropython/ports/rp2/build
run: |
cp firmware.uf2 ${{env.RELEASE_FILE}}
- name: Store .uf2 as artifact
uses: actions/upload-artifact@v3
with:
name: ${{env.RELEASE_FILE}}
path: micropython/ports/rp2/build/${{env.RELEASE_FILE}}
- name: Upload .uf2
if: github.event_name == 'release'
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
with:
asset_path: micropython/ports/rp2/build/${{env.RELEASE_FILE}}
upload_url: ${{github.event.release.upload_url}}
asset_name: ${{env.RELEASE_FILE}}
asset_content_type: application/octet-stream

30
.github/workflows/python-linting.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Python Linting
on:
push:
pull_request:
jobs:
build:
name: Python Linting
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- name: Install Python Deps
run: python3 -m pip install flake8
- name: Lint MicroPython Examples
shell: bash
run: |
python3 -m flake8 --show-source --ignore E501 examples/micropython
- name: Lint Cosmic Unicorn Libs
shell: bash
run: |
python3 -m flake8 --show-source --ignore E501 firmware/PIMORONI_COSMIC_UNICORN/lib
- name: Lint Galactic Unicorn Libs
shell: bash
run: |
python3 -m flake8 --show-source --ignore E501 firmware/PIMORONI_GALACTIC_UNICORN/lib

21
LICENSE Normal file
View File

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

1
README.md Normal file
View File

@ -0,0 +1 @@
# Cosmic Unicorn & Galactic Unicorn

View File

@ -0,0 +1,84 @@
add_executable(
cosmic_rainbow_text
cosmic_rainbow_text.cpp
)
# Pull in pico libraries that we need
target_link_libraries(cosmic_rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
pico_enable_stdio_usb(cosmic_rainbow_text 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(cosmic_rainbow_text)
add_executable(
cosmic_rainbow
cosmic_rainbow.cpp
)
# Pull in pico libraries that we need
target_link_libraries(cosmic_rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
pico_enable_stdio_usb(cosmic_rainbow 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(cosmic_rainbow)
add_executable(
cosmic_eighties_super_computer
cosmic_eighties_super_computer.cpp
)
# Pull in pico libraries that we need
target_link_libraries(cosmic_eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
pico_enable_stdio_usb(cosmic_eighties_super_computer 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(cosmic_eighties_super_computer)
add_executable(
cosmic_fire_effect
cosmic_fire_effect.cpp
)
# Pull in pico libraries that we need
target_link_libraries(cosmic_fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
pico_enable_stdio_usb(cosmic_fire_effect 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(cosmic_fire_effect)
add_executable(
cosmic_scroll_text
cosmic_scroll_text.cpp
)
# Pull in pico libraries that we need
target_link_libraries(cosmic_scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
pico_enable_stdio_usb(cosmic_scroll_text 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(cosmic_scroll_text)
add_executable(
cosmic_lava_lamp
cosmic_lava_lamp.cpp
)
# Pull in pico libraries that we need
target_link_libraries(cosmic_lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics cosmic_unicorn)
pico_enable_stdio_usb(cosmic_lava_lamp 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(cosmic_lava_lamp)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "cosmic_unicorn.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
CosmicUnicorn cosmic_unicorn;
float lifetime[32][32];
float age[32][32];
int main() {
stdio_init_all();
for(int y = 0; y < 32; y++) {
for(int x = 0; x < 32; x++) {
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
}
}
cosmic_unicorn.init();
while(true) {
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
cosmic_unicorn.adjust_brightness(+0.01);
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
cosmic_unicorn.adjust_brightness(-0.01);
}
graphics.set_pen(0, 0, 0);
graphics.clear();
for(int y = 0; y < 32; y++) {
for(int x = 0; x < 32; x++) {
if(age[x][y] < lifetime[x][y] * 0.3f) {
graphics.set_pen(230, 150, 0);
graphics.pixel(Point(x, y));
}else if(age[x][y] < lifetime[x][y] * 0.5f) {
float decay = (lifetime[x][y] * 0.5f - age[x][y]) * 5.0f;
graphics.set_pen(decay * 230, decay * 150, 0);
graphics.pixel(Point(x, y));
}
if(age[x][y] >= lifetime[x][y]) {
age[x][y] = 0.0f;
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
}
age[x][y] += 0.01f;
}
}
cosmic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

View File

@ -0,0 +1,115 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "cosmic_unicorn.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
CosmicUnicorn cosmic_unicorn;
// extra row of pixels for sourcing flames and averaging
int width = 32;
int height = 33;
// a buffer that's at least big enough to store 55 x 15 values (to allow for both orientations)
float heat[2000] = {0.0f};
void set(int x, int y, float v) {
heat[x + y * width] = v;
}
float get(int x, int y) {
/*if(x < 0 || x >= width || y < 0 || y >= height) {
return 0.0f;
}*/
x = x < 0 ? 0 : x;
x = x >= width ? width - 1 : x;
return heat[x + y * width];
}
int main() {
stdio_init_all();
cosmic_unicorn.init();
cosmic_unicorn.set_brightness(0.2);
bool landscape = true;
/*
while(true) {
cosmic_unicorn.set_pixel(0, 0, 255, 0, 0);
cosmic_unicorn.set_pixel(1, 1, 0, 255, 0);
cosmic_unicorn.set_pixel(2, 2, 0, 0, 255);
}*/
while(true) {
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
cosmic_unicorn.adjust_brightness(+0.01);
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
cosmic_unicorn.adjust_brightness(-0.01);
}
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
float value = get(x, y);
graphics.set_pen(0, 0, 0);
if(value > 0.5f) {
graphics.set_pen(255, 255, 180);
}else if(value > 0.4f) {
graphics.set_pen(220, 160, 0);
}else if(value > 0.3f) {
graphics.set_pen(180, 30, 0);
}else if(value > 0.22f) {
graphics.set_pen(20, 20, 20);
}
if(landscape) {
graphics.pixel(Point(x, y));
}else{
graphics.pixel(Point(y, x));
}
// update this pixel by averaging the below pixels
float average = (get(x, y) + get(x, y + 2) + get(x, y + 1) + get(x - 1, y + 1) + get(x + 1, y + 1)) / 5.0f;
// damping factor to ensure flame tapers out towards the top of the displays
average *= 0.98f;
// update the heat map with our newly averaged value
set(x, y, average);
}
}
cosmic_unicorn.update(&graphics);
// clear the bottom row and then add a new fire seed to it
for(int x = 0; x < width; x++) {
set(x, height - 1, 0.0f);
}
// add a new random heat source
int source_count = landscape ? 5 : 1;
for(int c = 0; c < source_count; c++) {
int px = (rand() % (width - 4)) + 2;
set(px , height - 2, 1.0f);
set(px + 1, height - 2, 1.0f);
set(px - 1, height - 2, 1.0f);
set(px , height - 1, 1.0f);
set(px + 1, height - 1, 1.0f);
set(px - 1, height - 1, 1.0f);
}
sleep_ms(20);
}
return 0;
}

View File

@ -0,0 +1,148 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "cosmic_unicorn.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
CosmicUnicorn cosmic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
struct blob_t {
float x, y;
float r;
float dx, dy;
};
constexpr int blob_count = 20;
int main() {
stdio_init_all();
cosmic_unicorn.init();
cosmic_unicorn.set_brightness(0.5);
// randomise blob start positions, directions, and size
std::array<blob_t, blob_count> blobs;
for(auto &blob : blobs) {
blob.x = rand() % 32;
blob.y = rand() % 32;
blob.r = ((rand() % 40) / 10.0f) + 5.0f;
blob.dx = ((rand() % 2) / 10.0f) - 0.05f;
blob.dy = ((rand() % 3) / 10.0f) - 0.1f;
}
float hue = 0.0f;
while(true) {
// allow user to adjust brightness
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
cosmic_unicorn.adjust_brightness(+0.01);
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
cosmic_unicorn.adjust_brightness(-0.01);
}
uint start_ms = to_ms_since_boot(get_absolute_time());
// calculate the influence of each blob on the liquid based
// on their distance to each pixel. this causes blobs to
// "merge" into each other when we use fixed thresholds to
// determine which colour to draw with
float liquid[32][32] = {0.0f};
for(auto &blob : blobs) {
float r_sq = blob.r * blob.r;
for(int y = 0; y < 32; y++) {
for(int x = 0; x < 32; x++) {
float d_sq = (x - blob.x) * (x - blob.x) + (y - blob.y) * (y - blob.y);
if(d_sq <= r_sq) {
// add this blobs influence to this pixel
liquid[x][y] += 1.0f - (d_sq / r_sq);
}
}
}
}
// update the blob positions
for(auto &blob : blobs) {
blob.x += blob.dx;
blob.y += blob.dy;
// if we hit the edge then bounce!
if(blob.x < 0.0f || blob.x >= 11.0f) {
blob.dx *= -1.0f;
}
if(blob.y < 0.0f || blob.y >= 53.0f) {
blob.dy *= -1.0f;
}
}
// rotate the hue
hue += 0.001f;
// calculate dark, medium, and bright shades for rendering the
// lava
uint8_t dark_r, dark_g, dark_b;
from_hsv(hue, 1.0f, 0.3f, dark_r, dark_g, dark_b);
uint8_t mid_r, mid_g, mid_b;
from_hsv(hue, 1.0f, 0.6f, mid_r, mid_g, mid_b);
uint8_t bright_r, bright_g, bright_b;
from_hsv(hue, 1.0f, 1.0f, bright_r, bright_g, bright_b);
// clear the canvas
graphics.set_pen(0, 0, 0);
graphics.clear();
// render the lava
for(int y = 0; y < 32; y++) {
for(int x = 0; x < 32; x++) {
float v = liquid[x][y];
// select a colour for this pixel based on how much
// "blobfluence" there is at this position in the liquid
if(v >= 1.5f) {
graphics.set_pen(bright_r, bright_g, bright_b);
graphics.pixel(Point(y, x));
}else if(v >= 1.25f) {
graphics.set_pen(mid_r, mid_g, mid_b);
graphics.pixel(Point(y, x));
}else if(v >= 1.0f) {
graphics.set_pen(dark_r, dark_g, dark_b);
graphics.pixel(Point(y, x));
}
}
}
uint end_ms = to_ms_since_boot(get_absolute_time());
printf("rendering took %dms\n", end_ms - start_ms);
cosmic_unicorn.update(&graphics);
}
return 0;
}

View File

@ -0,0 +1,184 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "cosmic_unicorn.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
CosmicUnicorn cosmic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
int w = graphics.measure_text(t, s);
p.x += (53 / 2) - (w / 2);
p.y += (11 / 2);
graphics.text(t, Point(p.x, p.y), -1, s, a);
//graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
//graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
//graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
}
struct star_t {
float dx, dy, x, y, a;
uint8_t brightness() {
int b = a / 5;
return b > 15 ? 15 : b;
}
};
void init_star(star_t &s) {
s.x = ((rand() % 100) / 5.0f) - 10.0f;
s.y = ((rand() % 100) / 10.0f) - 5.0f;
s.dx = s.x / 10.0f;
s.dy = s.y / 10.0f;
s.a = 0;
}
void step_star(star_t &s) {
s.x += s.dx;
s.y += s.dy;
s.a++;
if(s.a > 100) {
init_star(s);
}
}
int main() {
stdio_init_all();
uint8_t hue_map[53][3];
for(int i = 0; i < 53; i++) {
from_hsv(i / 53.0f, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
}
star_t stars[100];
for(int i = 0; i < 100; i++) {
init_star(stars[i]);
stars[i].a = i;
}
gpio_set_function(28, GPIO_FUNC_SIO);
gpio_set_dir(28, GPIO_OUT);
for(int i = 0; i < 10; i++) {
gpio_put(28, !gpio_get(28));
sleep_ms(100);
}
sleep_ms(1000);
gpio_put(28,true);
cosmic_unicorn.init();
float i = 0;
float hue_offset = 0.0f;
bool animate = true;
float stripe_width = 3.0f;
float speed = 1.0f;
float curve = 0.0f;
while(true) {
if(animate) {
i += speed;
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_VOLUME_UP)) {
curve += 0.05;
if(hue_offset > 1.0f) hue_offset = 1.0f;
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_VOLUME_DOWN)) {
curve -= 0.05;
if(hue_offset < 0.0f) hue_offset = 0.0f;
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
cosmic_unicorn.adjust_brightness(+0.01);
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
cosmic_unicorn.adjust_brightness(-0.01);
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_SLEEP)) {
animate = false;
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_A)) {
speed += 0.05f;
speed = speed >= 10.0f ? 10.0f : speed;
animate = true;
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_B)) {
speed -= 0.05f;
speed = speed <= 0.0f ? 0.0f : speed;
animate = true;
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_C)) {
stripe_width += 0.05f;
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_D)) {
stripe_width -= 0.05f;
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
}
for(int x = 0; x < 32; x++) {
for(int y = 0; y < 32; y++) {
int v = ((sin((x + y) / stripe_width + (sin((y * 3.1415927f * 2.0f) / 11.0f) * curve) + i / 15.0f) + 1.5f) / 2.5f) * 255.0f;
uint8_t r = (hue_map[x][0] * v) / 256;
uint8_t g = (hue_map[x][1] * v) / 256;
uint8_t b = (hue_map[x][2] * v) / 256;
graphics.set_pen(r, g, b);
graphics.pixel(Point(x, y));
}
}
cosmic_unicorn.update(&graphics);
printf("%d\n", cosmic_unicorn.light());
sleep_ms(20);
}
printf("done\n");
return 0;
}

View File

@ -0,0 +1,102 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "cosmic_unicorn.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
CosmicUnicorn cosmic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
int w = graphics.measure_text(t, s);
p.x += (32 / 2) - (w / 2);
p.y += (32 / 2);
graphics.text(t, Point(p.x, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
}
int main() {
uint8_t hue_map[32][3];
for(int i = 0; i < 32; i++) {
from_hsv(i / 32.0f, 1.0f, 0.5f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
}
cosmic_unicorn.init();
graphics.set_font("sans");
uint i = 0;
while(true) {
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
cosmic_unicorn.adjust_brightness(+0.01);
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
cosmic_unicorn.adjust_brightness(-0.01);
}
i++;
graphics.set_pen(0, 0, 0);
graphics.clear();
float s = 0.8f;//0.65f + (sin(i / 25.0f) * 0.15f);
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
float x = (sin((i) / 50.0f) * 90.0f);
float y = (cos((i) / 40.0f) * 5.0f);
graphics.set_pen(255, 255, 255);
text("Galactic Unicorn", Point(x, y), s, a);
uint8_t *p = (uint8_t *)graphics.frame_buffer;
for(size_t i = 0; i < 32 * 32; i++) {
int x = i % 32;
int y = i / 32;
uint r = *p++;
uint g = *p++;
uint b = *p++;
p++;
if(r > 0) {
r = hue_map[x][0];
g = hue_map[x][1];
b = hue_map[x][2];
}
graphics.set_pen(r, g, b);
graphics.pixel(Point(x, y));
}
cosmic_unicorn.update(&graphics);
}
printf("done\n");
return 0;
}

View File

@ -0,0 +1,76 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "cosmic_unicorn.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(32, 32, nullptr);
CosmicUnicorn cosmic_unicorn;
std::string message = "Pirate. Monkey. Robot. Ninja.";
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
int main() {
stdio_init_all();
cosmic_unicorn.init();
float scroll = -32.0f;
while(true) {
//uint time_ms = to_ms_since_boot(get_absolute_time());
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_UP)) {
cosmic_unicorn.adjust_brightness(+0.01);
}
if(cosmic_unicorn.is_pressed(cosmic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
cosmic_unicorn.adjust_brightness(-0.01);
}
int width = graphics.measure_text(message, 1);
scroll += 0.25f;
if(scroll > width) {
scroll = -32.0f;
}
graphics.set_pen(0, 0, 0);
graphics.clear();
uint8_t r = 0, g = 0, b = 0;
from_hsv(scroll / 100.0f, 1.0f, 0.5f, r, g, b);
graphics.set_pen(r, g, b);
graphics.text(message, Point(0 - scroll, 14), -1, 0.55);
cosmic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

View File

@ -0,0 +1,125 @@
add_executable(
rainbow_text
rainbow_text.cpp
)
# Pull in pico libraries that we need
target_link_libraries(rainbow_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(rainbow_text 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(rainbow_text)
add_executable(
rainbow
rainbow.cpp
)
# Pull in pico libraries that we need
target_link_libraries(rainbow pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(rainbow 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(rainbow)
add_executable(
nostalgia_prompt
nostalgia_prompt.cpp
)
# Pull in pico libraries that we need
target_link_libraries(nostalgia_prompt pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(nostalgia_prompt 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(nostalgia_prompt)
add_executable(
eighties_super_computer
eighties_super_computer.cpp
)
# Pull in pico libraries that we need
target_link_libraries(eighties_super_computer pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(eighties_super_computer 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(eighties_super_computer)
add_executable(
fire_effect
fire_effect.cpp
)
# Pull in pico libraries that we need
target_link_libraries(fire_effect pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(fire_effect 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(fire_effect)
add_executable(
scroll_text
scroll_text.cpp
)
# Pull in pico libraries that we need
target_link_libraries(scroll_text pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(scroll_text 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(scroll_text)
add_executable(
balls
balls.cpp
)
# Pull in pico libraries that we need
target_link_libraries(balls pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(balls 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(balls)
add_executable(
lava_lamp
lava_lamp.cpp
)
# Pull in pico libraries that we need
target_link_libraries(lava_lamp pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(lava_lamp 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(lava_lamp)
add_executable(
feature_test
feature_test.cpp
audio_samples.cpp
)
# Pull in pico libraries that we need
target_link_libraries(feature_test pico_stdlib hardware_pio hardware_adc hardware_dma pico_graphics galactic_unicorn)
pico_enable_stdio_usb(feature_test 1)
# create map/bin/hex file etc.
pico_add_extra_outputs(feature_test)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// extra row of pixels for sourcing flames and averaging
float heat[53][13] = {0.0f};
int main() {
stdio_init_all();
galactic_unicorn.init();
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
graphics.set_pen(0, 0, 0);
graphics.clear();
for(int y = 0; y < 12; y++) {
for(int x = 0; x < 53; x++) {
if(heat[x][y] > 0.5f) {
graphics.set_pen(255, 255, 180);
graphics.pixel(Point(x, y));
}else if(heat[x][y] > 0.4f) {
graphics.set_pen(220, 160, 0);
graphics.pixel(Point(x, y));
}else if(heat[x][y] > 0.3f) {
graphics.set_pen(180, 50, 0);
graphics.pixel(Point(x, y));
}else if(heat[x][y] > 0.2f) {
graphics.set_pen(40, 40, 40);
graphics.pixel(Point(x, y));
}
// update this pixel by averaging the below pixels
if(x == 0) {
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x + 1][y + 1]) / 4.0f;
} else if(x == 52) {
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1]) / 4.0f;
} else {
heat[x][y] = (heat[x][y] + heat[x][y + 2] + heat[x][y + 1] + heat[x - 1][y + 1] + heat[x + 1][y + 1]) / 5.0f;
}
heat[x][y] -= 0.01f;
heat[x][y] = heat[x][y] < 0.0f ? 0.0f: heat[x][y];
}
}
galactic_unicorn.update(&graphics);
// clear the bottom row and then add a new fire seed to it
for(int x = 0; x < 53; x++) {
heat[x][11] = 0.0f;
}
// add a new random heat source
for(int c = 0; c < 5; c++) {
int px = (rand() % 51) + 1;
heat[px][11] = 1.0f;
heat[px + 1][11] = 1.0f;
heat[px - 1][11] = 1.0f;
heat[px][12] = 1.0f;
heat[px + 1][12] = 1.0f;
heat[px - 1][12] = 1.0f;
}
sleep_ms(50);
}
return 0;
}

View File

@ -0,0 +1,68 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
float lifetime[53][11];
float age[53][11];
int main() {
stdio_init_all();
for(int y = 0; y < 11; y++) {
for(int x = 0; x < 53; x++) {
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
age[x][y] = ((rand() % 100) / 100.0f) * lifetime[x][y];
}
}
galactic_unicorn.init();
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
graphics.set_pen(0, 0, 0);
graphics.clear();
for(int y = 0; y < 11; y++) {
for(int x = 0; x < 53; x++) {
if(age[x][y] < lifetime[x][y] * 0.3f) {
graphics.set_pen(230, 150, 0);
graphics.pixel(Point(x, y));
}else if(age[x][y] < lifetime[x][y] * 0.5f) {
float decay = (lifetime[x][y] * 0.5f - age[x][y]) * 5.0f;
graphics.set_pen(decay * 230, decay * 150, 0);
graphics.pixel(Point(x, y));
}
if(age[x][y] >= lifetime[x][y]) {
age[x][y] = 0.0f;
lifetime[x][y] = 1.0f + ((rand() % 10) / 100.0f);
}
age[x][y] += 0.01f;
}
}
galactic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

View File

@ -0,0 +1,164 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// left_channel_bin and right_channel_bin are in audio_samples.cpp
extern uint8_t left_channel_bin[];
extern uint32_t left_channel_bin_len;
extern uint8_t right_channel_bin[];
extern uint32_t right_channel_bin_len;
void gradient(uint8_t r, uint8_t g, uint8_t b) {
for(int y = 0; y < 12; y++) {
for(int x = 0; x < 53; x++) {
// graphics.set_pen((r * x) / 52, (g * x) / 52, (b * x) / 52);
graphics.set_pen(x, x, x);
graphics.pixel(Point(x, y));
}
}
}
typedef void (*shader_func_t)(int ms, int x, int y);
void grid(int ms, int x, int y) {
int v = (x + y + (ms / 1000)) % 2 == 0 ? 255 : 0;
graphics.set_pen(v, v, v);
graphics.pixel(Point(x, y));
}
void shader_fill(int ms, shader_func_t f) {
for(int y = 0; y < 12; y++) {
for(int x = 0; x < 53; x++) {
f(ms, x, y);
}
}
}
void outline_text(std::string text) {
uint ms = to_ms_since_boot(get_absolute_time());
graphics.set_font("bitmap8");
uint8_t v = (sin(ms / 100.0f) + 1.0f) * 127.0f;
uint w = graphics.measure_text(text, 1);
int x = 53 / 2 - w / 2 + 1, y = 2;
graphics.set_pen(0, 0, 0);
graphics.text(text, Point(x - 1, y - 1), -1, 1);
graphics.text(text, Point(x , y - 1), -1, 1);
graphics.text(text, Point(x + 1, y - 1), -1, 1);
graphics.text(text, Point(x - 1, y ), -1, 1);
graphics.text(text, Point(x + 1, y ), -1, 1);
graphics.text(text, Point(x - 1, y + 1), -1, 1);
graphics.text(text, Point(x , y + 1), -1, 1);
graphics.text(text, Point(x + 1, y + 1), -1, 1);
graphics.set_pen(v, v, v);
graphics.text(text, Point(x, y), -1, 1);
}
int main() {
stdio_init_all();
galactic_unicorn.init();
//galactic_unicorn.set_brightness(0.5f);
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01f);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01f);
}
uint ms = to_ms_since_boot(get_absolute_time());
uint8_t test = (ms / 1000) % 4;
graphics.set_pen(0, 0, 0);
graphics.clear();
switch(test) {
case 0: {
printf("grid pattern");
shader_fill(ms, grid);
}break;
case 1: {
printf("green gradient");
gradient(0, 255, 0);
}break;
case 2: {
printf("blue gradient");
gradient(0, 0, 255);
}break;
case 3: {
printf("white gradient");
gradient(255, 255, 255);
}break;
}
printf("%d\n", galactic_unicorn.light());
std::string text = "";
static bool was_a_pressed = false;
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
if(!was_a_pressed) {
galactic_unicorn.play_sample(left_channel_bin, left_channel_bin_len);
}
was_a_pressed = true;
}else{
was_a_pressed = false;
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_A)) {
text = "Button A";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_B)) {
text = "Button B";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_C)) {
text = "Button C";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_D)) {
text = "Button D";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_UP)) {
text = "Louder!";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_VOLUME_DOWN)) {
text = "quieter";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_UP)) {
text = "Brighter!";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_BRIGHTNESS_DOWN)) {
text = "Darker";
}
if(galactic_unicorn.is_pressed(GalacticUnicorn::SWITCH_SLEEP)) {
text = "Zzz... zzz...";
}
outline_text(text);
galactic_unicorn.update(&graphics);
sleep_ms(50);
}
return 0;
}

View File

@ -0,0 +1,122 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// extra row of pixels for sourcing flames and averaging
int width = 53;
int height = 15;
// a buffer that's at least big enough to store 55 x 15 values (to allow for both orientations)
float heat[1000] = {0.0f};
void set(int x, int y, float v) {
heat[x + y * width] = v;
}
float get(int x, int y) {
/*if(x < 0 || x >= width || y < 0 || y >= height) {
return 0.0f;
}*/
x = x < 0 ? 0 : x;
x = x >= width ? width - 1 : x;
return heat[x + y * width];
}
int main() {
stdio_init_all();
galactic_unicorn.init();
galactic_unicorn.set_brightness(0.5);
bool landscape = true;
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
landscape = true;
width = 53;
height = 15;
memset(heat, 0, sizeof(heat));
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
landscape = false;
width = 11;
height = 55;
memset(heat, 0, sizeof(heat));
}
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
float value = get(x, y);
graphics.set_pen(0, 0, 0);
if(value > 0.5f) {
graphics.set_pen(255, 255, 180);
}else if(value > 0.4f) {
graphics.set_pen(220, 160, 0);
}else if(value > 0.3f) {
graphics.set_pen(180, 30, 0);
}else if(value > 0.22f) {
graphics.set_pen(20, 20, 20);
}
if(landscape) {
graphics.pixel(Point(x, y));
}else{
graphics.pixel(Point(y, x));
}
// update this pixel by averaging the below pixels
float average = (get(x, y) + get(x, y + 2) + get(x, y + 1) + get(x - 1, y + 1) + get(x + 1, y + 1)) / 5.0f;
// damping factor to ensure flame tapers out towards the top of the displays
average *= landscape ? 0.95f : 0.99f;
// update the heat map with our newly averaged value
set(x, y, average);
}
}
galactic_unicorn.update(&graphics);
// clear the bottom row and then add a new fire seed to it
for(int x = 0; x < width; x++) {
set(x, height - 1, 0.0f);
}
// add a new random heat source
int source_count = landscape ? 5 : 1;
for(int c = 0; c < source_count; c++) {
int px = (rand() % (width - 4)) + 2;
set(px , height - 2, 1.0f);
set(px + 1, height - 2, 1.0f);
set(px - 1, height - 2, 1.0f);
set(px , height - 1, 1.0f);
set(px + 1, height - 1, 1.0f);
set(px - 1, height - 1, 1.0f);
}
sleep_ms(20);
}
return 0;
}

View File

@ -0,0 +1,149 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
struct blob_t {
float x, y;
float r;
float dx, dy;
};
constexpr int blob_count = 20;
int main() {
stdio_init_all();
galactic_unicorn.init();
galactic_unicorn.set_brightness(0.5);
// randomise blob start positions, directions, and size
std::array<blob_t, blob_count> blobs;
for(auto &blob : blobs) {
blob.x = rand() % 11;
blob.y = rand() % 53;
blob.r = ((rand() % 40) / 10.0f) + 5.0f;
blob.dx = ((rand() % 2) / 10.0f) - 0.05f;
blob.dy = ((rand() % 3) / 10.0f) - 0.1f;
}
float hue = 0.0f;
while(true) {
// allow user to adjust brightness
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
uint start_ms = to_ms_since_boot(get_absolute_time());
// calculate the influence of each blob on the liquid based
// on their distance to each pixel. this causes blobs to
// "merge" into each other when we use fixed thresholds to
// determine which colour to draw with
float liquid[11][53] = {0.0f};
for(auto &blob : blobs) {
float r_sq = blob.r * blob.r;
for(int y = 0; y < 53; y++) {
for(int x = 0; x < 11; x++) {
float d_sq = (x - blob.x) * (x - blob.x) + (y - blob.y) * (y - blob.y);
if(d_sq <= r_sq) {
// add this blobs influence to this pixel
liquid[x][y] += 1.0f - (d_sq / r_sq);
}
}
}
}
// update the blob positions
for(auto &blob : blobs) {
blob.x += blob.dx;
blob.y += blob.dy;
// if we hit the edge then bounce!
if(blob.x < 0.0f || blob.x >= 11.0f) {
blob.dx *= -1.0f;
}
if(blob.y < 0.0f || blob.y >= 53.0f) {
blob.dy *= -1.0f;
}
}
// rotate the hue
hue += 0.001f;
// calculate dark, medium, and bright shades for rendering the
// lava
uint8_t dark_r, dark_g, dark_b;
from_hsv(hue, 1.0f, 0.3f, dark_r, dark_g, dark_b);
uint8_t mid_r, mid_g, mid_b;
from_hsv(hue, 1.0f, 0.6f, mid_r, mid_g, mid_b);
uint8_t bright_r, bright_g, bright_b;
from_hsv(hue, 1.0f, 1.0f, bright_r, bright_g, bright_b);
// clear the canvas
graphics.set_pen(0, 0, 0);
graphics.clear();
// render the lava
for(int y = 0; y < 53; y++) {
for(int x = 0; x < 11; x++) {
float v = liquid[x][y];
// select a colour for this pixel based on how much
// "blobfluence" there is at this position in the liquid
if(v >= 1.5f) {
graphics.set_pen(bright_r, bright_g, bright_b);
graphics.pixel(Point(y, x));
}else if(v >= 1.25f) {
graphics.set_pen(mid_r, mid_g, mid_b);
graphics.pixel(Point(y, x));
}else if(v >= 1.0f) {
graphics.set_pen(dark_r, dark_g, dark_b);
graphics.pixel(Point(y, x));
}
}
}
uint end_ms = to_ms_since_boot(get_absolute_time());
printf("rendering took %dms\n", end_ms - start_ms);
galactic_unicorn.update(&graphics);
}
return 0;
}

View File

@ -0,0 +1,122 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
std::array<std::string, 11> c64 = {
"",
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX",
" OO OO OO OOOO OO OO OO OO XXXXXXX",
" OO OO OO OO OO OO OO OO OO XXXXXXX",
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX",
" OOOO OO OO OO OO OO OO XXXXXXX",
" OO OO OO OO OO OO OO OO OO XXXXXXX",
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX",
" XXXXXXX"
};
std::array<std::string, 11> spectrum = {
"",
" O OOOO OOOO OOOOO O O O O XXXXXXXX",
" O O O O O O O O O O O X XXXXXX",
" O O O O O O O X XXXXXX",
" O O O OOOOOO O O X XXXXXX",
" O O O O O O O X XXXXXX",
" OOOOOO OOOO O O OOOOO X XXXXXX",
" X X",
" XXXXXXXX"
};
std::array<std::string, 11> bbc_micro = {
"",
" OOOOO OO OOOO OOO OOOO O ",
" O O O O O O O O O O ",
" O O O O O O O O ",
" OOOOO O O OOOO O O O ",
" O O OOOOOO O O O O ",
" O O O O O O O O O O ",
" OOOOO O O OOOO OOO OOOO O ",
" XXXXXXX"
};
constexpr uint PROMPT_C64 = 0;
constexpr uint PROMPT_SPECTRUM = 1;
constexpr uint PROMPT_BBC_MICRO = 2;
uint prompt = 0;
int main() {
stdio_init_all();
galactic_unicorn.init();
while(true) {
uint time_ms = to_ms_since_boot(get_absolute_time());
prompt = (time_ms / 3000) % 3;
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
std::array<std::string, 11> image = c64;
if(prompt == PROMPT_C64) {
image = c64;
}else if(prompt == PROMPT_SPECTRUM) {
image = spectrum;
}else if(prompt == PROMPT_BBC_MICRO) {
image = bbc_micro;
}
if(prompt == PROMPT_C64) {
graphics.set_pen(20, 20, 120);
}else if(prompt == PROMPT_SPECTRUM){
graphics.set_pen(180, 150, 150);
}else if(prompt == PROMPT_BBC_MICRO){
graphics.set_pen(0, 0, 0);
}
graphics.clear();
if(prompt == PROMPT_C64) {
graphics.set_pen(230, 210, 250);
}else if(prompt == PROMPT_SPECTRUM){
graphics.set_pen(0, 0, 0);
}else if(prompt == PROMPT_BBC_MICRO){
graphics.set_pen(255, 255, 255);
}
for(size_t y = 0; y < image.size(); y++) {
for(size_t x = 0; x < image[y].length(); x++) {
// draw the prompt text
if(image[y][x] == 'O') {
graphics.pixel(Point(x, y + 1));
}
// draw the caret blinking
if(image[y][x] == 'X' && (time_ms / 300) % 2) {
graphics.pixel(Point(x, y + 1));
}
}
}
galactic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

View File

@ -0,0 +1,684 @@
#pragma once
// Copyright(c) 2021 Björn Ottosson
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this softwareand 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 noticeand 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.
#include <cmath>
#include <cfloat>
namespace ok_color
{
struct Lab { float L; float a; float b; };
struct RGB { float r; float g; float b; };
struct HSV { float h; float s; float v; };
struct HSL { float h; float s; float l; };
struct LC { float L; float C; };
// Alternative representation of (L_cusp, C_cusp)
// Encoded so S = C_cusp/L_cusp and T = C_cusp/(1-L_cusp)
// The maximum value for C in the triangle is then found as fmin(S*L, T*(1-L)), for a given L
struct ST { float S; float T; };
constexpr float pi = 3.1415926535897932384626433832795028841971693993751058209749445923078164062f;
float clamp(float x, float min, float max)
{
if (x < min)
return min;
if (x > max)
return max;
return x;
}
float sgn(float x)
{
return (float)(0.f < x) - (float)(x < 0.f);
}
float srgb_transfer_function(float a)
{
return .0031308f >= a ? 12.92f * a : 1.055f * powf(a, .4166666666666667f) - .055f;
}
float srgb_transfer_function_inv(float a)
{
return .04045f < a ? powf((a + .055f) / 1.055f, 2.4f) : a / 12.92f;
}
Lab linear_srgb_to_oklab(RGB c)
{
float l = 0.4122214708f * c.r + 0.5363325363f * c.g + 0.0514459929f * c.b;
float m = 0.2119034982f * c.r + 0.6806995451f * c.g + 0.1073969566f * c.b;
float s = 0.0883024619f * c.r + 0.2817188376f * c.g + 0.6299787005f * c.b;
float l_ = cbrtf(l);
float m_ = cbrtf(m);
float s_ = cbrtf(s);
return {
0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_,
1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_,
0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_,
};
}
RGB oklab_to_linear_srgb(Lab c)
{
float l_ = c.L + 0.3963377774f * c.a + 0.2158037573f * c.b;
float m_ = c.L - 0.1055613458f * c.a - 0.0638541728f * c.b;
float s_ = c.L - 0.0894841775f * c.a - 1.2914855480f * c.b;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
return {
+4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s,
-1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s,
-0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s,
};
}
// Finds the maximum saturation possible for a given hue that fits in sRGB
// Saturation here is defined as S = C/L
// a and b must be normalized so a^2 + b^2 == 1
float compute_max_saturation(float a, float b)
{
// Max saturation will be when one of r, g or b goes below zero.
// Select different coefficients depending on which component goes below zero first
float k0, k1, k2, k3, k4, wl, wm, ws;
if (-1.88170328f * a - 0.80936493f * b > 1)
{
// Red component
k0 = +1.19086277f; k1 = +1.76576728f; k2 = +0.59662641f; k3 = +0.75515197f; k4 = +0.56771245f;
wl = +4.0767416621f; wm = -3.3077115913f; ws = +0.2309699292f;
}
else if (1.81444104f * a - 1.19445276f * b > 1)
{
// Green component
k0 = +0.73956515f; k1 = -0.45954404f; k2 = +0.08285427f; k3 = +0.12541070f; k4 = +0.14503204f;
wl = -1.2684380046f; wm = +2.6097574011f; ws = -0.3413193965f;
}
else
{
// Blue component
k0 = +1.35733652f; k1 = -0.00915799f; k2 = -1.15130210f; k3 = -0.50559606f; k4 = +0.00692167f;
wl = -0.0041960863f; wm = -0.7034186147f; ws = +1.7076147010f;
}
// Approximate max saturation using a polynomial:
float S = k0 + k1 * a + k2 * b + k3 * a * a + k4 * a * b;
// Do one step Halley's method to get closer
// this gives an error less than 10e6, except for some blue hues where the dS/dh is close to infinite
// this should be sufficient for most applications, otherwise do two/three steps
float k_l = +0.3963377774f * a + 0.2158037573f * b;
float k_m = -0.1055613458f * a - 0.0638541728f * b;
float k_s = -0.0894841775f * a - 1.2914855480f * b;
{
float l_ = 1.f + S * k_l;
float m_ = 1.f + S * k_m;
float s_ = 1.f + S * k_s;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
float l_dS = 3.f * k_l * l_ * l_;
float m_dS = 3.f * k_m * m_ * m_;
float s_dS = 3.f * k_s * s_ * s_;
float l_dS2 = 6.f * k_l * k_l * l_;
float m_dS2 = 6.f * k_m * k_m * m_;
float s_dS2 = 6.f * k_s * k_s * s_;
float f = wl * l + wm * m + ws * s;
float f1 = wl * l_dS + wm * m_dS + ws * s_dS;
float f2 = wl * l_dS2 + wm * m_dS2 + ws * s_dS2;
S = S - f * f1 / (f1 * f1 - 0.5f * f * f2);
}
return S;
}
// finds L_cusp and C_cusp for a given hue
// a and b must be normalized so a^2 + b^2 == 1
LC find_cusp(float a, float b)
{
// First, find the maximum saturation (saturation S = C/L)
float S_cusp = compute_max_saturation(a, b);
// Convert to linear sRGB to find the first point where at least one of r,g or b >= 1:
RGB rgb_at_max = oklab_to_linear_srgb({ 1, S_cusp * a, S_cusp * b });
float L_cusp = cbrtf(1.f / fmax(fmax(rgb_at_max.r, rgb_at_max.g), rgb_at_max.b));
float C_cusp = L_cusp * S_cusp;
return { L_cusp , C_cusp };
}
// Finds intersection of the line defined by
// L = L0 * (1 - t) + t * L1;
// C = t * C1;
// a and b must be normalized so a^2 + b^2 == 1
float find_gamut_intersection(float a, float b, float L1, float C1, float L0, LC cusp)
{
// Find the intersection for upper and lower half seprately
float t;
if (((L1 - L0) * cusp.C - (cusp.L - L0) * C1) <= 0.f)
{
// Lower half
t = cusp.C * L0 / (C1 * cusp.L + cusp.C * (L0 - L1));
}
else
{
// Upper half
// First intersect with triangle
t = cusp.C * (L0 - 1.f) / (C1 * (cusp.L - 1.f) + cusp.C * (L0 - L1));
// Then one step Halley's method
{
float dL = L1 - L0;
float dC = C1;
float k_l = +0.3963377774f * a + 0.2158037573f * b;
float k_m = -0.1055613458f * a - 0.0638541728f * b;
float k_s = -0.0894841775f * a - 1.2914855480f * b;
float l_dt = dL + dC * k_l;
float m_dt = dL + dC * k_m;
float s_dt = dL + dC * k_s;
// If higher accuracy is required, 2 or 3 iterations of the following block can be used:
{
float L = L0 * (1.f - t) + t * L1;
float C = t * C1;
float l_ = L + C * k_l;
float m_ = L + C * k_m;
float s_ = L + C * k_s;
float l = l_ * l_ * l_;
float m = m_ * m_ * m_;
float s = s_ * s_ * s_;
float ldt = 3 * l_dt * l_ * l_;
float mdt = 3 * m_dt * m_ * m_;
float sdt = 3 * s_dt * s_ * s_;
float ldt2 = 6 * l_dt * l_dt * l_;
float mdt2 = 6 * m_dt * m_dt * m_;
float sdt2 = 6 * s_dt * s_dt * s_;
float r = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s - 1;
float r1 = 4.0767416621f * ldt - 3.3077115913f * mdt + 0.2309699292f * sdt;
float r2 = 4.0767416621f * ldt2 - 3.3077115913f * mdt2 + 0.2309699292f * sdt2;
float u_r = r1 / (r1 * r1 - 0.5f * r * r2);
float t_r = -r * u_r;
float g = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s - 1;
float g1 = -1.2684380046f * ldt + 2.6097574011f * mdt - 0.3413193965f * sdt;
float g2 = -1.2684380046f * ldt2 + 2.6097574011f * mdt2 - 0.3413193965f * sdt2;
float u_g = g1 / (g1 * g1 - 0.5f * g * g2);
float t_g = -g * u_g;
float b = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s - 1;
float b1 = -0.0041960863f * ldt - 0.7034186147f * mdt + 1.7076147010f * sdt;
float b2 = -0.0041960863f * ldt2 - 0.7034186147f * mdt2 + 1.7076147010f * sdt2;
float u_b = b1 / (b1 * b1 - 0.5f * b * b2);
float t_b = -b * u_b;
t_r = u_r >= 0.f ? t_r : FLT_MAX;
t_g = u_g >= 0.f ? t_g : FLT_MAX;
t_b = u_b >= 0.f ? t_b : FLT_MAX;
t += fmin(t_r, fmin(t_g, t_b));
}
}
}
return t;
}
float find_gamut_intersection(float a, float b, float L1, float C1, float L0)
{
// Find the cusp of the gamut triangle
LC cusp = find_cusp(a, b);
return find_gamut_intersection(a, b, L1, C1, L0, cusp);
}
RGB gamut_clip_preserve_chroma(RGB rgb)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
float L0 = clamp(L, 0, 1);
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1 - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_project_to_0_5(RGB rgb)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
float L0 = 0.5;
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1 - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_project_to_L_cusp(RGB rgb)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
LC cusp = find_cusp(a_, b_);
float L0 = cusp.L;
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1 - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_adaptive_L0_0_5(RGB rgb, float alpha = 0.05f)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
float Ld = L - 0.5f;
float e1 = 0.5f + fabs(Ld) + alpha * C;
float L0 = 0.5f * (1.f + sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * fabs(Ld))));
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1.f - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
RGB gamut_clip_adaptive_L0_L_cusp(RGB rgb, float alpha = 0.05f)
{
if (rgb.r < 1 && rgb.g < 1 && rgb.b < 1 && rgb.r > 0 && rgb.g > 0 && rgb.b > 0)
return rgb;
Lab lab = linear_srgb_to_oklab(rgb);
float L = lab.L;
float eps = 0.00001f;
float C = fmax(eps, sqrtf(lab.a * lab.a + lab.b * lab.b));
float a_ = lab.a / C;
float b_ = lab.b / C;
// The cusp is computed here and in find_gamut_intersection, an optimized solution would only compute it once.
LC cusp = find_cusp(a_, b_);
float Ld = L - cusp.L;
float k = 2.f * (Ld > 0 ? 1.f - cusp.L : cusp.L);
float e1 = 0.5f * k + fabs(Ld) + alpha * C / k;
float L0 = cusp.L + 0.5f * (sgn(Ld) * (e1 - sqrtf(e1 * e1 - 2.f * k * fabs(Ld))));
float t = find_gamut_intersection(a_, b_, L, C, L0);
float L_clipped = L0 * (1.f - t) + t * L;
float C_clipped = t * C;
return oklab_to_linear_srgb({ L_clipped, C_clipped * a_, C_clipped * b_ });
}
float toe(float x)
{
constexpr float k_1 = 0.206f;
constexpr float k_2 = 0.03f;
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
return 0.5f * (k_3 * x - k_1 + sqrtf((k_3 * x - k_1) * (k_3 * x - k_1) + 4 * k_2 * k_3 * x));
}
float toe_inv(float x)
{
constexpr float k_1 = 0.206f;
constexpr float k_2 = 0.03f;
constexpr float k_3 = (1.f + k_1) / (1.f + k_2);
return (x * x + k_1 * x) / (k_3 * (x + k_2));
}
ST to_ST(LC cusp)
{
float L = cusp.L;
float C = cusp.C;
return { C / L, C / (1 - L) };
}
// Returns a smooth approximation of the location of the cusp
// This polynomial was created by an optimization process
// It has been designed so that S_mid < S_max and T_mid < T_max
ST get_ST_mid(float a_, float b_)
{
float S = 0.11516993f + 1.f / (
+7.44778970f + 4.15901240f * b_
+ a_ * (-2.19557347f + 1.75198401f * b_
+ a_ * (-2.13704948f - 10.02301043f * b_
+ a_ * (-4.24894561f + 5.38770819f * b_ + 4.69891013f * a_
)))
);
float T = 0.11239642f + 1.f / (
+1.61320320f - 0.68124379f * b_
+ a_ * (+0.40370612f + 0.90148123f * b_
+ a_ * (-0.27087943f + 0.61223990f * b_
+ a_ * (+0.00299215f - 0.45399568f * b_ - 0.14661872f * a_
)))
);
return { S, T };
}
struct Cs { float C_0; float C_mid; float C_max; };
Cs get_Cs(float L, float a_, float b_)
{
LC cusp = find_cusp(a_, b_);
float C_max = find_gamut_intersection(a_, b_, L, 1, L, cusp);
ST ST_max = to_ST(cusp);
// Scale factor to compensate for the curved part of gamut shape:
float k = C_max / fmin((L * ST_max.S), (1 - L) * ST_max.T);
float C_mid;
{
ST ST_mid = get_ST_mid(a_, b_);
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
float C_a = L * ST_mid.S;
float C_b = (1.f - L) * ST_mid.T;
C_mid = 0.9f * k * sqrtf(sqrtf(1.f / (1.f / (C_a * C_a * C_a * C_a) + 1.f / (C_b * C_b * C_b * C_b))));
}
float C_0;
{
// for C_0, the shape is independent of hue, so ST are constant. Values picked to roughly be the average values of ST.
float C_a = L * 0.4f;
float C_b = (1.f - L) * 0.8f;
// Use a soft minimum function, instead of a sharp triangle shape to get a smooth value for chroma.
C_0 = sqrtf(1.f / (1.f / (C_a * C_a) + 1.f / (C_b * C_b)));
}
return { C_0, C_mid, C_max };
}
RGB okhsl_to_srgb(HSL hsl)
{
float h = hsl.h;
float s = hsl.s;
float l = hsl.l;
if (l == 1.0f)
{
return { 1.f, 1.f, 1.f };
}
else if (l == 0.f)
{
return { 0.f, 0.f, 0.f };
}
float a_ = cosf(2.f * pi * h);
float b_ = sinf(2.f * pi * h);
float L = toe_inv(l);
Cs cs = get_Cs(L, a_, b_);
float C_0 = cs.C_0;
float C_mid = cs.C_mid;
float C_max = cs.C_max;
float mid = 0.8f;
float mid_inv = 1.25f;
float C, t, k_0, k_1, k_2;
if (s < mid)
{
t = mid_inv * s;
k_1 = mid * C_0;
k_2 = (1.f - k_1 / C_mid);
C = t * k_1 / (1.f - k_2 * t);
}
else
{
t = (s - mid)/ (1 - mid);
k_0 = C_mid;
k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
k_2 = (1.f - (k_1) / (C_max - C_mid));
C = k_0 + t * k_1 / (1.f - k_2 * t);
}
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
return {
srgb_transfer_function(rgb.r),
srgb_transfer_function(rgb.g),
srgb_transfer_function(rgb.b),
};
}
HSL srgb_to_okhsl(RGB rgb)
{
Lab lab = linear_srgb_to_oklab({
srgb_transfer_function_inv(rgb.r),
srgb_transfer_function_inv(rgb.g),
srgb_transfer_function_inv(rgb.b)
});
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
float a_ = lab.a / C;
float b_ = lab.b / C;
float L = lab.L;
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
Cs cs = get_Cs(L, a_, b_);
float C_0 = cs.C_0;
float C_mid = cs.C_mid;
float C_max = cs.C_max;
// Inverse of the interpolation in okhsl_to_srgb:
float mid = 0.8f;
float mid_inv = 1.25f;
float s;
if (C < C_mid)
{
float k_1 = mid * C_0;
float k_2 = (1.f - k_1 / C_mid);
float t = C / (k_1 + k_2 * C);
s = t * mid;
}
else
{
float k_0 = C_mid;
float k_1 = (1.f - mid) * C_mid * C_mid * mid_inv * mid_inv / C_0;
float k_2 = (1.f - (k_1) / (C_max - C_mid));
float t = (C - k_0) / (k_1 + k_2 * (C - k_0));
s = mid + (1.f - mid) * t;
}
float l = toe(L);
return { h, s, l };
}
RGB okhsv_to_srgb(HSV hsv)
{
float h = hsv.h;
float s = hsv.s;
float v = hsv.v;
float a_ = cosf(2.f * pi * h);
float b_ = sinf(2.f * pi * h);
LC cusp = find_cusp(a_, b_);
ST ST_max = to_ST(cusp);
float S_max = ST_max.S;
float T_max = ST_max.T;
float S_0 = 0.5f;
float k = 1 - S_0 / S_max;
// first we compute L and V as if the gamut is a perfect triangle:
// L, C when v==1:
float L_v = 1 - s * S_0 / (S_0 + T_max - T_max * k * s);
float C_v = s * T_max * S_0 / (S_0 + T_max - T_max * k * s);
float L = v * L_v;
float C = v * C_v;
// then we compensate for both toe and the curved top part of the triangle:
float L_vt = toe_inv(L_v);
float C_vt = C_v * L_vt / L_v;
float L_new = toe_inv(L);
C = C * L_new / L;
L = L_new;
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
L = L * scale_L;
C = C * scale_L;
RGB rgb = oklab_to_linear_srgb({ L, C * a_, C * b_ });
return {
srgb_transfer_function(rgb.r),
srgb_transfer_function(rgb.g),
srgb_transfer_function(rgb.b),
};
}
HSV srgb_to_okhsv(RGB rgb)
{
Lab lab = linear_srgb_to_oklab({
srgb_transfer_function_inv(rgb.r),
srgb_transfer_function_inv(rgb.g),
srgb_transfer_function_inv(rgb.b)
});
float C = sqrtf(lab.a * lab.a + lab.b * lab.b);
float a_ = lab.a / C;
float b_ = lab.b / C;
float L = lab.L;
float h = 0.5f + 0.5f * atan2f(-lab.b, -lab.a) / pi;
LC cusp = find_cusp(a_, b_);
ST ST_max = to_ST(cusp);
float S_max = ST_max.S;
float T_max = ST_max.T;
float S_0 = 0.5f;
float k = 1 - S_0 / S_max;
// first we find L_v, C_v, L_vt and C_vt
float t = T_max / (C + L * T_max);
float L_v = t * L;
float C_v = t * C;
float L_vt = toe_inv(L_v);
float C_vt = C_v * L_vt / L_v;
// we can then use these to invert the step that compensates for the toe and the curved top part of the triangle:
RGB rgb_scale = oklab_to_linear_srgb({ L_vt, a_ * C_vt, b_ * C_vt });
float scale_L = cbrtf(1.f / fmax(fmax(rgb_scale.r, rgb_scale.g), fmax(rgb_scale.b, 0.f)));
L = L / scale_L;
C = C / scale_L;
C = C * toe(L) / L;
L = toe(L);
// we can now compute v and s:
float v = L / L_v;
float s = (S_0 + T_max) * C_v / ((T_max * S_0) + T_max * k * C_v);
return { h, s, v };
}
} // namespace ok_color

View File

@ -0,0 +1,200 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
int w = graphics.measure_text(t, s);
p.x += (53 / 2) - (w / 2);
p.y += (11 / 2);
graphics.text(t, Point(p.x, p.y), -1, s, a);
//graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
//graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
//graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
}
struct star_t {
float dx, dy, x, y, a;
uint8_t brightness() {
int b = a / 5;
return b > 15 ? 15 : b;
}
};
void init_star(star_t &s) {
s.x = ((rand() % 100) / 5.0f) - 10.0f;
s.y = ((rand() % 100) / 10.0f) - 5.0f;
s.dx = s.x / 10.0f;
s.dy = s.y / 10.0f;
s.a = 0;
}
void step_star(star_t &s) {
s.x += s.dx;
s.y += s.dy;
s.a++;
if(s.a > 100) {
init_star(s);
}
}
int main() {
stdio_init_all();
uint8_t hue_map[53][3];
for(int i = 0; i < 53; i++) {
from_hsv(i / 53.0f, 1.0f, 1.0f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
}
star_t stars[100];
for(int i = 0; i < 100; i++) {
init_star(stars[i]);
stars[i].a = i;
}
gpio_set_function(28, GPIO_FUNC_SIO);
gpio_set_dir(28, GPIO_OUT);
for(int i = 0; i < 10; i++) {
gpio_put(28, !gpio_get(28));
sleep_ms(100);
}
sleep_ms(1000);
gpio_put(28,true);
galactic_unicorn.init();
/*
bool a_pressed = false;
bool b_pressed = false;
bool x_pressed = false;
bool y_pressed = false;
*/
graphics.set_font("bitmap8");
float i = 0;
float hue_offset = 0.0f;
bool animate = true;
float stripe_width = 3.0f;
float speed = 1.0f;
float curve = 0.0f;
while(true) {
if(animate) {
i += speed;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_UP)) {
curve += 0.05;
if(hue_offset > 1.0f) hue_offset = 1.0f;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_VOLUME_DOWN)) {
curve -= 0.05;
if(hue_offset < 0.0f) hue_offset = 0.0f;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_SLEEP)) {
animate = false;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_A)) {
speed += 0.05f;
speed = speed >= 10.0f ? 10.0f : speed;
animate = true;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_B)) {
speed -= 0.05f;
speed = speed <= 0.0f ? 0.0f : speed;
animate = true;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_C)) {
stripe_width += 0.05f;
stripe_width = stripe_width >= 10.0f ? 10.0f : stripe_width;
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_D)) {
stripe_width -= 0.05f;
stripe_width = stripe_width <= 1.0f ? 1.0f : stripe_width;
}
/*
graphics.set_pen(255, 255, 255);
float s = 0.65f;//0.65f + (sin(i / 25.0f) * 0.15f);
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
float x = (sin(i / 74.0f) * 80.0f) * s;
float y = (cos(i / 43.0f) * 6.0f) * s;
text("Chester smells!", Point(x, y - 3), s, a);
*/
for(int x = 0; x < 53; x++) {
for(int y = 0; y < 11; y++) {
int v = ((sin((x + y) / stripe_width + (sin((y * 3.1415927f * 2.0f) / 11.0f) * curve) + i / 15.0f) + 1.5f) / 2.5f) * 255.0f;
uint8_t r = (hue_map[x][0] * v) / 256;
uint8_t g = (hue_map[x][1] * v) / 256;
uint8_t b = (hue_map[x][2] * v) / 256;
graphics.set_pen(r, g, b);
graphics.pixel(Point(x, y));
}
}
galactic_unicorn.update(&graphics);
printf("%d\n", galactic_unicorn.light());
sleep_ms(20);
}
printf("done\n");
return 0;
}

View File

@ -0,0 +1,103 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
// HSV Conversion expects float inputs in the range of 0.00-1.00 for each channel
// Outputs are rgb in the range 0-255 for each channel
void from_hsv(float h, float s, float v, uint8_t &r, uint8_t &g, uint8_t &b) {
float i = floor(h * 6.0f);
float f = h * 6.0f - i;
v *= 255.0f;
uint8_t p = v * (1.0f - s);
uint8_t q = v * (1.0f - f * s);
uint8_t t = v * (1.0f - (1.0f - f) * s);
switch (int(i) % 6) {
case 0: r = v; g = t; b = p; break;
case 1: r = q; g = v; b = p; break;
case 2: r = p; g = v; b = t; break;
case 3: r = p; g = q; b = v; break;
case 4: r = t; g = p; b = v; break;
case 5: r = v; g = p; b = q; break;
}
}
void text(std::string t, Point p, float s = 1.0f, float a = 1.0f) {
int w = graphics.measure_text(t, s);
p.x += (53 / 2) - (w / 2);
p.y += (11 / 2);
graphics.text(t, Point(p.x, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y), -1, s, a);
graphics.text(t, Point(p.x + 1, p.y + 1), -1, s, a);
graphics.text(t, Point(p.x, p.y + 1), -1, s, a);
}
int main() {
uint8_t hue_map[53][3];
for(int i = 0; i < 53; i++) {
from_hsv(i / 53.0f, 1.0f, 0.1f, hue_map[i][0], hue_map[i][1], hue_map[i][2]);
}
galactic_unicorn.init();
graphics.set_font("sans");
uint i = 0;
while(true) {
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
i++;
graphics.set_pen(0, 0, 0);
graphics.clear();
float s = 0.8f;//0.65f + (sin(i / 25.0f) * 0.15f);
float a = 1.0f;// (sin(i / 25.0f) * 100.0f);
float x = (sin((i) / 50.0f) * 90.0f);
float y = (cos((i) / 40.0f) * 5.0f);
graphics.set_pen(255, 255, 255);
text("Galactic Unicorn", Point(x, y), s, a);
uint8_t *p = (uint8_t *)graphics.frame_buffer;
for(size_t i = 0; i < 53 * 11; i++) {
int x = i % 53;
int y = i / 53;
uint r = *p++;
uint g = *p++;
uint b = *p++;
p++;
if(r > 0) {
r = hue_map[x][0];
g = hue_map[x][1];
b = hue_map[x][2];
}
graphics.set_pen(r, g, b);
graphics.pixel(Point(x, y));
}
}
printf("done\n");
return 0;
}

View File

@ -0,0 +1,68 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "pico/stdlib.h"
#include "libraries/pico_graphics/pico_graphics.hpp"
#include "galactic_unicorn.hpp"
#include "okcolor.hpp"
using namespace pimoroni;
PicoGraphics_PenRGB888 graphics(53, 11, nullptr);
GalacticUnicorn galactic_unicorn;
std::string message = "Pirate. Monkey. Robot. Ninja.";
int main() {
stdio_init_all();
galactic_unicorn.init();
float scroll = -53.0f;
while(true) {
//uint time_ms = to_ms_since_boot(get_absolute_time());
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_UP)) {
galactic_unicorn.adjust_brightness(+0.01);
}
if(galactic_unicorn.is_pressed(galactic_unicorn.SWITCH_BRIGHTNESS_DOWN)) {
galactic_unicorn.adjust_brightness(-0.01);
}
int width = graphics.measure_text(message, 1);
scroll += 0.25f;
if(scroll > width) {
scroll = -53.0f;
}
graphics.set_pen(0, 0, 0);
graphics.clear();
ok_color::HSL hsl{scroll / 100.0f, 1.0f, 0.5f};
ok_color::RGB rgb = ok_color::okhsl_to_srgb(hsl);
graphics.set_pen(rgb.r * 255, rgb.g * 255, rgb.b * 255);
graphics.text(message, Point(0 - scroll, 5), -1, 0.55);
/*graphics.text(message, Point(0 - scroll + 1, 5), -1, 0.5);
graphics.text(message, Point(0 - scroll, 5 + 1), -1, 0.5);
graphics.text(message, Point(0 - scroll + 1, 5 + 1), -1, 0.5);*/
/*
for(int x = 0; x < 53; x++) {
for(int y = 0; y < 2; y++) {
float b = sin((x - y * 3 + int(scroll) + 100) / 3.0f);
graphics.set_pen(180 * b, 150 * b, 0);
graphics.pixel(Point(52 - x, y));
graphics.pixel(Point(52 - x, y + 9));
}
}*/
galactic_unicorn.update(&graphics);
sleep_ms(10);
}
return 0;
}

View File

@ -0,0 +1,176 @@
# Cosmic Unicorn MicroPython Examples <!-- omit in toc -->
- [About Cosmic Unicorn](#about-cosmic-unicorn)
- [Cosmic Unicorn and PicoGraphics](#cosmic-unicorn-and-picographics)
- [Examples](#examples)
- [Clock](#clock)
- [Eighties Super Computer](#eighties-super-computer)
- [Feature Test](#feature-test)
- [Feature Test With Audio](#feature-test-with-audio)
- [Fire Effect](#fire-effect)
- [Lava Lamp](#lava-lamp)
- [Nostalgia Prompt](#nostalgia-prompt)
- [Rainbow](#rainbow)
- [Scrolling Text](#scrolling-text)
- [Today](#today)
- [Wireless Examples](#wireless-examples)
- [Cheerlights History](#cheerlights-history)
- [Cosmic Paint](#cosmic-paint)
- [Exchange Ticker](#exchange-ticker)
- [HTTP Text](#http-text)
- [Weather](#weather)
- [NumPy examples](#numpy-examples)
- [Other Examples](#other-examples)
- [Launch (Demo Reel)](#launch-demo-reel)
- [Other Resources](#other-resources)
## About Cosmic Unicorn
Cosmic Unicorn offers 32x32 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
- :link: [Cosmic Unicorn store page](https://shop.pimoroni.com/products/cosmic-unicorn)
Cosmic Unicorn ships with MicroPython firmware pre-loaded, but you can download the most recent version at the link below (you'll want the `cosmic-unicorn` image).
- [MicroPython releases](https://github.com/pimoroni/pimoroni-pico/releases)
- [Installing MicroPython](../../../setting-up-micropython.md)
## Cosmic Unicorn and PicoGraphics
The easiest way to start displaying cool stuff on Cosmic Unicorn is using our Cosmic Unicorn module (which contains a bunch of helpful functions for interacting with the buttons, adjusting brightness and suchlike) and our PicoGraphics library, which is chock full of useful functions for drawing on the LED matrix.
- [Cosmic Unicorn function reference](../../modules/cosmic_unicorn/README.md)
- [PicoGraphics function reference](../../modules/picographics/README.md)
## Examples
### Clock
[clock.py](clock.py)
Clock example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the time by pressing A.
### Eighties Super Computer
[eighties_super_computer.py](eighties_super_computer.py)
Random LEDs blink on and off mimicing the look of a movie super computer doing its work in the eighties. You can adjust the brightness with LUX + and -.
### Feature Test
[feature_test.py](feature_test.py)
Displays some text, gradients and colours and demonstrates button use. You can adjust the brightness with LUX + and -.
### Feature Test With Audio
[feature_test_with_audio.py](feature_test_with_audio.py)
Displays some text, gradients and colours and demonstrates button use. Also demonstrates some of the audio / synth features.
- Button A plays a synth tune
- Button B plays a solo channel of the synth tune
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
- Sleep button stops the sounds
### Fire Effect
[fire_effect.py](fire_effect.py)
A pretty, procedural fire effect. Switch between landscape fire and vertical fire using the A and B buttons! You can adjust the brightness with LUX + and -.
### Lava Lamp
[lava_lamp.py](lava_lamp.py)
A 70s-tastic, procedural rainbow lava lamp. You can adjust the brightness with LUX + and -.
### Nostalgia Prompt
[nostalgia_prompt.py](nostalgia_prompt.py)
A collection of copies of classic terminal styles including C64, MS-DOS, Spectrum, and more. Images and text are drawn pixel by pixel from a pattern of Os and Xs. You can adjust the brightness with LUX + and -.
### Rainbow
[rainbow.py](rainbow.py)
Some good old fashioned rainbows! You can adjust the cycling speed with A and B, stripe width with C and D, hue with VOL + and -, and the brightness with LUX + and -. The sleep button stops the animation (can be started again with A or B).
### Scrolling Text
[scrolling_text.py](scrolling_text.py)
Display scrolling wisdom, quotes or greetz. You can adjust the brightness with LUX + and -.
### Today
[today.py](today.py)
Calendar example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the date by pressing C.
## Wireless Examples
These examples need `WIFI_CONFIG.py` and `network_manager.py` (from the `common` directory) to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
- [micropython/examples/common](../../examples/common)
### Cheerlights History
[cheerlights_history.py](cheerlights_history.py)
Updates one pixel every two minutes to display the most recent #Cheerlights colour. Discover the most popular colours over time, or use it as an avant garde (but colourful) 32 hour clock! Find out more about the Cheerlights API at https://cheerlights.com/
You can adjust the brightness with LUX + and -.
### Cosmic Paint
[cosmic_paint](cosmic_paint)
Draw on your Cosmic Unicorn from another device in real time, over wifi!
This example needs the `micropython-phew` and `microdot` libraries (you can install these using Thonny's 'Tools > Manage Packages').
### Exchange Ticker
[exchange_ticker.py](exchange_ticker.py)
This example uses the Coinbase open API to collect the current exchange rates of various cryptocurrencies.
Press A to change to a different base exchange currency.
### HTTP Text
[http_text](http_text)
Display scrolling wisdom, quotes or greetz... from another computer or device!
You can adjust the brightness with LUX + and -.
Requires `logging.mpy` and `tinyweb` from [micropython/examples/common](../../examples/common) - copy these into the `lib` folder on your Pico W. You'll also need `index.html` to be saved alongside `html_text.py`.
### Weather
[weather](weather)
Display current weather data from the [Open-Meteo](https://open-meteo.com/) weather API.
## NumPy examples
[numpy](numpy)
The examples in the folder use `numpy`-like array functions contained in the `ulab` library for super fast graphical effects.
## Other Examples
### Launch (Demo Reel)
[launch](launch)
If you want to get the demo reel that Cosmic Unicorn ships with back, copy the contents of this `launch` folder to your Pico W.
## Other Resources
Here are some cool Cosmic Unicorn community projects and resources that you might find useful / inspirational! Note that code at the links below has not been tested by us and we're not able to offer support with it.
- :link: [Green Energy Display with Cosmic Unicorn](https://www.hackster.io/andreas-motzek/clock-and-green-energy-display-with-cosmic-unicorn-641dcb)

View File

@ -0,0 +1,128 @@
# This Cosmic Unicorn example updates a pixel every two(ish) minutes
# to display the most recent #cheerlights colour. Discover the most popular
# colours over time, or use it as an avant garde (but colourful) 32 hour clock!
# Find out more about the Cheerlights API at https://cheerlights.com/
#
# To run this example you'll need WIFI_CONFIG.py and network_manager.py from
# the pimoroni-pico micropython/examples/common folder
import WIFI_CONFIG
from network_manager import NetworkManager
import uasyncio
import urequests
import time
from machine import Timer, Pin
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
URL = 'http://api.thingspeak.com/channels/1417/field/2/last.json'
UPDATE_INTERVAL = 113 # refresh interval in secs. Be nice to free APIs!
# this esoteric number is used so that a column of LEDs equates (approximately) to an hour
def status_handler(mode, status, ip):
# reports wifi connection status
print(mode, status, ip)
print('Connecting to wifi...')
if status is not None:
if status:
print('Wifi connection successful!')
else:
print('Wifi connection failed!')
def hex_to_rgb(hex):
# converts a hex colour code into RGB
h = hex.lstrip('#')
r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4))
return r, g, b
def get_data():
# open the json file
print(f'Requesting URL: {URL}')
r = urequests.get(URL)
# open the json data
j = r.json()
print('Data obtained!')
r.close()
# flash the onboard LED after getting data
pico_led.value(True)
time.sleep(0.2)
pico_led.value(False)
# extract hex colour from the json data
hex = j['field2']
# add the new hex colour to the end of the array
colour_array.append(hex)
print(f'Colour added to array: {hex}')
# remove the oldest colour in the array
colour_array.pop(0)
update_leds()
def update_leds():
# light up the LEDs
# this step takes a second, it's doing a lot of hex_to_rgb calculations!
print("Updating LEDs...")
i = 0
for x in range(width):
for y in range(height):
r, g, b = hex_to_rgb(colour_array[i])
current_colour = graphics.create_pen(r, g, b)
graphics.set_pen(current_colour)
graphics.pixel(x, y)
i = i + 1
cu.update(graphics)
print("LEDs updated!")
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
cu.set_brightness(0.5)
# set up the Pico W's onboard LED
pico_led = Pin('LED', Pin.OUT)
current_colour = graphics.create_pen(0, 0, 0)
# set up an list to store the colours
colour_array = ["#000000"] * 1024
# set up wifi
try:
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
except Exception as e:
print(f'Wifi connection failed! {e}')
# get the first lot of data
get_data()
# start timer (the timer will call the function to update our data every UPDATE_INTERVAL)
timer = Timer(-1)
timer.init(period=UPDATE_INTERVAL * 1000, mode=Timer.PERIODIC, callback=lambda t: get_data())
while True:
# adjust brightness with LUX + and -
# LEDs take a couple of secs to update, so adjust in big (10%) steps
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.1)
update_leds()
print(f"Brightness set to {cu.get_brightness()}")
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.1)
update_leds()
print(f"Brightness set to {cu.get_brightness()}")
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)

View File

@ -0,0 +1,229 @@
# Clock example with NTP synchronization
#
# Create a secrets.py with your Wifi details to be able to get the time
# when the Cosmic Unicorn isn't connected to Thonny.
#
# secrets.py should contain:
# WIFI_SSID = "Your WiFi SSID"
# WIFI_PASSWORD = "Your WiFi password"
#
# Clock synchronizes time on start, and resynchronizes if you press the A button
import time
import math
import machine
import network
import ntptime
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
try:
from secrets import WIFI_SSID, WIFI_PASSWORD
wifi_available = True
except ImportError:
print("Create secrets.py with your WiFi credentials to get time from NTP")
wifi_available = False
# constants for controlling the background colour throughout the day
MIDDAY_HUE = 1.1
MIDNIGHT_HUE = 0.8
HUE_OFFSET = -0.1
MIDDAY_SATURATION = 1.0
MIDNIGHT_SATURATION = 1.0
MIDDAY_VALUE = 0.8
MIDNIGHT_VALUE = 0.3
# create cosmic object and graphics surface for drawing
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
# create the rtc object
rtc = machine.RTC()
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
# set up some pens to use later
WHITE = graphics.create_pen(255, 255, 255)
BLACK = graphics.create_pen(0, 0, 0)
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return int(v), int(t), int(p)
if i == 1:
return int(q), int(v), int(p)
if i == 2:
return int(p), int(v), int(t)
if i == 3:
return int(p), int(q), int(v)
if i == 4:
return int(t), int(p), int(v)
if i == 5:
return int(v), int(p), int(q)
# function for drawing a gradient background
def gradient_background(start_hue, start_sat, start_val, end_hue, end_sat, end_val):
half_width = width // 2
for x in range(0, half_width):
hue = ((end_hue - start_hue) * (x / half_width)) + start_hue
sat = ((end_sat - start_sat) * (x / half_width)) + start_sat
val = ((end_val - start_val) * (x / half_width)) + start_val
colour = from_hsv(hue, sat, val)
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
for y in range(0, height):
graphics.pixel(x, y)
graphics.pixel(width - x - 1, y)
colour = from_hsv(end_hue, end_sat, end_val)
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
for y in range(0, height):
graphics.pixel(half_width, y)
# function for drawing outlined text
def outline_text(text, x, y):
graphics.set_pen(BLACK)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(WHITE)
graphics.text(text, x, y, -1, 1)
# Connect to wifi and synchronize the RTC time from NTP
def sync_time():
if not wifi_available:
return
# Start connection
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
# Wait for connect success or failure
max_wait = 100
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(0.2)
redraw_display_if_reqd()
cu.update(graphics)
if max_wait > 0:
print("Connected")
try:
ntptime.settime()
print("Time set")
except OSError:
pass
wlan.disconnect()
wlan.active(False)
# NTP synchronizes the time to UTC, this allows you to adjust the displayed time
# by one hour increments from UTC by pressing the volume up/down buttons
#
# We use the IRQ method to detect the button presses to avoid incrementing/decrementing
# multiple times when the button is held.
utc_offset = 0
up_button = machine.Pin(CosmicUnicorn.SWITCH_VOLUME_UP, machine.Pin.IN, machine.Pin.PULL_UP)
down_button = machine.Pin(CosmicUnicorn.SWITCH_VOLUME_DOWN, machine.Pin.IN, machine.Pin.PULL_UP)
def adjust_utc_offset(pin):
global utc_offset
if pin == up_button:
utc_offset += 1
if pin == down_button:
utc_offset -= 1
up_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
down_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
last_second = second
# Check whether the RTC time has changed and if so redraw the display
def redraw_display_if_reqd():
global year, month, day, wd, hour, minute, second, last_second
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
if second != last_second:
hour = (hour + utc_offset) % 24
time_through_day = (((hour * 60) + minute) * 60) + second
percent_through_day = time_through_day / 86400
percent_to_midday = 1.0 - ((math.cos(percent_through_day * math.pi * 2) + 1) / 2)
print(percent_to_midday)
hue = ((MIDDAY_HUE - MIDNIGHT_HUE) * percent_to_midday) + MIDNIGHT_HUE
sat = ((MIDDAY_SATURATION - MIDNIGHT_SATURATION) * percent_to_midday) + MIDNIGHT_SATURATION
val = ((MIDDAY_VALUE - MIDNIGHT_VALUE) * percent_to_midday) + MIDNIGHT_VALUE
gradient_background(hue, sat, val,
hue + HUE_OFFSET, sat, val)
clock = "{:02}:{:02}:{:02}".format(hour, minute, second)
# calculate text position so that it is centred
w = graphics.measure_text(clock, 1)
x = int(width / 2 - w / 2 + 1)
y = 12
outline_text(clock, x, y)
last_second = second
# set the font
graphics.set_font("bitmap8")
cu.set_brightness(0.5)
sync_time()
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
sync_time()
redraw_display_if_reqd()
# update the display
cu.update(graphics)
time.sleep(0.01)

View File

@ -0,0 +1,11 @@
# Cosmic Paint
Cosmic Paint lets you paint pixels onto your Cosmic Unicorn over WiFi, in realtime!
## Setting Up
You'll need `WIFI_CONFIG.py` from the `common` directory to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
You will also have to install `micropython-phew` and `microdot` through Thonny's Tools -> Manage Packages.
Run the example through Thonny and it should get connected and give you a URL to visit. Open that URL in your browser and start painting!

View File

@ -0,0 +1,113 @@
import os
from microdot_asyncio import Microdot, send_file
from microdot_asyncio_websocket import with_websocket
from phew import connect_to_wifi
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
from WIFI_CONFIG import SSID, PSK
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
mv_graphics = memoryview(graphics)
cu.set_brightness(0.5)
WIDTH, HEIGHT = graphics.get_bounds()
ip = connect_to_wifi(SSID, PSK)
print(f"Start painting at: http://{ip}")
server = Microdot()
@server.route("/", methods=["GET"])
def route_index(request):
return send_file("cosmic_paint/index.html")
@server.route("/static/<path:path>", methods=["GET"])
def route_static(request, path):
return send_file(f"cosmic_paint/static/{path}")
def get_pixel(x, y):
if x < WIDTH and y < HEIGHT and x >= 0 and y >= 0:
o = (y * WIDTH + x) * 4
return tuple(mv_graphics[o:o + 3])
return None
def flood_fill(x, y, r, g, b):
todo = []
def fill(x, y, c):
if get_pixel(x, y) != c:
return
graphics.pixel(x, y)
up = get_pixel(x, y - 1)
dn = get_pixel(x, y + 1)
lf = get_pixel(x - 1, y)
ri = get_pixel(x + 1, y)
if up == c:
todo.append((x, y - 1))
if dn == c:
todo.append((x, y + 1))
if lf == c:
todo.append((x - 1, y))
if ri == c:
todo.append((x + 1, y))
c = get_pixel(x, y)
if c is None:
return
fill(x, y, c)
while len(todo):
x, y = todo.pop(0)
fill(x, y, c)
@server.route('/paint')
@with_websocket
async def echo(request, ws):
while True:
data = await ws.receive()
try:
x, y, r, g, b = [int(n) for n in data[0:5]]
graphics.set_pen(graphics.create_pen(r, g, b))
graphics.pixel(x, y)
except ValueError:
if data == "show":
cu.update(graphics)
if data == "fill":
data = await ws.receive()
x, y, r, g, b = [int(n) for n in data[0:5]]
graphics.set_pen(graphics.create_pen(r, g, b))
flood_fill(x, y, r, g, b)
if data == "clear":
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if data == "save":
filename = await ws.receive()
print(f"Saving to {filename}.bin")
try:
os.mkdir("saves")
except OSError:
pass
with open(f"saves/{filename}.bin", "wb") as f:
f.write(graphics)
await ws.send(f"alert: Saved to saves/{filename}.bin")
server.run(host="0.0.0.0", port=80)

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Cosmic Paint</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="/static/paint.css">
</head>
<body>
<div class="window">
<h1>Cosmic Paint</h1>
<table cellspacing="0" cellpadding="0" border-collapse="collapse">
<tbody></tbody>
</table>
<div id="palette">
<ul>
<li class="selected" style="background:rgb(0,0,0);"></li>
<li style="background:rgb(132,0,0);"></li>
<li style="background:rgb(0,132,0);"></li>
<li style="background:rgb(132,132,0);"></li>
<li style="background:rgb(0,0,132);"></li>
<li style="background:rgb(132,0,132);"></li>
<li style="background:rgb(0,132,132);"></li>
<li style="background:rgb(132,132,132);"></li>
<li style="background:rgb(198,198,198);"></li>
<li style="background:rgb(255,0,0);"></li>
<li style="background:rgb(0,255,0);"></li>
<li style="background:rgb(255,255,0);"></li>
<li style="background:rgb(0,0,255);"></li>
<li style="background:rgb(255,0,255);"></li>
<li style="background:rgb(0,255,255);"></li>
<li style="background:rgb(255,255,255);"></li>
</ul>
<input type="color" id="custom" name="custom" value="#ff0000">
</div>
<ul class="tools">
<li data-tool="paint" class="paint selected"><span class="fa fa-pencil"></span></li>
<li data-tool="fill" class="fill"><span class="fa fa-bitbucket"></span></li>
<li data-tool="erase" class="erase"><span class="fa fa-eraser"></span></li>
<li data-tool="pick" class="pick"><span class="fa fa-eyedropper"></span></li>
<li data-tool="lighten" class="lighten"><span class="fa fa-sun-o"></span></li>
<li data-tool="darken" class="darken"><span class="fa fa-adjust"></span></li>
<li data-tool="trash" class="trash"><span class="fa fa-trash"></span></li>
<li data-tool="save" class="save"><span class="fa fa-save"></span></li>
</ul>
</div>
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"></script>
<script type="text/javascript" src="/static/tinycolor.js"></script>
<script type="text/javascript" src="/static/paint.js"></script>
</body>
</html>

View File

@ -0,0 +1,131 @@
body {
background:#333;
padding:20px;
font-family:Arial, Verdana, Sans-Serif;
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAaUlEQVQYV33Q0Q3AIAgEUBjBFVyBFRzbWVjBEajXBIOVypcJj1NhETG61BiDVJX4Bh211v5hRDiniV+Elx0wQwd0hEatlUop65srMSah23vf8Auz65AWMc8rDHvCCjAQK2KeDcuQDzh+AHEJX8mbbU1BAAAAAElFTkSuQmCC) repeat;
}
.icons {
position:absolute;
margin:0;
padding:20px;
list-style:none;
}
.icons li {
margin:20px;
padding:0;
list-style:none;
padding-top:80px;
width:100px;
}
.icons li span {
background:#FFF;
color:#000;
border:1px solid #000;
line-height:20px;
padding:5px 10px;
text-align:center;
font-size:10px;
line-height:10px;
display:inline-block;
}
#palette ul, #palette li {
margin:0;padding:0;list-style:none;
}
#palette {
list-style:none;
position:relative;
height: 122px;
padding:0 8px;
}
#palette ul {
display:block;
width:456px;
float: left;
}
#palette li, #palette input {
border: 2px outset;
width:49px;
height:49px;
float:left;
display:block;
margin:2px;
}
#palette input {
width:110px;
height:110px;
}
.window {
width: 640px;
position: relative;
background: #0E071A;
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
}
.tools {
margin:0;padding:0;list-style:none;
clear:both;
display:block;
position:absolute;
top: 50px;
right: 8px;
width: 98px;
background:#999999;
font-size:0;
}
.tools span {
line-height:30px;
}
.tools li {
font-size:16px;
width: 45px;
height: 40px;
text-align:center;
margin:0;
padding:0;
display:inline-block;
line-height:40px;
border:2px outset #EEEEEE;
background:#F5F5F5;
cursor:pointer;
color:#000;
}
.tools li.selected {
background:#000;
color:#FFF;
}
h1 {
color: #FFF;
background: #6D38BB;
height:40px;
margin:0;
padding:0 8px;
line-height:40px;
font-weight:normal;
font-size:24px;
}
table {
clear:both;
cursor:pointer;
margin:10px;
border:1px solid #333;
background: #000000;
}
table td {
width:14px;
height:14px;
border:1px solid #333;
}

View File

@ -0,0 +1,216 @@
'use strict';
var WIDTH = 32;
var HEIGHT = 32;
var md = false;
var color = tinycolor('#840000');
var update;
$(document).ready(function(){
var picker = $('#custom');
var palette = $('#palette');
picker.val(color.toHexString());
$(document)
.on('mousedown',function(e){md=true;})
.on('mouseup',function(e){md=false;});
$('table').on('dragstart', function(e){
e.preventDefault();
return false;
});
for (var y = 0; y < HEIGHT; y++) {
var row = $('<tr></tr>');
for (var x = 0; x < WIDTH; x++) {
row.append('<td></td>');
}
$('tbody').append(row);
}
$('.tools li').on('click', function(){
switch($(this).index()){
case 6:
clear();
break;
case 7:
save();
break;
default:
$('.tools li').removeClass('selected');
$(this).addClass('selected');
break;
}
});
picker.on('change', function(){
color = tinycolor($(this).val());
})
palette.find('li').on('click', function(){
pick(this);
});
function handle_tool(obj, is_click){
switch($('.tools li.selected').index()){
case 0: //'paint':
paint(obj);
break;
case 1: // Fill
if( is_click ) fill(obj);
break;
case 2: // Erase
update_pixel(obj, tinycolor('#000000'));
break;
case 3: //'pick':
pick(obj);
break;
case 4: //'lighten':
lighten(obj);
break;
case 5: //'darken':
darken(obj);
break;
}
}
var fill_target = null;
var fill_stack = [];
function fill(obj){
fill_target = tinycolor($(obj).css('background-color')).toRgbString();
if( fill_target == color.toRgbString() ){
return false;
}
var x = $(obj).index();
var y = $(obj).parent().index();
socket.send("fill");
socket.send(new Uint8Array([x, y, color.toRgb().r, color.toRgb().g, color.toRgb().b]));
socket.send('show');
do_fill(obj);
while(fill_stack.length > 0){
var pixel = fill_stack.pop();
do_fill(pixel);
}
}
function is_target_color(obj){
return ( tinycolor($(obj).css('background-color')).toRgbString() == fill_target);
}
function do_fill(obj){
var obj = $(obj);
if( is_target_color(obj) ){
$(obj).css('background-color', color.toRgbString());
var r = obj.next('td'); // Right
var l = obj.prev('td'); // Left
var u = obj.parent().prev('tr').find('td:eq(' + obj.index() + ')'); // Above
var d = obj.parent().next('tr').find('td:eq(' + obj.index() + ')'); // Below
if( r.length && is_target_color(r[0]) ) fill_stack.push(r[0]);
if( l.length && is_target_color(l[0]) ) fill_stack.push(l[0]);
if( u.length && is_target_color(u[0]) ) fill_stack.push(u[0]);
if( d.length && is_target_color(d[0]) ) fill_stack.push(d[0]);
}
}
function save(){
var filename = prompt('Please enter a filename', 'mypaint');
filename = filename.replace(/[^a-z0-9]/gi, '_').toLowerCase();
socket.send('save');
socket.send(filename);
}
function clear(){
$('td').css('background-color','rgb(0,0,0)').data('changed',false);
socket.send('clear');
socket.send('show');
}
function lighten(obj){
var c = tinycolor($(obj).css('background-color'));
c.lighten(5);
update_pixel(obj, c);
}
function darken(obj){
var c = tinycolor($(obj).css('background-color'));
c.darken(5);
update_pixel(obj, c);
}
function pick(obj){
color = tinycolor($(obj).css('background-color'));
picker.val(color.toHexString());
}
function update_pixel(obj, col){
var bgcol = tinycolor($(obj).css('background-color'));
if(col != bgcol){
$(obj)
.data('changed', true)
.css('background-color', col.toRgbString());
}
}
function update_pixels(){
var changed = false;
$('td').each(function( index, obj ){
if($(obj).data('changed')){
$(obj).data('changed',false);
changed = true;
var x = $(this).index();
var y = $(this).parent().index();
var col = tinycolor($(obj).css('background-color')).toRgb();
if(socket) {
socket.send(new Uint8Array([x, y, col.r, col.g, col.b]));
}
}
});
if(changed){
socket.send('show');
}
}
function paint(obj){
update_pixel(obj, color);
}
$('table td').on('click', function(){
handle_tool(this, true);
});
$('table td').on('mousemove', function(){
if(!md) return false;
handle_tool(this, false);
})
const socket = new WebSocket('ws://' + window.location.host + '/paint');
socket.addEventListener('message', ev => {
console.log('<<< ' + ev.data);
if(ev.data.substring(0, 6) == "alert:") {
alert(ev.data.substring(6));
}
});
socket.addEventListener('close', ev => {
console.log('<<< closed');
});
socket.addEventListener('open', ev => {
clear();
update = setInterval(update_pixels, 50);
});
});

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
import time
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
Random LEDs blink on and off mimicing the look of a movie
super computer doing its work in the eighties.
You can adjust the brightness with LUX + and -.
'''
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
colour = (230, 150, 0)
@micropython.native # noqa: F821
def setup():
global width, height, lifetime, age
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
lifetime = [[0.0 for y in range(height)] for x in range(width)]
age = [[0.0 for y in range(height)] for x in range(width)]
for y in range(height):
for x in range(width):
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y]
@micropython.native # noqa: F821
def draw():
for y in range(height):
for x in range(width):
if age[x][y] < lifetime[x][y] * 0.3:
graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2]))
elif age[x][y] < lifetime[x][y] * 0.5:
decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0
graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2])))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
cu.update(graphics)
@micropython.native # noqa: F821
def update():
for y in range(height):
for x in range(width):
if age[x][y] >= lifetime[x][y]:
age[x][y] = 0.0
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] += 0.025
setup()
cu.set_brightness(0.5)
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
start = time.ticks_ms()
draw()
update()
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)
print("total took: {} ms".format(time.ticks_ms() - start))

View File

@ -0,0 +1,148 @@
"""
This example uses the Coinbase open API to collect the current exchange rates.
Use Switch A to change to a different base exchange currency.
"""
import WIFI_CONFIG
from network_manager import NetworkManager
import uasyncio
import urequests
import time
import math
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
import gc
URL = 'https://api.coinbase.com/v2/exchange-rates?currency={0}'
currencies = {"Bitcoin": "BTC", "Ethereun": "ETH", "Pound": "GBP", "Dollar": "USD", "Dogecoin": "DOGE"}
currency_keys = list(currencies.keys())
ref_currency_name = ""
currency_name = ""
currency_symbol = ""
currency_rate = ""
rate_keys = []
# diplay options
line_1_line = -2
line_2_line = 9
line_3_line = 20
ref_currency_index = 0
cycles_per_sequence = 120
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
# for Handling the wifi connection
def status_handler(mode, status, ip):
# reports wifi connection status
print(mode, status, ip)
print('Connecting to wifi...')
if status is not None:
if status:
print('Wifi connection successful!')
else:
print('Wifi connection failed!')
try:
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
except Exception as e:
print(f'Wifi connection failed! {e}')
def get_data(currency_selected):
graphics.set_pen(graphics.create_pen(20, 20, 20))
graphics.clear()
graphics.set_pen(graphics.create_pen(100, 100, 100))
graphics.text("Get", 0, 10, scale=1, spacing=1)
graphics.text("data", 8, 16, scale=1, spacing=1)
cu.update(graphics)
gc.collect()
# open the json file
print('Requesting URL:')
print(URL.format(currencies[currency_selected]))
r = urequests.get(URL.format(currencies[currency_selected]))
gc.collect()
# open the json data
data_obj = r.json()
print('Data obtained!')
r.close()
return data_obj
def calculate_xpos(length, cycle):
cycle_phase = math.cos(math.pi * cycle / (cycles_per_sequence / 2))
pos_x = int((-(length / 2) * 10) - (length / 2) * 10 * cycle_phase)
return pos_x
def update_display(cycle):
graphics.set_pen(graphics.create_pen(20, 20, 20))
graphics.clear()
graphics.set_pen(graphics.create_pen(100, 0, 0))
graphics.text(ref_currency_name, calculate_xpos((len(ref_currency_name)), cycle), line_1_line, scale=2, spacing=1)
graphics.set_pen(graphics.create_pen(100, 100, 0))
if len(currency_symbol) > 3:
graphics.text(currency_symbol, calculate_xpos((len(currency_symbol)), cycle), line_2_line, scale=2, spacing=1)
else:
graphics.text(currency_symbol, 0, line_2_line, scale=2, spacing=1)
graphics.set_pen(graphics.create_pen(0, 100, 100))
graphics.text(currency_rate, calculate_xpos((len(currency_rate)), cycle), line_3_line, scale=2, spacing=1)
def update_base_currency(index):
fetched_data = 0
global rates, rate_keys, currency_symbol, currency_rate, ref_currency_name
fetched_data = get_data(currency_keys[index])
rates = fetched_data['data']['rates']
rate_keys = list(rates.keys())
currency_symbol = rate_keys[index]
currency_rate = str(rates[rate_keys[index]])
ref_currency_name = "{0}-{1}".format(currency_keys[index], currencies[currency_keys[index]])
gc.collect()
update_base_currency(ref_currency_index)
update_display(0)
cu.update(graphics)
cycle_count = 0
symbol_index = 0
print("Display {0} {1}".format(currency_symbol, currency_rate))
while 1:
if cycle_count > 4 * cycles_per_sequence:
cycle_count = 0
symbol_index += 1
if symbol_index > len(currency_keys):
symbol_index = 0
print("Display {0} {1}".format(currency_symbol, currency_rate))
currency_symbol = rate_keys[symbol_index]
currency_rate = rates[rate_keys[symbol_index]]
if (cu.is_pressed(CosmicUnicorn.SWITCH_A)):
ref_currency_index += 1
if (ref_currency_index > len(currency_keys)):
ref_currency_index = 0
update_base_currency(ref_currency_index)
if (cu.is_pressed(CosmicUnicorn.SWITCH_B)):
cycle_count = 0
symbol_index += 1
if symbol_index > len(rate_keys):
symbol_index = 0
currency_symbol = rate_keys[symbol_index]
currency_rate = rates[rate_keys[symbol_index]]
update_display(cycle_count)
cu.update(graphics)
cycle_count += 1
time.sleep(0.1)

View File

@ -0,0 +1,123 @@
import time
import math
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
Displays some text, gradients and colours and demonstrates button use.
You can adjust the brightness with LUX + and -.
'''
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
def gradient(r, g, b):
for y in range(0, height):
for x in range(0, width):
graphics.set_pen(graphics.create_pen(int((r * x) / 32), int((g * x) / 32), int((b * x) / 32)))
graphics.pixel(x, y)
def grid(r, g, b):
for y in range(0, height):
for x in range(0, width):
if (x + y) % 2 == 0:
graphics.set_pen(graphics.create_pen(r, g, b))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
def outline_text(text):
ms = time.ticks_ms()
graphics.set_font("bitmap8")
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
w = graphics.measure_text(text, 1)
x = int(32 / 2 - w / 2 + 1)
y = 12
graphics.set_pen(0)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(v, v, v))
graphics.text(text, x, y, -1, 1)
cu.set_brightness(0.5)
while True:
time_ms = time.ticks_ms()
test = (time_ms // 1000) % 5
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if test == 0:
print("grid pattern")
grid(255, 255, 255)
elif test == 1:
print("red gradient")
gradient(255, 0, 0)
elif test == 2:
print("green gradient")
gradient(0, 255, 0)
elif test == 3:
print("blue gradient")
gradient(0, 0, 255)
elif test == 4:
print("white gradient")
gradient(255, 255, 255)
text = ""
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
text = "Button A"
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
text = "Button B"
if cu.is_pressed(CosmicUnicorn.SWITCH_C):
text = "Button C"
if cu.is_pressed(CosmicUnicorn.SWITCH_D):
text = "Button D"
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP):
text = "Louder!"
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN):
text = "Quieter"
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
text = "Brighter!"
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
text = "Darker"
if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP):
text = "Zzz... zzz..."
outline_text(text)
cu.update(graphics)

View File

@ -0,0 +1,347 @@
import gc
import time
import math
from machine import Timer
from cosmic import CosmicUnicorn, Channel
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
Displays some text, gradients and colours and demonstrates button use.
Also demonstrates some of the audio / synth features.
- Button A plays a synth tune
- Button B plays a solo channel of the synth tune
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
- Sleep button stops the sounds
'''
gc.collect()
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
SONG_LENGTH = 384
HAT = 20000
BASS = 500
SNARE = 6000
SUB = 50
melody_notes = (
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 262, 0, 294, 0, 392, 0, 440, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
rhythm_notes = (
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0)
drum_beats = (
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0)
hi_hat = (
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1)
bass_notes = (
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0)
notes = [melody_notes, rhythm_notes, drum_beats, hi_hat, bass_notes]
channels = [cu.synth_channel(i) for i in range(len(notes) + 1)] # Extra channel for tones
# Configure the synth to play our notes
channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
attack=0.016,
decay=0.168,
sustain=0xafff / 65535,
release=0.168,
volume=10000 / 65535)
channels[1].configure(waveforms=Channel.SINE + Channel.SQUARE,
attack=0.038,
decay=0.300,
sustain=0,
release=0,
volume=12000 / 65535)
channels[2].configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.010,
sustain=16000 / 65535,
release=0.100,
volume=18000 / 65535)
channels[3].configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.005,
sustain=8000 / 65535,
release=0.040,
volume=8000 / 65535)
channels[4].configure(waveforms=Channel.SQUARE,
attack=0.010,
decay=0.100,
sustain=0,
release=0.500,
volume=12000 / 65535)
def gradient(r, g, b):
for y in range(0, height):
for x in range(0, width):
graphics.set_pen(graphics.create_pen(int((r * x) / 52), int((g * x) / 52), int((b * x) / 52)))
graphics.pixel(x, y)
def grid(r, g, b):
for y in range(0, height):
for x in range(0, width):
if (x + y) % 2 == 0:
graphics.set_pen(graphics.create_pen(r, g, b))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
def outline_text(text):
ms = time.ticks_ms()
graphics.set_font("bitmap8")
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
w = graphics.measure_text(text, 1)
x = int(32 / 2 - w / 2 + 1)
y = 12
graphics.set_pen(0)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(v, v, v))
graphics.text(text, x, y, -1, 1)
cu.set_brightness(0.5)
# Vars for storing button state
was_a_pressed = False
was_b_pressed = False
was_c_pressed = False
was_d_pressed = False
was_z_pressed = False
# The two frequencies to play
tone_a = 0
tone_b = 0
# The current synth beat
beat = 0
text = ""
def next_beat():
global beat
for i in range(5):
if notes[i][beat] > 0:
channels[i].frequency(notes[i][beat])
channels[i].trigger_attack()
elif notes[i][beat] == -1:
channels[i].trigger_release()
beat = (beat + 1) % SONG_LENGTH
def tick(timer):
next_beat()
timer = Timer(-1)
synthing = False
while True:
time_ms = time.ticks_ms()
test = (time_ms // 1000) % 5
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
if not was_a_pressed:
channels[0].volume(10000 / 65535)
channels[1].volume(12000 / 65535)
channels[2].volume(18000 / 65535)
channels[3].volume(8000 / 65535)
channels[4].volume(12000 / 65535)
channels[5].volume(0)
# If the synth is not already playing, init the first beat
if not synthing:
beat = 0
next_beat()
cu.play_synth()
synthing = True
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
was_a_pressed = True
else:
was_a_pressed = False
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
if not was_b_pressed:
channels[0].volume(0)
channels[1].volume(12000 / 65535)
channels[2].volume(0)
channels[3].volume(0)
channels[4].volume(0)
channels[5].volume(0)
# If the synth is not already playing, init the first beat
if not synthing:
beat = 0
next_beat()
cu.play_synth()
synthing = True
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
was_b_pressed = True
else:
was_b_pressed = False
if cu.is_pressed(CosmicUnicorn.SWITCH_C):
if not was_c_pressed:
# Stop synth (if running) and play Tone A
timer.deinit()
tone_a = 400
channels[5].play_tone(tone_a, 0.06)
channels[5].volume(12000 / 65535)
cu.play_synth()
synthing = False
was_c_pressed = True
else:
was_c_pressed = False
if cu.is_pressed(CosmicUnicorn.SWITCH_D):
if not was_d_pressed:
# Stop synth (if running) and play Tone B
timer.deinit()
tone_b = 600
channels[5].play_tone(tone_b, 0.06, attack=0.5)
channels[5].volume(12000 / 65535)
cu.play_synth()
synthing = False
was_d_pressed = True
else:
was_d_pressed = False
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
if tone_b > 0: # Zero means tone not playing
# Increase Tone B
tone_b = min(tone_b + 10, 20000)
channels[5].frequency(tone_b)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
if tone_b > 0: # Zero means tone not playing
# Decrease Tone B
tone_b = max(tone_b - 10, 10)
channels[5].frequency(max(tone_b, 10))
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP):
if tone_a > 0: # Zero means tone not playing
# Increase Tone A
tone_a = min(tone_a + 10, 20000)
channels[5].frequency(tone_a)
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN):
if tone_a > 0: # Zero means tone not playing
# Decrease Tone A
tone_a = max(tone_a - 10, 10)
channels[5].frequency(tone_a)
if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP):
if not was_z_pressed:
# Stop synth and both tones
tone_a = 0
tone_b = 0
cu.stop_playing()
timer.deinit()
synthing = False
was_z_pressed = True
else:
was_z_pressed = False
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if test == 0:
# print("grid pattern")
grid(255, 255, 255)
elif test == 1:
# print("red gradient")
gradient(255, 0, 0)
elif test == 2:
# print("green gradient")
gradient(0, 255, 0)
elif test == 3:
# print("blue gradient")
gradient(0, 0, 255)
elif test == 4:
# print("white gradient")
gradient(255, 255, 255)
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
text = "PlaySyn"
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
text = "SoloSyn"
if cu.is_pressed(CosmicUnicorn.SWITCH_C):
text = "Tone A"
if cu.is_pressed(CosmicUnicorn.SWITCH_D):
text = "Tone B"
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP):
text = "RaiseA"
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN):
text = "LowerA"
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
text = "RaiseB"
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
text = "LowerB"
if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP):
text = "Stop"
outline_text(text)
cu.update(graphics)
# pause for a moment (important or the USB serial device will fail
time.sleep(0.001)

View File

@ -0,0 +1,102 @@
import time
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
A pretty, procedural fire effect.
You can adjust the brightness with LUX + and -.
'''
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
fire_colours = [graphics.create_pen(0, 0, 0),
graphics.create_pen(20, 20, 20),
graphics.create_pen(180, 30, 0),
graphics.create_pen(220, 160, 0),
graphics.create_pen(255, 255, 180)]
@micropython.native # noqa: F821
def update():
# take local references as it's quicker than accessing the global
# and we access it a lot in this method
_heat = heat
# clear the bottom row and then add a new fire seed to it
for x in range(width):
_heat[x][height - 1] = 0.0
_heat[x][height - 2] = 0.0
for c in range(fire_spawns):
x = random.randint(0, width - 4) + 2
_heat[x + 0][height - 1] = 1.0
_heat[x + 1][height - 1] = 1.0
_heat[x - 1][height - 1] = 1.0
_heat[x + 0][height - 2] = 1.0
_heat[x + 1][height - 2] = 1.0
_heat[x - 1][height - 2] = 1.0
factor = damping_factor / 5.0
for y in range(0, height - 2):
for x in range(1, width - 1):
_heat[x][y] += _heat[x][y + 1] + _heat[x][y + 2] + _heat[x - 1][y + 1] + _heat[x + 1][y + 1]
_heat[x][y] *= factor
@micropython.native # noqa: F821
def draw():
# take local references as it's quicker than accessing the global
# and we access it a lot in this method
_graphics = graphics
_heat = heat
_set_pen = graphics.set_pen
_pixel = graphics.pixel
_fire_colours = fire_colours
for y in range(CosmicUnicorn.HEIGHT):
for x in range(CosmicUnicorn.WIDTH):
value = _heat[x + 1][y]
if value < 0.15:
_set_pen(_fire_colours[0])
elif value < 0.25:
_set_pen(_fire_colours[1])
elif value < 0.35:
_set_pen(_fire_colours[2])
elif value < 0.45:
_set_pen(_fire_colours[3])
else:
_set_pen(_fire_colours[4])
_pixel(x, y)
cu.update(_graphics)
width = CosmicUnicorn.WIDTH + 2
height = CosmicUnicorn.HEIGHT + 4
heat = [[0.0 for y in range(height)] for x in range(width)]
fire_spawns = 5
damping_factor = 0.97
cu.set_brightness(0.5)
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
start = time.ticks_ms()
update()
draw()
print("total took: {} ms".format(time.ticks_ms() - start))
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)

View File

@ -0,0 +1,198 @@
import time
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
import WIFI_CONFIG
from network_manager import NetworkManager
import uasyncio as asyncio
import uasyncio.core
from tinyweb.server import webserver
'''
Display scrolling wisdom, quotes or greetz... from the internetz!
You can adjust the brightness with LUX + and -.
Requires network_manager.py , WIFI_CONFIG.py, logging.mpy and tinyweb from micropython/examples/common
You'll also need index.html to be saved alongside this file.
'''
# Server Settings
host = "0.0.0.0"
port = 80
def convert_colour(colour_str):
colour_str = colour_str.split(',')
print(colour_str)
return colour_str[0], colour_str[1], colour_str[2]
class text:
def get(self, data):
global MESSAGE, MESSAGE_COLOUR, BACKGROUND_COLOUR
print(data)
if 'text' in data.keys():
MESSAGE = data['text']
if 'colourfg' in data.keys():
MESSAGE_COLOUR = convert_colour(data['colourfg'])
if 'colourbg' in data.keys():
BACKGROUND_COLOUR = convert_colour(data['colourbg'])
return {'message': 'text updated'}, 201
def post(self, data):
return {'message': 'text updated'}, 201
def status_handler(mode, status, ip):
global MESSAGE
print("Network: {}".format(WIFI_CONFIG.SSID))
status_text = "Connecting..."
if status is not None:
if status:
status_text = "Connection successful!"
else:
status_text = "Connection failed!"
print(status_text)
print("IP: {}".format(ip))
MESSAGE = "{}".format(ip)
# Create web server application
app = webserver()
# Static page
html_file = open('index.html', 'r')
# WIFI settings
WIFI_COUNTRY = "GB" # Change to your local two-letter ISO 3166-1 country code
# Index page
@app.route('/')
async def index(request, response):
# Start HTTP response with content-type text/html
await response.start_html()
# Send actual HTML page
await response.send(html_file.read())
# HTTP redirection
@app.route('/redirect')
async def redirect(request, response):
# Start HTTP response with content-type text/html
await response.redirect('/')
# constants for controlling scrolling text
PADDING = 5
MESSAGE_COLOUR = (255, 255, 255)
OUTLINE_COLOUR = (0, 0, 0)
BACKGROUND_COLOUR = (10, 0, 96)
MESSAGE = "Connecting"
HOLD_TIME = 2.0
STEP_TIME = 0.075
# create galactic object and graphics surface for drawing
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
# function for drawing outlined text
def outline_text(text, x, y):
graphics.set_pen(graphics.create_pen(int(OUTLINE_COLOUR[0]), int(OUTLINE_COLOUR[1]), int(OUTLINE_COLOUR[2])))
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(int(MESSAGE_COLOUR[0]), int(MESSAGE_COLOUR[1]), int(MESSAGE_COLOUR[2])))
graphics.text(text, x, y, -1, 1)
def run():
# Setup wifi
network_manager = NetworkManager(WIFI_COUNTRY, status_handler=status_handler)
app.add_resource(text, '/update')
# Connect to Wifi network
asyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
while (not network_manager.isconnected()):
time.sleep(0.1)
cu.set_brightness(0.5)
# Start wifi connection
run()
async def message_update():
global MESSAGE
last_time = time.ticks_ms()
STATE_PRE_SCROLL = 0
STATE_SCROLLING = 1
STATE_POST_SCROLL = 2
shift = 0
state = STATE_PRE_SCROLL
# set the font
graphics.set_font("bitmap8")
# calculate the message width so scrolling can happen
msg_width = graphics.measure_text(MESSAGE, 1)
while 1:
msg_width = graphics.measure_text(MESSAGE, 1)
time_ms = time.ticks_ms()
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
if state == STATE_PRE_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
if msg_width + PADDING * 2 >= width:
state = STATE_SCROLLING
last_time = time_ms
if state == STATE_SCROLLING and time_ms - last_time > STEP_TIME * 1000:
shift += 1
if shift >= (msg_width + PADDING * 2) - width - 1:
state = STATE_POST_SCROLL
last_time = time_ms
if state == STATE_POST_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
state = STATE_PRE_SCROLL
shift = 0
last_time = time_ms
graphics.set_pen(graphics.create_pen(int(BACKGROUND_COLOUR[0]), int(BACKGROUND_COLOUR[1]), int(BACKGROUND_COLOUR[2])))
graphics.clear()
outline_text(MESSAGE, x=PADDING - shift, y=11)
# update the display
cu.update(graphics)
# pause for a moment (important or the USB serial device will fail)
await asyncio.sleep(0.001)
# The following is required to run both the web server and the scrolling text coherently
app._server_coro = app._tcp_server(host, port, app.backlog)
loop = asyncio.get_event_loop()
t1 = loop.create_task(message_update())
t2 = loop.create_task(app._server_coro)
loop.run_forever()

View File

@ -0,0 +1,102 @@
<http>
<head>
<style>
h1 {
align-content: center;
color: rgb(192, 192, 214);
margin-left: 20px;
background-color: darkmagenta;
font-size: xxx-large;
}
</style>
<script>
var getUrl = window.location;
var baseUrl = getUrl.protocol + "//" + getUrl.host;
var displayText;
var colourBF ="#000000";
var colourFG ="#000000";
function convertHexToRgb(hex) {
// Convert the first 2 characters to hexadecimal
var r = parseInt(hex.substring(1, 3), 16),
// Convert the middle 2 characters to hexadecimal
g = parseInt(hex.substring(3, 5), 16),
// Convert the last 2 characters to hexadecimal
b = parseInt(hex.substring(5, 7), 16);
// append them all
return r + ", " + g + ", "
+ b ;
}
function updateCU(comm,val){
console.log(typeof(comm))
console.log(typeof(val))
console.log(comm, val)
fetch(baseUrl+"/update" + "?"+comm+"="+String(val))
.then(response => {
// indicates whether the response is successful (status code 200-299) or not
if (!response.ok) {
throw new Error(`Request failed with status ${reponse.status}`)
}
return response.json()
})
.then(data => {
console.log(data)
})
.catch(error => console.log(error))
}
function updateText(){
displayText = document.getElementById("inputtxt").value;
colourFG = convertHexToRgb(document.getElementById("inputfgcolour").value);
colourBG = convertHexToRgb(document.getElementById("inputbgcolour").value);
updateCU("colourfg", colourFG);
updateCU("text", displayText);
updateCU("colourbg", colourBG);
}
function updateColour(){
colourFG = document.getElementById('inputfgcolour').value;
colourBG = document.getElementById('inputbgcolour').value;
}
</script>
</head>
<body>
<h1 id="title">Cosmic Unicorn Web Text</h1>
<center>
<label for="inputtxt"> Enter text to display:</label>
<br>
<input type="text" id="inputtxt" name="inputtxt" required
minlength="4" size="16">
<br>
<label for="inputfgcolour"> Text Colour:</label>
<br>
<input type="color" id="inputfgcolour" name="inputfgcolour" required
minlength="4" maxlength="30" size="16" onchange="updateColour()" value='#000000'>
<br>
<label for="inputbgcolour"> Background Colour:</label>
<br>
<input type="color" id="inputbgcolour" name="inputbgcolour" required
minlength="4" maxlength="30" size="16" onchange="updateColour()" value='#000000'>
<br>
<button class="favorite styled"
onclick="updateText()"
type="button">
Update
</button>
<p>
Please type in what you wish to be displayed on the Cosmic Unicorn and whe you are ready hit update to update the display
</p>
</center>
</body>
</http>

View File

@ -0,0 +1,80 @@
import random
from cosmic import CosmicUnicorn
graphics = None
palette = None
# setup heat value buffer and fire parameters
width = CosmicUnicorn.WIDTH + 2
height = CosmicUnicorn.HEIGHT + 4
heat = [[0.0 for y in range(height)] for x in range(width)]
fire_spawns = 5
damping_factor = 0.97
def init():
# a palette of five firey colours (white, yellow, orange, red, smoke)
global palette
palette = [
graphics.create_pen(0, 0, 0),
graphics.create_pen(20, 20, 20),
graphics.create_pen(180, 30, 0),
graphics.create_pen(220, 160, 0),
graphics.create_pen(255, 255, 180)
]
# returns the palette entry for a given heat value
@micropython.native # noqa: F821
def pen_from_value(value):
if value < 0.15:
return palette[0]
elif value < 0.25:
return palette[1]
elif value < 0.35:
return palette[2]
elif value < 0.45:
return palette[3]
return palette[4]
@micropython.native # noqa: F821
def draw():
# clear the the rows off the bottom of the display
for x in range(width):
heat[x][height - 1] = 0.0
heat[x][height - 2] = 0.0
# add new fire spawns
for c in range(fire_spawns):
x = random.randint(0, width - 4) + 2
heat[x + 0][height - 1] = 1.0
heat[x + 1][height - 1] = 1.0
heat[x - 1][height - 1] = 1.0
heat[x + 0][height - 2] = 1.0
heat[x + 1][height - 2] = 1.0
heat[x - 1][height - 2] = 1.0
# average and damp out each value to create rising flame effect
for y in range(0, height - 2):
for x in range(1, width - 1):
# update this pixel by averaging the below pixels
average = (
heat[x][y] + heat[x][y + 1] + heat[x][y + 2] + heat[x - 1][y + 1] + heat[x + 1][y + 1]
) / 5.0
# damping factor to ensure flame tapers out towards the top of the displays
average *= damping_factor
# update the heat map with our newly averaged value
heat[x][y] = average
# render the heat values to the graphics buffer
for y in range(CosmicUnicorn.HEIGHT):
for x in range(CosmicUnicorn.WIDTH):
graphics.set_pen(pen_from_value(heat[x + 1][y]))
graphics.pixel(x, y)
def test():
print("A")

View File

@ -0,0 +1,113 @@
import time
import machine
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
# overclock to 200Mhz
machine.freq(200000000)
# create cosmic object and graphics surface for drawing
cosmic = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
brightness = 0.5
# returns the id of the button that is currently pressed or
# None if none are
def pressed():
if cosmic.is_pressed(CosmicUnicorn.SWITCH_A):
return CosmicUnicorn.SWITCH_A
if cosmic.is_pressed(CosmicUnicorn.SWITCH_B):
return CosmicUnicorn.SWITCH_B
if cosmic.is_pressed(CosmicUnicorn.SWITCH_C):
return CosmicUnicorn.SWITCH_C
if cosmic.is_pressed(CosmicUnicorn.SWITCH_D):
return CosmicUnicorn.SWITCH_D
return None
# wait for a button to be pressed and load that effect
while True:
graphics.set_font("bitmap6")
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
graphics.set_pen(graphics.create_pen(155, 155, 155))
graphics.text("PRESS", 3, 6, -1, 1)
graphics.text("A B C OR D!", 5, 14, 32, 1, 0)
# brightness up/down
if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
brightness += 0.01
if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
brightness -= 0.01
brightness = max(min(brightness, 1.0), 0.0)
cosmic.set_brightness(brightness)
cosmic.update(graphics)
if pressed() == CosmicUnicorn.SWITCH_A:
import fire as effect
break
if pressed() == CosmicUnicorn.SWITCH_B:
import supercomputer as effect # noqa: F811
break
if pressed() == CosmicUnicorn.SWITCH_C:
import rainbow as effect # noqa: F811
break
if pressed() == CosmicUnicorn.SWITCH_D:
import today as effect # noqa: F811
break
# pause for a moment
time.sleep(0.01)
# wait until all buttons are released
while pressed() is not None:
time.sleep(0.1)
effect.graphics = graphics
effect.init()
sleep = False
was_sleep_pressed = False
# wait
while True:
# if A, B, C, or D are pressed then reset
if pressed() is not None:
machine.reset()
sleep_pressed = cosmic.is_pressed(CosmicUnicorn.SWITCH_SLEEP)
if sleep_pressed and not was_sleep_pressed:
sleep = not sleep
was_sleep_pressed = sleep_pressed
if sleep:
# fade out if screen not off
cosmic.set_brightness(cosmic.get_brightness() - 0.01)
if cosmic.get_brightness() > 0.0:
effect.draw()
# update the display
cosmic.update(graphics)
else:
effect.draw()
# update the display
cosmic.update(graphics)
# brightness up/down
if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
brightness += 0.01
if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
brightness -= 0.01
brightness = max(min(brightness, 1.0), 0.0)
cosmic.set_brightness(brightness)
# pause for a moment (important or the USB serial device will fail
time.sleep(0.001)

View File

@ -0,0 +1,59 @@
import math
from cosmic import CosmicUnicorn
graphics = None
palette = None
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return int(v), int(t), int(p)
if i == 1:
return int(q), int(v), int(p)
if i == 2:
return int(p), int(v), int(t)
if i == 3:
return int(p), int(q), int(v)
if i == 4:
return int(t), int(p), int(v)
if i == 5:
return int(v), int(p), int(q)
phase = 0
hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)]
hue_offset = 0.0
stripe_width = 3.0
speed = 5.0
def init():
pass
@micropython.native # noqa: F821
def draw():
global hue_offset, phase
phase += speed
phase_percent = phase / 15
for x in range(width):
colour = hue_map[int((x + (hue_offset * width)) % width)]
for y in range(height):
v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5)
graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v)))
graphics.pixel(x, y)

View File

@ -0,0 +1,40 @@
import random
from cosmic import CosmicUnicorn
graphics = None
colour = (230, 150, 0)
def init():
global width, height, lifetime, age
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
lifetime = [[0.0 for y in range(height)] for x in range(width)]
age = [[0.0 for y in range(height)] for x in range(width)]
for y in range(height):
for x in range(width):
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y]
@micropython.native # noqa: F821
def draw():
for y in range(height):
for x in range(width):
if age[x][y] >= lifetime[x][y]:
age[x][y] = 0.0
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] += 0.025
for y in range(height):
for x in range(width):
if age[x][y] < lifetime[x][y] * 0.3:
graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2]))
elif age[x][y] < lifetime[x][y] * 0.5:
decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0
graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2])))
else:
graphics.set_pen(0)
graphics.pixel(x, y)

View File

@ -0,0 +1,100 @@
import time
import network
import ntptime
import machine
# You will need to create or update the file secrets.py with your network credentials using Thonny
# in order for the example to update using the NTP.
# secrets.py should contain:
# WIFI_SSID = ""
# WIFI_PASSWORD = ""
try:
from secrets import WIFI_SSID, WIFI_PASSWORD
except ImportError:
print("Create secrets.py with your WiFi credentials")
graphics = None
WIDTH = 32 # CosmicUnicorn.WIDTH
HEIGHT = 32 # CosmicUnicorn.HEIGHT
rtc = machine.RTC()
DAYS = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat", "Sun"]
# Enable the Wireless
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
def network_connect(SSID, PSK):
# Number of attempts to make before timeout
max_wait = 5
# Sets the Wireless LED pulsing and attempts to connect to your local network.
print("connecting...")
wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs
wlan.connect(SSID, PSK)
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(1)
# Handle connection error. Switches the Warn LED on.
if wlan.status() != 3:
print("Unable to connect. Attempting connection again")
# Function to sync the Pico RTC using NTP
def sync_time():
try:
network_connect(WIFI_SSID, WIFI_PASSWORD)
except NameError:
print("Create secrets.py with your WiFi credentials")
if wlan.status() < 0 or wlan.status() >= 3:
try:
ntptime.settime()
except OSError:
print("Unable to sync with NTP server. Check network and try again.")
def init():
sync_time()
def draw():
# Pens
RED = graphics.create_pen(120, 0, 0)
WHITE = graphics.create_pen(255, 255, 255)
current_t = rtc.datetime()
# Set the pen to Red and clear the screen.
graphics.set_pen(WHITE)
graphics.clear()
# Measures the length of the text to help us with centring later.
day_length = graphics.measure_text(DAYS[current_t[3]], 1)
date_length = graphics.measure_text(str(current_t[2]), 3)
graphics.set_font("bitmap6")
graphics.set_pen(RED)
graphics.rectangle(0, 0, WIDTH, 7)
graphics.set_pen(WHITE)
graphics.text(DAYS[current_t[3]], (WIDTH // 2) - (day_length // 2) - 1, 0, 32, 1)
graphics.set_pen(RED)
graphics.set_font("bitmap8")
graphics.text(str(current_t[2]), (WIDTH // 2) - (date_length // 2) + 1, 9, 32, 3)
graphics.set_pen(graphics.create_pen(0, 0, 0))

View File

@ -0,0 +1,151 @@
import time
import random
import math
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
A 70s-tastic, procedural rainbow lava lamp.
You can adjust the brightness with LUX + and -.
'''
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
blob_count = 10
class Blob():
def __init__(self):
self.x = float(random.randint(0, width - 1))
self.y = float(random.randint(0, height - 1))
self.r = (float(random.randint(0, 40)) / 10.0) + 5.0
self.dx = (float(random.randint(0, 2)) / 10.0) - 0.1
self.dy = (float(random.randint(0, 2)) / 10.0) - 0.05 # positive bias
@micropython.native # noqa: F821
def setup_portrait():
global width, height, liquid, blobs
width = CosmicUnicorn.HEIGHT
height = CosmicUnicorn.WIDTH
liquid = [[0.0 for y in range(height)] for x in range(width)]
blobs = [Blob() for i in range(blob_count)]
hue = 0.0
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return graphics.create_pen(int(v), int(t), int(p))
if i == 1:
return graphics.create_pen(int(q), int(v), int(p))
if i == 2:
return graphics.create_pen(int(p), int(v), int(t))
if i == 3:
return graphics.create_pen(int(p), int(q), int(v))
if i == 4:
return graphics.create_pen(int(t), int(p), int(v))
if i == 5:
return graphics.create_pen(int(v), int(p), int(q))
@micropython.native # noqa: F821
def update_liquid():
for y in range(height):
for x in range(width):
liquid[x][y] = 0.0
for blob in blobs:
r_sq = blob.r * blob.r
blob_y_range = range(max(math.floor(blob.y - blob.r), 0),
min(math.ceil(blob.y + blob.r), height))
blob_x_range = range(max(math.floor(blob.x - blob.r), 0),
min(math.ceil(blob.x + blob.r), width))
for y in blob_y_range:
for x in blob_x_range:
x_diff = x - blob.x
y_diff = y - blob.y
d_sq = x_diff * x_diff + y_diff * y_diff
if d_sq <= r_sq:
liquid[x][y] += 1.0 - (d_sq / r_sq)
@micropython.native # noqa: F821
def move_blobs():
for blob in blobs:
blob.x += blob.dx
blob.y += blob.dy
if blob.x < 0.0 or blob.x >= float(width):
blob.dx = 0.0 - blob.dx
if blob.y < 0.0 or blob.y >= float(height):
blob.dy = 0.0 - blob.dy
@micropython.native # noqa: F821
def draw_portrait():
global hue
hue += 0.001
dark = from_hsv(hue, 1.0, 0.3)
mid = from_hsv(hue, 1.0, 0.6)
bright = from_hsv(hue, 1.0, 1.0)
for y in range(height):
for x in range(width):
v = liquid[x][y]
# select a colour for this pixel based on how much
# "blobfluence" there is at this position in the liquid
if v >= 1.5:
graphics.set_pen(bright)
elif v >= 1.25:
graphics.set_pen(mid)
elif v >= 1.0:
graphics.set_pen(dark)
else:
graphics.set_pen(0)
graphics.pixel(y, x)
cu.update(graphics)
setup_portrait()
cu.set_brightness(0.5)
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
setup_portrait()
start = time.ticks_ms()
update_liquid()
move_blobs()
draw_portrait()
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)
print("total took: {} ms".format(time.ticks_ms() - start))

View File

@ -0,0 +1,131 @@
import time
import random
from cosmic import CosmicUnicorn, Channel
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
"""
A gloriously terrible melody maker.
Use Vol + and Vol - to move up/down (note pitch)
Use Lux - and D to move left/right (note position)
Press A to set a note.
Press B to delete a note.
Use Lux + to play/pause.
"""
NOTE_DURATION = 125
cu = CosmicUnicorn()
cu.set_brightness(0.5)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
boopety_beepety = cu.synth_channel(0)
boopety_beepety.configure(
waveforms=Channel.SQUARE | Channel.SINE,
attack=0.1,
decay=0.5,
sustain=0.0,
release=1.0,
volume=1.0
)
cu.play_synth()
black = graphics.create_pen(0, 0, 0)
note = graphics.create_pen(255, 255, 255)
cursor_bg = graphics.create_pen(64, 0, 0)
cursor = graphics.create_pen(255, 0, 0)
playhead = graphics.create_pen(0, 128, 0)
cursor_position = [0, 0]
playhead_position = 0
width, height = graphics.get_bounds()
notes = [random.randint(0, height) for _ in range(width)]
last_note_advance = time.ticks_ms()
last_action = time.ticks_ms()
playing = True
def debounce(button, duration=100):
global last_action
if cu.is_pressed(button) and time.ticks_ms() - last_action > duration:
last_action = time.ticks_ms()
return True
return False
def note_to_frequency(note_number):
return int((2 ** ((note_number - 69.0) / 12)) * 440)
while True:
if debounce(CosmicUnicorn.SWITCH_D):
cursor_position[0] -= 1
cursor_position[0] %= width
if debounce(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cursor_position[0] += 1
cursor_position[0] %= width
if debounce(CosmicUnicorn.SWITCH_VOLUME_DOWN):
cursor_position[1] += 1
cursor_position[1] %= height
if debounce(CosmicUnicorn.SWITCH_VOLUME_UP):
cursor_position[1] -= 1
cursor_position[1] %= height
if debounce(CosmicUnicorn.SWITCH_BRIGHTNESS_UP, 500):
playing = not playing
if not playing:
boopety_beepety.trigger_release()
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
notes[cursor_position[0]] = cursor_position[1]
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
notes[cursor_position[0]] = None
if time.ticks_ms() - last_note_advance > NOTE_DURATION:
current_note = None
if playing:
playhead_position += 1
playhead_position %= width
current_note = notes[playhead_position]
if current_note is not None:
current_note = height - current_note # Bottom = Low, Top = High
current_note += 36 # Shift up the scale a couple of octaves
current_freq = note_to_frequency(current_note)
boopety_beepety.frequency(current_freq)
boopety_beepety.trigger_attack()
last_note_advance = time.ticks_ms()
graphics.set_pen(black)
graphics.clear()
graphics.set_pen(playhead)
graphics.line(playhead_position, 0, playhead_position, height)
graphics.set_pen(cursor_bg)
graphics.line(cursor_position[0], 0, cursor_position[0], height)
graphics.set_pen(note)
for x in range(width):
y = notes[x]
if y is not None:
graphics.pixel(x, y)
graphics.set_pen(cursor)
graphics.pixel(*cursor_position)
cu.update(graphics)

View File

@ -0,0 +1,158 @@
import time
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
A collection of copies of classic terminal styles including
C64, MS-DOS, Spectrum, and more. Images and text are drawn
pixel by pixel from a pattern of Os and Xs.
You can adjust the brightness with LUX + and -.
'''
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
prompt_x = 0
prompt_y = 4
c64 = [
" ",
" ",
" OOOOO OOOOOO OO OOOO ",
" OO OO OO OOOO OO OO ",
" OO OO OO OO OO OO OO ",
" OOOOO OOOO OOOOOO OO OO ",
" OOOO OO OO OO OO OO ",
" OO OO OO OO OO OO OO ",
" OO OO OOOOOO OO OO OOOO ",
" ",
" ",
" ",
" ",
" OO OO XXXXXXX ",
" OO OO XXXXXXX ",
" OO OO XXXXXXX ",
" OOOO XXXXXXX ",
" OO XXXXXXX ",
" OO OO XXXXXXX ",
" OO OO XXXXXXX ",
" XXXXXXX ",
" "
]
FOREGROUND_C64 = (230, 210, 250)
BACKGROUND_C64 = (20, 20, 120)
spectrum = [
" ",
" ",
" O OOOO OOOO OOOOO ",
" O O O O O O O ",
" O O O O O O O ",
" O O O OOOOOO O O ",
" O O O O O O O ",
" OOOOOO OOOO O O OOOOO ",
" ",
" ",
" ",
" ",
" ",
" O O O O XXXXXXXX ",
" O O O O X XXXXXX ",
" X XXXXXX ",
" X XXXXXX ",
" X XXXXXX ",
" X XXXXXX ",
" X X ",
" XXXXXXXX ",
" "
]
FOREGROUND_SPECTRUM = (0, 0, 0)
BACKGROUND_SPECTRUM = (180, 150, 150)
bbc_micro = [
" ",
" ",
" OOOOO OO OOOO OOO ",
" O O O O O O O ",
" O O O O O O ",
" OOOOO O O OOOO O ",
" O O OOOOOO O O ",
" O O O O O O O ",
" OOOOO O O OOOO OOO ",
" ",
" ",
" ",
" ",
" OOOO O ",
" O O O ",
" O O ",
" O O ",
" O O ",
" O O O ",
" OOOO O ",
" XXXXXXX ",
" "
]
FOREGROUND_BBC_MICRO = (255, 255, 255)
BACKGROUND_BBC_MICRO = (0, 0, 0)
PROMPT_C64 = 0
PROMPT_SPECTRUM = 1
PROMPT_BBC_MICRO = 2
prompt = 0
@micropython.native # noqa: F821
def draw(image, fg, bg, time_ms):
fg_pen = graphics.create_pen(fg[0], fg[1], fg[2])
bg_pen = graphics.create_pen(bg[0], bg[1], bg[2])
graphics.set_pen(bg_pen)
graphics.clear()
for y in range(len(image)):
row = image[y]
for x in range(len(row)):
pixel = row[x]
# draw the prompt text
if pixel == 'O':
graphics.set_pen(fg_pen)
# draw the caret blinking
elif pixel == 'X' and (time_ms // 300) % 2:
graphics.set_pen(fg_pen)
else:
graphics.set_pen(bg_pen)
graphics.pixel(x + prompt_x, y + prompt_y)
cu.update(graphics)
cu.set_brightness(0.5)
while True:
time_ms = time.ticks_ms()
prompt = (time_ms // 3000) % 3
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
start = time.ticks_ms()
if prompt == PROMPT_C64:
draw(c64, FOREGROUND_C64, BACKGROUND_C64, time_ms)
elif prompt == PROMPT_SPECTRUM:
draw(spectrum, FOREGROUND_SPECTRUM, BACKGROUND_SPECTRUM, time_ms)
elif prompt == PROMPT_BBC_MICRO:
draw(bbc_micro, FOREGROUND_BBC_MICRO, BACKGROUND_BBC_MICRO, time_ms)
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)
print("total took: {} ms".format(time.ticks_ms() - start))

View File

@ -0,0 +1,115 @@
import gc
import time
import random
from cosmic import CosmicUnicorn, Channel
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
from ulab import numpy
"""
A random, computer effect.
Experiment with the damping, number of spawns and intensity to change the effect.
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
DAMPING_FACTOR = 0.95
NUMBER_OF_LIGHTS = 10
INTENSITY = 20
volume = 0.5
cu = CosmicUnicorn()
cu.set_brightness(0.5)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
boopety_beepety = cu.synth_channel(0)
boopety_beepety.configure(
waveforms=Channel.SQUARE | Channel.SINE,
attack=0.1,
decay=0.1,
sustain=0.0,
release=0.5,
volume=volume
)
cu.play_synth()
# Fill palette with a yellow
r, g, b = (230, 150, 0)
PALETTE_ENTRIES = 255
for x in range(PALETTE_ENTRIES):
_ = graphics.create_pen(r * x // PALETTE_ENTRIES, g * x // PALETTE_ENTRIES, b)
def update():
computer[:] *= DAMPING_FACTOR
# Spawn random drops
for _ in range(NUMBER_OF_LIGHTS):
x = random.randint(0, width - 1)
y = random.randint(0, height - 1)
computer[y][x] = random.randint(0, INTENSITY)
def draw():
# Copy the effect to the framebuffer
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(computer, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes()
cu.update(graphics)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
computer = numpy.zeros((height, width))
t_count = 0
t_total = 0
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN):
volume -= 0.1
volume = max(0.0, volume)
boopety_beepety.volume(volume)
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP):
volume += 0.1
volume = min(1.0, volume)
boopety_beepety.volume(volume)
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
# Play random notes between 100 and 880Hz for a computery effect
boopety_beepety.frequency(random.randint(100, 880))
boopety_beepety.trigger_attack()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,126 @@
import time
import gc
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
from ulab import numpy
"""
Classic fire effect.
Play with the number of spawns, heat, damping factor and colour palette to tweak it.
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
cu = CosmicUnicorn()
cu.set_brightness(0.5)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
# Number of random fire spawns
FIRE_SPAWNS = 5
# Fire damping
DAMPING_FACTOR = 0.98
# TURN UP THE HEEEAAT
HEAT = 3.0
# Create the fire palette
"""
# Raging Gas Inferno
graphics.create_pen(0, 0, 0)
graphics.create_pen(0, 0, 0)
graphics.create_pen(20, 20, 20)
graphics.create_pen(50, 10, 0)
graphics.create_pen(180, 30, 0)
graphics.create_pen(220, 160, 0)
graphics.create_pen(255, 255, 180)
graphics.create_pen(255, 255, 220)
graphics.create_pen(90, 90, 255)
graphics.create_pen(255, 0, 255)
"""
# Original Colours
graphics.create_pen(0, 0, 0)
graphics.create_pen(20, 20, 20)
graphics.create_pen(180, 30, 0)
graphics.create_pen(220, 160, 0)
graphics.create_pen(255, 255, 180)
PALETTE_SIZE = 5 # Should match the number of colours defined above
def update():
# Clear the bottom two rows (off screen)
heat[height - 1][:] = 0.0
heat[height - 2][:] = 0.0
# Add random fire spawns
for c in range(FIRE_SPAWNS):
x = random.randint(0, width - 4) + 2
heat[height - 1][x - 1:x + 1] = HEAT / 2.0
heat[height - 2][x - 1:x + 1] = HEAT
# Propagate the fire upwards
a = numpy.roll(heat, -1, axis=0) # y + 1, x
b = numpy.roll(heat, -2, axis=0) # y + 2, x
c = numpy.roll(heat, -1, axis=0) # y + 1
d = numpy.roll(c, 1, axis=1) # y + 1, x + 1
e = numpy.roll(c, -1, axis=1) # y + 1, x - 1
# Average over 5 adjacent pixels and apply damping
heat[:] += a + b + d + e
heat[:] *= DAMPING_FACTOR / 5.0
def draw():
# Copy the fire effect to the framebuffer
# Clips the fire to 0.0 to 1.0
# Multiplies it by the number of palette entries (-1) to turn it into a palette index
# Converts to uint8_t datatype to match the framebuffer
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:32, 0:32], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes()
cu.update(graphics)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT + 4
heat = numpy.zeros((height, width))
t_count = 0
t_total = 0
while True:
gc.collect()
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,118 @@
import gc
import time
import math
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
from ulab import numpy
"""
A lava lamp effect, created by blurred, moving particles.
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
cu.set_brightness(0.5)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
lava = numpy.zeros((height, width))
class Blob():
def __init__(self):
self.x = float(random.randint(0, width - 1))
self.y = float(random.randint(0, height - 1))
self.r = (float(random.randint(0, 40)) / 10.0) + 5.0
self.dx = (float(random.randint(0, 2)) / 20.0) - 0.05
self.dy = (float(random.randint(0, 2)) / 20.0) - 0.025 # positive bias
def move(self):
self.x += self.dx
self.y += self.dy
if self.x < 0.0 or self.x >= float(width):
self.x = max(0.0, self.x)
self.x = min(float(width - 1), self.x)
self.dx = -self.dx
if self.y < 0.0 or self.y >= float(height):
self.y = max(0.0, self.y)
self.y = min(float(height - 1), self.y)
self.dy = -self.dy
blobs = [Blob() for _ in range(10)]
# Fill palette with a steep falloff from bright red to dark blue
PAL_COLS = 9
for x in range(PAL_COLS):
graphics.create_pen_hsv(0.5 + math.log(x + 1, PAL_COLS + 1) / 2.0, 1.0, math.log(x + 1, PAL_COLS + 1))
def update():
# Update the blobs and draw them into the effect
for blob in blobs:
blob.move()
lava[int(blob.y)][int(blob.x)] = blob.r
# Propogate the blobs outwards
a = numpy.roll(lava, 1, axis=0)
b = numpy.roll(lava, -1, axis=0)
d = numpy.roll(lava, 1, axis=1)
e = numpy.roll(lava, -1, axis=1)
# Average over 5 adjacent pixels and apply damping
lava[:] += a + b + d + e
lava[:] *= 0.97 / 5.0
def draw():
# Copy the lava effect to the framebuffer
# Clips to 0.0 - 1.0
# Multiplies by palette entries (-1) to turn it into a palette index
# Converts to uint8_t datatype to match the framebuffer
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(lava, 0.0, 1.0) * (PAL_COLS - 1), dtype=numpy.uint8).tobytes()
cu.update(graphics)
t_count = 0
t_total = 0
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,145 @@
import gc
import time
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
from ulab import numpy
"""
A randomly-seeded game-of-life cellular automata effect.
Experiment with the values below to change the effect.
Press "A" to manually re-seed.
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
INITIAL_LIFE = 500 # Number of live cells to seed
GENERATION_TIME_MS = 50 # MS between generations
MINIMUM_LIFE = 10 # Auto reseed when only this many alive cells remain
SMOOTHED = True # Enable for a more organic if somewhat unsettling feel
DECAY = 0.90 # Rate at which smoothing effect decays, higher number = more persistent, 1.0 = no decay
TENACITY = 32 # Rate at which smoothing effect increases
cu = CosmicUnicorn()
cu.set_brightness(0.5)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
for c in range(256):
graphics.create_pen(c // 2, 0, c)
def update():
global last_gen
if SMOOTHED:
duration[:] += life * TENACITY
duration[:] *= DECAY
if time.ticks_ms() - last_gen < GENERATION_TIME_MS:
return
last_gen = time.ticks_ms()
if numpy.sum(life) < MINIMUM_LIFE:
seed_life()
return
# Rollin' rollin' rollin.
_N = numpy.roll(life, -1, axis=0)
_NW = numpy.roll(_N, -1, axis=1)
_NE = numpy.roll(_N, 1, axis=1)
_S = numpy.roll(life, 1, axis=0)
_SW = numpy.roll(_S, -1, axis=1)
_SE = numpy.roll(_S, 1, axis=1)
_W = numpy.roll(life, -1, axis=1)
_E = numpy.roll(life, 1, axis=1)
# Compute the total neighbours for each cell
neighbours[:] = _N + _NW + _NE + _S + _SW + _SE + _W + _E
next_generation[:] = life[:]
# Any cells with exactly three neighbours should always stay alive
next_generation[:] += neighbours[:] == 3
# Any alive cells with less than two neighbours should die
next_generation[:] -= (neighbours[:] < 2) * life
# Any alive cells with more than three neighbours should die
next_generation[:] -= (neighbours[:] > 3) * life
life[:] = numpy.clip(next_generation, 0, 1)
def draw():
# Copy the effect to the framebuffer
if SMOOTHED:
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(duration, 0, 255), dtype=numpy.uint8).tobytes()
else:
memoryview(graphics)[:] = numpy.ndarray(life * 255, dtype=numpy.uint8).tobytes()
cu.update(graphics)
def seed_life():
for _ in range(INITIAL_LIFE):
x = random.randint(0, width - 1)
y = random.randint(0, height - 1)
life[y][x] = int(True) # Avoid: TypeError: 'bool' object isn't iterable
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
life = numpy.zeros((height, width), dtype=numpy.bool)
next_generation = numpy.zeros((height, width), dtype=numpy.bool)
neighbours = numpy.zeros((height, width), dtype=numpy.uint8)
duration = numpy.zeros((height, width))
last_gen = time.ticks_ms()
t_count = 0
t_total = 0
seed_life()
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
life[:] = int(False)
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
SMOOTHED = not SMOOTHED
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,95 @@
import gc
import time
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_RGB888
from ulab import numpy
"""
This example demonstrates how to work with full RGB888 colour in ulab/numpy.
Each colour channel is given its own array, and these are combined before
copying them into the PicoGraphics buffer.
At great cost to performance (about half the speed) this example works in
floating point 0.0 to 1.0 and converts the result to 8bits per channel.
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
cu = CosmicUnicorn()
cu.set_brightness(0.5)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_RGB888)
def update():
# Do something basic with the colour channels
# to prove this actually works.
red[:] = numpy.roll(red, 1, axis=1)
green[:] *= 0.999 # Slowly desaturate green
blue[:] *= 1.001 # Slowly saturate blue
def draw():
# Copy the red, green, blue channels into
# their respective places in the RGB_ array
rgb[2::4] = red.flatten()
rgb[1::4] = green.flatten()
rgb[0::4] = blue.flatten()
# Convert the results to 8bit RGB and copy to the framebuffer
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(rgb, 0, 1) * 255, dtype=numpy.uint8).tobytes()
# Copy the framebuffer to Cosmic
cu.update(graphics)
# Whew!
width, height = graphics.get_bounds()
# Individual channels
red = numpy.zeros((height, width))
green = numpy.zeros((height, width))
blue = numpy.zeros((height, width))
# Reserved for combined channels
rgb = numpy.zeros((width * height * 4),)
# Stick some gradients in the channels so we have something to look at
red[::] = numpy.linspace(0, 1, width)
# There has to be a better way!?
for x in range(width):
green[::, x] = numpy.linspace(0, 1, width)
blue[::, x] = numpy.linspace(1, 0, width,)
t_count = 0
t_total = 0
while True:
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,89 @@
import gc
import time
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
from ulab import numpy
"""
HELLO NEO.
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
cu = CosmicUnicorn()
cu.set_brightness(1.0)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
# Fill half the palette with GREEEN
for g in range(128):
_ = graphics.create_pen(0, g, 0)
# And half with bright green for white sparkles
for g in range(128):
_ = graphics.create_pen(128, 128 + g, 128)
def update():
trippy[:] *= 0.65
for _ in range(2):
x = random.randint(0, width - 1)
y = random.randint(0, height // 2)
trippy[y][x] = random.randint(128, 255) / 255.0
# Propagate downwards
old = numpy.ndarray(trippy) * 0.5
trippy[:] = numpy.roll(trippy, 1, axis=0)
trippy[:] += old
def draw():
# Copy the effect to the framebuffer
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * 254, dtype=numpy.uint8).tobytes()
cu.update(graphics)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
trippy = numpy.zeros((height, width))
t_count = 0
t_total = 0
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,131 @@
import time
import gc
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
from ulab import numpy
"""
THIS IS FINE!
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
cu = CosmicUnicorn()
cu.set_brightness(0.5)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
# Number of random fire spawns
FIRE_SPAWNS = 5
# Fire damping
DAMPING_FACTOR = 0.98
# TURN UP THE HEEEAAT
HEAT = 3.0
# Create the fire palette
"""
# Raging Gas Inferno
graphics.create_pen(0, 0, 0)
graphics.create_pen(0, 0, 0)
graphics.create_pen(20, 20, 20)
graphics.create_pen(50, 10, 0)
graphics.create_pen(180, 30, 0)
graphics.create_pen(220, 160, 0)
graphics.create_pen(255, 255, 180)
graphics.create_pen(255, 255, 220)
graphics.create_pen(90, 90, 255)
graphics.create_pen(255, 0, 255)
"""
# Original Colours
graphics.create_pen(0, 0, 0)
graphics.create_pen(20, 20, 20)
graphics.create_pen(180, 30, 0)
graphics.create_pen(220, 160, 0)
graphics.create_pen(255, 255, 180)
PALETTE_SIZE = 5 # Should match the number of colours defined above
def update():
# Clear the bottom two rows (off screen)
heat[height - 1][:] = 0.0
heat[height - 2][:] = 0.0
# Add random fire spawns
for c in range(FIRE_SPAWNS):
x = random.randint(0, width - 4) + 2
heat[height - 1][x - 1:x + 1] = HEAT / 2.0
heat[height - 2][x - 1:x + 1] = HEAT
# Propagate the fire upwards
a = numpy.roll(heat, -1, axis=0) # y + 1, x
b = numpy.roll(heat, -2, axis=0) # y + 2, x
c = numpy.roll(heat, -1, axis=0) # y + 1
d = numpy.roll(c, 1, axis=1) # y + 1, x + 1
e = numpy.roll(c, -1, axis=1) # y + 1, x - 1
# Average over 5 adjacent pixels and apply damping
heat[:] += a + b + d + e
heat[:] *= DAMPING_FACTOR / 5.0
def draw():
# Copy the fire effect to the framebuffer
# Clips the fire to 0.0 to 1.0
# Multiplies it by the number of palette entries (-1) to turn it into a palette index
# Converts to uint8_t datatype to match the framebuffer
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:32, 0:32], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes()
# Draw text over the top
graphics.set_pen(0)
graphics.text("This", 6, 4, 1, 1)
graphics.text("is", 11, 12, 1, 1)
graphics.text("fine", 6, 20, 1, 1)
cu.update(graphics)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT + 4
heat = numpy.zeros((height, width))
t_count = 0
t_total = 0
while True:
gc.collect()
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,97 @@
import gc
import time
import random
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8
from ulab import numpy
"""
A random, trippy effect.
Experiment with the damping, number of spawns, intensity and offset to change the effect.
"""
# MAXIMUM OVERKILL
# machine.freq(250_000_000)
cu = CosmicUnicorn()
cu.set_brightness(0.5)
graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8)
DAMPING_FACTOR = 0.95
NUMBER_OF_DROPS = 5
INTENSITY = 10
OFFSET = 0.0 # Try 0.5
# Fill palette with a rainbow sweep
PALETTE_ENTRIES = 255
for x in range(PALETTE_ENTRIES):
_ = graphics.create_pen_hsv(float(x) / PALETTE_ENTRIES + OFFSET, 1.0, 1.0)
def update():
trippy[:] *= DAMPING_FACTOR
# Spawn random drops
for _ in range(NUMBER_OF_DROPS):
x = random.randint(0, width - 1)
y = random.randint(0, height - 1)
trippy[y][x] = random.randint(0, INTENSITY)
a = numpy.roll(trippy, 1, axis=0)
b = numpy.roll(trippy, -1, axis=0)
d = numpy.roll(trippy, 1, axis=1)
e = numpy.roll(trippy, -1, axis=1)
# Average over 5 adjacent pixels and apply damping
trippy[:] += a + b + d + e
trippy[:] /= 5.0
def draw():
# Copy the effect to the framebuffer
memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes()
cu.update(graphics)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
trippy = numpy.zeros((height, width))
t_count = 0
t_total = 0
while True:
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
tstart = time.ticks_ms()
gc.collect()
update()
draw()
tfinish = time.ticks_ms()
total = tfinish - tstart
t_total += total
t_count += 1
if t_count == 60:
per_frame_avg = t_total / t_count
print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS")
t_count = 0
t_total = 0
# pause for a moment (important or the USB serial device will fail)
# try to pace at 60fps or 30fps
if total > 1000 / 30:
time.sleep(0.0001)
elif total > 1000 / 60:
t = 1000 / 30 - total
time.sleep(t / 1000)
else:
t = 1000 / 60 - total
time.sleep(t / 1000)

View File

@ -0,0 +1,115 @@
import time
import math
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
Some good old fashioned rainbows!
You can adjust the cycling speed with A and B,
stripe width with C and D, hue with VOL + and -,
and the brightness with LUX + and -.
The sleep button stops the animation (can be started again with A or B).
'''
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return int(v), int(t), int(p)
if i == 1:
return int(q), int(v), int(p)
if i == 2:
return int(p), int(v), int(t)
if i == 3:
return int(p), int(q), int(v)
if i == 4:
return int(t), int(p), int(v)
if i == 5:
return int(v), int(p), int(q)
@micropython.native # noqa: F821
def draw():
global hue_offset, phase
phase_percent = phase / 15
for x in range(width):
colour = hue_map[int((x + (hue_offset * width)) % width)]
for y in range(height):
v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5)
graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v)))
graphics.pixel(x, y)
cu.update(graphics)
hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)]
hue_offset = 0.0
animate = True
stripe_width = 3.0
speed = 1.0
cu.set_brightness(0.5)
phase = 0
while True:
if animate:
phase += speed
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP):
hue_offset += 0.01
hue_offset = 1.0 if hue_offset > 1.0 else hue_offset
if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN):
hue_offset -= 0.01
hue_offset = 0.0 if hue_offset < 0.0 else hue_offset
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP):
animate = False
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
speed += 0.05
speed = 10.0 if speed > 10.0 else speed
animate = True
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
speed -= 0.05
speed = 0.0 if speed < 0.0 else speed
animate = True
if cu.is_pressed(CosmicUnicorn.SWITCH_C):
stripe_width += 0.05
stripe_width = 10.0 if stripe_width > 10.0 else stripe_width
if cu.is_pressed(CosmicUnicorn.SWITCH_D):
stripe_width -= 0.05
stripe_width = 1.0 if stripe_width < 1.0 else stripe_width
start = time.ticks_ms()
draw()
print("total took: {} ms".format(time.ticks_ms() - start))

View File

@ -0,0 +1,96 @@
import time
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
'''
Display scrolling wisdom, quotes or greetz.
You can adjust the brightness with LUX + and -.
'''
# constants for controlling scrolling text
PADDING = 5
MESSAGE_COLOUR = (255, 255, 255)
OUTLINE_COLOUR = (0, 0, 0)
BACKGROUND_COLOUR = (10, 0, 96)
MESSAGE = "\"Space is big. Really big. You just won't believe how vastly hugely mind-bogglingly big it is. I mean, you may think it's a long way down the road to the chemist, but that's just peanuts to space.\" - Douglas Adams"
HOLD_TIME = 2.0
STEP_TIME = 0.075
# create cosmic object and graphics surface for drawing
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
width = CosmicUnicorn.WIDTH
height = CosmicUnicorn.HEIGHT
# function for drawing outlined text
def outline_text(text, x, y):
graphics.set_pen(graphics.create_pen(int(OUTLINE_COLOUR[0]), int(OUTLINE_COLOUR[1]), int(OUTLINE_COLOUR[2])))
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(int(MESSAGE_COLOUR[0]), int(MESSAGE_COLOUR[1]), int(MESSAGE_COLOUR[2])))
graphics.text(text, x, y, -1, 1)
cu.set_brightness(0.5)
# state constants
STATE_PRE_SCROLL = 0
STATE_SCROLLING = 1
STATE_POST_SCROLL = 2
shift = 0
state = STATE_PRE_SCROLL
# set the font
graphics.set_font("bitmap8")
# calculate the message width so scrolling can happen
msg_width = graphics.measure_text(MESSAGE, 1)
last_time = time.ticks_ms()
while True:
time_ms = time.ticks_ms()
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
if state == STATE_PRE_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
if msg_width + PADDING * 2 >= width:
state = STATE_SCROLLING
last_time = time_ms
if state == STATE_SCROLLING and time_ms - last_time > STEP_TIME * 1000:
shift += 1
if shift >= (msg_width + PADDING * 2) - width - 1:
state = STATE_POST_SCROLL
last_time = time_ms
if state == STATE_POST_SCROLL and time_ms - last_time > HOLD_TIME * 1000:
state = STATE_PRE_SCROLL
shift = 0
last_time = time_ms
graphics.set_pen(graphics.create_pen(int(BACKGROUND_COLOUR[0]), int(BACKGROUND_COLOUR[1]), int(BACKGROUND_COLOUR[2])))
graphics.clear()
outline_text(MESSAGE, x=PADDING - shift, y=2)
# update the display
cu.update(graphics)
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)

View File

@ -0,0 +1,128 @@
import time
import network
import ntptime
import machine
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
cu = CosmicUnicorn()
graphics = PicoGraphics(DISPLAY)
# Default Brightness
cu.set_brightness(0.4)
# You will need to create or update the file secrets.py with your network credentials using Thonny
# in order for the example to update using the NTP.
# secrets.py should contain:
# WIFI_SSID = ""
# WIFI_PASSWORD = ""
try:
from secrets import WIFI_SSID, WIFI_PASSWORD
except ImportError:
print("Create secrets.py with your WiFi credentials")
WIDTH = CosmicUnicorn.WIDTH
HEIGHT = CosmicUnicorn.HEIGHT
rtc = machine.RTC()
DAYS = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat", "Sun"]
# Enable the Wireless
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
def network_connect(SSID, PSK):
# Number of attempts to make before timeout
max_wait = 5
# Sets the Wireless LED pulsing and attempts to connect to your local network.
print("connecting...")
wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs
wlan.connect(SSID, PSK)
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(1)
# Handle connection error. Switches the Warn LED on.
if wlan.status() != 3:
print("Unable to connect. Attempting connection again")
# Function to sync the Pico RTC using NTP
def sync_time():
try:
network_connect(WIFI_SSID, WIFI_PASSWORD)
except NameError:
print("Create secrets.py with your WiFi credentials")
if wlan.status() < 0 or wlan.status() >= 3:
try:
ntptime.settime()
except OSError:
print("Unable to sync with NTP server. Check network and try again.")
def init():
sync_time()
def draw():
# Pens
RED = graphics.create_pen(120, 0, 0)
WHITE = graphics.create_pen(255, 255, 255)
current_t = rtc.datetime()
# Set the pen to Red and clear the screen.
graphics.set_pen(WHITE)
graphics.clear()
# Measures the length of the text to help us with centring later.
day_length = graphics.measure_text(DAYS[current_t[3]], 1)
date_length = graphics.measure_text(str(current_t[2]), 3)
graphics.set_font("bitmap6")
graphics.set_pen(RED)
graphics.rectangle(0, 0, WIDTH, 7)
graphics.set_pen(WHITE)
graphics.text(DAYS[current_t[3]], (WIDTH // 2) - (day_length // 2) - 1, 0, 32, 1)
graphics.set_pen(RED)
graphics.set_font("bitmap8")
graphics.text(str(current_t[2]), (WIDTH // 2) - (date_length // 2) + 1, 9, 32, 3)
graphics.set_pen(graphics.create_pen(0, 0, 0))
cu.update(graphics)
init()
while 1:
# Adjust Brightness +/-
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
cu.adjust_brightness(+0.01)
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
cu.adjust_brightness(-0.01)
# Connect to the network and sync with the NTP server
if cu.is_pressed(CosmicUnicorn.SWITCH_C):
sync_time()
draw()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -0,0 +1,153 @@
import time
from cosmic import CosmicUnicorn
from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY
import WIFI_CONFIG
from network_manager import NetworkManager
import uasyncio as asyncio
import urequests
import jpegdec
# Set your latitude/longitude here (find yours by right clicking in Google Maps!)
LAT = 53.38609085276884
LNG = -1.4239983439328177
TIMEZONE = "auto" # determines time zone from lat/long
URL = "http://api.open-meteo.com/v1/forecast?latitude=" + str(LAT) + "&longitude=" + str(LNG) + "&current_weather=true&timezone=" + TIMEZONE
WEATHER_TEXT = ''
user_icon = None
def get_data():
global WEATHER_TEXT, temperature, weathercode
print(f"Requesting URL: {URL}")
r = urequests.get(URL)
# open the json data
j = r.json()
print("Data obtained!")
print(j)
# parse relevant data from JSON
current = j["current_weather"]
temperature = current["temperature"]
windspeed = current["windspeed"]
winddirection = calculate_bearing(current["winddirection"])
weathercode = current["weathercode"]
date, now = current["time"].split("T")
WEATHER_TEXT = f"Temp: {temperature}°C Wind Speed: {windspeed}kmph Wind Direction: {winddirection} As of: {date}, {now}"
print(WEATHER_TEXT)
r.close()
def calculate_bearing(d):
# calculates a compass direction from the wind direction in degrees
dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']
ix = round(d / (360. / len(dirs)))
return dirs[ix % len(dirs)]
def status_handler(mode, status, ip):
global MESSAGE
print("Network: {}".format(WIFI_CONFIG.SSID))
# create galactic object and graphics surface for drawing
cu = CosmicUnicorn()
display = PicoGraphics(DISPLAY)
WIDTH = CosmicUnicorn.WIDTH
HEIGHT = CosmicUnicorn.HEIGHT
jpeg = jpegdec.JPEG(display)
TEXT_COLOUR = display.create_pen(200, 0, 200)
BLACK = display.create_pen(0, 0, 0)
WHITE = display.create_pen(255, 255, 255)
def run():
# Setup wifi
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
# Connect to Wifi network
asyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
while (not network_manager.isconnected()):
time.sleep(0.1)
cu.set_brightness(1)
run() # Sets up Wifi connection
def outline_text(text, x, y):
display.set_pen(BLACK)
display.text(text, x - 1, y - 1, -1, 1)
display.text(text, x, y - 1, -1, 1)
display.text(text, x + 1, y - 1, -1, 1)
display.text(text, x - 1, y, -1, 1)
display.text(text, x + 1, y, -1, 1)
display.text(text, x - 1, y + 1, -1, 1)
display.text(text, x, y + 1, -1, 1)
display.text(text, x + 1, y + 1, -1, 1)
display.set_pen(WHITE)
display.text(text, x, y, -1, 1)
def draw_page(cycle):
global user_icon
text_cycle = cycle % 1000
cycle = cycle % 4
# Clear the display
display.set_pen(15)
display.clear()
# Draw the page header
display.set_font("bitmap6")
if temperature is not None:
# Choose an appropriate icon based on the weather code
# Weather codes from https://open-meteo.com/en/docs
if user_icon is not None:
icons = ["icons/snow{0}.jpg".format(cycle + 1), "icons/rain{0}.jpg".format(cycle + 1), "icons/cloud{0}.jpg".format(cycle + 1), "icons/sun{0}.jpg".format(cycle + 1), "icons/storm{0}.jpg".format(cycle + 1)]
jpeg.open_file(icons[user_icon])
else:
if weathercode in [71, 73, 75, 77, 85, 86]: # codes for snow
jpeg.open_file("icons/snow{0}.jpg".format(cycle + 1))
elif weathercode in [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82]: # codes for rain
jpeg.open_file("icons/rain{0}.jpg".format(cycle + 1))
elif weathercode in [1, 2, 3, 45, 48]: # codes for cloud
jpeg.open_file("icons/cloud{0}.jpg".format(cycle + 1))
elif weathercode in [0]: # codes for sun
jpeg.open_file("icons/sun{0}.jpg".format(cycle + 1))
elif weathercode in [95, 96, 99]: # codes for storm
jpeg.open_file("icons/storm{0}.jpg".format(cycle + 1))
jpeg.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
display.set_pen(TEXT_COLOUR)
outline_text(WEATHER_TEXT, 32 - text_cycle, 0)
else:
display.set_pen(0)
display.set_pen(15)
display.text("Unable to display weather! Check your network settings in WIFI_CONFIG.py", 5, 65, WIDTH, 1)
cu.update(display)
while 1:
get_data()
for count in range(500):
if cu.is_pressed(CosmicUnicorn.SWITCH_A):
user_icon = 0
if cu.is_pressed(CosmicUnicorn.SWITCH_B):
user_icon = 1
if cu.is_pressed(CosmicUnicorn.SWITCH_C):
user_icon = 2
if cu.is_pressed(CosmicUnicorn.SWITCH_D):
user_icon = 3
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP):
user_icon = 4
if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN):
user_icon = None
draw_page(count)
time.sleep(0.2)

View File

@ -0,0 +1,155 @@
# Galactic Unicorn MicroPython Examples <!-- omit in toc -->
- [About Galactic Unicorn](#about-galactic-unicorn)
- [Galactic Unicorn and PicoGraphics](#galactic-unicorn-and-picographics)
- [Examples](#examples)
- [Clock](#clock)
- [CO2](#co2)
- [Eighties Super Computer](#eighties-super-computer)
- [Feature Test](#feature-test)
- [Feature Test With Audio](#feature-test-with-audio)
- [Fire Effect](#fire-effect)
- [Lava Lamp](#lava-lamp)
- [Nostalgia Prompt](#nostalgia-prompt)
- [Rainbow](#rainbow)
- [Scrolling Text](#scrolling-text)
- [Wireless Examples](#wireless-examples)
- [Cheerlights](#cheerlights)
- [Cheerlights History](#cheerlights-history)
- [Galactic Paint](#galactic-paint)
- [Other Examples](#other-examples)
- [Launch (Demo Reel)](#launch-demo-reel)
- [Other Resources](#other-resources)
## About Galactic Unicorn
Galactic Unicorn offers 53x11 bright RGB LEDs driven by Pico W's PIO in addition to a 1W amplifier + speaker, a collection of system and user buttons, and two Qw/ST connectors for adding external sensors and devices. Woha!
- :link: [Galactic Unicorn store page](https://shop.pimoroni.com/products/galactic-unicorn)
Galactic Unicorn ships with MicroPython firmware pre-loaded, but you can download the most recent version at the link below (you'll want the `galactic-unicorn` image).
- [MicroPython releases](https://github.com/pimoroni/pimoroni-pico/releases)
- [Installing MicroPython](../../../setting-up-micropython.md)
## Galactic Unicorn and PicoGraphics
The easiest way to start displaying cool stuff on Galactic Unicorn is using our Galactic Unicorn module (which contains a bunch of helpful functions for interacting with the buttons, adjusting brightness and suchlike) and our PicoGraphics library, which is chock full of useful functions for drawing on the LED matrix.
- [Galactic Unicorn function reference](../../modules/galactic_unicorn/README.md)
- [PicoGraphics function reference](../../modules/picographics/README.md)
## Examples
### Clock
[clock.py](clock.py)
Clock example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the time by pressing A.
### CO2
[co2.py](co2.py)
Add a [SCD41 sensor breakout](https://shop.pimoroni.com/products/scd41-co2-sensor-breakout) to make an unsubtle carbon dioxide detector. Press A to reset the high/low values.
### Eighties Super Computer
[eighties_super_computer.py](eighties_super_computer.py)
Random LEDs blink on and off mimicing the look of a movie super computer doing its work in the eighties. You can adjust the brightness with LUX + and -.
### Feature Test
[feature_test.py](feature_test.py)
Displays some text, gradients and colours and demonstrates button use. You can adjust the brightness with LUX + and -.
### Feature Test With Audio
[feature_test_with_audio.py](feature_test_with_audio.py)
Displays some text, gradients and colours and demonstrates button use. Also demonstrates some of the audio / synth features.
- Button A plays a synth tune
- Button B plays a solo channel of the synth tune
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
- Sleep button stops the sounds
### Fire Effect
[fire_effect.py](fire_effect.py)
A pretty, procedural fire effect. Switch between landscape fire and vertical fire using the A and B buttons! You can adjust the brightness with LUX + and -.
### Lava Lamp
[lava_lamp.py](lava_lamp.py)
A 70s-tastic, procedural rainbow lava lamp. You can adjust the brightness with LUX + and -.
### Nostalgia Prompt
[nostalgia_prompt.py](nostalgia_prompt.py)
A collection of copies of classic terminal styles including C64, MS-DOS, Spectrum, and more. Images and text are drawn pixel by pixel from a pattern of Os and Xs. You can adjust the brightness with LUX + and -.
### Rainbow
[rainbow.py](rainbow.py)
Some good old fashioned rainbows! You can adjust the cycling speed with A and B, stripe width with C and D, hue with VOL + and -, and the brightness with LUX + and -. The sleep button stops the animation (can be started again with A or B).
### Scrolling Text
[scrolling_text.py](scrolling_text.py)
Display scrolling wisdom, quotes or greetz. You can adjust the brightness with LUX + and -.
## Wireless Examples
These examples require `WIFI_CONFIG.py` and `network_manager.py` (from the `common` directory) to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
- [micropython/examples/common](../../examples/common)
### Cheerlights
[cheerlights.py](cheerlights.py)
Displays the most recent #Cheerlights colour. Find out more about the Cheerlights API at https://cheerlights.com/
### Cheerlights History
[cheerlights_history.py](cheerlights_history.py)
Updates one pixel every five minutes to display the most recent #Cheerlights colour. Discover the most popular colours over time, or use it as an avant garde (but colourful) 53 hour clock! Find out more about the Cheerlights API at https://cheerlights.com/
You can adjust the brightness with LUX + and -.
### Galactic Paint
[galactic_paint](galactic_paint)
Draw on your Galactic Unicorn from another device in real time, over wifi!
Requires `WIFI_CONFIG.py` from the `common` directory. It also needs the `micropython-phew` and `microdot` libraries (you can install these using Thonny's 'Tools > Manage Packages').
## Other Examples
### Launch (Demo Reel)
[launch](launch)
If you want to get the demo reel that Galactic Unicorn ships with back, copy the contents of this `launch` folder to your Pico W.
## Other Resources
Here are some cool Galactic Unicorn community projects and resources that you might find useful / inspirational! Note that code at the links below has not been tested by us and we're not able to offer support with it.
- :link: [Galactic Unicorn MQTT scroller (and 3D printed case)](https://github.com/ucl-casa-ce/Galactic-Unicorn-MQTT-Scroller)
- :link: [Compiling custom pimoroni-pico MicroPython (with ulab)](https://medium.com/@iestynlloyd/galactic-unicorns-and-custom-pimoroni-pico-firmware-38dd7c5913b8)
- :link: [Galactic Unicorn Graphical Workout](https://www.instructables.com/Galactic-Unicorn-Graphical-Workout/)
- :link: [Galactic Unicorn Bounce - Simple GFX Demo](https://www.instructables.com/Galactic-Unicorn-Bounce-Simple-GFX-Demo/)
- :link: [Cheerlights + Galactic Unicorn + MicroPython (beginner-friendly tutorial)](https://cheerlights.com/cheerlights-raspberry-pi-pico-w-micropython/)
- :link: [CheerClock (plus laser-cut templates for a fancy case/diffuser)](https://github.com/seanosteen/CheerClock)
- :link: [Make a giant Pomodoro timer using Galactic Unicorn](https://www.raspberrypi.com/news/make-a-giant-pomodoro-timer-using-galactic-unicorn/)
- :link: [Galactic Weather Clock](https://github.com/raphv/galactic-weather-clock)

View File

@ -0,0 +1,63 @@
# cheerlights
from time import sleep
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
import uasyncio
from network_manager import NetworkManager
import urequests
import WIFI_CONFIG
# setup graphics
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
# fill display
def draw(colour):
graphics.set_pen(colour)
graphics.clear()
gu.update(graphics)
def status_handler(mode, status, ip):
# reports wifi connection status
print(mode, status, ip)
print('Connecting to wifi...')
if status is not None:
if status:
print('Wifi connection successful!')
else:
print('Wifi connection failed!')
# set up wifi
try:
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
except Exception as e:
print(f'Wifi connection failed! {e}')
# cheerlights
# cheerlights colours
colours = {b'red': graphics.create_pen(0xff, 0x00, 0x00), b'green': graphics.create_pen(0x00, 0x80, 0x00),
b'blue': graphics.create_pen(0x00, 0x00, 0xff),
b'cyan': graphics.create_pen(0x00, 0xff, 0xff), b'white': graphics.create_pen(0xff, 0xff, 0xff),
b'oldlace': graphics.create_pen(0xfd, 0xf5, 0xe6),
b'purple': graphics.create_pen(0x80, 0x00, 0x80), b'magenta': graphics.create_pen(0xff, 0x00, 0xff),
b'yellow': graphics.create_pen(0xff, 0xff, 0x00),
b'orange': graphics.create_pen(0xff, 0xa5, 0x00), b'pink': graphics.create_pen(0xff, 0xc0, 0xcb)}
# get request
while True:
r = urequests.get('http://api.thingspeak.com/channels/1417/field/1/last.txt')
print(f'Colour: {r.content}')
if r.content in colours:
draw(colours[r.content])
r.close()
sleep(15)

View File

@ -0,0 +1,129 @@
# This Galactic Unicorn example updates a pixel every five(ish) minutes
# to display the most recent #cheerlights colour. Discover the most popular
# colours over time, or use it as an avant garde (but colourful) 53 hour clock!
# Find out more about the Cheerlights API at https://cheerlights.com/
#
# To run this example you'll need WIFI_CONFIG.py and network_manager.py from
# the pimoroni-pico micropython/examples/common folder
import WIFI_CONFIG
from network_manager import NetworkManager
import uasyncio
import urequests
import time
from machine import Timer, Pin
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
URL = 'http://api.thingspeak.com/channels/1417/field/2/last.json'
UPDATE_INTERVAL = 327 # refresh interval in secs. Be nice to free APIs!
# this esoteric number is used so that a column of LEDs equates (approximately) to an hour
def status_handler(mode, status, ip):
# reports wifi connection status
print(mode, status, ip)
print('Connecting to wifi...')
if status is not None:
if status:
print('Wifi connection successful!')
else:
print('Wifi connection failed!')
def hex_to_rgb(hex):
# converts a hex colour code into RGB
h = hex.lstrip('#')
r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4))
return r, g, b
def get_data():
# open the json file
print(f'Requesting URL: {URL}')
r = urequests.get(URL)
# open the json data
j = r.json()
print('Data obtained!')
r.close()
# flash the onboard LED after getting data
pico_led.value(True)
time.sleep(0.2)
pico_led.value(False)
# extract hex colour from the json data
hex = j['field2']
# add the new hex colour to the end of the array
colour_array.append(hex)
print(f'Colour added to array: {hex}')
# remove the oldest colour in the array
colour_array.pop(0)
update_leds()
def update_leds():
# light up the LEDs
# this step takes a second, it's doing a lot of hex_to_rgb calculations!
print("Updating LEDs...")
i = 0
for x in range(width):
for y in range(height):
r = hex_to_rgb(colour_array[i])[0]
g = hex_to_rgb(colour_array[i])[1]
b = hex_to_rgb(colour_array[i])[2]
current_colour = graphics.create_pen(r, g, b)
graphics.set_pen(current_colour)
graphics.pixel(x, y)
i = i + 1
gu.update(graphics)
print("LEDs updated!")
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
gu.set_brightness(0.5)
# set up the Pico W's onboard LED
pico_led = Pin('LED', Pin.OUT)
current_colour = graphics.create_pen(0, 0, 0)
# set up an list to store the colours
colour_array = ["#000000"] * 583
# set up wifi
try:
network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler)
uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK))
except Exception as e:
print(f'Wifi connection failed! {e}')
# get the first lot of data
get_data()
# start timer (the timer will call the function to update our data every UPDATE_INTERVAL)
timer = Timer(-1)
timer.init(period=UPDATE_INTERVAL * 1000, mode=Timer.PERIODIC, callback=lambda t: get_data())
while True:
# adjust brightness with LUX + and -
# LEDs take a couple of secs to update, so adjust in big (10%) steps
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.1)
update_leds()
print(f"Brightness set to {gu.get_brightness()}")
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.1)
update_leds()
print(f"Brightness set to {gu.get_brightness()}")
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)

View File

@ -0,0 +1,230 @@
# Clock example with NTP synchronization
#
# Create a secrets.py with your Wifi details to be able to get the time
# when the Galactic Unicorn isn't connected to Thonny.
#
# secrets.py should contain:
# WIFI_SSID = "Your WiFi SSID"
# WIFI_PASSWORD = "Your WiFi password"
#
# Clock synchronizes time on start, and resynchronizes if you press the A button
import time
import math
import machine
import network
import ntptime
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
try:
from secrets import WIFI_SSID, WIFI_PASSWORD
wifi_available = True
except ImportError:
print("Create secrets.py with your WiFi credentials to get time from NTP")
wifi_available = False
# constants for controlling the background colour throughout the day
MIDDAY_HUE = 1.1
MIDNIGHT_HUE = 0.8
HUE_OFFSET = -0.1
MIDDAY_SATURATION = 1.0
MIDNIGHT_SATURATION = 1.0
MIDDAY_VALUE = 0.8
MIDNIGHT_VALUE = 0.3
# create galactic object and graphics surface for drawing
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
# create the rtc object
rtc = machine.RTC()
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
# set up some pens to use later
WHITE = graphics.create_pen(255, 255, 255)
BLACK = graphics.create_pen(0, 0, 0)
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return int(v), int(t), int(p)
if i == 1:
return int(q), int(v), int(p)
if i == 2:
return int(p), int(v), int(t)
if i == 3:
return int(p), int(q), int(v)
if i == 4:
return int(t), int(p), int(v)
if i == 5:
return int(v), int(p), int(q)
# function for drawing a gradient background
def gradient_background(start_hue, start_sat, start_val, end_hue, end_sat, end_val):
half_width = width // 2
for x in range(0, half_width):
hue = ((end_hue - start_hue) * (x / half_width)) + start_hue
sat = ((end_sat - start_sat) * (x / half_width)) + start_sat
val = ((end_val - start_val) * (x / half_width)) + start_val
colour = from_hsv(hue, sat, val)
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
for y in range(0, height):
graphics.pixel(x, y)
graphics.pixel(width - x - 1, y)
colour = from_hsv(end_hue, end_sat, end_val)
graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2])))
for y in range(0, height):
graphics.pixel(half_width, y)
# function for drawing outlined text
def outline_text(text, x, y):
graphics.set_pen(BLACK)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(WHITE)
graphics.text(text, x, y, -1, 1)
# Connect to wifi and synchronize the RTC time from NTP
def sync_time():
if not wifi_available:
return
# Start connection
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs
wlan.connect(WIFI_SSID, WIFI_PASSWORD)
# Wait for connect success or failure
max_wait = 100
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
time.sleep(0.2)
redraw_display_if_reqd()
gu.update(graphics)
if max_wait > 0:
print("Connected")
try:
ntptime.settime()
print("Time set")
except OSError:
pass
wlan.disconnect()
wlan.active(False)
# NTP synchronizes the time to UTC, this allows you to adjust the displayed time
# by one hour increments from UTC by pressing the volume up/down buttons
#
# We use the IRQ method to detect the button presses to avoid incrementing/decrementing
# multiple times when the button is held.
utc_offset = 0
up_button = machine.Pin(GalacticUnicorn.SWITCH_VOLUME_UP, machine.Pin.IN, machine.Pin.PULL_UP)
down_button = machine.Pin(GalacticUnicorn.SWITCH_VOLUME_DOWN, machine.Pin.IN, machine.Pin.PULL_UP)
def adjust_utc_offset(pin):
global utc_offset
if pin == up_button:
utc_offset += 1
if pin == down_button:
utc_offset -= 1
up_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
down_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset)
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
last_second = second
# Check whether the RTC time has changed and if so redraw the display
def redraw_display_if_reqd():
global year, month, day, wd, hour, minute, second, last_second
year, month, day, wd, hour, minute, second, _ = rtc.datetime()
if second != last_second:
hour = (hour + utc_offset) % 24
time_through_day = (((hour * 60) + minute) * 60) + second
percent_through_day = time_through_day / 86400
percent_to_midday = 1.0 - ((math.cos(percent_through_day * math.pi * 2) + 1) / 2)
print(percent_to_midday)
hue = ((MIDDAY_HUE - MIDNIGHT_HUE) * percent_to_midday) + MIDNIGHT_HUE
sat = ((MIDDAY_SATURATION - MIDNIGHT_SATURATION) * percent_to_midday) + MIDNIGHT_SATURATION
val = ((MIDDAY_VALUE - MIDNIGHT_VALUE) * percent_to_midday) + MIDNIGHT_VALUE
gradient_background(hue, sat, val,
hue + HUE_OFFSET, sat, val)
clock = "{:02}:{:02}:{:02}".format(hour, minute, second)
# calculate text position so that it is centred
w = graphics.measure_text(clock, 1)
x = int(width / 2 - w / 2 + 1)
y = 2
outline_text(clock, x, y)
last_second = second
# set the font
graphics.set_font("bitmap8")
gu.set_brightness(0.5)
sync_time()
while True:
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
sync_time()
redraw_display_if_reqd()
# update the display
gu.update(graphics)
time.sleep(0.01)

View File

@ -0,0 +1,102 @@
# Add a SCD41 sensor breakout to your Galactic Unicorn to make a handy CO2 detector!
# https://shop.pimoroni.com/products/scd41-co2-sensor-breakout
# Press A to reset the high/low values.
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
import breakout_scd41
from pimoroni_i2c import PimoroniI2C
from pimoroni import BREAKOUT_GARDEN_I2C_PINS
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
i2c = PimoroniI2C(**BREAKOUT_GARDEN_I2C_PINS)
WIDTH, HEIGHT = graphics.get_bounds()
# the range of readings to map to colours
# https://www.kane.co.uk/knowledge-centre/what-are-safe-levels-of-co-and-co2-in-rooms
MIN = 400
MAX = 2000
# pick what bits of the colour wheel to use (from 0-360°)
# https://www.cssscript.com/demo/hsv-hsl-color-wheel-picker-reinvented/
HUE_START = 100 # green
HUE_END = 0 # red
# some pen colours to use
BLACK = graphics.create_pen(0, 0, 0)
WHITE = graphics.create_pen(255, 255, 255)
GREY = graphics.create_pen(20, 20, 20)
# sets up a handy function we can call to clear the screen
def clear():
graphics.set_pen(BLACK)
graphics.clear()
# some variables to keep track of high / low readings
highest = 0.0
lowest = 4000.0
# set up
breakout_scd41.init(i2c)
breakout_scd41.start()
graphics.set_font("bitmap8")
graphics.set_pen(WHITE)
graphics.text("Waiting!", 0, 0, scale=1)
gu.update(graphics)
while True:
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
# reset recorded high / low values
highest = 0.0
lowest = 4000.0
if breakout_scd41.ready():
clear()
# read the sensor
co2, temperature, humidity = breakout_scd41.measure()
# update highest / lowest values
if co2 < lowest:
lowest = co2
if co2 > highest:
highest = co2
# calculate a colour from the co2 reading
hue = max(0, HUE_START + ((co2 - MIN) * (HUE_END - HUE_START) / (MAX - MIN)))
# create pens with this colour (and with the high / low colours)
CURRENT_COLOUR = graphics.create_pen_hsv(hue / 360, 1.0, 0.8)
HIGH_COLOUR = graphics.create_pen_hsv(HUE_END / 360, 1.0, 0.8)
LOW_COLOUR = graphics.create_pen_hsv(HUE_START / 360, 1.0, 0.8)
# draw the text
graphics.set_pen(CURRENT_COLOUR)
graphics.text("CO2", 0, 0, scale=1)
# measure the rest of the text before drawing so that we can right align it
text_width = graphics.measure_text(f"{co2:.0f}ppm", scale=1)
graphics.text(f"{co2:.0f}ppm", WIDTH - text_width, 0, scale=1)
# draw a bar for the background
graphics.set_pen(GREY)
graphics.rectangle(0, 9, WIDTH, 10)
# draw a bar for the current co2 level
graphics.set_pen(CURRENT_COLOUR)
graphics.rectangle(0, 9, int(co2 / WIDTH), 10)
# draw a bar for the highest co2 level seen
graphics.set_pen(HIGH_COLOUR)
graphics.pixel(int(highest / WIDTH), 9)
graphics.pixel(int(highest / WIDTH), 10)
# draw a bar for the lowest co2 level seen
graphics.set_pen(LOW_COLOUR)
graphics.pixel(int(lowest / WIDTH), 9)
graphics.pixel(int(lowest / WIDTH), 10)
gu.update(graphics)

View File

@ -0,0 +1,79 @@
import time
import random
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
'''
Random LEDs blink on and off mimicing the look of a movie
super computer doing its work in the eighties.
You can adjust the brightness with LUX + and -.
'''
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
colour = (230, 150, 0)
@micropython.native # noqa: F821
def setup():
global width, height, lifetime, age
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
lifetime = [[0.0 for y in range(height)] for x in range(width)]
age = [[0.0 for y in range(height)] for x in range(width)]
for y in range(height):
for x in range(width):
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y]
@micropython.native # noqa: F821
def draw():
for y in range(height):
for x in range(width):
if age[x][y] < lifetime[x][y] * 0.3:
graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2]))
elif age[x][y] < lifetime[x][y] * 0.5:
decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0
graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2])))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
gu.update(graphics)
@micropython.native # noqa: F821
def update():
for y in range(height):
for x in range(width):
if age[x][y] >= lifetime[x][y]:
age[x][y] = 0.0
lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1)
age[x][y] += 0.025
setup()
gu.set_brightness(0.5)
while True:
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
start = time.ticks_ms()
draw()
update()
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)
print("total took: {} ms".format(time.ticks_ms() - start))

View File

@ -0,0 +1,123 @@
import time
import math
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
'''
Displays some text, gradients and colours and demonstrates button use.
You can adjust the brightness with LUX + and -.
'''
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
def gradient(r, g, b):
for y in range(0, height):
for x in range(0, width):
graphics.set_pen(graphics.create_pen(int((r * x) / 52), int((g * x) / 52), int((b * x) / 52)))
graphics.pixel(x, y)
def grid(r, g, b):
for y in range(0, height):
for x in range(0, width):
if (x + y) % 2 == 0:
graphics.set_pen(graphics.create_pen(r, g, b))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
def outline_text(text):
ms = time.ticks_ms()
graphics.set_font("bitmap8")
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
w = graphics.measure_text(text, 1)
x = int(53 / 2 - w / 2 + 1)
y = 2
graphics.set_pen(0)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(v, v, v))
graphics.text(text, x, y, -1, 1)
gu.set_brightness(0.5)
while True:
time_ms = time.ticks_ms()
test = (time_ms // 1000) % 5
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if test == 0:
print("grid pattern")
grid(255, 255, 255)
elif test == 1:
print("red gradient")
gradient(255, 0, 0)
elif test == 2:
print("green gradient")
gradient(0, 255, 0)
elif test == 3:
print("blue gradient")
gradient(0, 0, 255)
elif test == 4:
print("white gradient")
gradient(255, 255, 255)
text = ""
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
text = "Button A"
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
text = "Button B"
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
text = "Button C"
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
text = "Button D"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
text = "Louder!"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
text = "Quieter"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
text = "Brighter!"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
text = "Darker"
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
text = "Zzz... zzz..."
outline_text(text)
gu.update(graphics)

View File

@ -0,0 +1,359 @@
import gc
import time
import math
from machine import Timer
from galactic import GalacticUnicorn, Channel
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
'''
Displays some text, gradients and colours and demonstrates button use.
Also demonstrates some of the audio / synth features.
- Button A plays a synth tune
- Button B plays a solo channel of the synth tune
- Button C plays a sinewave (it's frequency can be adjusted with VOL + and -)
- Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -)
- Sleep button stops the sounds
'''
gc.collect()
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
SONG_LENGTH = 384
HAT = 20000
BASS = 500
SNARE = 6000
SUB = 50
melody_notes = (
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 220, 0, 196, 0, 147, 0, 175, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0,
147, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 247, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 175, 0, 0, 0, 0, 0, 0, 0, 175, 0, 196, 0, 220, 0, 262, 0, 330, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 349, 0, 0, 0, 0, 0, 0, 0, 349, 0, 330, 0, 294, 0, 220, 0, 262, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, 0, 247, 0, 0, 0, 0, 0, 0, 0, 247, 0, 262, 0, 294, 0, 392, 0, 440, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
rhythm_notes = (
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0,
294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 294, 0, 440, 0, 587, 0, 440, 0, 392, 0, 523, 0, 659, 0, 523, 0, 392, 0, 523, 0, 659, 0, 523, 0, 698, 0, 587, 0, 440, 0, 587, 0, 698, 0, 587, 0, 440, 0, 587, 0, 523, 0, 440, 0, 330, 0, 440, 0, 523, 0, 440, 0, 330, 0, 440, 0, 349, 0, 294, 0, 220, 0, 294, 0, 349, 0, 294, 0, 220, 0, 294, 0, 262, 0, 247, 0, 220, 0, 175, 0, 165, 0, 147, 0, 131, 0, 98, 0)
drum_beats = (
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0,
BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, BASS, -1, BASS, -1, 0, 0, 0, 0, 0, 0, SNARE, 0, -1, 0, 0, 0, 0, 0)
hi_hat = (
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1,
HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1)
bass_notes = (
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0,
SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, SUB, -1, SUB, -1, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0, 0)
notes = [melody_notes, rhythm_notes, drum_beats, hi_hat, bass_notes]
channels = [gu.synth_channel(i) for i in range(len(notes))]
def gradient(r, g, b):
for y in range(0, height):
for x in range(0, width):
graphics.set_pen(graphics.create_pen(int((r * x) / 52), int((g * x) / 52), int((b * x) / 52)))
graphics.pixel(x, y)
def grid(r, g, b):
for y in range(0, height):
for x in range(0, width):
if (x + y) % 2 == 0:
graphics.set_pen(graphics.create_pen(r, g, b))
else:
graphics.set_pen(0)
graphics.pixel(x, y)
def outline_text(text):
ms = time.ticks_ms()
graphics.set_font("bitmap8")
v = int((math.sin(ms / 100.0) + 1.0) * 127.0)
w = graphics.measure_text(text, 1)
x = int(53 / 2 - w / 2 + 1)
y = 2
graphics.set_pen(0)
graphics.text(text, x - 1, y - 1, -1, 1)
graphics.text(text, x, y - 1, -1, 1)
graphics.text(text, x + 1, y - 1, -1, 1)
graphics.text(text, x - 1, y, -1, 1)
graphics.text(text, x + 1, y, -1, 1)
graphics.text(text, x - 1, y + 1, -1, 1)
graphics.text(text, x, y + 1, -1, 1)
graphics.text(text, x + 1, y + 1, -1, 1)
graphics.set_pen(graphics.create_pen(v, v, v))
graphics.text(text, x, y, -1, 1)
gu.set_brightness(0.5)
# Vars for storing button state
was_a_pressed = False
was_b_pressed = False
was_c_pressed = False
was_d_pressed = False
was_z_pressed = False
# The two frequencies to play
tone_a = 0
tone_b = 0
# The current synth beat
beat = 0
def next_beat():
global beat
for i in range(5):
if notes[i][beat] > 0:
channels[i].frequency(notes[i][beat])
channels[i].trigger_attack()
elif notes[i][beat] == -1:
channels[i].trigger_release()
beat = (beat + 1) % SONG_LENGTH
def tick(timer):
next_beat()
timer = Timer(-1)
synthing = False
while True:
time_ms = time.ticks_ms()
test = (time_ms // 1000) % 5
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
if not was_a_pressed:
# Configure the synth to play our notes
channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
attack=0.016,
decay=0.168,
sustain=0xafff / 65535,
release=0.168,
volume=10000 / 65535)
channels[1].configure(waveforms=Channel.SINE + Channel.SQUARE,
attack=0.038,
decay=0.300,
sustain=0,
release=0,
volume=12000 / 65535)
channels[2].configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.010,
sustain=16000 / 65535,
release=0.100,
volume=18000 / 65535)
channels[3].configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.005,
sustain=8000 / 65535,
release=0.040,
volume=8000 / 65535)
channels[4].configure(waveforms=Channel.SQUARE,
attack=0.010,
decay=0.100,
sustain=0,
release=0.500,
volume=12000 / 65535)
# If the synth is not already playing, init the first beat
if not synthing:
beat = 0
next_beat()
gu.play_synth()
synthing = True
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
was_a_pressed = True
else:
was_a_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
if not was_b_pressed:
# Configure the synth to play our notes, but with only one channel audable
channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE,
attack=0.016,
decay=0.168,
sustain=0,
release=0.168,
volume=0)
channels[1].configure(waveforms=Channel.SINE + Channel.SQUARE,
attack=0.038,
decay=0.300,
sustain=0,
release=0,
volume=12000 / 65535)
channels[2].configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.010,
sustain=16000 / 65535,
release=0.100,
volume=0)
channels[3].configure(waveforms=Channel.NOISE,
attack=0.005,
decay=0.005,
sustain=8000 / 65535,
release=0.040,
volume=0)
channels[4].configure(waveforms=Channel.SQUARE,
attack=0.010,
decay=0.100,
sustain=0,
release=0.500,
volume=0)
# If the synth is not already playing, init the first beat
if not synthing:
beat = 0
next_beat()
gu.play_synth()
synthing = True
timer.init(freq=10, mode=Timer.PERIODIC, callback=tick)
was_b_pressed = True
else:
was_b_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
if not was_c_pressed:
# Stop synth (if running) and play Tone A
timer.deinit()
tone_a = 400
channels[0].play_tone(tone_a, 0.06)
gu.play_synth()
synthing = False
was_c_pressed = True
else:
was_c_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
if not was_d_pressed:
# Stop synth (if running) and play Tone B
timer.deinit()
tone_b = 600
channels[1].play_tone(tone_b, 0.06, attack=0.5)
gu.play_synth()
synthing = False
was_d_pressed = True
else:
was_d_pressed = False
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
if tone_b > 0: # Zero means tone not playing
# Increase Tone B
tone_b = min(tone_b + 10, 20000)
channels[1].frequency(tone_b)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
if tone_b > 0: # Zero means tone not playing
# Decrease Tone B
tone_b = max(tone_b - 10, 10)
channels[1].frequency(max(tone_b, 10))
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
if tone_a > 0: # Zero means tone not playing
# Increase Tone A
tone_a = min(tone_a + 10, 20000)
channels[0].frequency(tone_a)
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
if tone_a > 0: # Zero means tone not playing
# Decrease Tone A
tone_a = max(tone_a - 10, 10)
channels[0].frequency(tone_a)
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
if not was_z_pressed:
# Stop synth and both tones
tone_a = 0
tone_b = 0
gu.stop_playing()
timer.deinit()
synthing = False
was_z_pressed = True
else:
was_z_pressed = False
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if test == 0:
# print("grid pattern")
grid(255, 255, 255)
elif test == 1:
# print("red gradient")
gradient(255, 0, 0)
elif test == 2:
# print("green gradient")
gradient(0, 255, 0)
elif test == 3:
# print("blue gradient")
gradient(0, 0, 255)
elif test == 4:
# print("white gradient")
gradient(255, 255, 255)
text = ""
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
text = "Play Synth"
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
text = "Solo Synth"
if gu.is_pressed(GalacticUnicorn.SWITCH_C):
text = "Tone A"
if gu.is_pressed(GalacticUnicorn.SWITCH_D):
text = "Tone B"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_UP):
text = "Raise A"
if gu.is_pressed(GalacticUnicorn.SWITCH_VOLUME_DOWN):
text = "Lower A"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
text = "Raise B"
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
text = "Lower B"
if gu.is_pressed(GalacticUnicorn.SWITCH_SLEEP):
text = "Stop"
outline_text(text)
gu.update(graphics)
# pause for a moment (important or the USB serial device will fail
time.sleep(0.001)

View File

@ -0,0 +1,145 @@
import time
import random
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
'''
A pretty, procedural fire effect.
Switch between landscape fire and vertical fire using the A and B buttons!
You can adjust the brightness with LUX + and -.
'''
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
fire_colours = [graphics.create_pen(0, 0, 0),
graphics.create_pen(20, 20, 20),
graphics.create_pen(180, 30, 0),
graphics.create_pen(220, 160, 0),
graphics.create_pen(255, 255, 180)]
@micropython.native # noqa: F821
def setup_landscape():
global width, height, heat, fire_spawns, damping_factor
width = GalacticUnicorn.WIDTH + 2
height = GalacticUnicorn.HEIGHT + 4
heat = [[0.0 for y in range(height)] for x in range(width)]
fire_spawns = 5
damping_factor = 0.97
@micropython.native # noqa: F821
def setup_portrait():
global width, height, heat, fire_spawns, damping_factor
width = GalacticUnicorn.HEIGHT + 2
height = GalacticUnicorn.WIDTH + 4
heat = [[0.0 for y in range(height)] for x in range(width)]
fire_spawns = 2
damping_factor = 0.99
@micropython.native # noqa: F821
def update():
# clear the bottom row and then add a new fire seed to it
for x in range(width):
heat[x][height - 1] = 0.0
heat[x][height - 2] = 0.0
for c in range(fire_spawns):
x = random.randint(0, width - 4) + 2
heat[x + 0][height - 1] = 1.0
heat[x + 1][height - 1] = 1.0
heat[x - 1][height - 1] = 1.0
heat[x + 0][height - 2] = 1.0
heat[x + 1][height - 2] = 1.0
heat[x - 1][height - 2] = 1.0
for y in range(0, height - 2):
for x in range(1, width - 1):
# update this pixel by averaging the below pixels
average = (
heat[x][y] + heat[x][y + 1] + heat[x][y + 2] + heat[x - 1][y + 1] + heat[x + 1][y + 1]
) / 5.0
# damping factor to ensure flame tapers out towards the top of the displays
average *= damping_factor
# update the heat map with our newly averaged value
heat[x][y] = average
@micropython.native # noqa: F821
def draw_landscape():
for y in range(GalacticUnicorn.HEIGHT):
for x in range(GalacticUnicorn.WIDTH):
value = heat[x + 1][y]
if value < 0.15:
graphics.set_pen(fire_colours[0])
elif value < 0.25:
graphics.set_pen(fire_colours[1])
elif value < 0.35:
graphics.set_pen(fire_colours[2])
elif value < 0.45:
graphics.set_pen(fire_colours[3])
else:
graphics.set_pen(fire_colours[4])
graphics.pixel(x, y)
gu.update(graphics)
@micropython.native # noqa: F821
def draw_portrait():
for y in range(GalacticUnicorn.WIDTH):
for x in range(GalacticUnicorn.HEIGHT):
value = heat[x + 1][y]
if value < 0.15:
graphics.set_pen(fire_colours[0])
elif value < 0.25:
graphics.set_pen(fire_colours[1])
elif value < 0.35:
graphics.set_pen(fire_colours[2])
elif value < 0.45:
graphics.set_pen(fire_colours[3])
else:
graphics.set_pen(fire_colours[4])
graphics.pixel(y, x)
gu.update(graphics)
landscape = True
setup_landscape()
gu.set_brightness(0.5)
while True:
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
gu.adjust_brightness(+0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
gu.adjust_brightness(-0.01)
if gu.is_pressed(GalacticUnicorn.SWITCH_A):
landscape = True
setup_landscape()
if gu.is_pressed(GalacticUnicorn.SWITCH_B):
landscape = False
setup_portrait()
start = time.ticks_ms()
update()
if landscape:
draw_landscape()
else:
draw_portrait()
# pause for a moment (important or the USB serial device will fail)
time.sleep(0.001)
print("total took: {} ms".format(time.ticks_ms() - start))

View File

@ -0,0 +1,11 @@
# Galactic Paint
Galactic Paint lets you paint pixels onto your Galatic Unicorn over WiFi, in realtime!
## Setting Up
You'll need `WIFI_CONFIG.py` from the `common` directory to be saved to your Pico W. Open up `WIFI_CONFIG.py` in Thonny to add your wifi details (and save it when you're done).
You will also have to install `micropython-phew` and `microdot` through Thonny's Tools -> Manage Packages.
Run the example through Thonny and it should get connected and give you a URL to visit. Open that URL in your browser and start painting!

View File

@ -0,0 +1,113 @@
import os
from microdot_asyncio import Microdot, send_file
from microdot_asyncio_websocket import with_websocket
from phew import connect_to_wifi
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
from WIFI_CONFIG import SSID, PSK
gu = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
mv_graphics = memoryview(graphics)
gu.set_brightness(0.5)
WIDTH, HEIGHT = graphics.get_bounds()
ip = connect_to_wifi(SSID, PSK)
print(f"Start painting at: http://{ip}")
server = Microdot()
@server.route("/", methods=["GET"])
def route_index(request):
return send_file("galactic_paint/index.html")
@server.route("/static/<path:path>", methods=["GET"])
def route_static(request, path):
return send_file(f"galactic_paint/static/{path}")
def get_pixel(x, y):
if x < WIDTH and y < HEIGHT and x >= 0 and y >= 0:
o = (y * WIDTH + x) * 4
return tuple(mv_graphics[o:o + 3])
return None
def flood_fill(x, y, r, g, b):
todo = []
def fill(x, y, c):
if get_pixel(x, y) != c:
return
graphics.pixel(x, y)
up = get_pixel(x, y - 1)
dn = get_pixel(x, y + 1)
lf = get_pixel(x - 1, y)
ri = get_pixel(x + 1, y)
if up == c:
todo.append((x, y - 1))
if dn == c:
todo.append((x, y + 1))
if lf == c:
todo.append((x - 1, y))
if ri == c:
todo.append((x + 1, y))
c = get_pixel(x, y)
if c is None:
return
fill(x, y, c)
while len(todo):
x, y = todo.pop(0)
fill(x, y, c)
@server.route('/paint')
@with_websocket
async def echo(request, ws):
while True:
data = await ws.receive()
try:
x, y, r, g, b = [int(n) for n in data[0:5]]
graphics.set_pen(graphics.create_pen(r, g, b))
graphics.pixel(x, y)
except ValueError:
if data == "show":
gu.update(graphics)
if data == "fill":
data = await ws.receive()
x, y, r, g, b = [int(n) for n in data[0:5]]
graphics.set_pen(graphics.create_pen(r, g, b))
flood_fill(x, y, r, g, b)
if data == "clear":
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
if data == "save":
filename = await ws.receive()
print(f"Saving to {filename}.bin")
try:
os.mkdir("saves")
except OSError:
pass
with open(f"saves/{filename}.bin", "wb") as f:
f.write(graphics)
await ws.send(f"alert: Saved to saves/{filename}.bin")
server.run(host="0.0.0.0", port=80)

View File

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Galactic Paint</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="//cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="/static/paint.css">
</head>
<body>
<div class="window">
<h1>Galactic Paint</h1>
<table cellspacing="0" cellpadding="0" border-collapse="collapse">
<tbody></tbody>
</table>
<div id="palette">
<ul>
<li class="selected" style="background:rgb(0,0,0);"></li>
<li style="background:rgb(132,0,0);"></li>
<li style="background:rgb(0,132,0);"></li>
<li style="background:rgb(132,132,0);"></li>
<li style="background:rgb(0,0,132);"></li>
<li style="background:rgb(132,0,132);"></li>
<li style="background:rgb(0,132,132);"></li>
<li style="background:rgb(132,132,132);"></li>
<li style="background:rgb(198,198,198);"></li>
<li style="background:rgb(255,0,0);"></li>
<li style="background:rgb(0,255,0);"></li>
<li style="background:rgb(255,255,0);"></li>
<li style="background:rgb(0,0,255);"></li>
<li style="background:rgb(255,0,255);"></li>
<li style="background:rgb(0,255,255);"></li>
<li style="background:rgb(255,255,255);"></li>
</ul>
<input type="color" id="custom" name="custom" value="#ff0000">
</div>
<ul class="tools">
<li data-tool="paint" class="paint selected"><span class="fa fa-pencil"></span></li>
<li data-tool="fill" class="fill"><span class="fa fa-bitbucket"></span></li>
<li data-tool="erase" class="erase"><span class="fa fa-eraser"></span></li>
<li data-tool="pick" class="pick"><span class="fa fa-eyedropper"></span></li>
<li data-tool="lighten" class="lighten"><span class="fa fa-sun-o"></span></li>
<li data-tool="darken" class="darken"><span class="fa fa-adjust"></span></li>
<li data-tool="trash" class="trash"><span class="fa fa-trash"></span></li>
<li data-tool="save" class="save"><span class="fa fa-save"></span></li>
</ul>
</div>
<script type="text/javascript" src="//cdn.jsdelivr.net/npm/jquery@3.6.1/dist/jquery.min.js"></script>
<script type="text/javascript" src="/static/tinycolor.js"></script>
<script type="text/javascript" src="/static/paint.js"></script>
</body>
</html>

View File

@ -0,0 +1,131 @@
body {
background:#333;
padding:20px;
font-family:Arial, Verdana, Sans-Serif;
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAaUlEQVQYV33Q0Q3AIAgEUBjBFVyBFRzbWVjBEajXBIOVypcJj1NhETG61BiDVJX4Bh211v5hRDiniV+Elx0wQwd0hEatlUop65srMSah23vf8Auz65AWMc8rDHvCCjAQK2KeDcuQDzh+AHEJX8mbbU1BAAAAAElFTkSuQmCC) repeat;
}
.icons {
position:absolute;
margin:0;
padding:20px;
list-style:none;
}
.icons li {
margin:20px;
padding:0;
list-style:none;
padding-top:80px;
width:100px;
}
.icons li span {
background:#FFF;
color:#000;
border:1px solid #000;
line-height:20px;
padding:5px 10px;
text-align:center;
font-size:10px;
line-height:10px;
display:inline-block;
}
#palette ul, #palette li {
margin:0;padding:0;list-style:none;
}
#palette {
list-style:none;
position:relative;
height: 122px;
padding:0 8px;
}
#palette ul {
display:block;
width:456px;
float: left;
}
#palette li, #palette input {
border: 2px outset;
width:49px;
height:49px;
float:left;
display:block;
margin:2px;
}
#palette input {
width:110px;
height:110px;
}
.window {
width: 976px;
position: relative;
background: #0E071A;
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
}
.tools {
margin:0;padding:0;list-style:none;
clear:both;
display:block;
position:absolute;
top: 50px;
right: 8px;
width: 98px;
background:#999999;
font-size:0;
}
.tools span {
line-height:30px;
}
.tools li {
font-size:16px;
width: 45px;
height: 40px;
text-align:center;
margin:0;
padding:0;
display:inline-block;
line-height:40px;
border:2px outset #EEEEEE;
background:#F5F5F5;
cursor:pointer;
color:#000;
}
.tools li.selected {
background:#000;
color:#FFF;
}
h1 {
color: #FFF;
background: #6D38BB;
height:40px;
margin:0;
padding:0 8px;
line-height:40px;
font-weight:normal;
font-size:24px;
}
table {
clear:both;
cursor:pointer;
margin:10px;
border:1px solid #333;
background: #000000;
}
table td {
width:14px;
height:14px;
border:1px solid #333;
}

View File

@ -0,0 +1,214 @@
'use strict';
var md = false;
var color = tinycolor('#840000');
var update;
$(document).ready(function(){
var picker = $('#custom');
var palette = $('#palette');
picker.val(color.toHexString());
$(document)
.on('mousedown',function(e){md=true;})
.on('mouseup',function(e){md=false;});
$('table').on('dragstart', function(e){
e.preventDefault();
return false;
});
for (var y = 0; y < 11; y++) {
var row = $('<tr></tr>');
for (var x = 0; x < 53; x++) {
row.append('<td></td>');
}
$('tbody').append(row);
}
$('.tools li').on('click', function(){
switch($(this).index()){
case 6:
clear();
break;
case 7:
save();
break;
default:
$('.tools li').removeClass('selected');
$(this).addClass('selected');
break;
}
});
picker.on('change', function(){
color = tinycolor($(this).val());
})
palette.find('li').on('click', function(){
pick(this);
});
function handle_tool(obj, is_click){
switch($('.tools li.selected').index()){
case 0: //'paint':
paint(obj);
break;
case 1: // Fill
if( is_click ) fill(obj);
break;
case 2: // Erase
update_pixel(obj, tinycolor('#000000'));
break;
case 3: //'pick':
pick(obj);
break;
case 4: //'lighten':
lighten(obj);
break;
case 5: //'darken':
darken(obj);
break;
}
}
var fill_target = null;
var fill_stack = [];
function fill(obj){
fill_target = tinycolor($(obj).css('background-color')).toRgbString();
if( fill_target == color.toRgbString() ){
return false;
}
var x = $(obj).index();
var y = $(obj).parent().index();
socket.send("fill");
socket.send(new Uint8Array([x, y, color.toRgb().r, color.toRgb().g, color.toRgb().b]));
socket.send('show');
do_fill(obj);
while(fill_stack.length > 0){
var pixel = fill_stack.pop();
do_fill(pixel);
}
}
function is_target_color(obj){
return ( tinycolor($(obj).css('background-color')).toRgbString() == fill_target);
}
function do_fill(obj){
var obj = $(obj);
if( is_target_color(obj) ){
$(obj).css('background-color', color.toRgbString());
var r = obj.next('td'); // Right
var l = obj.prev('td'); // Left
var u = obj.parent().prev('tr').find('td:eq(' + obj.index() + ')'); // Above
var d = obj.parent().next('tr').find('td:eq(' + obj.index() + ')'); // Below
if( r.length && is_target_color(r[0]) ) fill_stack.push(r[0]);
if( l.length && is_target_color(l[0]) ) fill_stack.push(l[0]);
if( u.length && is_target_color(u[0]) ) fill_stack.push(u[0]);
if( d.length && is_target_color(d[0]) ) fill_stack.push(d[0]);
}
}
function save(){
var filename = prompt('Please enter a filename', 'mypaint');
filename = filename.replace(/[^a-z0-9]/gi, '_').toLowerCase();
socket.send('save');
socket.send(filename);
}
function clear(){
$('td').css('background-color','rgb(0,0,0)').data('changed',false);
socket.send('clear');
socket.send('show');
}
function lighten(obj){
var c = tinycolor($(obj).css('background-color'));
c.lighten(5);
update_pixel(obj, c);
}
function darken(obj){
var c = tinycolor($(obj).css('background-color'));
c.darken(5);
update_pixel(obj, c);
}
function pick(obj){
color = tinycolor($(obj).css('background-color'));
picker.val(color.toHexString());
}
function update_pixel(obj, col){
var bgcol = tinycolor($(obj).css('background-color'));
if(col != bgcol){
$(obj)
.data('changed', true)
.css('background-color', col.toRgbString());
}
}
function update_pixels(){
var changed = false;
$('td').each(function( index, obj ){
if($(obj).data('changed')){
$(obj).data('changed',false);
changed = true;
var x = $(this).index();
var y = $(this).parent().index();
var col = tinycolor($(obj).css('background-color')).toRgb();
if(socket) {
socket.send(new Uint8Array([x, y, col.r, col.g, col.b]));
}
}
});
if(changed){
socket.send('show');
}
}
function paint(obj){
update_pixel(obj, color);
}
$('table td').on('click', function(){
handle_tool(this, true);
});
$('table td').on('mousemove', function(){
if(!md) return false;
handle_tool(this, false);
})
const socket = new WebSocket('ws://' + window.location.host + '/paint');
socket.addEventListener('message', ev => {
console.log('<<< ' + ev.data);
if(ev.data.substring(0, 6) == "alert:") {
alert(ev.data.substring(6));
}
});
socket.addEventListener('close', ev => {
console.log('<<< closed');
});
socket.addEventListener('open', ev => {
clear();
update = setInterval(update_pixels, 50);
});
});

View File

@ -0,0 +1,80 @@
import random
from galactic import GalacticUnicorn
graphics = None
palette = None
# setup heat value buffer and fire parameters
width = GalacticUnicorn.WIDTH + 2
height = GalacticUnicorn.HEIGHT + 4
heat = [[0.0 for y in range(height)] for x in range(width)]
fire_spawns = 5
damping_factor = 0.97
def init():
# a palette of five firey colours (white, yellow, orange, red, smoke)
global palette
palette = [
graphics.create_pen(0, 0, 0),
graphics.create_pen(20, 20, 20),
graphics.create_pen(180, 30, 0),
graphics.create_pen(220, 160, 0),
graphics.create_pen(255, 255, 180)
]
# returns the palette entry for a given heat value
@micropython.native # noqa: F821
def pen_from_value(value):
if value < 0.15:
return palette[0]
elif value < 0.25:
return palette[1]
elif value < 0.35:
return palette[2]
elif value < 0.45:
return palette[3]
return palette[4]
@micropython.native # noqa: F821
def draw():
# clear the the rows off the bottom of the display
for x in range(width):
heat[x][height - 1] = 0.0
heat[x][height - 2] = 0.0
# add new fire spawns
for c in range(fire_spawns):
x = random.randint(0, width - 4) + 2
heat[x + 0][height - 1] = 1.0
heat[x + 1][height - 1] = 1.0
heat[x - 1][height - 1] = 1.0
heat[x + 0][height - 2] = 1.0
heat[x + 1][height - 2] = 1.0
heat[x - 1][height - 2] = 1.0
# average and damp out each value to create rising flame effect
for y in range(0, height - 2):
for x in range(1, width - 1):
# update this pixel by averaging the below pixels
average = (
heat[x][y] + heat[x][y + 1] + heat[x][y + 2] + heat[x - 1][y + 1] + heat[x + 1][y + 1]
) / 5.0
# damping factor to ensure flame tapers out towards the top of the displays
average *= damping_factor
# update the heat map with our newly averaged value
heat[x][y] = average
# render the heat values to the graphics buffer
for y in range(GalacticUnicorn.HEIGHT):
for x in range(GalacticUnicorn.WIDTH):
graphics.set_pen(pen_from_value(heat[x + 1][y]))
graphics.pixel(x, y)
def test():
print("A")

View File

@ -0,0 +1,113 @@
import time
import machine
from galactic import GalacticUnicorn
from picographics import PicoGraphics, DISPLAY_GALACTIC_UNICORN as DISPLAY
# overclock to 200Mhz
machine.freq(200000000)
# create galactic object and graphics surface for drawing
galactic = GalacticUnicorn()
graphics = PicoGraphics(DISPLAY)
brightness = 0.5
# returns the id of the button that is currently pressed or
# None if none are
def pressed():
if galactic.is_pressed(GalacticUnicorn.SWITCH_A):
return GalacticUnicorn.SWITCH_A
if galactic.is_pressed(GalacticUnicorn.SWITCH_B):
return GalacticUnicorn.SWITCH_B
if galactic.is_pressed(GalacticUnicorn.SWITCH_C):
return GalacticUnicorn.SWITCH_C
if galactic.is_pressed(GalacticUnicorn.SWITCH_D):
return GalacticUnicorn.SWITCH_D
return None
# wait for a button to be pressed and load that effect
while True:
graphics.set_font("bitmap6")
graphics.set_pen(graphics.create_pen(0, 0, 0))
graphics.clear()
graphics.set_pen(graphics.create_pen(155, 155, 155))
graphics.text("PRESS", 12, -1, -1, 1)
graphics.text("A B C OR D!", 2, 5, -1, 1)
# brightness up/down
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
brightness += 0.01
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
brightness -= 0.01
brightness = max(min(brightness, 1.0), 0.0)
galactic.set_brightness(brightness)
galactic.update(graphics)
if pressed() == GalacticUnicorn.SWITCH_A:
import fire as effect
break
if pressed() == GalacticUnicorn.SWITCH_B:
import supercomputer as effect # noqa: F811
break
if pressed() == GalacticUnicorn.SWITCH_C:
import rainbow as effect # noqa: F811
break
if pressed() == GalacticUnicorn.SWITCH_D:
import retroprompt as effect # noqa: F811
break
# pause for a moment
time.sleep(0.01)
# wait until all buttons are released
while pressed() is not None:
time.sleep(0.1)
effect.graphics = graphics
effect.init()
sleep = False
was_sleep_pressed = False
# wait
while True:
# if A, B, C, or D are pressed then reset
if pressed() is not None:
machine.reset()
sleep_pressed = galactic.is_pressed(GalacticUnicorn.SWITCH_SLEEP)
if sleep_pressed and not was_sleep_pressed:
sleep = not sleep
was_sleep_pressed = sleep_pressed
if sleep:
# fade out if screen not off
galactic.set_brightness(galactic.get_brightness() - 0.01)
if galactic.get_brightness() > 0.0:
effect.draw()
# update the display
galactic.update(graphics)
else:
effect.draw()
# update the display
galactic.update(graphics)
# brightness up/down
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_UP):
brightness += 0.01
if galactic.is_pressed(GalacticUnicorn.SWITCH_BRIGHTNESS_DOWN):
brightness -= 0.01
brightness = max(min(brightness, 1.0), 0.0)
galactic.set_brightness(brightness)
# pause for a moment (important or the USB serial device will fail
time.sleep(0.001)

View File

@ -0,0 +1,59 @@
import math
from galactic import GalacticUnicorn
graphics = None
palette = None
width = GalacticUnicorn.WIDTH
height = GalacticUnicorn.HEIGHT
@micropython.native # noqa: F821
def from_hsv(h, s, v):
i = math.floor(h * 6.0)
f = h * 6.0 - i
v *= 255.0
p = v * (1.0 - s)
q = v * (1.0 - f * s)
t = v * (1.0 - (1.0 - f) * s)
i = int(i) % 6
if i == 0:
return int(v), int(t), int(p)
if i == 1:
return int(q), int(v), int(p)
if i == 2:
return int(p), int(v), int(t)
if i == 3:
return int(p), int(q), int(v)
if i == 4:
return int(t), int(p), int(v)
if i == 5:
return int(v), int(p), int(q)
phase = 0
hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)]
hue_offset = 0.0
stripe_width = 3.0
speed = 5.0
def init():
pass
@micropython.native # noqa: F821
def draw():
global hue_offset, phase
phase += speed
phase_percent = phase / 15
for x in range(width):
colour = hue_map[int((x + (hue_offset * width)) % width)]
for y in range(height):
v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5)
graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v)))
graphics.pixel(x, y)

View File

@ -0,0 +1,100 @@
import time
graphics = None
palette = None
c64 = [
" ",
" ",
" OOOOO OOOOOO OO OOOO OO OO XXXXXXX ",
" OO OO OO OOOO OO OO OO OO XXXXXXX ",
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
" OOOOO OOOO OOOOOO OO OO OOOO XXXXXXX ",
" OOOO OO OO OO OO OO OO XXXXXXX ",
" OO OO OO OO OO OO OO OO OO XXXXXXX ",
" OO OO OOOOOO OO OO OOOO OO OO XXXXXXX ",
" XXXXXXX ",
" "
]
FOREGROUND_C64 = (230, 210, 250)
BACKGROUND_C64 = (20, 20, 120)
spectrum = [
" ",
" ",
" O OOOO OOOO OOOOO O O O O XXXXXXXX ",
" O O O O O O O O O O O X XXXXXX ",
" O O O O O O O X XXXXXX ",
" O O O OOOOOO O O X XXXXXX ",
" O O O O O O O X XXXXXX ",
" OOOOOO OOOO O O OOOOO X XXXXXX ",
" X X ",
" XXXXXXXX ",
" "
]
FOREGROUND_SPECTRUM = (0, 0, 0)
BACKGROUND_SPECTRUM = (180, 150, 150)
bbc_micro = [
" ",
" ",
" OOOOO OO OOOO OOO OOOO O ",
" O O O O O O O O O O ",
" O O O O O O O O ",
" OOOOO O O OOOO O O O ",
" O O OOOOOO O O O O ",
" O O O O O O O O O O ",
" OOOOO O O OOOO OOO OOOO O ",
" XXXXXXX ",
" "
]
FOREGROUND_BBC_MICRO = (255, 255, 255)
BACKGROUND_BBC_MICRO = (0, 0, 0)
PROMPT_C64 = 0
PROMPT_SPECTRUM = 1
PROMPT_BBC_MICRO = 2
prompt = 0
def init():
pass
@micropython.native # noqa: F821
def draw():
time_ms = time.ticks_ms()
prompt = (time_ms // 3000) % 3
if prompt == PROMPT_C64:
image = c64
fg = FOREGROUND_C64
bg = BACKGROUND_C64
elif prompt == PROMPT_SPECTRUM:
image = spectrum
fg = FOREGROUND_SPECTRUM
bg = BACKGROUND_SPECTRUM
elif prompt == PROMPT_BBC_MICRO:
image = bbc_micro
fg = FOREGROUND_BBC_MICRO
bg = BACKGROUND_BBC_MICRO
fg_pen = graphics.create_pen(fg[0], fg[1], fg[2])
bg_pen = graphics.create_pen(bg[0], bg[1], bg[2])
for y in range(len(image)):
row = image[y]
for x in range(len(row)):
pixel = row[x]
# draw the prompt text
if pixel == 'O':
graphics.set_pen(fg_pen)
# draw the caret blinking
elif pixel == 'X' and (time_ms // 300) % 2:
graphics.set_pen(fg_pen)
else:
graphics.set_pen(bg_pen)
graphics.pixel(x, y)

Some files were not shown because too many files have changed in this diff Show More