Initial commit.
167
.github/workflows/micropython.yml
vendored
Normal 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
@ -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
@ -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.
|
||||
84
examples/c++/cosmic_unicorn/CMakeLists.txt
Normal 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)
|
||||
|
||||
7847
examples/c++/cosmic_unicorn/audio_samples.cpp
Normal 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;
|
||||
}
|
||||
115
examples/c++/cosmic_unicorn/cosmic_fire_effect.cpp
Normal 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;
|
||||
}
|
||||
148
examples/c++/cosmic_unicorn/cosmic_lava_lamp.cpp
Normal 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;
|
||||
}
|
||||
184
examples/c++/cosmic_unicorn/cosmic_rainbow.cpp
Normal 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;
|
||||
}
|
||||
102
examples/c++/cosmic_unicorn/cosmic_rainbow_text.cpp
Normal 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;
|
||||
}
|
||||
76
examples/c++/cosmic_unicorn/cosmic_scroll_text.cpp
Normal 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;
|
||||
}
|
||||
125
examples/c++/galactic_unicorn/CMakeLists.txt
Normal 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)
|
||||
7847
examples/c++/galactic_unicorn/audio_samples.cpp
Normal file
87
examples/c++/galactic_unicorn/balls.cpp
Normal 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;
|
||||
}
|
||||
68
examples/c++/galactic_unicorn/eighties_super_computer.cpp
Normal 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;
|
||||
}
|
||||
164
examples/c++/galactic_unicorn/feature_test.cpp
Normal 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;
|
||||
}
|
||||
122
examples/c++/galactic_unicorn/fire_effect.cpp
Normal 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;
|
||||
}
|
||||
149
examples/c++/galactic_unicorn/lava_lamp.cpp
Normal 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;
|
||||
}
|
||||
122
examples/c++/galactic_unicorn/nostalgia_prompt.cpp
Normal 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;
|
||||
}
|
||||
684
examples/c++/galactic_unicorn/okcolor.hpp
Normal 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
|
||||
200
examples/c++/galactic_unicorn/rainbow.cpp
Normal 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;
|
||||
}
|
||||
103
examples/c++/galactic_unicorn/rainbow_text.cpp
Normal 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;
|
||||
}
|
||||
68
examples/c++/galactic_unicorn/scroll_text.cpp
Normal 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;
|
||||
}
|
||||
176
examples/micropython/cosmic_unicorn/README.md
Normal 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)
|
||||
128
examples/micropython/cosmic_unicorn/cheerlights_history.py
Normal 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)
|
||||
229
examples/micropython/cosmic_unicorn/clock.py
Normal 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)
|
||||
11
examples/micropython/cosmic_unicorn/cosmic_paint/README.md
Normal 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!
|
||||
113
examples/micropython/cosmic_unicorn/cosmic_paint/cosmic_paint.py
Normal 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)
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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))
|
||||
148
examples/micropython/cosmic_unicorn/exchange_ticker.py
Normal 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)
|
||||
123
examples/micropython/cosmic_unicorn/feature_test.py
Normal 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)
|
||||
347
examples/micropython/cosmic_unicorn/feature_test_with_audio.py
Normal 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)
|
||||
102
examples/micropython/cosmic_unicorn/fire_effect.py
Normal 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)
|
||||
198
examples/micropython/cosmic_unicorn/http_text/html_text.py
Normal 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()
|
||||
102
examples/micropython/cosmic_unicorn/http_text/index.html
Normal 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>
|
||||
80
examples/micropython/cosmic_unicorn/launch/fire.py
Normal 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")
|
||||
113
examples/micropython/cosmic_unicorn/launch/main.py
Normal 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)
|
||||
59
examples/micropython/cosmic_unicorn/launch/rainbow.py
Normal 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)
|
||||
40
examples/micropython/cosmic_unicorn/launch/supercomputer.py
Normal 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)
|
||||
100
examples/micropython/cosmic_unicorn/launch/today.py
Normal 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))
|
||||
151
examples/micropython/cosmic_unicorn/lava_lamp.py
Normal 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))
|
||||
131
examples/micropython/cosmic_unicorn/melody_maker.py
Normal 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)
|
||||
158
examples/micropython/cosmic_unicorn/nostalgia_prompt.py
Normal 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))
|
||||
@ -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)
|
||||
126
examples/micropython/cosmic_unicorn/numpy/fire_effect.py
Normal 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)
|
||||
118
examples/micropython/cosmic_unicorn/numpy/lava_lamp.py
Normal 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)
|
||||
145
examples/micropython/cosmic_unicorn/numpy/life.py
Normal 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)
|
||||
95
examples/micropython/cosmic_unicorn/numpy/rgb_channels.py
Normal 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)
|
||||
89
examples/micropython/cosmic_unicorn/numpy/the_matrix.py
Normal 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)
|
||||
131
examples/micropython/cosmic_unicorn/numpy/this_is_fine.py
Normal 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)
|
||||
97
examples/micropython/cosmic_unicorn/numpy/trippy.py
Normal 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)
|
||||
115
examples/micropython/cosmic_unicorn/rainbow.py
Normal 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))
|
||||
96
examples/micropython/cosmic_unicorn/scrolling_text.py
Normal 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)
|
||||
128
examples/micropython/cosmic_unicorn/today.py
Normal 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()
|
||||
BIN
examples/micropython/cosmic_unicorn/weather/icons/cloud1.jpg
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/cloud2.jpg
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/cloud3.jpg
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/cloud4.jpg
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/rain1.jpg
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/rain2.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/rain3.jpg
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/rain4.jpg
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/snow1.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/snow2.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/snow3.jpg
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/snow4.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/storm1.jpg
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/storm2.jpg
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/storm3.jpg
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/storm4.jpg
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/sun1.jpg
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/sun2.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/sun3.jpg
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
examples/micropython/cosmic_unicorn/weather/icons/sun4.jpg
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 81 KiB |
153
examples/micropython/cosmic_unicorn/weather/weather.py
Normal 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) + "¤t_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)
|
||||
155
examples/micropython/galactic_unicorn/README.md
Normal 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)
|
||||
63
examples/micropython/galactic_unicorn/cheerlights.py
Normal 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)
|
||||
129
examples/micropython/galactic_unicorn/cheerlights_history.py
Normal 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)
|
||||
230
examples/micropython/galactic_unicorn/clock.py
Normal 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)
|
||||
102
examples/micropython/galactic_unicorn/co2.py
Normal 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)
|
||||
@ -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))
|
||||
123
examples/micropython/galactic_unicorn/feature_test.py
Normal 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)
|
||||
359
examples/micropython/galactic_unicorn/feature_test_with_audio.py
Normal 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)
|
||||
145
examples/micropython/galactic_unicorn/fire_effect.py
Normal 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))
|
||||
@ -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!
|
||||
@ -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)
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
80
examples/micropython/galactic_unicorn/launch/fire.py
Normal 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")
|
||||
113
examples/micropython/galactic_unicorn/launch/main.py
Normal 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)
|
||||
59
examples/micropython/galactic_unicorn/launch/rainbow.py
Normal 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)
|
||||
100
examples/micropython/galactic_unicorn/launch/retroprompt.py
Normal 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)
|
||||