├── .github └── workflows │ └── micropython.yml ├── .gitignore ├── LICENSE ├── README.md ├── boards ├── common.cmake ├── manifest-common.py ├── presto │ ├── manifest.py │ ├── manifest.txt │ ├── mpconfigboard.cmake │ ├── mpconfigboard.h │ ├── pins.csv │ ├── presto.h │ └── usermodules.cmake └── usermod-common.cmake ├── ci └── micropython.sh ├── docs ├── picovector.md ├── presto.md └── wifi.md ├── drivers └── st7701 │ ├── CMakeLists.txt │ ├── st7701.cpp │ ├── st7701.hpp │ ├── st7701_palette.pio │ ├── st7701_parallel.pio │ ├── st7701_presto.cmake │ └── st7701_timing.pio ├── examples ├── Roboto-Medium.af ├── agile_pricing_display.py ├── attitude_indicator.py ├── auto_backlight.py ├── awesome_game.py ├── backlight_ball.py ├── backlight_images.py ├── backlight_slider.py ├── badapple.py ├── balls_demo.py ├── buzzer.py ├── cheerlights_bulb.py ├── cherry-hq.af ├── cubes.py ├── gallery │ ├── example-image1.jpg │ ├── example-image2.jpg │ └── example-image3.jpg ├── hello.py ├── image_gallery.py ├── indoor-outdoor-temp.py ├── main.py ├── micro_sd.jpg ├── multi_player.py ├── random_maze.py ├── s4m_ur4i-pirate-characters.16bpp ├── s4m_ur4i-pirate-tilemap.16bpp ├── sd_basic.py ├── sd_image.py ├── sd_rw.py ├── secrets.py ├── sensor-stick-temperature.py ├── splash.py ├── stop_watch.py ├── tomato.py ├── touch_buttons.py ├── touchscreen_dots.py ├── touchscreen_dots_vector.py ├── vector_clock_full.py ├── word_clock.py └── wordclock_background.png ├── modules ├── c │ └── presto │ │ ├── micropython.cmake │ │ ├── presto.c │ │ ├── presto.cpp │ │ └── presto.h └── py_frozen │ ├── boot.py │ ├── ezwifi.py │ ├── lsm6ds3.py │ ├── presto.py │ ├── psram.py │ ├── qwstpad.py │ └── touch.py ├── pimoroni_pico_import.cmake └── tools └── convert-image-rgb565.py /.github/workflows/micropython.yml: -------------------------------------------------------------------------------- 1 | name: MicroPython 2 | 3 | on: 4 | push: 5 | pull_request: 6 | release: 7 | types: [created] 8 | 9 | jobs: 10 | build: 11 | name: MicroPython ${{ matrix.name }} 12 | runs-on: ubuntu-24.04 13 | continue-on-error: true 14 | strategy: 15 | matrix: 16 | include: 17 | - name: presto 18 | 19 | env: 20 | # MicroPython version will be contained in github.event.release.tag_name for releases 21 | CI_RELEASE_FILENAME: ${{ matrix.name }}-${{ github.event.release.tag_name || github.sha }}-micropython 22 | CI_PROJECT_ROOT: ${{ github.workspace }}/src-${{ github.sha }} 23 | CI_BUILD_ROOT: ${{ github.workspace }} 24 | CI_USE_ENV: 1 25 | 26 | steps: 27 | - name: Compiler Cache Fixup 28 | run: | 29 | mkdir -p /home/runner/.ccache 30 | 31 | - name: "CCache: Restore saved cache" 32 | uses: actions/cache@v4 33 | with: 34 | path: /home/runner/.ccache 35 | key: ccache-micropython-${{ matrix.name }}-${{ github.ref }}-${{ github.sha }} 36 | restore-keys: | 37 | ccache-micropython-${{ matrix.name }}-${{ github.ref }} 38 | ccache-micropython-${{ matrix.name }}- 39 | 40 | - name: "Checkout Project" 41 | uses: actions/checkout@v4 42 | with: 43 | submodules: true 44 | path: ${{ env.CI_PROJECT_ROOT }} 45 | 46 | - name: "Install Arm GNU Toolchain (arm-none-eabi-gcc)" 47 | uses: carlosperate/arm-none-eabi-gcc-action@v1 48 | with: 49 | release: '13.3.Rel1' 50 | 51 | - name: "Prepare tools & dependencies" 52 | shell: bash 53 | run: | 54 | source $CI_PROJECT_ROOT/ci/micropython.sh && ci_debug 55 | mkdir -p $CI_BUILD_ROOT 56 | ci_apt_install_build_deps 57 | ci_prepare_all 58 | 59 | - name: "MicroPython: Configure" 60 | shell: bash 61 | run: | 62 | source $CI_PROJECT_ROOT/ci/micropython.sh && ci_debug 63 | micropython_version 64 | ci_cmake_configure ${{ matrix.name }} 65 | 66 | - name: "MicroPython: Build" 67 | shell: bash 68 | run: | 69 | source $CI_PROJECT_ROOT/ci/micropython.sh && ci_debug 70 | ci_cmake_build ${{ matrix.name }} 71 | 72 | - name: "Artifacts: Upload .uf2" 73 | uses: actions/upload-artifact@v4 74 | with: 75 | name: ${{ env.CI_RELEASE_FILENAME }}.uf2 76 | path: ${{ env.CI_BUILD_ROOT }}/${{ env.CI_RELEASE_FILENAME }}.uf2 77 | 78 | - name: "Artifacts: Upload .uf2 (With Filesystem)" 79 | uses: actions/upload-artifact@v4 80 | with: 81 | if-no-files-found: ignore 82 | name: ${{ env.CI_RELEASE_FILENAME }}-with-filesystem.uf2 83 | path: ${{ env.CI_BUILD_ROOT }}/${{ env.CI_RELEASE_FILENAME }}-with-filesystem.uf2 84 | 85 | - name: "Release: Upload .uf2" 86 | if: github.event_name == 'release' 87 | uses: softprops/action-gh-release@v1 88 | with: 89 | files: ${{ env.CI_BUILD_ROOT }}/${{ env.CI_RELEASE_FILENAME }}.uf2 90 | 91 | - name: "Release: Upload .uf2 (With Filesystem)" 92 | if: github.event_name == 'release' 93 | uses: softprops/action-gh-release@v1 94 | with: 95 | files: ${{ env.CI_BUILD_ROOT }}/${{ env.CI_RELEASE_FILENAME }}-with-filesystem.uf2 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | **/build 35 | .vscode 36 | 37 | # Apple filesystem cruft 38 | .DS_Store 39 | venv -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Pimoroni Ltd 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 | # Pimoroni Presto 2 | 3 | ## RP2350-powered 4" touchscreen display with RGB ambient lighting 4 | 5 | This repository is home to the MicroPython firmware and examples for 6 | Pimoroni Presto. 7 | 8 | - [Get Pimoroni Presto](#get-pimoroni-presto) 9 | - [Download Firmware](#download-firmware) 10 | - [Installation](#installation) 11 | - [Useful Links](#useful-links) 12 | - [Other Resources](#other-resources) 13 | 14 | ## Get Pimoroni Presto 15 | 16 | * [Pimoroni Presto](https://shop.pimoroni.com/products/presto) 17 | 18 | ## Download Firmware 19 | 20 | You can find the latest firmware releases at [https://github.com/pimoroni/presto/releases/latest](https://github.com/pimoroni/presto/releases/latest). 21 | 22 | There are two choices, a regular build that just updates the MicroPython firmware and a "-with-examples" build which includes everything in [examples](examples). 23 | 24 | :warning: If you've changed any of the code on your board then back up before flashing "-with-examples" - *your files will be erased!* 25 | 26 | ## Installation 27 | 28 | 1. Connect Presto to your computer with a USB-C cable. 29 | 2. Put your device into bootloader mode by holding down the BOOT button whilst tapping RESET. 30 | 3. Drag and drop the downloaded .uf2 file to the "RP2350" drive that appears. 31 | 4. Your device should reset, and you should then be able to connect to it using [Thonny](https://thonny.org/). 32 | 33 | ## Useful Links 34 | 35 | * [Learn: Getting Started with Presto](https://learn.pimoroni.com/article/getting-started-with-presto) 36 | * [Function Reference](docs/presto.md) 37 | * [Pico Graphics documentation](https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/picographics/README.md) 38 | * [Pico Vector documentation](docs/picovector.md) 39 | * [EzWiFi documentation](docs/wifi.md) 40 | 41 | ## Other Resources 42 | 43 | Links to community projects and other resources that you might find helpful can be found below. Note that these code examples have not been written/tested by us and we're not able to offer support with them. 44 | 45 | - PrestoDeck (Spotify Music Player) - [Youtube](https://www.youtube.com/watch?v=iOz5XUVkFkY) / [Github](https://github.com/fatihak/PrestoDeck) 46 | - [Last.fm Now Playing Display](https://github.com/andypiper/presto-lastfm) 47 | - [PrestoMaze - maze generation and solving](https://github.com/kurosuke/PrestoMaze/) 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /boards/common.cmake: -------------------------------------------------------------------------------- 1 | # Make sure we get our VirtualEnv Python 2 | set(Python_FIND_VIRTUALENV "FIRST") 3 | set(Python_FIND_UNVERSIONED_NAMES "FIRST") 4 | set(Python_FIND_STRATEGY "LOCATION") 5 | find_package (Python COMPONENTS Interpreter Development) 6 | 7 | message("dir2uf2/py_decl: Using Python ${Python_EXECUTABLE}") 8 | MESSAGE("dir2uf2/py_decl: Using pimoroni tools dir ${PIMORONI_TOOLS_DIR}") 9 | 10 | # Convert supplies paths to absolute, for a quieter life 11 | get_filename_component(PIMORONI_UF2_MANIFEST ${PIMORONI_UF2_MANIFEST} REALPATH) 12 | get_filename_component(PIMORONI_UF2_DIR ${PIMORONI_UF2_DIR} REALPATH) 13 | 14 | if (EXISTS "${PIMORONI_TOOLS_DIR}/py_decl/py_decl.py") 15 | MESSAGE("py_decl: py_decl.py found, will verify uf2.") 16 | add_custom_target("${MICROPY_TARGET}-verify" ALL 17 | COMMAND ${Python_EXECUTABLE} "${PIMORONI_TOOLS_DIR}/py_decl/py_decl.py" --to-json --verify "${CMAKE_CURRENT_BINARY_DIR}/${MICROPY_TARGET}.uf2" 18 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 19 | COMMENT "pydecl: Verifying ${MICROPY_TARGET}.uf2" 20 | DEPENDS ${MICROPY_TARGET} 21 | ) 22 | endif() 23 | 24 | if (EXISTS "${PIMORONI_TOOLS_DIR}/dir2uf2/dir2uf2" AND EXISTS "${PIMORONI_UF2_MANIFEST}" AND EXISTS "${PIMORONI_UF2_DIR}") 25 | MESSAGE("dir2uf2: Using manifest ${PIMORONI_UF2_MANIFEST}.") 26 | MESSAGE("dir2uf2: Using root ${PIMORONI_UF2_DIR}.") 27 | add_custom_target("${MICROPY_TARGET}-with-filesystem.uf2" ALL 28 | COMMAND ${Python_EXECUTABLE} "${PIMORONI_TOOLS_DIR}/dir2uf2/dir2uf2" --fs-compact --sparse --append-to "${MICROPY_TARGET}.uf2" --manifest "${PIMORONI_UF2_MANIFEST}" --filename with-filesystem.uf2 "${PIMORONI_UF2_DIR}" 29 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 30 | COMMENT "dir2uf2: Appending filesystem to ${MICROPY_TARGET}.uf2." 31 | DEPENDS ${MICROPY_TARGET} 32 | DEPENDS "${MICROPY_TARGET}-verify" 33 | ) 34 | else() 35 | MESSAGE("dir2uf2: Could not find manifest ${PIMORONI_UF2_MANIFEST}") 36 | MESSAGE(" and/or root ${PIMORONI_UF2_DIR}.") 37 | endif() -------------------------------------------------------------------------------- /boards/manifest-common.py: -------------------------------------------------------------------------------- 1 | # https://github.com/micropython/micropython-lib/blob/master/micropython/bundles/bundle-networking/manifest.py 2 | require("bundle-networking") 3 | require("urllib.urequest") 4 | require("umqtt.simple") 5 | 6 | # Handy for dealing with APIs 7 | require("datetime") 8 | 9 | # SD Card 10 | require("sdcard") 11 | 12 | # Bluetooth 13 | require("aioble") 14 | 15 | # Include the manifest.py from micropython/ports/rp2/boards/manifest.py 16 | include("$(PORT_DIR)/boards/manifest.py") 17 | 18 | # Include the manifest.py from micropython//manifest.py 19 | include("$(BOARD_DIR)/manifest.py") 20 | 21 | # Include pga/modules/py_frozen 22 | freeze("../modules/py_frozen/") 23 | -------------------------------------------------------------------------------- /boards/presto/manifest.py: -------------------------------------------------------------------------------- 1 | include("$(PORT_DIR)/boards/manifest.py") 2 | include("../manifest-common.py") -------------------------------------------------------------------------------- /boards/presto/manifest.txt: -------------------------------------------------------------------------------- 1 | *.af 2 | *.png 3 | *.16bpp 4 | main.py 5 | attitude_indicator.py 6 | awesome_game.py 7 | cheerlights_bulb.py 8 | cubes.py 9 | image_gallery.py 10 | random_maze.py 11 | secrets.py 12 | sensor-stick-temperature.py 13 | stop_watch.py 14 | tomato.py 15 | vector_clock_full.py 16 | word_clock.py 17 | gallery/*.jpg -------------------------------------------------------------------------------- /boards/presto/mpconfigboard.cmake: -------------------------------------------------------------------------------- 1 | # cmake file for the Pimoroni PGA2350 2 | set(PICO_BOARD "presto") 3 | set(PICO_PLATFORM "rp2350-arm-s") 4 | set(PICO_NUM_GPIOS 48) 5 | 6 | # Make sure we find pga2350.h (PICO_BOARD) in the current dir 7 | set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_LIST_DIR}) 8 | 9 | # Board specific version of the frozen manifest 10 | set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) 11 | 12 | # If USER_C_MODULES or MicroPython customisations use malloc then 13 | # there needs to be some RAM reserved for the C heap 14 | set(MICROPY_C_HEAP_SIZE 4096) 15 | 16 | # Links micropy_lib_lwip and sets MICROPY_PY_LWIP = 1 17 | # Picked up and expanded upon in mpconfigboard.h 18 | set(MICROPY_PY_LWIP ON) 19 | 20 | # Links cyw43-driver and sets: 21 | # MICROPY_PY_NETWORK_CYW43 = 1, 22 | # MICROPY_PY_SOCKET_DEFAULT_TIMEOUT_MS = 30000 23 | set(MICROPY_PY_NETWORK_CYW43 ON) 24 | 25 | # Adds mpbthciport.c 26 | # And sets: 27 | # MICROPY_PY_BLUETOOTH = 1, 28 | # MICROPY_PY_BLUETOOTH_USE_SYNC_EVENTS = 1, 29 | # MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE = 1 30 | set(MICROPY_PY_BLUETOOTH ON) 31 | 32 | # Links pico_btstack_hci_transport_cyw43 33 | # And sets: 34 | # MICROPY_BLUETOOTH_BTSTACK = 1, 35 | # MICROPY_BLUETOOTH_BTSTACK_CONFIG_FILE = 36 | set(MICROPY_BLUETOOTH_BTSTACK ON) 37 | 38 | # Sets: 39 | # CYW43_ENABLE_BLUETOOTH = 1, 40 | # MICROPY_PY_BLUETOOTH_CYW43 = 1 41 | set(MICROPY_PY_BLUETOOTH_CYW43 ON) 42 | 43 | set(MICROPY_HW_ENABLE_PSRAM ON) 44 | 45 | # Board specific version of the frozen manifest 46 | set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) 47 | 48 | set(PIMORONI_UF2_MANIFEST ${MICROPY_BOARD_DIR}/manifest.txt) 49 | set(PIMORONI_UF2_DIR ${CMAKE_CURRENT_LIST_DIR}/../../examples) 50 | include(${CMAKE_CURRENT_LIST_DIR}/../common.cmake) -------------------------------------------------------------------------------- /boards/presto/mpconfigboard.h: -------------------------------------------------------------------------------- 1 | // Board and hardware specific configuration 2 | 3 | #define MICROPY_HW_BOARD_NAME "Presto" 4 | 5 | // Portion of onboard flash to reserve for the user filesystem 6 | // PGA2350 has 16MB flash, so reserve 2MiB for the firmware and leave 14MiB 7 | #define MICROPY_HW_FLASH_STORAGE_BYTES (14 * 1024 * 1024) 8 | 9 | // Set up networking. 10 | #define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "PPP2" 11 | 12 | // Enable WiFi & PPP 13 | #define MICROPY_PY_NETWORK (1) 14 | #define MICROPY_PY_NETWORK_PPP_LWIP (1) 15 | 16 | // CYW43 driver configuration. 17 | #define CYW43_USE_SPI (1) 18 | #define CYW43_LWIP (1) 19 | #define CYW43_GPIO (1) 20 | #define CYW43_SPI_PIO (1) 21 | 22 | #ifndef CYW43_WL_GPIO_COUNT 23 | #define CYW43_WL_GPIO_COUNT 3 24 | #endif 25 | 26 | #define MICROPY_HW_PIN_EXT_COUNT CYW43_WL_GPIO_COUNT 27 | 28 | int mp_hal_is_pin_reserved(int n); 29 | #define MICROPY_HW_PIN_RESERVED(i) mp_hal_is_pin_reserved(i) 30 | 31 | // Alias the chip select pin specified by presto.h 32 | #define MICROPY_HW_PSRAM_CS_PIN PIMORONI_PRESTO_PSRAM_CS_PIN 33 | 34 | #define MICROPY_PY_THREAD (0) 35 | #define MICROPY_GC_SPLIT_HEAP (0) -------------------------------------------------------------------------------- /boards/presto/pins.csv: -------------------------------------------------------------------------------- 1 | GP0,GPIO0 2 | GP1,GPIO1 3 | GP2,GPIO2 4 | GP3,GPIO3 5 | GP4,GPIO4 6 | GP5,GPIO5 7 | GP6,GPIO6 8 | GP7,GPIO7 9 | GP8,GPIO8 10 | GP9,GPIO9 11 | GP10,GPIO10 12 | GP11,GPIO11 13 | GP12,GPIO12 14 | GP13,GPIO13 15 | GP14,GPIO14 16 | GP15,GPIO15 17 | GP16,GPIO16 18 | GP17,GPIO17 19 | GP18,GPIO18 20 | GP19,GPIO19 21 | GP20,GPIO20 22 | GP21,GPIO21 23 | GP22,GPIO22 24 | GP25,GPIO25 25 | GP26,GPIO26 26 | GP27,GPIO27 27 | GP28,GPIO28 28 | GP29,GPIO29 29 | GP30,GPIO30 30 | GP31,GPIO31 31 | GP32,GPIO32 32 | GP33,GPIO33 33 | GP34,GPIO34 34 | GP35,GPIO35 35 | GP36,GPIO36 36 | GP37,GPIO37 37 | GP38,GPIO38 38 | GP39,GPIO39 39 | GP40,GPIO40 40 | GP41,GPIO41 41 | GP42,GPIO42 42 | GP43,GPIO43 43 | GP44,GPIO44 44 | GP45,GPIO45 45 | GP46,GPIO46 46 | GP47,GPIO47 47 | LED,GPIO25 -------------------------------------------------------------------------------- /boards/presto/presto.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | // ----------------------------------------------------- 8 | // NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO 9 | // SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES 10 | // ----------------------------------------------------- 11 | 12 | // This header may be included by other board headers as "boards/pimoroni_presto.h" 13 | 14 | // pico_cmake_set PICO_PLATFORM=rp2350 15 | 16 | #ifndef _BOARDS_PIMORONI_PRESTO_H 17 | #define _BOARDS_PIMORONI_PRESTO_H 18 | 19 | // For board detection 20 | #define PIMORONI_PRESTO 21 | 22 | // --- BOARD SPECIFIC --- 23 | #define PIMORONI_PRESTO_PSRAM_CS_PIN 47 24 | 25 | // --- UART --- 26 | #ifndef PICO_DEFAULT_UART 27 | #define PICO_DEFAULT_UART 0 28 | #endif 29 | #ifndef PICO_DEFAULT_UART_TX_PIN 30 | #define PICO_DEFAULT_UART_TX_PIN 0 31 | #endif 32 | #ifndef PICO_DEFAULT_UART_RX_PIN 33 | #define PICO_DEFAULT_UART_RX_PIN 1 34 | #endif 35 | 36 | // --- LED --- 37 | #ifndef PICO_DEFAULT_LED_PIN 38 | #define PICO_DEFAULT_LED_PIN 25 39 | #endif 40 | // no PICO_DEFAULT_WS2812_PIN 41 | 42 | // --- I2C --- 43 | #ifndef PICO_DEFAULT_I2C 44 | #define PICO_DEFAULT_I2C 0 45 | #endif 46 | #ifndef PICO_DEFAULT_I2C_SDA_PIN 47 | #define PICO_DEFAULT_I2C_SDA_PIN 40 48 | #endif 49 | #ifndef PICO_DEFAULT_I2C_SCL_PIN 50 | #define PICO_DEFAULT_I2C_SCL_PIN 41 51 | #endif 52 | 53 | // --- SPI --- 54 | #ifndef PICO_DEFAULT_SPI 55 | #define PICO_DEFAULT_SPI 0 56 | #endif 57 | #ifndef PICO_DEFAULT_SPI_SCK_PIN 58 | #define PICO_DEFAULT_SPI_SCK_PIN 18 59 | #endif 60 | #ifndef PICO_DEFAULT_SPI_TX_PIN 61 | #define PICO_DEFAULT_SPI_TX_PIN 19 62 | #endif 63 | #ifndef PICO_DEFAULT_SPI_RX_PIN 64 | #define PICO_DEFAULT_SPI_RX_PIN 16 65 | #endif 66 | #ifndef PICO_DEFAULT_SPI_CSN_PIN 67 | #define PICO_DEFAULT_SPI_CSN_PIN 17 68 | #endif 69 | 70 | // --- FLASH --- 71 | 72 | #define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 73 | 74 | #ifndef PICO_FLASH_SPI_CLKDIV 75 | #define PICO_FLASH_SPI_CLKDIV 2 76 | #endif 77 | 78 | // pico_cmake_set_default PICO_FLASH_SIZE_BYTES = (16 * 1024 * 1024) 79 | #ifndef PICO_FLASH_SIZE_BYTES 80 | #define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024) 81 | #endif 82 | 83 | // --- WIRELESS --- 84 | 85 | // PICO_CONFIG: CYW43_DEFAULT_PIN_WL_REG_ON, gpio pin to power up the cyw43 chip, type=int, default=23, advanced=true, group=pico_cyw43_driver 86 | #ifndef CYW43_DEFAULT_PIN_WL_REG_ON 87 | #define CYW43_DEFAULT_PIN_WL_REG_ON 23u 88 | #endif 89 | 90 | // PICO_CONFIG: CYW43_DEFAULT_PIN_WL_DATA_OUT, gpio pin for spi data out to the cyw43 chip, type=int, default=24, advanced=true, group=pico_cyw43_driver 91 | #ifndef CYW43_DEFAULT_PIN_WL_DATA_OUT 92 | #define CYW43_DEFAULT_PIN_WL_DATA_OUT 24u 93 | #endif 94 | 95 | // PICO_CONFIG: CYW43_DEFAULT_PIN_WL_DATA_IN, gpio pin for spi data in from the cyw43 chip, type=int, default=24, advanced=true, group=pico_cyw43_driver 96 | #ifndef CYW43_DEFAULT_PIN_WL_DATA_IN 97 | #define CYW43_DEFAULT_PIN_WL_DATA_IN 24u 98 | #endif 99 | 100 | // PICO_CONFIG: CYW43_DEFAULT_PIN_WL_HOST_WAKE, gpio (irq) pin for the irq line from the cyw43 chip, type=int, default=24, advanced=true, group=pico_cyw43_driver 101 | #ifndef CYW43_DEFAULT_PIN_WL_HOST_WAKE 102 | #define CYW43_DEFAULT_PIN_WL_HOST_WAKE 24u 103 | #endif 104 | 105 | // PICO_CONFIG: CYW43_DEFAULT_PIN_WL_CLOCK, gpio pin for the spi clock line to the cyw43 chip, type=int, default=29, advanced=true, group=pico_cyw43_driver 106 | #ifndef CYW43_DEFAULT_PIN_WL_CLOCK 107 | #define CYW43_DEFAULT_PIN_WL_CLOCK 29u 108 | #endif 109 | 110 | // PICO_CONFIG: CYW43_DEFAULT_PIN_WL_CS, gpio pin for the spi chip select to the cyw43 chip, type=int, default=25, advanced=true, group=pico_cyw43_driver 111 | #ifndef CYW43_DEFAULT_PIN_WL_CS 112 | #define CYW43_DEFAULT_PIN_WL_CS 25u 113 | #endif 114 | 115 | // no PICO_SMPS_MODE_PIN 116 | // no PICO_VBUS_PIN 117 | // no PICO_VSYS_PIN 118 | 119 | #ifndef PICO_RP2350_A2_SUPPORTED 120 | #define PICO_RP2350_A2_SUPPORTED 1 121 | #endif 122 | 123 | // Increase the clock divider to allow additional overclocking headroom 124 | #define CYW43_PIO_CLOCK_DIV_INT 3 125 | 126 | // Allocate LWIP buffers in PSRAM 127 | #define LWIP_DECLARE_MEMORY_ALIGNED(variable_name, size) __attribute__((section(".psram_data"), aligned(4))) uint8_t variable_name[LWIP_MEM_ALIGN_BUFFER(size)] 128 | 129 | // Default the system clock to 200MHz for best performance 130 | #define SYS_CLK_HZ 200000000 131 | #define PLL_SYS_VCO_FREQ_HZ 1200000000 132 | #define PLL_SYS_POSTDIV1 6 133 | #define PLL_SYS_POSTDIV2 1 134 | 135 | #endif 136 | -------------------------------------------------------------------------------- /boards/presto/usermodules.cmake: -------------------------------------------------------------------------------- 1 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../") 2 | 3 | include(usermod-common) -------------------------------------------------------------------------------- /boards/usermod-common.cmake: -------------------------------------------------------------------------------- 1 | if(NOT DEFINED PIMORONI_PICO_PATH) 2 | set(PIMORONI_PICO_PATH ${CMAKE_CURRENT_LIST_DIR}/../pimoroni-pico) 3 | endif() 4 | include(${PIMORONI_PICO_PATH}/pimoroni_pico_import.cmake) 5 | 6 | include_directories(${CMAKE_CURRENT_LIST_DIR}/..) 7 | include_directories(${PIMORONI_PICO_PATH}/micropython) 8 | 9 | list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython") 10 | list(APPEND CMAKE_MODULE_PATH "${PIMORONI_PICO_PATH}/micropython/modules") 11 | 12 | # Allows us to find /modules/c//micropython 13 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/..") 14 | 15 | set(CMAKE_C_STANDARD 11) 16 | set(CMAKE_CXX_STANDARD 17) 17 | 18 | include(modules/c/presto/micropython) 19 | 20 | # Essential 21 | include(pimoroni_i2c/micropython) 22 | include(pimoroni_bus/micropython) 23 | 24 | # Pico Graphics Essential 25 | include(hershey_fonts/micropython) 26 | include(bitmap_fonts/micropython) 27 | include(picographics/micropython) 28 | 29 | # Pico Graphics Extra 30 | include(pngdec/micropython) 31 | include(jpegdec/micropython) 32 | include(picovector/micropython) 33 | include(qrcode/micropython/micropython) 34 | 35 | # Sensors & Breakouts 36 | include(micropython-common-breakouts) 37 | 38 | # Utility 39 | include(adcfft/micropython) 40 | 41 | # LEDs & Matrices 42 | include(plasma/micropython) 43 | 44 | # ULAB 45 | include(micropython-common-ulab) 46 | enable_ulab() 47 | 48 | include(modules_py/modules_py) 49 | 50 | # C++ Magic Memory 51 | include(cppmem/micropython) 52 | target_compile_definitions(usermod INTERFACE 53 | CPP_FIXED_HEAP_SIZE=256) 54 | 55 | # Disable build-busting C++ exceptions 56 | include(micropython-disable-exceptions) -------------------------------------------------------------------------------- /ci/micropython.sh: -------------------------------------------------------------------------------- 1 | export TERM=${TERM:="xterm-256color"} 2 | 3 | MICROPYTHON_FLAVOUR="pimoroni" 4 | MICROPYTHON_VERSION="feature/presto-wireless-2025" 5 | 6 | PIMORONI_PICO_FLAVOUR="pimoroni" 7 | PIMORONI_PICO_VERSION="feature/picovector2-and-layers" 8 | 9 | PY_DECL_VERSION="v0.0.3" 10 | DIR2UF2_VERSION="v0.0.9" 11 | 12 | 13 | function log_success { 14 | echo -e "$(tput setaf 2)$1$(tput sgr0)" 15 | } 16 | 17 | function log_inform { 18 | echo -e "$(tput setaf 6)$1$(tput sgr0)" 19 | } 20 | 21 | function log_warning { 22 | echo -e "$(tput setaf 1)$1$(tput sgr0)" 23 | } 24 | 25 | function ci_pimoroni_pico_clone { 26 | log_inform "Using Pimoroni Pico $PIMORONI_PICO_FLAVOUR/$PIMORONI_PICO_VERSION" 27 | git clone https://github.com/$PIMORONI_PICO_FLAVOUR/pimoroni-pico "$CI_BUILD_ROOT/pimoroni-pico" 28 | cd "$CI_BUILD_ROOT/pimoroni-pico" || return 1 29 | git checkout $PIMORONI_PICO_VERSION 30 | git submodule update --init 31 | cd "$CI_BUILD_ROOT" 32 | } 33 | 34 | function ci_micropython_clone { 35 | log_inform "Using MicroPython $MICROPYTHON_FLAVOUR/$MICROPYTHON_VERSION" 36 | git clone https://github.com/$MICROPYTHON_FLAVOUR/micropython "$CI_BUILD_ROOT/micropython" 37 | cd "$CI_BUILD_ROOT/micropython" || return 1 38 | git checkout $MICROPYTHON_VERSION 39 | git submodule update --init lib/pico-sdk 40 | git submodule update --init lib/cyw43-driver 41 | git submodule update --init lib/lwip 42 | git submodule update --init lib/mbedtls 43 | git submodule update --init lib/micropython-lib 44 | git submodule update --init lib/tinyusb 45 | git submodule update --init lib/btstack 46 | cd "$CI_BUILD_ROOT" 47 | } 48 | 49 | function ci_tools_clone { 50 | mkdir -p "$CI_BUILD_ROOT/tools" 51 | git clone https://github.com/gadgetoid/py_decl -b "$PY_DECL_VERSION" "$CI_BUILD_ROOT/tools/py_decl" 52 | git clone https://github.com/gadgetoid/dir2uf2 -b "$DIR2UF2_VERSION" "$CI_BUILD_ROOT/tools/dir2uf2" 53 | python3 -m pip install littlefs-python==0.12.0 54 | } 55 | 56 | function ci_micropython_build_mpy_cross { 57 | cd "$CI_BUILD_ROOT/micropython/mpy-cross" || return 1 58 | ccache --zero-stats || true 59 | CROSS_COMPILE="ccache " make 60 | ccache --show-stats || true 61 | cd "$CI_BUILD_ROOT" 62 | } 63 | 64 | function ci_apt_install_build_deps { 65 | sudo apt update && sudo apt install ccache 66 | } 67 | 68 | function ci_prepare_all { 69 | ci_tools_clone 70 | ci_micropython_clone 71 | ci_pimoroni_pico_clone 72 | ci_micropython_build_mpy_cross 73 | } 74 | 75 | function ci_debug { 76 | log_inform "Project root: $CI_PROJECT_ROOT" 77 | log_inform "Build root: $CI_BUILD_ROOT" 78 | } 79 | 80 | function micropython_version { 81 | BOARD=$1 82 | echo "MICROPY_GIT_TAG=$MICROPYTHON_VERSION, $BOARD $TAG_OR_SHA" >> $GITHUB_ENV 83 | echo "MICROPY_GIT_HASH=$MICROPYTHON_VERSION-$TAG_OR_SHA" >> $GITHUB_ENV 84 | } 85 | 86 | function ci_cmake_configure { 87 | BOARD=$1 88 | TOOLS_DIR="$CI_BUILD_ROOT/tools" 89 | MICROPY_BOARD_DIR=$CI_PROJECT_ROOT/boards/$BOARD 90 | if [ ! -f "$MICROPY_BOARD_DIR/usermodules.cmake" ]; then 91 | log_warning "Invalid board: \"$BOARD\". Run with ci_cmake_configure ." 92 | return 1 93 | fi 94 | BUILD_DIR="$CI_BUILD_ROOT/build-$BOARD" 95 | cmake -S $CI_BUILD_ROOT/micropython/ports/rp2 -B "$BUILD_DIR" \ 96 | -DPICOTOOL_FORCE_FETCH_FROM_GIT=1 \ 97 | -DPICO_BUILD_DOCS=0 \ 98 | -DPICO_NO_COPRO_DIS=1 \ 99 | -DPICOTOOL_FETCH_FROM_GIT_PATH="$TOOLS_DIR/picotool" \ 100 | -DPIMORONI_PICO_PATH="$CI_BUILD_ROOT/pimoroni-pico" \ 101 | -DPIMORONI_TOOLS_DIR="$TOOLS_DIR" \ 102 | -DUSER_C_MODULES="$MICROPY_BOARD_DIR/usermodules.cmake" \ 103 | -DMICROPY_BOARD_DIR="$MICROPY_BOARD_DIR" \ 104 | -DMICROPY_BOARD="$BOARD" \ 105 | -DCMAKE_C_COMPILER_LAUNCHER=ccache \ 106 | -DCMAKE_CXX_COMPILER_LAUNCHER=ccache 107 | } 108 | 109 | function ci_cmake_build { 110 | BOARD=$1 111 | MICROPY_BOARD_DIR=$CI_PROJECT_ROOT/boards/$BOARD 112 | if [ ! -f "$MICROPY_BOARD_DIR/usermodules.cmake" ]; then 113 | log_warning "Invalid board: \"$BOARD\". Run with ci_cmake_build ." 114 | return 1 115 | fi 116 | BUILD_DIR="$CI_BUILD_ROOT/build-$BOARD" 117 | ccache --zero-stats || true 118 | cmake --build $BUILD_DIR -j 2 119 | ccache --show-stats || true 120 | 121 | if [ -z ${CI_RELEASE_FILENAME+x} ]; then 122 | CI_RELEASE_FILENAME=$BOARD 123 | fi 124 | 125 | log_inform "Copying .uf2 to $(pwd)/$CI_RELEASE_FILENAME.uf2" 126 | cp "$BUILD_DIR/firmware.uf2" $CI_RELEASE_FILENAME.uf2 127 | 128 | if [ -f "$BUILD_DIR/firmware-with-filesystem.uf2" ]; then 129 | log_inform "Copying -with-filesystem .uf2 to $(pwd)/$CI_RELEASE_FILENAME-with-filesystem.uf2" 130 | cp "$BUILD_DIR/firmware-with-filesystem.uf2" $CI_RELEASE_FILENAME-with-filesystem.uf2 131 | fi 132 | } 133 | 134 | if [ -z ${CI_USE_ENV+x} ] || [ -z ${CI_PROJECT_ROOT+x} ] || [ -z ${CI_BUILD_ROOT+x} ]; then 135 | SCRIPT_PATH="$(dirname $0)" 136 | CI_PROJECT_ROOT=$(realpath "$SCRIPT_PATH/..") 137 | CI_BUILD_ROOT=$(pwd) 138 | fi 139 | 140 | ci_debug -------------------------------------------------------------------------------- /docs/picovector.md: -------------------------------------------------------------------------------- 1 | # PicoVector 2 | 3 | PicoVector is a vector graphics library for the Raspberry Pi Pico. 4 | 5 | Instead of painting individual pixels, it deals with points and paths which 6 | make up shapes from simple rectangles to complex UI elements or typography. 7 | 8 | 9 | ## Getting Started 10 | 11 | To get started with PicoVector on Presto you must first import and set it up 12 | by passing in your PicoGraphics surface: 13 | 14 | ```python 15 | from presto import Presto 16 | from picovector import PicoVector, Polygon, Transform, ANTIALIAS_BEST 17 | 18 | presto = Presto() 19 | vector = PicoVector(presto.display) 20 | 21 | transform = Transform() 22 | vector.set_transform(transform) 23 | 24 | vector.set_antialiasing(ANTIALIAS_BEST) 25 | ``` 26 | 27 | ## Transforms 28 | 29 | Scaling and rotating vectors is accomplished with a `Transform` and happens 30 | when one is drawn. In most cases it will suffice to create one `Transform` 31 | and then apply rotation and scale as needed. 32 | 33 | * `rotate(angle, (x, y))` - rotate polygons by `angle` in degrees 34 | around point `(x, y)`. 35 | * `scale(scale_x, scale_y)` - apply a scale, change the size of polygons 36 | * `translate(x, y)` - apply a translation, change the position of polygons 37 | * `matrix([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0])` - apply an arbitrary matrix transformation 38 | * `reset()` - reset the transform object back to the default (no transform) 39 | 40 | Internally transforms are matrix operations, so the order you apply them 41 | both matters and may be counter-intuitive. 42 | 43 | If, for example, you want to rotate something in the middle of the screen 44 | you'll find that you need to rotate it *first* and then translate it into 45 | position. Eg: 46 | 47 | ```python 48 | shape = Polygon() 49 | shape.path((-10, -20), (10, 0), (-10, 20)) 50 | 51 | transform = Transform() 52 | transform.translate(presto.width // 2, presto.height // 2) 53 | transform.rotate(a, (0, 0)) 54 | 55 | vector.set_transform(transform) 56 | vector.draw(shape) 57 | ``` 58 | 59 | ## Antialiasing 60 | 61 | Behind the scenes all of PicoVector's drawing is done by PicoGraphics- by 62 | setting pixels. Unlike just directly drawing shapes with pixels PicoVector 63 | includes anti-aliasing, a smoothing technique that turns diagonal lines 64 | into the crisp, blended edges we'd expect from computers today. 65 | 66 | Available options are: 67 | 68 | * `ANTIALIAS_NONE` - turns off anti-aliasing for best performance 69 | * `ANTIALIAS_FAST` - 4x anti-aliasing, a good balance between speed & quality 70 | * `ANTIALIAS_BEST` - 16x anti-aliasing, best quality at the expense of speed 71 | 72 | ## Polygons & Primitives 73 | 74 | The basis of all drawing operations in PicoVector is the `Polygon` object. 75 | 76 | A `Polygon` is a collection of one or more paths to be drawn together. This 77 | allows for shapes with holes - letters, for example - or more complicated 78 | designs - logos or icons - to be scaled, rotated and drawn at once. 79 | 80 | If paths overlap then the top-most path will "punch" out the one underneath. 81 | 82 | To use any of the primitives or path drawing methods you must first create 83 | a `Polygon`, for example here's a simple triangle: 84 | 85 | ```python 86 | from picovector import Polygon 87 | my_shape = Polygon() 88 | my_shape.path((10, 0), (0, 10), (20, 10)) 89 | ``` 90 | 91 | ### Path 92 | 93 | * `path((x, y), (x2, y2), (x3, y3), ...)` 94 | 95 | A path is simply an arbitrary list of points that produce a complete closed 96 | shape. It's ideal for drawing complex shapes such as logos or icons. 97 | 98 | If you have a list of points you can use Python's spread operator to pass it 99 | into `path`, eg: 100 | 101 | ```python 102 | my_points = [(10, 0), (0, 10), (20, 10)] 103 | my_shape = Polygon() 104 | my_shape.path(*my_points) 105 | ``` 106 | 107 | ### Rectangle 108 | 109 | * `rectangle(x, y, w, h, corners=(r1, r2, r3, r4), stroke=0)` 110 | 111 | A rectangle is a plain old rectangular shape with optional rounded corners. 112 | 113 | If `stroke` is greater than zero then the rectangle outline will be produced. 114 | 115 | If any of the corner radii are greater than zero then that corner will be created. 116 | 117 | ### Regular 118 | 119 | * `regular(x, y, radius, sides, stroke=0)` 120 | 121 | Creates a regular polygon with the given radius and number of sides. Needs at 122 | least 3 sides (an equilateral triangle) and converges on a circle. 123 | 124 | If `stroke` is greater than zero then the regular polygon outline will be created. 125 | 126 | ### Circle 127 | 128 | * `circle(x, y, radius, stroke=0)` 129 | 130 | Effectively a regular polygon, approximates a circle by automatically picking 131 | a number of sides that will look smooth for a given radius. 132 | 133 | If `stroke` is greater than zero then the circle outline will be created. 134 | 135 | ### Arc 136 | 137 | * `arc(x, y, radius, from, to, stroke=0)` 138 | 139 | Create an arc at x, y with radius, from and to angle (degrees). 140 | 141 | Great for radial graphs. 142 | 143 | If `stroke` is greater than zero then the arc outline will be created. 144 | 145 | ### Star 146 | 147 | * `star(x, y, points, inner_radius, outer_radius, stroke=0)` 148 | 149 | Create a star at x, y with given number of points. 150 | 151 | The inner and outer radius (in pixels) define where the points start and end. 152 | 153 | If `stroke` is greater than zero then the arc outline will be created. 154 | 155 | ## Fonts & Text 156 | 157 | Under the hood PicoVector uses [Alright Fonts](https://github.com/lowfatcode/alright-fonts) 158 | a font-format for embedded and low resource platforms. 159 | 160 | Alright Fonts supports converting TTF and OTF fonts into .af format which can 161 | then be displayed using PicoVector. Most of your favourite fonts should work, including silly fonts like [Jokerman](https://en.wikipedia.org/wiki/Jokerman_(typeface)) - but there are some limitations to their complexity. 162 | 163 | ### Converting 164 | 165 | Converting from an OTF or TTF font is done with the `afinate` utility. It's a 166 | Python script that handles decomposing the font into a simple list of points. 167 | 168 | The latest version of this conversion utility can be found at the repo below, along with installation instructions and some pre-converted sample fonts: 169 | 170 | - [PicoVector Fonts / Alright Fonts](https://github.com/pimoroni/picovector-fonts) 171 | 172 | ### Loading & Configuring 173 | 174 | ```python 175 | vector.set_font("jokerman.af", 24) 176 | ``` 177 | 178 | `set_font` specifies the font and size. 179 | 180 | Your *.af font file will need to be present on Presto's file system, you can upload it using Thonny's Files window or another means like `mpremote`. 181 | 182 | ### Spacing & Alignment 183 | 184 | * `set_font_size()` 185 | * `set_font_word_spacing()` 186 | * `set_font_letter_spacing()` 187 | * `set_font_line_height()` 188 | * `set_font_align()` 189 | 190 | ### Measuring Text 191 | 192 | * `x, y, w, h = measure_text(text, x=0, y=0, angle=None)` 193 | 194 | Returns a four tuple with the x, y position, width and height in pixels. 195 | 196 | ### Drawing Text 197 | 198 | * `text(text, x, y, angle=None, max_width=0, max_height=0)` 199 | 200 | When you draw text the x, y position and angle are used to create a new 201 | Transform to control the position and rotation of your text. The transform 202 | set with `.set_transform()` is also applied. 203 | -------------------------------------------------------------------------------- /docs/presto.md: -------------------------------------------------------------------------------- 1 | # Presto 2 | 3 | Most of your interaction with Presto will be through the `presto` module. 4 | 5 | It will help you set up PicoGraphics, touch, WiFi, ambient lighting and more. 6 | 7 | - [Getting Started](#getting-started) 8 | - [Features](#features) 9 | - [Updating The Display](#updating-the-display) 10 | - [Touch](#touch) 11 | - [Back/Ambient Lights](#backambient-lights) 12 | - [Auto LEDs](#auto-leds) 13 | - [Manual LEDs](#manual-leds) 14 | - [Wireless](#wireless) 15 | 16 | ## Getting Started 17 | 18 | Create a new Presto instance: 19 | 20 | ```python 21 | from presto import Presto 22 | 23 | presto = Presto() 24 | ``` 25 | 26 | The `Presto()` class accepts some optional arguments: 27 | 28 | * `full_res=True/False` - Use 480x480 resolution (slower but crisp!) 29 | * `ambient_light=True/False` - automatically run the onboard LEDs 30 | * `layers=1/2` - optionally use multiple layers in PicoGraphics 31 | * `direct_to_fb=True/False` - in `full_res` mode, draws directly to the front-buffer 32 | 33 | ## Features 34 | 35 | ### Updating The Display 36 | 37 | Presto provides you a PicoGraphics instance at `presto.display`, we usually 38 | alias this in code like so: 39 | 40 | ```python 41 | display = presto.display 42 | ``` 43 | 44 | Once you've done your normal PicoGraphics/PicoVector drawing operations you 45 | can either: 46 | 47 | * `presto.update()` - Copy the full front-buffer to the back buffer 48 | * `presto.partial_update(x, y, w, h)` - Copy part of the front-buffer to the back buffer 49 | 50 | ### Touch 51 | 52 | Presto ostensibly supports two simultaneous touches, but there are some caveats. 53 | 54 | Touches that line up horizontally - such as a two finger vertical scroll - will 55 | not be correctly registered. This is a known issue with the touch feature and 56 | not a problem with your board! 57 | 58 | Touches one above the other - two separate horizontal sliders, or maybe a friendly 59 | game of pong? - should register fine! 60 | 61 | To access touch information you can use: 62 | 63 | * `presto.touch_a` - (Property) a three tuple of X, Y and state (True for touched) 64 | * `presto.touch_b` - (Property) a three tuple of X, Y and state 65 | * `presto.touch_delta` - (Property) a two tuple of distance and angle between touches 66 | * `presto.touch_poll()` - Force the touch to be updated 67 | 68 | ### Back/Ambient Lights 69 | 70 | #### Auto LEDs 71 | 72 | If you've set `ambient_light=True` then Presto will automatically update the LEDs 73 | to match the screen content. 74 | 75 | Note - you can disable this with `presto.auto_ambient_leds(False)`. 76 | 77 | #### Manual LEDs 78 | 79 | With Presto's auto backlighting disabled you can control the LED colours 80 | manually, like so: 81 | 82 | 83 | ```python 84 | presto.set_led_hsv(0, 0.5, 1.0, 1.0) 85 | presto.set_led_rgb(1, 255, 255, 0) 86 | ``` 87 | 88 | ### Wireless 89 | 90 | Presto assumes you have a `secrets.py` with the format: 91 | 92 | ```python 93 | WIFI_SSID = "Your SSID" 94 | WIFI_PASSWORD = "Password" 95 | ``` 96 | 97 | Then you can simply: 98 | 99 | ```python 100 | connection_successful = presto.connect() 101 | ``` 102 | 103 | For tips on reporting connection status on-screen [see the wifi docs](wifi.md). 104 | -------------------------------------------------------------------------------- /docs/wifi.md: -------------------------------------------------------------------------------- 1 | # EzWiFi 2 | 3 | - [EzWiFi ](#ezwifi-) 4 | - [Easy EzWiFi](#easy-ezwifi) 5 | - [EzWiFi Class](#ezwifi-class) 6 | - [Asyncio](#asyncio) 7 | - [Connect Options](#connect-options) 8 | - [Handlers](#handlers) 9 | - [Other Functions](#other-functions) 10 | 11 | EzWiFi, or Easy WiFi, is a helper module to get you connected to wireless networks. 12 | 13 | It's based around the use of `secrets.py`, a Python file that very simply tucks 14 | your wireless SSID and password away for use across multiple scripts. 15 | 16 | `secrets.py` looks like this: 17 | 18 | ```python 19 | WIFI_SSID = "your_ssid" 20 | WIFI_PASSWORD = "your_password" 21 | ``` 22 | 23 | ## Easy EzWiFi 24 | 25 | The easiest way to use EzWiFi is with the blocking `connect` method, like so: 26 | 27 | ```python 28 | import ezwifi 29 | 30 | ezwifi.connect() 31 | ``` 32 | 33 | This will load login details from `secrets.py` and quietly connect to your 34 | wireless network. It will try ten times by default with an overall timeout 35 | of 60 seconds. 36 | 37 | If you need a little more debugging information you can 38 | supply a log handler like so: 39 | 40 | ```python 41 | import ezwifi 42 | 43 | ezwifi.connect(verbose=True) 44 | ``` 45 | 46 | If you need specific log messages, want to perform an action or display a message 47 | on screen when connected/failed then you can supply: 48 | 49 | * `connected` - Called when a connection is established (with no message). 50 | * `failed` - Called when the connection fails. 51 | * `info` - Called with basic info messages. 52 | * `warning` - Called when a connection attempt fails (and in other cases in future). 53 | * `error` - Called when a connection totally fails (and in other cases in future). 54 | * `failed` - Called when a connection fails (with no message). 55 | 56 | For example, this will call a function if/when the connection succeeds or fails: 57 | 58 | ```python 59 | import ezwifi 60 | 61 | 62 | def connect_handler(wifi): 63 | pass 64 | 65 | 66 | def failed_handler(wifi): 67 | pass 68 | 69 | 70 | ezwifi.connect(connected=connect_handler, failed=failed_handler) 71 | ``` 72 | 73 | ## EzWiFi Class 74 | 75 | EzWiFi is also available as a class if you need to integrate with async. 76 | 77 | Create an instance with: 78 | 79 | ```python 80 | from ezwifi import EzWiFi 81 | 82 | wifi = EzWiFi() 83 | ``` 84 | 85 | You can then use the async `connect()` method, if you're using the class and 86 | want this to run in synchronous code you'll need to: 87 | 88 | ```python 89 | import asyncio 90 | 91 | asyncio.get_event_loop().run_until_complete(wifi.connect()) 92 | ``` 93 | 94 | ### Asyncio 95 | 96 | With asyncio you can do other things while waiting for WiFi to connect, like so: 97 | 98 | ```python 99 | import asyncio 100 | from ezwifi import EzWiFi 101 | 102 | wifi = EzWiFi() 103 | 104 | 105 | @wifi.on("connected") 106 | async def handle_connect(wifi): 107 | print("Connected!") 108 | 109 | 110 | @wifi.on("failed") 111 | async def handle_connect(wifi): 112 | print("Failed!") 113 | 114 | 115 | async def main(): 116 | wifi_task = asyncio.create_task(wifi.connect()) 117 | while True: 118 | print("Main loop...") 119 | await asyncio.sleep_ms(1000) 120 | 121 | 122 | asyncio.run(main()) 123 | ``` 124 | 125 | ### Connect Options 126 | 127 | You can supply an optional `ssid`, `password`, `timeout` and number of retries to 128 | `connect()`: 129 | 130 | ### Handlers 131 | 132 | If you need specific log messages, want to perform an action or display a message 133 | on screen when connected/failed then you use the following handlers: 134 | 135 | * `connected` - Called when a connection is established (with no message). 136 | * `failed` - Called when the connection fails. 137 | * `info` - Called with basic info messages. 138 | * `warning` - Called when a connection attempt fails (and in other cases in future). 139 | * `error` - Called when a connection totally fails (and in other cases in future). 140 | * `failed` - Called when a connection fails (with no message). 141 | 142 | These can be supplied to EzWiFi as an argument, eg: 143 | 144 | ```python 145 | import asyncio 146 | from ezwifi import EzWiFi 147 | 148 | 149 | async def info_handler(wifi, message): 150 | print(message) 151 | 152 | 153 | wifi = EzWiFi(info=info_handler) 154 | 155 | 156 | async def main(): 157 | wifi_task = asyncio.create_task(wifi.connect()) 158 | while True: 159 | print("Main loop...") 160 | await asyncio.sleep_ms(1000) 161 | 162 | 163 | asyncio.run(main()) 164 | ``` 165 | 166 | Or by using the `wifi.on` decorator: 167 | 168 | ```python 169 | import asyncio 170 | from ezwifi import EzWiFi 171 | 172 | wifi = EzWiFi() 173 | 174 | 175 | @wifi.on("info") 176 | async def info_handler(wifi, message): 177 | print(message) 178 | 179 | 180 | async def main(): 181 | wifi_task = asyncio.create_task(wifi.connect()) 182 | while True: 183 | print("Main loop...") 184 | await asyncio.sleep_ms(1000) 185 | 186 | 187 | asyncio.run(main()) 188 | ``` 189 | 190 | ### Other Functions 191 | 192 | * `ipv4` - returns the ipv4 address, shortcut for `if.ipconfig("addr4")[0]` 193 | * `ipv6` - returns the first ipv6 address, shortcut for `if.ipconfig("addr6")[0][0]` 194 | * `isconnected` - returns the connection status 195 | -------------------------------------------------------------------------------- /drivers/st7701/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(${CMAKE_CURRENT_LIST_DIR}/st7701_presto.cmake) -------------------------------------------------------------------------------- /drivers/st7701/st7701.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "hardware/spi.h" 4 | #include "hardware/dma.h" 5 | #include "hardware/gpio.h" 6 | #include "hardware/pio.h" 7 | #include "hardware/pwm.h" 8 | #include "hardware/clocks.h" 9 | #include "common/pimoroni_common.hpp" 10 | #include "common/pimoroni_bus.hpp" 11 | #include "libraries/pico_graphics/pico_graphics.hpp" 12 | 13 | #include 14 | #include 15 | 16 | namespace pimoroni { 17 | 18 | class ST7701 : public DisplayDriver { 19 | spi_inst_t *spi = PIMORONI_SPI_DEFAULT_INSTANCE; 20 | 21 | //-------------------------------------------------- 22 | // Variables 23 | //-------------------------------------------------- 24 | private: 25 | 26 | // interface pins with our standard defaults where appropriate 27 | uint spi_cs; 28 | uint spi_sck; 29 | uint spi_dat; 30 | uint lcd_bl; 31 | uint parallel_sm; 32 | uint timing_sm; 33 | uint palette_sm; 34 | PIO st_pio; 35 | uint parallel_offset; 36 | uint timing_offset; 37 | uint palette_offset; 38 | uint st_dma; 39 | uint st_dma2; 40 | int st_dma3 = -1; 41 | int st_dma4 = -1; 42 | 43 | uint d0 = 1; // First pin of 18-bit parallel interface 44 | uint hsync = 19; 45 | uint vsync = 20; 46 | uint lcd_de = 21; 47 | uint lcd_dot_clk = 22; 48 | 49 | static const uint32_t SPI_BAUD = 8'000'000; 50 | static const uint32_t BACKLIGHT_PWM_TOP = 6200; 51 | 52 | public: 53 | // Parallel init 54 | ST7701(uint16_t width, uint16_t height, Rotation rotation, SPIPins control_pins, uint16_t* framebuffer, uint32_t* palette = nullptr, 55 | uint d0=1, uint hsync=19, uint vsync=20, uint lcd_de = 21, uint lcd_dot_clk = 22); 56 | 57 | void init(); 58 | void cleanup() override; 59 | void update(PicoGraphics *graphics) override; 60 | void partial_update(PicoGraphics *display, Rect region) override; 61 | void set_backlight(uint8_t brightness) override; 62 | 63 | void set_palette_colour(uint8_t entry, RGB888 colour); 64 | void set_palette_colour(uint8_t entry, const RGB& colour); 65 | 66 | // The format is an 18-bit value: RGB566, followed by the final bit of red. 67 | // It is MSB aligned, i.e. the top bit of red is in the MSB. 68 | uint32_t get_encoded_palette_entry(uint8_t entry) const { return palette[entry]; } 69 | 70 | void set_framebuffer(uint16_t* next_fb) { 71 | next_framebuffer = next_fb; 72 | } 73 | 74 | void wait_for_vsync(); 75 | 76 | // Only to be called by ISR 77 | void drive_timing(); 78 | void handle_end_of_line(); 79 | 80 | private: 81 | void common_init(); 82 | void configure_display(Rotation rotate); 83 | void command(uint8_t command, size_t len = 0, const char *data = NULL); 84 | 85 | void start_line_xfer(); 86 | void start_frame_xfer(); 87 | 88 | // Timing status 89 | uint16_t timing_row = 0; 90 | uint16_t timing_phase = 0; 91 | volatile bool waiting_for_vsync = false; 92 | 93 | uint16_t* framebuffer; 94 | uint16_t* next_framebuffer = nullptr; 95 | 96 | uint32_t* palette = nullptr; 97 | 98 | uint16_t* next_line_addr; 99 | int display_row = 0; 100 | int row_shift = 0; 101 | int fill_row = 0; 102 | }; 103 | 104 | } -------------------------------------------------------------------------------- /drivers/st7701/st7701_palette.pio: -------------------------------------------------------------------------------- 1 | ; Program for decoding a 256 colour palette. Takes an 8-bit index, and creates 2 | ; the memory address of the palette entry. 3 | ; 4 | ; Thanks to DmitryGR for the idea, and implementation this is based on. 5 | 6 | .program st7701_palette 7 | 8 | .wrap_target 9 | out y, 8 10 | in y, 8 11 | in x, 22 12 | .wrap 13 | -------------------------------------------------------------------------------- /drivers/st7701/st7701_parallel.pio: -------------------------------------------------------------------------------- 1 | ; Output 16 bit parallel, bit reversed, RGB565 data every 4th clock 2 | ; Wait for irq 4 from the timing SM between each row 3 | ; Side-set is data enable 4 | 5 | .program st7701_parallel 6 | .side_set 1 7 | 8 | .wrap_target 9 | mov x, y side 1 ; y needs to be set to (width/2)-1 at init time 10 | wait 1 irq 4 side 1 ; wait for the irq from the timing SM 11 | loop: 12 | out isr, 32 side 1 13 | mov pins, ::isr side 1 [1] 14 | in null, 16 side 1 [1] 15 | mov pins, ::isr side 1 [1] 16 | jmp x-- loop side 1 17 | mov pins, null side 1 18 | .wrap 19 | 20 | .program st7701_parallel_18bpp 21 | .side_set 1 22 | 23 | .wrap_target 24 | mov x, y side 0 ; y needs to be set to (width/2)-1 at init time 25 | wait 1 irq 4 side 0 ; wait for the irq from the timing SM 26 | loop: 27 | out isr, 32 side 1 28 | mov pins, ::isr side 1 [1] 29 | out isr, 32 side 1 [1] 30 | mov pins, ::isr side 1 [1] 31 | jmp x-- loop side 1 32 | mov pins, null side 1 33 | .wrap 34 | -------------------------------------------------------------------------------- /drivers/st7701/st7701_presto.cmake: -------------------------------------------------------------------------------- 1 | add_library(st7701_presto INTERFACE) 2 | 3 | target_sources(st7701_presto INTERFACE 4 | ${CMAKE_CURRENT_LIST_DIR}/st7701.cpp) 5 | 6 | pico_generate_pio_header(st7701_presto ${CMAKE_CURRENT_LIST_DIR}/st7701_parallel.pio) 7 | pico_generate_pio_header(st7701_presto ${CMAKE_CURRENT_LIST_DIR}/st7701_timing.pio) 8 | pico_generate_pio_header(st7701_presto ${CMAKE_CURRENT_LIST_DIR}/st7701_palette.pio) 9 | 10 | target_include_directories(st7701_presto INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 11 | 12 | # Pull in pico libraries that we need 13 | target_link_libraries(st7701_presto INTERFACE pico_stdlib pimoroni_bus hardware_spi hardware_pwm hardware_pio hardware_dma pico_graphics) 14 | -------------------------------------------------------------------------------- /drivers/st7701/st7701_timing.pio: -------------------------------------------------------------------------------- 1 | ; Set two sync pins and then wait for a number of cycles based on input word: 2 | ; 31: VSync 3 | ; 30: HSync 4 | ; 29-16: Delay duration in pixel clocks, total loop duration is delay + 3 5 | ; 15-0: Instruction to run after delay, sensible options are: 6 | ; nop : 0xb042 ; Do nothing 7 | ; irq n : 0xd00n ; Signal to CPU or a data PIO 8 | ; Side set is data clock, which runs continuously 9 | .program st7701_timing 10 | .side_set 1 11 | .origin 0 12 | 13 | .wrap_target 14 | out pins, 2 side 0 ; Set VS & HS 15 | out x, 14 side 1 ; Loop count 16 | sync_loop: 17 | nop side 0 18 | jmp x--, sync_loop side 1 19 | out exec, 16 side 0 20 | .wrap 21 | -------------------------------------------------------------------------------- /examples/Roboto-Medium.af: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/Roboto-Medium.af -------------------------------------------------------------------------------- /examples/agile_pricing_display.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-20.0, 16.67), (-20.0, 12.22), (-15.56, 7.78), (-15.56, 16.67), (-20.0, 16.67)], [(-11.11, 16.67), (-11.11, 3.33), (-6.67, -1.11), (-6.67, 16.67), (-11.11, 16.67)], [(-2.22, 16.67), (-2.22, -1.11), (2.22, 3.39), (2.22, 16.67), (-2.22, 16.67)], [(6.67, 16.67), (6.67, 3.39), (11.11, -1.06), (11.11, 16.67), (6.67, 16.67)], [(15.56, 16.67), (15.56, -5.56), (20.0, -10.0), (20.0, 16.67), (15.56, 16.67)], [(-20.0, 5.17), (-20.0, -1.11), (-4.44, -16.67), (4.44, -7.78), (20.0, -23.33), (20.0, -17.06), (4.44, -1.5), (-4.44, -10.39), (-20.0, 5.17)]] 2 | # NAME Energy Price 3 | # DESC Shows last, current and next energy price. 4 | 5 | ''' 6 | A demo for the Pimoroni Presto. 7 | Shows the current, next and last energy price for Octopus Energys Agile Price tarrif 8 | ''' 9 | 10 | import datetime 11 | import time 12 | 13 | import ntptime 14 | import requests 15 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 16 | from presto import Presto 17 | 18 | # Constants 19 | # API_URL = 'https://api.octopus.energy/v1/products/AGILE-24-10-01/electricity-tariffs/E-1R-AGILE-24-10-01-C/standard-unit-rates/' 20 | API_URL = None # Will try to guess the rates 21 | 22 | # Find your region code: https://en.wikipedia.org/wiki/Meter_Point_Administration_Number#Distributor_ID 23 | # This is required for automatic API endpoint detection 24 | REGION_CODE = "_E" 25 | 26 | # Print out API endpoints for debugging 27 | DEBUG = True 28 | 29 | # Setup for the Presto display 30 | 31 | presto = Presto(ambient_light=True) 32 | display = presto.display 33 | WIDTH, HEIGHT = display.get_bounds() 34 | 35 | # Pico Vector 36 | vector = PicoVector(display) 37 | vector.set_antialiasing(ANTIALIAS_BEST) 38 | 39 | t = Transform() 40 | vector.set_font("Roboto-Medium.af", 54) 41 | vector.set_font_letter_spacing(100) 42 | vector.set_font_word_spacing(100) 43 | vector.set_transform(t) 44 | 45 | # Couple of colours for use later 46 | ORANGE = display.create_pen(255, 99, 71) 47 | ORANGE_2 = display.create_pen(255, 99 + 50, 71 + 50) 48 | ORANGE_3 = display.create_pen(255, 99 + 20, 71 + 20) 49 | ORANGE_4 = display.create_pen(255, 99 + 70, 71 + 70) 50 | WHITE = display.create_pen(255, 255, 255) 51 | BLACK = display.create_pen(0, 0, 0) 52 | 53 | MARGIN = 15 54 | 55 | 56 | def show_message(text): 57 | display.set_pen(ORANGE) 58 | display.clear() 59 | display.set_pen(ORANGE_2) 60 | display.text(f"{text}", 5, 10, WIDTH, 2) 61 | presto.update() 62 | 63 | 64 | # Connect to the network and get time. 65 | show_message("Connecting...") 66 | 67 | try: 68 | presto.connect() 69 | except ValueError as e: 70 | while True: 71 | show_message(e) 72 | except ImportError as e: 73 | while True: 74 | show_message(e) 75 | 76 | # Set the correct time using the NTP service. 77 | try: 78 | ntptime.settime() 79 | except OSError: 80 | while True: 81 | show_message("Unable to get time.\n\nCheck network settings in 'secrets.py' and try again.") 82 | 83 | # Keep a record of the last time we updated. 84 | # We only want to be requesting new information every half an hour. 85 | last_updated = time.time() 86 | 87 | 88 | def get_api_endpoint(): 89 | # Get the latest valid API endpoint for prices 90 | # First look up the right product in the products list 91 | request = requests.get("https://api.octopus.energy/v1/products/") 92 | json = request.json() 93 | product = [result for result in json["results"] if result["display_name"].startswith("Agile") and result["direction"] == "IMPORT" and result["brand"] == "OCTOPUS_ENERGY"][0] 94 | product_api_endpoint = [link["href"] for link in product["links"] if link["rel"] == "self"][0] 95 | 96 | # Now get the API endpoint URL by region code 97 | request = requests.get(product_api_endpoint) 98 | json = request.json() 99 | links = json["single_register_electricity_tariffs"][REGION_CODE]["direct_debit_monthly"]["links"] 100 | prices_api_endpoint = [link for link in links if link["rel"] == "standard_unit_rates"][0]["href"] 101 | 102 | if DEBUG: 103 | print(f"Got API endpoint: {prices_api_endpoint}") 104 | return prices_api_endpoint 105 | 106 | 107 | def get_prices(): 108 | try: 109 | # We only need the the first 6 elements covering the date and time 110 | t = time.localtime()[0:5] 111 | 112 | # Put that into a datetime object 113 | period_current = datetime.datetime(*t) 114 | period_next = period_current + datetime.timedelta(hours=1) 115 | period_last = period_current - datetime.timedelta(minutes=30) 116 | 117 | # Construct the time period to/from for our request later. 118 | request_string = (f"?period_from={period_last.year}-{period_last.month}-{period_last.day}T{period_last.hour}:{'00' if period_last.minute <= 29 else 30}Z" 119 | f"&period_to={period_next.year}-{period_next.month}-{period_next.day}T{period_next.hour}:{'00' if period_next.minute <= 29 else '30'}Z") 120 | 121 | # Assemble our URL and make a request. 122 | request = requests.get(f"{API_URL}{request_string}") 123 | json = request.json() 124 | 125 | if DEBUG: 126 | print(f"Prices endpoint: {API_URL}{request_string}") 127 | 128 | # Finally we return our 3 values 129 | return json['results'][0]['value_inc_vat'], json['results'][1]['value_inc_vat'], json['results'][2]['value_inc_vat'] 130 | 131 | # if the above request fails, we want to handle the error and return values to keep the application running. 132 | except ValueError: 133 | return 0, 0, 0 134 | 135 | 136 | # Get the correct API endpoint on start up, if it has not been specified already 137 | if API_URL is None: 138 | API_URL = get_api_endpoint() 139 | 140 | # Get the prices on start up, after this we'll only check again at the top of the hour. 141 | next_price, current_price, last_price = get_prices() 142 | 143 | 144 | while True: 145 | 146 | # Clear the screen and use orange as the background colour 147 | display.set_pen(ORANGE) 148 | display.clear() 149 | 150 | # Draw a big orange circle that's lighter than the background 151 | display.set_pen(ORANGE_4) 152 | v = Polygon() 153 | v.circle(0, HEIGHT // 2, 190) 154 | vector.draw(v) 155 | 156 | # Check if it has been over half an hour since the last update 157 | # if it has, update the prices again. 158 | if time.time() - last_updated > 1800: 159 | next_price, current_price, last_price = get_prices() 160 | last_updated = time.time() 161 | 162 | # Draw the drop shadows and the main text for the last, current and next prices. 163 | vector.set_font_size(28) 164 | display.set_pen(ORANGE_2) 165 | vector.text("last:", MARGIN, 50) 166 | vector.text(f"{last_price}p", MARGIN, 70) 167 | 168 | vector.set_font_size(52) 169 | 170 | display.set_pen(BLACK) 171 | vector.text("Now:", MARGIN + 2, 120 + 2) 172 | vector.set_font_size(58) 173 | vector.text(f"{current_price}p", MARGIN + 2, 160 + 2) 174 | 175 | display.set_pen(WHITE) 176 | vector.set_font_size(52) 177 | vector.text("Now:", MARGIN, 120) 178 | vector.set_font_size(58) 179 | vector.text(f"{current_price}p", MARGIN, 160) 180 | 181 | vector.set_font_size(28) 182 | display.set_pen(ORANGE_3) 183 | vector.text("Next:", MARGIN, 195) 184 | vector.text(f"{next_price}p", MARGIN, 215) 185 | 186 | # Finally we update the screen with our changes :) 187 | presto.update() 188 | -------------------------------------------------------------------------------- /examples/attitude_indicator.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-4.5, 16.82), (-9.92, 6.75), (-19.99, 1.33), (-16.1, -2.5), (-8.17, -1.13), (-2.58, -6.71), (-19.93, -14.1), (-15.33, -18.8), (5.73, -15.08), (12.52, -21.87), (13.6, -22.66), (15.25, -23.11), (16.46, -23.05), (17.73, -22.63), (19.14, -21.42), (19.62, -20.63), (19.97, -19.45), (19.99, -18.25), (19.79, -17.33), (19.32, -16.37), (18.79, -15.72), (11.92, -8.84), (15.64, 12.17), (10.99, 16.82), (3.54, -0.53), (-2.04, 5.05), (-0.61, 12.93), (-4.5, 16.82)]] 2 | # NAME Attitude Indicator 3 | # DESC A Demo for the Multi-Sensor Stick 4 | from presto import Presto 5 | from picovector import ANTIALIAS_FAST, PicoVector, Polygon, Transform 6 | import machine 7 | from lsm6ds3 import LSM6DS3, NORMAL_MODE_104HZ 8 | 9 | # Setup for the Presto display 10 | presto = Presto(ambient_light=True) 11 | display = presto.display 12 | WIDTH, HEIGHT = display.get_bounds() 13 | CX = WIDTH // 2 14 | CY = HEIGHT // 2 15 | 16 | # Colours 17 | GRAY = display.create_pen(42, 52, 57) 18 | BLACK = display.create_pen(0, 0, 0) 19 | SKY_COLOUR = display.create_pen(86, 159, 201) 20 | GROUND_COLOUR = display.create_pen(101, 81, 63) 21 | WHITE = display.create_pen(255, 255, 255) 22 | RED = display.create_pen(200, 0, 0) 23 | 24 | # Pico Vector 25 | vector = PicoVector(display) 26 | vector.set_antialiasing(ANTIALIAS_FAST) 27 | t = Transform() 28 | normal = Transform() 29 | vector.set_transform(t) 30 | 31 | x, y = 0, CY 32 | x_prev = x 33 | y_prev = y 34 | alpha = 0.15 35 | 36 | # Setup some of our vector shapes 37 | background_rect = Polygon() 38 | background_rect.rectangle(0, 0, WIDTH, HEIGHT) 39 | background_rect.circle(CX, CY, 109) 40 | 41 | instrument_outline = Polygon().circle(CX, CY, 110, stroke=8) 42 | 43 | ground = Polygon().rectangle(0, HEIGHT // 2, WIDTH, HEIGHT) 44 | horizon = Polygon().rectangle(0, HEIGHT // 2, WIDTH, 2) 45 | pitch_lines = Polygon() 46 | 47 | for line in range(1, 7): 48 | if line % 2: 49 | pitch_lines.rectangle(CX - 10, CY - line * 14, 20, 1.5) 50 | pitch_lines.rectangle(CX - 10, CY + line * 14, 20, 1.5) 51 | else: 52 | pitch_lines.rectangle(CX - 30, CY - line * 14, 60, 1.5) 53 | pitch_lines.rectangle(CX - 30, CY + line * 14, 60, 1.5) 54 | 55 | craft_centre = Polygon().circle(CX, CY - 1, 2) 56 | craft_left = Polygon().rectangle(CX - 70, CY - 1, 50, 2, (2, 2, 2, 2)) 57 | craft_right = Polygon().rectangle(CX + 20, CY - 1, 50, 2, (2, 2, 2, 2)) 58 | craft_arc = Polygon().arc(CX, CY, 22, -90, 90, stroke=2) 59 | 60 | 61 | def show_message(text): 62 | display.set_pen(GRAY) 63 | display.clear() 64 | display.set_pen(WHITE) 65 | display.text(f"{text}", 5, 10, WIDTH, 2) 66 | presto.update() 67 | 68 | 69 | try: 70 | i2c = machine.I2C() 71 | sensor = LSM6DS3(i2c, mode=NORMAL_MODE_104HZ) 72 | except OSError: 73 | while True: 74 | show_message("No Multi-Sensor stick detected!\n\nConnect and try again.") 75 | 76 | while True: 77 | # Clear screen with the SKY colour 78 | display.set_pen(SKY_COLOUR) 79 | display.clear() 80 | 81 | try: 82 | # Get the raw readings from the sensor 83 | ax, ay, az, gx, gy, gz = sensor.get_readings() 84 | except OSError: 85 | while True: 86 | show_message("Multi-Sensor stick disconnected!\n\nReconnect and reset your Presto.") 87 | 88 | # Apply some smoothing to the X and Y 89 | # and cap the Y with min/max 90 | y_axis = max(-11000, min(int(alpha * ay + (1 - alpha) * y_prev), 11000)) 91 | y_prev = y_axis 92 | 93 | x_axis = int(alpha * ax + (1 - alpha) * x_prev) 94 | x_prev = x_axis 95 | 96 | # Draw the ground 97 | t.reset() 98 | t.rotate(-x_axis / 180, (WIDTH // 2, HEIGHT // 2)) 99 | t.translate(0, y_axis / 100) 100 | 101 | vector.set_transform(t) 102 | display.set_pen(GROUND_COLOUR) 103 | vector.draw(ground) 104 | display.set_pen(WHITE) 105 | vector.draw(horizon) 106 | vector.draw(pitch_lines) 107 | vector.set_transform(normal) 108 | 109 | # Draw the aircraft 110 | display.set_pen(RED) 111 | vector.draw(craft_centre) 112 | vector.draw(craft_left) 113 | vector.draw(craft_right) 114 | vector.draw(craft_arc) 115 | 116 | display.set_pen(GRAY) 117 | vector.draw(background_rect) 118 | display.set_pen(BLACK) 119 | vector.draw(instrument_outline) 120 | 121 | # Update the screen so we can see our changes 122 | presto.update() 123 | -------------------------------------------------------------------------------- /examples/auto_backlight.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-7.43, 6.46), (-4.6, 6.46), (-3.19, 2.39), (3.27, 2.39), (4.69, 6.46), (7.43, 6.46), (1.42, -9.47), (-1.42, -9.47), (-7.43, 6.46)], [(-2.39, 0.09), (-0.09, -6.55), (0.09, -6.55), (2.39, 0.09), (-2.39, 0.09)], [(-0.0, 19.38), (-5.93, 13.54), (-14.16, 13.54), (-14.16, 5.31), (-20.0, -0.62), (-14.16, -6.55), (-14.16, -14.78), (-5.93, -14.78), (-0.0, -20.62), (5.93, -14.78), (14.16, -14.78), (14.16, -6.55), (20.0, -0.62), (14.16, 5.31), (14.16, 13.54), (5.93, 13.54), (-0.0, 19.38)], [(-0.0, 14.42), (4.42, 10.0), (10.62, 10.0), (10.62, 3.81), (15.04, -0.62), (10.62, -5.04), (10.62, -11.24), (4.42, -11.24), (-0.0, -15.66), (-4.42, -11.24), (-10.62, -11.24), (-10.62, -5.04), (-15.04, -0.62), (-10.62, 3.81), (-10.62, 10.0), (-4.42, 10.0), (-0.0, 14.42)]] 2 | # NAME Auto Backlight Demo 3 | # DESC Using the Multi-Sensor Stick 4 | from presto import Presto 5 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 6 | from machine import I2C 7 | from breakout_ltr559 import BreakoutLTR559 8 | 9 | # Setup for the Presto display 10 | presto = Presto(ambient_light=False) 11 | display = presto.display 12 | WIDTH, HEIGHT = display.get_bounds() 13 | 14 | ltr = BreakoutLTR559(I2C()) 15 | LUX_MAX = 200 16 | LUX_MIN = 0 17 | 18 | CX = WIDTH // 2 19 | CY = HEIGHT // 2 20 | 21 | # Colours 22 | BLACK = display.create_pen(0, 0, 0) 23 | hue = 0.8 24 | BACKGROUND = display.create_pen_hsv(hue, 0.8, 1.0) # We'll use this one for the background. 25 | FOREGROUND = display.create_pen_hsv(hue, 0.5, 1.0) # Slightly lighter for foreground elements. 26 | TEXT_COLOUR = display.create_pen_hsv(hue, 0.2, 1.0) 27 | 28 | # Pico Vector 29 | vector = PicoVector(display) 30 | vector.set_antialiasing(ANTIALIAS_BEST) 31 | t = Transform() 32 | vector.set_transform(t) 33 | 34 | # We'll use a rect with rounded corners for the background. 35 | background_rect = Polygon() 36 | background_rect.rectangle(0, 0, WIDTH, HEIGHT) 37 | background_rect.rectangle(0, 0, WIDTH, HEIGHT, (10, 10, 10, 10)) 38 | 39 | # Vector icon 40 | AUTO_BACKLIGHT_PATHS = [[(-7.43, 6.46), (-4.6, 6.46), (-3.19, 2.39), (3.27, 2.39), (4.69, 6.46), (7.43, 6.46), (1.42, -9.47), (-1.42, -9.47), (-7.43, 6.46)], [(-2.39, 0.09), (-0.09, -6.55), (0.09, -6.55), (2.39, 0.09), (-2.39, 0.09)], [(-0.0, 19.38), (-5.93, 13.54), (-14.16, 13.54), (-14.16, 5.31), (-20.0, -0.62), (-14.16, -6.55), (-14.16, -14.78), (-5.93, -14.78), (-0.0, -20.62), (5.93, -14.78), (14.16, -14.78), (14.16, -6.55), (20.0, -0.62), (14.16, 5.31), (14.16, 13.54), (5.93, 13.54), (-0.0, 19.38)], [(-0.0, 14.42), (4.42, 10.0), (10.62, 10.0), (10.62, 3.81), (15.04, -0.62), (10.62, -5.04), (10.62, -11.24), (4.42, -11.24), (-0.0, -15.66), (-4.42, -11.24), (-10.62, -11.24), (-10.62, -5.04), (-15.04, -0.62), (-10.62, 3.81), (-10.62, 10.0), (-4.42, 10.0), (-0.0, 14.42)]] 41 | auto_backlight_icon = Polygon() 42 | 43 | for path in AUTO_BACKLIGHT_PATHS: 44 | auto_backlight_icon.path(*path) 45 | 46 | # Store our last 5 lux readings. 47 | # We've put some low values in to start things off. 48 | lux_readings = [10, 10, 10, 10, 10] 49 | 50 | 51 | def show_message(text): 52 | display.set_pen(BACKGROUND) 53 | display.clear() 54 | display.set_pen(FOREGROUND) 55 | display.text(f"{text}", 5, 10, WIDTH, 2) 56 | presto.update() 57 | 58 | 59 | while True: 60 | 61 | # Clear the screen 62 | display.set_pen(BACKGROUND) 63 | display.clear() 64 | 65 | reading = ltr.get_reading() 66 | 67 | if reading is not None: 68 | # Lux reading, capped between 0 and 200. 69 | lux = max(min(round(reading[BreakoutLTR559.LUX]), LUX_MAX), LUX_MIN) 70 | lux_readings.append(lux) 71 | 72 | # We'll use the average from the last 5 readings to reduce flicker. 73 | if len(lux_readings) > 5: 74 | lux_readings.pop(0) 75 | 76 | lux_avg = round(sum(lux_readings) / len(lux_readings)) 77 | 78 | # Lux normalised with the lower bounds capped at 0.1 to keep the screen on. 79 | lux_norm = max((lux_avg - LUX_MIN) / (LUX_MAX - LUX_MIN), 0.1) 80 | 81 | # Set the backlight! 82 | presto.set_backlight(lux_norm) 83 | 84 | display.set_pen(FOREGROUND) 85 | display.text(f"Brightness Level: {round(lux_norm * 10)}", 10, CY + 100, WIDTH, 1) 86 | 87 | else: 88 | display.set_pen(FOREGROUND) 89 | display.text("Unable to get reading.\nCheck your multi-sensor stick and try again", 7, CY + 90, WIDTH, 1) 90 | 91 | # Draw the min/max brightness icons 92 | display.set_pen(FOREGROUND) 93 | t.translate(CX, CY) 94 | t.scale(3.0, 3.0) 95 | vector.draw(auto_backlight_icon) 96 | t.reset() 97 | 98 | # Draw the rounded corners 99 | display.set_pen(BLACK) 100 | vector.draw(background_rect) 101 | 102 | presto.update() 103 | -------------------------------------------------------------------------------- /examples/awesome_game.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-6.09, 20.0), (-7.14, 19.96), (-8.63, 19.78), (-10.29, 19.37), (-11.02, 19.11), (-12.48, 18.43), (-14.22, 17.27), (-15.52, 16.12), (-16.4, 15.14), (-17.1, 14.22), (-18.16, 12.38), (-18.65, 11.2), (-19.11, 9.61), (-19.36, 7.97), (-19.42, 6.62), (-19.38, 5.51), (-19.18, 3.92), (-18.88, 2.66), (-18.41, 1.33), (-17.67, -0.14), (-16.69, -1.58), (-15.84, -2.55), (-14.4, -3.87), (-13.45, -4.56), (-12.16, -5.31), (-10.14, -6.11), (-9.01, -6.39), (-7.41, -6.62), (-5.64, -6.67), (-4.29, -8.95), (-3.72, -9.57), (-3.13, -9.89), (-1.99, -10.1), (-0.79, -9.76), (0.58, -8.98), (1.09, -9.81), (2.19, -10.97), (3.05, -11.48), (4.52, -11.93), (5.79, -12.0), (6.89, -11.81), (7.63, -11.53), (9.64, -10.4), (7.87, -7.33), (6.31, -8.22), (5.76, -8.42), (4.89, -8.34), (4.09, -7.86), (3.64, -7.2), (5.42, -6.18), (6.24, -5.44), (6.6, -4.67), (6.71, -3.51), (6.42, -2.62), (5.2, -0.44), (5.84, 0.67), (6.61, 2.64), (6.97, 4.01), (7.21, 5.68), (7.24, 6.93), (7.07, 8.92), (6.58, 10.95), (6.05, 12.29), (5.46, 13.42), (4.79, 14.43), (3.52, 15.94), (1.73, 17.51), (0.37, 18.39), (-1.78, 19.33), (-3.76, 19.82), (-6.04, 20.0)], [(-6.09, 16.44), (-4.82, 16.37), (-3.91, 16.22), (-2.71, 15.87), (-1.8, 15.49), (-0.58, 14.77), (0.12, 14.22), (1.31, 13.05), (1.98, 12.2), (2.87, 10.64), (3.33, 9.36), (3.66, 7.41), (3.63, 5.64), (3.46, 4.6), (3.06, 3.23), (2.2, 1.46), (1.02, -0.4), (2.89, -3.6), (-1.73, -6.27), (-3.6, -3.07), (-5.56, -3.07), (-6.96, -2.99), (-8.83, -2.61), (-10.73, -1.84), (-12.3, -0.84), (-13.31, 0.04), (-14.04, 0.85), (-14.84, 2.07), (-15.34, 3.18), (-15.77, 4.78), (-15.9, 6.13), (-15.8, 8.14), (-15.36, 9.93), (-14.76, 11.29), (-14.07, 12.38), (-13.1, 13.52), (-12.35, 14.21), (-10.96, 15.18), (-9.77, 15.76), (-8.63, 16.13), (-7.05, 16.4), (-6.13, 16.44)], [(14.09, -4.89), (14.09, -8.44), (19.42, -8.44), (19.42, -4.89), (14.09, -4.89)], [(4.31, -14.67), (4.31, -20.0), (7.87, -20.0), (7.87, -14.67), (4.31, -14.67)], [(12.98, -11.07), (10.49, -13.56), (14.27, -17.33), (16.76, -14.84), (12.98, -11.07)]] 2 | # NAME Awesome Game 3 | # DESC tufty2040 port of awesome_game.py 4 | """ 5 | This is a port of awesome_game.py from the tufty2040 examples 6 | https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/examples/tufty2040/awesome_game.py 7 | """ 8 | 9 | import math 10 | from time import sleep 11 | from presto import Presto 12 | import random 13 | import time 14 | 15 | # Setup for the Presto display 16 | presto = Presto() 17 | display = presto.display 18 | WIDTH, HEIGHT = display.get_bounds() 19 | 20 | # We'll need this for the touch element of the screen 21 | touch = presto.touch 22 | 23 | # Load the spreadsheets so we can flip between them 24 | tilemap = bytearray(32_768) 25 | open("s4m_ur4i-pirate-tilemap.16bpp", "rb").readinto(tilemap) 26 | 27 | character = bytearray(32_768) 28 | open("s4m_ur4i-pirate-characters.16bpp", "rb").readinto(character) 29 | 30 | display.set_spritesheet(character) 31 | 32 | class Player(): 33 | def __init__(self): 34 | self.reset() 35 | 36 | def reset(self): 37 | self.x = 150 38 | self.y = 180 39 | self.w = 15 40 | self.h = 30 41 | self.speed = 5 42 | self.is_alive = True 43 | self.lives = 3 44 | self.score = 0 45 | self.moving = 0 46 | 47 | def move(self, x, y): 48 | if self.x + x > 0 - self.w and self.x + x < WIDTH - self.w: 49 | self.x += x 50 | self.y += y 51 | 52 | def sprite(self): 53 | display.set_spritesheet(character) 54 | display.sprite(1, 1 if self.moving else 0, self.x, self.y, 4, 0) 55 | 56 | 57 | class Treasure(): 58 | def __init__(self): 59 | self.w = 16 60 | self.h = 16 61 | self.randomize() 62 | 63 | def sprite(self): 64 | if not self.enabled: 65 | return 66 | display.set_spritesheet(tilemap) 67 | display.sprite(4, 2, self.x, self.y, 3, 0) 68 | 69 | def randomize(self): 70 | self.enabled = True 71 | self.x = random.randint(15, WIDTH - 60) 72 | self.y = HEIGHT - 50 73 | 74 | 75 | class Block(): 76 | def __init__(self): 77 | self.w = 16 78 | self.h = 16 79 | self.is_alive = True 80 | self.randomize() 81 | 82 | def move(self): 83 | self.y += self.speed 84 | 85 | def sprite(self): 86 | display.set_spritesheet(character) 87 | display.sprite(10, 8, self.x, self.y, 4, 0) 88 | 89 | def randomize(self): 90 | self.last_update = time.time() 91 | self.x = random.randint(10, WIDTH - self.w - 10) 92 | self.y = -self.h 93 | # was originally 4-12, but 12 felt too fast 94 | # self.speed = random.randint(4, 12) 95 | self.speed = random.randint(2, 4) 96 | 97 | 98 | 99 | class Game(): 100 | def __init__(self, FPS_COUNTER=False): 101 | self.player = Player() 102 | self.block = [] 103 | self.last_new_block = 0 104 | 105 | self.treasure = Treasure() 106 | self.last_treasure = 0 107 | 108 | self.SKY = display.create_pen(72, 180, 224) 109 | #FPS setup 110 | self.show_fps = FPS_COUNTER 111 | self.fps_start_time = time.time() 112 | self.fps_counter = 0 113 | self.current_fps = 0 114 | 115 | for _i in range(5): 116 | self.block.append(Block()) 117 | 118 | def reset(self): 119 | for i in range(7): 120 | presto.set_led_rgb(i, 0, 0, 0) 121 | for block in self.block: 122 | block.randomize() 123 | 124 | self.treasure.randomize() 125 | self.player.reset() 126 | if self.show_fps: 127 | self.current_fps = 0 128 | self.fps_counter = 0 129 | self.fps_start_time = time.time() 130 | 131 | def get_input(self): 132 | x = touch.x 133 | if x > 120: 134 | self.player.move(self.player.speed, 0) 135 | self.player.moving = 0 136 | if x < 120: 137 | self.player.move(-self.player.speed, 0) 138 | self.player.moving = 1 139 | 140 | def background(self): 141 | display.set_spritesheet(tilemap) 142 | display.set_pen(self.SKY) 143 | display.clear() 144 | 145 | for i in range(WIDTH / 32): 146 | display.sprite(1, 2, i * 32, 210, 4, 0) 147 | 148 | def draw(self): 149 | self.background() 150 | for block in self.block: 151 | block.sprite() 152 | display.set_pen(0xFFFF) 153 | display.text("Score: " + str(self.player.score), 10, 10, 320, 2) 154 | self.treasure.sprite() 155 | display.set_pen(0) 156 | self.player.sprite() 157 | if self.show_fps: 158 | #1 for every second 159 | self.fps_counter += 1 160 | if (time.time() - self.fps_start_time) > 1: 161 | self.current_fps = self.fps_counter / (time.time() - self.fps_start_time) 162 | self.fps_counter = 0 163 | self.fps_start_time = time.time() 164 | display.set_pen(0xFFFF) 165 | display.text("FPS: " + str(math.floor(self.current_fps)), 175, 10, 320, 2) 166 | display.update() 167 | 168 | def check_collision(self, a, b): 169 | return a.x + a.w >= b.x and a.x <= b.x + b.w and a.y + a.h >= b.y and a.y <= b.y + b.h 170 | 171 | def update(self): 172 | for block in self.block: 173 | block.move() 174 | if block.y > HEIGHT: 175 | block.randomize() 176 | 177 | if block.y + block.h >= self.player.y and self.check_collision(self.player, block): 178 | block.randomize() 179 | self.player.is_alive = False 180 | 181 | if self.treasure.enabled: 182 | if self.check_collision(self.player, self.treasure): 183 | self.player.score += 1 184 | if 0 < self.player.score < 8: 185 | for i in range(self.player.score): 186 | # turn a light gold 187 | presto.set_led_rgb(i, 255, 215, 0) 188 | self.treasure.enabled = False 189 | self.last_treasure = time.time() 190 | 191 | if time.time() - self.last_treasure > 2: 192 | if not self.treasure.enabled: 193 | self.treasure.randomize() 194 | 195 | if self.player.lives == 0: 196 | self.player.is_alive = False 197 | 198 | 199 | 200 | game = Game(FPS_COUNTER=True) 201 | while True: 202 | 203 | touch.poll() 204 | game.background() 205 | display.set_pen(0xFFFF) 206 | display.text("ARGH!", 40, 35, 200, 5) 207 | display.text("Touch screen to Start", 80, 150, 180, 1) 208 | display.update() 209 | 210 | while not touch.state: 211 | presto.update() 212 | 213 | while game.player.is_alive: 214 | if touch.state: 215 | game.get_input() 216 | game.update() 217 | game.draw() 218 | presto.update() 219 | 220 | game.background() 221 | display.set_pen(0xFFFF) 222 | display.text("OOPS!", 40, 35, 200, 5) 223 | display.text("Your score: " + str(game.player.score), 50, 150, 180, 2) 224 | display.update() 225 | presto.update() 226 | #Added a because if you are still touching the screen may breeze pass the scoring screen 227 | sleep(.5) 228 | # See if the display is still being touched after the sleep 229 | touch.poll() 230 | while not touch.state: 231 | presto.update() 232 | 233 | game.reset() 234 | presto.update() 235 | -------------------------------------------------------------------------------- /examples/backlight_ball.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Watch the backlighting react to a ball moving on screen 3 | ''' 4 | 5 | import math 6 | import time 7 | 8 | from presto import Presto 9 | 10 | presto = Presto(ambient_light=True) 11 | display = presto.display 12 | WIDTH, HEIGHT = display.get_bounds() 13 | 14 | # Couple of colours for use later 15 | BLUE = display.create_pen(28, 181, 202) 16 | WHITE = display.create_pen(255, 255, 255) 17 | RED = display.create_pen(230, 60, 45) 18 | ORANGE = display.create_pen(245, 165, 4) 19 | GREEN = display.create_pen(9, 185, 120) 20 | PINK = display.create_pen(250, 125, 180) 21 | PURPLE = display.create_pen(118, 95, 210) 22 | BLACK = display.create_pen(0, 0, 0) 23 | 24 | # Set our initial pen colour 25 | pen = display.create_pen_hsv(1.0, 1.0, 1.0) 26 | 27 | while True: 28 | 29 | display.set_pen(BLACK) 30 | display.clear() 31 | 32 | # We'll use this for cycling through the rainbow 33 | t = time.ticks_ms() / 5000 34 | 35 | degrees = (t * 360) / 5 36 | rad = math.radians(degrees) 37 | 38 | display.reset_pen(pen) 39 | pen = display.create_pen_hsv(t, 1.0, 1.0) 40 | display.set_pen(pen) 41 | 42 | display.circle(WIDTH // 2 + int(math.cos(rad) * 100), HEIGHT // 2 + int(math.sin(rad) * 100), 80) 43 | 44 | # Finally we update the screen with our changes :) 45 | presto.update() 46 | -------------------------------------------------------------------------------- /examples/backlight_images.py: -------------------------------------------------------------------------------- 1 | ''' 2 | A demo that flips between 2 images and changes the backlighting 3 | ''' 4 | 5 | import time 6 | 7 | import jpegdec 8 | from presto import Presto 9 | 10 | # File names for your 2 images. The reactive backlighting works best with images that match the resolution of the screen 11 | # In this example we're running at 240 x 240 12 | 13 | IMAGE_1 = "image1.jpg" 14 | IMAGE_2 = "image2.jpg" 15 | 16 | # Setup for the Presto display 17 | presto = Presto(ambient_light=True) 18 | display = presto.display 19 | WIDTH, HEIGHT = display.get_bounds() 20 | 21 | # Setup JPEG decoder 22 | j = jpegdec.JPEG(display) 23 | 24 | flip = True 25 | 26 | while True: 27 | 28 | # Select opposite image to what's currently shown 29 | if flip: 30 | j.open_file(IMAGE_1) 31 | 32 | else: 33 | j.open_file(IMAGE_2) 34 | 35 | j.decode(0, 0, jpegdec.JPEG_SCALE_FULL, dither=True) 36 | flip = not flip 37 | 38 | # Finally we update the screen with our changes :) 39 | presto.update() 40 | time.sleep(1) 41 | -------------------------------------------------------------------------------- /examples/backlight_slider.py: -------------------------------------------------------------------------------- 1 | # ICON [[(0.0, 7.0), (3.0, 4.0), (8.0, 4.0), (8.0, -1.0), (11.0, -4.0), (8.0, -7.0), (8.0, -12.0), (3.0, -12.0), (0.0, -15.0), (-3.0, -12.0), (-8.0, -12.0), (-8.0, -7.0), (-11.0, -4.0), (-8.0, -1.0), (-8.0, 4.0), (-3.0, 4.0), (0.0, 7.0)], [(0.0, 2.0), (0.0, -10.0), (1.9, -9.71), (3.4, -8.97), (4.8, -7.62), (5.59, -6.24), (5.85, -5.39), (6.0, -4.1), (5.96, -3.3), (5.56, -1.7), (5.04, -0.71), (4.07, 0.42), (2.74, 1.36), (1.84, 1.73), (0.05, 2.0)], [(-16.0, 12.0), (-16.87, 11.91), (-18.0, 11.49), (-18.81, 10.88), (-19.31, 10.27), (-19.82, 9.22), (-20.0, 8.05), (-20.0, -16.0), (-19.91, -16.88), (-19.52, -17.92), (-19.12, -18.51), (-18.19, -19.34), (-16.72, -19.94), (-16.05, -20.0), (16.0, -20.0), (17.35, -19.77), (18.4, -19.17), (19.01, -18.58), (19.8, -17.25), (20.0, -16.05), (20.0, 8.0), (19.89, 8.96), (19.58, 9.79), (18.84, 10.81), (17.99, 11.47), (17.05, 11.87), (16.05, 12.0), (-16.0, 12.0)], [(-16.0, 8.0), (16.0, 8.0), (16.0, -16.0), (-16.0, -16.0), (-16.0, 8.0)]] 2 | # NAME Backlight Control Demo 3 | # DESC Let there be light. 4 | from presto import Presto 5 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 6 | 7 | # Setup for the Presto display 8 | presto = Presto(ambient_light=False) 9 | display = presto.display 10 | WIDTH, HEIGHT = display.get_bounds() 11 | 12 | CX = WIDTH // 2 13 | CY = HEIGHT // 2 14 | 15 | # Colours 16 | BLACK = display.create_pen(0, 0, 0) 17 | hue = 0.6 18 | BACKGROUND = display.create_pen_hsv(hue, 0.8, 1.0) # We'll use this one for the background. 19 | FOREGROUND = display.create_pen_hsv(hue, 0.5, 1.0) # Slightly lighter for foreground elements. 20 | TEXT_COLOUR = display.create_pen_hsv(hue, 0.2, 1.0) 21 | 22 | # Pico Vector 23 | vector = PicoVector(display) 24 | vector.set_antialiasing(ANTIALIAS_BEST) 25 | t = Transform() 26 | vector.set_transform(t) 27 | 28 | # We'll use a rect with rounded corners for the background. 29 | background_rect = Polygon() 30 | background_rect.rectangle(0, 0, WIDTH, HEIGHT) 31 | background_rect.rectangle(0, 0, WIDTH, HEIGHT, (10, 10, 10, 10)) 32 | 33 | # Constants for our slider 34 | BAR_W = 50 35 | BAR_H = 170 36 | BAR_X = CX - BAR_W // 2 37 | BAR_Y = CY - BAR_H // 2 38 | SLIDER_HEIGHT = BAR_H // 4 39 | 40 | # Vector icons 41 | BACKLIGHT_MAX_PATHS = [[(-0.0, 9.69), (-2.96, 6.77), (-7.08, 6.77), (-7.08, 2.65), (-10.0, -0.31), (-7.08, -3.27), (-7.08, -7.39), (-2.96, -7.39), (-0.0, -10.31), (2.96, -7.39), (7.08, -7.39), (7.08, -3.27), (10.0, -0.31), (7.08, 2.65), (7.08, 6.77), (2.96, 6.77), (-0.0, 9.69)], [(-0.0, 4.12), (0.94, 4.02), (1.52, 3.86), (1.99, 3.66), (2.67, 3.23), (3.1, 2.85), (3.67, 2.17), (4.07, 1.44), (4.24, 0.98), (4.39, 0.27), (4.42, -0.29), (4.33, -1.25), (4.16, -1.85), (3.84, -2.52), (3.48, -3.03), (3.18, -3.37), (2.7, -3.81), (2.36, -4.06), (1.78, -4.37), (1.2, -4.58), (0.65, -4.69), (-0.2, -4.73), (-0.79, -4.67), (-1.24, -4.57), (-1.93, -4.3), (-2.4, -4.04), (-2.89, -3.67), (-3.57, -2.94), (-3.9, -2.44), (-4.22, -1.68), (-4.39, -0.87), (-4.4, 0.15), (-4.32, 0.7), (-4.17, 1.22), (-3.81, 1.98), (-3.45, 2.49), (-3.16, 2.82), (-2.73, 3.19), (-2.31, 3.48), (-1.82, 3.74), (-1.18, 3.97), (-0.79, 4.05), (-0.02, 4.11)], [(-0.0, 7.21), (2.21, 5.0), (5.31, 5.0), (5.31, 1.9), (7.52, -0.31), (5.31, -2.52), (5.31, -5.62), (2.21, -5.62), (-0.0, -7.83), (-2.21, -5.62), (-5.31, -5.62), (-5.31, -2.52), (-7.52, -0.31), (-5.31, 1.9), (-5.31, 5.0), (-2.21, 5.0), (-0.0, 7.21)]] 42 | BACKLIGHT_MIN_PATHS = [[(-0.0, 9.69), (-2.96, 6.77), (-7.08, 6.77), (-7.08, 2.65), (-10.0, -0.31), (-7.08, -3.27), (-7.08, -7.39), (-2.96, -7.39), (-0.0, -10.31), (2.96, -7.39), (7.08, -7.39), (7.08, -3.27), (10.0, -0.31), (7.08, 2.65), (7.08, 6.77), (2.96, 6.77), (-0.0, 9.69)], [(-0.0, 7.21), (2.21, 5.0), (5.31, 5.0), (5.31, 1.9), (7.52, -0.31), (5.31, -2.52), (5.31, -5.62), (2.21, -5.62), (-0.0, -7.83), (-2.21, -5.62), (-5.31, -5.62), (-5.31, -2.52), (-7.52, -0.31), (-5.31, 1.9), (-5.31, 5.0), (-2.21, 5.0), (-0.0, 7.21)]] 43 | backlight_max_icon = Polygon() 44 | backlight_min_icon = Polygon() 45 | 46 | for path in BACKLIGHT_MAX_PATHS: 47 | backlight_max_icon.path(*path) 48 | 49 | for path in BACKLIGHT_MIN_PATHS: 50 | backlight_min_icon.path(*path) 51 | 52 | 53 | class Slider(object): 54 | def __init__(self, x, y, w, h, bar_colour, slider_colour): 55 | self.x = x 56 | self.y = y 57 | self.w = w 58 | self.h = h 59 | self.slider_h = self.h // 4 60 | self.value = 1.0 61 | self.slider_vector = Polygon().rectangle(self.x, self.y, self.w, self.slider_h, (10, 10, 10, 10)) 62 | self.bar_vector = Polygon().rectangle(self.x, self.y, self.w, self.h, (10, 10, 10, 10)) 63 | self.slider_colour = slider_colour 64 | self.bar_colour = bar_colour 65 | self.touch = presto.touch 66 | 67 | def update(self): 68 | self.touch.poll() 69 | 70 | if self.touch.state and self.touch.x >= self.x and self.touch.x <= self.x + self.w and self.touch.y >= self.y - self.slider_h and self.touch.y <= (self.y + self.h): 71 | 72 | new_pos_y = max(min(self.touch.y, (self.y + self.h) - self.slider_h), self.y) 73 | self.slider_vector = Polygon().rectangle(self.x, new_pos_y, self.w, self.slider_h, (10, 10, 10, 10)) 74 | self.value = 1.0 - (new_pos_y - self.y) / ((self.y + self.h - self.slider_h) - self.y) 75 | 76 | def draw(self): 77 | display.set_pen(self.bar_colour) 78 | vector.draw(self.bar_vector) 79 | display.set_pen(self.slider_colour) 80 | vector.draw(self.slider_vector) 81 | 82 | def get_value(self): 83 | return self.value 84 | 85 | 86 | # Create our slider object 87 | s = Slider(BAR_X, BAR_Y, BAR_W, BAR_H, FOREGROUND, TEXT_COLOUR) 88 | 89 | while True: 90 | 91 | s.update() 92 | 93 | brightness = max(s.get_value(), 0.1) 94 | presto.set_backlight(brightness) 95 | 96 | # Clear the screen 97 | display.set_pen(BACKGROUND) 98 | display.clear() 99 | 100 | # Draw the slider 101 | s.draw() 102 | 103 | # Draw the min/max brightness icons 104 | display.set_pen(TEXT_COLOUR) 105 | t.translate(CX, BAR_Y + 20) 106 | vector.draw(backlight_max_icon) 107 | t.reset() 108 | t.translate(CX, BAR_Y + BAR_H - 20) 109 | vector.draw(backlight_min_icon) 110 | t.reset() 111 | 112 | # Draw thhe rounded corners 113 | display.set_pen(BLACK) 114 | vector.draw(background_rect) 115 | 116 | presto.update() 117 | -------------------------------------------------------------------------------- /examples/badapple.py: -------------------------------------------------------------------------------- 1 | from picographics import PicoGraphics, DISPLAY_PRESTO 2 | from presto import Presto 3 | from time import ticks_us 4 | 5 | import machine 6 | 7 | machine.freq(264000000) 8 | 9 | import sdcard 10 | import machine 11 | import uos 12 | 13 | try: 14 | # Setup for SD Card 15 | sd_spi = machine.SPI(0, baudrate=66_000_000, sck=machine.Pin(34, machine.Pin.OUT), mosi=machine.Pin(35, machine.Pin.OUT), miso=machine.Pin(36, machine.Pin.OUT)) 16 | sd = sdcard.SDCard(sd_spi, machine.Pin(39)) 17 | 18 | # Mount the SD to the directory 'sd' 19 | uos.mount(sd, "/sd") 20 | 21 | except OSError as e: 22 | print(e) 23 | 24 | # Setup for the Presto display 25 | presto = Presto() 26 | display = PicoGraphics(DISPLAY_PRESTO, buffer=memoryview(presto)) 27 | WIDTH, HEIGHT = display.get_bounds() 28 | 29 | # Read the bad apple video file from the SD card 30 | video = open(f"/sd/badapple{WIDTH}x{HEIGHT}.bin", "rb") 31 | 32 | y = 0 33 | x = 0 34 | tick_increment = 1000000 // 30 35 | next_tick = ticks_us() 36 | 37 | # This Micropython Viper function is compiled to native code 38 | # for maximum execution speed. 39 | @micropython.viper 40 | def render(data:ptr8, x:int, y:int, next_tick:int): 41 | for i in range(0, 1024, 2): 42 | # The encoded video data is an array of span lengths and 43 | # greyscale colour values 44 | span_len = int(data[i]) 45 | colour = int(data[i+1]) 46 | 47 | # Expand the grey colour to each colour channel 48 | colour = (colour << 11) | (colour << 6) | colour 49 | 50 | # Byte swap for the display 51 | colour = (colour & 0xFF) << 8 | (colour >> 8) 52 | 53 | display.set_pen(colour) 54 | display.pixel_span(x, y, span_len) 55 | 56 | x += span_len 57 | if x >= 240: 58 | y += 1 59 | x = 0 60 | if y >= 240: 61 | presto.update(display) 62 | 63 | # Wait until the next frame at 15FPS 64 | next_tick += 1000000 // 15 65 | while int(ticks_us()) < next_tick: 66 | pass 67 | y = 0 68 | 69 | return x, y, next_tick 70 | 71 | # Read out the file and render 72 | while True: 73 | data = video.read(1024) 74 | if len(data) < 1024: break 75 | x, y, next_tick = render(data, x, y, next_tick) 76 | -------------------------------------------------------------------------------- /examples/balls_demo.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from presto import Presto 4 | 5 | # Setup for the Presto display 6 | presto = Presto() 7 | display = presto.display 8 | WIDTH, HEIGHT = display.get_bounds() 9 | 10 | # We're creating 170 balls with their own individual colour and 1 BG colour 11 | # for a total of 171 colours, which will all fit in the custom 256 entry palette! 12 | 13 | 14 | class Ball: 15 | def __init__(self, x, y, r, dx, dy, pen): 16 | self.x = x 17 | self.y = y 18 | self.r = r 19 | self.dx = dx 20 | self.dy = dy 21 | self.pen = pen 22 | 23 | 24 | # initialise shapes 25 | balls = [] 26 | for i in range(0, 170): 27 | r = random.randint(0, 10) + 3 28 | balls.append( 29 | Ball( 30 | random.randint(r, r + (WIDTH - 2 * r)), 31 | random.randint(r, r + (HEIGHT - 2 * r)), 32 | r, 33 | (14 - r) / 2, 34 | (14 - r) / 2, 35 | display.create_pen(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), 36 | ) 37 | ) 38 | 39 | BG = display.create_pen(40, 40, 40) 40 | 41 | while True: 42 | 43 | display.set_layer(0) 44 | display.set_pen(BG) 45 | display.clear() 46 | 47 | for ball in balls: 48 | ball.x += ball.dx 49 | ball.y += ball.dy 50 | 51 | xmax = WIDTH - ball.r 52 | xmin = ball.r 53 | ymax = HEIGHT - ball.r 54 | ymin = ball.r 55 | 56 | if ball.x < xmin or ball.x > xmax: 57 | ball.dx *= -1 58 | 59 | if ball.y < ymin or ball.y > ymax: 60 | ball.dy *= -1 61 | 62 | display.set_pen(ball.pen) 63 | display.circle(int(ball.x), int(ball.y), int(ball.r)) 64 | 65 | # Finally we update the screen with our changes :) 66 | presto.update() 67 | -------------------------------------------------------------------------------- /examples/buzzer.py: -------------------------------------------------------------------------------- 1 | from presto import Presto, Buzzer 2 | from touch import Button 3 | from picovector import ANTIALIAS_FAST, PicoVector, Polygon, Transform 4 | 5 | presto = Presto() 6 | display = presto.display 7 | WIDTH, HEIGHT = display.get_bounds() 8 | 9 | # CONSTANTS 10 | CX = WIDTH // 2 11 | CY = HEIGHT // 2 12 | 13 | BUTTON_WIDTH = 110 14 | BUTTON_HEIGHT = 110 15 | 16 | # Pico Vector 17 | vector = PicoVector(display) 18 | vector.set_antialiasing(ANTIALIAS_FAST) 19 | t = Transform() 20 | 21 | vector.set_font("Roboto-Medium.af", 54) 22 | vector.set_font_letter_spacing(100) 23 | vector.set_font_word_spacing(100) 24 | vector.set_transform(t) 25 | 26 | # Couple of colours for use later 27 | WHITE = display.create_pen(255, 255, 255) 28 | RED = display.create_pen(230, 60, 45) 29 | BLACK = display.create_pen(0, 0, 0) 30 | 31 | # We'll need this for the touch element of the screen 32 | touch = presto.touch 33 | 34 | # Define our button vectors and touch button 35 | button = Button(CX - (BUTTON_WIDTH // 2), CY - (BUTTON_HEIGHT // 2), BUTTON_WIDTH, BUTTON_HEIGHT) 36 | button_vector = Polygon() 37 | button_vector.circle(CX, CY, 50) 38 | button_outline = Polygon() 39 | button_outline.circle(CX, CY, 54, 5) 40 | 41 | # Calculate our text positions now rather than in the main loop 42 | vector.set_font_size(28) 43 | x, y, w, h = vector.measure_text("TOUCH", x=0, y=0, angle=None) 44 | text_x = int(CX - (w // 2)) 45 | text_y = int(CY + (h // 2)) 46 | text_x_offset = text_x + 2 47 | text_y_offset = text_y + 2 48 | 49 | # Setup the buzzer. The Presto piezo is on pin 43. 50 | buzzer = Buzzer(43) 51 | 52 | while True: 53 | 54 | # Check for touch changes 55 | touch.poll() 56 | 57 | # Clear the screen and set the background colour 58 | display.set_pen(WHITE) 59 | display.clear() 60 | 61 | # Draw the touch button outline and inner section 62 | display.set_pen(BLACK) 63 | vector.draw(button_outline) 64 | display.set_pen(RED) 65 | vector.draw(button_vector) 66 | 67 | # Draw vector text with drop shadow 68 | display.set_pen(BLACK) 69 | vector.text("TOUCH", text_x_offset, text_y_offset) 70 | display.set_pen(WHITE) 71 | vector.text("TOUCH", text_x, text_y) 72 | 73 | # If we're pressing the onscreen button, play a tone! 74 | # otherwise play nothing :) 75 | if button.is_pressed(): 76 | buzzer.set_tone(150) 77 | else: 78 | buzzer.set_tone(-1) 79 | 80 | # Finally, we update the screen so we can see our changes! 81 | presto.update() 82 | -------------------------------------------------------------------------------- /examples/cheerlights_bulb.py: -------------------------------------------------------------------------------- 1 | # ICON [[(0.0, 24.0), (-1.93, 23.66), (-3.75, 22.51), (-4.42, 21.69), (-5.11, 20.23), (-5.33, 18.73), (5.33, 18.67), (4.91, 20.77), (4.06, 22.12), (3.34, 22.82), (2.1, 23.58), (0.07, 24.0)], [(-10.67, 16.0), (-10.67, 10.67), (10.67, 10.67), (10.67, 16.0), (-10.67, 16.0)], [(-10.0, 8.0), (-12.44, 6.31), (-15.06, 3.8), (-16.01, 2.64), (-17.3, 0.72), (-18.55, -1.82), (-19.23, -3.78), (-19.81, -6.5), (-20.0, -9.27), (-19.89, -11.51), (-19.37, -14.52), (-18.51, -17.1), (-17.78, -18.66), (-16.29, -21.03), (-14.06, -23.61), (-12.39, -25.08), (-10.53, -26.4), (-8.18, -27.66), (-6.51, -28.31), (-4.11, -28.95), (-2.22, -29.22), (0.14, -29.33), (1.92, -29.25), (4.76, -28.8), (6.5, -28.31), (8.89, -27.32), (10.67, -26.3), (12.4, -25.04), (13.76, -23.84), (15.55, -21.92), (17.31, -19.45), (18.36, -17.41), (18.97, -15.86), (19.6, -13.52), (19.94, -11.05), (20.0, -9.2), (19.79, -6.43), (19.48, -4.78), (18.72, -2.32), (17.93, -0.54), (16.56, 1.79), (15.4, 3.37), (13.36, 5.52), (11.65, 6.91), (10.06, 7.97), (-10.0, 8.0)], [(-8.4, 2.67), (8.4, 2.67), (10.02, 1.35), (10.94, 0.41), (12.14, -1.14), (13.64, -3.98), (14.23, -5.77), (14.51, -7.17), (14.67, -9.27), (14.51, -11.57), (14.19, -13.19), (13.44, -15.35), (12.77, -16.67), (11.72, -18.23), (10.81, -19.31), (9.38, -20.66), (7.64, -21.92), (6.27, -22.66), (4.72, -23.28), (2.72, -23.77), (0.53, -23.99), (-1.72, -23.91), (-3.36, -23.64), (-5.45, -23.01), (-7.77, -21.85), (-9.2, -20.82), (-10.81, -19.3), (-11.81, -18.11), (-12.99, -16.27), (-13.5, -15.21), (-14.28, -12.86), (-14.62, -10.59), (-14.65, -8.58), (-14.21, -5.65), (-13.82, -4.34), (-13.1, -2.66), (-11.6, -0.36), (-10.56, 0.82), (-8.45, 2.63)], [(0.0, 2.67), (0.0, 2.67)]] 2 | # NAME Bulb 3 | # DESC A cheerlight connected desk light. 4 | 5 | import time 6 | 7 | import plasma 8 | import requests 9 | from machine import Pin 10 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 11 | from presto import Presto 12 | 13 | user_button = Pin(46, Pin.IN) 14 | 15 | dark_mode = False 16 | 17 | BULB_OUTLINE = [(130.44, 0.0), 18 | (150.36, 1.51), 19 | (165.02, 4.64), 20 | (183.83, 11.41), 21 | (201.36, 20.97), 22 | (213.45, 29.85), 23 | (231.09, 47.51), 24 | (239.96, 59.6), 25 | (249.51, 77.14), 26 | (256.26, 95.95), 27 | (260.51, 120.55), 28 | (260.54, 140.53), 29 | (257.63, 160.3), 30 | (249.3, 183.81), 31 | (239.13, 201.01), 32 | (226.92, 216.84), 33 | (210.84, 235.98), 34 | (199.26, 252.27), 35 | (194.56, 261.09), 36 | (189.52, 275.19), 37 | (186.53, 299.95), 38 | (184.32, 304.38), 39 | (175.48, 308.37), 40 | (85.48, 308.37), 41 | (76.62, 304.46), 42 | (74.37, 300.04), 43 | (71.38, 275.29), 44 | (66.36, 261.17), 45 | (61.67, 252.35), 46 | (50.1, 236.05), 47 | (34.02, 216.92), 48 | (21.8, 201.09), 49 | (11.63, 183.89), 50 | (3.27, 160.39), 51 | (0.35, 140.63), 52 | (0.36, 120.64), 53 | (3.37, 100.89), 54 | (11.33, 77.23), 55 | (23.67, 55.53), 56 | (32.98, 43.78), 57 | (51.27, 26.8), 58 | (68.01, 15.9), 59 | (90.97, 6.09), 60 | (110.42, 1.53), 61 | (130.35, 0.0)] 62 | 63 | BULB_INNER = [(130.44, 6.81), 64 | (150.35, 8.47), 65 | (164.95, 11.87), 66 | (183.54, 19.17), 67 | (200.7, 29.4), 68 | (216.02, 42.22), 69 | (226.08, 53.33), 70 | (237.34, 69.85), 71 | (245.83, 87.92), 72 | (251.31, 107.14), 73 | (253.57, 126.99), 74 | (252.61, 146.94), 75 | (248.29, 166.44), 76 | (244.81, 175.81), 77 | (235.5, 193.48), 78 | (223.9, 209.77), 79 | (208.12, 229.16), 80 | (196.2, 245.21), 81 | (186.84, 262.85), 82 | (183.75, 272.35), 83 | (180.29, 297.0), 84 | (172.24, 302.02), 85 | (92.24, 302.05), 86 | (82.74, 299.85), 87 | (80.07, 295.69), 88 | (76.77, 270.99), 89 | (69.14, 252.56), 90 | (58.07, 235.92), 91 | (36.12, 208.67), 92 | (24.64, 192.3), 93 | (15.53, 174.52), 94 | (12.17, 165.1), 95 | (7.62, 140.58), 96 | (7.67, 120.59), 97 | (8.92, 110.68), 98 | (13.83, 91.31), 99 | (21.8, 72.99), 100 | (32.58, 56.17), 101 | (45.86, 41.25), 102 | (61.32, 28.59), 103 | (78.6, 18.56), 104 | (97.28, 11.47), 105 | (106.99, 9.12), 106 | (126.84, 6.86)] 107 | 108 | # How long we'll wait between updates 109 | INTERVAL = 60 110 | 111 | # Setup for the Presto display 112 | presto = Presto() 113 | display = presto.display 114 | WIDTH, HEIGHT = display.get_bounds() 115 | 116 | touch = presto.touch 117 | 118 | # Pico Vector 119 | vector = PicoVector(display) 120 | vector.set_antialiasing(ANTIALIAS_BEST) 121 | 122 | # Colours for use later 123 | WHITE = display.create_pen(255, 255, 255) 124 | BLACK = display.create_pen(0, 0, 0) 125 | GRAY = display.create_pen(75, 75, 75) 126 | PINK = display.create_pen(250, 125, 180) 127 | 128 | 129 | def show_message(text): 130 | display.set_pen(PINK) 131 | display.clear() 132 | display.set_pen(WHITE) 133 | display.text(f"{text}", 5, 10, WIDTH - 10, 2) 134 | presto.update() 135 | 136 | 137 | show_message("Connecting...") 138 | 139 | try: 140 | wifi = presto.connect() 141 | except ValueError as e: 142 | while True: 143 | show_message(e) 144 | except ImportError as e: 145 | while True: 146 | show_message(e) 147 | 148 | # Centre points 149 | CX = WIDTH // 2 150 | CY = HEIGHT // 2 151 | 152 | # Shape constants 153 | BAR_W = 60 154 | BAR_H = 10 155 | HALF_BAR_W = BAR_W // 2 156 | HALF_BAR_H = BAR_H // 2 157 | BAR_Y_START = 160 158 | 159 | # Define our vector shapes 160 | bars = Polygon() 161 | bars.rectangle(CX - HALF_BAR_W, BAR_Y_START + 10, 60, 10, (5, 5, 5, 5)) 162 | bars.rectangle(CX - HALF_BAR_W, BAR_Y_START + 25, 60, 10, (5, 5, 5, 5)) 163 | bars.rectangle(CX - HALF_BAR_W, BAR_Y_START + 40, 60, 10, (5, 5, 5, 5)) 164 | end = Polygon() 165 | end.arc(CX - 14, BAR_Y_START + 55, 14, 90, 270) 166 | 167 | bulb = Polygon() 168 | bulb.path(*BULB_INNER) 169 | bulb_outline = Polygon() 170 | bulb_outline.path(*BULB_OUTLINE) 171 | 172 | transform = Transform() 173 | 174 | 175 | def draw_bulb(colour): 176 | 177 | display.set_pen(GRAY) 178 | 179 | transform.reset() 180 | vector.set_transform(transform) 181 | transform.rotate(180, (CX - 7, BAR_Y_START + 55)) 182 | vector.draw(end) 183 | 184 | transform.reset() 185 | 186 | vector.draw(bars) 187 | 188 | cl = display.create_pen(*colour) 189 | 190 | transform.reset() 191 | 192 | transform.translate(54, 11) 193 | transform.scale(0.5, 0.5) 194 | 195 | display.set_pen(BLACK) 196 | vector.draw(bulb_outline) 197 | 198 | display.set_pen(cl) 199 | vector.draw(bulb) 200 | 201 | 202 | def get_cheerlight(): 203 | try: 204 | print("Getting new colour...") 205 | req = requests.get("http://api.thingspeak.com/channels/1417/field/2/last.json", timeout=None) 206 | json = req.json() 207 | req.close() 208 | print("Success!") 209 | 210 | colour = tuple(int(json['field2'][i:i + 2], 16) for i in (1, 3, 5)) 211 | 212 | return colour 213 | 214 | except OSError: 215 | print("Error: Failed to get new colour") 216 | return (255, 255, 255) 217 | 218 | 219 | bulb_on = True 220 | last_updated = time.time() 221 | 222 | # Get the first colour from cheerlights 223 | colour = get_cheerlight() 224 | 225 | while True: 226 | 227 | touch.poll() 228 | 229 | if user_button.value() == 0: 230 | dark_mode = not dark_mode 231 | time.sleep(0.2) 232 | 233 | if wifi: 234 | # If the user is touching the screen we'll do the following 235 | if touch.state: 236 | bulb_on = not bulb_on 237 | # Wait for the user to stop touching the screen 238 | while touch.state: 239 | touch.poll() 240 | 241 | if bulb_on: 242 | if time.time() - last_updated > INTERVAL: 243 | colour = get_cheerlight() 244 | last_updated = time.time() 245 | 246 | if dark_mode: 247 | display.set_pen(BLACK) 248 | else: 249 | display.set_pen(WHITE) 250 | 251 | display.clear() 252 | 253 | draw_bulb(colour) 254 | 255 | for i in range(7): 256 | presto.set_led_rgb(i, *colour) 257 | 258 | time.sleep(0.02) 259 | 260 | else: 261 | display.set_pen(BLACK) 262 | display.clear() 263 | 264 | for i in range(7): 265 | presto.set_led_rgb(i, 0, 0, 0) 266 | 267 | time.sleep(0.02) 268 | 269 | draw_bulb((50, 50, 50)) 270 | 271 | else: 272 | show_message("No network connection!") 273 | 274 | presto.update() 275 | -------------------------------------------------------------------------------- /examples/cherry-hq.af: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/cherry-hq.af -------------------------------------------------------------------------------- /examples/cubes.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-2.22, 14.25), (-2.22, -0.97), (-15.56, -8.69), (-15.56, 6.53), (-2.22, 14.25)], [(2.22, 14.25), (15.56, 6.53), (15.56, -8.69), (2.22, -0.97), (2.22, 14.25)], [(0.0, -4.8), (13.17, -12.41), (0.0, -20.02), (-13.17, -12.41), (0.0, -4.8)], [(-17.78, 10.42), (-18.66, 9.77), (-19.42, 8.86), (-19.9, 7.58), (-20.0, 6.64), (-20.0, -11.08), (-19.9, -12.09), (-19.47, -13.25), (-18.77, -14.15), (-17.83, -14.88), (-2.22, -23.86), (-0.66, -24.42), (0.61, -24.42), (2.17, -23.89), (17.78, -14.91), (19.13, -13.71), (19.81, -12.36), (20.0, -11.13), (20.0, 6.59), (19.87, 7.68), (19.3, 8.95), (18.5, 9.91), (17.83, 10.39), (2.22, 19.37), (1.51, 19.7), (0.06, 19.98), (-1.15, 19.82), (-2.17, 19.39), (-17.78, 10.42)], [(0.0, -2.25), (0.0, -2.25)]] 2 | # NAME Flying Cubes 3 | # DESC Cubes that are flying! 4 | 5 | import math 6 | import time 7 | from random import randint, randrange 8 | 9 | from presto import Presto 10 | 11 | # Setup for the Presto display 12 | presto = Presto() 13 | display = presto.display 14 | WIDTH, HEIGHT = display.get_bounds() 15 | 16 | BLACK = display.create_pen(0, 0, 0) 17 | WHITE = display.create_pen(255, 255, 255) 18 | 19 | 20 | class Cube(object): 21 | # The corners of the cube 22 | vertices = [[-1, -1, 1], 23 | [1, -1, 1], 24 | [1, -1, -1], 25 | [-1, -1, -1], 26 | [-1, 1, 1], 27 | [1, 1, 1], 28 | [1, 1, -1], 29 | [-1, 1, -1]] 30 | 31 | # The corners that will be connected together to make a cube :) 32 | edges = [(0, 1), (1, 2), (2, 3), (3, 0), 33 | (4, 5), (5, 6), (6, 7), (7, 4), 34 | (0, 4), (1, 5), (2, 6), (3, 7)] 35 | 36 | def __init__(self, fov, distance, x, y, speed): 37 | self.tick = time.ticks_ms() / 1000.0 38 | self.cos = math.cos(self.tick) 39 | self.sin = math.sin(self.tick) 40 | self.fov = fov 41 | self.distance = distance 42 | self.pos_x = x 43 | self.pos_y = y 44 | self.speed = speed 45 | 46 | self.cube_points = [] 47 | 48 | # Project our points 49 | def to_2d(self, x, y, z, pos_x, pos_y, fov, distance): 50 | factor = fov / (distance + z) 51 | x = x * factor + pos_x 52 | y = -y * factor + pos_y 53 | 54 | return int(x), int(y) 55 | 56 | def return_tick(self): 57 | return self.tick 58 | 59 | # Clear our points and recalculate the sin and cos values 60 | def _update(self): 61 | 62 | self.cube_points = [] 63 | 64 | self.tick = time.ticks_ms() / (self.speed * 1000) 65 | self.cos = math.cos(self.tick) 66 | self.sin = math.sin(self.tick) 67 | 68 | def set_fov(self, fov): 69 | self.fov = fov 70 | 71 | def set_distance(self, distance): 72 | self.distance = distance 73 | 74 | def set_speed(self, speed): 75 | self.speed = speed 76 | 77 | def set_x(self, x): 78 | self.pos_x = x 79 | 80 | def set_y(self, y): 81 | self.pos_y = y 82 | 83 | def get_fov(self): 84 | return self.fov 85 | 86 | # Rotate on XYZ and save the new points in our list 87 | def rotate(self): 88 | 89 | for v in self.vertices: 90 | 91 | start_x, start_y, start_z = v 92 | 93 | # X 94 | y = start_y * self.cos - start_z * self.sin 95 | z = start_y * self.sin + start_z * self.cos 96 | 97 | # Y 98 | x = start_x * self.cos - z * self.sin 99 | z = start_x * self.sin + z * self.cos 100 | 101 | # Z 102 | n_y = x * self.sin + y * self.cos 103 | n_x = x * self.cos - y * self.sin 104 | 105 | y = n_y 106 | x = n_x 107 | 108 | point = self.to_2d(x, y, z, self.pos_x, self.pos_y, self.fov, self.distance) 109 | self.cube_points.append(point) 110 | 111 | # Draw the edges of the cube so we can see it on screen! 112 | def draw(self): 113 | 114 | for edge in self.edges: 115 | display.line(self.cube_points[edge[0]][0], self.cube_points[edge[0]][1], self.cube_points[edge[1]][0], self.cube_points[edge[1]][1]) 116 | 117 | self._update() 118 | 119 | 120 | # Setup the first 3 cubes. 121 | cubes = [Cube(16, 4, WIDTH / 2, HEIGHT / 2, 1.0), Cube(32, 4, 100, 100, 0.9), Cube(32, 4, 100, 100, 0.5)] 122 | 123 | # Set our initial pen colour 124 | pen = display.create_pen_hsv(1.0, 1.0, 1.0) 125 | 126 | while 1: 127 | 128 | # We'll use this for cycling through the rainbow 129 | t = time.ticks_ms() / 1000 130 | 131 | # Set the layer we're going to be drawing to. 132 | display.set_layer(0) 133 | 134 | # Clear the screen and set the pen colour for the cubes 135 | display.set_pen(BLACK) 136 | display.clear() 137 | display.set_pen(WHITE) 138 | display.text("Flying Cubes!", 90, 110, 320, 1) 139 | display.reset_pen(pen) 140 | pen = display.create_pen_hsv(t, 1.0, 1.0) 141 | display.set_pen(pen) 142 | 143 | # Now we go through each Cube object we have in 'cubes' 144 | # and increase the FOV angle so it appears closer to the screen. 145 | # We'll also rotate the cube during this loop too. 146 | for i, cube in enumerate(cubes): 147 | fov = cube.get_fov() 148 | fov += 5 149 | cube.set_fov(fov) 150 | cube.rotate() 151 | cube.draw() 152 | 153 | # We want the cubes to disappear randomly as they appear close to the screen, so we'll decide when this happens based on the current FOV 154 | # We'll replace that cube with a new one and start the process from the beginning! 155 | if fov > randint(250, 600): 156 | cubes[i] = Cube(8, 4, randint(10, WIDTH), randint(10, HEIGHT), randrange(4, 9) / 10) 157 | 158 | # Finally we update the screen with our changes :) 159 | presto.update() 160 | time.sleep(0.01) 161 | -------------------------------------------------------------------------------- /examples/gallery/example-image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/gallery/example-image1.jpg -------------------------------------------------------------------------------- /examples/gallery/example-image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/gallery/example-image2.jpg -------------------------------------------------------------------------------- /examples/gallery/example-image3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/gallery/example-image3.jpg -------------------------------------------------------------------------------- /examples/hello.py: -------------------------------------------------------------------------------- 1 | from presto import Presto 2 | 3 | # Setup for the Presto display 4 | presto = Presto(ambient_light=True) 5 | display = presto.display 6 | WIDTH, HEIGHT = display.get_bounds() 7 | 8 | # Couple of colours for use later 9 | BLUE = display.create_pen(28, 181, 202) 10 | WHITE = display.create_pen(255, 255, 255) 11 | 12 | 13 | while True: 14 | 15 | # Clear the screen and use blue as the background colour 16 | display.set_pen(BLUE) 17 | display.clear() 18 | # Set the pen to a different colour otherwise we won't be able to see the text! 19 | display.set_pen(WHITE) 20 | 21 | # draw the text 22 | display.text("Hello!", 10, 85, WIDTH, 8) 23 | 24 | # Finally we update the screen with our changes :) 25 | presto.update() 26 | -------------------------------------------------------------------------------- /examples/image_gallery.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-6.0, 2.0), (14.0, 2.0), (7.1, -7.0), (2.5, -1.0), (-0.6, -5.0), (-6.0, 2.0)], [(-8.0, 10.0), (-8.77, 9.93), (-10.0, 9.49), (-10.81, 8.88), (-11.53, 7.91), (-11.87, 7.04), (-12.0, 6.05), (-12.0, -18.0), (-11.72, -19.5), (-11.15, -20.47), (-10.47, -21.14), (-9.78, -21.58), (-8.86, -21.91), (-8.05, -22.0), (16.0, -22.0), (16.82, -21.92), (18.02, -21.44), (18.51, -21.07), (19.42, -20.05), (19.83, -19.17), (20.0, -18.05), (20.0, 6.0), (19.7, 7.54), (19.37, 8.16), (18.65, 8.99), (17.36, 9.77), (16.05, 10.0), (-8.0, 10.0)], [(-8.0, 6.0), (16.0, 6.0), (16.0, -18.0), (-8.0, -18.0), (-8.0, 6.0)], [(-16.0, 18.0), (-16.73, 17.94), (-17.83, 17.58), (-18.81, 16.88), (-19.26, 16.35), (-19.79, 15.31), (-20.0, 14.05), (-20.0, -14.0), (-16.0, -14.0), (-16.0, 14.0), (12.0, 14.0), (12.0, 18.0), (-16.0, 18.0)], [(-8.0, -18.0), (-8.0, -18.0)]] 2 | # NAME Photo Frame 3 | # DESC A touch enabled image gallery 4 | 5 | ''' 6 | An image gallery demo to turn your Pimoroni Presto into a desktop photo frame! 7 | 8 | - Create a folder called 'gallery' on the root of your SD card and fill it with JPEGs. 9 | - The image will change automatically every 5 minutes 10 | - You can also tap the right side of the screen to skip next image and left side to go to the previous :) 11 | 12 | ''' 13 | import os 14 | import time 15 | 16 | import jpegdec 17 | import machine 18 | import plasma 19 | import sdcard 20 | import uos 21 | from presto import Presto 22 | 23 | # The total number of LEDs to set, the Presto has 7 24 | NUM_LEDS = 7 25 | 26 | # Seconds between changing the image on screen 27 | # This interval shows us a new image every 5 minutes 28 | INTERVAL = 60 * 5 29 | 30 | LEDS_LEFT = [4, 5, 6] 31 | LEDS_RIGHT = [0, 1, 2] 32 | 33 | # Setup for the Presto display 34 | presto = Presto() 35 | display = presto.display 36 | WIDTH, HEIGHT = display.get_bounds() 37 | 38 | BACKGROUND = display.create_pen(1, 1, 1) 39 | WHITE = display.create_pen(255, 255, 255) 40 | BLACK = display.create_pen(0, 0, 0) 41 | 42 | # We'll need this for the touch element of the screen 43 | touch = presto.touch 44 | 45 | # JPEG Dec 46 | j = jpegdec.JPEG(display) 47 | 48 | # Where our images are located 49 | directory = 'gallery' 50 | 51 | # Stores the total number of images in the user gallery 52 | total_image_count = 0 53 | 54 | # Store our current location within the user gallery 55 | current_image = 0 56 | 57 | lfsr = 1 58 | tap = 0xdc29 59 | 60 | 61 | # Display an error msg on screen and keep it looping 62 | def display_error(text): 63 | while 1: 64 | for i in range(2): 65 | display.set_layer(i) 66 | display.set_pen(BACKGROUND) 67 | display.clear() 68 | display.set_pen(WHITE) 69 | display.text(f"Error: {text}", 10, 10, WIDTH - 10, 1) 70 | presto.update() 71 | time.sleep(1) 72 | 73 | 74 | try: 75 | # Setup for SD Card 76 | sd_spi = machine.SPI(0, sck=machine.Pin(34, machine.Pin.OUT), mosi=machine.Pin(35, machine.Pin.OUT), miso=machine.Pin(36, machine.Pin.OUT)) 77 | sd = sdcard.SDCard(sd_spi, machine.Pin(39)) 78 | 79 | # Mount the SD to the directory 'sd' 80 | uos.mount(sd, "/sd") 81 | 82 | # if the gallery folder exists on the SD card we want to use the images in there! 83 | if os.stat('sd/gallery'): 84 | directory = 'sd/gallery' 85 | 86 | except OSError: 87 | pass 88 | 89 | 90 | def numberedfiles(k): 91 | try: 92 | return int(k[:-4]) 93 | except ValueError: 94 | pass 95 | return 0 96 | 97 | 98 | try: 99 | files = list(file for file in sorted(os.listdir(directory), key=numberedfiles) if file.endswith('.jpg')) 100 | except OSError: 101 | display_error("Problem loading images.\n\nEnsure that your Presto or SD card contains a 'gallery' folder in the root") 102 | 103 | total_image_count = len(files) - 1 104 | 105 | 106 | def return_point(): 107 | global lfsr 108 | 109 | x = lfsr & 0x00ff 110 | y = (lfsr & 0xff00) >> 8 111 | 112 | lsb = lfsr & 1 113 | lfsr >>= 1 114 | 115 | if lsb: 116 | lfsr ^= tap 117 | 118 | if x - 1 < 240 and y < 240: 119 | return x - 1, y 120 | 121 | return -1, -1 122 | 123 | 124 | def fizzlefade(): 125 | display.set_pen(BLACK) 126 | display.set_layer(1) 127 | 128 | while True: 129 | 130 | for i in range(2000): 131 | x, y = return_point() 132 | if x > -1 and y > -1: 133 | display.pixel(x, y) 134 | if lfsr == 1: 135 | break 136 | 137 | presto.update() 138 | if lfsr == 1: 139 | break 140 | 141 | 142 | def show_image(show_next=False, show_previous=False): 143 | global current_image 144 | global total_image_count 145 | 146 | # Get the next image in the gallery 147 | # If we're at the end of the gallery, loop back and start from 1. 148 | if show_next: 149 | if current_image < total_image_count: 150 | current_image += 1 151 | else: 152 | current_image = 0 153 | if show_previous: 154 | if current_image > 0: 155 | current_image -= 1 156 | else: 157 | current_image = total_image_count 158 | 159 | # Open the index file and read lines until we're at the correct position 160 | try: 161 | img = f"{directory}/{files[current_image]}" 162 | 163 | j.open_file(img) 164 | 165 | img_height, img_width = j.get_height(), j.get_width() 166 | 167 | img_x = 0 168 | img_y = 0 169 | 170 | # if the image isn't exactly 240x240 then we'll try to centre the image 171 | if img_width < WIDTH: 172 | img_x = (WIDTH // 2) - (img_width // 2) 173 | 174 | if img_height < HEIGHT: 175 | img_y = (HEIGHT // 2) - (img_height // 2) 176 | 177 | display.set_layer(0) 178 | display.set_pen(BACKGROUND) 179 | display.clear() 180 | j.decode(img_x, img_y, jpegdec.JPEG_SCALE_FULL, dither=True) 181 | 182 | fizzlefade() 183 | 184 | # Now draw the current image to Layer 1 185 | display.set_layer(1) 186 | # Decode the JPEG 187 | j.decode(img_x, img_y, jpegdec.JPEG_SCALE_FULL, dither=True) 188 | 189 | except OSError: 190 | display_error("Unable to find/read file.\n\nCheck that the 'gallery' folder in the root of your SD card contains JPEG images!") 191 | except IndexError: 192 | display_error("Unable to read images in the 'gallery' folder.\n\nCheck the files are present and are in JPEG format.") 193 | 194 | 195 | def clear(): 196 | display.set_pen(BACKGROUND) 197 | display.set_layer(0) 198 | display.clear() 199 | display.set_layer(1) 200 | display.clear() 201 | 202 | 203 | # Store the last time the screen was updated 204 | last_updated = time.time() 205 | 206 | # Show the first image on the screen so it's not just noise :) 207 | # We're not passing the arg for 'show_next' or 'show_previous' so it'll show whichever image is current 208 | clear() 209 | show_image() 210 | presto.update() 211 | 212 | while True: 213 | 214 | # Poll the touch so we can see if anything changed since the last time 215 | touch.poll() 216 | 217 | # Check if it's time to update the image! 218 | if time.time() - last_updated > INTERVAL: 219 | 220 | last_updated = time.time() 221 | show_image(show_next=True) 222 | presto.update() 223 | 224 | # if the screen is reporting that there is touch we want to handle that here 225 | if touch.state: 226 | # Right half of the screen moves to the next image 227 | # The LEDs on the right side of the presto light up to show it is working 228 | if touch.x > WIDTH // 2: 229 | for i in LEDS_RIGHT: 230 | presto.set_led_rgb(i, 255, 255, 255) 231 | show_image(show_next=True) 232 | presto.update() 233 | last_updated = time.time() 234 | for i in LEDS_RIGHT: 235 | presto.set_led_rgb(i, 0, 0, 0) 236 | time.sleep(0.01) 237 | 238 | # Left half of the screen moves to the previous image 239 | elif touch.x < WIDTH // 2: 240 | for i in LEDS_LEFT: 241 | presto.set_led_rgb(i, 255, 255, 255) 242 | show_image(show_previous=True) 243 | presto.update() 244 | last_updated = time.time() 245 | for i in LEDS_LEFT: 246 | presto.set_led_rgb(i, 0, 0, 0) 247 | time.sleep(0.01) 248 | 249 | # Wait here until the user stops touching the screen 250 | while touch.state: 251 | touch.poll() 252 | time.sleep(0.02) 253 | -------------------------------------------------------------------------------- /examples/indoor-outdoor-temp.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-20.0, 16.67), (-20.0, 12.22), (-15.56, 7.78), (-15.56, 16.67), (-20.0, 16.67)], [(-11.11, 16.67), (-11.11, 3.33), (-6.67, -1.11), (-6.67, 16.67), (-11.11, 16.67)], [(-2.22, 16.67), (-2.22, -1.11), (2.22, 3.39), (2.22, 16.67), (-2.22, 16.67)], [(6.67, 16.67), (6.67, 3.39), (11.11, -1.06), (11.11, 16.67), (6.67, 16.67)], [(15.56, 16.67), (15.56, -5.56), (20.0, -10.0), (20.0, 16.67), (15.56, 16.67)], [(-20.0, 5.17), (-20.0, -1.11), (-4.44, -16.67), (4.44, -7.78), (20.0, -23.33), (20.0, -17.06), (4.44, -1.5), (-4.44, -10.39), (-20.0, 5.17)]] 2 | # NAME Indoor/Outdoor 3 | # DESC Display the indoor and outdoor temperature 4 | 5 | from presto import Presto 6 | from breakout_bme280 import BreakoutBME280 7 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 8 | import machine 9 | import urequests 10 | import time 11 | 12 | # Set your latitude/longitude here (find yours by right clicking in Google Maps!) 13 | LAT = 53.38609085276884 14 | LNG = -1.4239983439328177 15 | 16 | TIMEZONE = "auto" # determines time zone from lat/long 17 | URL = "http://api.open-meteo.com/v1/forecast?latitude=" + str(LAT) + "&longitude=" + str(LNG) + "¤t_weather=true&timezone=" + TIMEZONE 18 | 19 | # Setup for the Presto display 20 | presto = Presto(ambient_light=True) 21 | display = presto.display 22 | WIDTH, HEIGHT = display.get_bounds() 23 | CX = WIDTH // 2 24 | CY = HEIGHT // 2 25 | 26 | # Colours 27 | BLACK = display.create_pen(0, 0, 0) 28 | hue = 0.90 29 | BACKGROUND = display.create_pen_hsv(hue, 0.8, 1.0) # We'll use this one for the background. 30 | FOREGROUND = display.create_pen_hsv(hue, 0.5, 1.0) # Slightly lighter for foreground elements. 31 | TEXT_COLOUR = display.create_pen_hsv(hue, 0.3, 1.0) 32 | 33 | # Pico Vector 34 | vector = PicoVector(display) 35 | vector.set_antialiasing(ANTIALIAS_BEST) 36 | t = Transform() 37 | 38 | vector.set_font("Roboto-Medium.af", 96) 39 | vector.set_font_letter_spacing(100) 40 | vector.set_font_word_spacing(100) 41 | vector.set_transform(t) 42 | 43 | 44 | def show_message(text): 45 | display.set_pen(BACKGROUND) 46 | display.clear() 47 | display.set_pen(FOREGROUND) 48 | display.text(f"{text}", 5, 10, WIDTH, 2) 49 | presto.update() 50 | 51 | 52 | # Connect to the network and get time. 53 | show_message("Connecting...") 54 | 55 | try: 56 | presto.connect() 57 | except ValueError as e: 58 | while True: 59 | show_message(e) 60 | except ImportError as e: 61 | while True: 62 | show_message(e) 63 | 64 | 65 | # Setup for the i2c and bme sensor 66 | try: 67 | bme = BreakoutBME280(machine.I2C()) 68 | except RuntimeError: 69 | while True: 70 | show_message("No Multi-Sensor stick detected!\n\nConnect and try again.") 71 | 72 | 73 | # We'll use a rect with rounded corners for the background. 74 | background_rect = Polygon() 75 | background_rect.rectangle(0, 0, WIDTH, HEIGHT) 76 | background_rect.rectangle(0, 0, WIDTH, HEIGHT, (10, 10, 10, 10)) 77 | 78 | 79 | # get the current outdoor temperature 80 | def get_data(): 81 | try: 82 | 83 | r = urequests.get(URL) 84 | # open the json data 85 | j = r.json() 86 | 87 | # parse relevant data from JSON 88 | current = j["current_weather"] 89 | temperature = current["temperature"] 90 | 91 | r.close() 92 | 93 | return temperature 94 | except OSError: 95 | return None 96 | 97 | 98 | # Keep a record of the last time we updated. 99 | # We only want to be requesting new data every 15 minutes. 100 | last_updated = time.time() 101 | 102 | # Get the first outdoor reading 103 | out = get_data() 104 | outdoor_temp_string = f"{out}C" if out else "N/A" 105 | 106 | while True: 107 | 108 | # Clear screen and draw our background rectangle 109 | display.set_pen(BACKGROUND) 110 | display.clear() 111 | 112 | # Get readings and format strings 113 | try: 114 | reading = bme.read() 115 | except RuntimeError: 116 | while True: 117 | show_message("Failed to get reading from BME280.\n\nCheck connection and reset :)") 118 | 119 | indoor_temp_string = f"{reading[0]:.1f}C" 120 | 121 | if time.time() - last_updated > 900: # 15 minutes in seconds 122 | out = get_data() 123 | outdoor_temp_string = f"{out}C" if out else "N/A" 124 | last_updated = time.time() 125 | 126 | t.rotate(-45, (CX, CY)) 127 | 128 | display.set_pen(FOREGROUND) 129 | half_screen = Polygon().rectangle(-WIDTH // 2, - HEIGHT // 2, WIDTH * 2, HEIGHT) 130 | vector.draw(half_screen) 131 | 132 | display.set_pen(BACKGROUND) 133 | vector.set_font_size(92) 134 | x, y, w, h = vector.measure_text(indoor_temp_string) 135 | tx = int(CX - (w // 2)) 136 | ty = CY - 5 137 | vector.text(indoor_temp_string, tx, ty) 138 | 139 | display.set_pen(FOREGROUND) 140 | x, y, w, h = vector.measure_text(outdoor_temp_string) 141 | tx = int(CX - (w // 2)) 142 | ty = CY + int(h) + 5 143 | vector.text(outdoor_temp_string, tx, ty) 144 | 145 | t.reset() 146 | 147 | display.set_pen(BLACK) 148 | vector.draw(background_rect) 149 | 150 | # Update the screen so we can see our changes 151 | presto.update() 152 | -------------------------------------------------------------------------------- /examples/main.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import os 4 | import machine 5 | import psram 6 | import plasma 7 | 8 | from presto import Presto 9 | from picovector import ANTIALIAS_FAST, PicoVector, Polygon, Transform, HALIGN_CENTER 10 | 11 | psram.mkramfs() 12 | 13 | try: 14 | with open("/ramfs/launch.txt", "r") as f: 15 | result = f.readline() 16 | except OSError: 17 | result = "" 18 | 19 | if result.endswith(".py"): 20 | os.remove("/ramfs/launch.txt") 21 | __import__(result[:-3]) 22 | 23 | # Setup for the Presto display 24 | presto = Presto(ambient_light=False) 25 | 26 | display = presto.display 27 | 28 | WIDTH, HEIGHT = display.get_bounds() 29 | 30 | CENTER_Y = HEIGHT // 2 31 | CENTER_X = WIDTH // 2 32 | 33 | OFFSET_X = 0 34 | OFFSET_Y = -30 35 | 36 | RADIUS_X = 160 37 | RADIUS_Y = 30 38 | 39 | # Couple of colours for use later 40 | BLACK = display.create_pen(0, 0, 0) 41 | BACKGROUND = display.create_pen(255, 250, 240) 42 | 43 | # We do a clear and update here to stop the screen showing whatever is in the buffer. 44 | display.set_pen(BLACK) 45 | display.clear() 46 | presto.update() 47 | 48 | # Pico Vector 49 | vector = PicoVector(display) 50 | vector.set_antialiasing(ANTIALIAS_FAST) 51 | vector.set_font("Roboto-Medium.af", 10) 52 | vector.set_font_align(HALIGN_CENTER) 53 | t = Transform() 54 | 55 | # Touch tracking and menu movement 56 | touch_start_x = 0 57 | touch_start_time = None 58 | tap = False 59 | 60 | move_angle = 0 61 | move = 0 62 | friction = 0.98 63 | 64 | # Rounded corners 65 | rounded_corners = Polygon() 66 | rounded_corners.rectangle(0, 0, WIDTH, HEIGHT) 67 | rounded_corners.rectangle(0, 0, WIDTH, HEIGHT, (10, 10, 10, 10)) 68 | 69 | 70 | class Application: 71 | DEFAULT_ICON = [ 72 | [(-10.0, 12.5), (10.0, 12.5), (10.0, 7.5), (-10.0, 7.5), (-10.0, 12.5)], 73 | [(-10.0, 2.5), (10.0, 2.5), (10.0, -2.5), (-10.0, -2.5), (-10.0, 2.5)], 74 | [ 75 | (-15.0, 22.5), 76 | (-16.43, 22.31), 77 | (-17.75, 21.71), 78 | (-18.52, 21.11), 79 | (-19.47, 19.78), 80 | (-19.92, 18.46), 81 | (-20.0, 17.56), 82 | (-20.0, -22.5), 83 | (-19.77, -24.05), 84 | (-18.82, -25.73), 85 | (-17.79, -26.64), 86 | (-16.69, -27.21), 87 | (-15.06, -27.5), 88 | (5.0, -27.5), 89 | (20.0, -12.5), 90 | (20.0, 17.5), 91 | (19.77, 19.04), 92 | (19.36, 19.95), 93 | (18.55, 21.02), 94 | (17.74, 21.68), 95 | (16.7, 22.21), 96 | (15.06, 22.5), 97 | (-15.0, 22.5), 98 | ], 99 | [ 100 | (2.5, -10.0), 101 | (2.5, -22.5), 102 | (-15.0, -22.5), 103 | (-15.0, 17.5), 104 | (15.0, 17.5), 105 | (15.0, -10.0), 106 | (2.5, -10.0), 107 | ] 108 | ] 109 | 110 | maximum_scale = 1.6 111 | minimum_scale = 0.6 112 | count = 0 113 | 114 | def __init__(self, w, h, file): 115 | self.index = Application.count 116 | Application.count += 1 117 | 118 | self.selected = False 119 | self.icon = Polygon() 120 | 121 | # Bit of filename formatting for scripts without a title in the header. 122 | self.name = " ".join([w[0].upper() + w[1:] for w in file[:-3].replace("_", " ").split()]) 123 | self.description = "" 124 | 125 | with open(file) as f: 126 | header = [f.readline().strip() for _ in range(3)] 127 | 128 | for line in header: 129 | if line.startswith("# ICON "): 130 | try: 131 | for path in eval(line[7:]): 132 | self.icon.path(*path) 133 | except: # noqa: E722 - eval could barf up all kinds of nonsense 134 | pass 135 | else: 136 | # If there's no icon in the file header we'll use the default. 137 | for path in self.DEFAULT_ICON: 138 | self.icon.path(*path) 139 | 140 | if line.startswith("# NAME "): 141 | self.name = line[7:] 142 | 143 | if line.startswith("# DESC "): 144 | self.description = line[7:] 145 | 146 | self.w = w 147 | self.h = h 148 | self.file = file 149 | 150 | # Background 151 | self.bg = Polygon().rectangle(0 - w / 2, 0 - h / 2, w, h, (10, 10, 10, 10)) 152 | 153 | # Outline 154 | self.ol = Polygon().rectangle(0 - w / 2, 0 - h / 2, w, h, (10, 10, 10, 10), stroke=2) 155 | 156 | # Transform 157 | self.t = Transform() 158 | 159 | self.angle = 0 160 | self.scale = 0 161 | self.x = 0 162 | self.y = 0 163 | 164 | self.color_fg = None 165 | self.color_bg = None 166 | self.color_ol = None 167 | 168 | def __lt__(self, icon): 169 | return self.scale < icon.scale 170 | 171 | def touched(self, touch): 172 | x, y, w, h = self.bounds() 173 | return touch.x > x and touch.x < x + w and touch.y > y and touch.y < y + h 174 | 175 | def update(self, move_angle): 176 | angle_per_icon = 2 * math.pi / Application.count 177 | self.angle = angle_per_icon * self.index + move_angle 178 | 179 | self.angle %= 2 * math.pi 180 | 181 | scale_factor = (math.cos(self.angle) + 1.0) / 2 182 | self.scale = max(self.minimum_scale, scale_factor) * self.maximum_scale 183 | 184 | # The lower the lower bounds here, the less saturated 185 | s = min(0.6, scale_factor + 0.1) 186 | 187 | self.hue = (angle_per_icon * self.index) / (2 * math.pi) 188 | self.color_fg = display.create_pen_hsv(self.hue, s, 0.2) 189 | self.color_ol = display.create_pen_hsv(self.hue, s, 1.0) 190 | self.color_bg = display.create_pen_hsv(self.hue, s, 0.9) 191 | 192 | self.y = RADIUS_Y * math.cos(self.angle) 193 | self.x = RADIUS_X * math.sin(self.angle) 194 | 195 | # Quick and dirty way to "perspective correct" the circle 196 | self.x *= self.scale 197 | 198 | # Logically these things below happen in reverse order 199 | # but because these are matrix operations we need to apply them back to front 200 | # THIS IS WEIRD BUT MATHS GON' MATHS 201 | self.t.reset() 202 | # Translate to our final display offset 203 | self.t.translate(OFFSET_X, OFFSET_Y) 204 | # Translate back to screen space, moving origin 0, 0 to our X and Y 205 | self.t.translate(CENTER_X + self.x, CENTER_Y + self.y) 206 | # Scale the icon around origin 0, 0 207 | self.t.scale(self.scale, self.scale) 208 | 209 | def draw(self, selected=False): 210 | display.set_pen(self.color_bg) 211 | vector.set_transform(self.t) 212 | vector.draw(self.bg) 213 | display.set_pen(self.color_fg) 214 | self.t.translate(0, 2) 215 | vector.draw(self.icon) 216 | 217 | if selected: 218 | self.t.translate(0, -2) 219 | display.set_pen(BLACK) 220 | vector.set_font_size(10) 221 | vector.text(self.name, -self.w, 40, max_width=self.w * 2) 222 | display.set_pen(self.color_ol) 223 | vector.draw(self.ol) 224 | vector.set_font_size(8) 225 | desc_length = vector.measure_text(self.description, 18) 226 | vector.text( 227 | self.description, 228 | -int(desc_length[2]) // 2, 229 | 50, 230 | max_width=int(desc_length[2]) + 5, 231 | ) 232 | 233 | # Useful for debugging 234 | # display.rectangle(*self.bounds()) 235 | 236 | def bounds(self): 237 | w = self.w * self.scale 238 | h = self.h * self.scale 239 | x = -w // 2 240 | y = -h // 2 241 | 242 | return ( 243 | int(x + self.x + CENTER_X + OFFSET_X), 244 | int(y + self.y + CENTER_Y + OFFSET_Y), 245 | int(w), 246 | int(h), 247 | ) 248 | 249 | def launch(self): 250 | with open("/ramfs/launch.txt", "w") as f: 251 | f.write(self.file) 252 | 253 | # Clear the display buffer before launching the next app 254 | display.set_pen(BLACK) 255 | display.clear() 256 | presto.update() 257 | 258 | # Reset! 259 | machine.reset() 260 | 261 | 262 | icons = [ 263 | Application(60, 60, file) for file in os.listdir() 264 | if file.endswith(".py") and file not in ("main.py", "secrets.py")] 265 | 266 | # Take a local reference to touch for a tiny performance boost 267 | touch = presto.touch 268 | 269 | while True: 270 | # We don't want any of the icon transforms to apply to our background 271 | vector.set_transform(t) 272 | 273 | # Clear screen to our background colour 274 | display.set_pen(BACKGROUND) 275 | display.clear() 276 | 277 | # Draw rounded corners in black 278 | display.set_pen(BLACK) 279 | vector.draw(rounded_corners) 280 | 281 | touch.poll() 282 | 283 | if touch.state and touch_start_time is None: 284 | touch_start_time = time.ticks_ms() 285 | touch_start_x = touch.x 286 | last_touch_x = touch.x 287 | tap = True 288 | 289 | elif touch.state: 290 | # Get the duration of the touch in milliseconds 291 | touch_ms = time.ticks_ms() - touch_start_time 292 | 293 | # Get the x distance between the touch start and current touch 294 | touch_dist = touch_start_x - touch.x 295 | 296 | # Calculate the touch speed, speed = distance / time 297 | touch_speed = touch_dist / touch_ms 298 | 299 | # Any movement should cancel our tap action 300 | if abs(touch_dist) > 4: 301 | tap = False 302 | 303 | # If a touch is under this minimal distance it counts as a "stop spinning, darn it" 304 | if abs(touch_dist) > 10: 305 | # Follow finger as it moves 306 | move = -math.radians(last_touch_x - touch.x) * 0.12 307 | last_touch_x = touch.x 308 | 309 | # Normal friction after touch ends ( the closer this is to 1 the longer it will take to slow down ) 310 | friction = 0.8 311 | 312 | else: 313 | # Pick the one you like best 314 | # move = 0 # Stop abruptly 315 | friction = 0.7 # Apply a braking friction 316 | else: 317 | touch_start_time = None 318 | 319 | move_angle += move # Apply the movement distance, this is in degrees and needs finagled to follow your finger 320 | move_angle %= 2 * math.pi # Wrap at 360 degrees (in radians) 321 | move *= friction # Apply friction, this will slowly decrease "move" when there's no touch, to slow the spin down 322 | 323 | # Pre-calculate the scales and angles for sorting. 324 | for icon in icons: 325 | icon.update(move_angle) 326 | 327 | # We have implemented the __lt__ magic method on Icons so we can just sort them 328 | # by visual size- the biggest icon is at the front! 329 | sorted_icons = sorted(icons) 330 | 331 | # Draw all but the front-most icon 332 | for icon in sorted_icons[:-1]: 333 | icon.draw() 334 | 335 | # Draw the front-most selected icons, True == selected 336 | front_most_icon = sorted_icons[-1] 337 | front_most_icon.draw(True) 338 | 339 | if tap and not touch.state: 340 | tap = False 341 | if front_most_icon.touched(touch): 342 | front_most_icon.launch() 343 | 344 | # Handle touches on all the inactive icons 345 | for icon in sorted_icons[:-1]: 346 | if icon.touched(touch): 347 | a = icon.angle 348 | friction = 0.5 # The lower this value, the faster the transition 349 | if a - math.pi > 0: # Take the shortest route 350 | a = 2 * math.pi - a 351 | move = a * (1.0 - friction) 352 | else: 353 | move = -a * (1.0 - friction) 354 | 355 | # Cycle the hue of the backlight LEDs to match the icon colours 356 | hue = 1.0 - (move_angle % (2 * math.pi)) / (2 * math.pi) 357 | for i in range(7): 358 | presto.set_led_hsv(i, hue, 1.0, 0.5) 359 | 360 | presto.update() 361 | -------------------------------------------------------------------------------- /examples/micro_sd.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/micro_sd.jpg -------------------------------------------------------------------------------- /examples/multi_player.py: -------------------------------------------------------------------------------- 1 | import math 2 | from collections import namedtuple 3 | 4 | from machine import I2C 5 | from presto import Presto 6 | from qwstpad import ADDRESSES, QwSTPad 7 | 8 | """ 9 | A multi-player QwSTPad game demo. Each player drives a tank-like vehicle around an arena 10 | with the goal of hitting other players with projects to get the most points. 11 | Makes use of 1 to 4 QwSTPads and a Pimoroni Presto 12 | 13 | Controls: 14 | * U = Move Forward 15 | * D = Move Backward 16 | * R = Turn Right 17 | * L = Turn left 18 | * A = Fire 19 | """ 20 | 21 | # Setup for the Presto display 22 | presto = Presto() 23 | display = presto.display 24 | WIDTH, HEIGHT = display.get_bounds() 25 | 26 | # General Constants 27 | I2C_PINS = {"id": 0, "sda": 40, "scl": 41} # The I2C pins the QwSTPad is connected to 28 | BRIGHTNESS = 1.0 # The brightness of the LCD backlight (from 0.0 to 1.0) 29 | 30 | # Colour Constants (RGB565) 31 | WHITE = display.create_pen(255, 255, 255) 32 | BLACK = display.create_pen(0, 0, 0) 33 | CYAN = display.create_pen(0, 255, 255) 34 | MAGENTA = display.create_pen(255, 0, 255) 35 | YELLOW = display.create_pen(255, 255, 0) 36 | GREEN = display.create_pen(0, 255, 0) 37 | RED = display.create_pen(255, 0, 0) 38 | BLUE = display.create_pen(0, 0, 255) 39 | GREY = display.create_pen(115, 115, 115) 40 | 41 | # Gameplay Constants 42 | PlayerDef = namedtuple("PlayerDef", ("x", "y", "colour")) 43 | PLAYERS = (PlayerDef(x=30, y=50, colour=GREEN), 44 | PlayerDef(x=280, y=50, colour=MAGENTA), 45 | PlayerDef(x=30, y=200, colour=CYAN), 46 | PlayerDef(x=280, y=200, colour=BLUE)) 47 | PLAYER_RADIUS = 10 48 | PLAYER_SPEED = 4 49 | LINE_LENGTH = 25 50 | START_ANGLE = 20 51 | PROJECTILE_LIMIT = 15 52 | PROJECTILE_SPEED = 5 53 | GRID_SPACING = 20 54 | SCORE_TARGET = 1000 55 | TEXT_SHADOW = 2 56 | 57 | i2c = I2C(**I2C_PINS) # The I2C instance to pass to all QwSTPads 58 | players = [] # The list that will store the player objects 59 | complete = False # Has the game been completed? 60 | 61 | 62 | # Classes 63 | class Projectile: 64 | def __init__(self, x, y, direction): 65 | self.x = x 66 | self.y = y 67 | self.direction = direction 68 | 69 | def update(self): 70 | self.x += PROJECTILE_SPEED * math.cos(self.direction) 71 | self.y += PROJECTILE_SPEED * math.sin(self.direction) 72 | 73 | def draw(self, display): 74 | display.pixel(int(self.x), int(self.y)) 75 | 76 | def is_on_screen(self): 77 | return self.x >= 0 and self.x < WIDTH and self.y >= 0 and self.y < HEIGHT 78 | 79 | def has_hit(self, player): 80 | xdiff = self.x - player.x 81 | ydiff = self.y - player.y 82 | 83 | sqdist = xdiff ** 2 + ydiff ** 2 84 | return sqdist < player.size ** 2 85 | 86 | 87 | class Player: 88 | def __init__(self, index, x, y, size, colour, pad): 89 | self.index = index 90 | self.x = x 91 | self.y = y 92 | self.direction = math.radians(START_ANGLE) 93 | self.size = size 94 | self.colour = colour 95 | self.pad = pad 96 | 97 | self.projectiles = [] 98 | self.was_hit = False 99 | self.score = 0 100 | 101 | def fire(self): 102 | if len(self.projectiles) < PROJECTILE_LIMIT: 103 | self.projectiles.append(Projectile(self.x, self.y, self.direction)) 104 | 105 | def update(self): 106 | # Read the player's gamepad 107 | button = self.pad.read_buttons() 108 | 109 | if button['L']: 110 | self.direction -= 0.1 111 | 112 | if button['R']: 113 | self.direction += 0.1 114 | 115 | if button['U']: 116 | self.x += PLAYER_SPEED * math.cos(self.direction) 117 | self.y += PLAYER_SPEED * math.sin(self.direction) 118 | 119 | if button['D']: 120 | self.x -= PLAYER_SPEED * math.cos(self.direction) 121 | self.y -= PLAYER_SPEED * math.sin(self.direction) 122 | 123 | # Clamp the player to the screen area 124 | self.x = min(max(self.x, self.size), WIDTH - self.size) 125 | self.y = min(max(self.y, self.size), HEIGHT - self.size) 126 | 127 | if button['A']: 128 | self.fire() 129 | 130 | new_proj = [] 131 | for projectile in self.projectiles: 132 | projectile.update() 133 | if projectile.is_on_screen(): 134 | new_proj.append(projectile) 135 | 136 | self.projectiles = new_proj 137 | 138 | def hit(self): 139 | self.was_hit = True 140 | self.pad.set_leds(0b1111) 141 | 142 | def draw(self, display): 143 | x, y = int(self.x), int(self.y) 144 | display.set_pen(WHITE) 145 | display.circle(x, y, self.size) 146 | display.set_pen(BLACK) if not self.was_hit else display.set_pen(RED) 147 | display.circle(x, y, self.size - 1) 148 | self.was_hit = False 149 | self.pad.set_leds(self.pad.address_code()) 150 | 151 | # Draw the direction line in our colour 152 | display.set_pen(self.colour) 153 | display.line(x, y, 154 | int(self.x + (LINE_LENGTH * math.cos(self.direction))), 155 | int(self.y + (LINE_LENGTH * math.sin(self.direction)))) 156 | 157 | # Draw the projectiles in our colour 158 | display.set_pen(self.colour) 159 | for p in self.projectiles: 160 | p.draw(display) 161 | 162 | # Draw our score at the bottom of the screen 163 | display.set_pen(self.colour) 164 | display.text(f"P{self.index + 1}: {self.score}", 15 + self.index * 60, 227, WIDTH, 1) 165 | 166 | def check_hits(self, players): 167 | for other in players: 168 | if other is not self: 169 | for projectile in self.projectiles: 170 | if projectile.has_hit(other): 171 | other.hit() 172 | self.score += 1 173 | 174 | 175 | # Create a player for each connected QwSTPad 176 | for i in range(len(ADDRESSES)): 177 | try: 178 | p = PLAYERS[i] 179 | pad = QwSTPad(i2c, ADDRESSES[i]) 180 | players.append(Player(i, p.x, p.y, PLAYER_RADIUS, p.colour, pad)) 181 | print(f"P{i + 1}: Connected") 182 | except OSError: 183 | print(f"P{i + 1}: Not Connected") 184 | 185 | if len(players) == 0: 186 | print("No QwSTPads connected ... Exiting") 187 | raise SystemExit 188 | 189 | print("QwSTPads connected ... Starting") 190 | 191 | # Wrap the code in a try block, to catch any exceptions (including KeyboardInterrupt) 192 | try: 193 | # Loop forever 194 | while True: 195 | if not complete: 196 | # Update all players (and their projectiles) 197 | for p in players: 198 | try: 199 | p.update() 200 | # Handle QwSTPads being disconnected unexpectedly 201 | except OSError: 202 | print(f"P{p.index + 1}: Disconnected ... Exiting") 203 | raise SystemExit 204 | 205 | # Check if any projectiles have hit players 206 | for p in players: 207 | p.check_hits(players) 208 | 209 | # Check if any player has reached the score target 210 | if p.score >= SCORE_TARGET: 211 | complete = True 212 | 213 | # Clear the screen 214 | display.set_pen(BLACK) 215 | display.clear() 216 | 217 | # Draw a grid for the background 218 | display.set_pen(GREY) 219 | for x in range(10, WIDTH, GRID_SPACING): 220 | for y in range(10, HEIGHT, GRID_SPACING): 221 | display.pixel(x, y) 222 | 223 | # Draw players 224 | for p in players: 225 | p.draw(display) 226 | 227 | if complete: 228 | # Draw banner shadow 229 | display.set_pen(BLACK) 230 | display.rectangle(4, 94, WIDTH, 50) 231 | 232 | # Draw banner 233 | display.set_pen(GREEN) 234 | display.rectangle(0, 90, WIDTH, 50) 235 | 236 | # Draw text shadow 237 | display.set_pen(BLACK) 238 | display.text("Game Complete!", 10 + TEXT_SHADOW, 105 + TEXT_SHADOW, WIDTH, 3) 239 | 240 | # Draw text 241 | display.set_pen(WHITE) 242 | display.text("Game Complete!", 10, 105, WIDTH, 3) 243 | 244 | # Update the screen 245 | presto.update() 246 | 247 | # Turn off the LEDs of any connected QwSTPads 248 | finally: 249 | for p in players: 250 | try: 251 | p.pad.clear_leds() 252 | except OSError: 253 | pass 254 | -------------------------------------------------------------------------------- /examples/s4m_ur4i-pirate-characters.16bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/s4m_ur4i-pirate-characters.16bpp -------------------------------------------------------------------------------- /examples/s4m_ur4i-pirate-tilemap.16bpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/s4m_ur4i-pirate-tilemap.16bpp -------------------------------------------------------------------------------- /examples/sd_basic.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | An example to show how to setup the SD Card slot and list the files in the root dir 4 | 5 | ''' 6 | import sdcard 7 | import machine 8 | import uos 9 | 10 | try: 11 | # Setup for SD Card 12 | sd_spi = machine.SPI(0, sck=machine.Pin(34, machine.Pin.OUT), mosi=machine.Pin(35, machine.Pin.OUT), miso=machine.Pin(36, machine.Pin.OUT)) 13 | sd = sdcard.SDCard(sd_spi, machine.Pin(39)) 14 | 15 | # Mount the SD to the directory 'sd' 16 | uos.mount(sd, "/sd") 17 | 18 | # Print a list of the files on the root of the SD 19 | print(uos.listdir('sd')) 20 | 21 | except OSError as e: 22 | print(e) 23 | -------------------------------------------------------------------------------- /examples/sd_image.py: -------------------------------------------------------------------------------- 1 | import sdcard 2 | import machine 3 | import uos 4 | import jpegdec 5 | from presto import Presto 6 | 7 | # Setup for the Presto display 8 | presto = Presto() 9 | display = presto.display 10 | WIDTH, HEIGHT = display.get_bounds() 11 | 12 | j = jpegdec.JPEG(display) 13 | 14 | # Couple of pens for clearing the screen and text. 15 | WHITE = display.create_pen(255, 255, 255) 16 | BLACK = display.create_pen(0, 0, 0) 17 | 18 | try: 19 | # Setup for SD Card 20 | sd_spi = machine.SPI(0, sck=machine.Pin(34, machine.Pin.OUT), mosi=machine.Pin(35, machine.Pin.OUT), miso=machine.Pin(36, machine.Pin.OUT)) 21 | sd = sdcard.SDCard(sd_spi, machine.Pin(39)) 22 | 23 | # Mount the SD to the directory 'sd' 24 | uos.mount(sd, "/sd") 25 | except OSError as e: 26 | print(e) 27 | 28 | 29 | while True: 30 | # Clear the screen 31 | display.set_pen(WHITE) 32 | display.clear() 33 | 34 | # Add some text 35 | display.set_pen(BLACK) 36 | display.text("Image loaded from SD:", 10, 10, WIDTH, 2) 37 | 38 | # Open the JPEG file 39 | j.open_file("sd/micro_sd.jpg") 40 | 41 | # Decode the JPEG 42 | j.decode(10, 40, jpegdec.JPEG_SCALE_FULL, dither=True) 43 | 44 | # Finally we update the screen with our changes :) 45 | presto.update() 46 | -------------------------------------------------------------------------------- /examples/sd_rw.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | SD Card example showing the writing and reading back of a text file. 4 | 5 | ''' 6 | import sdcard 7 | import machine 8 | import uos 9 | 10 | try: 11 | # Setup for SD Card 12 | sd_spi = machine.SPI(0, sck=machine.Pin(34, machine.Pin.OUT), mosi=machine.Pin(35, machine.Pin.OUT), miso=machine.Pin(36, machine.Pin.OUT)) 13 | sd = sdcard.SDCard(sd_spi, machine.Pin(39)) 14 | 15 | # Mount the SD to the directory 'sd' 16 | uos.mount(sd, "/sd") 17 | 18 | # Open the file in write mode, if the file doesn't exist it will create it 19 | f = open('sd/presto.txt', 'w') 20 | # Write the string to the file we opened above 21 | f.write("Hello from Pimoroni Presto!") 22 | # Once we're done writing to the file we can close it. 23 | f.close() 24 | 25 | # Now lets read the file back and print the content to the terminal! 26 | # This opens the file in read only mode 27 | f = open('sd/presto.txt') 28 | 29 | # Read the content and store it in a variable 30 | data = f.read() 31 | 32 | # And now we're done, close the file 33 | f.close() 34 | 35 | # Finally, we'll print the content we stored in our data variable 36 | print("Here is the content from our file:\n\n{}".format(data)) 37 | except OSError as e: 38 | print(e) 39 | -------------------------------------------------------------------------------- /examples/secrets.py: -------------------------------------------------------------------------------- 1 | WIFI_SSID = "" 2 | WIFI_PASSWORD = "" -------------------------------------------------------------------------------- /examples/sensor-stick-temperature.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-20.0, 16.67), (-20.0, 12.22), (-15.56, 7.78), (-15.56, 16.67), (-20.0, 16.67)], [(-11.11, 16.67), (-11.11, 3.33), (-6.67, -1.11), (-6.67, 16.67), (-11.11, 16.67)], [(-2.22, 16.67), (-2.22, -1.11), (2.22, 3.39), (2.22, 16.67), (-2.22, 16.67)], [(6.67, 16.67), (6.67, 3.39), (11.11, -1.06), (11.11, 16.67), (6.67, 16.67)], [(15.56, 16.67), (15.56, -5.56), (20.0, -10.0), (20.0, 16.67), (15.56, 16.67)], [(-20.0, 5.17), (-20.0, -1.11), (-4.44, -16.67), (4.44, -7.78), (20.0, -23.33), (20.0, -17.06), (4.44, -1.5), (-4.44, -10.39), (-20.0, 5.17)]] 2 | # NAME Temperature 3 | # DESC Display data from your Multi Sensor Stick! 4 | 5 | from presto import Presto 6 | from breakout_bme280 import BreakoutBME280 7 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 8 | import machine 9 | 10 | # Setup for the Presto display 11 | presto = Presto(ambient_light=True) 12 | display = presto.display 13 | WIDTH, HEIGHT = display.get_bounds() 14 | 15 | CX = WIDTH // 2 16 | CY = HEIGHT // 2 17 | 18 | # Colours 19 | BLACK = display.create_pen(0, 0, 0) 20 | hue = 0.05 21 | BACKGROUND = display.create_pen_hsv(hue, 0.8, 1.0) # We'll use this one for the background. 22 | FOREGROUND = display.create_pen_hsv(hue, 0.5, 1.0) # Slightly lighter for foreground elements. 23 | TEXT_COLOUR = display.create_pen_hsv(hue, 0.2, 1.0) 24 | 25 | # Pico Vector 26 | vector = PicoVector(display) 27 | vector.set_antialiasing(ANTIALIAS_BEST) 28 | t = Transform() 29 | 30 | vector.set_font("Roboto-Medium.af", 96) 31 | vector.set_font_letter_spacing(100) 32 | vector.set_font_word_spacing(100) 33 | vector.set_transform(t) 34 | 35 | 36 | def show_message(text): 37 | display.set_pen(BACKGROUND) 38 | display.clear() 39 | display.set_pen(FOREGROUND) 40 | display.text(f"{text}", 5, 10, WIDTH, 2) 41 | presto.update() 42 | 43 | 44 | # Setup for the i2c and bme sensor 45 | try: 46 | bme = BreakoutBME280(machine.I2C()) 47 | except RuntimeError: 48 | while True: 49 | show_message("No Multi-Sensor stick detected!\n\nConnect and try again.") 50 | 51 | 52 | class Widget(object): 53 | def __init__(self, x, y, w, h, radius=10, text_size=42): 54 | self.x = x 55 | self.y = y 56 | self.w = w 57 | self.h = h 58 | self.r = radius 59 | self.text = None 60 | self.size = text_size 61 | self.title = None 62 | 63 | self.widget = Polygon() 64 | self.widget.rectangle(self.x, self.y, self.w, self.h, (self.r, self.r, self.r, self.r)) 65 | 66 | def draw(self): 67 | 68 | display.set_pen(FOREGROUND) 69 | vector.draw(self.widget) 70 | 71 | if self.text: 72 | display.set_pen(TEXT_COLOUR) 73 | vector.set_font_size(self.size) 74 | x, y, w, h = vector.measure_text(self.text) 75 | tx = int((self.x + self.w // 2) - (w // 2)) 76 | ty = int((self.y + self.h // 2) + (h // 2)) + 5 77 | vector.text(self.text, tx, ty) 78 | 79 | if self.title: 80 | display.set_pen(TEXT_COLOUR) 81 | vector.set_font_size(14) 82 | x, y, w, h = vector.measure_text(self.title) 83 | tx = int((self.x + self.w // 2) - (w // 2)) 84 | ty = self.y + 15 85 | vector.text(self.title, tx, ty) 86 | 87 | def set_label(self, text): 88 | self.text = text 89 | 90 | def set_title(self, title): 91 | self.title = title 92 | 93 | 94 | # We'll use a rect with rounded corners for the background. 95 | background_rect = Polygon() 96 | background_rect.rectangle(0, 0, WIDTH, HEIGHT, (10, 10, 10, 10)) 97 | 98 | widgets = [ 99 | Widget(10, 7, WIDTH - 20, HEIGHT // 2 - 5, 10, 82), # Temperature 100 | Widget(10, CY + 10, (WIDTH // 2) - 15, HEIGHT // 2 - 17, 10, 26), # Pressure 101 | Widget(CX + 5, CY + 10, (WIDTH // 2) - 15, HEIGHT // 2 - 17, 10, 52) # Humidity 102 | ] 103 | 104 | 105 | widgets[0].set_title("Temperature") 106 | widgets[1].set_title("Pressure") 107 | widgets[2].set_title("Humidity") 108 | 109 | while True: 110 | 111 | # Clear screen and draw our background rectangle 112 | display.set_pen(BLACK) 113 | display.clear() 114 | display.set_pen(BACKGROUND) 115 | vector.draw(background_rect) 116 | 117 | # Get readings and format strings 118 | try: 119 | reading = bme.read() 120 | except RuntimeError: 121 | while True: 122 | show_message("Failed to get reading from BME280.\n\nCheck connection and reset :)") 123 | 124 | temp_string = f"{reading[0]:.1f}C" 125 | pressure_string = f"{reading[1] // 100:.0f} hPa" 126 | humidity_string = f"{reading[2]:.0f}%" 127 | 128 | # Update the widget labels 129 | widgets[0].set_label(temp_string) 130 | widgets[1].set_label(pressure_string) 131 | widgets[2].set_label(humidity_string) 132 | 133 | # Draw all of our widgets to the display 134 | for w in widgets: 135 | w.draw() 136 | 137 | # Update the screen so we can see our changes 138 | presto.update() 139 | -------------------------------------------------------------------------------- /examples/splash.py: -------------------------------------------------------------------------------- 1 | from picovector import ANTIALIAS_FAST, PicoVector, Polygon, Transform 2 | from presto import Presto 3 | import time 4 | import math 5 | 6 | presto = Presto(ambient_light=True) 7 | display = presto.display 8 | WIDTH, HEIGHT = display.get_bounds() 9 | 10 | # Couple of colours for use later 11 | BLUE = display.create_pen(28, 181, 202) 12 | WHITE = display.create_pen(255, 255, 255) 13 | RED = display.create_pen(230, 60, 45) 14 | ORANGE = display.create_pen(245, 165, 4) 15 | GREEN = display.create_pen(9, 185, 120) 16 | PINK = display.create_pen(250, 125, 180) 17 | PURPLE = display.create_pen(118, 95, 210) 18 | BLACK = display.create_pen(0, 0, 0) 19 | 20 | CX = WIDTH // 2 21 | CY = HEIGHT // 2 22 | 23 | # Set our initial pen colour 24 | pen = display.create_pen_hsv(1.0, 1.0, 1.0) 25 | 26 | # Pico Vector 27 | vector = PicoVector(display) 28 | vector.set_antialiasing(ANTIALIAS_FAST) 29 | 30 | t = Transform() 31 | t2 = Transform() 32 | vector.set_transform(t) 33 | 34 | circle_inner_1 = Polygon() 35 | circle_inner_2 = Polygon() 36 | circle_inner_3 = Polygon() 37 | circle_inner_4 = Polygon() 38 | 39 | offset = 20 40 | 41 | circle_inner_1.circle(0 - offset, 0 - offset, 110) 42 | circle_inner_2.circle(WIDTH + offset, 0 - offset, 110) 43 | circle_inner_3.circle(WIDTH + offset, HEIGHT + offset, 110) 44 | circle_inner_4.circle(0 - offset, HEIGHT + offset, 110) 45 | 46 | vector.set_font("cherry-hq.af", 54) 47 | vector.set_font_letter_spacing(100) 48 | vector.set_font_word_spacing(100) 49 | vector.set_transform(t) 50 | 51 | while True: 52 | 53 | tick = time.ticks_ms() / 100.0 54 | sin = math.sin(tick) 55 | text_y = (CY - 40) + int(sin * 4) 56 | 57 | display.set_pen(BLACK) 58 | display.clear() 59 | 60 | vector.set_transform(t) 61 | t.rotate(1, (CX, CY)) 62 | 63 | display.set_pen(PINK) 64 | vector.draw(circle_inner_4) 65 | 66 | display.set_pen(ORANGE) 67 | vector.draw(circle_inner_3) 68 | 69 | display.set_pen(BLUE) 70 | vector.draw(circle_inner_2) 71 | 72 | display.set_pen(PURPLE) 73 | vector.draw(circle_inner_1) 74 | 75 | vector.set_transform(t2) 76 | display.set_pen(WHITE) 77 | vector.set_font_size(32) 78 | vector.text("Hey Presto!", CX - 64, text_y) 79 | 80 | vector.set_font_size(18) 81 | vector.text("Welcome to the Presto Beta! :)", CX - 95, CY - 20) 82 | 83 | vector.set_font_size(15) 84 | vector.text("This unit is pre-loaded with MicroPython", CX - 105, CY + 10) 85 | vector.text("Plug in and play!", CX - 41, CY + 25) 86 | 87 | presto.update() 88 | -------------------------------------------------------------------------------- /examples/stop_watch.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-6.68, -21.11), (-6.68, -25.56), (6.65, -25.56), (6.65, -21.11), (-6.68, -21.11)], [(-2.24, 3.33), (2.21, 3.33), (2.21, -10.0), (-2.24, -10.0), (-2.24, 3.33)], [(-0.01, 21.11), (-1.81, 21.04), (-4.31, 20.66), (-6.31, 20.12), (-7.74, 19.58), (-9.01, 18.96), (-11.19, 17.66), (-13.09, 16.19), (-14.09, 15.26), (-15.63, 13.54), (-16.45, 12.45), (-17.69, 10.44), (-18.44, 8.94), (-19.27, 6.56), (-19.81, 4.04), (-19.98, 2.23), (-19.96, -0.37), (-19.74, -2.24), (-19.26, -4.37), (-18.84, -5.66), (-18.07, -7.44), (-17.05, -9.29), (-15.94, -10.92), (-14.5, -12.61), (-12.53, -14.43), (-11.26, -15.37), (-8.95, -16.74), (-7.69, -17.32), (-5.89, -17.99), (-4.53, -18.37), (-2.66, -18.71), (0.04, -18.89), (1.4, -18.84), (3.65, -18.56), (6.55, -17.8), (8.48, -17.01), (10.52, -15.93), (12.5, -14.59), (15.65, -17.67), (18.76, -14.56), (15.65, -11.44), (16.86, -9.69), (17.83, -7.96), (18.93, -5.34), (19.54, -3.11), (19.83, -1.43), (19.98, 0.78), (19.94, 2.46), (19.72, 4.37), (19.45, 5.71), (18.93, 7.47), (17.98, 9.76), (17.27, 11.08), (16.21, 12.74), (15.1, 14.15), (13.38, 15.9), (11.77, 17.21), (10.65, 17.99), (8.92, 18.98), (6.84, 19.9), (5.13, 20.45), (3.04, 20.88), (0.04, 21.11)], [(-0.01, 16.67), (2.64, 16.46), (4.57, 16.02), (6.73, 15.18), (8.41, 14.23), (9.35, 13.55), (11.26, 11.83), (12.39, 10.51), (13.25, 9.3), (14.08, 7.8), (14.79, 6.03), (15.25, 4.22), (15.54, 1.33), (15.39, -1.12), (14.81, -3.73), (14.22, -5.27), (13.69, -6.34), (12.58, -8.05), (11.48, -9.37), (10.3, -10.53), (8.13, -12.18), (6.44, -13.09), (4.91, -13.69), (2.83, -14.2), (1.19, -14.4), (-0.74, -14.43), (-2.3, -14.29), (-4.61, -13.79), (-7.19, -12.74), (-9.09, -11.55), (-11.13, -9.77), (-12.29, -8.46), (-13.7, -6.36), (-14.42, -4.87), (-15.1, -2.81), (-15.42, -1.16), (-15.57, 1.11), (-15.42, 3.35), (-14.88, 5.81), (-13.86, 8.28), (-12.89, 9.89), (-11.05, 12.07), (-9.71, 13.29), (-8.51, 14.17), (-6.09, 15.48), (-4.09, 16.16), (-2.86, 16.43), (-0.07, 16.67)], [(-0.01, 1.11)]] 2 | # NAME Stopwatch 3 | # DESC A simple stopwatch timer 4 | 5 | import datetime 6 | import time 7 | 8 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 9 | from presto import Presto 10 | from touch import Button 11 | 12 | presto = Presto() 13 | display = presto.display 14 | WIDTH, HEIGHT = display.get_bounds() 15 | 16 | CX = WIDTH // 2 17 | CY = HEIGHT // 2 18 | 19 | # Couple of colours for use later 20 | BLACK = display.create_pen(0, 0, 0) 21 | hue = 0.09 22 | background = display.create_pen_hsv(hue, 0.8, 1.0) 23 | foreground = display.create_pen_hsv(hue, 0.5, 1.0) 24 | text_colour = display.create_pen_hsv(hue, 0.2, 1.0) 25 | 26 | # We'll need this for the touch element of the screen 27 | touch = presto.touch 28 | 29 | # Pico Vector 30 | vector = PicoVector(display) 31 | vector.set_antialiasing(ANTIALIAS_BEST) 32 | t = Transform() 33 | 34 | vector.set_font("Roboto-Medium.af", 54) 35 | vector.set_font_letter_spacing(100) 36 | vector.set_font_word_spacing(100) 37 | vector.set_transform(t) 38 | 39 | # Touch buttons 40 | start_button = Button(3, HEIGHT - 55, CX - 5, 49) 41 | stop_button = Button((WIDTH - CX) + 1, HEIGHT - 55, CX - 5, 49) 42 | 43 | start = Polygon() 44 | start.rectangle(*start_button.bounds, (10, 10, 10, 10)) 45 | 46 | stop = Polygon() 47 | stop.rectangle(*stop_button.bounds, (10, 10, 10, 10)) 48 | 49 | outline = Polygon() 50 | outline.rectangle(5, 20, WIDTH - 10, HEIGHT - 100, (10, 10, 10, 10), 2) 51 | 52 | # We'll use a rect with rounded corners for the background. 53 | background_rect = Polygon() 54 | background_rect.rectangle(0, 0, WIDTH, HEIGHT, (10, 10, 10, 10)) 55 | 56 | 57 | class StopWatch(object): 58 | 59 | def __init__(self): 60 | self.start_time = 0 61 | self.elapsed = 0 62 | self.running = False 63 | 64 | def start(self): 65 | 66 | self.running = True 67 | if self.start_time: 68 | self.start_time = time.ticks_ms() - self.elapsed 69 | else: 70 | self.start_time = time.ticks_ms() 71 | 72 | def stop(self): 73 | 74 | self.running = False 75 | 76 | def reset(self): 77 | 78 | self.start_time = 0 79 | self.elapsed = 0 80 | 81 | def return_string(self): 82 | 83 | if self.running: 84 | self.elapsed = time.ticks_ms() - self.start_time 85 | 86 | dt = datetime.timedelta(hours=0, minutes=0, seconds=0, milliseconds=self.elapsed) 87 | 88 | return str(dt)[:10] 89 | 90 | 91 | timer = StopWatch() 92 | 93 | while True: 94 | 95 | display.set_pen(BLACK) 96 | display.clear() 97 | 98 | display.set_pen(background) 99 | vector.draw(background_rect) 100 | 101 | display.set_pen(foreground) 102 | vector.draw(start) 103 | 104 | display.set_pen(foreground) 105 | vector.draw(stop) 106 | 107 | display.set_pen(text_colour) 108 | vector.draw(outline) 109 | 110 | vector.set_font_size(32) 111 | if timer.elapsed and timer.running is False: 112 | vector.text("Resume", start_button.bounds[0] + 8, start_button.bounds[1] + 33) 113 | else: 114 | vector.text("Start", start_button.bounds[0] + 27, start_button.bounds[1] + 33) 115 | 116 | if timer.running: 117 | vector.text("Stop", stop_button.bounds[0] + 30, stop_button.bounds[1] + 33) 118 | else: 119 | vector.text("Reset", stop_button.bounds[0] + 23, stop_button.bounds[1] + 33) 120 | 121 | if start_button.is_pressed() and timer.running is False: 122 | timer.start() 123 | 124 | if stop_button.is_pressed(): 125 | if timer.running: 126 | timer.stop() 127 | while stop_button.is_pressed(): 128 | touch.poll() 129 | else: 130 | timer.reset() 131 | 132 | time_string = timer.return_string() 133 | vector.set_font_size(54) 134 | vector.text(f"{time_string}", 10, 110) 135 | 136 | presto.update() 137 | -------------------------------------------------------------------------------- /examples/tomato.py: -------------------------------------------------------------------------------- 1 | # ICON [[(-0.01, 10.0), (2.24, 9.81), (3.75, 9.44), (4.75, 9.07), (6.15, 8.34), (7.06, 7.74), (8.39, 6.6), (9.5, 5.36), (10.75, 3.41), (11.53, 1.42), (11.85, -0.05), (11.98, -1.31), (11.96, -2.9), (11.84, -4.02), (11.32, -6.07), (10.89, -7.15), (9.93, -8.8), (8.7, -10.29), (7.47, -11.42), (6.58, -12.08), (4.99, -12.96), (4.04, -13.34), (2.41, -13.78), (0.04, -14.0), (-0.01, -2.0), (-8.51, 6.5), (-7.0, 7.73), (-5.84, 8.47), (-4.65, 9.08), (-3.52, 9.48), (-2.22, 9.79), (-0.06, 10.0)], [(-0.01, 18.0), (-2.77, 17.82), (-5.22, 17.33), (-6.81, 16.84), (-9.0, 15.88), (-10.82, 14.83), (-12.37, 13.73), (-13.38, 12.88), (-14.8, 11.47), (-16.53, 9.28), (-17.71, 7.33), (-18.44, 5.84), (-18.93, 4.56), (-19.44, 2.82), (-19.69, 1.62), (-19.93, -0.24), (-19.98, -3.03), (-19.82, -4.82), (-19.36, -7.14), (-18.78, -8.99), (-18.18, -10.41), (-16.87, -12.77), (-15.61, -14.52), (-14.53, -15.77), (-13.03, -17.19), (-11.75, -18.19), (-9.49, -19.6), (-7.63, -20.48), (-5.31, -21.29), (-2.8, -21.81), (-1.17, -21.97), (0.56, -22.0), (2.17, -21.89), (4.17, -21.57), (5.78, -21.15), (6.98, -20.74), (8.54, -20.07), (10.61, -18.95), (12.5, -17.62), (14.56, -15.73), (15.71, -14.38), (16.82, -12.81), (18.11, -10.45), (18.75, -8.94), (19.3, -7.26), (19.84, -4.56), (19.98, -2.76), (19.98, -1.18), (19.8, 0.82), (19.39, 2.89), (18.67, 5.12), (17.97, 6.73), (16.56, 9.2), (15.45, 10.7), (13.58, 12.69), (11.88, 14.09), (10.45, 15.06), (9.16, 15.79), (6.7, 16.87), (5.01, 17.38), (2.25, 17.88), (0.04, 18.0)], [(-0.01, 14.0), (1.92, 13.89), (4.04, 13.53), (6.12, 12.86), (7.5, 12.21), (8.55, 11.61), (9.86, 10.67), (11.86, 8.81), (13.1, 7.28), (14.15, 5.61), (14.75, 4.36), (15.29, 2.88), (15.79, 0.68), (15.96, -0.91), (15.96, -3.16), (15.72, -5.08), (15.17, -7.27), (14.67, -8.56), (13.98, -9.93), (12.87, -11.61), (11.38, -13.32), (9.85, -14.68), (8.89, -15.38), (7.49, -16.22), (5.15, -17.22), (3.7, -17.61), (1.72, -17.92), (0.3, -18.0), (-1.89, -17.9), (-4.1, -17.52), (-5.31, -17.17), (-7.19, -16.38), (-8.13, -15.88), (-9.61, -14.88), (-10.93, -13.76), (-12.29, -12.34), (-13.3, -11.02), (-14.5, -8.96), (-15.22, -7.16), (-15.61, -5.71), (-15.89, -4.07), (-15.99, -1.35), (-15.76, 0.93), (-15.4, 2.54), (-14.53, 4.89), (-13.63, 6.52), (-12.25, 8.38), (-10.86, 9.82), (-9.67, 10.82), (-8.31, 11.75), (-6.16, 12.84), (-5.16, 13.21), (-2.83, 13.77), (-1.35, 13.95), (-0.06, 14.0)], [(-0.01, -2.0)]] 2 | # NAME Tomato Timer 3 | # DESC A pomodoro timer for your Presto 4 | 5 | import time 6 | from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform 7 | from presto import Presto, Buzzer 8 | from touch import Button 9 | 10 | presto = Presto(ambient_light=True) 11 | display = presto.display 12 | WIDTH, HEIGHT = display.get_bounds() 13 | 14 | # Centre points for the display 15 | CX = WIDTH // 2 16 | CY = HEIGHT // 2 17 | 18 | # We'll need this for the touch element of the screen 19 | touch = presto.touch 20 | 21 | # Pico Vector 22 | vector = PicoVector(display) 23 | vector.set_antialiasing(ANTIALIAS_BEST) 24 | t = Transform() 25 | 26 | vector.set_font("Roboto-Medium.af", 96) 27 | vector.set_font_letter_spacing(100) 28 | vector.set_font_word_spacing(100) 29 | vector.set_transform(t) 30 | 31 | BLACK = display.create_pen(0, 0, 0) 32 | 33 | # Setup the buzzer. The Presto piezo is on pin 43. 34 | buzzer = Buzzer(43) 35 | 36 | 37 | class Tomato(object): 38 | def __init__(self): 39 | 40 | self.hue = 0 41 | self.background = display.create_pen_hsv(self.hue, 0.8, 1.0) # We'll use this one for the background. 42 | self.foreground = display.create_pen_hsv(self.hue, 0.5, 1.0) # Slightly lighter for foreground elements. 43 | self.text_colour = display.create_pen_hsv(self.hue, 0.2, 1.0) 44 | 45 | # Time constants. 46 | # Feel free to change these to ones that work better for you. 47 | self.TASK = 25 * 60 48 | self.SHORT = 10 * 60 49 | self.LONG = 30 * 60 50 | 51 | # How long the completion alert should be played (seconds) 52 | self.alert_duration = 2 53 | self.alert_start_time = 0 54 | 55 | self.is_break_time = False 56 | self.start_time = 0 57 | self.tasks_complete = 0 58 | self.running = False 59 | self.paused = False 60 | self.time_elapsed = 0 61 | self.current_timer = self.TASK 62 | 63 | # We'll use a rect with rounded corners for the background. 64 | self.background_rect = Polygon() 65 | self.background_rect.rectangle(0, 0, WIDTH, HEIGHT, (10, 10, 10, 10)) 66 | 67 | self.foreground_rect = Polygon() 68 | self.foreground_rect.rectangle(10, 10, WIDTH - 20, HEIGHT - 120, (10, 10, 10, 10)) 69 | 70 | # Touch button 71 | self.start_button = Button(CX // 2, HEIGHT - 75, CX, 50) 72 | x, y, w, h = self.start_button.bounds 73 | self.start = Polygon() 74 | self.start.rectangle(x, y, w, h, (10, 10, 10, 10)) 75 | self.start_shadow = Polygon() 76 | self.start_shadow.rectangle(x + 3, y + 3, w, h, (10, 10, 10, 10)) 77 | 78 | # Update the pens for the background, foreground and text elements based on the given hue. 79 | def update_pens(self, hue): 80 | self.hue = hue 81 | self.background = display.create_pen_hsv(self.hue, 0.8, 1.0) 82 | self.foreground = display.create_pen_hsv(self.hue, 0.5, 1.0) 83 | self.text_colour = display.create_pen_hsv(self.hue, 0.2, 1.0) 84 | 85 | def draw(self): 86 | 87 | # Clear the screen 88 | display.set_pen(BLACK) 89 | display.clear() 90 | 91 | # Draw the background rect with rounded corners 92 | display.set_pen(self.background) 93 | vector.draw(self.background_rect) 94 | 95 | # Draw the foreground rect, this is where we will show the time remaining. 96 | display.set_pen(self.foreground) 97 | vector.draw(self.foreground_rect) 98 | 99 | # Draw the button with drop shadow 100 | vector.draw(self.start_shadow) 101 | display.set_pen(self.text_colour) 102 | vector.draw(self.start) 103 | 104 | # Draw the button text, the text shown here depends on the current timer state 105 | vector.set_font_size(24) 106 | display.set_pen(self.foreground) 107 | 108 | if not self.running: 109 | if self.is_break_time: 110 | vector.text("Start Break", self.start_button.bounds[0] + 8, self.start_button.bounds[1] + 33) 111 | else: 112 | vector.text("Start Task", self.start_button.bounds[0] + 13, self.start_button.bounds[1] + 33) 113 | elif self.running and self.paused: 114 | vector.text("Resume", self.start_button.bounds[0] + 24, self.start_button.bounds[1] + 33) 115 | else: 116 | vector.text("Pause", self.start_button.bounds[0] + 32, self.start_button.bounds[1] + 33) 117 | 118 | display.set_pen(self.text_colour) 119 | text = self.return_string() 120 | vector.set_font_size(96) 121 | tx = int(CX - (205 // 2)) 122 | ty = int(CY - (58 // 2)) + 10 123 | vector.text(text, tx, ty) 124 | 125 | def run(self): 126 | self.stop_buzzer() 127 | 128 | if self.is_break_time: 129 | if self.tasks_complete < 4: 130 | self.current_timer = self.SHORT 131 | self.update_pens(0.55) 132 | else: 133 | self.current_timer = self.LONG 134 | self.update_pens(0.55) 135 | else: 136 | self.current_timer = self.TASK 137 | self.update_pens(0.0) 138 | 139 | if not self.running: 140 | self.reset() 141 | self.running = True 142 | self.start_time = time.time() 143 | elif self.running and not self.paused: 144 | self.paused = True 145 | elif self.running and self.paused: 146 | self.paused = False 147 | self.start_time = time.time() - self.time_elapsed 148 | 149 | def reset(self): 150 | self.start_time = 0 151 | self.time_elapsed = 0 152 | 153 | def start_buzzer(self): 154 | self.alert_start_time = time.time() 155 | buzzer.set_tone(150) 156 | 157 | def stop_buzzer(self): 158 | buzzer.set_tone(-1) 159 | self.alert_start_time = 0 160 | 161 | def update(self): 162 | 163 | if time.time() - self.alert_start_time >= self.alert_duration: 164 | self.stop_buzzer() 165 | 166 | if self.running and not self.paused: 167 | 168 | self.time_elapsed = time.time() - self.start_time 169 | 170 | if self.time_elapsed >= self.current_timer: 171 | self.running = False 172 | self.start_buzzer() 173 | if not self.is_break_time: 174 | if self.tasks_complete < 4: 175 | self.tasks_complete += 1 176 | else: 177 | self.tasks_complete = 0 178 | self.is_break_time = not self.is_break_time 179 | 180 | # Return the remaining time formatted in a string for displaying with vector text. 181 | def return_string(self): 182 | minutes, seconds = divmod(self.current_timer - self.time_elapsed, 60) 183 | return f"{minutes:02d}:{seconds:02d}" 184 | 185 | def pressed(self): 186 | return self.start_button.is_pressed() 187 | 188 | 189 | # Create an instance of our timer object 190 | timer = Tomato() 191 | 192 | while True: 193 | 194 | if timer.pressed(): 195 | while timer.pressed(): 196 | touch.poll() 197 | timer.run() 198 | 199 | timer.draw() 200 | timer.update() 201 | presto.update() 202 | -------------------------------------------------------------------------------- /examples/touch_buttons.py: -------------------------------------------------------------------------------- 1 | from presto import Presto 2 | from touch import Button 3 | 4 | presto = Presto() 5 | display = presto.display 6 | WIDTH, HEIGHT = display.get_bounds() 7 | 8 | # Couple of colours for use later 9 | WHITE = display.create_pen(255, 255, 255) 10 | RED = display.create_pen(230, 60, 45) 11 | GREEN = display.create_pen(9, 185, 120) 12 | BLACK = display.create_pen(0, 0, 0) 13 | 14 | # We'll need this for the touch element of the screen 15 | touch = presto.touch 16 | 17 | CX = WIDTH // 2 18 | CY = HEIGHT // 2 19 | BUTTON_WIDTH = 100 20 | BUTTON_HEIGHT = 50 21 | 22 | # Create a touch button and set the touch region. 23 | # Button(x, y, width, height) 24 | button_1 = Button(10, 35, BUTTON_WIDTH, BUTTON_HEIGHT) 25 | button_2 = Button(10, 95, BUTTON_WIDTH, BUTTON_HEIGHT) 26 | button_3 = Button(10, 155, BUTTON_WIDTH, BUTTON_HEIGHT) 27 | 28 | while True: 29 | 30 | # Check for touch changes 31 | touch.poll() 32 | 33 | # Clear the screen and set the background colour 34 | display.set_pen(WHITE) 35 | display.clear() 36 | display.set_pen(BLACK) 37 | 38 | # Title text 39 | display.text("Touch Button Demo", 23, 7) 40 | 41 | # Finding the state of a touch button is much the same as a physical button 42 | # calling '.is_pressed()' on your button object will return True or False 43 | if button_1.is_pressed(): 44 | display.set_pen(GREEN) 45 | display.text("You Pressed\nButton 1!", (button_1.x + button_1.w) + 20, button_1.y + 3, 100, 2) 46 | else: 47 | display.set_pen(RED) 48 | 49 | # We've defined our touch Button object but we need a visual representation of it for the user! 50 | # We can use the '.bounds' property of our Button object to set the X, Y, WIDTH and HEIGHT 51 | display.rectangle(*button_1.bounds) 52 | 53 | if button_2.is_pressed(): 54 | display.set_pen(GREEN) 55 | display.text("You Pressed\nButton 2!", (button_2.x + button_2.w) + 20, button_2.y + 3, 100, 2) 56 | else: 57 | display.set_pen(RED) 58 | 59 | display.rectangle(*button_2.bounds) 60 | 61 | if button_3.is_pressed(): 62 | display.set_pen(GREEN) 63 | display.text("You Pressed\nButton 3!", (button_3.x + button_3.w) + 20, button_3.y + 3, 100, 2) 64 | else: 65 | display.set_pen(RED) 66 | 67 | display.rectangle(*button_3.bounds) 68 | 69 | # Finally, we update the screen so we can see our changes! 70 | presto.update() 71 | -------------------------------------------------------------------------------- /examples/touchscreen_dots.py: -------------------------------------------------------------------------------- 1 | import time 2 | from random import randint 3 | from presto import Presto 4 | 5 | # Setup for the Presto display 6 | presto = Presto(ambient_light=True) 7 | display = presto.display 8 | WIDTH, HEIGHT = display.get_bounds() 9 | 10 | # Couple of colours for use later 11 | BLUE = display.create_pen(28, 181, 202) 12 | WHITE = display.create_pen(255, 255, 255) 13 | RED = display.create_pen(230, 60, 45) 14 | ORANGE = display.create_pen(245, 165, 4) 15 | GREEN = display.create_pen(9, 185, 120) 16 | PINK = display.create_pen(250, 125, 180) 17 | PURPLE = display.create_pen(118, 95, 210) 18 | BLACK = display.create_pen(0, 0, 0) 19 | 20 | COLOURS = [BLUE, RED, ORANGE, GREEN, PINK, PURPLE] 21 | 22 | # We'll need this for the touch element of the screen 23 | touch = presto.touch 24 | 25 | 26 | class DOT(object): 27 | def __init__(self, x, y, size, colour): 28 | self.x = x 29 | self.y = y 30 | self.size = size 31 | self.colour = colour 32 | 33 | 34 | # We'll store any dots in this array 35 | dots = [] 36 | 37 | 38 | while True: 39 | 40 | # Poll the touch so we can see if anything changed since the last time 41 | touch.poll() 42 | 43 | # If the user is touching the screen we'll do the following 44 | if touch.state: 45 | # set the base size to 10 for a single tap 46 | s = 10 47 | # While the user is still touching the screen, we'll make the dot bigger! 48 | while touch.state: 49 | touch.poll() 50 | time.sleep(0.02) 51 | s += 0.5 52 | # Once the user stops touching the screen 53 | # We'll add a new dot with the x and y position of the touch, 54 | # size and a random colour! 55 | dots.append(DOT(touch.x, touch.y, round(s), COLOURS[randint(0, len(COLOURS) - 1)])) 56 | 57 | # Clear the screen 58 | display.set_pen(WHITE) 59 | display.clear() 60 | 61 | # Draw the dots in our array 62 | for dot in dots: 63 | display.set_pen(dot.colour) 64 | display.circle(dot.x, dot.y, dot.size) 65 | 66 | # Some text to let the user know what to do! 67 | display.set_pen(BLACK) 68 | display.text("Tap the screen!", 45, 110, WIDTH, 2) 69 | 70 | # Finally we update the screen with our changes :) 71 | presto.update() 72 | -------------------------------------------------------------------------------- /examples/touchscreen_dots_vector.py: -------------------------------------------------------------------------------- 1 | import time 2 | from random import randint 3 | 4 | from picovector import ANTIALIAS_FAST, PicoVector, Polygon 5 | from presto import Presto 6 | 7 | # Setup for the Presto display 8 | presto = Presto(ambient_light=True) 9 | display = presto.display 10 | WIDTH, HEIGHT = display.get_bounds() 11 | 12 | # Pico Vector 13 | vector = PicoVector(display) 14 | vector.set_antialiasing(ANTIALIAS_FAST) 15 | 16 | # Couple of colours for use later 17 | BLUE = display.create_pen(28, 181, 202) 18 | WHITE = display.create_pen(255, 255, 255) 19 | RED = display.create_pen(230, 60, 45) 20 | ORANGE = display.create_pen(245, 165, 4) 21 | GREEN = display.create_pen(9, 185, 120) 22 | PINK = display.create_pen(250, 125, 180) 23 | PURPLE = display.create_pen(118, 95, 210) 24 | BLACK = display.create_pen(0, 0, 0) 25 | 26 | COLOURS = [BLUE, RED, ORANGE, GREEN, PINK, PURPLE] 27 | 28 | # We'll need this for the touch element of the screen 29 | touch = presto.touch 30 | 31 | 32 | class DOT(object): 33 | def __init__(self, x, y, size, colour): 34 | self.x = x 35 | self.y = y 36 | self.size = size 37 | self.colour = colour 38 | 39 | 40 | # We'll store any dots in this array 41 | dots = [] 42 | 43 | 44 | while True: 45 | 46 | # Poll the touch so we can see if anything changed since the last time 47 | touch.poll() 48 | 49 | # If the user is touching the screen we'll do the following 50 | if touch.state: 51 | # set the base size to 10 for a single tap 52 | s = 10 53 | # While the user is still touching the screen, we'll make the dot bigger! 54 | while touch.state: 55 | touch.poll() 56 | time.sleep(0.02) 57 | s += 0.5 58 | # Once the user stops touching the screen 59 | # We'll add a new dot with the x and y position of the touch, 60 | # size and a random colour! 61 | dots.append(DOT(touch.x, touch.y, round(s), COLOURS[randint(0, len(COLOURS) - 1)])) 62 | 63 | # Clear the screen 64 | display.set_pen(WHITE) 65 | display.clear() 66 | 67 | # Draw the dots in our array 68 | for dot in dots: 69 | v = Polygon() 70 | display.set_pen(dot.colour) 71 | v.circle(dot.x, dot.y, dot.size) 72 | vector.draw(v) 73 | 74 | # Some text to let the user know what to do! 75 | display.set_pen(BLACK) 76 | display.text("Tap the screen!", 45, 110, WIDTH, 2) 77 | 78 | # Finally we update the screen with our changes :) 79 | presto.update() 80 | -------------------------------------------------------------------------------- /examples/vector_clock_full.py: -------------------------------------------------------------------------------- 1 | # ICON [[(6.59, 7.4), (9.39, 4.6), (1.99, -2.8), (1.99, -12.0), (-2.01, -12.0), (-2.01, -1.2), (6.59, 7.4)], [(-0.01, 18.0), (-2.77, 17.82), (-5.22, 17.33), (-6.81, 16.84), (-9.0, 15.88), (-10.82, 14.83), (-12.37, 13.73), (-13.38, 12.88), (-14.8, 11.47), (-16.53, 9.28), (-17.71, 7.33), (-18.44, 5.84), (-18.93, 4.56), (-19.44, 2.82), (-19.69, 1.62), (-19.93, -0.24), (-19.98, -3.03), (-19.82, -4.82), (-19.36, -7.14), (-18.78, -8.99), (-18.18, -10.41), (-16.87, -12.77), (-15.61, -14.52), (-14.53, -15.77), (-13.03, -17.19), (-11.75, -18.19), (-9.49, -19.6), (-7.63, -20.48), (-5.31, -21.29), (-2.8, -21.81), (-1.17, -21.97), (0.56, -22.0), (2.17, -21.89), (4.17, -21.57), (5.78, -21.15), (6.98, -20.74), (8.54, -20.07), (10.61, -18.95), (12.5, -17.62), (14.56, -15.73), (15.71, -14.38), (16.82, -12.81), (18.11, -10.45), (18.75, -8.94), (19.3, -7.26), (19.84, -4.56), (19.98, -2.76), (19.98, -1.18), (19.8, 0.82), (19.39, 2.89), (18.67, 5.12), (17.97, 6.73), (16.56, 9.2), (15.45, 10.7), (13.58, 12.69), (11.88, 14.09), (10.45, 15.06), (9.16, 15.79), (6.7, 16.87), (5.01, 17.38), (2.25, 17.88), (0.04, 18.0)], [(-0.01, -2.0)], [(-0.01, 14.0), (1.87, 13.9), (3.1, 13.72), (4.92, 13.27), (6.57, 12.65), (7.85, 12.0), (9.95, 10.56), (11.26, 9.38), (12.07, 8.51), (13.65, 6.4), (14.66, 4.51), (15.18, 3.17), (15.75, 0.9), (15.93, -0.48), (15.99, -2.41), (15.75, -4.87), (15.46, -6.25), (14.87, -8.01), (14.31, -9.23), (13.28, -10.95), (12.42, -12.08), (11.05, -13.55), (9.91, -14.56), (8.05, -15.86), (6.45, -16.69), (4.54, -17.39), (3.36, -17.68), (1.71, -17.92), (0.44, -18.0), (-1.44, -17.94), (-2.97, -17.75), (-5.29, -17.16), (-6.71, -16.59), (-8.07, -15.88), (-10.05, -14.49), (-11.32, -13.34), (-12.48, -12.07), (-13.2, -11.12), (-14.1, -9.69), (-14.72, -8.44), (-15.33, -6.79), (-15.77, -4.91), (-15.98, -3.05), (-16.0, -1.85), (-15.9, -0.04), (-15.44, 2.39), (-14.95, 3.89), (-14.24, 5.45), (-13.24, 7.08), (-12.22, 8.41), (-11.39, 9.31), (-10.07, 10.49), (-8.57, 11.58), (-7.27, 12.32), (-5.83, 12.96), (-4.11, 13.51), (-1.72, 13.91), (-0.06, 14.0)]] 2 | # NAME Analog Clock 3 | # DESC Full resolution vector clock! 4 | import presto 5 | 6 | import time 7 | import gc 8 | 9 | from picovector import PicoVector, Polygon, Transform, ANTIALIAS_X16 10 | 11 | 12 | presto = presto.Presto(full_res=True) 13 | 14 | display = presto.display 15 | 16 | vector = PicoVector(display) 17 | t = Transform() 18 | vector.set_transform(t) 19 | vector.set_antialiasing(ANTIALIAS_X16) 20 | 21 | RED = display.create_pen(200, 0, 0) 22 | BLACK = display.create_pen(0, 0, 0) 23 | DARKGREY = display.create_pen(100, 100, 100) 24 | GREY = display.create_pen(200, 200, 200) 25 | WHITE = display.create_pen(255, 255, 255) 26 | 27 | """ 28 | # Redefine colours for a Blue clock 29 | RED = display.create_pen(200, 0, 0) 30 | BLACK = display.create_pen(135, 159, 169) 31 | GREY = display.create_pen(10, 40, 50) 32 | WHITE = display.create_pen(14, 60, 76) 33 | """ 34 | 35 | WIDTH, HEIGHT = display.get_bounds() 36 | MIDDLE = (int(WIDTH / 2), int(HEIGHT / 2)) 37 | 38 | hub = Polygon() 39 | hub.circle(int(WIDTH / 2), int(HEIGHT / 2), 5) 40 | 41 | face = Polygon() 42 | face.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2)) 43 | 44 | tick_mark = Polygon() 45 | tick_mark.rectangle(int(WIDTH / 2) - 3, 10, 6, int(HEIGHT / 48)) 46 | 47 | hour_mark = Polygon() 48 | hour_mark.rectangle(int(WIDTH / 2) - 5, 10, 10, int(HEIGHT / 10)) 49 | 50 | minute_hand_length = int(HEIGHT / 2) - int(HEIGHT / 24) 51 | minute_hand = Polygon() 52 | minute_hand.path((-5, -minute_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -minute_hand_length)) 53 | 54 | hour_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) 55 | hour_hand = Polygon() 56 | hour_hand.path((-5, -hour_hand_length), (-10, int(HEIGHT / 16)), (10, int(HEIGHT / 16)), (5, -hour_hand_length)) 57 | 58 | second_hand_length = int(HEIGHT / 2) - int(HEIGHT / 8) 59 | second_hand = Polygon() 60 | second_hand.path((-2, -second_hand_length), (-2, int(HEIGHT / 8)), (2, int(HEIGHT / 8)), (2, -second_hand_length)) 61 | 62 | print(time.localtime()) 63 | 64 | last_second = None 65 | 66 | display.set_pen(BLACK) 67 | display.clear() 68 | display.set_pen(WHITE) 69 | vector.draw(face) 70 | 71 | 72 | while True: 73 | t_start = time.ticks_ms() 74 | year, month, day, hour, minute, second, _, _ = time.localtime() 75 | 76 | if last_second == second: 77 | time.sleep_ms(10) 78 | continue 79 | 80 | last_second = second 81 | 82 | t.reset() 83 | 84 | display.set_pen(WHITE) 85 | display.circle(int(WIDTH / 2), int(HEIGHT / 2), int(HEIGHT / 2) - 4) 86 | 87 | display.set_pen(GREY) 88 | 89 | for a in range(60): 90 | t.rotate(360 / 60.0 * a, MIDDLE) 91 | t.translate(0, 2) 92 | vector.draw(tick_mark) 93 | t.reset() 94 | 95 | for a in range(12): 96 | t.rotate(360 / 12.0 * a, MIDDLE) 97 | t.translate(0, 2) 98 | vector.draw(hour_mark) 99 | t.reset() 100 | 101 | display.set_pen(GREY) 102 | 103 | x, y = MIDDLE 104 | y += 5 105 | 106 | angle_minute = minute * 6 107 | angle_minute += second / 10.0 108 | t.rotate(angle_minute, MIDDLE) 109 | t.translate(x, y) 110 | vector.draw(minute_hand) 111 | t.reset() 112 | 113 | angle_hour = (hour % 12) * 30 114 | angle_hour += minute / 2 115 | t.rotate(angle_hour, MIDDLE) 116 | t.translate(x, y) 117 | vector.draw(hour_hand) 118 | t.reset() 119 | 120 | angle_second = second * 6 121 | t.rotate(angle_second, MIDDLE) 122 | t.translate(x, y) 123 | vector.draw(second_hand) 124 | t.reset() 125 | 126 | display.set_pen(BLACK) 127 | 128 | for a in range(60): 129 | t.rotate(360 / 60.0 * a, MIDDLE) 130 | vector.draw(tick_mark) 131 | t.reset() 132 | 133 | for a in range(12): 134 | t.rotate(360 / 12.0 * a, MIDDLE) 135 | vector.draw(hour_mark) 136 | t.reset() 137 | 138 | x, y = MIDDLE 139 | 140 | t.rotate(angle_minute, MIDDLE) 141 | t.translate(x, y) 142 | vector.draw(minute_hand) 143 | t.reset() 144 | 145 | t.rotate(angle_hour, MIDDLE) 146 | t.translate(x, y) 147 | vector.draw(hour_hand) 148 | t.reset() 149 | 150 | display.set_pen(RED) 151 | t.rotate(angle_second, MIDDLE) 152 | t.translate(x, y) 153 | vector.draw(second_hand) 154 | 155 | t.reset() 156 | vector.draw(hub) 157 | 158 | presto.update() 159 | gc.collect() 160 | 161 | t_end = time.ticks_ms() 162 | print(f"Took {t_end - t_start}ms") 163 | -------------------------------------------------------------------------------- /examples/word_clock.py: -------------------------------------------------------------------------------- 1 | # ICON [[(6.59, 7.4), (9.39, 4.6), (1.99, -2.8), (1.99, -12.0), (-2.01, -12.0), (-2.01, -1.2), (6.59, 7.4)], [(-0.01, 18.0), (-2.77, 17.82), (-5.22, 17.33), (-6.81, 16.84), (-9.0, 15.88), (-10.82, 14.83), (-12.37, 13.73), (-13.38, 12.88), (-14.8, 11.47), (-16.53, 9.28), (-17.71, 7.33), (-18.44, 5.84), (-18.93, 4.56), (-19.44, 2.82), (-19.69, 1.62), (-19.93, -0.24), (-19.98, -3.03), (-19.82, -4.82), (-19.36, -7.14), (-18.78, -8.99), (-18.18, -10.41), (-16.87, -12.77), (-15.61, -14.52), (-14.53, -15.77), (-13.03, -17.19), (-11.75, -18.19), (-9.49, -19.6), (-7.63, -20.48), (-5.31, -21.29), (-2.8, -21.81), (-1.17, -21.97), (0.56, -22.0), (2.17, -21.89), (4.17, -21.57), (5.78, -21.15), (6.98, -20.74), (8.54, -20.07), (10.61, -18.95), (12.5, -17.62), (14.56, -15.73), (15.71, -14.38), (16.82, -12.81), (18.11, -10.45), (18.75, -8.94), (19.3, -7.26), (19.84, -4.56), (19.98, -2.76), (19.98, -1.18), (19.8, 0.82), (19.39, 2.89), (18.67, 5.12), (17.97, 6.73), (16.56, 9.2), (15.45, 10.7), (13.58, 12.69), (11.88, 14.09), (10.45, 15.06), (9.16, 15.79), (6.7, 16.87), (5.01, 17.38), (2.25, 17.88), (0.04, 18.0)], [(-0.01, -2.0)], [(-0.01, 14.0), (1.87, 13.9), (3.1, 13.72), (4.92, 13.27), (6.57, 12.65), (7.85, 12.0), (9.95, 10.56), (11.26, 9.38), (12.07, 8.51), (13.65, 6.4), (14.66, 4.51), (15.18, 3.17), (15.75, 0.9), (15.93, -0.48), (15.99, -2.41), (15.75, -4.87), (15.46, -6.25), (14.87, -8.01), (14.31, -9.23), (13.28, -10.95), (12.42, -12.08), (11.05, -13.55), (9.91, -14.56), (8.05, -15.86), (6.45, -16.69), (4.54, -17.39), (3.36, -17.68), (1.71, -17.92), (0.44, -18.0), (-1.44, -17.94), (-2.97, -17.75), (-5.29, -17.16), (-6.71, -16.59), (-8.07, -15.88), (-10.05, -14.49), (-11.32, -13.34), (-12.48, -12.07), (-13.2, -11.12), (-14.1, -9.69), (-14.72, -8.44), (-15.33, -6.79), (-15.77, -4.91), (-15.98, -3.05), (-16.0, -1.85), (-15.9, -0.04), (-15.44, 2.39), (-14.95, 3.89), (-14.24, 5.45), (-13.24, 7.08), (-12.22, 8.41), (-11.39, 9.31), (-10.07, 10.49), (-8.57, 11.58), (-7.27, 12.32), (-5.83, 12.96), (-4.11, 13.51), (-1.72, 13.91), (-0.06, 14.0)]] 2 | # NAME Word Clock 3 | # DESC No hands! 4 | 5 | import time 6 | 7 | import machine 8 | import ntptime 9 | import pngdec 10 | from presto import Presto 11 | 12 | # Setup for the Presto display 13 | presto = Presto() 14 | display = presto.display 15 | WIDTH, HEIGHT = display.get_bounds() 16 | BLACK = display.create_pen(0, 0, 0) 17 | WHITE = display.create_pen(200, 200, 200) 18 | GRAY = display.create_pen(30, 30, 30) 19 | 20 | # Clear the screen before the network call is made 21 | display.set_pen(BLACK) 22 | display.clear() 23 | presto.update() 24 | 25 | # Length of time between updates in minutes. 26 | UPDATE_INTERVAL = 15 27 | 28 | rtc = machine.RTC() 29 | time_string = None 30 | words = ["it", "d", "is", "m", "about", "lv", "half", "c", "quarter", "b", "to", "past", "n", "one", 31 | "two", "three", "four", "five", "six", "eleven", "ten", "d", "qdh", "eight", "seven", "rm", "twelve", "nine", "p", "ncsnheypresto", "O'Clock", "agrdsp"] 32 | 33 | 34 | def show_message(text): 35 | display.set_pen(BLACK) 36 | display.clear() 37 | display.set_pen(WHITE) 38 | display.text(f"{text}", 5, 10, WIDTH, 2) 39 | presto.update() 40 | 41 | 42 | show_message("Connecting...") 43 | 44 | try: 45 | wifi = presto.connect() 46 | except ValueError as e: 47 | while True: 48 | show_message(e) 49 | except ImportError as e: 50 | while True: 51 | show_message(e) 52 | 53 | # Set the correct time using the NTP service. 54 | try: 55 | ntptime.settime() 56 | except OSError: 57 | while True: 58 | show_message("Unable to get time.\n\nCheck your network try again.") 59 | 60 | 61 | def approx_time(hours, minutes): 62 | nums = {0: "twelve", 1: "one", 2: "two", 63 | 3: "three", 4: "four", 5: "five", 6: "six", 64 | 7: "seven", 8: "eight", 9: "nine", 10: "ten", 65 | 11: "eleven", 12: "twelve"} 66 | 67 | if hours == 12: 68 | hours = 0 69 | if minutes > 0 and minutes < 8: 70 | return "it is about " + nums[hours] + " O'Clock" 71 | elif minutes >= 8 and minutes < 23: 72 | return "it is about quarter past " + nums[hours] 73 | elif minutes >= 23 and minutes < 38: 74 | return "it is about half past " + nums[hours] 75 | elif minutes >= 38 and minutes < 53: 76 | return "it is about quarter to " + nums[hours + 1] 77 | else: 78 | return "it is about " + nums[hours + 1] + " O'Clock" 79 | 80 | 81 | def update(): 82 | global time_string 83 | # grab the current time from the ntp server and update the Pico RTC 84 | try: 85 | ntptime.settime() 86 | except OSError: 87 | print("Unable to contact NTP server") 88 | 89 | current_t = rtc.datetime() 90 | time_string = approx_time(current_t[4] - 12 if current_t[4] > 12 else current_t[4], current_t[5]) 91 | 92 | # Splits the string into an array of words for displaying later 93 | time_string = time_string.split() 94 | 95 | print(time_string) 96 | 97 | 98 | def draw(): 99 | global time_string 100 | display.set_font("bitmap8") 101 | 102 | display.set_layer(1) 103 | 104 | # Clear the screen 105 | display.set_pen(BLACK) 106 | display.clear() 107 | 108 | default_x = 25 109 | x = default_x 110 | y = 35 111 | 112 | line_space = 20 113 | letter_space = 15 114 | margin = 25 115 | scale = 1 116 | spacing = 1 117 | 118 | for word in words: 119 | 120 | if word in time_string: 121 | display.set_pen(WHITE) 122 | else: 123 | display.set_pen(GRAY) 124 | 125 | for letter in word: 126 | text_length = display.measure_text(letter, scale, spacing) 127 | if not x + text_length <= WIDTH - margin: 128 | y += line_space 129 | x = default_x 130 | 131 | display.text(letter.upper(), x, y, WIDTH, scale=scale, spacing=spacing) 132 | x += letter_space 133 | 134 | presto.update() 135 | 136 | 137 | # Set the background in layer 0 138 | # This means we don't need to decode the image every frame 139 | 140 | display.set_layer(0) 141 | 142 | try: 143 | p = pngdec.PNG(display) 144 | 145 | p.open_file("wordclock_background.png") 146 | p.decode(0, 0) 147 | except OSError: 148 | display.set_pen(BLACK) 149 | display.clear() 150 | 151 | 152 | while True: 153 | update() 154 | draw() 155 | time.sleep(60 * UPDATE_INTERVAL) 156 | -------------------------------------------------------------------------------- /examples/wordclock_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pimoroni/presto/de571d79ca590a7c03f1c07da9cf1cf2201aa69b/examples/wordclock_background.png -------------------------------------------------------------------------------- /modules/c/presto/micropython.cmake: -------------------------------------------------------------------------------- 1 | add_library(usermod_presto INTERFACE) 2 | 3 | get_filename_component(REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}../../../../" ABSOLUTE) 4 | 5 | target_sources(usermod_presto INTERFACE 6 | ${CMAKE_CURRENT_LIST_DIR}/presto.c 7 | ${CMAKE_CURRENT_LIST_DIR}/presto.cpp 8 | ${REPO_ROOT}/drivers/st7701/st7701.cpp 9 | ) 10 | pico_generate_pio_header(usermod_presto ${REPO_ROOT}/drivers/st7701/st7701_parallel.pio) 11 | pico_generate_pio_header(usermod_presto ${REPO_ROOT}/drivers/st7701/st7701_timing.pio) 12 | pico_generate_pio_header(usermod_presto ${REPO_ROOT}/drivers/st7701/st7701_palette.pio) 13 | 14 | set_source_files_properties(${REPO_ROOT}/drivers/st7701/st7701.cpp PROPERTIES COMPILE_OPTIONS "-O2;-fgcse-after-reload;-floop-interchange;-fpeel-loops;-fpredictive-commoning;-fsplit-paths;-ftree-loop-distribute-patterns;-ftree-loop-distribution;-ftree-vectorize;-ftree-partial-pre;-funswitch-loops") 15 | 16 | target_include_directories(usermod_presto INTERFACE 17 | ${CMAKE_CURRENT_LIST_DIR} 18 | ${PIMORONI_PICO_PATH}/libraries/pico_graphics/ 19 | ${REPO_ROOT}/drivers/st7701/ 20 | ${REPO_ROOT}/micropython/modules 21 | ) 22 | 23 | target_link_libraries(usermod INTERFACE usermod_presto) -------------------------------------------------------------------------------- /modules/c/presto/presto.c: -------------------------------------------------------------------------------- 1 | #include "presto.h" 2 | 3 | 4 | /***** Methods *****/ 5 | 6 | MP_DEFINE_CONST_FUN_OBJ_1(Presto___del___obj, Presto___del__); 7 | MP_DEFINE_CONST_FUN_OBJ_2(Presto_update_obj, Presto_update); 8 | MP_DEFINE_CONST_FUN_OBJ_KW(Presto_partial_update_obj, 5, Presto_partial_update); 9 | MP_DEFINE_CONST_FUN_OBJ_2(Presto_set_backlight_obj, Presto_set_backlight); 10 | MP_DEFINE_CONST_FUN_OBJ_2(Presto_auto_ambient_leds_obj, Presto_auto_ambient_leds); 11 | 12 | MP_DEFINE_CONST_FUN_OBJ_KW(Presto_set_led_rgb_obj, 5, Presto_set_led_rgb); 13 | MP_DEFINE_CONST_FUN_OBJ_KW(Presto_set_led_hsv_obj, 3, Presto_set_led_hsv); 14 | 15 | /***** Binding of Methods *****/ 16 | 17 | static const mp_rom_map_elem_t Presto_locals_dict_table[] = { 18 | { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&Presto___del___obj) }, 19 | { MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&Presto_update_obj) }, 20 | { MP_ROM_QSTR(MP_QSTR_partial_update), MP_ROM_PTR(&Presto_partial_update_obj) }, 21 | { MP_ROM_QSTR(MP_QSTR_set_backlight), MP_ROM_PTR(&Presto_set_backlight_obj) }, 22 | { MP_ROM_QSTR(MP_QSTR_auto_ambient_leds), MP_ROM_PTR(&Presto_auto_ambient_leds_obj) }, 23 | { MP_ROM_QSTR(MP_QSTR_set_led_rgb), MP_ROM_PTR(&Presto_set_led_rgb_obj) }, 24 | { MP_ROM_QSTR(MP_QSTR_set_led_hsv), MP_ROM_PTR(&Presto_set_led_hsv_obj) }, 25 | 26 | { MP_ROM_QSTR(MP_QSTR_WIDTH), MP_ROM_INT(WIDTH/2) }, 27 | { MP_ROM_QSTR(MP_QSTR_HEIGHT), MP_ROM_INT(HEIGHT/2) }, 28 | { MP_ROM_QSTR(MP_QSTR_FULL_WIDTH), MP_ROM_INT(WIDTH) }, 29 | { MP_ROM_QSTR(MP_QSTR_FULL_HEIGHT), MP_ROM_INT(HEIGHT) }, 30 | }; 31 | 32 | static MP_DEFINE_CONST_DICT(Presto_locals_dict, Presto_locals_dict_table); 33 | 34 | 35 | MP_DEFINE_CONST_OBJ_TYPE( 36 | Presto_type, 37 | MP_QSTR_Presto, 38 | MP_TYPE_FLAG_NONE, 39 | make_new, Presto_make_new, 40 | buffer, Presto_get_framebuffer, 41 | locals_dict, (mp_obj_dict_t*)&Presto_locals_dict 42 | ); 43 | 44 | /***** Globals Table *****/ 45 | static const mp_map_elem_t presto_globals_table[] = { 46 | { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR__presto) }, 47 | { MP_OBJ_NEW_QSTR(MP_QSTR_Presto), (mp_obj_t)&Presto_type }, 48 | }; 49 | 50 | static MP_DEFINE_CONST_DICT(mp_module_presto_globals, presto_globals_table); 51 | 52 | /***** Module Definition *****/ 53 | 54 | const mp_obj_module_t presto_user_cmodule = { 55 | .base = { &mp_type_module }, 56 | .globals = (mp_obj_dict_t*)&mp_module_presto_globals, 57 | }; 58 | 59 | MP_REGISTER_MODULE(MP_QSTR__presto, presto_user_cmodule); -------------------------------------------------------------------------------- /modules/c/presto/presto.h: -------------------------------------------------------------------------------- 1 | // Include MicroPython API. 2 | #include "py/runtime.h" 3 | 4 | /***** Constants *****/ 5 | static const uint BACKLIGHT = 45; 6 | 7 | static const int WIDTH = 480; 8 | static const int HEIGHT = 480; 9 | static const uint LCD_CLK = 26; 10 | static const uint LCD_CS = 28; 11 | static const uint LCD_DAT = 27; 12 | static const uint LCD_DC = -1; 13 | static const uint LCD_D0 = 1; 14 | static const uint LED_DAT = 33; 15 | 16 | /***** Extern of Class Definition *****/ 17 | extern const mp_obj_type_t Presto_type; 18 | 19 | /***** Extern of Class Methods *****/ 20 | extern mp_obj_t Presto_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); 21 | extern mp_obj_t Presto_update(mp_obj_t self_in, mp_obj_t graphics_in); 22 | extern mp_obj_t Presto_partial_update(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); 23 | extern mp_int_t Presto_get_framebuffer(mp_obj_t self_in, mp_buffer_info_t *bufinfo, mp_uint_t flags); 24 | extern mp_obj_t Presto_set_backlight(mp_obj_t self_in, mp_obj_t brightness); 25 | extern mp_obj_t Presto_auto_ambient_leds(mp_obj_t self_in, mp_obj_t enable); 26 | 27 | extern mp_obj_t Presto_set_led_rgb(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); 28 | extern mp_obj_t Presto_set_led_hsv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args); 29 | 30 | extern mp_obj_t Presto___del__(mp_obj_t self_in); -------------------------------------------------------------------------------- /modules/py_frozen/boot.py: -------------------------------------------------------------------------------- 1 | import cppmem 2 | # Switch C++ memory allocations to use MicroPython's heap 3 | cppmem.set_mode(cppmem.MICROPYTHON) 4 | -------------------------------------------------------------------------------- /modules/py_frozen/ezwifi.py: -------------------------------------------------------------------------------- 1 | import network 2 | import asyncio 3 | from micropython import const 4 | 5 | 6 | class LogLevel: 7 | INFO = const(0) 8 | WARNING = const(1) 9 | ERROR = const(2) 10 | 11 | text = ["info", "warning", "error"] 12 | 13 | 14 | class EzWiFi: 15 | def __init__(self, **kwargs): 16 | get = kwargs.get 17 | 18 | self._last_error = None 19 | 20 | self._verbose = get("verbose", False) 21 | 22 | self._events = { 23 | "connected": get("connected", None), 24 | "failed": get("failed", None), 25 | "info": get("info", None), 26 | "warning": get("warning", None), 27 | "error": get("error", None) 28 | } 29 | 30 | self._if = network.WLAN(network.STA_IF) 31 | self._if.active(True) 32 | # self._if.config(pm=0xa11140) # TODO: ??? 33 | self._statuses = {v: k[5:] for (k, v) in network.__dict__.items() if k.startswith("STAT_")} 34 | 35 | def _callback(self, handler_name, *args, **kwargs): 36 | handler = self._events.get(handler_name, None) 37 | if callable(handler): 38 | handler(self, *args, **kwargs) 39 | return True 40 | return False 41 | 42 | def _log(self, text, level=LogLevel.INFO): 43 | self._callback(LogLevel.text[level], text) or (self._verbose and print(text)) 44 | 45 | def on(self, handler_name, handler=None): 46 | if handler_name not in self._events.keys(): 47 | raise ValueError(f"Invalid event: \"{handler_name}\"") 48 | 49 | def _on(handler): 50 | self._events[handler_name] = handler 51 | 52 | if handler is not None: 53 | _on(handler) 54 | return True 55 | 56 | return _on 57 | 58 | def error(self): 59 | if self._last_error is not None: 60 | return self._last_error, self._statuses[self._last_error] 61 | return None, None 62 | 63 | async def connect(self, ssid=None, password=None, timeout=60, retries=10): 64 | if not ssid and not password: 65 | ssid, password = self._secrets() 66 | elif password and not ssid: 67 | raise ValueError("ssid required!") 68 | 69 | for retry in range(retries): 70 | self._log(f"Connecting to {ssid} (Attempt {retry + 1})") 71 | try: 72 | self._if.connect(ssid, password) 73 | if await asyncio.wait_for(self._wait_for_connection(), timeout): 74 | return True 75 | 76 | except asyncio.TimeoutError: 77 | self._log("Attempt failed...", LogLevel.WARNING) 78 | 79 | self._callback("failed") 80 | return False 81 | 82 | async def _wait_for_connection(self): 83 | while not self._if.isconnected(): 84 | self._log("Connecting...") 85 | status = self._if.status() 86 | if status in [network.STAT_CONNECT_FAIL, network.STAT_NO_AP_FOUND, network.STAT_WRONG_PASSWORD]: 87 | self._log(f"Connection failed with: {self._statuses[status]}", LogLevel.ERROR) 88 | self._last_error = status 89 | return False 90 | await asyncio.sleep_ms(1000) 91 | self._log(f"Connected! IP: {self.ipv4()}") 92 | self._callback("connected") 93 | return True 94 | 95 | def ipv4(self): 96 | return self._if.ipconfig("addr4")[0] 97 | 98 | def ipv6(self): 99 | return self._if.ipconfig("addr6")[0][0] 100 | 101 | def isconnected(self): 102 | return self._if.isconnected() 103 | 104 | def _secrets(self): 105 | try: 106 | from secrets import WIFI_SSID, WIFI_PASSWORD 107 | if not WIFI_SSID: 108 | raise ValueError("secrets.py: WIFI_SSID is empty!") 109 | if not WIFI_PASSWORD: 110 | raise ValueError("secrets.py: WIFI_PASSWORD is empty!") 111 | return WIFI_SSID, WIFI_PASSWORD 112 | except ImportError: 113 | raise ImportError("secrets.py: missing or invalid!") 114 | 115 | 116 | def connect(**kwargs): 117 | return asyncio.get_event_loop().run_until_complete(EzWiFi(**kwargs).connect(retries=kwargs.get("retries", 10))) 118 | -------------------------------------------------------------------------------- /modules/py_frozen/lsm6ds3.py: -------------------------------------------------------------------------------- 1 | # Registers 2 | WHO_AM_I = const(0x0F) 3 | CTRL2_G = const(0x11) 4 | CTRL1_XL = const(0x10) 5 | CTRL10_C = const(0x19) 6 | CTRL3_C = const(0x12) 7 | 8 | # This is the start of the data registers for the Gyro and Accelerometer 9 | # There are 12 Bytes in total starting at 0x23 and ending at 0x2D 10 | OUTX_L_G = const(0x22) 11 | 12 | STEP_COUNTER_L = const(0x4B) 13 | STEP_COUNTER_H = const(0x4C) 14 | TAP_SRC = const(0x1C) 15 | TAP_CFG = const(0x58) 16 | FUNC_SRC1 = const(0x53) 17 | FUNC_SRC2 = const(0x54) 18 | TAP_THS_6D = const(0x59) 19 | FREE_FALL = const(0x5D) 20 | WAKE_UP_THS = const(0x5B) 21 | WAKE_UP_SRC = const(0x1B) 22 | INT_DUR2 = const(0x5A) 23 | 24 | # CONFIG DATA 25 | NORMAL_MODE_104HZ = const(0x40) 26 | NORMAL_MODE_208HZ = const(0x50) 27 | PERFORMANCE_MODE_416HZ = const(0x60) 28 | LOW_POWER_26HZ = const(0x02) 29 | SET_FUNC_EN = const(0xBD) 30 | RESET_STEPS = const(0x02) 31 | TAP_EN_XYZ = const(0x8E) 32 | TAP_THRESHOLD = const(0x02) 33 | DOUBLE_TAP_EN = const(0x80) 34 | DOUBLE_TAP_DUR = const(0x20) 35 | 36 | 37 | def twos_comp(val, bits=16): 38 | mask = 1 << (bits - 1) 39 | 40 | if val & mask: 41 | val &= ~mask 42 | val -= mask 43 | 44 | return val 45 | 46 | 47 | class LSM6DS3: 48 | def __init__(self, i2c, address=0x6A, mode=NORMAL_MODE_104HZ): 49 | self.bus = i2c 50 | self.address = address 51 | self.mode = mode 52 | 53 | # Set gyro mode/enable 54 | self.bus.writeto_mem(self.address, CTRL2_G, bytearray([self.mode])) 55 | 56 | # Set accel mode/enable 57 | self.bus.writeto_mem(self.address, CTRL1_XL, bytearray([self.mode])) 58 | 59 | # Send the reset bit to clear the pedometer step count 60 | self.bus.writeto_mem(self.address, CTRL10_C, bytearray([RESET_STEPS])) 61 | 62 | # Enable sensor functions (Tap, Tilt, Significant Motion) 63 | self.bus.writeto_mem(self.address, CTRL10_C, bytearray([SET_FUNC_EN])) 64 | 65 | # Enable X Y Z Tap Detection 66 | self.bus.writeto_mem(self.address, TAP_CFG, bytearray([TAP_EN_XYZ])) 67 | 68 | # Enable Double tap 69 | self.bus.writeto_mem(self.address, WAKE_UP_THS, bytearray([DOUBLE_TAP_EN])) 70 | 71 | # Set tap threshold 72 | self.bus.writeto_mem(self.address, TAP_THS_6D, bytearray([TAP_THRESHOLD])) 73 | 74 | # Set double tap max time gap 75 | self.bus.writeto_mem(self.address, INT_DUR2, bytearray([DOUBLE_TAP_DUR])) 76 | 77 | def _read_reg(self, reg, size): 78 | return self.bus.readfrom_mem(self.address, reg, size) 79 | 80 | def get_readings(self): 81 | 82 | # Read 12 bytes starting from 0x22. This covers the XYZ data for gyro and accel 83 | data = self._read_reg(OUTX_L_G, 12) 84 | 85 | gx = (data[1] << 8) | data[0] 86 | gx = twos_comp(gx) 87 | 88 | gy = (data[3] << 8) | data[2] 89 | gy = twos_comp(gy) 90 | 91 | gz = (data[5] << 8) | data[4] 92 | gz = twos_comp(gz) 93 | 94 | ax = (data[7] << 8) | data[6] 95 | ax = twos_comp(ax) 96 | 97 | ay = (data[9] << 8) | data[8] 98 | ay = twos_comp(ay) 99 | 100 | az = (data[11] << 8) | data[10] 101 | az = twos_comp(az) 102 | 103 | return ax, ay, az, gx, gy, gz 104 | 105 | def get_step_count(self): 106 | 107 | data = self._read_reg(STEP_COUNTER_L, 2) 108 | steps = (data[1] << 8) | data[0] 109 | steps = twos_comp(steps) 110 | 111 | return steps 112 | 113 | def reset_step_count(self): 114 | 115 | # Send the reset bit 116 | self.bus.writeto_mem(self.address, CTRL10_C, bytearray([RESET_STEPS])) 117 | # Enable functions again 118 | self.bus.writeto_mem(self.address, CTRL10_C, bytearray([SET_FUNC_EN])) 119 | 120 | def tilt_detected(self): 121 | 122 | tilt = self._read_reg(FUNC_SRC1, 1) 123 | tilt = (tilt[0] >> 5) & 0b1 124 | 125 | return tilt 126 | 127 | def sig_motion_detected(self): 128 | 129 | sig = self._read_reg(FUNC_SRC1, 1) 130 | sig = (sig[0] >> 6) & 0b1 131 | 132 | return sig 133 | 134 | def single_tap_detected(self): 135 | 136 | s = self._read_reg(TAP_SRC, 1) 137 | s = (s[0] >> 5) & 0b1 138 | 139 | return s 140 | 141 | def double_tap_detected(self): 142 | 143 | d = self._read_reg(TAP_SRC, 1) 144 | d = (d[0] >> 4) & 0b1 145 | 146 | return d 147 | 148 | def freefall_detected(self): 149 | 150 | fall = self._read_reg(WAKE_UP_SRC, 1) 151 | fall = fall[0] >> 5 152 | 153 | return fall 154 | -------------------------------------------------------------------------------- /modules/py_frozen/presto.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import _presto 3 | from touch import FT6236 4 | from ezwifi import EzWiFi 5 | 6 | from collections import namedtuple 7 | from machine import Pin, PWM 8 | from picographics import PicoGraphics, DISPLAY_PRESTO, DISPLAY_PRESTO_FULL_RES, PEN_RGB565, PEN_P8 9 | 10 | 11 | Touch = namedtuple("touch", ("x", "y", "touched")) 12 | 13 | 14 | class Buzzer: 15 | def __init__(self, pin): 16 | self.pwm = PWM(Pin(pin)) 17 | 18 | def set_tone(self, freq, duty=0.5): 19 | if freq < 50.0: # uh... https://github.com/micropython/micropython/blob/af64c2ddbd758ab6bac0fcca94c66d89046663be/ports/rp2/machine_pwm.c#L105-L119 20 | self.pwm.duty_u16(0) 21 | return False 22 | 23 | self.pwm.freq(freq) 24 | self.pwm.duty_u16(int(65535 * duty)) 25 | return True 26 | 27 | 28 | class Presto(): 29 | NUM_LEDS = 7 30 | LED_PIN = 33 31 | 32 | def __init__(self, full_res=False, palette=False, ambient_light=False, direct_to_fb=False, layers=None): 33 | # WiFi - *must* happen before Presto bringup 34 | # Note: Forces WiFi details to be in secrets.py 35 | self.wifi = EzWiFi() 36 | 37 | # Touch Input 38 | self.touch = FT6236(full_res=full_res) 39 | 40 | # Display Driver & PicoGraphics 41 | if layers is None: 42 | layers = 1 if full_res else 2 43 | pen = PEN_P8 if palette else PEN_RGB565 44 | self.presto = _presto.Presto(full_res=full_res, palette=palette) 45 | self.buffer = None if (full_res and not palette and not direct_to_fb) else memoryview(self.presto) 46 | self.display = PicoGraphics(DISPLAY_PRESTO_FULL_RES if full_res else DISPLAY_PRESTO, buffer=self.buffer, layers=layers, pen_type=pen) 47 | self.width, self.height = self.display.get_bounds() 48 | 49 | if ambient_light: 50 | self.presto.auto_ambient_leds(True) 51 | 52 | @property 53 | def touch_a(self): 54 | return Touch(self.touch.x, self.touch.y, self.touch.state) 55 | 56 | @property 57 | def touch_b(self): 58 | return Touch(self.touch.x2, self.touch.y2, self.touch.state2) 59 | 60 | @property 61 | def touch_delta(self): 62 | return self.touch.distance, self.touch.angle 63 | 64 | async def async_connect(self): 65 | await self.wifi.connect() 66 | 67 | def set_backlight(self, brightness): 68 | self.presto.set_backlight(brightness) 69 | 70 | def auto_ambient_leds(self, enable): 71 | self.presto.auto_ambient_leds(enable) 72 | 73 | def set_led_rgb(self, i, r, g, b): 74 | self.presto.set_led_rgb(i, r, g, b) 75 | 76 | def set_led_hsv(self, i, h, s, v): 77 | self.presto.set_led_hsv(i, h, s, v) 78 | 79 | def connect(self, ssid=None, password=None): 80 | return asyncio.get_event_loop().run_until_complete(self.wifi.connect(ssid, password)) 81 | 82 | def touch_poll(self): 83 | self.touch.poll() 84 | 85 | def update(self): 86 | self.presto.update(self.display) 87 | self.touch.poll() 88 | 89 | def partial_update(self, x, y, w, h): 90 | self.presto.partial_update(self.display, x, y, w, h) 91 | self.touch.poll() 92 | 93 | def clear(self): 94 | self.display.clear() 95 | self.presto.update(self.display) -------------------------------------------------------------------------------- /modules/py_frozen/psram.py: -------------------------------------------------------------------------------- 1 | import io 2 | import struct 3 | import vfs 4 | import micropython 5 | 6 | 7 | PSRAM_BASE = 0x11000000 8 | PSRAM_SIZE = 8 * 1024 * 1024 9 | 10 | 11 | @micropython.viper 12 | def viper_memcpy(dest: ptr8, src: ptr8, num: int) -> int: 13 | for i in range(num): 14 | dest[i] = src[i] 15 | return num 16 | 17 | 18 | @micropython.viper 19 | def viper_memset(dest: ptr8, value: int, num: int): 20 | for i in range(num): 21 | dest[i] = value 22 | 23 | 24 | @micropython.viper 25 | def viper_psram_flush(): 26 | dest: ptr8 = ptr8(0x18000000) # XIP_MAINTENANCE_BASE 27 | for i in range(0, 16 * 1024, 8): 28 | dest[i + 1] = 0 # Clean 29 | dest[i + 0] = 0 # Invalidate (without this, frozen bytecode doesn't work?) 30 | 31 | 32 | class PSRAMBlockDevice: 33 | def __init__(self, size, offset=None, blocksize=256, debug=False): 34 | self.debug = debug 35 | self.blocks, remainder = divmod(size, blocksize) 36 | 37 | if remainder: 38 | raise ValueError("Size should be a multiple of {blocksize:0,d}") 39 | 40 | self.blocksize = blocksize 41 | 42 | if offset is None: 43 | offset = PSRAM_SIZE - size 44 | 45 | self.offset = PSRAM_BASE | offset 46 | 47 | def readblocks(self, block_num, buf, offset=0): 48 | if self.debug: 49 | print(f"PSRAM: readblocks: {block_num} {len(buf)}, {offset}") 50 | viper_memcpy(buf, self.offset + (block_num * self.blocksize) + offset, len(buf)) 51 | 52 | def writeblocks(self, block_num, buf, offset=0): 53 | if self.debug: 54 | print(f"PSRAM: writeblocks: {block_num} {len(buf)}, {offset}") 55 | viper_memcpy(self.offset + (block_num * self.blocksize) + offset, buf, len(buf)) 56 | 57 | def ioctl(self, op, arg): 58 | if self.debug: 59 | print(f"PSRAML: ioctl: {op} {arg}") 60 | if op == 1: # Initialize 61 | return None 62 | if op == 2: # Shutdown 63 | return None 64 | if op == 3: # Sync 65 | viper_psram_flush() 66 | return 0 67 | if op == 4: # Block Count 68 | return self.blocks 69 | if op == 5: # Block Size 70 | return self.blocksize 71 | if op == 6: # Erase 72 | # We don't really *need* to erase blocks but should we? 73 | return 0 74 | 75 | 76 | def mkramfs(size=1024 * 64, mount_point="/ramfs", debug=False): 77 | psram = PSRAMBlockDevice(size, debug=debug) 78 | 79 | try: 80 | fs = vfs.VfsLfs2(psram, progsize=256) 81 | except OSError: 82 | vfs.VfsLfs2.mkfs(psram, progsize=256) 83 | fs = vfs.VfsLfs2(psram, progsize=256) 84 | 85 | vfs.mount(fs, mount_point) 86 | -------------------------------------------------------------------------------- /modules/py_frozen/qwstpad.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from collections import OrderedDict 3 | 4 | from micropython import const 5 | 6 | __version__ = "0.0.1" 7 | 8 | # Constants 9 | NUM_LEDS = const(4) 10 | NUM_BUTTONS = const(10) 11 | 12 | DEFAULT_ADDRESS = const(0x21) 13 | ALT_ADDRESS_1 = const(0x23) 14 | ALT_ADDRESS_2 = const(0x25) 15 | ALT_ADDRESS_3 = const(0x27) 16 | ADDRESSES = (DEFAULT_ADDRESS, ALT_ADDRESS_1, ALT_ADDRESS_2, ALT_ADDRESS_3) 17 | 18 | 19 | class QwSTPad: 20 | # Registers 21 | INPUT_PORT0 = const(0x00) 22 | INPUT_PORT1 = const(0x01) 23 | OUTPUT_PORT0 = const(0x02) 24 | OUTPUT_PORT1 = const(0x03) 25 | POLARITY_PORT0 = const(0x04) 26 | POLARITY_PORT1 = const(0x05) 27 | CONFIGURATION_PORT0 = const(0x06) 28 | CONFIGURATION_PORT1 = const(0x07) 29 | 30 | # Mappings 31 | BUTTON_MAPPING = OrderedDict({'A': 0xE, 'B': 0xC, 'X': 0xF, 'Y': 0xD, 32 | 'U': 0x1, 'D': 0x4, 'L': 0x2, 'R': 0x3, 33 | '+': 0xB, '-': 0x5 34 | }) 35 | LED_MAPPING = (0x6, 0x7, 0x9, 0xA) 36 | 37 | def __init__(self, i2c, address=DEFAULT_ADDRESS, show_address=True): 38 | if address not in ADDRESSES: 39 | raise ValueError("address is not valid. Expected: 0x21, 0x23, 0x25, or 0x27") 40 | 41 | self.__i2c = i2c 42 | self.__address = address 43 | 44 | # Set up the TCA9555 with the correct input and output pins 45 | self.__reg_write_uint16(self.__i2c, self.__address, self.CONFIGURATION_PORT0, 0b11111001_00111111) 46 | self.__reg_write_uint16(self.__i2c, self.__address, self.POLARITY_PORT0, 0b11111000_00111111) 47 | self.__reg_write_uint16(self.__i2c, self.__address, self.OUTPUT_PORT0, 0b00000110_11000000) 48 | 49 | self.__button_states = OrderedDict({}) 50 | for key, _ in self.BUTTON_MAPPING.items(): 51 | self.__button_states[key] = False 52 | 53 | self.__led_states = 0b0000 54 | if show_address: 55 | self.set_leds(self.address_code()) 56 | 57 | def address_code(self): 58 | return self.__change_bit(0x0000, ADDRESSES.index(self.__address), True) 59 | 60 | def read_buttons(self): 61 | state = self.__reg_read_uint16(self.__i2c, self.__address, self.INPUT_PORT0) 62 | for key, value in self.BUTTON_MAPPING.items(): 63 | self.__button_states[key] = self.__get_bit(state, value) 64 | return self.__button_states 65 | 66 | def set_leds(self, states): 67 | self.__led_states = states & 0b1111 68 | self.__update_leds() 69 | 70 | def set_led(self, led, state): 71 | if led < 1 or led > NUM_LEDS: 72 | raise ValueError("'led' out of range. Expected 1 to 4") 73 | 74 | self.__led_states = self.__change_bit(self.__led_states, led - 1, state) 75 | self.__update_leds() 76 | 77 | def clear_leds(self): 78 | self.__led_states = 0b0000 79 | self.__update_leds() 80 | 81 | def __update_leds(self): 82 | output = 0 83 | for i in range(NUM_LEDS): 84 | output = self.__change_bit(output, self.LED_MAPPING[i], not self.__get_bit(self.__led_states, i)) 85 | self.__reg_write_uint16(self.__i2c, self.__address, self.OUTPUT_PORT0, output) 86 | 87 | def __get_bit(self, num, bit_pos): 88 | return (num & (1 << bit_pos)) != 0 89 | 90 | def __change_bit(self, num, bit_pos, state): 91 | return num | (1 << bit_pos) if state else num & ~(1 << bit_pos) 92 | 93 | def __reg_write_uint16(self, i2c, address, reg, value): 94 | buffer = struct.pack("> 6 70 | x = ((data[0] & 0x0f) << 8) | data[1] 71 | y = ((data[2] & 0x0f) << 8) | data[3] 72 | return int(x / self._scale), int(y / self._scale), e not in (self.STATE_NONE, self.STATE_UP) 73 | 74 | def _handle_touch(self, pin): 75 | self.state = self.state2 = False 76 | 77 | self._i2c.writeto(self.TOUCH_ADDR, b'\x00', False) 78 | self._i2c.readfrom_into(self.TOUCH_ADDR, self._buf) 79 | 80 | mode, gesture, touches = self._data[:3] 81 | touches &= 0x0f 82 | 83 | for n in range(touches): 84 | data = self._data[3 + n * 6:] 85 | touch_id = data[2] >> 4 86 | if touch_id == 0: 87 | self.x, self.y, self.state = self._read_touch(data) 88 | else: 89 | self.x2, self.y2, self.state2 = self._read_touch(data) 90 | 91 | if self.state and self.state2: 92 | self.distance = math.sqrt(abs(self.x2 - self.x)**2 + abs(self.y2 - self.y)**2) 93 | self.angle = math.degrees(math.atan2(self.y2 - self.y, self.x2 - self.x)) + 180 94 | 95 | if self.debug: 96 | print(self.x, self.y, self.x2, self.y2, self.distance, self.angle, self.state, self.state2) 97 | 98 | for button in Button.buttons: 99 | if self.state and self.x >= button.x and self.x <= button.x + button.w and self.y >= button.y and self.y <= button.y + button.h: 100 | button.pressed = True 101 | elif self.state2 and self.x2 >= button.x and self.x2 <= button.x + button.w and self.y2 >= button.y and self.y2 <= button.y + button.h: 102 | button.pressed = True 103 | else: 104 | button.pressed = False 105 | -------------------------------------------------------------------------------- /pimoroni_pico_import.cmake: -------------------------------------------------------------------------------- 1 | # This file can be dropped into a project to help locate the Pimoroni Pico libraries 2 | # It will also set up the required include and module search paths. 3 | 4 | if (DEFINED ENV{PIMORONI_PICO_FETCH_FROM_GIT} AND (NOT PIMORONI_PICO_FETCH_FROM_GIT)) 5 | set(PIMORONI_PICO_FETCH_FROM_GIT $ENV{PIMORONI_PICO_FETCH_FROM_GIT}) 6 | message("Using PIMORONI_PICO_FETCH_FROM_GIT from environment ('${PIMORONI_PICO_FETCH_FROM_GIT}')") 7 | endif () 8 | 9 | if (DEFINED ENV{PIMORONI_PICO_FETCH_FROM_GIT_PATH} AND (NOT PIMORONI_PICO_FETCH_FROM_GIT_PATH)) 10 | set(PIMORONI_PICO_FETCH_FROM_GIT_PATH $ENV{PIMORONI_PICO_FETCH_FROM_GIT_PATH}) 11 | message("Using PIMORONI_PICO_FETCH_FROM_GIT_PATH from environment ('${PIMORONI_PICO_FETCH_FROM_GIT_PATH}')") 12 | endif () 13 | 14 | if (NOT PIMORONI_PICO_PATH) 15 | if (PIMORONI_PICO_FETCH_FROM_GIT) 16 | include(FetchContent) 17 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 18 | if (PIMORONI_PICO_FETCH_FROM_GIT_PATH) 19 | get_filename_component(FETCHCONTENT_BASE_DIR "${PIMORONI_PICO_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 20 | endif () 21 | FetchContent_Declare( 22 | pimoroni_pico 23 | GIT_REPOSITORY https://github.com/pimoroni/pimoroni-pico 24 | GIT_TAG main 25 | ) 26 | if (NOT pimoroni_pico) 27 | message("Downloading PIMORONI_PICO SDK") 28 | FetchContent_Populate(pimoroni_pico) 29 | set(PIMORONI_PICO_PATH ${pimoroni_pico_SOURCE_DIR}) 30 | endif () 31 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 32 | elseif(PICO_SDK_PATH AND EXISTS "${PICO_SDK_PATH}/../pimoroni-pico") 33 | set(PIMORONI_PICO_PATH ${PICO_SDK_PATH}/../pimoroni-pico) 34 | message("Defaulting PIMORONI_PICO_PATH as sibling of PICO_SDK_PATH: ${PIMORONI_PICO_PATH}") 35 | elseif(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/../../pimoroni-pico/") 36 | set(PIMORONI_PICO_PATH ${CMAKE_CURRENT_BINARY_DIR}/../../pimoroni-pico/) 37 | else() 38 | message(FATAL_ERROR "Pimoroni Pico location was not specified. Please set PIMORONI_PICO_PATH.") 39 | endif() 40 | endif() 41 | 42 | get_filename_component(PIMORONI_PICO_PATH "${PIMORONI_PICO_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 43 | if (NOT EXISTS ${PIMORONI_PICO_PATH}) 44 | message(FATAL_ERROR "Directory '${PIMORONI_PICO_PATH}' not found") 45 | endif () 46 | 47 | if (NOT EXISTS ${PIMORONI_PICO_PATH}/pimoroni_pico_import.cmake) 48 | message(FATAL_ERROR "Directory '${PIMORONI_PICO_PATH}' does not appear to contain the Pimoroni Pico libraries") 49 | endif () 50 | 51 | message("PIMORONI_PICO_PATH is ${PIMORONI_PICO_PATH}") 52 | 53 | set(PIMORONI_PICO_PATH ${PIMORONI_PICO_PATH} CACHE PATH "Path to the Pimoroni Pico libraries" FORCE) 54 | 55 | include_directories(${PIMORONI_PICO_PATH}) 56 | list(APPEND CMAKE_MODULE_PATH ${PIMORONI_PICO_PATH}) -------------------------------------------------------------------------------- /tools/convert-image-rgb565.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python3 2 | from PIL import Image 3 | import numpy 4 | import sys 5 | import pathlib 6 | 7 | # This scripts takes and converts an image to a format you can load with the PicoGraphics sprite methods 8 | # https://github.com/pimoroni/pimoroni-pico/blob/main/micropython/modules/picographics/README.md#sprites 9 | # The sprite sheets displays and loads best if your image is 128x128 with each sprite being 8x8 10 | # An example can be seen in the assets of 32blint-games repo 11 | # https://github.com/mikerr/32blit-games/tree/37daeaacdb661e2f9a47e3682859234b9b849226/sprite-browser/assets 12 | 13 | # Run with `./filename.py source-image.jpg` 14 | IMAGE_PATH = pathlib.Path(sys.argv[1]) 15 | OUTPUT_PATH = IMAGE_PATH.with_suffix(".16bpp") 16 | 17 | 18 | def image_to_data(image): 19 | """Generator function to convert a PIL image to 16-bit 565 RGB bytes.""" 20 | # Convert the image to RGB (ignoring alpha if present) 21 | image = image.convert('RGB') 22 | 23 | # Convert the image to a NumPy array 24 | pb = numpy.array(image).astype('uint16') 25 | 26 | # Extract the RGB channels 27 | r = (pb[:, :, 0] >> 3) & 0x1F # 5 bits for red 28 | g = (pb[:, :, 1] >> 2) & 0x3F # 6 bits for green 29 | b = (pb[:, :, 2] >> 3) & 0x1F # 5 bits for blue 30 | 31 | # Pack the RGB channels into a 16-bit 565 format 32 | color = (r << 11) | (g << 5) | b 33 | 34 | # Flatten the array and convert to bytes 35 | return color.byteswap().tobytes() 36 | 37 | img = Image.open(IMAGE_PATH) 38 | w, h = img.size 39 | data = image_to_data(img) 40 | 41 | print(f"Converted: {w}x{h} {len(data)} bytes") 42 | 43 | with open(OUTPUT_PATH, "wb") as f: 44 | f.write(data) 45 | 46 | print(f"Written to: {OUTPUT_PATH}") --------------------------------------------------------------------------------