├── .gitignore ├── .travis.yml ├── .vscode ├── arduino.json ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── assets └── tetris_ribba.gif ├── lib └── readme.txt ├── platformio.ini └── src ├── colors.h ├── definitions.h ├── drawing.h ├── ntp_time.h ├── numbers.h └── tetris_clock.ino /.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs 2 | .piolibdeps 3 | .pio 4 | .vscode/.browse.c_cpp.db* 5 | .vscode/c_cpp_properties.json 6 | .vscode/launch.json 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to by used as a library with examples 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /.vscode/arduino.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": "COM3", 3 | "board": "esp8266:esp8266:nodemcuv2", 4 | "configuration": "CpuFrequency=80,UploadSpeed=230400,FlashSize=4M3M" 5 | } -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "terminal.integrated.env.windows": { 3 | "PATH": "C:\\Users\\tblum\\.platformio\\penv\\Scripts;C:\\Users\\tblum\\.platformio\\penv;C:\\Program Files (x86)\\Intel\\iCLS Client\\;C:\\Program Files\\Intel\\iCLS Client\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Git\\cmd;C:\\Program Files\\nodejs\\;C:\\Program Files\\PuTTY\\;C:\\Users\\tblum\\AppData\\Local\\Microsoft\\WindowsApps;;C:\\Program Files (x86)\\Microsoft VS Code\\bin;C:\\Users\\tblum\\AppData\\Roaming\\npm;C:\\Program Files (x86)\\Intel\\iCLS Client\\;C:\\Program Files\\Intel\\iCLS Client\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\NVIDIA Corporation\\PhysX\\Common;C:\\Program Files\\Intel\\WiFi\\bin\\;C:\\Program Files\\Common Files\\Intel\\WirelessCommon\\;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\DAL;C:\\Program Files (x86)\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\Program Files\\Intel\\Intel(R) Management Engine Components\\IPT;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\Program Files (x86)\\Git\\cmd;C:\\Program Files\\nodejs\\;C:\\Program Files\\PuTTY\\;C:\\Users\\tblum\\AppData\\Local\\Microsoft\\WindowsApps;;C:\\Program Files (x86)\\Microsoft VS Code\\bin;C:\\Users\\tblum\\AppData\\Roaming\\npm", 4 | "PLATFORMIO_CALLER": "vscode" 5 | } 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tobias Blum 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 | # esp_p10_tetris_clock 2 | A tetris clock based on a 16x32 RGB matrix and a ESP8266. 3 | 4 | 5 | [![alt text](https://j.gifs.com/6RvBDl.gif "Tetris clock animation")](https://youtu.be/BGmjvfqf_0U) 6 | ![alt text](https://github.com/toblum/esp_p10_tetris_clock/blob/master/assets/tetris_ribba.gif "Tetris clock build in IKEA RIBBA frame") 7 | 8 | 9 | Note: Please also have a look on the https://github.com/toblum/TetrisAnimation repo. Brian Lough ported this code to a library and also added text support [in this video](https://www.youtube.com/watch?v=2IejVgrSlWE). 10 | 11 | 12 | This is the first implementation of the Tetris Clock on a RGB matrix. It uses the ESP8266 that was the only option at that time. The code was ported to [ESP32](https://github.com/witnessmenow/WiFi-Tetris-Clock) by Brian Lough. He also has a version based on the fast [ESP32-HUB75-MatrixPanel-I2S-DMA](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA) library that you can [easily install using a web flasher](https://www.youtube.com/watch?v=xjTACT1qO_8). Maybe you want to have a look here, if you have a ESP32 at hand. 13 | 14 | 15 | ## Installation 16 | This project is built on [platform.io](http://docs.platformio.org). All dependencies are automatically installed, if you use it. I recommend to use it [together with Visual Studio Code](http://docs.platformio.org/en/latest/ide/vscode.html). 17 | 18 | It's also possible to use the Arduino IDE, then you have to install the required libraries manually (see tetris_clock.ino). 19 | 20 | Please note: These matrices exist in different pitch sizes P10 means 10mm and results in 32x16 cm panels. The latest build is based on a P6 matrix that is roughly 20x10 cm in size. 21 | 22 | ## Wiring 23 | This project is based on the famous PxMatrix library. For wiring the matrix, see the excellent instructions [here](https://github.com/2dom/PxMatrix/). 24 | 25 | ## Building 26 | The complete build instructions can be found on [instructables](https://www.instructables.com/id/Tetris-Time-on-a-P10-RGB-Matrix-With-ESP8266). 27 | 28 | You find the 3D printable parts [here](https://www.thingiverse.com/thing:2846368). 29 | 30 | ## Enhancement 31 | * Support for 32x32 displays 32 | * 12 hour mode [issue](https://github.com/toblum/esp_p10_tetris_clock/issues/2#issuecomment-392286952) 33 | * Weather forecast 34 | * Youtube counter 35 | -------------------------------------------------------------------------------- /assets/tetris_ribba.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toblum/esp_p10_tetris_clock/3f52e7be94a3d7bda79231dac18c1dd774f83a18/assets/tetris_ribba.gif -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | More information about PlatformIO Library Dependency Finder 36 | - http://docs.platformio.org/page/librarymanager/ldf.html 37 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; http://docs.platformio.org/page/projectconf.html 10 | 11 | [common] 12 | arduino_core_2_3_0 = espressif8266@1.5.0 13 | arduino_core_2_4_0 = espressif8266@1.6.0 14 | arduino_core_2_4_1 = espressif8266@1.7.3 15 | arduino_core_2_4_2 = espressif8266@1.8.0 16 | arduino_core_2_5_0 = espressif8266@2.0.4 17 | arduino_core_stage = https://github.com/platformio/platform-espressif8266.git#feature/stage 18 | 19 | [platformio] 20 | env_default = nodemcuv2 21 | description = "Tetris clock implementation for RGB matrix and ESP8266" 22 | 23 | 24 | ; You MUST inject these options into [env:] section 25 | ; using ${common_env_data.***} (see below) 26 | [common_env_data] 27 | build_flags = 28 | ;-D VERSION=1.2.3 29 | ;-D DEBUG=1 30 | 31 | lib_deps_builtin = 32 | ; DNSServer 33 | 34 | lib_deps_external = 35 | Adafruit BusIO@1.3.2 36 | Adafruit GFX Library@1.9.0 37 | PxMatrix LED MATRIX library@1.8.2 38 | WiFiManager@0.14 39 | Time@1.5 40 | NtpClientLib@2.5.1 41 | 42 | 43 | [env:nodemcuv2] 44 | platform = ${common.arduino_core_2_5_0} 45 | framework = arduino 46 | board = nodemcuv2 47 | upload_speed = 512000 48 | ; board_build.f_cpu = 160000000L 49 | 50 | ; Build options 51 | build_flags = 52 | ${common_env_data.build_flags} 53 | 54 | ; Library options 55 | lib_deps = 56 | ${common_env_data.lib_deps_builtin} 57 | ${common_env_data.lib_deps_external} 58 | 59 | ; Serial Monitor options 60 | monitor_speed = 115200 61 | 62 | ; Unit Testing options 63 | test_ignore = test_desktop -------------------------------------------------------------------------------- /src/colors.h: -------------------------------------------------------------------------------- 1 | // ********************************************************************* 2 | // Color definitions 3 | // ********************************************************************* 4 | 5 | // Some standard colors 6 | uint16_t myRED = display.color565(255, 0, 0); 7 | uint16_t myGREEN = display.color565(0, 255, 0); 8 | uint16_t myBLUE = display.color565(48, 73, 255); 9 | uint16_t myWHITE = display.color565(255, 255, 255); 10 | uint16_t myYELLOW = display.color565(255, 255, 0); 11 | uint16_t myCYAN = display.color565(0, 255, 255); 12 | uint16_t myMAGENTA = display.color565(255, 0, 255); 13 | uint16_t myORANGE = display.color565(255, 96, 0); 14 | uint16_t myBLACK = display.color565(0, 0, 0); 15 | 16 | uint16 myCOLORS[8] = {myRED, myGREEN, myBLUE, myWHITE, myYELLOW, myCYAN, myMAGENTA, myBLACK}; 17 | -------------------------------------------------------------------------------- /src/definitions.h: -------------------------------------------------------------------------------- 1 | // ********************************************************************* 2 | // Holds all central definitions and globals 3 | // ********************************************************************* 4 | 5 | Ticker display_ticker; // Ticker that triggers redraw of the screen 6 | 7 | // NTP 8 | int8_t timeZone = 1; // Timezone for NTP client 9 | int8_t minutesTimeZone = 0; // Minutes difference for time zone 10 | 11 | // Globals 12 | uint8_t init_state = 0; // Hold the current state of the initialization process 13 | String str_display_time = "0000"; // Holds the currently displayed time as string 14 | bool seconds_odd = false; // True when the seconds are currently odd 15 | unsigned long nextDisplayUpdate = millis(); 16 | unsigned long nextNumberUpdate = millis(); 17 | 18 | // WiFiManager 19 | WiFiManager wifiManager; // Global WiFiManager object 20 | #define AP_NAME "tetris_clock" 21 | #define AP_PASS "tetromino" 22 | #define AP_TIMEOUT 300 23 | 24 | 25 | // Pins for LED MATRIX 26 | #define PxMATRIX_double_buffer true 27 | #define P_LAT 16 28 | #define P_A 5 29 | #define P_B 4 30 | #define P_C 15 31 | #define P_D 12 32 | #define P_OE 2 33 | PxMATRIX display(32,32, P_LAT, P_OE,P_A,P_B,P_C,P_D); -------------------------------------------------------------------------------- /src/drawing.h: -------------------------------------------------------------------------------- 1 | // ********************************************************************* 2 | // Functions for drawing on the matrix 3 | // ********************************************************************* 4 | 5 | 6 | // ********************************************************************* 7 | // Helper function that draws a letter at a given position of the matric in a given color 8 | // ********************************************************************* 9 | void drawChar(String letter, uint8_t x, uint8_t y, uint16_t color) 10 | { 11 | display.setTextColor(color); 12 | display.setCursor(x, y); 13 | display.print(letter); 14 | } 15 | 16 | // ********************************************************************* 17 | // Draws the intro screen 18 | // ********************************************************************* 19 | void drawIntro() 20 | { 21 | drawChar("T", 0, 0, myCYAN); 22 | drawChar("e", 5, 0, myMAGENTA); 23 | drawChar("t", 11, 0, myYELLOW); 24 | drawChar("r", 17, 0, myGREEN); 25 | drawChar("i", 22, 0, myBLUE); 26 | drawChar("s", 26, 0, myRED); 27 | 28 | drawChar("T", 6, 9, myRED); 29 | drawChar("i", 11, 9, myWHITE); 30 | drawChar("m", 16, 9, myCYAN); 31 | drawChar("e", 22, 9, myMAGENTA); 32 | } 33 | 34 | // ********************************************************************* 35 | // Draws a brick shape at a given position 36 | // ********************************************************************* 37 | void drawShape(int blocktype, uint16_t color, int x_pos, int y_pos, int num_rot) 38 | { 39 | // Square 40 | if (blocktype == 0) 41 | { 42 | display.drawPixel(x_pos, y_pos, color); 43 | display.drawPixel(x_pos + 1, y_pos, color); 44 | display.drawPixel(x_pos, y_pos - 1, color); 45 | display.drawPixel(x_pos + 1, y_pos - 1, color); 46 | } 47 | 48 | // L-Shape 49 | if (blocktype == 1) 50 | { 51 | if (num_rot == 0) 52 | { 53 | display.drawPixel(x_pos, y_pos, color); 54 | display.drawPixel(x_pos + 1, y_pos, color); 55 | display.drawPixel(x_pos, y_pos - 1, color); 56 | display.drawPixel(x_pos, y_pos - 2, color); 57 | } 58 | if (num_rot == 1) 59 | { 60 | display.drawPixel(x_pos, y_pos, color); 61 | display.drawPixel(x_pos, y_pos - 1, color); 62 | display.drawPixel(x_pos + 1, y_pos - 1, color); 63 | display.drawPixel(x_pos + 2, y_pos - 1, color); 64 | } 65 | if (num_rot == 2) 66 | { 67 | display.drawPixel(x_pos + 1, y_pos, color); 68 | display.drawPixel(x_pos + 1, y_pos - 1, color); 69 | display.drawPixel(x_pos + 1, y_pos - 2, color); 70 | display.drawPixel(x_pos, y_pos - 2, color); 71 | } 72 | if (num_rot == 3) 73 | { 74 | display.drawPixel(x_pos, y_pos, color); 75 | display.drawPixel(x_pos + 1, y_pos, color); 76 | display.drawPixel(x_pos + 2, y_pos, color); 77 | display.drawPixel(x_pos + 2, y_pos - 1, color); 78 | } 79 | } 80 | 81 | // L-Shape (reverse) 82 | if (blocktype == 2) 83 | { 84 | if (num_rot == 0) 85 | { 86 | display.drawPixel(x_pos, y_pos, color); 87 | display.drawPixel(x_pos + 1, y_pos, color); 88 | display.drawPixel(x_pos + 1, y_pos - 1, color); 89 | display.drawPixel(x_pos + 1, y_pos - 2, color); 90 | } 91 | if (num_rot == 1) 92 | { 93 | display.drawPixel(x_pos, y_pos, color); 94 | display.drawPixel(x_pos + 1, y_pos, color); 95 | display.drawPixel(x_pos + 2, y_pos, color); 96 | display.drawPixel(x_pos, y_pos - 1, color); 97 | } 98 | if (num_rot == 2) 99 | { 100 | display.drawPixel(x_pos, y_pos, color); 101 | display.drawPixel(x_pos, y_pos - 1, color); 102 | display.drawPixel(x_pos, y_pos - 2, color); 103 | display.drawPixel(x_pos + 1, y_pos - 2, color); 104 | } 105 | if (num_rot == 3) 106 | { 107 | display.drawPixel(x_pos, y_pos - 1, color); 108 | display.drawPixel(x_pos + 1, y_pos - 1, color); 109 | display.drawPixel(x_pos + 2, y_pos - 1, color); 110 | display.drawPixel(x_pos + 2, y_pos, color); 111 | } 112 | } 113 | 114 | // I-Shape 115 | if (blocktype == 3) 116 | { 117 | if (num_rot == 0 || num_rot == 2) 118 | { // Horizontal 119 | display.drawPixel(x_pos, y_pos, color); 120 | display.drawPixel(x_pos + 1, y_pos, color); 121 | display.drawPixel(x_pos + 2, y_pos, color); 122 | display.drawPixel(x_pos + 3, y_pos, color); 123 | } 124 | if (num_rot == 1 || num_rot == 3) 125 | { // Vertical 126 | display.drawPixel(x_pos, y_pos, color); 127 | display.drawPixel(x_pos, y_pos - 1, color); 128 | display.drawPixel(x_pos, y_pos - 2, color); 129 | display.drawPixel(x_pos, y_pos - 3, color); 130 | } 131 | } 132 | 133 | // S-Shape 134 | if (blocktype == 4) 135 | { 136 | if (num_rot == 0 || num_rot == 2) 137 | { 138 | display.drawPixel(x_pos + 1, y_pos, color); 139 | display.drawPixel(x_pos, y_pos - 1, color); 140 | display.drawPixel(x_pos + 1, y_pos - 1, color); 141 | display.drawPixel(x_pos, y_pos - 2, color); 142 | } 143 | if (num_rot == 1 || num_rot == 3) 144 | { 145 | display.drawPixel(x_pos, y_pos, color); 146 | display.drawPixel(x_pos + 1, y_pos, color); 147 | display.drawPixel(x_pos + 1, y_pos - 1, color); 148 | display.drawPixel(x_pos + 2, y_pos - 1, color); 149 | } 150 | } 151 | 152 | // S-Shape (reversed) 153 | if (blocktype == 5) 154 | { 155 | if (num_rot == 0 || num_rot == 2) 156 | { 157 | display.drawPixel(x_pos, y_pos, color); 158 | display.drawPixel(x_pos, y_pos - 1, color); 159 | display.drawPixel(x_pos + 1, y_pos - 1, color); 160 | display.drawPixel(x_pos + 1, y_pos - 2, color); 161 | } 162 | if (num_rot == 1 || num_rot == 3) 163 | { 164 | display.drawPixel(x_pos + 1, y_pos, color); 165 | display.drawPixel(x_pos + 2, y_pos, color); 166 | display.drawPixel(x_pos, y_pos - 1, color); 167 | display.drawPixel(x_pos + 1, y_pos - 1, color); 168 | } 169 | } 170 | 171 | // Half cross 172 | if (blocktype == 6) 173 | { 174 | if (num_rot == 0) 175 | { 176 | display.drawPixel(x_pos, y_pos, color); 177 | display.drawPixel(x_pos + 1, y_pos, color); 178 | display.drawPixel(x_pos + 2, y_pos, color); 179 | display.drawPixel(x_pos + 1, y_pos - 1, color); 180 | } 181 | if (num_rot == 1) 182 | { 183 | display.drawPixel(x_pos, y_pos, color); 184 | display.drawPixel(x_pos, y_pos - 1, color); 185 | display.drawPixel(x_pos, y_pos - 2, color); 186 | display.drawPixel(x_pos + 1, y_pos - 1, color); 187 | } 188 | if (num_rot == 2) 189 | { 190 | display.drawPixel(x_pos + 1, y_pos, color); 191 | display.drawPixel(x_pos, y_pos - 1, color); 192 | display.drawPixel(x_pos + 1, y_pos - 1, color); 193 | display.drawPixel(x_pos + 2, y_pos - 1, color); 194 | } 195 | if (num_rot == 3) 196 | { 197 | display.drawPixel(x_pos + 1, y_pos, color); 198 | display.drawPixel(x_pos, y_pos - 1, color); 199 | display.drawPixel(x_pos + 1, y_pos - 1, color); 200 | display.drawPixel(x_pos + 1, y_pos - 2, color); 201 | } 202 | } 203 | } 204 | 205 | // ********************************************************************* 206 | // Helper function that that return the falling instruction for a given number 207 | // ********************************************************************* 208 | fall_instr getFallinstrByNum(int num, int blockindex) 209 | { 210 | if (num == 0) 211 | { 212 | return num_0[blockindex]; 213 | } 214 | if (num == 1) 215 | { 216 | return num_1[blockindex]; 217 | } 218 | if (num == 2) 219 | { 220 | return num_2[blockindex]; 221 | } 222 | if (num == 3) 223 | { 224 | return num_3[blockindex]; 225 | } 226 | if (num == 4) 227 | { 228 | return num_4[blockindex]; 229 | } 230 | if (num == 5) 231 | { 232 | return num_5[blockindex]; 233 | } 234 | if (num == 6) 235 | { 236 | return num_6[blockindex]; 237 | } 238 | if (num == 7) 239 | { 240 | return num_7[blockindex]; 241 | } 242 | if (num == 8) 243 | { 244 | return num_8[blockindex]; 245 | } 246 | if (num == 9) 247 | { 248 | return num_9[blockindex]; 249 | } 250 | } 251 | 252 | // ********************************************************************* 253 | // Helper function that return the number of bricks for a given number 254 | // ********************************************************************* 255 | int getBocksizeByNum(int num) 256 | { 257 | if (num == 0) 258 | { 259 | return SIZE_NUM_0; 260 | } 261 | if (num == 1) 262 | { 263 | return SIZE_NUM_1; 264 | } 265 | if (num == 2) 266 | { 267 | return SIZE_NUM_2; 268 | } 269 | if (num == 3) 270 | { 271 | return SIZE_NUM_3; 272 | } 273 | if (num == 4) 274 | { 275 | return SIZE_NUM_4; 276 | } 277 | if (num == 5) 278 | { 279 | return SIZE_NUM_5; 280 | } 281 | if (num == 6) 282 | { 283 | return SIZE_NUM_6; 284 | } 285 | if (num == 7) 286 | { 287 | return SIZE_NUM_7; 288 | } 289 | if (num == 8) 290 | { 291 | return SIZE_NUM_8; 292 | } 293 | if (num == 9) 294 | { 295 | return SIZE_NUM_9; 296 | } 297 | } 298 | 299 | // ********************************************************************* 300 | // Main function that handles the drawing of all numbers 301 | // ********************************************************************* 302 | void drawNumbers() 303 | { 304 | // For each number position 305 | for (int numpos = 0; numpos < SIZE_NUM_STATES; numpos++) 306 | { 307 | 308 | // Draw falling shape 309 | if (numstates[numpos].blockindex < getBocksizeByNum(numstates[numpos].num_to_draw)) 310 | { 311 | fall_instr current_fall = getFallinstrByNum(numstates[numpos].num_to_draw, numstates[numpos].blockindex); 312 | 313 | // Handle variations of rotations 314 | uint8_t rotations = current_fall.num_rot; 315 | if (rotations == 1) 316 | { 317 | if (numstates[numpos].fallindex < (int)(current_fall.y_stop / 2)) 318 | { 319 | rotations = 0; 320 | } 321 | } 322 | if (rotations == 2) 323 | { 324 | if (numstates[numpos].fallindex < (int)(current_fall.y_stop / 3)) 325 | { 326 | rotations = 0; 327 | } 328 | if (numstates[numpos].fallindex < (int)(current_fall.y_stop / 3 * 2)) 329 | { 330 | rotations = 1; 331 | } 332 | } 333 | if (rotations == 3) 334 | { 335 | if (numstates[numpos].fallindex < (int)(current_fall.y_stop / 4)) 336 | { 337 | rotations = 0; 338 | } 339 | if (numstates[numpos].fallindex < (int)(current_fall.y_stop / 4 * 2)) 340 | { 341 | rotations = 1; 342 | } 343 | if (numstates[numpos].fallindex < (int)(current_fall.y_stop / 4 * 3)) 344 | { 345 | rotations = 2; 346 | } 347 | } 348 | 349 | drawShape(current_fall.blocktype, current_fall.color, current_fall.x_pos + numstates[numpos].x_shift, numstates[numpos].fallindex - 1, rotations); 350 | numstates[numpos].fallindex++; 351 | 352 | if (numstates[numpos].fallindex > current_fall.y_stop) 353 | { 354 | numstates[numpos].fallindex = 0; 355 | numstates[numpos].blockindex++; 356 | } 357 | } 358 | 359 | // Draw already dropped shapes 360 | if (numstates[numpos].blockindex > 0) 361 | { 362 | for (int i = 0; i < numstates[numpos].blockindex; i++) 363 | { 364 | fall_instr fallen_block = getFallinstrByNum(numstates[numpos].num_to_draw, i); 365 | drawShape(fallen_block.blocktype, fallen_block.color, fallen_block.x_pos + numstates[numpos].x_shift, fallen_block.y_stop - 1, fallen_block.num_rot); 366 | } 367 | } 368 | } 369 | 370 | // Hour / minutes divider (blinking) 371 | if (seconds_odd) 372 | { 373 | display.fillRect(15, 12, 2, 2, myWHITE); 374 | display.fillRect(15, 8, 2, 2, myWHITE); 375 | } 376 | } 377 | 378 | // ********************************************************************* 379 | // Handler for the display refresh ticker 380 | // ********************************************************************* 381 | void display_updater() 382 | { 383 | // ISR for display refresh 384 | display.display(60); 385 | } 386 | 387 | // ********************************************************************* 388 | // Handler for the number refresh ticker 389 | // ********************************************************************* 390 | void number_updater() 391 | { 392 | display.fillScreen(myBLACK); 393 | drawNumbers(); 394 | display.showBuffer(); 395 | } -------------------------------------------------------------------------------- /src/ntp_time.h: -------------------------------------------------------------------------------- 1 | // ********************************************************************* 2 | // Functions for NTP and time handling 3 | // ********************************************************************* 4 | 5 | 6 | // ********************************************************************* 7 | // Function that updates the number to draw at a given position 8 | // ********************************************************************* 9 | void setTimeNumber(uint8_t pos, uint8_t number) 10 | { 11 | // Serial.printf("Set position %d to %d\n", pos, number); 12 | numstates[pos].num_to_draw = number; 13 | numstates[pos].fallindex = 0; 14 | numstates[pos].blockindex = 0; 15 | } 16 | 17 | // ********************************************************************* 18 | // Called when time has changed, updates numbers where necessary 19 | // ********************************************************************* 20 | void updateTime(String str_current_time) 21 | { 22 | // Serial.print(str_display_time); 23 | // Serial.print(" -> "); 24 | // Serial.println(str_current_time); 25 | 26 | for (uint8_t pos = 0; pos < 4; pos++) 27 | { 28 | if (str_display_time.charAt(pos) != str_current_time.charAt(pos)) 29 | { 30 | int number = str_current_time.substring(pos, pos + 1).toInt(); 31 | setTimeNumber(pos, number); 32 | } 33 | } 34 | } 35 | 36 | // ********************************************************************* 37 | // Returns the current NTP time as a four letter string 12:34 returns "1234" 38 | // Also sets the seconds odd global 39 | // ********************************************************************* 40 | String getTimeAsString() 41 | { 42 | String current_time = NTP.getTimeStr(); 43 | String hours = current_time.substring(0, 2); 44 | String minutes = current_time.substring(3, 5); 45 | String seconds = current_time.substring(6, 8); 46 | String str_current_time = hours + minutes; 47 | 48 | // Check if seconds odd 49 | seconds_odd = (seconds.toInt() % 2 == 1); 50 | 51 | return str_current_time; 52 | } -------------------------------------------------------------------------------- /src/numbers.h: -------------------------------------------------------------------------------- 1 | // ********************************************************************* 2 | // Types and data that describes how numbers are drawed 3 | // ********************************************************************* 4 | 5 | // Type that describes how a brick is falling down 6 | typedef struct 7 | { 8 | int blocktype; // Number of the block type 9 | uint16_t color; // Color of the brick 10 | int x_pos; // x-position (starting from the left number staring point) where the brick should be placed 11 | int y_stop; // y-position (1-16, where 16 is the last line of the matrix) where the brick should stop falling 12 | int num_rot; // Number of 90-degree (clockwise) rotations a brick is turned from the standard position 13 | } fall_instr; 14 | 15 | // Type that describes the current state of a drawed number 16 | typedef struct 17 | { 18 | int num_to_draw; // Number to draw (0-9) 19 | int blockindex; // The index of the brick (as defined in the falling instructions) that is currently falling 20 | int fallindex; // y-position of the brick it already has (incrementing with each step) 21 | int x_shift; // x-position of the number relative to the matrix where the number should be placed. 22 | } numstate; 23 | 24 | // States of the 4 shown numbers 25 | #define SIZE_NUM_STATES 4 26 | numstate numstates[SIZE_NUM_STATES] = { 27 | {0, 0, 0, 1}, 28 | {0, 0, 0, 8}, 29 | {0, 0, 0, 18}, 30 | {0, 0, 0, 25}}; 31 | 32 | 33 | // ********************************************************************* 34 | // Fall instructions for all numbers 35 | // ********************************************************************* 36 | 37 | // ********************************************************************* 38 | // Number 0 39 | // ********************************************************************* 40 | #define SIZE_NUM_0 13 41 | fall_instr num_0[SIZE_NUM_0] = { 42 | {2, myCYAN, 4, 16, 0}, 43 | {4, myORANGE, 2, 16, 1}, 44 | {3, myYELLOW, 0, 16, 1}, 45 | {6, myMAGENTA, 1, 16, 1}, 46 | {5, myGREEN, 4, 14, 0}, 47 | {6, myMAGENTA, 0, 13, 3}, 48 | {5, myGREEN, 4, 12, 0}, 49 | {5, myGREEN, 0, 11, 0}, 50 | {6, myMAGENTA, 4, 10, 1}, 51 | {6, myMAGENTA, 0, 9, 1}, 52 | {5, myGREEN, 1, 8, 1}, 53 | {2, myCYAN, 3, 8, 3}}; 54 | 55 | // ********************************************************************* 56 | // Number 1 57 | // ********************************************************************* 58 | #define SIZE_NUM_1 5 59 | fall_instr num_1[SIZE_NUM_1] = { 60 | {2, myCYAN, 4, 16, 0}, 61 | {3, myYELLOW, 4, 15, 1}, 62 | {3, myYELLOW, 5, 13, 3}, 63 | {2, myCYAN, 4, 11, 2}, 64 | {0, myRED, 4, 8, 0}}; 65 | 66 | // ********************************************************************* 67 | // Number 2 68 | // ********************************************************************* 69 | #define SIZE_NUM_2 11 70 | fall_instr num_2[SIZE_NUM_2] = { 71 | {0, myRED, 4, 16, 0}, 72 | {3, myYELLOW, 0, 16, 1}, 73 | {1, myBLUE, 1, 16, 3}, 74 | {1, myBLUE, 1, 15, 0}, 75 | {3, myYELLOW, 1, 12, 2}, 76 | {1, myBLUE, 0, 12, 1}, 77 | {2, myCYAN, 3, 12, 3}, 78 | {0, myRED, 4, 10, 0}, 79 | {3, myYELLOW, 1, 8, 0}, 80 | {2, myCYAN, 3, 8, 3}, 81 | {1, myBLUE, 0, 8, 1}}; 82 | 83 | // ********************************************************************* 84 | // Number 3 85 | // ********************************************************************* 86 | #define SIZE_NUM_3 11 87 | fall_instr num_3[SIZE_NUM_3] = { 88 | {1, myBLUE, 3, 16, 3}, 89 | {2, myCYAN, 0, 16, 1}, 90 | {3, myYELLOW, 1, 15, 2}, 91 | {0, myRED, 4, 14, 0}, 92 | {3, myYELLOW, 1, 12, 2}, 93 | {1, myBLUE, 0, 12, 1}, 94 | {3, myYELLOW, 5, 12, 3}, 95 | {2, myCYAN, 3, 11, 0}, 96 | {3, myYELLOW, 1, 8, 0}, 97 | {1, myBLUE, 0, 8, 1}, 98 | {2, myCYAN, 3, 8, 3}}; 99 | 100 | // ********************************************************************* 101 | // Number 4 102 | // ********************************************************************* 103 | #define SIZE_NUM_4 9 104 | fall_instr num_4[SIZE_NUM_4] = { 105 | {0, myRED, 4, 16, 0}, 106 | {0, myRED, 4, 14, 0}, 107 | {3, myYELLOW, 1, 12, 0}, 108 | {1, myBLUE, 0, 12, 1}, 109 | {2, myCYAN, 0, 10, 0}, 110 | {2, myCYAN, 3, 12, 3}, 111 | {3, myYELLOW, 4, 10, 3}, 112 | {2, myCYAN, 0, 9, 2}, 113 | {3, myYELLOW, 5, 10, 1}}; 114 | 115 | // ********************************************************************* 116 | // Number 5 117 | // ********************************************************************* 118 | #define SIZE_NUM_5 11 119 | fall_instr num_5[SIZE_NUM_5] = { 120 | {0, myRED, 0, 16, 0}, 121 | {2, myCYAN, 2, 16, 1}, 122 | {2, myCYAN, 3, 15, 0}, 123 | {3, myYELLOW, 5, 16, 1}, 124 | {3, myYELLOW, 1, 12, 0}, 125 | {1, myBLUE, 0, 12, 1}, 126 | {2, myCYAN, 3, 12, 3}, 127 | {0, myRED, 0, 10, 0}, 128 | {3, myYELLOW, 1, 8, 2}, 129 | {1, myBLUE, 0, 8, 1}, 130 | {2, myCYAN, 3, 8, 3}}; 131 | 132 | // ********************************************************************* 133 | // Number 6 134 | // ********************************************************************* 135 | #define SIZE_NUM_6 12 136 | fall_instr num_6[SIZE_NUM_6] = { 137 | {2, myCYAN, 0, 16, 1}, 138 | {5, myGREEN, 2, 16, 1}, 139 | {6, myMAGENTA, 0, 15, 3}, 140 | {6, myMAGENTA, 4, 16, 3}, 141 | {5, myGREEN, 4, 14, 0}, 142 | {3, myYELLOW, 1, 12, 2}, 143 | {2, myCYAN, 0, 13, 2}, 144 | {3, myYELLOW, 2, 11, 0}, 145 | {0, myRED, 0, 10, 0}, 146 | {3, myYELLOW, 1, 8, 0}, 147 | {1, myBLUE, 0, 8, 1}, 148 | {2, myCYAN, 3, 8, 3}}; 149 | 150 | // ********************************************************************* 151 | // Number 7 152 | // ********************************************************************* 153 | #define SIZE_NUM_7 7 154 | fall_instr num_7[SIZE_NUM_7] = { 155 | {0, myRED, 4, 16, 0}, 156 | {1, myBLUE, 4, 14, 0}, 157 | {3, myYELLOW, 5, 13, 1}, 158 | {2, myCYAN, 4, 11, 2}, 159 | {3, myYELLOW, 1, 8, 2}, 160 | {2, myCYAN, 3, 8, 3}, 161 | {1, myBLUE, 0, 8, 1}}; 162 | 163 | // ********************************************************************* 164 | // Number 8 165 | // ********************************************************************* 166 | #define SIZE_NUM_8 13 167 | fall_instr num_8[SIZE_NUM_8] = { 168 | {3, myYELLOW, 1, 16, 0}, 169 | {6, myMAGENTA, 0, 16, 1}, 170 | {3, myYELLOW, 5, 16, 1}, 171 | {1, myBLUE, 2, 15, 3}, 172 | {4, myORANGE, 0, 14, 0}, 173 | {1, myBLUE, 1, 12, 3}, 174 | {6, myMAGENTA, 4, 13, 1}, 175 | {2, myCYAN, 0, 11, 1}, 176 | {4, myORANGE, 0, 10, 0}, 177 | {4, myORANGE, 4, 11, 0}, 178 | {5, myGREEN, 0, 8, 1}, 179 | {5, myGREEN, 2, 8, 1}, 180 | {1, myBLUE, 4, 9, 2}}; 181 | 182 | // ********************************************************************* 183 | // Number 9 184 | // ********************************************************************* 185 | #define SIZE_NUM_9 12 186 | fall_instr num_9[SIZE_NUM_9] = { 187 | {0, myRED, 0, 16, 0}, 188 | {3, myYELLOW, 2, 16, 0}, 189 | {1, myBLUE, 2, 15, 3}, 190 | {1, myBLUE, 4, 15, 2}, 191 | {3, myYELLOW, 1, 12, 2}, 192 | {3, myYELLOW, 5, 12, 3}, 193 | {5, myGREEN, 0, 12, 0}, 194 | {1, myBLUE, 2, 11, 3}, 195 | {5, myGREEN, 4, 9, 0}, 196 | {6, myMAGENTA, 0, 10, 1}, 197 | {5, myGREEN, 0, 8, 1}, 198 | {6, myMAGENTA, 2, 8, 2}}; 199 | -------------------------------------------------------------------------------- /src/tetris_clock.ino: -------------------------------------------------------------------------------- 1 | #define double_buffer 2 | #include 3 | #include 4 | #include // Download from: https://github.com/2dom/PxMatrix/, needs https://github.com/adafruit/Adafruit-GFX-Library via library manager 5 | #include 6 | #include // https://github.com/gmag11/NtpClient via library manager 7 | #include 8 | 9 | // WiFi-Manager 10 | #include // Local DNS Server used for redirecting all requests to the configuration portal 11 | #include // Local WebServer used to serve the configuration portal 12 | #include // https://github.com/tzapu/WiFiManager WiFi Configuration Magic 13 | 14 | // Local includes 15 | #include "definitions.h" 16 | #include "colors.h" 17 | #include "numbers.h" 18 | #include "drawing.h" 19 | #include "ntp_time.h" 20 | 21 | // **************************************************************** 22 | // * Setup 23 | // **************************************************************** 24 | void setup() 25 | { 26 | Serial.begin(115200); 27 | 28 | display.begin(16); 29 | display.flushDisplay(); 30 | 31 | // Draw intro while WiFi is connecting 32 | drawIntro(); 33 | 34 | // Connect to WiFi using WiFiManager 35 | // wifiManager.resetSettings(); // Reset WiFiManager settings, uncomment if needed 36 | wifiManager.setTimeout(AP_TIMEOUT); // Timeout until config portal is turned off 37 | if (!wifiManager.autoConnect(AP_NAME, AP_PASS)) 38 | { 39 | Serial.println("Failed to connect and hit timeout"); 40 | delay(3000); 41 | //reset and try again 42 | ESP.reset(); 43 | delay(5000); 44 | } 45 | 46 | init_state = 1; 47 | 48 | display_ticker.attach(0.004, display_updater); 49 | yield(); 50 | delay(2000); 51 | } 52 | 53 | void loop() 54 | { 55 | unsigned long now = millis(); 56 | 57 | // Initialization state 1 58 | // WiFi is connected, now start NTP 59 | if (init_state == 1) 60 | { 61 | NTP.begin("pool.ntp.org", timeZone, true, minutesTimeZone); 62 | NTP.setInterval(63); 63 | init_state = 2; 64 | } 65 | 66 | // Initialization state 2 67 | // Clock is now set, init number updater 68 | if (init_state == 2) 69 | { 70 | // Log the current time 71 | Serial.print(NTP.getTimeDateString()); 72 | Serial.print(" "); 73 | Serial.print(NTP.isSummerTime() ? "Summer Time. " : "Winter Time. "); 74 | Serial.print("WiFi is "); 75 | Serial.print(WiFi.isConnected() ? "connected" : "not connected"); 76 | Serial.print(". "); 77 | Serial.print("Uptime: "); 78 | Serial.print(NTP.getUptimeString()); 79 | Serial.print(" since "); 80 | Serial.println(NTP.getTimeDateString(NTP.getFirstSync()).c_str()); 81 | init_state = 3; 82 | } 83 | 84 | // Initialization state 3 85 | // Everything ready, check for time change 86 | if (init_state == 3) 87 | { 88 | String str_current_time = getTimeAsString(); 89 | 90 | // Time has changed 91 | if (str_display_time != str_current_time) 92 | { 93 | Serial.print("Time changed: "); 94 | Serial.println(str_current_time); 95 | updateTime(str_current_time); 96 | str_display_time = str_current_time; 97 | } 98 | 99 | if (now > nextNumberUpdate) { 100 | number_updater(); 101 | 102 | nextNumberUpdate = now + 100; 103 | } 104 | } 105 | } --------------------------------------------------------------------------------