├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── bitfonts.py ├── cheerlights_history.py ├── clapper.py ├── clock.py ├── common ├── README.md ├── WIFI_CONFIG.py └── network_manager.py ├── cosmic_paint ├── README.md ├── cosmic_paint.py └── cosmic_paint │ ├── index.html │ └── static │ ├── paint.css │ ├── paint.js │ └── tinycolor.js ├── custom_font.py ├── eighties_super_computer.py ├── exchange_ticker.py ├── feature_test.py ├── feature_test_with_audio.py ├── fire_effect.py ├── http_text ├── html_text.py └── index.html ├── launch ├── fire.py ├── main.py ├── rainbow.py ├── supercomputer.py └── today.py ├── lava_lamp.py ├── melody_maker.py ├── nostalgia_prompt.py ├── numpy ├── eighties_super_computer.py ├── fire_effect.py ├── lava_lamp.py ├── life.py ├── rgb_channels.py ├── the_matrix.py ├── this_is_fine.py └── trippy.py ├── rainbow.py ├── ready_set_go.py ├── scrolling_text.py ├── strobe.py ├── today.py ├── traffic-signal.py ├── train_clock.py └── weather ├── icons ├── cloud1.jpg ├── cloud2.jpg ├── cloud3.jpg ├── cloud4.jpg ├── rain1.jpg ├── rain2.jpg ├── rain3.jpg ├── rain4.jpg ├── snow1.jpg ├── snow2.jpg ├── snow3.jpg ├── snow4.jpg ├── storm1.jpg ├── storm2.jpg ├── storm3.jpg ├── storm4.jpg ├── sun1.jpg ├── sun2.jpg ├── sun3.jpg └── sun4.jpg ├── icons_sourcefile(doesn't need copying).svg └── weather.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 105 | __pypackages__/ 106 | 107 | # Celery stuff 108 | celerybeat-schedule 109 | celerybeat.pid 110 | 111 | # SageMath parsed files 112 | *.sage.py 113 | 114 | # Environments 115 | .env 116 | .venv 117 | env/ 118 | venv/ 119 | ENV/ 120 | env.bak/ 121 | venv.bak/ 122 | 123 | # Spyder project settings 124 | .spyderproject 125 | .spyproject 126 | 127 | # Rope project settings 128 | .ropeproject 129 | 130 | # mkdocs documentation 131 | /site 132 | 133 | # mypy 134 | .mypy_cache/ 135 | .dmypy.json 136 | dmypy.json 137 | 138 | # Pyre type checker 139 | .pyre/ 140 | 141 | # pytype static type analyzer 142 | .pytype/ 143 | 144 | # Cython debug symbols 145 | cython_debug/ 146 | 147 | # PyCharm 148 | # JetBrains specific template is maintainted in a separate JetBrains.gitignore that can 149 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 150 | # and can be added to the global gitignore or merged into this file. For a more nuclear 151 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 152 | #.idea/ 153 | 154 | # Mac 155 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Kieran Farr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cosmic Unicorn Projects 2 | 3 | MicroPython demos of custom bitmap fonts, a train-themed "Ok to Wake" clock, and more for the [Pimoroni Cosmic Unicorn LED Matrix with built-in Raspberry Pi Pico W 2040](https://shop.pimoroni.com/products/cosmic-unicorn). 4 | 5 | Table of Contents: 6 | * [Custom Tiny Bitmap Fonts](#custom-tiny-bitmap-fonts-for-cosmic-unicorn) 7 | * [Ok to Wake Train Clock](#ok-to-wake-train-clock) 8 | * Traffic Signals 9 | 10 | ## Custom (Tiny) Bitmap Fonts for Cosmic Unicorn 11 | 12 | ### Why? 13 | 14 | The default bitmap fonts that come PicoGraphics library are awesome, but sometimes you need a smaller font to display lots of numbers and text on LED matrices such as the Cosmic Unicorn. Further, some PicoGraphics fonts are variable width which cause display issues for timers and clocks. 15 | 16 | ### What does it look like? 17 | LEFT: default Cosmic Unicorn bitmap fonts, RIGHT: new fonts available via this bitfonts library 18 | 19 | 20 | ### Usage Example 21 | 1. Upload `bitfonts.py` file to the Pico W 22 | 2. Import in your MicroPython .py project as follows (only import fonts you need for your project) 23 | ``` 24 | from bitfonts import BitFont, font2x5, font3x5, font4x5, font5x9 25 | ``` 26 | 27 | 3. Then, initialise the `bitfont` Class immediately after you initialize `PicoGraphics`: 28 | ``` 29 | # INITIALISE GALACTIC UNICORN 30 | cu = CosmicUnicorn() 31 | graphics = PicoGraphics(DISPLAY) 32 | bitfont = BitFont(graphics) 33 | ``` 34 | 35 | 4. Finally, use the draw_text method to draw the font 36 | ``` 37 | bitfont.draw_text("my text string",0,16,font3x5) 38 | ``` 39 | 40 | See [this demo project custom_font.py](https://github.com/kfarr/cosmic-unicorn-playground/blob/main/custom_font.py) for an example of a working demo of the custom fonts used on the Cosmic Unicorn. 41 | 42 | ### Method variables 43 | 44 | Function `draw_text` accepts a `str` of the text to be displayed, an `int` of the `x` position (where 0 = leftmost), an `int` of the `y` position (where 0 = topmost), and a `dict` font array as defined in `bitfonts.py` file [such as this 2x5 font](https://github.com/kfarr/cosmic-unicorn-playground/blob/main/bitfonts.py#L37). 45 | 46 | ### ⚠️ WARNING - only numbers 0-9 are supported across all fonts 47 | 48 | ONLY `0 1 2 3 4 5 6 7 8 9` characters are supported across all fonts. Some fonts include all alphanumeric characters. Some fonts include delimiters and limited emojis. 49 | 50 | When using in your project, first refer to [the source code to see which characaters are supported](https://github.com/kfarr/cosmic-unicorn-playground/blob/main/bitfonts.py#L31). It is very easy to add your own characters. Then, please contribute them back to this repo! :) 51 | 52 | ### Credits 53 | * Thanks to https://github.com/NiVZ78/galactic_unicorn_custom_font for improved bitmap font parser and definition format 54 | * Thanks to https://github.com/leswright1977/picofont for 5x9 custom bitmap font example 55 | * This repo is originally forked from Pimoroni's [Cosmic Unicorn MicroPython Examples](https://github.com/pimoroni/pimoroni-pico/tree/main/micropython/examples/cosmic_unicorn) 56 | 57 | ## Ok to Wake Train Clock 58 | 59 | To be written. See twitter for now: https://twitter.com/kfarr/status/1663203677828694022 and this file: https://github.com/kfarr/cosmic-unicorn-playground/blob/main/train_clock.py 60 | 61 | === 62 | Original readme: 63 | 64 | - [About Cosmic Unicorn](#about-cosmic-unicorn) 65 | - [Cosmic Unicorn and PicoGraphics](#cosmic-unicorn-and-picographics) 66 | - [Examples](#examples) 67 | - [Clock](#clock) 68 | - [Eighties Super Computer](#eighties-super-computer) 69 | - [Feature Test](#feature-test) 70 | - [Feature Test With Audio](#feature-test-with-audio) 71 | - [Fire Effect](#fire-effect) 72 | - [Lava Lamp](#lava-lamp) 73 | - [Nostalgia Prompt](#nostalgia-prompt) 74 | - [Rainbow](#rainbow) 75 | - [Scrolling Text](#scrolling-text) 76 | - [Today](#today) 77 | - [Wireless Examples](#wireless-examples) 78 | - [Cheerlights History](#cheerlights-history) 79 | - [Cosmic Paint](#cosmic-paint) 80 | - [Exchange Ticker](#exchange-ticker) 81 | - [HTTP Text](#http-text) 82 | - [Weather](#weather) 83 | - [NumPy examples](#numpy-examples) 84 | - [Other Examples](#other-examples) 85 | - [Launch (Demo Reel)](#launch-demo-reel) 86 | - [Other Resources](#other-resources) 87 | 88 | ## About Cosmic Unicorn 89 | 90 | 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! 91 | 92 | - :link: [Cosmic Unicorn store page](https://shop.pimoroni.com/products/cosmic-unicorn) 93 | 94 | 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). 95 | 96 | - [MicroPython releases](https://github.com/pimoroni/pimoroni-pico/releases) 97 | - [Installing MicroPython](../../../setting-up-micropython.md) 98 | 99 | ## Cosmic Unicorn and PicoGraphics 100 | 101 | 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. 102 | 103 | - [Cosmic Unicorn function reference](../../modules/cosmic_unicorn/README.md) 104 | - [PicoGraphics function reference](../../modules/picographics/README.md) 105 | 106 | ## Examples 107 | 108 | ### Clock 109 | 110 | [clock.py](clock.py) 111 | 112 | Clock example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the time by pressing A. 113 | 114 | ### Eighties Super Computer 115 | 116 | [eighties_super_computer.py](eighties_super_computer.py) 117 | 118 | 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 -. 119 | 120 | ### Feature Test 121 | 122 | [feature_test.py](feature_test.py) 123 | 124 | Displays some text, gradients and colours and demonstrates button use. You can adjust the brightness with LUX + and -. 125 | 126 | ### Feature Test With Audio 127 | 128 | [feature_test_with_audio.py](feature_test_with_audio.py) 129 | 130 | Displays some text, gradients and colours and demonstrates button use. Also demonstrates some of the audio / synth features. 131 | - Button A plays a synth tune 132 | - Button B plays a solo channel of the synth tune 133 | - Button C plays a sinewave (it's frequency can be adjusted with VOL + and -) 134 | - Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -) 135 | - Sleep button stops the sounds 136 | 137 | ### Fire Effect 138 | 139 | [fire_effect.py](fire_effect.py) 140 | 141 | 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 -. 142 | 143 | ### Lava Lamp 144 | 145 | [lava_lamp.py](lava_lamp.py) 146 | 147 | A 70s-tastic, procedural rainbow lava lamp. You can adjust the brightness with LUX + and -. 148 | 149 | ### Nostalgia Prompt 150 | 151 | [nostalgia_prompt.py](nostalgia_prompt.py) 152 | 153 | 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 -. 154 | 155 | ### Rainbow 156 | 157 | [rainbow.py](rainbow.py) 158 | 159 | 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). 160 | 161 | ### Scrolling Text 162 | 163 | [scrolling_text.py](scrolling_text.py) 164 | 165 | Display scrolling wisdom, quotes or greetz. You can adjust the brightness with LUX + and -. 166 | 167 | ### Today 168 | 169 | [today.py](today.py) 170 | 171 | Calendar example with (optional) NTP synchronization. You can adjust the brightness with LUX + and -, and resync the date by pressing C. 172 | 173 | ## Wireless Examples 174 | 175 | 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). 176 | 177 | - [micropython/examples/common](../../examples/common) 178 | 179 | ### Cheerlights History 180 | 181 | [cheerlights_history.py](cheerlights_history.py) 182 | 183 | 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/ 184 | 185 | You can adjust the brightness with LUX + and -. 186 | 187 | ### Cosmic Paint 188 | 189 | [cosmic_paint](cosmic_paint) 190 | 191 | Draw on your Cosmic Unicorn from another device in real time, over wifi! 192 | 193 | This example needs the `micropython-phew` and `microdot` libraries (you can install these using Thonny's 'Tools > Manage Packages'). 194 | 195 | ### Exchange Ticker 196 | 197 | [exchange_ticker.py](exchange_ticker.py) 198 | 199 | This example uses the Coinbase open API to collect the current exchange rates of various cryptocurrencies. 200 | 201 | Press A to change to a different base exchange currency. 202 | 203 | ### HTTP Text 204 | 205 | [http_text](http_text) 206 | 207 | Display scrolling wisdom, quotes or greetz... from another computer or device! 208 | 209 | You can adjust the brightness with LUX + and -. 210 | 211 | 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`. 212 | 213 | ### Weather 214 | 215 | [weather](weather) 216 | 217 | Display current weather data from the [Open-Meteo](https://open-meteo.com/) weather API. 218 | 219 | ## NumPy examples 220 | 221 | [numpy](numpy) 222 | 223 | The examples in the folder use `numpy`-like array functions contained in the `ulab` library for super fast graphical effects. 224 | 225 | ## Other Examples 226 | 227 | ### Launch (Demo Reel) 228 | 229 | [launch](launch) 230 | 231 | 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. 232 | 233 | ## Other Resources 234 | 235 | 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. 236 | 237 | - :link: [Green Energy Display with Cosmic Unicorn](https://www.hackster.io/andreas-motzek/clock-and-green-energy-display-with-cosmic-unicorn-641dcb) 238 | -------------------------------------------------------------------------------- /bitfonts.py: -------------------------------------------------------------------------------- 1 | class BitFont: 2 | def __init__(self, graphics): 3 | self.graphics = graphics 4 | 5 | # DRAW A SINGLE BITMAP FONT CHARACTER 6 | @micropython.native # noqa: F821 7 | def draw_char(self,d,x,y,f): 8 | # LOOP THROUGH ROWS 9 | for i in range(f[d]["h"]): 10 | # LOOP THROUGH COLUMNS 11 | for j in range(f[d]["w"]): 12 | # IF THIS BIT IS SET THEN DRAW A PIXEL 13 | if f[d]["data"] & (0b1 << ((i*f[d]["w"])+j)): 14 | self.graphics.pixel(f[d]["w"]-1-j+x,f[d]["h"]-1-i+y) 15 | 16 | # DRAW A STRING WITH BITMAP FONT 17 | @micropython.native # noqa: F821 18 | def draw_text(self,s,x,y,f,d=1): 19 | # LEFT JUSTIFIED 20 | if d == 1: 21 | for i in range(len(s)): 22 | self.draw_char(s[i],x,y,f) 23 | x += f[s[i]]["w"] + f[s[i]]["s"] 24 | else: 25 | # RIGHT JUSTIFIED 26 | for i in reversed(range(len(s))): 27 | x -= f[s[i]]["w"] 28 | self.draw_char(s[i],x,y,f) 29 | x -= f[s[i]]["s"] 30 | 31 | # FONT DATA 32 | # x,y are unused - could be used for drawing above below line 33 | # w, h are width and height 34 | # s is spacing 35 | # data is binary bit representation of pixels, starting at top left 36 | 37 | font2x5 = { 38 | " ": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0000000000}, 39 | "!": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0101010001}, 40 | "'": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0100000000}, 41 | ",": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0000010110}, 42 | "-": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0000110000}, 43 | ".": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0000000010}, 44 | "0": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1111111111}, 45 | "1": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0101010101}, 46 | "2": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1101111011}, 47 | "3": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1101110111}, 48 | "4": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1010110101}, 49 | "5": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1110110111}, 50 | "6": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1010111111}, 51 | "7": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1101010101}, 52 | "8": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1111001111}, 53 | "9": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1111110101}, 54 | ":": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0001000100}, 55 | ";": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0001000110}, 56 | "<": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0001100100}, 57 | "=": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0011001100}, 58 | ">": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b0010011000}, 59 | "?": {"x": 0, "y": 0, "w": 2, "h": 5, "s": 1, "data": 0b1101111000}, 60 | } 61 | 62 | font3x5 = { 63 | " ": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b000000000000000}, 64 | "🚶": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b010110111010101}, 65 | "✋": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b000010110111110}, 66 | "!": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b010010010000010}, 67 | "0": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111101101101111}, 68 | "1": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b110010010010111}, 69 | "2": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111001111100111}, 70 | "3": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111001011001111}, 71 | "4": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101111001001}, 72 | "5": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111100111001111}, 73 | "6": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111100111101111}, 74 | "7": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111001010010010}, 75 | "8": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111101111101111}, 76 | "9": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111101111001111}, 77 | "A": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b010101111101101}, 78 | "B": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b110101110101110}, 79 | "C": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b011100100100011}, 80 | "D": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b110101101101110}, 81 | "E": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111100110100111}, 82 | "F": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111100110100100}, 83 | "G": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b011100101101011}, 84 | "H": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101111101101}, 85 | "I": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111010010010111}, 86 | "J": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b011001001101010}, 87 | "K": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101110101101}, 88 | "L": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b100100100100111}, 89 | "M": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101111111101101}, 90 | "N": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111101101101101}, 91 | "O": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111101101101111}, 92 | "P": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b110101110100100}, 93 | "Q": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b010101101110011}, 94 | "R": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b110101110101101}, 95 | "S": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b011100010001110}, 96 | "T": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111010010010010}, 97 | "U": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101101101111}, 98 | "V": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101101101010}, 99 | "W": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101111111101}, 100 | "X": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101010101101}, 101 | "Y": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b101101111001111}, 102 | "Z": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111001010100111}, 103 | "-": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b000000111000000}, 104 | ":": {"x":0,"y":0,"w":1,"h":5,"s":1,"data":0b01010}, 105 | ".": {"x":0,"y":0,"w":1,"h":5,"s":1,"data":0b00001} 106 | } 107 | 108 | 109 | font4x5 = { 110 | "0": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01101001100110010110}, 111 | "1": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b110010010010111}, 112 | "2": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11100001011110001111}, 113 | "3": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11100001011000011110}, 114 | "4": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10011001011100010001}, 115 | "5": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11111000111000011110}, 116 | "6": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01111000111010010110}, 117 | "7": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11110001001001000100}, 118 | "8": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01101001011010010110}, 119 | "9": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01101001011100011110}, 120 | "A": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01101001100111111001}, 121 | "B": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11101001111010011110}, 122 | "C": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01111000100010000111}, 123 | "D": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11101001100110011110}, 124 | "E": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11111000111010001111}, 125 | "F": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11111000111010001000}, 126 | "G": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01111000101110010111}, 127 | "H": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10011001111110011001}, 128 | "I": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111010010010111}, 129 | "J": {"x":0,"y":0,"w":3,"h":5,"s":1,"data":0b111001001001110}, 130 | "K": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10011010110010101001}, 131 | "L": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10001000100010001111}, 132 | "M": {"x":0,"y":0,"w":5,"h":5,"s":1,"data":0b1000111011101011000110001}, 133 | "N": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10011101101110011001}, 134 | "O": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01101001100110010110}, 135 | "P": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11101001100111101000}, 136 | "Q": {"x":0,"y":0,"w":5,"h":5,"s":1,"data":0b0110010010100101011001111}, 137 | "R": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11101001100111101001}, 138 | "S": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b01111000011000011110}, 139 | "T": {"x":0,"y":0,"w":5,"h":5,"s":1,"data":0b1111100100001000010000100}, 140 | "U": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10011001100110010110}, 141 | "V": {"x":0,"y":0,"w":5,"h":5,"s":1,"data":0b1000110001100010101000100}, 142 | "X": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10011001011010011001}, 143 | "Y": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b10011001011100011110}, 144 | "Z": {"x":0,"y":0,"w":4,"h":5,"s":1,"data":0b11110001011010001111}, 145 | } 146 | 147 | 148 | font5x9 = { 149 | " ": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000000000000000000000000000}, 150 | "!": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100001000010000100001000000000100}, 151 | '"': {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01010010100000000000000000000000000}, 152 | "#": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01010010101101100000110110101001010}, 153 | "$": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100011111000001110000011111000100}, 154 | "%": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11001110010001000100010001001110011}, 155 | "&": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01000101001010001000101011001001101}, 156 | "'": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10000100001000000000000000000000000}, 157 | "(": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100010001000010000100000100000100}, 158 | ")": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100000100000100001000010001000100}, 159 | "*": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000001001010101110101010010000000}, 160 | "+": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000001000010011111001000010000000}, 161 | ",": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000000000000110000100010000}, 162 | "-": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000000011111000000000000000}, 163 | ".": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000000000000000001100011000}, 164 | "/": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00001000010001000100010001000010000}, 165 | "0": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000110001100011000101110}, 166 | "1": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100011000010000100001000010001110}, 167 | "2": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100010000101110100001000011111}, 168 | "3": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100010000101110000011000101110}, 169 | "4": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00010001100101011111000100001000010}, 170 | "5": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11111100001111000001000011000101110}, 171 | "6": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100001000011110100011000101110}, 172 | "7": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11111000010001000100010001000010000}, 173 | "8": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000101110100011000101110}, 174 | "9": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000101111000010000101110}, 175 | ":": {"x":0,"y":0,"w":2,"h":9,"s":1,"data":0b00111100111100}, 176 | ";": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01100011000000001100011000010001000}, 177 | "<": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00010001000100010000010000010000010}, 178 | "=": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001111100000111110000000000}, 179 | ">": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01000001000001000001000100010001000}, 180 | "?": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01100100100001000100001000000000100}, 181 | "@": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100010000101101101011010101110}, 182 | "A": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100010101000110001111111000110001}, 183 | "B": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11110010010100111110010010100111110}, 184 | "C": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000010000100001000101110}, 185 | "D": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11110010010100101001010010100111110}, 186 | "E": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11111100001000011100100001000011111}, 187 | "F": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11111100001000011100100001000010000}, 188 | "G": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000010111100011000101110}, 189 | "H": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001100011000111111100011000110001}, 190 | "I": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110001000010000100001000010001110}, 191 | "J": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00111000100001000010000101001001100}, 192 | "K": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001100101010011000101001001010001}, 193 | "L": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10000100001000010000100001000011111}, 194 | "M": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001110111010110101100011000110001}, 195 | "N": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001110011010110011100011000110001}, 196 | "O": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000110001100011000101110}, 197 | "P": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11110100011000111110100001000010000}, 198 | "Q": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000110001101011001001101}, 199 | "R": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11110100011000111110101001001010001}, 200 | "S": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000001110000011000101110}, 201 | "T": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11111001000010000100001000010000100}, 202 | "U": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001100011000110001100011000101110}, 203 | "V": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001100011000101010010100010000100}, 204 | "W": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001100011000110101101011101110001}, 205 | "X": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001100010101000100010101000110001}, 206 | "Y": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10001100010101000100001000010000100}, 207 | "Z": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11111000010001000100010001000011111}, 208 | "[": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110010000100001000010000100001110}, 209 | "\"": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10000100000100000100000100000100001}, 210 | "]": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00111000010000100001000010000100111}, 211 | "^": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100010101000100000000000000000000}, 212 | "_": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000000000000000000000011111}, 213 | "`": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b11000110001000001000000000000000000}, 214 | "a": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000111000001011111000101110}, 215 | "b": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10000100001011011001100011100110110}, 216 | "c": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000011101000010000100000111}, 217 | "d": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00001000010110110011100011001101101}, 218 | "e": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000111010001111111000001110}, 219 | "f": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00110010010100011110010000100001000}, 220 | "g": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000110001011110000101110}, 221 | "h": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10000100001011011001100011000110001}, 222 | "i": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100000000110000100001000010001110}, 223 | "j": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000001100001000010000101001001100}, 224 | "k": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b10000100001001010100110001010010010}, 225 | "l": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01100001000010000100001000010001110}, 226 | "m": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001101010101101011010110101}, 227 | "n": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001011011001100011000110001}, 228 | "o": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000111010001100011000101110}, 229 | "p": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000110001111101000010000}, 230 | "q": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01110100011000110001011110000100001}, 231 | "r": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001011011001100001000010000}, 232 | "s": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000000111110000011100000111110}, 233 | "t": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100001000111100100001000010000111}, 234 | "u": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001000110001100011001101101}, 235 | "v": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001000110001100010101000100}, 236 | "w": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001000110001101011010101010}, 237 | "x": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001000101010001000101010001}, 238 | "y": {"x":0,"y":0,"w":5,"h":10,"s":1,"data":0b0000000000100011000110001011110000101110}, 239 | "z": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00000000001111100010001000100011111}, 240 | "{": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00010001000010001000001000010000010}, 241 | "|": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b00100001000010000000001000010000100}, 242 | "}": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01000001000010000010001000010001000}, 243 | "~": {"x":0,"y":0,"w":5,"h":9,"s":1,"data":0b01000101010001000000000000000000000}, 244 | } 245 | -------------------------------------------------------------------------------- /cheerlights_history.py: -------------------------------------------------------------------------------- 1 | # This Cosmic Unicorn example updates a pixel every two(ish) minutes 2 | # to display the most recent #cheerlights colour. Discover the most popular 3 | # colours over time, or use it as an avant garde (but colourful) 32 hour clock! 4 | # Find out more about the Cheerlights API at https://cheerlights.com/ 5 | # 6 | # To run this example you'll need WIFI_CONFIG.py and network_manager.py from 7 | # the pimoroni-pico micropython/examples/common folder 8 | 9 | import WIFI_CONFIG 10 | from network_manager import NetworkManager 11 | import uasyncio 12 | import urequests 13 | import time 14 | from machine import Timer, Pin 15 | from cosmic import CosmicUnicorn 16 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 17 | 18 | URL = 'http://api.thingspeak.com/channels/1417/field/2/last.json' 19 | 20 | UPDATE_INTERVAL = 113 # refresh interval in secs. Be nice to free APIs! 21 | # this esoteric number is used so that a column of LEDs equates (approximately) to an hour 22 | 23 | 24 | def status_handler(mode, status, ip): 25 | # reports wifi connection status 26 | print(mode, status, ip) 27 | print('Connecting to wifi...') 28 | if status is not None: 29 | if status: 30 | print('Wifi connection successful!') 31 | else: 32 | print('Wifi connection failed!') 33 | 34 | 35 | def hex_to_rgb(hex): 36 | # converts a hex colour code into RGB 37 | h = hex.lstrip('#') 38 | r, g, b = (int(h[i:i + 2], 16) for i in (0, 2, 4)) 39 | return r, g, b 40 | 41 | 42 | def get_data(): 43 | # open the json file 44 | print(f'Requesting URL: {URL}') 45 | r = urequests.get(URL) 46 | # open the json data 47 | j = r.json() 48 | print('Data obtained!') 49 | r.close() 50 | 51 | # flash the onboard LED after getting data 52 | pico_led.value(True) 53 | time.sleep(0.2) 54 | pico_led.value(False) 55 | 56 | # extract hex colour from the json data 57 | hex = j['field2'] 58 | 59 | # add the new hex colour to the end of the array 60 | colour_array.append(hex) 61 | print(f'Colour added to array: {hex}') 62 | # remove the oldest colour in the array 63 | colour_array.pop(0) 64 | update_leds() 65 | 66 | 67 | def update_leds(): 68 | # light up the LEDs 69 | # this step takes a second, it's doing a lot of hex_to_rgb calculations! 70 | print("Updating LEDs...") 71 | i = 0 72 | for x in range(width): 73 | for y in range(height): 74 | r, g, b = hex_to_rgb(colour_array[i]) 75 | 76 | current_colour = graphics.create_pen(r, g, b) 77 | graphics.set_pen(current_colour) 78 | graphics.pixel(x, y) 79 | i = i + 1 80 | cu.update(graphics) 81 | print("LEDs updated!") 82 | 83 | 84 | cu = CosmicUnicorn() 85 | graphics = PicoGraphics(DISPLAY) 86 | 87 | width = CosmicUnicorn.WIDTH 88 | height = CosmicUnicorn.HEIGHT 89 | 90 | cu.set_brightness(0.5) 91 | 92 | # set up the Pico W's onboard LED 93 | pico_led = Pin('LED', Pin.OUT) 94 | 95 | current_colour = graphics.create_pen(0, 0, 0) 96 | 97 | # set up an list to store the colours 98 | colour_array = ["#000000"] * 1024 99 | 100 | # set up wifi 101 | try: 102 | network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler) 103 | uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK)) 104 | except Exception as e: 105 | print(f'Wifi connection failed! {e}') 106 | 107 | # get the first lot of data 108 | get_data() 109 | 110 | # start timer (the timer will call the function to update our data every UPDATE_INTERVAL) 111 | timer = Timer(-1) 112 | timer.init(period=UPDATE_INTERVAL * 1000, mode=Timer.PERIODIC, callback=lambda t: get_data()) 113 | 114 | while True: 115 | # adjust brightness with LUX + and - 116 | # LEDs take a couple of secs to update, so adjust in big (10%) steps 117 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 118 | cu.adjust_brightness(+0.1) 119 | update_leds() 120 | print(f"Brightness set to {cu.get_brightness()}") 121 | 122 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 123 | cu.adjust_brightness(-0.1) 124 | update_leds() 125 | print(f"Brightness set to {cu.get_brightness()}") 126 | 127 | # pause for a moment (important or the USB serial device will fail) 128 | time.sleep(0.001) 129 | -------------------------------------------------------------------------------- /clapper.py: -------------------------------------------------------------------------------- 1 | # IMPORTS 2 | import time 3 | import machine 4 | import math 5 | from cosmic import CosmicUnicorn 6 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 7 | from bitfonts import BitFont, font2x5, font3x5, font4x5, font5x9 8 | 9 | # INITIALISE GALACTIC UNICORN 10 | cu = CosmicUnicorn() 11 | graphics = PicoGraphics(DISPLAY) 12 | bitfont = BitFont(graphics) 13 | 14 | width = CosmicUnicorn.WIDTH 15 | height = CosmicUnicorn.HEIGHT 16 | 17 | cu.set_brightness(1) 18 | 19 | WHITE = graphics.create_pen(255, 255, 255) 20 | RED = graphics.create_pen(255, 0, 0) 21 | DARK_RED = graphics.create_pen(100, 0, 0) 22 | LIGHT_GRAY = graphics.create_pen(120, 120, 120) 23 | 24 | # create the rtc object 25 | rtc = machine.RTC() 26 | 27 | paused = 0 28 | 29 | # CLAPPER HEADER 30 | @micropython.native # noqa: F821 31 | def clapper_row(y=-1,x_offset=4): 32 | graphics.set_pen(LIGHT_GRAY) 33 | for tie in range(11): 34 | graphics.line(4*tie+x_offset, y+1, 4*tie+2+x_offset, y+1) 35 | graphics.line(4*tie+x_offset+1, y+2, 4*tie+2+x_offset+1, y+2) 36 | graphics.line(4*tie+x_offset+2, y+3, 4*tie+2+x_offset+2, y+3) 37 | 38 | graphics.line(4*tie+x_offset+2, y+5, 4*tie+2+x_offset+2, y+5) 39 | graphics.line(4*tie+x_offset+1, y+6, 4*tie+2+x_offset+1, y+6) 40 | graphics.line(4*tie+x_offset, y+7, 4*tie+2+x_offset, y+7) 41 | 42 | # CLAPPER HEADER 43 | @micropython.native # noqa: F821 44 | def dot_separators(y=12,x_offset=7): 45 | graphics.set_pen(DARK_RED) 46 | for tie in range(3): 47 | graphics.pixel(8*tie+x_offset, y+1) 48 | 49 | year, month, day, wd, hour, minute, second, subsecond = rtc.datetime() 50 | 51 | last_second = second; 52 | last_second_tick = time.ticks_ms(); 53 | 54 | # MAIN LOOP 55 | while True: 56 | year, month, day, wd, hour, minute, second, subsecond = rtc.datetime() 57 | 58 | if last_second != second: 59 | last_second = second 60 | last_second_tick = (time.ticks_diff(time.ticks_ms(), 0)); 61 | 62 | time_ms = time.ticks_ms() 63 | time_ms_since_last_second = str(time.ticks_diff(time_ms, last_second_tick) / 1000) 64 | 65 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 66 | cu.adjust_brightness(+0.01) 67 | 68 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 69 | cu.adjust_brightness(-0.01) 70 | 71 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 72 | graphics.clear() 73 | 74 | text = "✋🚶" 75 | timer_text = "{:02}{:02}{:02}{:02}".format(hour, minute, second, time_ms_since_last_second[2:5]) 76 | date_text = " 07 JUN" 77 | 78 | if paused: 79 | timer_text = "0123456789" 80 | 81 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 82 | text = "A" 83 | 84 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 85 | text = "B" 86 | 87 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 88 | text = "Play" 89 | paused = 0 90 | 91 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 92 | text = "Pause" 93 | paused = 1 94 | 95 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 96 | text = "Louder!" 97 | 98 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 99 | text = "Quieter" 100 | 101 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 102 | text = "Brighter!" 103 | 104 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 105 | text = "Darker" 106 | 107 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 108 | text = "Zzz... zzz..." 109 | 110 | graphics.set_pen(RED) 111 | 112 | bitfont.draw_text(timer_text,0,8,font3x5) 113 | 114 | bitfont.draw_text(str(date_text),0,14,font3x5) 115 | 116 | # bitfont.draw_text(timer_text,0,16,font5x9) 117 | 118 | bitfont.draw_text(text.upper(),6,26,font3x5) 119 | 120 | clapper_row() 121 | dot_separators() 122 | if paused: 123 | graphics.set_pen(graphics.create_pen(255, 0, 0)) 124 | bitfont.draw_char("-",(time_ms // 1000) % 2,21,font3x5) 125 | else: 126 | graphics.set_pen(graphics.create_pen(0, 255, 0)) 127 | bitfont.draw_char("-",(time_ms // 1000) % 2,23,font3x5) 128 | 129 | cu.update(graphics) 130 | 131 | # pause for a moment 132 | # time.sleep(0.1) 133 | 134 | -------------------------------------------------------------------------------- /clock.py: -------------------------------------------------------------------------------- 1 | # Clock example with NTP synchronization 2 | # 3 | # Create a secrets.py with your Wifi details to be able to get the time 4 | # when the Cosmic Unicorn isn't connected to Thonny. 5 | # 6 | # secrets.py should contain: 7 | # WIFI_SSID = "Your WiFi SSID" 8 | # WIFI_PASSWORD = "Your WiFi password" 9 | # 10 | # Clock synchronizes time on start, and resynchronizes if you press the A button 11 | 12 | import time 13 | import math 14 | import machine 15 | import network 16 | import ntptime 17 | from cosmic import CosmicUnicorn 18 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 19 | from bitfonts import BitFont, font5x9 20 | try: 21 | from secrets import WIFI_SSID, WIFI_PASSWORD 22 | wifi_available = True 23 | except ImportError: 24 | print("Create secrets.py with your WiFi credentials to get time from NTP") 25 | wifi_available = False 26 | 27 | 28 | # constant for wakeup hour of green color 29 | WAKEUP_HOUR = 6 30 | UTC_OFFSET_HOUR = -7 31 | 32 | 33 | # create cosmic object and graphics surface for drawing 34 | cu = CosmicUnicorn() 35 | graphics = PicoGraphics(DISPLAY) 36 | bitfont = BitFont(graphics) 37 | 38 | # create the rtc object 39 | rtc = machine.RTC() 40 | 41 | width = CosmicUnicorn.WIDTH 42 | height = CosmicUnicorn.HEIGHT 43 | 44 | # set up some pens to use later 45 | WHITE = graphics.create_pen(255, 255, 255) 46 | BLACK = graphics.create_pen(0, 0, 0) 47 | 48 | def gradient(r, g, b): 49 | for y in range(0, height): 50 | for x in range(0, width): 51 | graphics.set_pen(graphics.create_pen(int((r * (height - y)) / 32), int((g * (height - y)) / 32), int((b * (height - y)) / 32))) 52 | graphics.pixel(x, y) 53 | 54 | 55 | # function for drawing outlined text 56 | def outline_text_custom_font(text, x, y, font): 57 | graphics.set_pen(BLACK) 58 | bitfont.draw_text(text, x - 1, y - 1, font) 59 | bitfont.draw_text(text, x, y - 1, font) 60 | bitfont.draw_text(text, x + 1, y - 1, font) 61 | bitfont.draw_text(text, x - 1, y, font) 62 | bitfont.draw_text(text, x + 1, y, font) 63 | bitfont.draw_text(text, x - 1, y + 1, font) 64 | bitfont.draw_text(text, x, y + 1, font) 65 | bitfont.draw_text(text, x + 1, y + 1, font) 66 | 67 | graphics.set_pen(WHITE) 68 | bitfont.draw_text(text, x, y, font) 69 | 70 | # Connect to wifi and synchronize the RTC time from NTP 71 | def sync_time(): 72 | if not wifi_available: 73 | return 74 | 75 | # Start connection 76 | wlan = network.WLAN(network.STA_IF) 77 | wlan.active(True) 78 | wlan.connect(WIFI_SSID, WIFI_PASSWORD) 79 | 80 | # Wait for connect success or failure 81 | max_wait = 100 82 | while max_wait > 0: 83 | if wlan.status() < 0 or wlan.status() >= 3: 84 | break 85 | max_wait -= 1 86 | print('waiting for connection...') 87 | time.sleep(0.2) 88 | 89 | redraw_display_if_reqd() 90 | cu.update(graphics) 91 | 92 | if max_wait > 0: 93 | print("Connected") 94 | 95 | try: 96 | ntptime.settime() 97 | print("Time set") 98 | except OSError: 99 | pass 100 | 101 | wlan.disconnect() 102 | wlan.active(False) 103 | 104 | 105 | # NTP synchronizes the time to UTC, this allows you to adjust the displayed time 106 | # by one hour increments from UTC by pressing the volume up/down buttons 107 | # 108 | # We use the IRQ method to detect the button presses to avoid incrementing/decrementing 109 | # multiple times when the button is held. 110 | utc_offset = UTC_OFFSET_HOUR 111 | 112 | up_button = machine.Pin(CosmicUnicorn.SWITCH_VOLUME_UP, machine.Pin.IN, machine.Pin.PULL_UP) 113 | down_button = machine.Pin(CosmicUnicorn.SWITCH_VOLUME_DOWN, machine.Pin.IN, machine.Pin.PULL_UP) 114 | 115 | 116 | def adjust_utc_offset(pin): 117 | global utc_offset 118 | if pin == up_button: 119 | utc_offset += 1 120 | if pin == down_button: 121 | utc_offset -= 1 122 | 123 | 124 | up_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset) 125 | down_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset) 126 | 127 | 128 | year, month, day, wd, hour, minute, second, _ = rtc.datetime() 129 | 130 | last_second = second 131 | 132 | 133 | # Check whether the RTC time has changed and if so redraw the display 134 | def redraw_display_if_reqd(): 135 | global year, month, day, wd, hour, minute, second, last_second 136 | 137 | year, month, day, wd, hour, minute, second, _ = rtc.datetime() 138 | if second != last_second: 139 | hour = (hour + utc_offset) % 24 140 | 141 | if hour == WAKEUP_HOUR: 142 | gradient(0,255,0) 143 | elif hour > WAKEUP_HOUR and hour < WAKEUP_HOUR + 10: 144 | gradient(255,255,0) 145 | else: 146 | gradient(255,0,0) 147 | 148 | 149 | clock = "{:02}:{:02}".format(hour, minute) 150 | 151 | 152 | outline_text_custom_font(clock, 3, 2, font5x9) 153 | 154 | last_second = second 155 | 156 | 157 | # set the font 158 | graphics.set_font("bitmap8") 159 | cu.set_brightness(0.5) 160 | 161 | sync_time() 162 | 163 | while True: 164 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 165 | cu.adjust_brightness(+0.01) 166 | 167 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 168 | cu.adjust_brightness(-0.01) 169 | 170 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 171 | sync_time() 172 | 173 | redraw_display_if_reqd() 174 | 175 | # update the display 176 | cu.update(graphics) 177 | 178 | time.sleep(0.01) 179 | -------------------------------------------------------------------------------- /common/README.md: -------------------------------------------------------------------------------- 1 | # Common Files For Pico Examples 2 | 3 | These files are compiled to .mpy to save RAM. You can find the original .py sources and associated licenses from: 4 | 5 | - [TinyWeb](https://github.com/belyalov/tinyweb) 6 | - [logging](https://github.com/pfalcon/pycopy-lib) 7 | - [urllib.urequest](https://github.com/pfalcon/pycopy-lib) 8 | - [sdcard](https://github.com/micropython/micropython-lib/blob/master/micropython/drivers/storage/sdcard/sdcard.py) 9 | -------------------------------------------------------------------------------- /common/WIFI_CONFIG.py: -------------------------------------------------------------------------------- 1 | SSID = "" 2 | PSK = "" 3 | COUNTRY = "" # Change to your local two-letter ISO 3166-1 country code 4 | -------------------------------------------------------------------------------- /common/network_manager.py: -------------------------------------------------------------------------------- 1 | import rp2 2 | import network 3 | import machine 4 | import uasyncio 5 | 6 | 7 | class NetworkManager: 8 | _ifname = ("Client", "Access Point") 9 | 10 | def __init__(self, country="GB", client_timeout=60, access_point_timeout=5, status_handler=None, error_handler=None): 11 | rp2.country(country) 12 | self._ap_if = network.WLAN(network.AP_IF) 13 | self._sta_if = network.WLAN(network.STA_IF) 14 | 15 | self._mode = network.STA_IF 16 | self._client_timeout = client_timeout 17 | self._access_point_timeout = access_point_timeout 18 | self._status_handler = status_handler 19 | self._error_handler = error_handler 20 | self.UID = ("{:02X}" * 8).format(*machine.unique_id()) 21 | 22 | def isconnected(self): 23 | return self._sta_if.isconnected() or self._ap_if.isconnected() 24 | 25 | def config(self, var): 26 | if self._sta_if.active(): 27 | return self._sta_if.config(var) 28 | else: 29 | if var == "password": 30 | return self.UID 31 | return self._ap_if.config(var) 32 | 33 | def mode(self): 34 | if self._sta_if.isconnected(): 35 | return self._ifname[0] 36 | if self._ap_if.isconnected(): 37 | return self._ifname[1] 38 | return None 39 | 40 | def ifaddress(self): 41 | if self._sta_if.isconnected(): 42 | return self._sta_if.ifconfig()[0] 43 | if self._ap_if.isconnected(): 44 | return self._ap_if.ifconfig()[0] 45 | return '0.0.0.0' 46 | 47 | def disconnect(self): 48 | if self._sta_if.isconnected(): 49 | self._sta_if.disconnect() 50 | if self._ap_if.isconnected(): 51 | self._ap_if.disconnect() 52 | 53 | async def wait(self, mode): 54 | while not self.isconnected(): 55 | self._handle_status(mode, None) 56 | await uasyncio.sleep_ms(1000) 57 | 58 | def _handle_status(self, mode, status): 59 | if callable(self._status_handler): 60 | self._status_handler(self._ifname[mode], status, self.ifaddress()) 61 | 62 | def _handle_error(self, mode, msg): 63 | if callable(self._error_handler): 64 | if self._error_handler(self._ifname[mode], msg): 65 | return 66 | raise RuntimeError(msg) 67 | 68 | async def client(self, ssid, psk): 69 | if self._sta_if.isconnected(): 70 | self._handle_status(network.STA_IF, True) 71 | return 72 | 73 | self._ap_if.disconnect() 74 | self._ap_if.active(False) 75 | 76 | self._sta_if.active(True) 77 | self._sta_if.config(pm=0xa11140) 78 | self._sta_if.connect(ssid, psk) 79 | 80 | try: 81 | await uasyncio.wait_for(self.wait(network.STA_IF), self._client_timeout) 82 | self._handle_status(network.STA_IF, True) 83 | 84 | except uasyncio.TimeoutError: 85 | self._sta_if.active(False) 86 | self._handle_status(network.STA_IF, False) 87 | self._handle_error(network.STA_IF, "WIFI Client Failed") 88 | 89 | async def access_point(self): 90 | if self._ap_if.isconnected(): 91 | self._handle_status(network.AP_IF, True) 92 | return 93 | 94 | self._sta_if.disconnect() 95 | self._sta_if.active(False) 96 | 97 | self._ap_if.ifconfig(("10.10.1.1", "255.255.255.0", "10.10.1.1", "10.10.1.1")) 98 | self._ap_if.config(password=self.UID) 99 | self._ap_if.active(True) 100 | 101 | try: 102 | await uasyncio.wait_for(self.wait(network.AP_IF), self._access_point_timeout) 103 | self._handle_status(network.AP_IF, True) 104 | 105 | except uasyncio.TimeoutError: 106 | self._sta_if.active(False) 107 | self._handle_status(network.AP_IF, False) 108 | self._handle_error(network.AP_IF, "WIFI Client Failed") 109 | -------------------------------------------------------------------------------- /cosmic_paint/README.md: -------------------------------------------------------------------------------- 1 | # Cosmic Paint 2 | 3 | Cosmic Paint lets you paint pixels onto your Cosmic Unicorn over WiFi, in realtime! 4 | 5 | ## Setting Up 6 | 7 | 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). 8 | 9 | You will also have to install `micropython-phew` and `microdot` through Thonny's Tools -> Manage Packages. 10 | 11 | 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! -------------------------------------------------------------------------------- /cosmic_paint/cosmic_paint.py: -------------------------------------------------------------------------------- 1 | import os 2 | from microdot_asyncio import Microdot, send_file 3 | from microdot_asyncio_websocket import with_websocket 4 | from phew import connect_to_wifi 5 | from cosmic import CosmicUnicorn 6 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 7 | from WIFI_CONFIG import SSID, PSK 8 | 9 | 10 | cu = CosmicUnicorn() 11 | graphics = PicoGraphics(DISPLAY) 12 | mv_graphics = memoryview(graphics) 13 | cu.set_brightness(0.5) 14 | 15 | WIDTH, HEIGHT = graphics.get_bounds() 16 | 17 | ip = connect_to_wifi(SSID, PSK) 18 | 19 | print(f"Start painting at: http://{ip}") 20 | 21 | 22 | server = Microdot() 23 | 24 | graphics.set_pen(graphics.create_pen(50, 50, 50)) 25 | graphics.set_font("bitmap6") 26 | graphics.text(ip[:7], 0, 0, wordwrap=16, scale=1) 27 | graphics.text(ip[7:], 0, 10, wordwrap=16, scale=1) 28 | cu.update(graphics) 29 | 30 | @server.route("/", methods=["GET"]) 31 | def route_index(request): 32 | return send_file("cosmic_paint/index.html") 33 | 34 | 35 | @server.route("/static/", methods=["GET"]) 36 | def route_static(request, path): 37 | return send_file(f"cosmic_paint/static/{path}") 38 | 39 | 40 | def get_pixel(x, y): 41 | if x < WIDTH and y < HEIGHT and x >= 0 and y >= 0: 42 | o = (y * WIDTH + x) * 4 43 | return tuple(mv_graphics[o:o + 3]) 44 | return None 45 | 46 | 47 | def flood_fill(x, y, r, g, b): 48 | todo = [] 49 | 50 | def fill(x, y, c): 51 | if get_pixel(x, y) != c: 52 | return 53 | 54 | graphics.pixel(x, y) 55 | 56 | up = get_pixel(x, y - 1) 57 | dn = get_pixel(x, y + 1) 58 | lf = get_pixel(x - 1, y) 59 | ri = get_pixel(x + 1, y) 60 | 61 | if up == c: 62 | todo.append((x, y - 1)) 63 | if dn == c: 64 | todo.append((x, y + 1)) 65 | if lf == c: 66 | todo.append((x - 1, y)) 67 | if ri == c: 68 | todo.append((x + 1, y)) 69 | 70 | c = get_pixel(x, y) 71 | 72 | if c is None: 73 | return 74 | 75 | fill(x, y, c) 76 | 77 | while len(todo): 78 | x, y = todo.pop(0) 79 | fill(x, y, c) 80 | 81 | 82 | @server.route('/paint') 83 | @with_websocket 84 | async def echo(request, ws): 85 | while True: 86 | data = await ws.receive() 87 | try: 88 | x, y, r, g, b = [int(n) for n in data[0:5]] 89 | graphics.set_pen(graphics.create_pen(r, g, b)) 90 | graphics.pixel(x, y) 91 | 92 | except ValueError: 93 | if data == "show": 94 | cu.update(graphics) 95 | 96 | if data == "fill": 97 | data = await ws.receive() 98 | x, y, r, g, b = [int(n) for n in data[0:5]] 99 | graphics.set_pen(graphics.create_pen(r, g, b)) 100 | flood_fill(x, y, r, g, b) 101 | 102 | if data == "clear": 103 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 104 | graphics.clear() 105 | 106 | if data == "save": 107 | filename = await ws.receive() 108 | print(f"Saving to {filename}.bin") 109 | try: 110 | os.mkdir("saves") 111 | except OSError: 112 | pass 113 | with open(f"saves/{filename}.bin", "wb") as f: 114 | f.write(graphics) 115 | await ws.send(f"alert: Saved to saves/{filename}.bin") 116 | 117 | 118 | server.run(host="0.0.0.0", port=80) 119 | -------------------------------------------------------------------------------- /cosmic_paint/cosmic_paint/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Cosmic Paint 6 | 7 | 8 | 9 | 10 | 11 | 12 | Cosmic Paint 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /cosmic_paint/cosmic_paint/static/paint.css: -------------------------------------------------------------------------------- 1 | body { 2 | background:#333; 3 | padding:20px; 4 | font-family:Arial, Verdana, Sans-Serif; 5 | background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAaUlEQVQYV33Q0Q3AIAgEUBjBFVyBFRzbWVjBEajXBIOVypcJj1NhETG61BiDVJX4Bh211v5hRDiniV+Elx0wQwd0hEatlUop65srMSah23vf8Auz65AWMc8rDHvCCjAQK2KeDcuQDzh+AHEJX8mbbU1BAAAAAElFTkSuQmCC) repeat; 6 | } 7 | 8 | .icons { 9 | position:absolute; 10 | margin:0; 11 | padding:20px; 12 | list-style:none; 13 | } 14 | 15 | .icons li { 16 | margin:20px; 17 | padding:0; 18 | list-style:none; 19 | padding-top:80px; 20 | width:100px; 21 | } 22 | 23 | .icons li span { 24 | background:#FFF; 25 | color:#000; 26 | border:1px solid #000; 27 | line-height:20px; 28 | padding:5px 10px; 29 | text-align:center; 30 | font-size:10px; 31 | line-height:10px; 32 | display:inline-block; 33 | } 34 | 35 | #palette ul, #palette li { 36 | margin:0;padding:0;list-style:none; 37 | } 38 | 39 | #palette { 40 | list-style:none; 41 | position:relative; 42 | height: 122px; 43 | padding:0 8px; 44 | } 45 | 46 | #palette ul { 47 | display:block; 48 | width:456px; 49 | float: left; 50 | } 51 | 52 | #palette li, #palette input { 53 | border: 2px outset; 54 | width:49px; 55 | height:49px; 56 | float:left; 57 | display:block; 58 | margin:2px; 59 | } 60 | 61 | #palette input { 62 | width:110px; 63 | height:110px; 64 | } 65 | 66 | .window { 67 | width: 640px; 68 | position: relative; 69 | background: #0E071A; 70 | box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5); 71 | } 72 | 73 | .tools { 74 | margin:0;padding:0;list-style:none; 75 | clear:both; 76 | display:block; 77 | position:absolute; 78 | top: 50px; 79 | right: 8px; 80 | width: 98px; 81 | background:#999999; 82 | font-size:0; 83 | } 84 | .tools span { 85 | line-height:30px; 86 | } 87 | 88 | .tools li { 89 | font-size:16px; 90 | width: 45px; 91 | height: 40px; 92 | text-align:center; 93 | margin:0; 94 | padding:0; 95 | display:inline-block; 96 | line-height:40px; 97 | border:2px outset #EEEEEE; 98 | background:#F5F5F5; 99 | cursor:pointer; 100 | color:#000; 101 | } 102 | 103 | .tools li.selected { 104 | background:#000; 105 | color:#FFF; 106 | } 107 | 108 | h1 { 109 | color: #FFF; 110 | background: #6D38BB; 111 | height:40px; 112 | margin:0; 113 | padding:0 8px; 114 | line-height:40px; 115 | font-weight:normal; 116 | font-size:24px; 117 | } 118 | 119 | table { 120 | clear:both; 121 | cursor:pointer; 122 | margin:10px; 123 | border:1px solid #333; 124 | background: #000000; 125 | } 126 | 127 | table td { 128 | width:14px; 129 | height:14px; 130 | border:1px solid #333; 131 | } -------------------------------------------------------------------------------- /cosmic_paint/cosmic_paint/static/paint.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var WIDTH = 32; 4 | var HEIGHT = 32; 5 | var md = false; 6 | var color = tinycolor('#840000'); 7 | var update; 8 | 9 | $(document).ready(function(){ 10 | 11 | var picker = $('#custom'); 12 | var palette = $('#palette'); 13 | 14 | picker.val(color.toHexString()); 15 | 16 | $(document) 17 | .on('mousedown',function(e){md=true;}) 18 | .on('mouseup',function(e){md=false;}); 19 | 20 | $('table').on('dragstart', function(e){ 21 | e.preventDefault(); 22 | return false; 23 | }); 24 | 25 | for (var y = 0; y < HEIGHT; y++) { 26 | var row = $(''); 27 | for (var x = 0; x < WIDTH; x++) { 28 | row.append(''); 29 | } 30 | $('tbody').append(row); 31 | } 32 | 33 | $('.tools li').on('click', function(){ 34 | switch($(this).index()){ 35 | case 6: 36 | clear(); 37 | break; 38 | case 7: 39 | save(); 40 | break; 41 | default: 42 | $('.tools li').removeClass('selected'); 43 | $(this).addClass('selected'); 44 | break; 45 | } 46 | }); 47 | 48 | picker.on('change', function(){ 49 | color = tinycolor($(this).val()); 50 | }) 51 | 52 | palette.find('li').on('click', function(){ 53 | pick(this); 54 | }); 55 | 56 | function handle_tool(obj, is_click){ 57 | switch($('.tools li.selected').index()){ 58 | case 0: //'paint': 59 | paint(obj); 60 | break; 61 | case 1: // Fill 62 | if( is_click ) fill(obj); 63 | break; 64 | case 2: // Erase 65 | update_pixel(obj, tinycolor('#000000')); 66 | break; 67 | case 3: //'pick': 68 | pick(obj); 69 | break; 70 | case 4: //'lighten': 71 | lighten(obj); 72 | break; 73 | case 5: //'darken': 74 | darken(obj); 75 | break; 76 | } 77 | } 78 | 79 | var fill_target = null; 80 | var fill_stack = []; 81 | function fill(obj){ 82 | fill_target = tinycolor($(obj).css('background-color')).toRgbString(); 83 | 84 | if( fill_target == color.toRgbString() ){ 85 | return false; 86 | } 87 | 88 | var x = $(obj).index(); 89 | var y = $(obj).parent().index(); 90 | 91 | socket.send("fill"); 92 | socket.send(new Uint8Array([x, y, color.toRgb().r, color.toRgb().g, color.toRgb().b])); 93 | socket.send('show'); 94 | 95 | do_fill(obj); 96 | 97 | while(fill_stack.length > 0){ 98 | var pixel = fill_stack.pop(); 99 | do_fill(pixel); 100 | } 101 | } 102 | 103 | function is_target_color(obj){ 104 | return ( tinycolor($(obj).css('background-color')).toRgbString() == fill_target); 105 | } 106 | 107 | function do_fill(obj){ 108 | var obj = $(obj); 109 | 110 | if( is_target_color(obj) ){ 111 | 112 | $(obj).css('background-color', color.toRgbString()); 113 | 114 | var r = obj.next('td'); // Right 115 | var l = obj.prev('td'); // Left 116 | var u = obj.parent().prev('tr').find('td:eq(' + obj.index() + ')'); // Above 117 | var d = obj.parent().next('tr').find('td:eq(' + obj.index() + ')'); // Below 118 | 119 | if( r.length && is_target_color(r[0]) ) fill_stack.push(r[0]); 120 | if( l.length && is_target_color(l[0]) ) fill_stack.push(l[0]); 121 | if( u.length && is_target_color(u[0]) ) fill_stack.push(u[0]); 122 | if( d.length && is_target_color(d[0]) ) fill_stack.push(d[0]); 123 | } 124 | } 125 | 126 | function save(){ 127 | var filename = prompt('Please enter a filename', 'mypaint'); 128 | filename = filename.replace(/[^a-z0-9]/gi, '_').toLowerCase(); 129 | socket.send('save'); 130 | socket.send(filename); 131 | } 132 | 133 | function clear(){ 134 | $('td').css('background-color','rgb(0,0,0)').data('changed',false); 135 | socket.send('clear'); 136 | socket.send('show'); 137 | } 138 | 139 | function lighten(obj){ 140 | var c = tinycolor($(obj).css('background-color')); 141 | c.lighten(5); 142 | update_pixel(obj, c); 143 | } 144 | 145 | function darken(obj){ 146 | var c = tinycolor($(obj).css('background-color')); 147 | c.darken(5); 148 | update_pixel(obj, c); 149 | } 150 | 151 | function pick(obj){ 152 | color = tinycolor($(obj).css('background-color')); 153 | picker.val(color.toHexString()); 154 | } 155 | 156 | function update_pixel(obj, col){ 157 | var bgcol = tinycolor($(obj).css('background-color')); 158 | 159 | if(col != bgcol){ 160 | $(obj) 161 | .data('changed', true) 162 | .css('background-color', col.toRgbString()); 163 | } 164 | } 165 | 166 | function update_pixels(){ 167 | var changed = false; 168 | 169 | $('td').each(function( index, obj ){ 170 | if($(obj).data('changed')){ 171 | $(obj).data('changed',false); 172 | changed = true; 173 | 174 | var x = $(this).index(); 175 | var y = $(this).parent().index(); 176 | var col = tinycolor($(obj).css('background-color')).toRgb(); 177 | 178 | if(socket) { 179 | socket.send(new Uint8Array([x, y, col.r, col.g, col.b])); 180 | } 181 | } 182 | }); 183 | if(changed){ 184 | socket.send('show'); 185 | } 186 | } 187 | 188 | function paint(obj){ 189 | update_pixel(obj, color); 190 | } 191 | 192 | $('table td').on('click', function(){ 193 | handle_tool(this, true); 194 | }); 195 | $('table td').on('mousemove', function(){ 196 | if(!md) return false; 197 | handle_tool(this, false); 198 | }) 199 | 200 | const socket = new WebSocket('ws://' + window.location.host + '/paint'); 201 | socket.addEventListener('message', ev => { 202 | console.log('<<< ' + ev.data); 203 | 204 | if(ev.data.substring(0, 6) == "alert:") { 205 | alert(ev.data.substring(6)); 206 | } 207 | }); 208 | socket.addEventListener('close', ev => { 209 | console.log('<<< closed'); 210 | }); 211 | 212 | socket.addEventListener('open', ev => { 213 | clear(); 214 | update = setInterval(update_pixels, 50); 215 | }); 216 | }); 217 | -------------------------------------------------------------------------------- /custom_font.py: -------------------------------------------------------------------------------- 1 | # IMPORTS 2 | import time 3 | import math 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 6 | from bitfonts import BitFont, font2x5, font3x5, font4x5, font5x9 7 | 8 | # INITIALISE GALACTIC UNICORN 9 | cu = CosmicUnicorn() 10 | graphics = PicoGraphics(DISPLAY) 11 | bitfont = BitFont(graphics) 12 | 13 | width = CosmicUnicorn.WIDTH 14 | height = CosmicUnicorn.HEIGHT 15 | 16 | cu.set_brightness(1) 17 | 18 | paused = 0 19 | TEST_TEXT = "1234567890" 20 | 21 | # MAIN LOOP 22 | while True: 23 | 24 | time_ms = time.ticks_ms() 25 | 26 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 27 | cu.adjust_brightness(+0.01) 28 | 29 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 30 | cu.adjust_brightness(-0.01) 31 | 32 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 33 | graphics.clear() 34 | 35 | # text = "✋🚶" 36 | # timer_text = str(time_ms)[3:] 37 | text = "BITFONTS" 38 | if paused: 39 | text = "PICOFONTS" 40 | 41 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 42 | text = "A" 43 | paused = not paused 44 | 45 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 46 | text = "B" 47 | paused = not paused 48 | 49 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 50 | text = "Play" 51 | paused = 0 52 | 53 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 54 | text = "Pause" 55 | paused = 1 56 | 57 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 58 | text = "Louder!" 59 | 60 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 61 | text = "Quieter" 62 | 63 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 64 | text = "Brighter!" 65 | 66 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 67 | text = "Darker" 68 | 69 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 70 | text = "Zzz... zzz..." 71 | 72 | graphics.set_pen(graphics.create_pen(255, 255, 255)) 73 | 74 | if not paused: 75 | bitfont.draw_text(TEST_TEXT,0,0,font2x5) 76 | bitfont.draw_text(TEST_TEXT,0,6,font3x5) 77 | bitfont.draw_text(TEST_TEXT,0,12,font4x5) 78 | bitfont.draw_text(TEST_TEXT,0,16,font5x9) 79 | bitfont.draw_text(text.upper(),0,26,font3x5) 80 | else: 81 | graphics.set_font('bitmap6') 82 | graphics.text(TEST_TEXT,0,-1,scale=1) 83 | graphics.set_font('bitmap8') 84 | graphics.text(TEST_TEXT,0,6,scale=1) 85 | graphics.set_font('bitmap14_outline') 86 | graphics.text(TEST_TEXT,0,12,scale=1) 87 | graphics.set_font('bitmap6') 88 | graphics.text(text.upper(),0,26,scale=1) 89 | 90 | cu.update(graphics) 91 | 92 | -------------------------------------------------------------------------------- /eighties_super_computer.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | from cosmic import CosmicUnicorn 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 5 | 6 | ''' 7 | Random LEDs blink on and off mimicing the look of a movie 8 | super computer doing its work in the eighties. 9 | 10 | You can adjust the brightness with LUX + and -. 11 | ''' 12 | 13 | cu = CosmicUnicorn() 14 | graphics = PicoGraphics(DISPLAY) 15 | 16 | colour = (230, 150, 0) 17 | 18 | 19 | @micropython.native # noqa: F821 20 | def setup(): 21 | global width, height, lifetime, age 22 | width = CosmicUnicorn.WIDTH 23 | height = CosmicUnicorn.HEIGHT 24 | lifetime = [[0.0 for y in range(height)] for x in range(width)] 25 | age = [[0.0 for y in range(height)] for x in range(width)] 26 | for y in range(height): 27 | for x in range(width): 28 | lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1) 29 | age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y] 30 | 31 | 32 | @micropython.native # noqa: F821 33 | def draw(): 34 | for y in range(height): 35 | for x in range(width): 36 | if age[x][y] < lifetime[x][y] * 0.3: 37 | graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2])) 38 | elif age[x][y] < lifetime[x][y] * 0.5: 39 | decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0 40 | graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2]))) 41 | else: 42 | graphics.set_pen(0) 43 | graphics.pixel(x, y) 44 | 45 | cu.update(graphics) 46 | 47 | 48 | @micropython.native # noqa: F821 49 | def update(): 50 | for y in range(height): 51 | for x in range(width): 52 | if age[x][y] >= lifetime[x][y]: 53 | age[x][y] = 0.0 54 | lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1) 55 | 56 | age[x][y] += 0.025 57 | 58 | 59 | setup() 60 | 61 | cu.set_brightness(0.5) 62 | 63 | while True: 64 | 65 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 66 | cu.adjust_brightness(+0.01) 67 | 68 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 69 | cu.adjust_brightness(-0.01) 70 | 71 | start = time.ticks_ms() 72 | 73 | draw() 74 | update() 75 | 76 | # pause for a moment (important or the USB serial device will fail) 77 | time.sleep(0.001) 78 | 79 | print("total took: {} ms".format(time.ticks_ms() - start)) 80 | -------------------------------------------------------------------------------- /exchange_ticker.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example uses the Coinbase open API to collect the current exchange rates. 3 | Use Switch A to change to a different base exchange currency. 4 | """ 5 | 6 | import WIFI_CONFIG 7 | from network_manager import NetworkManager 8 | import uasyncio 9 | import urequests 10 | import time 11 | import math 12 | from cosmic import CosmicUnicorn 13 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 14 | import gc 15 | 16 | URL = 'https://api.coinbase.com/v2/exchange-rates?currency={0}' 17 | 18 | currencies = {"Bitcoin": "BTC", "Ethereun": "ETH", "Pound": "GBP", "Dollar": "USD", "Dogecoin": "DOGE"} 19 | currency_keys = list(currencies.keys()) 20 | 21 | ref_currency_name = "" 22 | currency_name = "" 23 | currency_symbol = "" 24 | currency_rate = "" 25 | rate_keys = [] 26 | 27 | # diplay options 28 | line_1_line = -2 29 | line_2_line = 9 30 | line_3_line = 20 31 | 32 | ref_currency_index = 0 33 | 34 | cycles_per_sequence = 120 35 | 36 | cu = CosmicUnicorn() 37 | graphics = PicoGraphics(DISPLAY) 38 | 39 | 40 | # for Handling the wifi connection 41 | def status_handler(mode, status, ip): 42 | # reports wifi connection status 43 | print(mode, status, ip) 44 | print('Connecting to wifi...') 45 | if status is not None: 46 | if status: 47 | print('Wifi connection successful!') 48 | else: 49 | print('Wifi connection failed!') 50 | 51 | 52 | try: 53 | network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler) 54 | uasyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK)) 55 | except Exception as e: 56 | print(f'Wifi connection failed! {e}') 57 | 58 | 59 | def get_data(currency_selected): 60 | 61 | graphics.set_pen(graphics.create_pen(20, 20, 20)) 62 | graphics.clear() 63 | graphics.set_pen(graphics.create_pen(100, 100, 100)) 64 | graphics.text("Get", 0, 10, scale=1, spacing=1) 65 | graphics.text("data", 8, 16, scale=1, spacing=1) 66 | cu.update(graphics) 67 | gc.collect() 68 | # open the json file 69 | print('Requesting URL:') 70 | print(URL.format(currencies[currency_selected])) 71 | r = urequests.get(URL.format(currencies[currency_selected])) 72 | gc.collect() 73 | # open the json data 74 | data_obj = r.json() 75 | print('Data obtained!') 76 | r.close() 77 | return data_obj 78 | 79 | 80 | def calculate_xpos(length, cycle): 81 | cycle_phase = math.cos(math.pi * cycle / (cycles_per_sequence / 2)) 82 | pos_x = int((-(length / 2) * 10) - (length / 2) * 10 * cycle_phase) 83 | return pos_x 84 | 85 | 86 | def update_display(cycle): 87 | 88 | graphics.set_pen(graphics.create_pen(20, 20, 20)) 89 | graphics.clear() 90 | graphics.set_pen(graphics.create_pen(100, 0, 0)) 91 | graphics.text(ref_currency_name, calculate_xpos((len(ref_currency_name)), cycle), line_1_line, scale=2, spacing=1) 92 | graphics.set_pen(graphics.create_pen(100, 100, 0)) 93 | if len(currency_symbol) > 3: 94 | graphics.text(currency_symbol, calculate_xpos((len(currency_symbol)), cycle), line_2_line, scale=2, spacing=1) 95 | else: 96 | graphics.text(currency_symbol, 0, line_2_line, scale=2, spacing=1) 97 | graphics.set_pen(graphics.create_pen(0, 100, 100)) 98 | graphics.text(currency_rate, calculate_xpos((len(currency_rate)), cycle), line_3_line, scale=2, spacing=1) 99 | 100 | 101 | def update_base_currency(index): 102 | fetched_data = 0 103 | global rates, rate_keys, currency_symbol, currency_rate, ref_currency_name 104 | fetched_data = get_data(currency_keys[index]) 105 | rates = fetched_data['data']['rates'] 106 | rate_keys = list(rates.keys()) 107 | currency_symbol = rate_keys[index] 108 | currency_rate = str(rates[rate_keys[index]]) 109 | ref_currency_name = "{0}-{1}".format(currency_keys[index], currencies[currency_keys[index]]) 110 | gc.collect() 111 | 112 | 113 | update_base_currency(ref_currency_index) 114 | update_display(0) 115 | cu.update(graphics) 116 | cycle_count = 0 117 | symbol_index = 0 118 | print("Display {0} {1}".format(currency_symbol, currency_rate)) 119 | 120 | while 1: 121 | if cycle_count > 4 * cycles_per_sequence: 122 | cycle_count = 0 123 | symbol_index += 1 124 | if symbol_index > len(currency_keys): 125 | symbol_index = 0 126 | print("Display {0} {1}".format(currency_symbol, currency_rate)) 127 | currency_symbol = rate_keys[symbol_index] 128 | currency_rate = rates[rate_keys[symbol_index]] 129 | 130 | if (cu.is_pressed(CosmicUnicorn.SWITCH_A)): 131 | ref_currency_index += 1 132 | if (ref_currency_index > len(currency_keys)): 133 | ref_currency_index = 0 134 | update_base_currency(ref_currency_index) 135 | 136 | if (cu.is_pressed(CosmicUnicorn.SWITCH_B)): 137 | cycle_count = 0 138 | symbol_index += 1 139 | 140 | if symbol_index > len(rate_keys): 141 | symbol_index = 0 142 | currency_symbol = rate_keys[symbol_index] 143 | currency_rate = rates[rate_keys[symbol_index]] 144 | 145 | update_display(cycle_count) 146 | cu.update(graphics) 147 | cycle_count += 1 148 | time.sleep(0.1) 149 | -------------------------------------------------------------------------------- /feature_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | from cosmic import CosmicUnicorn 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 5 | 6 | ''' 7 | Displays some text, gradients and colours and demonstrates button use. 8 | 9 | You can adjust the brightness with LUX + and -. 10 | ''' 11 | 12 | cu = CosmicUnicorn() 13 | graphics = PicoGraphics(DISPLAY) 14 | 15 | width = CosmicUnicorn.WIDTH 16 | height = CosmicUnicorn.HEIGHT 17 | 18 | 19 | def gradient(r, g, b): 20 | for y in range(0, height): 21 | for x in range(0, width): 22 | graphics.set_pen(graphics.create_pen(int((r * x) / 32), int((g * x) / 32), int((b * x) / 32))) 23 | graphics.pixel(x, y) 24 | 25 | 26 | def grid(r, g, b): 27 | for y in range(0, height): 28 | for x in range(0, width): 29 | if (x + y) % 2 == 0: 30 | graphics.set_pen(graphics.create_pen(r, g, b)) 31 | else: 32 | graphics.set_pen(0) 33 | graphics.pixel(x, y) 34 | 35 | 36 | def outline_text(text): 37 | ms = time.ticks_ms() 38 | 39 | graphics.set_font("bitmap8") 40 | v = int((math.sin(ms / 100.0) + 1.0) * 127.0) 41 | w = graphics.measure_text(text, 1) 42 | 43 | x = int(32 / 2 - w / 2 + 1) 44 | y = 12 45 | 46 | graphics.set_pen(0) 47 | graphics.text(text, x - 1, y - 1, -1, 1) 48 | graphics.text(text, x, y - 1, -1, 1) 49 | graphics.text(text, x + 1, y - 1, -1, 1) 50 | graphics.text(text, x - 1, y, -1, 1) 51 | graphics.text(text, x + 1, y, -1, 1) 52 | graphics.text(text, x - 1, y + 1, -1, 1) 53 | graphics.text(text, x, y + 1, -1, 1) 54 | graphics.text(text, x + 1, y + 1, -1, 1) 55 | 56 | graphics.set_pen(graphics.create_pen(v, v, v)) 57 | graphics.text(text, x, y, -1, 1) 58 | 59 | 60 | cu.set_brightness(0.5) 61 | 62 | while True: 63 | 64 | time_ms = time.ticks_ms() 65 | test = (time_ms // 1000) % 5 66 | 67 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 68 | cu.adjust_brightness(+0.01) 69 | 70 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 71 | cu.adjust_brightness(-0.01) 72 | 73 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 74 | graphics.clear() 75 | 76 | if test == 0: 77 | print("grid pattern") 78 | grid(255, 255, 255) 79 | elif test == 1: 80 | print("red gradient") 81 | gradient(255, 0, 0) 82 | elif test == 2: 83 | print("green gradient") 84 | gradient(0, 255, 0) 85 | elif test == 3: 86 | print("blue gradient") 87 | gradient(0, 0, 255) 88 | elif test == 4: 89 | print("white gradient") 90 | gradient(255, 255, 255) 91 | 92 | text = "" 93 | 94 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 95 | text = "Button A" 96 | 97 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 98 | text = "Button B" 99 | 100 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 101 | text = "Button C" 102 | 103 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 104 | text = "Button D" 105 | 106 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 107 | text = "Louder!" 108 | 109 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 110 | text = "Quieter" 111 | 112 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 113 | text = "Brighter!" 114 | 115 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 116 | text = "Darker" 117 | 118 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 119 | text = "Zzz... zzz..." 120 | 121 | outline_text(text) 122 | 123 | cu.update(graphics) 124 | -------------------------------------------------------------------------------- /feature_test_with_audio.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import time 3 | import math 4 | from machine import Timer 5 | from cosmic import CosmicUnicorn, Channel 6 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 7 | 8 | ''' 9 | Displays some text, gradients and colours and demonstrates button use. 10 | Also demonstrates some of the audio / synth features. 11 | 12 | - Button A plays a synth tune 13 | - Button B plays a solo channel of the synth tune 14 | - Button C plays a sinewave (it's frequency can be adjusted with VOL + and -) 15 | - Button D plays a second sinewave (it's frequency can be adjusted with LUX + and -) 16 | - Sleep button stops the sounds 17 | ''' 18 | 19 | gc.collect() 20 | 21 | cu = CosmicUnicorn() 22 | graphics = PicoGraphics(DISPLAY) 23 | 24 | width = CosmicUnicorn.WIDTH 25 | height = CosmicUnicorn.HEIGHT 26 | 27 | SONG_LENGTH = 384 28 | HAT = 20000 29 | BASS = 500 30 | SNARE = 6000 31 | SUB = 50 32 | 33 | melody_notes = ( 34 | 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, 35 | 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, 36 | 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) 37 | 38 | rhythm_notes = ( 39 | 261, 0, 0, 0, 261, 0, 0, 0, 261, 0, 0, 0, 261, 0, 0, 0, 523, 523, 523, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 40 | 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, 41 | 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) 42 | 43 | drum_beats = ( 44 | 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, 45 | 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, 46 | 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) 47 | 48 | hi_hat = ( 49 | HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, 50 | HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, 51 | HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1, HAT, -1) 52 | 53 | bass_notes = ( 54 | 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, 55 | 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, 56 | 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) 57 | 58 | notes = [melody_notes, rhythm_notes, drum_beats, hi_hat, bass_notes] 59 | channels = [cu.synth_channel(i) for i in range(len(notes) + 1)] # Extra channel for tones 60 | 61 | # Configure the synth to play our notes 62 | channels[0].configure(waveforms=Channel.TRIANGLE + Channel.SQUARE, 63 | attack=0.016, 64 | decay=0.168, 65 | sustain=0xafff / 65535, 66 | release=0.168, 67 | volume=10000 / 65535) 68 | 69 | channels[1].configure(waveforms=Channel.SINE + Channel.SQUARE, 70 | attack=0.038, 71 | decay=0.300, 72 | sustain=0, 73 | release=0, 74 | volume=12000 / 65535) 75 | 76 | channels[2].configure(waveforms=Channel.NOISE, 77 | attack=0.005, 78 | decay=0.010, 79 | sustain=16000 / 65535, 80 | release=0.100, 81 | volume=18000 / 65535) 82 | 83 | channels[3].configure(waveforms=Channel.NOISE, 84 | attack=0.005, 85 | decay=0.005, 86 | sustain=8000 / 65535, 87 | release=0.040, 88 | volume=8000 / 65535) 89 | 90 | channels[4].configure(waveforms=Channel.SQUARE, 91 | attack=0.010, 92 | decay=0.100, 93 | sustain=0, 94 | release=0.500, 95 | volume=12000 / 65535) 96 | 97 | 98 | def gradient(r, g, b): 99 | for y in range(0, height): 100 | for x in range(0, width): 101 | graphics.set_pen(graphics.create_pen(int((r * x) / 52), int((g * x) / 52), int((b * x) / 52))) 102 | graphics.pixel(x, y) 103 | 104 | 105 | def grid(r, g, b): 106 | for y in range(0, height): 107 | for x in range(0, width): 108 | if (x + y) % 2 == 0: 109 | graphics.set_pen(graphics.create_pen(r, g, b)) 110 | else: 111 | graphics.set_pen(0) 112 | graphics.pixel(x, y) 113 | 114 | 115 | def outline_text(text): 116 | ms = time.ticks_ms() 117 | 118 | graphics.set_font("bitmap8") 119 | v = int((math.sin(ms / 100.0) + 1.0) * 127.0) 120 | w = graphics.measure_text(text, 1) 121 | 122 | x = int(32 / 2 - w / 2 + 1) 123 | y = 12 124 | 125 | graphics.set_pen(0) 126 | graphics.text(text, x - 1, y - 1, -1, 1) 127 | graphics.text(text, x, y - 1, -1, 1) 128 | graphics.text(text, x + 1, y - 1, -1, 1) 129 | graphics.text(text, x - 1, y, -1, 1) 130 | graphics.text(text, x + 1, y, -1, 1) 131 | graphics.text(text, x - 1, y + 1, -1, 1) 132 | graphics.text(text, x, y + 1, -1, 1) 133 | graphics.text(text, x + 1, y + 1, -1, 1) 134 | 135 | graphics.set_pen(graphics.create_pen(v, v, v)) 136 | graphics.text(text, x, y, -1, 1) 137 | 138 | 139 | cu.set_brightness(0.5) 140 | 141 | # Vars for storing button state 142 | was_a_pressed = False 143 | was_b_pressed = False 144 | was_c_pressed = False 145 | was_d_pressed = False 146 | was_z_pressed = False 147 | 148 | # The two frequencies to play 149 | tone_a = 0 150 | tone_b = 0 151 | 152 | # The current synth beat 153 | beat = 0 154 | 155 | text = "" 156 | 157 | 158 | def next_beat(): 159 | global beat 160 | for i in range(5): 161 | if notes[i][beat] > 0: 162 | channels[i].frequency(notes[i][beat]) 163 | channels[i].trigger_attack() 164 | elif notes[i][beat] == -1: 165 | channels[i].trigger_release() 166 | 167 | beat = (beat + 1) % SONG_LENGTH 168 | 169 | 170 | def tick(timer): 171 | next_beat() 172 | 173 | 174 | timer = Timer(-1) 175 | 176 | synthing = False 177 | 178 | 179 | while True: 180 | 181 | time_ms = time.ticks_ms() 182 | test = (time_ms // 1000) % 5 183 | 184 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 185 | if not was_a_pressed: 186 | channels[0].volume(10000 / 65535) 187 | channels[1].volume(12000 / 65535) 188 | channels[2].volume(18000 / 65535) 189 | channels[3].volume(8000 / 65535) 190 | channels[4].volume(12000 / 65535) 191 | channels[5].volume(0) 192 | 193 | # If the synth is not already playing, init the first beat 194 | if not synthing: 195 | beat = 0 196 | next_beat() 197 | 198 | cu.play_synth() 199 | synthing = True 200 | timer.init(freq=10, mode=Timer.PERIODIC, callback=tick) 201 | 202 | was_a_pressed = True 203 | else: 204 | was_a_pressed = False 205 | 206 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 207 | if not was_b_pressed: 208 | channels[0].volume(0) 209 | channels[1].volume(12000 / 65535) 210 | channels[2].volume(0) 211 | channels[3].volume(0) 212 | channels[4].volume(0) 213 | channels[5].volume(0) 214 | 215 | # If the synth is not already playing, init the first beat 216 | if not synthing: 217 | beat = 0 218 | next_beat() 219 | 220 | cu.play_synth() 221 | synthing = True 222 | timer.init(freq=10, mode=Timer.PERIODIC, callback=tick) 223 | 224 | was_b_pressed = True 225 | else: 226 | was_b_pressed = False 227 | 228 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 229 | if not was_c_pressed: 230 | # Stop synth (if running) and play Tone A 231 | timer.deinit() 232 | tone_a = 400 233 | channels[5].play_tone(tone_a, 0.06) 234 | channels[5].volume(12000 / 65535) 235 | 236 | cu.play_synth() 237 | synthing = False 238 | 239 | was_c_pressed = True 240 | else: 241 | was_c_pressed = False 242 | 243 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 244 | if not was_d_pressed: 245 | # Stop synth (if running) and play Tone B 246 | timer.deinit() 247 | tone_b = 600 248 | 249 | channels[5].play_tone(tone_b, 0.06, attack=0.5) 250 | channels[5].volume(12000 / 65535) 251 | 252 | cu.play_synth() 253 | synthing = False 254 | 255 | was_d_pressed = True 256 | else: 257 | was_d_pressed = False 258 | 259 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 260 | if tone_b > 0: # Zero means tone not playing 261 | # Increase Tone B 262 | tone_b = min(tone_b + 10, 20000) 263 | channels[5].frequency(tone_b) 264 | 265 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 266 | if tone_b > 0: # Zero means tone not playing 267 | # Decrease Tone B 268 | tone_b = max(tone_b - 10, 10) 269 | channels[5].frequency(max(tone_b, 10)) 270 | 271 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 272 | if tone_a > 0: # Zero means tone not playing 273 | # Increase Tone A 274 | tone_a = min(tone_a + 10, 20000) 275 | channels[5].frequency(tone_a) 276 | 277 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 278 | if tone_a > 0: # Zero means tone not playing 279 | # Decrease Tone A 280 | tone_a = max(tone_a - 10, 10) 281 | channels[5].frequency(tone_a) 282 | 283 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 284 | if not was_z_pressed: 285 | # Stop synth and both tones 286 | tone_a = 0 287 | tone_b = 0 288 | cu.stop_playing() 289 | timer.deinit() 290 | synthing = False 291 | 292 | was_z_pressed = True 293 | else: 294 | was_z_pressed = False 295 | 296 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 297 | graphics.clear() 298 | 299 | if test == 0: 300 | # print("grid pattern") 301 | grid(255, 255, 255) 302 | elif test == 1: 303 | # print("red gradient") 304 | gradient(255, 0, 0) 305 | elif test == 2: 306 | # print("green gradient") 307 | gradient(0, 255, 0) 308 | elif test == 3: 309 | # print("blue gradient") 310 | gradient(0, 0, 255) 311 | elif test == 4: 312 | # print("white gradient") 313 | gradient(255, 255, 255) 314 | 315 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 316 | text = "PlaySyn" 317 | 318 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 319 | text = "SoloSyn" 320 | 321 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 322 | text = "Tone A" 323 | 324 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 325 | text = "Tone B" 326 | 327 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 328 | text = "RaiseA" 329 | 330 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 331 | text = "LowerA" 332 | 333 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 334 | text = "RaiseB" 335 | 336 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 337 | text = "LowerB" 338 | 339 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 340 | text = "Stop" 341 | 342 | outline_text(text) 343 | 344 | cu.update(graphics) 345 | 346 | # pause for a moment (important or the USB serial device will fail 347 | time.sleep(0.001) 348 | -------------------------------------------------------------------------------- /fire_effect.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | from cosmic import CosmicUnicorn 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 5 | 6 | ''' 7 | A pretty, procedural fire effect. 8 | 9 | You can adjust the brightness with LUX + and -. 10 | ''' 11 | 12 | cu = CosmicUnicorn() 13 | graphics = PicoGraphics(DISPLAY) 14 | 15 | fire_colours = [graphics.create_pen(0, 0, 0), 16 | graphics.create_pen(20, 20, 20), 17 | graphics.create_pen(180, 30, 0), 18 | graphics.create_pen(220, 160, 0), 19 | graphics.create_pen(255, 255, 180)] 20 | 21 | 22 | @micropython.native # noqa: F821 23 | def update(): 24 | # take local references as it's quicker than accessing the global 25 | # and we access it a lot in this method 26 | _heat = heat 27 | 28 | # clear the bottom row and then add a new fire seed to it 29 | for x in range(width): 30 | _heat[x][height - 1] = 0.0 31 | _heat[x][height - 2] = 0.0 32 | 33 | for c in range(fire_spawns): 34 | x = random.randint(0, width - 4) + 2 35 | _heat[x + 0][height - 1] = 1.0 36 | _heat[x + 1][height - 1] = 1.0 37 | _heat[x - 1][height - 1] = 1.0 38 | _heat[x + 0][height - 2] = 1.0 39 | _heat[x + 1][height - 2] = 1.0 40 | _heat[x - 1][height - 2] = 1.0 41 | 42 | factor = damping_factor / 5.0 43 | for y in range(0, height - 2): 44 | for x in range(1, width - 1): 45 | _heat[x][y] += _heat[x][y + 1] + _heat[x][y + 2] + _heat[x - 1][y + 1] + _heat[x + 1][y + 1] 46 | _heat[x][y] *= factor 47 | 48 | 49 | @micropython.native # noqa: F821 50 | def draw(): 51 | # take local references as it's quicker than accessing the global 52 | # and we access it a lot in this method 53 | _graphics = graphics 54 | _heat = heat 55 | _set_pen = graphics.set_pen 56 | _pixel = graphics.pixel 57 | _fire_colours = fire_colours 58 | 59 | for y in range(CosmicUnicorn.HEIGHT): 60 | for x in range(CosmicUnicorn.WIDTH): 61 | value = _heat[x + 1][y] 62 | if value < 0.15: 63 | _set_pen(_fire_colours[0]) 64 | elif value < 0.25: 65 | _set_pen(_fire_colours[1]) 66 | elif value < 0.35: 67 | _set_pen(_fire_colours[2]) 68 | elif value < 0.45: 69 | _set_pen(_fire_colours[3]) 70 | else: 71 | _set_pen(_fire_colours[4]) 72 | _pixel(x, y) 73 | 74 | cu.update(_graphics) 75 | 76 | 77 | width = CosmicUnicorn.WIDTH + 2 78 | height = CosmicUnicorn.HEIGHT + 4 79 | heat = [[0.0 for y in range(height)] for x in range(width)] 80 | fire_spawns = 5 81 | damping_factor = 0.97 82 | 83 | 84 | cu.set_brightness(0.5) 85 | 86 | while True: 87 | 88 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 89 | cu.adjust_brightness(+0.01) 90 | 91 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 92 | cu.adjust_brightness(-0.01) 93 | 94 | start = time.ticks_ms() 95 | 96 | update() 97 | draw() 98 | 99 | print("total took: {} ms".format(time.ticks_ms() - start)) 100 | 101 | # pause for a moment (important or the USB serial device will fail) 102 | time.sleep(0.001) 103 | -------------------------------------------------------------------------------- /http_text/html_text.py: -------------------------------------------------------------------------------- 1 | import time 2 | from cosmic import CosmicUnicorn 3 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 4 | import WIFI_CONFIG 5 | from network_manager import NetworkManager 6 | import uasyncio as asyncio 7 | import uasyncio.core 8 | from tinyweb.server import webserver 9 | 10 | ''' 11 | Display scrolling wisdom, quotes or greetz... from the internetz! 12 | 13 | You can adjust the brightness with LUX + and -. 14 | 15 | Requires network_manager.py , WIFI_CONFIG.py, logging.mpy and tinyweb from micropython/examples/common 16 | You'll also need index.html to be saved alongside this file. 17 | ''' 18 | 19 | # Server Settings 20 | host = "0.0.0.0" 21 | port = 80 22 | 23 | 24 | def convert_colour(colour_str): 25 | colour_str = colour_str.split(',') 26 | print(colour_str) 27 | return colour_str[0], colour_str[1], colour_str[2] 28 | 29 | 30 | class text: 31 | 32 | def get(self, data): 33 | global MESSAGE, MESSAGE_COLOUR, BACKGROUND_COLOUR 34 | print(data) 35 | if 'text' in data.keys(): 36 | MESSAGE = data['text'] 37 | if 'colourfg' in data.keys(): 38 | MESSAGE_COLOUR = convert_colour(data['colourfg']) 39 | if 'colourbg' in data.keys(): 40 | BACKGROUND_COLOUR = convert_colour(data['colourbg']) 41 | return {'message': 'text updated'}, 201 42 | 43 | def post(self, data): 44 | 45 | return {'message': 'text updated'}, 201 46 | 47 | 48 | def status_handler(mode, status, ip): 49 | global MESSAGE 50 | print("Network: {}".format(WIFI_CONFIG.SSID)) 51 | status_text = "Connecting..." 52 | if status is not None: 53 | if status: 54 | status_text = "Connection successful!" 55 | else: 56 | status_text = "Connection failed!" 57 | 58 | print(status_text) 59 | print("IP: {}".format(ip)) 60 | MESSAGE = "{}".format(ip) 61 | 62 | 63 | # Create web server application 64 | app = webserver() 65 | 66 | # Static page 67 | html_file = open('index.html', 'r') 68 | 69 | # WIFI settings 70 | WIFI_COUNTRY = "GB" # Change to your local two-letter ISO 3166-1 country code 71 | 72 | 73 | # Index page 74 | @app.route('/') 75 | async def index(request, response): 76 | # Start HTTP response with content-type text/html 77 | await response.start_html() 78 | # Send actual HTML page 79 | await response.send(html_file.read()) 80 | 81 | 82 | # HTTP redirection 83 | @app.route('/redirect') 84 | async def redirect(request, response): 85 | # Start HTTP response with content-type text/html 86 | await response.redirect('/') 87 | 88 | # constants for controlling scrolling text 89 | PADDING = 5 90 | MESSAGE_COLOUR = (255, 255, 255) 91 | OUTLINE_COLOUR = (0, 0, 0) 92 | BACKGROUND_COLOUR = (10, 0, 96) 93 | MESSAGE = "Connecting" 94 | HOLD_TIME = 2.0 95 | STEP_TIME = 0.075 96 | 97 | # create galactic object and graphics surface for drawing 98 | cu = CosmicUnicorn() 99 | graphics = PicoGraphics(DISPLAY) 100 | 101 | width = CosmicUnicorn.WIDTH 102 | height = CosmicUnicorn.HEIGHT 103 | 104 | 105 | # function for drawing outlined text 106 | def outline_text(text, x, y): 107 | graphics.set_pen(graphics.create_pen(int(OUTLINE_COLOUR[0]), int(OUTLINE_COLOUR[1]), int(OUTLINE_COLOUR[2]))) 108 | graphics.text(text, x - 1, y - 1, -1, 1) 109 | graphics.text(text, x, y - 1, -1, 1) 110 | graphics.text(text, x + 1, y - 1, -1, 1) 111 | graphics.text(text, x - 1, y, -1, 1) 112 | graphics.text(text, x + 1, y, -1, 1) 113 | graphics.text(text, x - 1, y + 1, -1, 1) 114 | graphics.text(text, x, y + 1, -1, 1) 115 | graphics.text(text, x + 1, y + 1, -1, 1) 116 | 117 | graphics.set_pen(graphics.create_pen(int(MESSAGE_COLOUR[0]), int(MESSAGE_COLOUR[1]), int(MESSAGE_COLOUR[2]))) 118 | graphics.text(text, x, y, -1, 1) 119 | 120 | 121 | def run(): 122 | # Setup wifi 123 | network_manager = NetworkManager(WIFI_COUNTRY, status_handler=status_handler) 124 | 125 | app.add_resource(text, '/update') 126 | 127 | # Connect to Wifi network 128 | asyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK)) 129 | while (not network_manager.isconnected()): 130 | time.sleep(0.1) 131 | 132 | 133 | cu.set_brightness(0.5) 134 | 135 | # Start wifi connection 136 | run() 137 | 138 | 139 | async def message_update(): 140 | global MESSAGE 141 | last_time = time.ticks_ms() 142 | STATE_PRE_SCROLL = 0 143 | STATE_SCROLLING = 1 144 | STATE_POST_SCROLL = 2 145 | 146 | shift = 0 147 | state = STATE_PRE_SCROLL 148 | 149 | # set the font 150 | graphics.set_font("bitmap8") 151 | 152 | # calculate the message width so scrolling can happen 153 | msg_width = graphics.measure_text(MESSAGE, 1) 154 | while 1: 155 | 156 | msg_width = graphics.measure_text(MESSAGE, 1) 157 | time_ms = time.ticks_ms() 158 | 159 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 160 | cu.adjust_brightness(+0.01) 161 | 162 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 163 | cu.adjust_brightness(-0.01) 164 | 165 | if state == STATE_PRE_SCROLL and time_ms - last_time > HOLD_TIME * 1000: 166 | if msg_width + PADDING * 2 >= width: 167 | state = STATE_SCROLLING 168 | last_time = time_ms 169 | 170 | if state == STATE_SCROLLING and time_ms - last_time > STEP_TIME * 1000: 171 | shift += 1 172 | if shift >= (msg_width + PADDING * 2) - width - 1: 173 | state = STATE_POST_SCROLL 174 | last_time = time_ms 175 | 176 | if state == STATE_POST_SCROLL and time_ms - last_time > HOLD_TIME * 1000: 177 | state = STATE_PRE_SCROLL 178 | shift = 0 179 | last_time = time_ms 180 | 181 | graphics.set_pen(graphics.create_pen(int(BACKGROUND_COLOUR[0]), int(BACKGROUND_COLOUR[1]), int(BACKGROUND_COLOUR[2]))) 182 | graphics.clear() 183 | 184 | outline_text(MESSAGE, x=PADDING - shift, y=11) 185 | 186 | # update the display 187 | cu.update(graphics) 188 | 189 | # pause for a moment (important or the USB serial device will fail) 190 | await asyncio.sleep(0.001) 191 | 192 | 193 | # The following is required to run both the web server and the scrolling text coherently 194 | app._server_coro = app._tcp_server(host, port, app.backlog) 195 | loop = asyncio.get_event_loop() 196 | t1 = loop.create_task(message_update()) 197 | t2 = loop.create_task(app._server_coro) 198 | loop.run_forever() 199 | -------------------------------------------------------------------------------- /http_text/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 69 | 70 | 71 | Cosmic Unicorn Web Text 72 | 73 | Enter text to display: 74 | 75 | 76 | 78 | 79 | Text Colour: 80 | 81 | 82 | 84 | 85 | Background Colour: 86 | 87 | 88 | 90 | 91 | 94 | Update 95 | 96 | 97 | Please type in what you wish to be displayed on the Cosmic Unicorn and whe you are ready hit update to update the display 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /launch/fire.py: -------------------------------------------------------------------------------- 1 | import random 2 | from cosmic import CosmicUnicorn 3 | 4 | graphics = None 5 | palette = None 6 | 7 | # setup heat value buffer and fire parameters 8 | width = CosmicUnicorn.WIDTH + 2 9 | height = CosmicUnicorn.HEIGHT + 4 10 | heat = [[0.0 for y in range(height)] for x in range(width)] 11 | fire_spawns = 5 12 | damping_factor = 0.97 13 | 14 | 15 | def init(): 16 | # a palette of five firey colours (white, yellow, orange, red, smoke) 17 | global palette 18 | palette = [ 19 | graphics.create_pen(0, 0, 0), 20 | graphics.create_pen(20, 20, 20), 21 | graphics.create_pen(180, 30, 0), 22 | graphics.create_pen(220, 160, 0), 23 | graphics.create_pen(255, 255, 180) 24 | ] 25 | 26 | 27 | # returns the palette entry for a given heat value 28 | @micropython.native # noqa: F821 29 | def pen_from_value(value): 30 | if value < 0.15: 31 | return palette[0] 32 | elif value < 0.25: 33 | return palette[1] 34 | elif value < 0.35: 35 | return palette[2] 36 | elif value < 0.45: 37 | return palette[3] 38 | return palette[4] 39 | 40 | 41 | @micropython.native # noqa: F821 42 | def draw(): 43 | # clear the the rows off the bottom of the display 44 | for x in range(width): 45 | heat[x][height - 1] = 0.0 46 | heat[x][height - 2] = 0.0 47 | 48 | # add new fire spawns 49 | for c in range(fire_spawns): 50 | x = random.randint(0, width - 4) + 2 51 | heat[x + 0][height - 1] = 1.0 52 | heat[x + 1][height - 1] = 1.0 53 | heat[x - 1][height - 1] = 1.0 54 | heat[x + 0][height - 2] = 1.0 55 | heat[x + 1][height - 2] = 1.0 56 | heat[x - 1][height - 2] = 1.0 57 | 58 | # average and damp out each value to create rising flame effect 59 | for y in range(0, height - 2): 60 | for x in range(1, width - 1): 61 | # update this pixel by averaging the below pixels 62 | average = ( 63 | heat[x][y] + heat[x][y + 1] + heat[x][y + 2] + heat[x - 1][y + 1] + heat[x + 1][y + 1] 64 | ) / 5.0 65 | 66 | # damping factor to ensure flame tapers out towards the top of the displays 67 | average *= damping_factor 68 | 69 | # update the heat map with our newly averaged value 70 | heat[x][y] = average 71 | 72 | # render the heat values to the graphics buffer 73 | for y in range(CosmicUnicorn.HEIGHT): 74 | for x in range(CosmicUnicorn.WIDTH): 75 | graphics.set_pen(pen_from_value(heat[x + 1][y])) 76 | graphics.pixel(x, y) 77 | 78 | 79 | def test(): 80 | print("A") 81 | -------------------------------------------------------------------------------- /launch/main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import machine 3 | from cosmic import CosmicUnicorn 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 5 | 6 | # overclock to 200Mhz 7 | machine.freq(200000000) 8 | 9 | # create cosmic object and graphics surface for drawing 10 | cosmic = CosmicUnicorn() 11 | graphics = PicoGraphics(DISPLAY) 12 | 13 | brightness = 0.5 14 | 15 | 16 | # returns the id of the button that is currently pressed or 17 | # None if none are 18 | def pressed(): 19 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_A): 20 | return CosmicUnicorn.SWITCH_A 21 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_B): 22 | return CosmicUnicorn.SWITCH_B 23 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_C): 24 | return CosmicUnicorn.SWITCH_C 25 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_D): 26 | return CosmicUnicorn.SWITCH_D 27 | return None 28 | 29 | 30 | # wait for a button to be pressed and load that effect 31 | while True: 32 | graphics.set_font("bitmap6") 33 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 34 | graphics.clear() 35 | graphics.set_pen(graphics.create_pen(155, 155, 155)) 36 | graphics.text("PRESS", 3, 6, -1, 1) 37 | graphics.text("A B C OR D!", 5, 14, 32, 1, 0) 38 | 39 | # brightness up/down 40 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 41 | brightness += 0.01 42 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 43 | brightness -= 0.01 44 | brightness = max(min(brightness, 1.0), 0.0) 45 | 46 | cosmic.set_brightness(brightness) 47 | cosmic.update(graphics) 48 | 49 | if pressed() == CosmicUnicorn.SWITCH_A: 50 | import fire as effect 51 | break 52 | if pressed() == CosmicUnicorn.SWITCH_B: 53 | import supercomputer as effect # noqa: F811 54 | break 55 | if pressed() == CosmicUnicorn.SWITCH_C: 56 | import rainbow as effect # noqa: F811 57 | break 58 | if pressed() == CosmicUnicorn.SWITCH_D: 59 | import today as effect # noqa: F811 60 | break 61 | 62 | # pause for a moment 63 | time.sleep(0.01) 64 | 65 | # wait until all buttons are released 66 | while pressed() is not None: 67 | time.sleep(0.1) 68 | 69 | effect.graphics = graphics 70 | effect.init() 71 | 72 | sleep = False 73 | was_sleep_pressed = False 74 | 75 | 76 | # wait 77 | while True: 78 | # if A, B, C, or D are pressed then reset 79 | if pressed() is not None: 80 | machine.reset() 81 | 82 | sleep_pressed = cosmic.is_pressed(CosmicUnicorn.SWITCH_SLEEP) 83 | if sleep_pressed and not was_sleep_pressed: 84 | sleep = not sleep 85 | 86 | was_sleep_pressed = sleep_pressed 87 | 88 | if sleep: 89 | # fade out if screen not off 90 | cosmic.set_brightness(cosmic.get_brightness() - 0.01) 91 | 92 | if cosmic.get_brightness() > 0.0: 93 | effect.draw() 94 | 95 | # update the display 96 | cosmic.update(graphics) 97 | else: 98 | effect.draw() 99 | 100 | # update the display 101 | cosmic.update(graphics) 102 | 103 | # brightness up/down 104 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 105 | brightness += 0.01 106 | if cosmic.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 107 | brightness -= 0.01 108 | brightness = max(min(brightness, 1.0), 0.0) 109 | 110 | cosmic.set_brightness(brightness) 111 | 112 | # pause for a moment (important or the USB serial device will fail 113 | time.sleep(0.001) 114 | -------------------------------------------------------------------------------- /launch/rainbow.py: -------------------------------------------------------------------------------- 1 | import math 2 | from cosmic import CosmicUnicorn 3 | 4 | graphics = None 5 | palette = None 6 | 7 | width = CosmicUnicorn.WIDTH 8 | height = CosmicUnicorn.HEIGHT 9 | 10 | 11 | @micropython.native # noqa: F821 12 | def from_hsv(h, s, v): 13 | i = math.floor(h * 6.0) 14 | f = h * 6.0 - i 15 | v *= 255.0 16 | p = v * (1.0 - s) 17 | q = v * (1.0 - f * s) 18 | t = v * (1.0 - (1.0 - f) * s) 19 | 20 | i = int(i) % 6 21 | if i == 0: 22 | return int(v), int(t), int(p) 23 | if i == 1: 24 | return int(q), int(v), int(p) 25 | if i == 2: 26 | return int(p), int(v), int(t) 27 | if i == 3: 28 | return int(p), int(q), int(v) 29 | if i == 4: 30 | return int(t), int(p), int(v) 31 | if i == 5: 32 | return int(v), int(p), int(q) 33 | 34 | 35 | phase = 0 36 | hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)] 37 | hue_offset = 0.0 38 | stripe_width = 3.0 39 | speed = 5.0 40 | 41 | 42 | def init(): 43 | pass 44 | 45 | 46 | @micropython.native # noqa: F821 47 | def draw(): 48 | global hue_offset, phase 49 | 50 | phase += speed 51 | 52 | phase_percent = phase / 15 53 | for x in range(width): 54 | colour = hue_map[int((x + (hue_offset * width)) % width)] 55 | for y in range(height): 56 | v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5) 57 | 58 | graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v))) 59 | graphics.pixel(x, y) 60 | -------------------------------------------------------------------------------- /launch/supercomputer.py: -------------------------------------------------------------------------------- 1 | import random 2 | from cosmic import CosmicUnicorn 3 | 4 | graphics = None 5 | 6 | colour = (230, 150, 0) 7 | 8 | 9 | def init(): 10 | global width, height, lifetime, age 11 | width = CosmicUnicorn.WIDTH 12 | height = CosmicUnicorn.HEIGHT 13 | lifetime = [[0.0 for y in range(height)] for x in range(width)] 14 | age = [[0.0 for y in range(height)] for x in range(width)] 15 | for y in range(height): 16 | for x in range(width): 17 | lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1) 18 | age[x][y] = random.uniform(0.0, 1.0) * lifetime[x][y] 19 | 20 | 21 | @micropython.native # noqa: F821 22 | def draw(): 23 | for y in range(height): 24 | for x in range(width): 25 | if age[x][y] >= lifetime[x][y]: 26 | age[x][y] = 0.0 27 | lifetime[x][y] = 1.0 + random.uniform(0.0, 0.1) 28 | 29 | age[x][y] += 0.025 30 | 31 | for y in range(height): 32 | for x in range(width): 33 | if age[x][y] < lifetime[x][y] * 0.3: 34 | graphics.set_pen(graphics.create_pen(colour[0], colour[1], colour[2])) 35 | elif age[x][y] < lifetime[x][y] * 0.5: 36 | decay = (lifetime[x][y] * 0.5 - age[x][y]) * 5.0 37 | graphics.set_pen(graphics.create_pen(int(decay * colour[0]), int(decay * colour[1]), int(decay * colour[2]))) 38 | else: 39 | graphics.set_pen(0) 40 | graphics.pixel(x, y) 41 | -------------------------------------------------------------------------------- /launch/today.py: -------------------------------------------------------------------------------- 1 | import time 2 | import network 3 | import ntptime 4 | import machine 5 | 6 | # You will need to create or update the file secrets.py with your network credentials using Thonny 7 | # in order for the example to update using the NTP. 8 | 9 | # secrets.py should contain: 10 | # WIFI_SSID = "" 11 | # WIFI_PASSWORD = "" 12 | 13 | try: 14 | from secrets import WIFI_SSID, WIFI_PASSWORD 15 | except ImportError: 16 | print("Create secrets.py with your WiFi credentials") 17 | 18 | graphics = None 19 | 20 | WIDTH = 32 # CosmicUnicorn.WIDTH 21 | HEIGHT = 32 # CosmicUnicorn.HEIGHT 22 | 23 | rtc = machine.RTC() 24 | 25 | DAYS = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat", "Sun"] 26 | 27 | # Enable the Wireless 28 | wlan = network.WLAN(network.STA_IF) 29 | wlan.active(True) 30 | 31 | 32 | def network_connect(SSID, PSK): 33 | 34 | # Number of attempts to make before timeout 35 | max_wait = 5 36 | 37 | # Sets the Wireless LED pulsing and attempts to connect to your local network. 38 | print("connecting...") 39 | wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs 40 | wlan.connect(SSID, PSK) 41 | 42 | while max_wait > 0: 43 | if wlan.status() < 0 or wlan.status() >= 3: 44 | break 45 | max_wait -= 1 46 | print('waiting for connection...') 47 | time.sleep(1) 48 | 49 | # Handle connection error. Switches the Warn LED on. 50 | if wlan.status() != 3: 51 | print("Unable to connect. Attempting connection again") 52 | 53 | 54 | # Function to sync the Pico RTC using NTP 55 | def sync_time(): 56 | 57 | try: 58 | network_connect(WIFI_SSID, WIFI_PASSWORD) 59 | except NameError: 60 | print("Create secrets.py with your WiFi credentials") 61 | 62 | if wlan.status() < 0 or wlan.status() >= 3: 63 | try: 64 | ntptime.settime() 65 | except OSError: 66 | print("Unable to sync with NTP server. Check network and try again.") 67 | 68 | 69 | def init(): 70 | 71 | sync_time() 72 | 73 | 74 | def draw(): 75 | 76 | # Pens 77 | RED = graphics.create_pen(120, 0, 0) 78 | WHITE = graphics.create_pen(255, 255, 255) 79 | 80 | current_t = rtc.datetime() 81 | 82 | # Set the pen to Red and clear the screen. 83 | graphics.set_pen(WHITE) 84 | graphics.clear() 85 | 86 | # Measures the length of the text to help us with centring later. 87 | day_length = graphics.measure_text(DAYS[current_t[3]], 1) 88 | date_length = graphics.measure_text(str(current_t[2]), 3) 89 | 90 | graphics.set_font("bitmap6") 91 | graphics.set_pen(RED) 92 | graphics.rectangle(0, 0, WIDTH, 7) 93 | graphics.set_pen(WHITE) 94 | graphics.text(DAYS[current_t[3]], (WIDTH // 2) - (day_length // 2) - 1, 0, 32, 1) 95 | 96 | graphics.set_pen(RED) 97 | graphics.set_font("bitmap8") 98 | graphics.text(str(current_t[2]), (WIDTH // 2) - (date_length // 2) + 1, 9, 32, 3) 99 | 100 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 101 | -------------------------------------------------------------------------------- /lava_lamp.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | import math 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 6 | 7 | ''' 8 | A 70s-tastic, procedural rainbow lava lamp. 9 | 10 | You can adjust the brightness with LUX + and -. 11 | ''' 12 | 13 | cu = CosmicUnicorn() 14 | graphics = PicoGraphics(DISPLAY) 15 | 16 | blob_count = 10 17 | 18 | 19 | class Blob(): 20 | def __init__(self): 21 | self.x = float(random.randint(0, width - 1)) 22 | self.y = float(random.randint(0, height - 1)) 23 | self.r = (float(random.randint(0, 40)) / 10.0) + 5.0 24 | self.dx = (float(random.randint(0, 2)) / 10.0) - 0.1 25 | self.dy = (float(random.randint(0, 2)) / 10.0) - 0.05 # positive bias 26 | 27 | 28 | @micropython.native # noqa: F821 29 | def setup_portrait(): 30 | global width, height, liquid, blobs 31 | width = CosmicUnicorn.HEIGHT 32 | height = CosmicUnicorn.WIDTH 33 | liquid = [[0.0 for y in range(height)] for x in range(width)] 34 | blobs = [Blob() for i in range(blob_count)] 35 | 36 | 37 | hue = 0.0 38 | 39 | 40 | @micropython.native # noqa: F821 41 | def from_hsv(h, s, v): 42 | i = math.floor(h * 6.0) 43 | f = h * 6.0 - i 44 | v *= 255.0 45 | p = v * (1.0 - s) 46 | q = v * (1.0 - f * s) 47 | t = v * (1.0 - (1.0 - f) * s) 48 | 49 | i = int(i) % 6 50 | if i == 0: 51 | return graphics.create_pen(int(v), int(t), int(p)) 52 | if i == 1: 53 | return graphics.create_pen(int(q), int(v), int(p)) 54 | if i == 2: 55 | return graphics.create_pen(int(p), int(v), int(t)) 56 | if i == 3: 57 | return graphics.create_pen(int(p), int(q), int(v)) 58 | if i == 4: 59 | return graphics.create_pen(int(t), int(p), int(v)) 60 | if i == 5: 61 | return graphics.create_pen(int(v), int(p), int(q)) 62 | 63 | 64 | @micropython.native # noqa: F821 65 | def update_liquid(): 66 | for y in range(height): 67 | for x in range(width): 68 | liquid[x][y] = 0.0 69 | 70 | for blob in blobs: 71 | r_sq = blob.r * blob.r 72 | blob_y_range = range(max(math.floor(blob.y - blob.r), 0), 73 | min(math.ceil(blob.y + blob.r), height)) 74 | blob_x_range = range(max(math.floor(blob.x - blob.r), 0), 75 | min(math.ceil(blob.x + blob.r), width)) 76 | 77 | for y in blob_y_range: 78 | for x in blob_x_range: 79 | x_diff = x - blob.x 80 | y_diff = y - blob.y 81 | d_sq = x_diff * x_diff + y_diff * y_diff 82 | if d_sq <= r_sq: 83 | liquid[x][y] += 1.0 - (d_sq / r_sq) 84 | 85 | 86 | @micropython.native # noqa: F821 87 | def move_blobs(): 88 | for blob in blobs: 89 | blob.x += blob.dx 90 | blob.y += blob.dy 91 | 92 | if blob.x < 0.0 or blob.x >= float(width): 93 | blob.dx = 0.0 - blob.dx 94 | 95 | if blob.y < 0.0 or blob.y >= float(height): 96 | blob.dy = 0.0 - blob.dy 97 | 98 | 99 | @micropython.native # noqa: F821 100 | def draw_portrait(): 101 | global hue 102 | hue += 0.001 103 | 104 | dark = from_hsv(hue, 1.0, 0.3) 105 | mid = from_hsv(hue, 1.0, 0.6) 106 | bright = from_hsv(hue, 1.0, 1.0) 107 | 108 | for y in range(height): 109 | for x in range(width): 110 | v = liquid[x][y] 111 | 112 | # select a colour for this pixel based on how much 113 | # "blobfluence" there is at this position in the liquid 114 | if v >= 1.5: 115 | graphics.set_pen(bright) 116 | elif v >= 1.25: 117 | graphics.set_pen(mid) 118 | elif v >= 1.0: 119 | graphics.set_pen(dark) 120 | else: 121 | graphics.set_pen(0) 122 | graphics.pixel(y, x) 123 | 124 | cu.update(graphics) 125 | 126 | 127 | setup_portrait() 128 | 129 | cu.set_brightness(0.5) 130 | 131 | while True: 132 | 133 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 134 | cu.adjust_brightness(+0.01) 135 | 136 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 137 | cu.adjust_brightness(-0.01) 138 | 139 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 140 | setup_portrait() 141 | 142 | start = time.ticks_ms() 143 | 144 | update_liquid() 145 | move_blobs() 146 | draw_portrait() 147 | 148 | # pause for a moment (important or the USB serial device will fail) 149 | time.sleep(0.001) 150 | 151 | print("total took: {} ms".format(time.ticks_ms() - start)) 152 | -------------------------------------------------------------------------------- /melody_maker.py: -------------------------------------------------------------------------------- 1 | import time 2 | import random 3 | from cosmic import CosmicUnicorn, Channel 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 5 | 6 | """ 7 | A gloriously terrible melody maker. 8 | 9 | Use Vol + and Vol - to move up/down (note pitch) 10 | 11 | Use Lux - and D to move left/right (note position) 12 | 13 | Press A to set a note. 14 | 15 | Press B to delete a note. 16 | 17 | Use Lux + to play/pause. 18 | """ 19 | 20 | NOTE_DURATION = 125 21 | 22 | cu = CosmicUnicorn() 23 | cu.set_brightness(0.5) 24 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 25 | 26 | boopety_beepety = cu.synth_channel(0) 27 | boopety_beepety.configure( 28 | waveforms=Channel.SQUARE | Channel.SINE, 29 | attack=0.1, 30 | decay=0.5, 31 | sustain=0.0, 32 | release=1.0, 33 | volume=1.0 34 | ) 35 | 36 | cu.play_synth() 37 | 38 | black = graphics.create_pen(0, 0, 0) 39 | note = graphics.create_pen(255, 255, 255) 40 | cursor_bg = graphics.create_pen(64, 0, 0) 41 | cursor = graphics.create_pen(255, 0, 0) 42 | playhead = graphics.create_pen(0, 128, 0) 43 | 44 | cursor_position = [0, 0] 45 | 46 | playhead_position = 0 47 | 48 | width, height = graphics.get_bounds() 49 | 50 | notes = [random.randint(0, height) for _ in range(width)] 51 | 52 | last_note_advance = time.ticks_ms() 53 | 54 | last_action = time.ticks_ms() 55 | 56 | playing = True 57 | 58 | 59 | def debounce(button, duration=100): 60 | global last_action 61 | if cu.is_pressed(button) and time.ticks_ms() - last_action > duration: 62 | last_action = time.ticks_ms() 63 | return True 64 | return False 65 | 66 | 67 | def note_to_frequency(note_number): 68 | return int((2 ** ((note_number - 69.0) / 12)) * 440) 69 | 70 | 71 | while True: 72 | if debounce(CosmicUnicorn.SWITCH_D): 73 | cursor_position[0] -= 1 74 | cursor_position[0] %= width 75 | 76 | if debounce(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 77 | cursor_position[0] += 1 78 | cursor_position[0] %= width 79 | 80 | if debounce(CosmicUnicorn.SWITCH_VOLUME_DOWN): 81 | cursor_position[1] += 1 82 | cursor_position[1] %= height 83 | 84 | if debounce(CosmicUnicorn.SWITCH_VOLUME_UP): 85 | cursor_position[1] -= 1 86 | cursor_position[1] %= height 87 | 88 | if debounce(CosmicUnicorn.SWITCH_BRIGHTNESS_UP, 500): 89 | playing = not playing 90 | if not playing: 91 | boopety_beepety.trigger_release() 92 | 93 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 94 | notes[cursor_position[0]] = cursor_position[1] 95 | 96 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 97 | notes[cursor_position[0]] = None 98 | 99 | if time.ticks_ms() - last_note_advance > NOTE_DURATION: 100 | current_note = None 101 | if playing: 102 | playhead_position += 1 103 | playhead_position %= width 104 | current_note = notes[playhead_position] 105 | if current_note is not None: 106 | current_note = height - current_note # Bottom = Low, Top = High 107 | current_note += 36 # Shift up the scale a couple of octaves 108 | current_freq = note_to_frequency(current_note) 109 | boopety_beepety.frequency(current_freq) 110 | boopety_beepety.trigger_attack() 111 | last_note_advance = time.ticks_ms() 112 | 113 | graphics.set_pen(black) 114 | graphics.clear() 115 | 116 | graphics.set_pen(playhead) 117 | graphics.line(playhead_position, 0, playhead_position, height) 118 | 119 | graphics.set_pen(cursor_bg) 120 | graphics.line(cursor_position[0], 0, cursor_position[0], height) 121 | 122 | graphics.set_pen(note) 123 | for x in range(width): 124 | y = notes[x] 125 | if y is not None: 126 | graphics.pixel(x, y) 127 | 128 | graphics.set_pen(cursor) 129 | graphics.pixel(*cursor_position) 130 | 131 | cu.update(graphics) 132 | -------------------------------------------------------------------------------- /nostalgia_prompt.py: -------------------------------------------------------------------------------- 1 | import time 2 | from cosmic import CosmicUnicorn 3 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 4 | 5 | ''' 6 | A collection of copies of classic terminal styles including 7 | C64, MS-DOS, Spectrum, and more. Images and text are drawn 8 | pixel by pixel from a pattern of Os and Xs. 9 | You can adjust the brightness with LUX + and -. 10 | ''' 11 | 12 | cu = CosmicUnicorn() 13 | graphics = PicoGraphics(DISPLAY) 14 | 15 | prompt_x = 0 16 | prompt_y = 4 17 | 18 | c64 = [ 19 | " ", 20 | " ", 21 | " OOOOO OOOOOO OO OOOO ", 22 | " OO OO OO OOOO OO OO ", 23 | " OO OO OO OO OO OO OO ", 24 | " OOOOO OOOO OOOOOO OO OO ", 25 | " OOOO OO OO OO OO OO ", 26 | " OO OO OO OO OO OO OO ", 27 | " OO OO OOOOOO OO OO OOOO ", 28 | " ", 29 | " ", 30 | " ", 31 | " ", 32 | " OO OO XXXXXXX ", 33 | " OO OO XXXXXXX ", 34 | " OO OO XXXXXXX ", 35 | " OOOO XXXXXXX ", 36 | " OO XXXXXXX ", 37 | " OO OO XXXXXXX ", 38 | " OO OO XXXXXXX ", 39 | " XXXXXXX ", 40 | " " 41 | ] 42 | FOREGROUND_C64 = (230, 210, 250) 43 | BACKGROUND_C64 = (20, 20, 120) 44 | 45 | spectrum = [ 46 | " ", 47 | " ", 48 | " O OOOO OOOO OOOOO ", 49 | " O O O O O O O ", 50 | " O O O O O O O ", 51 | " O O O OOOOOO O O ", 52 | " O O O O O O O ", 53 | " OOOOOO OOOO O O OOOOO ", 54 | " ", 55 | " ", 56 | " ", 57 | " ", 58 | " ", 59 | " O O O O XXXXXXXX ", 60 | " O O O O X XXXXXX ", 61 | " X XXXXXX ", 62 | " X XXXXXX ", 63 | " X XXXXXX ", 64 | " X XXXXXX ", 65 | " X X ", 66 | " XXXXXXXX ", 67 | " " 68 | ] 69 | FOREGROUND_SPECTRUM = (0, 0, 0) 70 | BACKGROUND_SPECTRUM = (180, 150, 150) 71 | 72 | bbc_micro = [ 73 | " ", 74 | " ", 75 | " OOOOO OO OOOO OOO ", 76 | " O O O O O O O ", 77 | " O O O O O O ", 78 | " OOOOO O O OOOO O ", 79 | " O O OOOOOO O O ", 80 | " O O O O O O O ", 81 | " OOOOO O O OOOO OOO ", 82 | " ", 83 | " ", 84 | " ", 85 | " ", 86 | " OOOO O ", 87 | " O O O ", 88 | " O O ", 89 | " O O ", 90 | " O O ", 91 | " O O O ", 92 | " OOOO O ", 93 | " XXXXXXX ", 94 | " " 95 | ] 96 | FOREGROUND_BBC_MICRO = (255, 255, 255) 97 | BACKGROUND_BBC_MICRO = (0, 0, 0) 98 | 99 | PROMPT_C64 = 0 100 | PROMPT_SPECTRUM = 1 101 | PROMPT_BBC_MICRO = 2 102 | prompt = 0 103 | 104 | 105 | @micropython.native # noqa: F821 106 | def draw(image, fg, bg, time_ms): 107 | fg_pen = graphics.create_pen(fg[0], fg[1], fg[2]) 108 | bg_pen = graphics.create_pen(bg[0], bg[1], bg[2]) 109 | graphics.set_pen(bg_pen) 110 | graphics.clear() 111 | for y in range(len(image)): 112 | row = image[y] 113 | for x in range(len(row)): 114 | pixel = row[x] 115 | # draw the prompt text 116 | if pixel == 'O': 117 | graphics.set_pen(fg_pen) 118 | 119 | # draw the caret blinking 120 | elif pixel == 'X' and (time_ms // 300) % 2: 121 | graphics.set_pen(fg_pen) 122 | 123 | else: 124 | graphics.set_pen(bg_pen) 125 | 126 | graphics.pixel(x + prompt_x, y + prompt_y) 127 | 128 | cu.update(graphics) 129 | 130 | 131 | cu.set_brightness(0.5) 132 | 133 | while True: 134 | 135 | time_ms = time.ticks_ms() 136 | prompt = (time_ms // 3000) % 3 137 | 138 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 139 | cu.adjust_brightness(+0.01) 140 | 141 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 142 | cu.adjust_brightness(-0.01) 143 | 144 | start = time.ticks_ms() 145 | 146 | if prompt == PROMPT_C64: 147 | draw(c64, FOREGROUND_C64, BACKGROUND_C64, time_ms) 148 | 149 | elif prompt == PROMPT_SPECTRUM: 150 | draw(spectrum, FOREGROUND_SPECTRUM, BACKGROUND_SPECTRUM, time_ms) 151 | 152 | elif prompt == PROMPT_BBC_MICRO: 153 | draw(bbc_micro, FOREGROUND_BBC_MICRO, BACKGROUND_BBC_MICRO, time_ms) 154 | 155 | # pause for a moment (important or the USB serial device will fail) 156 | time.sleep(0.001) 157 | 158 | print("total took: {} ms".format(time.ticks_ms() - start)) 159 | -------------------------------------------------------------------------------- /numpy/eighties_super_computer.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import time 3 | import random 4 | from cosmic import CosmicUnicorn, Channel 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 6 | from ulab import numpy 7 | 8 | """ 9 | A random, computer effect. 10 | Experiment with the damping, number of spawns and intensity to change the effect. 11 | """ 12 | 13 | # MAXIMUM OVERKILL 14 | # machine.freq(250_000_000) 15 | 16 | DAMPING_FACTOR = 0.95 17 | NUMBER_OF_LIGHTS = 10 18 | INTENSITY = 20 19 | 20 | volume = 0.5 21 | 22 | cu = CosmicUnicorn() 23 | cu.set_brightness(0.5) 24 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 25 | 26 | boopety_beepety = cu.synth_channel(0) 27 | boopety_beepety.configure( 28 | waveforms=Channel.SQUARE | Channel.SINE, 29 | attack=0.1, 30 | decay=0.1, 31 | sustain=0.0, 32 | release=0.5, 33 | volume=volume 34 | ) 35 | 36 | cu.play_synth() 37 | 38 | # Fill palette with a yellow 39 | r, g, b = (230, 150, 0) 40 | PALETTE_ENTRIES = 255 41 | for x in range(PALETTE_ENTRIES): 42 | _ = graphics.create_pen(r * x // PALETTE_ENTRIES, g * x // PALETTE_ENTRIES, b) 43 | 44 | 45 | def update(): 46 | computer[:] *= DAMPING_FACTOR 47 | 48 | # Spawn random drops 49 | for _ in range(NUMBER_OF_LIGHTS): 50 | x = random.randint(0, width - 1) 51 | y = random.randint(0, height - 1) 52 | computer[y][x] = random.randint(0, INTENSITY) 53 | 54 | 55 | def draw(): 56 | # Copy the effect to the framebuffer 57 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(computer, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes() 58 | cu.update(graphics) 59 | 60 | 61 | width = CosmicUnicorn.WIDTH 62 | height = CosmicUnicorn.HEIGHT 63 | computer = numpy.zeros((height, width)) 64 | 65 | t_count = 0 66 | t_total = 0 67 | 68 | 69 | while True: 70 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 71 | cu.adjust_brightness(+0.01) 72 | 73 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 74 | cu.adjust_brightness(-0.01) 75 | 76 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 77 | volume -= 0.1 78 | volume = max(0.0, volume) 79 | boopety_beepety.volume(volume) 80 | 81 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 82 | volume += 0.1 83 | volume = min(1.0, volume) 84 | boopety_beepety.volume(volume) 85 | 86 | tstart = time.ticks_ms() 87 | gc.collect() 88 | update() 89 | draw() 90 | tfinish = time.ticks_ms() 91 | 92 | # Play random notes between 100 and 880Hz for a computery effect 93 | boopety_beepety.frequency(random.randint(100, 880)) 94 | boopety_beepety.trigger_attack() 95 | 96 | total = tfinish - tstart 97 | t_total += total 98 | t_count += 1 99 | 100 | if t_count == 60: 101 | per_frame_avg = t_total / t_count 102 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 103 | t_count = 0 104 | t_total = 0 105 | 106 | # pause for a moment (important or the USB serial device will fail) 107 | # try to pace at 60fps or 30fps 108 | if total > 1000 / 30: 109 | time.sleep(0.0001) 110 | elif total > 1000 / 60: 111 | t = 1000 / 30 - total 112 | time.sleep(t / 1000) 113 | else: 114 | t = 1000 / 60 - total 115 | time.sleep(t / 1000) 116 | -------------------------------------------------------------------------------- /numpy/fire_effect.py: -------------------------------------------------------------------------------- 1 | import time 2 | import gc 3 | import random 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 6 | from ulab import numpy 7 | 8 | """ 9 | Classic fire effect. 10 | Play with the number of spawns, heat, damping factor and colour palette to tweak it. 11 | """ 12 | 13 | # MAXIMUM OVERKILL 14 | # machine.freq(250_000_000) 15 | 16 | cu = CosmicUnicorn() 17 | cu.set_brightness(0.5) 18 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 19 | 20 | # Number of random fire spawns 21 | FIRE_SPAWNS = 5 22 | 23 | # Fire damping 24 | DAMPING_FACTOR = 0.98 25 | 26 | # TURN UP THE HEEEAAT 27 | HEAT = 3.0 28 | 29 | # Create the fire palette 30 | """ 31 | # Raging Gas Inferno 32 | graphics.create_pen(0, 0, 0) 33 | graphics.create_pen(0, 0, 0) 34 | graphics.create_pen(20, 20, 20) 35 | graphics.create_pen(50, 10, 0) 36 | graphics.create_pen(180, 30, 0) 37 | graphics.create_pen(220, 160, 0) 38 | graphics.create_pen(255, 255, 180) 39 | graphics.create_pen(255, 255, 220) 40 | graphics.create_pen(90, 90, 255) 41 | graphics.create_pen(255, 0, 255) 42 | """ 43 | # Original Colours 44 | graphics.create_pen(0, 0, 0) 45 | graphics.create_pen(20, 20, 20) 46 | graphics.create_pen(180, 30, 0) 47 | graphics.create_pen(220, 160, 0) 48 | graphics.create_pen(255, 255, 180) 49 | 50 | PALETTE_SIZE = 5 # Should match the number of colours defined above 51 | 52 | 53 | def update(): 54 | # Clear the bottom two rows (off screen) 55 | heat[height - 1][:] = 0.0 56 | heat[height - 2][:] = 0.0 57 | 58 | # Add random fire spawns 59 | for c in range(FIRE_SPAWNS): 60 | x = random.randint(0, width - 4) + 2 61 | heat[height - 1][x - 1:x + 1] = HEAT / 2.0 62 | heat[height - 2][x - 1:x + 1] = HEAT 63 | 64 | # Propagate the fire upwards 65 | a = numpy.roll(heat, -1, axis=0) # y + 1, x 66 | b = numpy.roll(heat, -2, axis=0) # y + 2, x 67 | c = numpy.roll(heat, -1, axis=0) # y + 1 68 | d = numpy.roll(c, 1, axis=1) # y + 1, x + 1 69 | e = numpy.roll(c, -1, axis=1) # y + 1, x - 1 70 | 71 | # Average over 5 adjacent pixels and apply damping 72 | heat[:] += a + b + d + e 73 | heat[:] *= DAMPING_FACTOR / 5.0 74 | 75 | 76 | def draw(): 77 | # Copy the fire effect to the framebuffer 78 | # Clips the fire to 0.0 to 1.0 79 | # Multiplies it by the number of palette entries (-1) to turn it into a palette index 80 | # Converts to uint8_t datatype to match the framebuffer 81 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:32, 0:32], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes() 82 | cu.update(graphics) 83 | 84 | 85 | width = CosmicUnicorn.WIDTH 86 | height = CosmicUnicorn.HEIGHT + 4 87 | heat = numpy.zeros((height, width)) 88 | 89 | t_count = 0 90 | t_total = 0 91 | 92 | while True: 93 | gc.collect() 94 | 95 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 96 | cu.adjust_brightness(+0.01) 97 | 98 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 99 | cu.adjust_brightness(-0.01) 100 | 101 | tstart = time.ticks_ms() 102 | gc.collect() 103 | update() 104 | draw() 105 | tfinish = time.ticks_ms() 106 | 107 | total = tfinish - tstart 108 | t_total += total 109 | t_count += 1 110 | 111 | if t_count == 60: 112 | per_frame_avg = t_total / t_count 113 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 114 | t_count = 0 115 | t_total = 0 116 | 117 | # pause for a moment (important or the USB serial device will fail) 118 | # try to pace at 60fps or 30fps 119 | if total > 1000 / 30: 120 | time.sleep(0.0001) 121 | elif total > 1000 / 60: 122 | t = 1000 / 30 - total 123 | time.sleep(t / 1000) 124 | else: 125 | t = 1000 / 60 - total 126 | time.sleep(t / 1000) 127 | -------------------------------------------------------------------------------- /numpy/lava_lamp.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import time 3 | import math 4 | import random 5 | from cosmic import CosmicUnicorn 6 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 7 | from ulab import numpy 8 | 9 | """ 10 | A lava lamp effect, created by blurred, moving particles. 11 | """ 12 | 13 | # MAXIMUM OVERKILL 14 | # machine.freq(250_000_000) 15 | 16 | cu = CosmicUnicorn() 17 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 18 | cu.set_brightness(0.5) 19 | 20 | width = CosmicUnicorn.WIDTH 21 | height = CosmicUnicorn.HEIGHT 22 | lava = numpy.zeros((height, width)) 23 | 24 | 25 | class Blob(): 26 | def __init__(self): 27 | self.x = float(random.randint(0, width - 1)) 28 | self.y = float(random.randint(0, height - 1)) 29 | self.r = (float(random.randint(0, 40)) / 10.0) + 5.0 30 | self.dx = (float(random.randint(0, 2)) / 20.0) - 0.05 31 | self.dy = (float(random.randint(0, 2)) / 20.0) - 0.025 # positive bias 32 | 33 | def move(self): 34 | self.x += self.dx 35 | self.y += self.dy 36 | 37 | if self.x < 0.0 or self.x >= float(width): 38 | self.x = max(0.0, self.x) 39 | self.x = min(float(width - 1), self.x) 40 | self.dx = -self.dx 41 | 42 | if self.y < 0.0 or self.y >= float(height): 43 | self.y = max(0.0, self.y) 44 | self.y = min(float(height - 1), self.y) 45 | self.dy = -self.dy 46 | 47 | 48 | blobs = [Blob() for _ in range(10)] 49 | 50 | 51 | # Fill palette with a steep falloff from bright red to dark blue 52 | PAL_COLS = 9 53 | for x in range(PAL_COLS): 54 | graphics.create_pen_hsv(0.5 + math.log(x + 1, PAL_COLS + 1) / 2.0, 1.0, math.log(x + 1, PAL_COLS + 1)) 55 | 56 | 57 | def update(): 58 | # Update the blobs and draw them into the effect 59 | for blob in blobs: 60 | blob.move() 61 | lava[int(blob.y)][int(blob.x)] = blob.r 62 | 63 | # Propogate the blobs outwards 64 | a = numpy.roll(lava, 1, axis=0) 65 | b = numpy.roll(lava, -1, axis=0) 66 | d = numpy.roll(lava, 1, axis=1) 67 | e = numpy.roll(lava, -1, axis=1) 68 | 69 | # Average over 5 adjacent pixels and apply damping 70 | lava[:] += a + b + d + e 71 | lava[:] *= 0.97 / 5.0 72 | 73 | 74 | def draw(): 75 | # Copy the lava effect to the framebuffer 76 | # Clips to 0.0 - 1.0 77 | # Multiplies by palette entries (-1) to turn it into a palette index 78 | # Converts to uint8_t datatype to match the framebuffer 79 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(lava, 0.0, 1.0) * (PAL_COLS - 1), dtype=numpy.uint8).tobytes() 80 | cu.update(graphics) 81 | 82 | 83 | t_count = 0 84 | t_total = 0 85 | 86 | while True: 87 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 88 | cu.adjust_brightness(+0.01) 89 | 90 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 91 | cu.adjust_brightness(-0.01) 92 | 93 | tstart = time.ticks_ms() 94 | gc.collect() 95 | update() 96 | draw() 97 | tfinish = time.ticks_ms() 98 | 99 | total = tfinish - tstart 100 | t_total += total 101 | t_count += 1 102 | 103 | if t_count == 60: 104 | per_frame_avg = t_total / t_count 105 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 106 | t_count = 0 107 | t_total = 0 108 | 109 | # pause for a moment (important or the USB serial device will fail) 110 | # try to pace at 60fps or 30fps 111 | if total > 1000 / 30: 112 | time.sleep(0.0001) 113 | elif total > 1000 / 60: 114 | t = 1000 / 30 - total 115 | time.sleep(t / 1000) 116 | else: 117 | t = 1000 / 60 - total 118 | time.sleep(t / 1000) 119 | -------------------------------------------------------------------------------- /numpy/life.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import time 3 | import random 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 6 | from ulab import numpy 7 | 8 | """ 9 | A randomly-seeded game-of-life cellular automata effect. 10 | Experiment with the values below to change the effect. 11 | 12 | Press "A" to manually re-seed. 13 | """ 14 | 15 | # MAXIMUM OVERKILL 16 | # machine.freq(250_000_000) 17 | 18 | INITIAL_LIFE = 500 # Number of live cells to seed 19 | GENERATION_TIME_MS = 50 # MS between generations 20 | MINIMUM_LIFE = 10 # Auto reseed when only this many alive cells remain 21 | SMOOTHED = True # Enable for a more organic if somewhat unsettling feel 22 | 23 | DECAY = 0.90 # Rate at which smoothing effect decays, higher number = more persistent, 1.0 = no decay 24 | TENACITY = 32 # Rate at which smoothing effect increases 25 | 26 | cu = CosmicUnicorn() 27 | cu.set_brightness(0.5) 28 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 29 | 30 | for c in range(256): 31 | graphics.create_pen(c // 2, 0, c) 32 | 33 | 34 | def update(): 35 | global last_gen 36 | 37 | if SMOOTHED: 38 | duration[:] += life * TENACITY 39 | duration[:] *= DECAY 40 | 41 | if time.ticks_ms() - last_gen < GENERATION_TIME_MS: 42 | return 43 | 44 | last_gen = time.ticks_ms() 45 | 46 | if numpy.sum(life) < MINIMUM_LIFE: 47 | seed_life() 48 | return 49 | 50 | # Rollin' rollin' rollin. 51 | _N = numpy.roll(life, -1, axis=0) 52 | _NW = numpy.roll(_N, -1, axis=1) 53 | _NE = numpy.roll(_N, 1, axis=1) 54 | _S = numpy.roll(life, 1, axis=0) 55 | _SW = numpy.roll(_S, -1, axis=1) 56 | _SE = numpy.roll(_S, 1, axis=1) 57 | _W = numpy.roll(life, -1, axis=1) 58 | _E = numpy.roll(life, 1, axis=1) 59 | 60 | # Compute the total neighbours for each cell 61 | neighbours[:] = _N + _NW + _NE + _S + _SW + _SE + _W + _E 62 | 63 | next_generation[:] = life[:] 64 | 65 | # Any cells with exactly three neighbours should always stay alive 66 | next_generation[:] += neighbours[:] == 3 67 | 68 | # Any alive cells with less than two neighbours should die 69 | next_generation[:] -= (neighbours[:] < 2) * life 70 | 71 | # Any alive cells with more than three neighbours should die 72 | next_generation[:] -= (neighbours[:] > 3) * life 73 | 74 | life[:] = numpy.clip(next_generation, 0, 1) 75 | 76 | 77 | def draw(): 78 | # Copy the effect to the framebuffer 79 | if SMOOTHED: 80 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(duration, 0, 255), dtype=numpy.uint8).tobytes() 81 | else: 82 | memoryview(graphics)[:] = numpy.ndarray(life * 255, dtype=numpy.uint8).tobytes() 83 | cu.update(graphics) 84 | 85 | 86 | def seed_life(): 87 | for _ in range(INITIAL_LIFE): 88 | x = random.randint(0, width - 1) 89 | y = random.randint(0, height - 1) 90 | life[y][x] = int(True) # Avoid: TypeError: 'bool' object isn't iterable 91 | 92 | 93 | width = CosmicUnicorn.WIDTH 94 | height = CosmicUnicorn.HEIGHT 95 | life = numpy.zeros((height, width), dtype=numpy.bool) 96 | next_generation = numpy.zeros((height, width), dtype=numpy.bool) 97 | neighbours = numpy.zeros((height, width), dtype=numpy.uint8) 98 | duration = numpy.zeros((height, width)) 99 | last_gen = time.ticks_ms() 100 | 101 | t_count = 0 102 | t_total = 0 103 | 104 | seed_life() 105 | 106 | 107 | while True: 108 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 109 | cu.adjust_brightness(+0.01) 110 | 111 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 112 | cu.adjust_brightness(-0.01) 113 | 114 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 115 | life[:] = int(False) 116 | 117 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 118 | SMOOTHED = not SMOOTHED 119 | 120 | tstart = time.ticks_ms() 121 | gc.collect() 122 | update() 123 | draw() 124 | tfinish = time.ticks_ms() 125 | 126 | total = tfinish - tstart 127 | t_total += total 128 | t_count += 1 129 | 130 | if t_count == 60: 131 | per_frame_avg = t_total / t_count 132 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 133 | t_count = 0 134 | t_total = 0 135 | 136 | # pause for a moment (important or the USB serial device will fail) 137 | # try to pace at 60fps or 30fps 138 | if total > 1000 / 30: 139 | time.sleep(0.0001) 140 | elif total > 1000 / 60: 141 | t = 1000 / 30 - total 142 | time.sleep(t / 1000) 143 | else: 144 | t = 1000 / 60 - total 145 | time.sleep(t / 1000) 146 | -------------------------------------------------------------------------------- /numpy/rgb_channels.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import time 3 | from cosmic import CosmicUnicorn 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_RGB888 5 | from ulab import numpy 6 | 7 | """ 8 | This example demonstrates how to work with full RGB888 colour in ulab/numpy. 9 | 10 | Each colour channel is given its own array, and these are combined before 11 | copying them into the PicoGraphics buffer. 12 | 13 | At great cost to performance (about half the speed) this example works in 14 | floating point 0.0 to 1.0 and converts the result to 8bits per channel. 15 | """ 16 | 17 | # MAXIMUM OVERKILL 18 | # machine.freq(250_000_000) 19 | 20 | cu = CosmicUnicorn() 21 | cu.set_brightness(0.5) 22 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_RGB888) 23 | 24 | 25 | def update(): 26 | # Do something basic with the colour channels 27 | # to prove this actually works. 28 | red[:] = numpy.roll(red, 1, axis=1) 29 | green[:] *= 0.999 # Slowly desaturate green 30 | blue[:] *= 1.001 # Slowly saturate blue 31 | 32 | 33 | def draw(): 34 | # Copy the red, green, blue channels into 35 | # their respective places in the RGB_ array 36 | rgb[2::4] = red.flatten() 37 | rgb[1::4] = green.flatten() 38 | rgb[0::4] = blue.flatten() 39 | 40 | # Convert the results to 8bit RGB and copy to the framebuffer 41 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(rgb, 0, 1) * 255, dtype=numpy.uint8).tobytes() 42 | 43 | # Copy the framebuffer to Cosmic 44 | cu.update(graphics) 45 | # Whew! 46 | 47 | 48 | width, height = graphics.get_bounds() 49 | 50 | # Individual channels 51 | red = numpy.zeros((height, width)) 52 | green = numpy.zeros((height, width)) 53 | blue = numpy.zeros((height, width)) 54 | 55 | # Reserved for combined channels 56 | rgb = numpy.zeros((width * height * 4),) 57 | 58 | # Stick some gradients in the channels so we have something to look at 59 | red[::] = numpy.linspace(0, 1, width) 60 | 61 | # There has to be a better way!? 62 | for x in range(width): 63 | green[::, x] = numpy.linspace(0, 1, width) 64 | blue[::, x] = numpy.linspace(1, 0, width,) 65 | 66 | t_count = 0 67 | t_total = 0 68 | 69 | while True: 70 | tstart = time.ticks_ms() 71 | gc.collect() 72 | update() 73 | draw() 74 | tfinish = time.ticks_ms() 75 | 76 | total = tfinish - tstart 77 | t_total += total 78 | t_count += 1 79 | 80 | if t_count == 60: 81 | per_frame_avg = t_total / t_count 82 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 83 | t_count = 0 84 | t_total = 0 85 | 86 | # pause for a moment (important or the USB serial device will fail) 87 | # try to pace at 60fps or 30fps 88 | if total > 1000 / 30: 89 | time.sleep(0.0001) 90 | elif total > 1000 / 60: 91 | t = 1000 / 30 - total 92 | time.sleep(t / 1000) 93 | else: 94 | t = 1000 / 60 - total 95 | time.sleep(t / 1000) 96 | -------------------------------------------------------------------------------- /numpy/the_matrix.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import time 3 | import random 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 6 | from ulab import numpy 7 | 8 | """ 9 | HELLO NEO. 10 | """ 11 | 12 | # MAXIMUM OVERKILL 13 | # machine.freq(250_000_000) 14 | 15 | cu = CosmicUnicorn() 16 | cu.set_brightness(1.0) 17 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 18 | 19 | 20 | # Fill half the palette with GREEEN 21 | for g in range(128): 22 | _ = graphics.create_pen(0, g, 0) 23 | 24 | # And half with bright green for white sparkles 25 | for g in range(128): 26 | _ = graphics.create_pen(128, 128 + g, 128) 27 | 28 | 29 | def update(): 30 | trippy[:] *= 0.65 31 | 32 | for _ in range(2): 33 | x = random.randint(0, width - 1) 34 | y = random.randint(0, height // 2) 35 | trippy[y][x] = random.randint(128, 255) / 255.0 36 | 37 | # Propagate downwards 38 | old = numpy.ndarray(trippy) * 0.5 39 | trippy[:] = numpy.roll(trippy, 1, axis=0) 40 | trippy[:] += old 41 | 42 | 43 | def draw(): 44 | # Copy the effect to the framebuffer 45 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * 254, dtype=numpy.uint8).tobytes() 46 | cu.update(graphics) 47 | 48 | 49 | width = CosmicUnicorn.WIDTH 50 | height = CosmicUnicorn.HEIGHT 51 | trippy = numpy.zeros((height, width)) 52 | 53 | t_count = 0 54 | t_total = 0 55 | 56 | 57 | while True: 58 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 59 | cu.adjust_brightness(+0.01) 60 | 61 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 62 | cu.adjust_brightness(-0.01) 63 | 64 | tstart = time.ticks_ms() 65 | gc.collect() 66 | update() 67 | draw() 68 | tfinish = time.ticks_ms() 69 | 70 | total = tfinish - tstart 71 | t_total += total 72 | t_count += 1 73 | 74 | if t_count == 60: 75 | per_frame_avg = t_total / t_count 76 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 77 | t_count = 0 78 | t_total = 0 79 | 80 | # pause for a moment (important or the USB serial device will fail) 81 | # try to pace at 60fps or 30fps 82 | if total > 1000 / 30: 83 | time.sleep(0.0001) 84 | elif total > 1000 / 60: 85 | t = 1000 / 30 - total 86 | time.sleep(t / 1000) 87 | else: 88 | t = 1000 / 60 - total 89 | time.sleep(t / 1000) 90 | -------------------------------------------------------------------------------- /numpy/this_is_fine.py: -------------------------------------------------------------------------------- 1 | import time 2 | import gc 3 | import random 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 6 | from ulab import numpy 7 | 8 | """ 9 | THIS IS FINE! 10 | """ 11 | 12 | # MAXIMUM OVERKILL 13 | # machine.freq(250_000_000) 14 | 15 | cu = CosmicUnicorn() 16 | cu.set_brightness(0.5) 17 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 18 | 19 | # Number of random fire spawns 20 | FIRE_SPAWNS = 5 21 | 22 | # Fire damping 23 | DAMPING_FACTOR = 0.98 24 | 25 | # TURN UP THE HEEEAAT 26 | HEAT = 3.0 27 | 28 | # Create the fire palette 29 | """ 30 | # Raging Gas Inferno 31 | graphics.create_pen(0, 0, 0) 32 | graphics.create_pen(0, 0, 0) 33 | graphics.create_pen(20, 20, 20) 34 | graphics.create_pen(50, 10, 0) 35 | graphics.create_pen(180, 30, 0) 36 | graphics.create_pen(220, 160, 0) 37 | graphics.create_pen(255, 255, 180) 38 | graphics.create_pen(255, 255, 220) 39 | graphics.create_pen(90, 90, 255) 40 | graphics.create_pen(255, 0, 255) 41 | """ 42 | # Original Colours 43 | graphics.create_pen(0, 0, 0) 44 | graphics.create_pen(20, 20, 20) 45 | graphics.create_pen(180, 30, 0) 46 | graphics.create_pen(220, 160, 0) 47 | graphics.create_pen(255, 255, 180) 48 | 49 | PALETTE_SIZE = 5 # Should match the number of colours defined above 50 | 51 | 52 | def update(): 53 | # Clear the bottom two rows (off screen) 54 | heat[height - 1][:] = 0.0 55 | heat[height - 2][:] = 0.0 56 | 57 | # Add random fire spawns 58 | for c in range(FIRE_SPAWNS): 59 | x = random.randint(0, width - 4) + 2 60 | heat[height - 1][x - 1:x + 1] = HEAT / 2.0 61 | heat[height - 2][x - 1:x + 1] = HEAT 62 | 63 | # Propagate the fire upwards 64 | a = numpy.roll(heat, -1, axis=0) # y + 1, x 65 | b = numpy.roll(heat, -2, axis=0) # y + 2, x 66 | c = numpy.roll(heat, -1, axis=0) # y + 1 67 | d = numpy.roll(c, 1, axis=1) # y + 1, x + 1 68 | e = numpy.roll(c, -1, axis=1) # y + 1, x - 1 69 | 70 | # Average over 5 adjacent pixels and apply damping 71 | heat[:] += a + b + d + e 72 | heat[:] *= DAMPING_FACTOR / 5.0 73 | 74 | 75 | def draw(): 76 | # Copy the fire effect to the framebuffer 77 | # Clips the fire to 0.0 to 1.0 78 | # Multiplies it by the number of palette entries (-1) to turn it into a palette index 79 | # Converts to uint8_t datatype to match the framebuffer 80 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(heat[0:32, 0:32], 0, 1) * (PALETTE_SIZE - 1), dtype=numpy.uint8).tobytes() 81 | 82 | # Draw text over the top 83 | graphics.set_pen(0) 84 | graphics.text("This", 6, 4, 1, 1) 85 | graphics.text("is", 11, 12, 1, 1) 86 | graphics.text("fine", 6, 20, 1, 1) 87 | cu.update(graphics) 88 | 89 | 90 | width = CosmicUnicorn.WIDTH 91 | height = CosmicUnicorn.HEIGHT + 4 92 | heat = numpy.zeros((height, width)) 93 | 94 | t_count = 0 95 | t_total = 0 96 | 97 | while True: 98 | gc.collect() 99 | 100 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 101 | cu.adjust_brightness(+0.01) 102 | 103 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 104 | cu.adjust_brightness(-0.01) 105 | 106 | tstart = time.ticks_ms() 107 | gc.collect() 108 | update() 109 | draw() 110 | tfinish = time.ticks_ms() 111 | 112 | total = tfinish - tstart 113 | t_total += total 114 | t_count += 1 115 | 116 | if t_count == 60: 117 | per_frame_avg = t_total / t_count 118 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 119 | t_count = 0 120 | t_total = 0 121 | 122 | # pause for a moment (important or the USB serial device will fail) 123 | # try to pace at 60fps or 30fps 124 | if total > 1000 / 30: 125 | time.sleep(0.0001) 126 | elif total > 1000 / 60: 127 | t = 1000 / 30 - total 128 | time.sleep(t / 1000) 129 | else: 130 | t = 1000 / 60 - total 131 | time.sleep(t / 1000) 132 | -------------------------------------------------------------------------------- /numpy/trippy.py: -------------------------------------------------------------------------------- 1 | import gc 2 | import time 3 | import random 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN, PEN_P8 6 | from ulab import numpy 7 | 8 | """ 9 | A random, trippy effect. 10 | Experiment with the damping, number of spawns, intensity and offset to change the effect. 11 | """ 12 | 13 | # MAXIMUM OVERKILL 14 | # machine.freq(250_000_000) 15 | 16 | cu = CosmicUnicorn() 17 | cu.set_brightness(0.5) 18 | graphics = PicoGraphics(DISPLAY_COSMIC_UNICORN, pen_type=PEN_P8) 19 | 20 | 21 | DAMPING_FACTOR = 0.95 22 | NUMBER_OF_DROPS = 5 23 | INTENSITY = 10 24 | OFFSET = 0.0 # Try 0.5 25 | 26 | # Fill palette with a rainbow sweep 27 | PALETTE_ENTRIES = 255 28 | for x in range(PALETTE_ENTRIES): 29 | _ = graphics.create_pen_hsv(float(x) / PALETTE_ENTRIES + OFFSET, 1.0, 1.0) 30 | 31 | 32 | def update(): 33 | trippy[:] *= DAMPING_FACTOR 34 | 35 | # Spawn random drops 36 | for _ in range(NUMBER_OF_DROPS): 37 | x = random.randint(0, width - 1) 38 | y = random.randint(0, height - 1) 39 | trippy[y][x] = random.randint(0, INTENSITY) 40 | 41 | a = numpy.roll(trippy, 1, axis=0) 42 | b = numpy.roll(trippy, -1, axis=0) 43 | d = numpy.roll(trippy, 1, axis=1) 44 | e = numpy.roll(trippy, -1, axis=1) 45 | 46 | # Average over 5 adjacent pixels and apply damping 47 | trippy[:] += a + b + d + e 48 | trippy[:] /= 5.0 49 | 50 | 51 | def draw(): 52 | # Copy the effect to the framebuffer 53 | memoryview(graphics)[:] = numpy.ndarray(numpy.clip(trippy, 0, 1) * (PALETTE_ENTRIES - 1), dtype=numpy.uint8).tobytes() 54 | cu.update(graphics) 55 | 56 | 57 | width = CosmicUnicorn.WIDTH 58 | height = CosmicUnicorn.HEIGHT 59 | trippy = numpy.zeros((height, width)) 60 | 61 | t_count = 0 62 | t_total = 0 63 | 64 | 65 | while True: 66 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 67 | cu.adjust_brightness(+0.01) 68 | 69 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 70 | cu.adjust_brightness(-0.01) 71 | 72 | tstart = time.ticks_ms() 73 | gc.collect() 74 | update() 75 | draw() 76 | tfinish = time.ticks_ms() 77 | 78 | total = tfinish - tstart 79 | t_total += total 80 | t_count += 1 81 | 82 | if t_count == 60: 83 | per_frame_avg = t_total / t_count 84 | print(f"60 frames in {t_total}ms, avg {per_frame_avg:.02f}ms per frame, {1000/per_frame_avg:.02f} FPS") 85 | t_count = 0 86 | t_total = 0 87 | 88 | # pause for a moment (important or the USB serial device will fail) 89 | # try to pace at 60fps or 30fps 90 | if total > 1000 / 30: 91 | time.sleep(0.0001) 92 | elif total > 1000 / 60: 93 | t = 1000 / 30 - total 94 | time.sleep(t / 1000) 95 | else: 96 | t = 1000 / 60 - total 97 | time.sleep(t / 1000) 98 | -------------------------------------------------------------------------------- /rainbow.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | from cosmic import CosmicUnicorn 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 5 | 6 | ''' 7 | Some good old fashioned rainbows! 8 | 9 | You can adjust the cycling speed with A and B, 10 | stripe width with C and D, hue with VOL + and -, 11 | and the brightness with LUX + and -. 12 | The sleep button stops the animation (can be started again with A or B). 13 | ''' 14 | 15 | cu = CosmicUnicorn() 16 | graphics = PicoGraphics(DISPLAY) 17 | 18 | width = CosmicUnicorn.WIDTH 19 | height = CosmicUnicorn.HEIGHT 20 | 21 | 22 | @micropython.native # noqa: F821 23 | def from_hsv(h, s, v): 24 | i = math.floor(h * 6.0) 25 | f = h * 6.0 - i 26 | v *= 255.0 27 | p = v * (1.0 - s) 28 | q = v * (1.0 - f * s) 29 | t = v * (1.0 - (1.0 - f) * s) 30 | 31 | i = int(i) % 6 32 | if i == 0: 33 | return int(v), int(t), int(p) 34 | if i == 1: 35 | return int(q), int(v), int(p) 36 | if i == 2: 37 | return int(p), int(v), int(t) 38 | if i == 3: 39 | return int(p), int(q), int(v) 40 | if i == 4: 41 | return int(t), int(p), int(v) 42 | if i == 5: 43 | return int(v), int(p), int(q) 44 | 45 | 46 | @micropython.native # noqa: F821 47 | def draw(): 48 | global hue_offset, phase 49 | phase_percent = phase / 15 50 | for x in range(width): 51 | colour = hue_map[int((x + (hue_offset * width)) % width)] 52 | for y in range(height): 53 | v = ((math.sin((x + y) / stripe_width + phase_percent) + 1.5) / 2.5) 54 | 55 | graphics.set_pen(graphics.create_pen(int(colour[0] * v), int(colour[1] * v), int(colour[2] * v))) 56 | graphics.pixel(x, y) 57 | 58 | cu.update(graphics) 59 | 60 | 61 | hue_map = [from_hsv(x / width, 1.0, 1.0) for x in range(width)] 62 | hue_offset = 0.0 63 | 64 | animate = True 65 | stripe_width = 3.0 66 | speed = 1.0 67 | 68 | cu.set_brightness(0.5) 69 | 70 | phase = 0 71 | while True: 72 | 73 | if animate: 74 | phase += speed 75 | 76 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 77 | hue_offset += 0.01 78 | hue_offset = 1.0 if hue_offset > 1.0 else hue_offset 79 | 80 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 81 | hue_offset -= 0.01 82 | hue_offset = 0.0 if hue_offset < 0.0 else hue_offset 83 | 84 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 85 | cu.adjust_brightness(+0.01) 86 | 87 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 88 | cu.adjust_brightness(-0.01) 89 | 90 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 91 | animate = False 92 | 93 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 94 | speed += 0.05 95 | speed = 10.0 if speed > 10.0 else speed 96 | animate = True 97 | 98 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 99 | speed -= 0.05 100 | speed = 0.0 if speed < 0.0 else speed 101 | animate = True 102 | 103 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 104 | stripe_width += 0.05 105 | stripe_width = 10.0 if stripe_width > 10.0 else stripe_width 106 | 107 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 108 | stripe_width -= 0.05 109 | stripe_width = 1.0 if stripe_width < 1.0 else stripe_width 110 | 111 | start = time.ticks_ms() 112 | 113 | draw() 114 | 115 | print("total took: {} ms".format(time.ticks_ms() - start)) 116 | -------------------------------------------------------------------------------- /ready_set_go.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | from cosmic import CosmicUnicorn, Channel 4 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 5 | 6 | ''' 7 | Displays some text, gradients and colours and demonstrates button use. 8 | 9 | You can adjust the brightness with LUX + and -. 10 | ''' 11 | 12 | cu = CosmicUnicorn() 13 | graphics = PicoGraphics(DISPLAY) 14 | 15 | width = CosmicUnicorn.WIDTH 16 | height = CosmicUnicorn.HEIGHT 17 | 18 | seconds = -1 19 | 20 | boopety_beepety = cu.synth_channel(0) 21 | # boopety_beepety.configure( 22 | # waveforms=Channel.SQUARE | Channel.SINE, 23 | # attack=0.1, 24 | # decay=0.1, 25 | # sustain=0, 26 | # release=0.1, 27 | # volume=1.0 28 | # ) 29 | 30 | # Configure the beeper waveform and envelope 31 | boopety_beepety.configure( 32 | waveforms=Channel.SINE + Channel.SQUARE, 33 | attack=0.05, 34 | decay=0.5, 35 | sustain=0, 36 | release=0, 37 | volume=1.0 38 | ) 39 | boopety_beepety.frequency(262) 40 | 41 | cu.play_synth() 42 | 43 | last_action = time.ticks_ms() 44 | 45 | def debounce(duration=1000): 46 | global last_action 47 | if time.ticks_ms() - last_action > duration: 48 | last_action = time.ticks_ms() 49 | return True 50 | return False 51 | 52 | def gradient(r, g, b): 53 | for y in range(0, height): 54 | for x in range(0, width): 55 | graphics.set_pen(graphics.create_pen(int((r * (height - y)) / 32), int((g * (height - y)) / 32), int((b * (height - y)) / 32))) 56 | graphics.pixel(x, y) 57 | 58 | 59 | def grid(r, g, b): 60 | for y in range(0, height): 61 | for x in range(0, width): 62 | if (x + y) % 2 == 0: 63 | graphics.set_pen(graphics.create_pen(r, g, b)) 64 | else: 65 | graphics.set_pen(0) 66 | graphics.pixel(x, y) 67 | 68 | def grid_gradient(r, g, b): 69 | for y in range(0, height): 70 | for x in range(0, width): 71 | if (x + y) % 2 == 0: 72 | graphics.set_pen(graphics.create_pen(int((r * (height - y)) / 32), int((g * (height - y)) / 32), int((b * (height - y)) / 32))) 73 | else: 74 | graphics.set_pen(0) 75 | graphics.pixel(x, y) 76 | 77 | def outline_text(text): 78 | ms = time.ticks_ms() 79 | 80 | graphics.set_font("bitmap8") 81 | v = int((math.sin(ms / 100.0) + 1.0) * 127.0) 82 | w = graphics.measure_text(text, 1) 83 | 84 | x = int(32 / 2 - w / 2 + 1) 85 | y = 12 86 | 87 | graphics.set_pen(0) 88 | graphics.text(text, x - 1, y - 1, -1, 1) 89 | graphics.text(text, x, y - 1, -1, 1) 90 | graphics.text(text, x + 1, y - 1, -1, 1) 91 | graphics.text(text, x - 1, y, -1, 1) 92 | graphics.text(text, x + 1, y, -1, 1) 93 | graphics.text(text, x - 1, y + 1, -1, 1) 94 | graphics.text(text, x, y + 1, -1, 1) 95 | graphics.text(text, x + 1, y + 1, -1, 1) 96 | 97 | graphics.set_pen(graphics.create_pen(v, v, v)) 98 | graphics.text(text, x, y, -1, 1) 99 | 100 | def outline_text_lower(text): 101 | graphics.set_font("bitmap6") 102 | 103 | w = graphics.measure_text(text, 1) 104 | 105 | # x = int(32 / 2 - w / 2 + 1) 106 | x = 1 107 | y = 26 108 | 109 | graphics.set_pen(0) 110 | graphics.text(text, x - 1, y - 1, -1, 1) 111 | graphics.text(text, x, y - 1, -1, 1) 112 | graphics.text(text, x + 1, y - 1, -1, 1) 113 | graphics.text(text, x - 1, y, -1, 1) 114 | graphics.text(text, x + 1, y, -1, 1) 115 | graphics.text(text, x - 1, y + 1, -1, 1) 116 | graphics.text(text, x, y + 1, -1, 1) 117 | graphics.text(text, x + 1, y + 1, -1, 1) 118 | 119 | graphics.set_pen(graphics.create_pen(255, 255, 255)) 120 | graphics.text(text, x, y, -1, 1) 121 | 122 | cu.set_brightness(0.5) 123 | start = time.ticks_ms() 124 | 125 | while True: 126 | 127 | # elapsed = (time.clock() - start) 128 | 129 | # elapsed 130 | time_ms = time.ticks_ms() - start 131 | if seconds != -1: 132 | seconds = (time_ms // 1000) % 5 133 | 134 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 135 | cu.adjust_brightness(+0.01) 136 | 137 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 138 | cu.adjust_brightness(-0.01) 139 | 140 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 141 | graphics.clear() 142 | 143 | text = "" 144 | timer_text = str(time_ms)[3:] 145 | 146 | if seconds == 0: 147 | print("grid pattern") 148 | grid(255, 255, 255) 149 | elif seconds == 1: 150 | print("red gradient") 151 | text = "Ready" 152 | gradient(255, 0, 0) 153 | elif seconds == 2: 154 | print("red gradient") 155 | text = "Ready" 156 | gradient(255, 0, 0) 157 | elif seconds == 3: 158 | print("yellow gradient") 159 | text = "Set" 160 | gradient(255, 255, 0) 161 | elif seconds == 4: 162 | print("green gradient") 163 | text = "Go" 164 | gradient(0, 255, 0) 165 | boopety_beepety.frequency(523) 166 | 167 | elif seconds == 5: 168 | print("green gradient") 169 | text = "Go" 170 | gradient(0, 255, 0) 171 | elif seconds == 5: 172 | print("white gradient") 173 | gradient(255, 255, 255) 174 | elif seconds == -1: 175 | print("gray gradient") 176 | grid_gradient(100, 100, 100) 177 | boopety_beepety.frequency(131) 178 | 179 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 180 | text = "Start" 181 | seconds = 0 182 | 183 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 184 | text = "Stop" 185 | seconds = -1 186 | 187 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 188 | text = "C" 189 | 190 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 191 | text = "D" 192 | 193 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 194 | text = "Louder!" 195 | 196 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 197 | text = "Quieter" 198 | 199 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 200 | text = "Brighter!" 201 | 202 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 203 | text = "Darker" 204 | 205 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 206 | text = "Zzz... zzz..." 207 | 208 | outline_text(text) 209 | outline_text_lower(timer_text) 210 | 211 | if debounce(1000): 212 | boopety_beepety.trigger_attack() 213 | cu.update(graphics) 214 | time.sleep(0.1) 215 | -------------------------------------------------------------------------------- /scrolling_text.py: -------------------------------------------------------------------------------- 1 | import time 2 | from cosmic import CosmicUnicorn 3 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 4 | 5 | ''' 6 | Display scrolling wisdom, quotes or greetz. 7 | 8 | You can adjust the brightness with LUX + and -. 9 | ''' 10 | 11 | # constants for controlling scrolling text 12 | PADDING = 5 13 | MESSAGE_COLOUR = (255, 255, 255) 14 | OUTLINE_COLOUR = (0, 0, 0) 15 | BACKGROUND_COLOUR = (10, 0, 96) 16 | 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" 17 | HOLD_TIME = 2.0 18 | STEP_TIME = 0.075 19 | 20 | # create cosmic object and graphics surface for drawing 21 | cu = CosmicUnicorn() 22 | graphics = PicoGraphics(DISPLAY) 23 | 24 | width = CosmicUnicorn.WIDTH 25 | height = CosmicUnicorn.HEIGHT 26 | 27 | 28 | # function for drawing outlined text 29 | def outline_text(text, x, y): 30 | graphics.set_pen(graphics.create_pen(int(OUTLINE_COLOUR[0]), int(OUTLINE_COLOUR[1]), int(OUTLINE_COLOUR[2]))) 31 | graphics.text(text, x - 1, y - 1, -1, 1) 32 | graphics.text(text, x, y - 1, -1, 1) 33 | graphics.text(text, x + 1, y - 1, -1, 1) 34 | graphics.text(text, x - 1, y, -1, 1) 35 | graphics.text(text, x + 1, y, -1, 1) 36 | graphics.text(text, x - 1, y + 1, -1, 1) 37 | graphics.text(text, x, y + 1, -1, 1) 38 | graphics.text(text, x + 1, y + 1, -1, 1) 39 | 40 | graphics.set_pen(graphics.create_pen(int(MESSAGE_COLOUR[0]), int(MESSAGE_COLOUR[1]), int(MESSAGE_COLOUR[2]))) 41 | graphics.text(text, x, y, -1, 1) 42 | 43 | 44 | cu.set_brightness(0.5) 45 | 46 | # state constants 47 | STATE_PRE_SCROLL = 0 48 | STATE_SCROLLING = 1 49 | STATE_POST_SCROLL = 2 50 | 51 | shift = 0 52 | state = STATE_PRE_SCROLL 53 | 54 | # set the font 55 | graphics.set_font("bitmap8") 56 | 57 | # calculate the message width so scrolling can happen 58 | msg_width = graphics.measure_text(MESSAGE, 1) 59 | 60 | last_time = time.ticks_ms() 61 | 62 | while True: 63 | time_ms = time.ticks_ms() 64 | 65 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 66 | cu.adjust_brightness(+0.01) 67 | 68 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 69 | cu.adjust_brightness(-0.01) 70 | 71 | if state == STATE_PRE_SCROLL and time_ms - last_time > HOLD_TIME * 1000: 72 | if msg_width + PADDING * 2 >= width: 73 | state = STATE_SCROLLING 74 | last_time = time_ms 75 | 76 | if state == STATE_SCROLLING and time_ms - last_time > STEP_TIME * 1000: 77 | shift += 1 78 | if shift >= (msg_width + PADDING * 2) - width - 1: 79 | state = STATE_POST_SCROLL 80 | last_time = time_ms 81 | 82 | if state == STATE_POST_SCROLL and time_ms - last_time > HOLD_TIME * 1000: 83 | state = STATE_PRE_SCROLL 84 | shift = 0 85 | last_time = time_ms 86 | 87 | graphics.set_pen(graphics.create_pen(int(BACKGROUND_COLOUR[0]), int(BACKGROUND_COLOUR[1]), int(BACKGROUND_COLOUR[2]))) 88 | graphics.clear() 89 | 90 | outline_text(MESSAGE, x=PADDING - shift, y=2) 91 | 92 | # update the display 93 | cu.update(graphics) 94 | 95 | # pause for a moment (important or the USB serial device will fail) 96 | time.sleep(0.001) 97 | -------------------------------------------------------------------------------- /strobe.py: -------------------------------------------------------------------------------- 1 | # Strobe light! 2 | 3 | import math 4 | import time 5 | from cosmic import CosmicUnicorn 6 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 7 | from bitfonts import BitFont, font5x9 8 | 9 | # create cosmic object and graphics surface for drawing 10 | cu = CosmicUnicorn() 11 | graphics = PicoGraphics(DISPLAY) 12 | bitfont = BitFont(graphics) 13 | 14 | width = CosmicUnicorn.WIDTH 15 | height = CosmicUnicorn.HEIGHT 16 | 17 | # set up some pens to use later 18 | WHITE = graphics.create_pen(255, 255, 255) 19 | BLACK = graphics.create_pen(0, 0, 0) 20 | 21 | @micropython.native # noqa: F821 22 | def from_hsv(h, s, v): 23 | i = math.floor(h * 6.0) 24 | f = h * 6.0 - i 25 | v *= 255.0 26 | p = v * (1.0 - s) 27 | q = v * (1.0 - f * s) 28 | t = v * (1.0 - (1.0 - f) * s) 29 | 30 | i = int(i) % 6 31 | if i == 0: 32 | return int(v), int(t), int(p) 33 | if i == 1: 34 | return int(q), int(v), int(p) 35 | if i == 2: 36 | return int(p), int(v), int(t) 37 | if i == 3: 38 | return int(p), int(q), int(v) 39 | if i == 4: 40 | return int(t), int(p), int(v) 41 | if i == 5: 42 | return int(v), int(p), int(q) 43 | 44 | # function for drawing a gradient background 45 | def gradient_background(start_hue, start_sat, start_val, end_hue, end_sat, end_val): 46 | half_width = width // 2 47 | for x in range(0, half_width): 48 | hue = ((end_hue - start_hue) * (x / half_width)) + start_hue 49 | sat = ((end_sat - start_sat) * (x / half_width)) + start_sat 50 | val = ((end_val - start_val) * (x / half_width)) + start_val 51 | colour = from_hsv(hue, sat, val) 52 | graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2]))) 53 | for y in range(0, height): 54 | graphics.pixel(x, y) 55 | graphics.pixel(width - x - 1, y) 56 | 57 | colour = from_hsv(end_hue, end_sat, end_val) 58 | graphics.set_pen(graphics.create_pen(int(colour[0]), int(colour[1]), int(colour[2]))) 59 | for y in range(0, height): 60 | graphics.pixel(half_width, y) 61 | 62 | cu.set_brightness(1) 63 | 64 | while True: 65 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 66 | cu.adjust_brightness(+0.01) 67 | 68 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 69 | cu.adjust_brightness(-0.01) 70 | 71 | gradient_background(0.15, 0, 1, 0.15, 0, 1) 72 | # update the display 73 | cu.update(graphics) 74 | 75 | time.sleep(0.01) 76 | gradient_background(1, 0, 0, 1, 0, 0) 77 | # update the display 78 | cu.update(graphics) 79 | time.sleep(0.01) 80 | 81 | -------------------------------------------------------------------------------- /today.py: -------------------------------------------------------------------------------- 1 | import time 2 | import network 3 | import ntptime 4 | import machine 5 | 6 | from cosmic import CosmicUnicorn 7 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 8 | 9 | cu = CosmicUnicorn() 10 | graphics = PicoGraphics(DISPLAY) 11 | 12 | # Default Brightness 13 | cu.set_brightness(0.4) 14 | 15 | # You will need to create or update the file secrets.py with your network credentials using Thonny 16 | # in order for the example to update using the NTP. 17 | 18 | # secrets.py should contain: 19 | # WIFI_SSID = "" 20 | # WIFI_PASSWORD = "" 21 | 22 | try: 23 | from secrets import WIFI_SSID, WIFI_PASSWORD 24 | except ImportError: 25 | print("Create secrets.py with your WiFi credentials") 26 | 27 | 28 | WIDTH = CosmicUnicorn.WIDTH 29 | HEIGHT = CosmicUnicorn.HEIGHT 30 | 31 | rtc = machine.RTC() 32 | 33 | DAYS = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat", "Sun"] 34 | 35 | # Enable the Wireless 36 | wlan = network.WLAN(network.STA_IF) 37 | wlan.active(True) 38 | 39 | 40 | def network_connect(SSID, PSK): 41 | 42 | # Number of attempts to make before timeout 43 | max_wait = 5 44 | 45 | # Sets the Wireless LED pulsing and attempts to connect to your local network. 46 | print("connecting...") 47 | wlan.config(pm=0xa11140) # Turn WiFi power saving off for some slow APs 48 | wlan.connect(SSID, PSK) 49 | 50 | while max_wait > 0: 51 | if wlan.status() < 0 or wlan.status() >= 3: 52 | break 53 | max_wait -= 1 54 | print('waiting for connection...') 55 | time.sleep(1) 56 | 57 | # Handle connection error. Switches the Warn LED on. 58 | if wlan.status() != 3: 59 | print("Unable to connect. Attempting connection again") 60 | 61 | 62 | # Function to sync the Pico RTC using NTP 63 | def sync_time(): 64 | 65 | try: 66 | network_connect(WIFI_SSID, WIFI_PASSWORD) 67 | except NameError: 68 | print("Create secrets.py with your WiFi credentials") 69 | 70 | if wlan.status() < 0 or wlan.status() >= 3: 71 | try: 72 | ntptime.settime() 73 | except OSError: 74 | print("Unable to sync with NTP server. Check network and try again.") 75 | 76 | 77 | def init(): 78 | 79 | sync_time() 80 | 81 | 82 | def draw(): 83 | 84 | # Pens 85 | RED = graphics.create_pen(120, 0, 0) 86 | WHITE = graphics.create_pen(255, 255, 255) 87 | 88 | current_t = rtc.datetime() 89 | 90 | # Set the pen to Red and clear the screen. 91 | graphics.set_pen(WHITE) 92 | graphics.clear() 93 | 94 | # Measures the length of the text to help us with centring later. 95 | day_length = graphics.measure_text(DAYS[current_t[3]], 1) 96 | date_length = graphics.measure_text(str(current_t[2]), 3) 97 | 98 | graphics.set_font("bitmap6") 99 | graphics.set_pen(RED) 100 | graphics.rectangle(0, 0, WIDTH, 7) 101 | graphics.set_pen(WHITE) 102 | graphics.text(DAYS[current_t[3]], (WIDTH // 2) - (day_length // 2) - 1, 0, 32, 1) 103 | 104 | graphics.set_pen(RED) 105 | graphics.set_font("bitmap8") 106 | graphics.text(str(current_t[2]), (WIDTH // 2) - (date_length // 2) + 1, 9, 32, 3) 107 | 108 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 109 | 110 | cu.update(graphics) 111 | 112 | 113 | init() 114 | 115 | while 1: 116 | 117 | # Adjust Brightness +/- 118 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 119 | cu.adjust_brightness(+0.01) 120 | 121 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 122 | cu.adjust_brightness(-0.01) 123 | 124 | # Connect to the network and sync with the NTP server 125 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 126 | sync_time() 127 | 128 | draw() 129 | -------------------------------------------------------------------------------- /traffic-signal.py: -------------------------------------------------------------------------------- 1 | # IMPORTS 2 | import time 3 | import math 4 | from cosmic import CosmicUnicorn 5 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 6 | from bitfonts import BitFont, font2x5, font3x5, font4x5, font5x9 7 | 8 | # INITIALISE GALACTIC UNICORN 9 | cu = CosmicUnicorn() 10 | graphics = PicoGraphics(DISPLAY) 11 | bitfont = BitFont(graphics) 12 | 13 | width = CosmicUnicorn.WIDTH 14 | height = CosmicUnicorn.HEIGHT 15 | 16 | cu.set_brightness(0.8) 17 | 18 | # SIGNAL HEAD FUNCTION 19 | def draw_signal_head(x, y, lamp="none"): 20 | # R 21 | graphics.set_pen(graphics.create_pen(15, 0, 0)) 22 | if lamp == "R": 23 | graphics.set_pen(graphics.create_pen(255, 0, 0)) 24 | graphics.pixel(x, y) 25 | # Y 26 | graphics.set_pen(graphics.create_pen(15, 15, 0)) 27 | if lamp == "Y": 28 | graphics.set_pen(graphics.create_pen(255, 255, 0)) 29 | graphics.pixel(x, y + 1) 30 | # G 31 | graphics.set_pen(graphics.create_pen(0, 15, 0)) 32 | if lamp == "G": 33 | graphics.set_pen(graphics.create_pen(0, 255, 0)) 34 | graphics.pixel(x, y + 2) 35 | 36 | def draw_signal_row(n, y, state, state_ped): 37 | # ROW LABEL 38 | graphics.set_pen(graphics.create_pen(100, 100, 100)) 39 | bitfont.draw_char(str(n), 0, y, font3x5) 40 | 41 | # PED COUNTER BG 42 | if state_ped: 43 | graphics.set_pen(graphics.create_pen(15, 0, 0)) 44 | bitfont.draw_text("00", 10, y, font2x5) 45 | 46 | # SIGNALS MAST 47 | graphics.set_pen(graphics.create_pen(50, 50, 50)) 48 | graphics.pixel(16, 5 + y) 49 | graphics.line(18, y - 1, 22, y - 1) 50 | graphics.line(22, y - 1, 22, 6 + y) 51 | 52 | # DRAW SIGNAL HEADS 53 | draw_signal_head(16, 2 + y, state) 54 | draw_signal_head(18, 0 + y, state) 55 | draw_signal_head(20, 0 + y, state) 56 | draw_signal_head(23, 2 + y, state) 57 | 58 | # PED SYMBOL 59 | if state_ped == "W": 60 | graphics.set_pen(graphics.create_pen(255, 255, 255)) 61 | bitfont.draw_char("🚶", 5, y, font3x5) 62 | if state_ped == "R": 63 | graphics.set_pen(graphics.create_pen(255, 0, 0)) 64 | bitfont.draw_char("✋", 5, y, font3x5) 65 | if state_ped == "F": 66 | invert_flash = 1 - (time_ms // 500) % 2 67 | graphics.set_pen(graphics.create_pen(255 * invert_flash, 0, 0)) 68 | bitfont.draw_char("✋", 5, y, font3x5) 69 | 70 | def ped_timing_count_Fs(s): 71 | start_index = s.find('F') 72 | end_index = s.rfind('F') 73 | count = s.count('F') 74 | 75 | return count, start_index, end_index 76 | 77 | def ped_timer_string(count, start_index, end_index, cycle_seconds): 78 | if cycle_seconds < start_index or cycle_seconds > end_index: 79 | return "" 80 | else: 81 | return str(end_index - cycle_seconds) 82 | 83 | 84 | 85 | # INITIAL STATE 86 | paused = 0 87 | scene = 'A' 88 | 89 | # TIMING - 60 second cycle 90 | # phase2_timing = "GGGGGGGGGGGGGGGGGGGGYYYYRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR" 91 | # phase2p_timing = "WWWWWFFFFFFFFFFFFFFRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRWW" 92 | # phase4_timing = "RRRRRRRRRRRRRRRRRRRRRRRRRRRRGGGGGGGGGGGGGGGGGGGGGGGYYYYRRRRR" 93 | # phase4p_timing = "RRRRRRRRRRRRRRRRRRRRRRRRRRWWWWWWWWWFFFFFFFFFFFFFFFRRRRRRRRRR" 94 | 95 | # TIMING - 30 second cycle 96 | phase2_timing = "GGGGGGGGGGYYYYRRRRRRRRRRRRRRRRRR" 97 | phase2p_timing = "WWWWFFFFFFRRRRRRRRRRRRRRRRRRRRWW" 98 | phase4_timing = "RRRRRRRRRRRRRRRRGGGGGGGGGYYYYRRR" 99 | phase4p_timing = "RRRRRRRRRRRRRRRWWWWWWWFFFFFFRRRR" 100 | 101 | # MAIN LOOP 102 | while True: 103 | time_ms = time.ticks_ms() 104 | 105 | # cycle_seconds = (time_ms // 1000) % 60 # 60 second cycle 106 | cycle_seconds = (time_ms // 1000) % 30 # 30 second cycle 107 | 108 | graphics.set_pen(graphics.create_pen(0, 0, 0)) 109 | graphics.clear() 110 | 111 | text = "" 112 | timer_text = str(time_ms)[3:] 113 | if paused: 114 | timer_text = "0123456789" 115 | 116 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 117 | text = "Scene A" 118 | scene = 'A' 119 | 120 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 121 | text = "Scene B" 122 | scene = 'B' 123 | 124 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 125 | text = "Scene C" 126 | scene = 'C' 127 | 128 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 129 | text = "Scene D" 130 | scene = 'D' 131 | 132 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_UP): 133 | text = "Louder!" 134 | 135 | if cu.is_pressed(CosmicUnicorn.SWITCH_VOLUME_DOWN): 136 | text = "Quieter" 137 | 138 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 139 | cu.adjust_brightness(+0.01) 140 | text = "Brite" + str(round(cu.get_brightness(),2)) 141 | 142 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 143 | cu.adjust_brightness(-0.01) 144 | text = "Dark" + str(round(cu.get_brightness(),2)) 145 | 146 | if cu.is_pressed(CosmicUnicorn.SWITCH_SLEEP): 147 | text = "Zzz..." 148 | 149 | if scene == 'A': 150 | # DRAW SIGNALS 151 | draw_signal_row(2, 1, phase2_timing[cycle_seconds], phase2p_timing[cycle_seconds]) 152 | draw_signal_row(4, 26, phase4_timing[cycle_seconds], phase4p_timing[cycle_seconds]) 153 | # PED COUNTERS 154 | graphics.set_pen(graphics.create_pen(255, 0, 0)) 155 | count, start_index, end_index = ped_timing_count_Fs(phase2p_timing) 156 | bitfont.draw_text(ped_timer_string(count, start_index, end_index, cycle_seconds),15,1,font2x5,-1) 157 | count4, start_index4, end_index4 = ped_timing_count_Fs(phase4p_timing) 158 | bitfont.draw_text(ped_timer_string(count4, start_index4, end_index4, cycle_seconds),15,26,font2x5,-1) 159 | # GLOBAL TIMER 160 | graphics.set_pen(graphics.create_pen(100, 100, 100)) 161 | bitfont.draw_text(str(cycle_seconds),32,13,font3x5,-1) 162 | if scene == 'B': 163 | draw_signal_row(' ', 26, phase2_timing[cycle_seconds],'') 164 | # RED 165 | graphics.set_pen(graphics.create_pen(15, 0, 0)) 166 | if phase2_timing[cycle_seconds] == 'R': 167 | graphics.set_pen(graphics.create_pen(255, 0, 0)) 168 | graphics.circle(25, 6, 6) 169 | # YELLOW 170 | graphics.set_pen(graphics.create_pen(15, 15, 0)) 171 | if phase2_timing[cycle_seconds] == 'Y': 172 | graphics.set_pen(graphics.create_pen(255, 255, 0)) 173 | graphics.circle(15, 16, 6) 174 | # GREEN 175 | graphics.set_pen(graphics.create_pen(0, 15, 0)) 176 | if phase2_timing[cycle_seconds] == 'G': 177 | graphics.set_pen(graphics.create_pen(0, 255, 0)) 178 | graphics.circle(6, 25, 6) 179 | # GLOBAL TIMER 180 | graphics.set_pen(graphics.create_pen(50, 50, 50)) 181 | bitfont.draw_text(str(cycle_seconds),32,27,font3x5,-1) 182 | if scene == 'C': 183 | draw_signal_row(' ', 26, phase2_timing[cycle_seconds],'') 184 | # RED 185 | graphics.set_pen(graphics.create_pen(15, 0, 0)) 186 | if phase2_timing[cycle_seconds] == 'R': 187 | graphics.set_pen(graphics.create_pen(255, 0, 0)) 188 | graphics.circle(15, 10, 10) 189 | # YELLOW 190 | graphics.set_pen(graphics.create_pen(15, 15, 0)) 191 | if phase2_timing[cycle_seconds] == 'Y': 192 | graphics.set_pen(graphics.create_pen(255, 255, 0)) 193 | graphics.circle(15, 31, 10) 194 | # GLOBAL TIMER 195 | graphics.set_pen(graphics.create_pen(50, 50, 50)) 196 | bitfont.draw_text(str(cycle_seconds),32,27,font3x5,-1) 197 | 198 | if scene == 'D': 199 | # YELLOW 200 | graphics.set_pen(graphics.create_pen(15, 15, 0)) 201 | if phase2_timing[cycle_seconds] == 'Y': 202 | graphics.set_pen(graphics.create_pen(255, 255, 0)) 203 | graphics.circle(15, 0, 10) 204 | # GREEN 205 | graphics.set_pen(graphics.create_pen(0, 15, 0)) 206 | if phase2_timing[cycle_seconds] == 'G': 207 | graphics.set_pen(graphics.create_pen(0, 255, 0)) 208 | graphics.circle(15, 21, 10) 209 | # GLOBAL TIMER 210 | graphics.set_pen(graphics.create_pen(50, 50, 50)) 211 | bitfont.draw_text(str(cycle_seconds),32,27,font3x5,-1) 212 | # STATUS TEXT 213 | bitfont.draw_text(text.upper(),0,20,font3x5) 214 | 215 | cu.update(graphics) 216 | 217 | # pause for a moment 218 | # time.sleep(0.1) 219 | 220 | 221 | -------------------------------------------------------------------------------- /train_clock.py: -------------------------------------------------------------------------------- 1 | # Clock example with NTP synchronization 2 | # 3 | # Create a secrets.py with your Wifi details to be able to get the time 4 | # when the Cosmic Unicorn isn't connected to Thonny. 5 | # 6 | # secrets.py should contain: 7 | # WIFI_SSID = "Your WiFi SSID" 8 | # WIFI_PASSWORD = "Your WiFi password" 9 | # 10 | # Clock synchronizes time on start, and resynchronizes if you press the A button 11 | 12 | import time 13 | import math 14 | import machine 15 | import network 16 | import ntptime 17 | from cosmic import CosmicUnicorn 18 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 19 | from bitfonts import BitFont, font5x9 20 | try: 21 | from secrets import WIFI_SSID, WIFI_PASSWORD 22 | wifi_available = True 23 | except ImportError: 24 | print("Create secrets.py with your WiFi credentials to get time from NTP") 25 | wifi_available = False 26 | 27 | 28 | # constant for wakeup hour of green color 29 | WAKEUP_HOUR = 6 30 | UTC_OFFSET_HOUR = -7 31 | GLOBAL_STATE_SLEEP = False 32 | 33 | # create cosmic object and graphics surface for drawing 34 | cu = CosmicUnicorn() 35 | graphics = PicoGraphics(DISPLAY) 36 | bitfont = BitFont(graphics) 37 | 38 | # create the rtc object 39 | rtc = machine.RTC() 40 | 41 | width = CosmicUnicorn.WIDTH 42 | height = CosmicUnicorn.HEIGHT 43 | 44 | # set up some pens to use later 45 | WHITE = graphics.create_pen(255, 255, 255) 46 | BLACK = graphics.create_pen(0, 0, 0) 47 | DARK_YELLOW = graphics.create_pen(50, 50, 0) 48 | DARK_RED = graphics.create_pen(30, 0, 0) 49 | DARK_GRAY = graphics.create_pen(20, 20, 20) 50 | LIGHT_GRAY = graphics.create_pen(120, 120, 120) 51 | DARK_CYAN = graphics.create_pen(0, 120, 120) 52 | YELLOW = graphics.create_pen(255, 255, 0) 53 | 54 | def gradient(r, g, b): 55 | for y in range(0, height): 56 | for x in range(0, width): 57 | graphics.set_pen(graphics.create_pen(int((r * (height - y)) / 32), int((g * (height - y)) / 32), int((b * (height - y)) / 32))) 58 | graphics.pixel(x, y) 59 | 60 | # function for drawing outlined text 61 | def outline_text_custom_font(text, x, y, font): 62 | graphics.set_pen(BLACK) 63 | bitfont.draw_text(text, x - 1, y - 1, font) 64 | bitfont.draw_text(text, x, y - 1, font) 65 | bitfont.draw_text(text, x + 1, y - 1, font) 66 | bitfont.draw_text(text, x - 1, y, font) 67 | bitfont.draw_text(text, x + 1, y, font) 68 | bitfont.draw_text(text, x - 1, y + 1, font) 69 | bitfont.draw_text(text, x, y + 1, font) 70 | bitfont.draw_text(text, x + 1, y + 1, font) 71 | 72 | graphics.set_pen(WHITE) 73 | bitfont.draw_text(text, x, y, font) 74 | 75 | # Connect to wifi and synchronize the RTC time from NTP 76 | def sync_time(): 77 | if not wifi_available: 78 | return 79 | 80 | # Start connection 81 | wlan = network.WLAN(network.STA_IF) 82 | wlan.active(True) 83 | wlan.connect(WIFI_SSID, WIFI_PASSWORD) 84 | 85 | # Wait for connect success or failure 86 | max_wait = 100 87 | while max_wait > 0: 88 | if wlan.status() < 0 or wlan.status() >= 3: 89 | break 90 | max_wait -= 1 91 | print('waiting for connection...') 92 | time.sleep(0.2) 93 | 94 | redraw_display_if_reqd() 95 | cu.update(graphics) 96 | 97 | if max_wait > 0: 98 | print("Connected") 99 | 100 | try: 101 | ntptime.settime() 102 | print("Time set") 103 | except OSError: 104 | pass 105 | 106 | wlan.disconnect() 107 | wlan.active(False) 108 | 109 | 110 | # NTP synchronizes the time to UTC, this allows you to adjust the displayed time 111 | # by one hour increments from UTC by pressing the volume up/down buttons 112 | # 113 | # We use the IRQ method to detect the button presses to avoid incrementing/decrementing 114 | # multiple times when the button is held. 115 | utc_offset = UTC_OFFSET_HOUR 116 | 117 | up_button = machine.Pin(CosmicUnicorn.SWITCH_VOLUME_UP, machine.Pin.IN, machine.Pin.PULL_UP) 118 | down_button = machine.Pin(CosmicUnicorn.SWITCH_VOLUME_DOWN, machine.Pin.IN, machine.Pin.PULL_UP) 119 | 120 | 121 | def adjust_utc_offset(pin): 122 | global utc_offset 123 | if pin == up_button: 124 | utc_offset += 1 125 | if pin == down_button: 126 | utc_offset -= 1 127 | 128 | 129 | up_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset) 130 | down_button.irq(trigger=machine.Pin.IRQ_FALLING, handler=adjust_utc_offset) 131 | 132 | 133 | year, month, day, wd, hour, minute, second, _ = rtc.datetime() 134 | 135 | last_second = second 136 | 137 | @micropython.native # noqa: F821 138 | def tracks(y=29,x_offset=0): 139 | graphics.set_pen(LIGHT_GRAY) 140 | graphics.line(0, y, 32, y) 141 | for tie in range(11): 142 | graphics.set_pen(DARK_YELLOW) 143 | graphics.line(4*tie+x_offset, y+1, 4*tie+2+x_offset, y+1) 144 | graphics.line(4*tie+x_offset, y+2, 4*tie+2+x_offset, y+2) 145 | graphics.set_pen(BLACK) 146 | graphics.line(4*tie+x_offset-2, y+1, 4*tie+2+x_offset-2, y+1) 147 | graphics.line(4*tie+x_offset-2, y+2, 4*tie+2+x_offset-2, y+2) 148 | 149 | @micropython.native # noqa: F821 150 | def train(x,y,dip=False): 151 | if dip: 152 | y = y + 1 153 | # LEFT FACING 154 | # NOSE AND WINDOW FRAME 155 | graphics.set_pen(DARK_CYAN) 156 | graphics.polygon([ 157 | (x, y), 158 | (x+26, y), 159 | (x+26, y-5), 160 | (x+5, y-5), 161 | (x, y), 162 | ]) 163 | # ROOF 164 | graphics.set_pen(LIGHT_GRAY) 165 | graphics.line(x+6, y-6, x+27, y-6) 166 | graphics.line(x+5, y-5, x+27, y-5) 167 | # BOTTOM FRAME 168 | graphics.polygon([ 169 | (x, y), 170 | (x+26, y), 171 | (x+26, y+4), 172 | (x+2, y+4), 173 | (x, y), 174 | ]) 175 | # WHEEL WELLS 176 | graphics.set_pen(BLACK) 177 | graphics.polygon([ 178 | (x+4, y+4), 179 | (x+4, y+5), 180 | (x+11, y+5), 181 | (x+11, y+4), 182 | (x+10, y+2), 183 | (x+6, y+2), 184 | (x+4, y+4), 185 | ]) 186 | # WHEELS 187 | if dip: 188 | y = y - 1 189 | graphics.set_pen(LIGHT_GRAY) 190 | graphics.line(x+5, y+4, x+7, y+4) 191 | graphics.line(x+5, y+5, x+7, y+5) 192 | graphics.line(x+9, y+4, x+11, y+4) 193 | graphics.line(x+9, y+5, x+11, y+5) 194 | 195 | def littlez(x,y,visible=True): 196 | if visible: 197 | graphics.set_pen(LIGHT_GRAY) 198 | graphics.line(x, y, x+3, y) 199 | graphics.line(x, y+2, x+3, y+2) 200 | graphics.set_pen(WHITE) 201 | graphics.line(x+2, y, x, y+3) 202 | 203 | def bigz(x,y,visible=True): 204 | if visible: 205 | graphics.set_pen(WHITE) 206 | graphics.line(x, y, x+4, y) 207 | graphics.line(x+3, y, x, y+3) 208 | graphics.line(x, y+3, x+4, y+3) 209 | 210 | @micropython.native # noqa: F821 211 | def sleepface(x,y,dip=False): 212 | if dip: 213 | y = y + 1 214 | # ADDS A SLEEP FACE TO LEFT FACING TRAIN AT SAME X Y 215 | graphics.set_pen(BLACK) 216 | # EYE 217 | graphics.polygon([ 218 | (x+3, y-1), 219 | (x+6, y-4), 220 | (x+13, y-4), 221 | (x+4, y-1), 222 | ]) 223 | # MOUTH 224 | graphics.polygon([ 225 | (x, y), 226 | (x+3, y), 227 | (x+1, y+2), 228 | (x, y+1), 229 | ]) 230 | 231 | @micropython.native # noqa: F821 232 | def wakeface(x,y,dip=False): 233 | if dip: 234 | y = y + 1 235 | # ADDS A WAKING FACE TO LEFT FACING TRAIN AT SAME X Y 236 | # EYE 237 | graphics.set_pen(WHITE) 238 | graphics.polygon([ 239 | (x + 5, y - 2), 240 | (x + 7, y - 4), 241 | (x + 7, y - 4), 242 | (x + 8, y - 2), 243 | (x + 7, y - 1), 244 | (x + 6, y - 1), 245 | ]) 246 | # PUPIL 247 | graphics.set_pen(BLACK) 248 | graphics.line(x + 6, y - 2, x + 8, y - 2) 249 | 250 | # MOUTH OPEN SMILE 251 | graphics.set_pen(BLACK) 252 | graphics.polygon([ 253 | (x, y), 254 | (x+3, y), 255 | (x+1, y+2), 256 | (x, y+1), 257 | ]) 258 | graphics.line(x + 2, y, x + 4, y) 259 | 260 | # Check whether the RTC time has changed and if so redraw the display 261 | def redraw_display_if_reqd(): 262 | global GLOBAL_STATE_SLEEP 263 | global year, month, day, wd, hour, minute, second, last_second 264 | 265 | year, month, day, wd, hour, minute, second, _ = rtc.datetime() 266 | modulo2 = second % 2 267 | modulo4 = second % 4 268 | modulo_ms4 = (time.ticks_ms() // 10) % 4 269 | 270 | if second != last_second: 271 | hour = (hour + utc_offset) % 24 272 | 273 | if hour == WAKEUP_HOUR: 274 | gradient(0,255,0) 275 | elif hour > WAKEUP_HOUR and hour < WAKEUP_HOUR + 10: 276 | gradient(40,40,255) 277 | graphics.set_pen(YELLOW) 278 | graphics.circle(32, 0, 9) 279 | else: 280 | gradient(255,0,0) 281 | 282 | 283 | if hour < WAKEUP_HOUR or hour >= WAKEUP_HOUR + 10: 284 | train(7, 23, modulo4) 285 | littlez(5, 18, modulo4 > 0) 286 | bigz(0, 13, modulo4 > 1) 287 | sleepface(7,23, modulo4) 288 | #tracks() 289 | GLOBAL_STATE_SLEEP = True 290 | else: 291 | train(7, 23, modulo2) 292 | wakeface(7,23, modulo2) 293 | #tracks(x_offset=modulo_ms4) 294 | GLOBAL_STATE_SLEEP = False 295 | 296 | clock = "{:02}:{:02}".format(hour, minute) 297 | outline_text_custom_font(clock, 3, 2, font5x9) 298 | 299 | last_second = second 300 | 301 | cu.set_brightness(0.5) 302 | 303 | sync_time() 304 | 305 | while True: 306 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 307 | cu.adjust_brightness(+0.01) 308 | 309 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 310 | cu.adjust_brightness(-0.01) 311 | 312 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 313 | sync_time() 314 | 315 | redraw_display_if_reqd() 316 | if GLOBAL_STATE_SLEEP: 317 | tracks() 318 | else: 319 | modulo_ms4 = (time.ticks_ms() // 100) % 4 320 | tracks(x_offset=modulo_ms4) 321 | 322 | # update the display 323 | cu.update(graphics) 324 | 325 | time.sleep(0.01) 326 | -------------------------------------------------------------------------------- /weather/icons/cloud1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/cloud1.jpg -------------------------------------------------------------------------------- /weather/icons/cloud2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/cloud2.jpg -------------------------------------------------------------------------------- /weather/icons/cloud3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/cloud3.jpg -------------------------------------------------------------------------------- /weather/icons/cloud4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/cloud4.jpg -------------------------------------------------------------------------------- /weather/icons/rain1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/rain1.jpg -------------------------------------------------------------------------------- /weather/icons/rain2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/rain2.jpg -------------------------------------------------------------------------------- /weather/icons/rain3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/rain3.jpg -------------------------------------------------------------------------------- /weather/icons/rain4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/rain4.jpg -------------------------------------------------------------------------------- /weather/icons/snow1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/snow1.jpg -------------------------------------------------------------------------------- /weather/icons/snow2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/snow2.jpg -------------------------------------------------------------------------------- /weather/icons/snow3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/snow3.jpg -------------------------------------------------------------------------------- /weather/icons/snow4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/snow4.jpg -------------------------------------------------------------------------------- /weather/icons/storm1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/storm1.jpg -------------------------------------------------------------------------------- /weather/icons/storm2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/storm2.jpg -------------------------------------------------------------------------------- /weather/icons/storm3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/storm3.jpg -------------------------------------------------------------------------------- /weather/icons/storm4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/storm4.jpg -------------------------------------------------------------------------------- /weather/icons/sun1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/sun1.jpg -------------------------------------------------------------------------------- /weather/icons/sun2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/sun2.jpg -------------------------------------------------------------------------------- /weather/icons/sun3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/sun3.jpg -------------------------------------------------------------------------------- /weather/icons/sun4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kfarr/cosmic-unicorn-playground/48a0d2edf968b699c9c152ced585aa8e1562cae8/weather/icons/sun4.jpg -------------------------------------------------------------------------------- /weather/weather.py: -------------------------------------------------------------------------------- 1 | import time 2 | from cosmic import CosmicUnicorn 3 | from picographics import PicoGraphics, DISPLAY_COSMIC_UNICORN as DISPLAY 4 | import WIFI_CONFIG 5 | from network_manager import NetworkManager 6 | import uasyncio as asyncio 7 | import urequests 8 | import jpegdec 9 | 10 | # Set your latitude/longitude here (find yours by right clicking in Google Maps!) 11 | LAT = 53.38609085276884 12 | LNG = -1.4239983439328177 13 | TIMEZONE = "auto" # determines time zone from lat/long 14 | 15 | URL = "http://api.open-meteo.com/v1/forecast?latitude=" + str(LAT) + "&longitude=" + str(LNG) + "¤t_weather=true&timezone=" + TIMEZONE 16 | WEATHER_TEXT = '' 17 | user_icon = None 18 | 19 | 20 | def get_data(): 21 | global WEATHER_TEXT, temperature, weathercode 22 | print(f"Requesting URL: {URL}") 23 | r = urequests.get(URL) 24 | # open the json data 25 | j = r.json() 26 | print("Data obtained!") 27 | print(j) 28 | 29 | # parse relevant data from JSON 30 | current = j["current_weather"] 31 | temperature = current["temperature"] 32 | windspeed = current["windspeed"] 33 | winddirection = calculate_bearing(current["winddirection"]) 34 | weathercode = current["weathercode"] 35 | date, now = current["time"].split("T") 36 | WEATHER_TEXT = f"Temp: {temperature}°C Wind Speed: {windspeed}kmph Wind Direction: {winddirection} As of: {date}, {now}" 37 | print(WEATHER_TEXT) 38 | r.close() 39 | 40 | 41 | def calculate_bearing(d): 42 | # calculates a compass direction from the wind direction in degrees 43 | dirs = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW'] 44 | ix = round(d / (360. / len(dirs))) 45 | return dirs[ix % len(dirs)] 46 | 47 | 48 | def status_handler(mode, status, ip): 49 | global MESSAGE 50 | print("Network: {}".format(WIFI_CONFIG.SSID)) 51 | 52 | 53 | # create galactic object and graphics surface for drawing 54 | cu = CosmicUnicorn() 55 | display = PicoGraphics(DISPLAY) 56 | 57 | WIDTH = CosmicUnicorn.WIDTH 58 | HEIGHT = CosmicUnicorn.HEIGHT 59 | 60 | jpeg = jpegdec.JPEG(display) 61 | TEXT_COLOUR = display.create_pen(200, 0, 200) 62 | BLACK = display.create_pen(0, 0, 0) 63 | WHITE = display.create_pen(255, 255, 255) 64 | 65 | 66 | def run(): 67 | # Setup wifi 68 | network_manager = NetworkManager(WIFI_CONFIG.COUNTRY, status_handler=status_handler) 69 | 70 | # Connect to Wifi network 71 | asyncio.get_event_loop().run_until_complete(network_manager.client(WIFI_CONFIG.SSID, WIFI_CONFIG.PSK)) 72 | while (not network_manager.isconnected()): 73 | time.sleep(0.1) 74 | 75 | 76 | cu.set_brightness(1) 77 | run() # Sets up Wifi connection 78 | 79 | 80 | def outline_text(text, x, y): 81 | display.set_pen(BLACK) 82 | display.text(text, x - 1, y - 1, -1, 1) 83 | display.text(text, x, y - 1, -1, 1) 84 | display.text(text, x + 1, y - 1, -1, 1) 85 | display.text(text, x - 1, y, -1, 1) 86 | display.text(text, x + 1, y, -1, 1) 87 | display.text(text, x - 1, y + 1, -1, 1) 88 | display.text(text, x, y + 1, -1, 1) 89 | display.text(text, x + 1, y + 1, -1, 1) 90 | 91 | display.set_pen(WHITE) 92 | display.text(text, x, y, -1, 1) 93 | 94 | 95 | def draw_page(cycle): 96 | global user_icon 97 | text_cycle = cycle % 1000 98 | cycle = cycle % 4 99 | # Clear the display 100 | display.set_pen(15) 101 | display.clear() 102 | 103 | # Draw the page header 104 | display.set_font("bitmap6") 105 | 106 | if temperature is not None: 107 | # Choose an appropriate icon based on the weather code 108 | # Weather codes from https://open-meteo.com/en/docs 109 | if user_icon is not None: 110 | 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)] 111 | jpeg.open_file(icons[user_icon]) 112 | else: 113 | if weathercode in [71, 73, 75, 77, 85, 86]: # codes for snow 114 | jpeg.open_file("icons/snow{0}.jpg".format(cycle + 1)) 115 | elif weathercode in [51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82]: # codes for rain 116 | jpeg.open_file("icons/rain{0}.jpg".format(cycle + 1)) 117 | elif weathercode in [1, 2, 3, 45, 48]: # codes for cloud 118 | jpeg.open_file("icons/cloud{0}.jpg".format(cycle + 1)) 119 | elif weathercode in [0]: # codes for sun 120 | jpeg.open_file("icons/sun{0}.jpg".format(cycle + 1)) 121 | elif weathercode in [95, 96, 99]: # codes for storm 122 | jpeg.open_file("icons/storm{0}.jpg".format(cycle + 1)) 123 | jpeg.decode(0, 0, jpegdec.JPEG_SCALE_FULL) 124 | display.set_pen(TEXT_COLOUR) 125 | outline_text(WEATHER_TEXT, 32 - text_cycle, 0) 126 | 127 | else: 128 | display.set_pen(0) 129 | display.set_pen(15) 130 | display.text("Unable to display weather! Check your network settings in WIFI_CONFIG.py", 5, 65, WIDTH, 1) 131 | 132 | cu.update(display) 133 | 134 | 135 | while 1: 136 | 137 | get_data() 138 | for count in range(500): 139 | 140 | if cu.is_pressed(CosmicUnicorn.SWITCH_A): 141 | user_icon = 0 142 | if cu.is_pressed(CosmicUnicorn.SWITCH_B): 143 | user_icon = 1 144 | if cu.is_pressed(CosmicUnicorn.SWITCH_C): 145 | user_icon = 2 146 | if cu.is_pressed(CosmicUnicorn.SWITCH_D): 147 | user_icon = 3 148 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_UP): 149 | user_icon = 4 150 | if cu.is_pressed(CosmicUnicorn.SWITCH_BRIGHTNESS_DOWN): 151 | user_icon = None 152 | draw_page(count) 153 | time.sleep(0.2) 154 | --------------------------------------------------------------------------------
97 | Please type in what you wish to be displayed on the Cosmic Unicorn and whe you are ready hit update to update the display 98 |