├── .gitattributes ├── .github └── workflows │ ├── clockwise-ci.yml │ └── esp-idf.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CHECKLIST.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── firmware ├── .gitignore ├── .vscode │ ├── extensions.json │ ├── launch.json │ └── settings.json ├── include │ └── README ├── lib │ ├── README │ ├── cw-commons │ │ ├── CMakeLists.txt │ │ ├── CWDateTime.cpp │ │ ├── CWDateTime.h │ │ ├── CWHttpClient.h │ │ ├── CWPreferences.h │ │ ├── CWWebServer.h │ │ ├── IClockface.h │ │ ├── Icons.h │ │ ├── README.md │ │ ├── SettingsWebPage.h │ │ ├── StatusController.h │ │ ├── WiFiController.h │ │ └── picopixel.h │ └── cw-gfx-engine │ │ ├── CMakeLists.txt │ │ ├── ColorUtil.h │ │ ├── EventBus.cpp │ │ ├── EventBus.h │ │ ├── EventTask.h │ │ ├── Game.h │ │ ├── ImageUtils.h │ │ ├── Locator.cpp │ │ ├── Locator.h │ │ ├── Macros.h │ │ ├── Object.h │ │ ├── README.md │ │ ├── Sprite.cpp │ │ ├── Sprite.h │ │ └── Tile.h ├── platformio.ini ├── src │ └── main.cpp └── test │ ├── README │ ├── test_embedded │ └── CWPreferences.cpp │ └── test_native │ └── SimpleTests.cpp ├── get-platformio.py ├── main ├── CMakeLists.txt ├── Kconfig.projbuild └── main.cpp └── sdkconfig.defaults /.gitattributes: -------------------------------------------------------------------------------- 1 | *.py -linguist-detectable 2 | firmware/** linguist-language=C++ linguist-detectable 3 | components/** linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/workflows/clockwise-ci.yml: -------------------------------------------------------------------------------- 1 | name: Clockwise CI/CD 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'releases/**' 7 | workflow_dispatch: 8 | inputs: 9 | tags: 10 | description: 'Reason for manual run' 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | strategy: 16 | max-parallel: 2 17 | matrix: 18 | clockfaces: 19 | - cw-cf-0x01 20 | - cw-cf-0x02 21 | - cw-cf-0x03 22 | - cw-cf-0x04 23 | - cw-cf-0x05 24 | - cw-cf-0x06 25 | - cw-cf-0x07 26 | steps: 27 | - name: Checkout clockwise 28 | uses: actions/checkout@v3 29 | with: 30 | path: clockwise 31 | - name: Update submodules 32 | run: | 33 | cd $GITHUB_WORKSPACE/clockwise 34 | git submodule update --init firmware/clockfaces/ 35 | - name: Checkout GitHub Pages 36 | uses: actions/checkout@v3 37 | with: 38 | repository: jnthas/clockwise 39 | ref: gh-pages 40 | path: github-pages 41 | - name: Cache pip 42 | uses: actions/cache@v3 43 | with: 44 | path: ~/.cache/pip 45 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} 46 | restore-keys: | 47 | ${{ runner.os }}-pip- 48 | - name: Cache PlatformIO 49 | uses: actions/cache@v3 50 | with: 51 | path: ~/.platformio 52 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 53 | - name: Set up Python 54 | uses: actions/setup-python@v4 55 | - name: Install PlatformIO 56 | run: | 57 | python -m pip install --upgrade pip 58 | pip install --upgrade platformio 59 | 60 | - name: Pulling changes from ${{ matrix.clockfaces }} 61 | run: | 62 | cd $GITHUB_WORKSPACE/clockwise/firmware/clockfaces/${{ matrix.clockfaces }} 63 | git reset --hard origin/main 64 | git checkout main 65 | 66 | - name: Building ${{ matrix.clockfaces }} 67 | run: | 68 | export FW_NAME=CW_$(date '+%Y%m%d') 69 | ln -s ../clockfaces/${{ matrix.clockfaces }} $GITHUB_WORKSPACE/clockwise/firmware/lib/${{ matrix.clockfaces }} 70 | pio run --project-dir clockwise/firmware/ 71 | rm $GITHUB_WORKSPACE/clockwise/firmware/lib/${{ matrix.clockfaces }} 72 | - name: Publish new version 73 | run: | 74 | export FW_NAME=CW_$(date '+%Y%m%d') 75 | cd $GITHUB_WORKSPACE/github-pages 76 | git config user.name github-actions 77 | git config user.email github-actions@github.com 78 | git pull origin gh-pages 79 | cp --force $GITHUB_WORKSPACE/clockwise/firmware/.pio/build/esp32dev/firmware.bin static/firmware/${{ matrix.clockfaces }}/firmware.bin 80 | sed -i '/name/c\ \"name\": \"'"$FW_NAME"'"\,' static/firmware/${{ matrix.clockfaces }}/manifest.json 81 | git add . 82 | git commit -m "new version published - ${{ matrix.clockfaces }}" 83 | git push 84 | 85 | -------------------------------------------------------------------------------- /.github/workflows/esp-idf.yml: -------------------------------------------------------------------------------- 1 | name: ESP-IDF CI Actions 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '**.md' 7 | pull_request: 8 | paths-ignore: 9 | - '**.md' 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repo 18 | uses: actions/checkout@v3 19 | with: 20 | submodules: 'recursive' 21 | - name: esp-idf build 22 | uses: espressif/esp-idf-ci-action@v1 23 | with: 24 | esp_idf_version: v4.4.4 25 | target: esp32 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore the "build" directory created by esp-idf 2 | build 3 | 4 | # Ignore the user-customized project configuration file. 5 | # (https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/kconfig.html) 6 | sdkconfig* 7 | # Unignore the default project configuration file. 8 | !sdkconfig.defaults 9 | 10 | # Ignore IDE configs 11 | .vscode 12 | 13 | dependencies.lock -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "firmware/clockfaces/cw-cf-0x01"] 2 | path = firmware/clockfaces/cw-cf-0x01 3 | url = https://github.com/jnthas/cw-cf-0x01.git 4 | shallow = true 5 | [submodule "firmware/clockfaces/cw-cf-0x02"] 6 | path = firmware/clockfaces/cw-cf-0x02 7 | url = https://github.com/jnthas/cw-cf-0x02.git 8 | shallow = true 9 | [submodule "firmware/clockfaces/cw-cf-0x03"] 10 | path = firmware/clockfaces/cw-cf-0x03 11 | url = https://github.com/jnthas/cw-cf-0x03.git 12 | shallow = true 13 | [submodule "firmware/clockfaces/cw-cf-0x04"] 14 | path = firmware/clockfaces/cw-cf-0x04 15 | url = https://github.com/jnthas/cw-cf-0x04 16 | shallow = true 17 | [submodule "firmware/clockfaces/cw-cf-0x05"] 18 | path = firmware/clockfaces/cw-cf-0x05 19 | url = https://github.com/jnthas/cw-cf-0x05 20 | shallow = true 21 | [submodule "components/ezTime"] 22 | path = components/ezTime 23 | url = https://github.com/ropg/ezTime.git 24 | shallow = true 25 | [submodule "components/ESP32-HUB75-MatrixPanel-I2S-DMA"] 26 | path = components/ESP32-HUB75-MatrixPanel-I2S-DMA 27 | url = https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA.git 28 | shallow = true 29 | [submodule "components/Adafruit_BusIO"] 30 | path = components/Adafruit_BusIO 31 | url = https://github.com/adafruit/Adafruit_BusIO.git 32 | shallow = true 33 | [submodule "components/Adafruit-GFX-Library"] 34 | path = components/Adafruit-GFX-Library 35 | url = https://github.com/adafruit/Adafruit-GFX-Library.git 36 | shallow = true 37 | [submodule "components/arduino"] 38 | path = components/arduino 39 | url = https://github.com/espressif/arduino-esp32.git 40 | shallow = true 41 | [submodule "components/Improv-WiFi-Library"] 42 | path = components/Improv-WiFi-Library 43 | url = https://github.com/jnthas/Improv-WiFi-Library.git 44 | shallow = true 45 | [submodule "firmware/clockfaces/cw-cf-0x06"] 46 | path = firmware/clockfaces/cw-cf-0x06 47 | url = https://github.com/jnthas/cw-cf-0x06.git 48 | shallow = true 49 | [submodule "components/WiFiManager"] 50 | path = components/WiFiManager 51 | url = https://github.com/tzapu/WiFiManager 52 | shallow = true 53 | [submodule "firmware/clockfaces/cw-cf-0x07"] 54 | path = firmware/clockfaces/cw-cf-0x07 55 | url = https://github.com/jnthas/cw-cf-0x07.git 56 | shallow = true -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | 9 | ## [Unreleased] 10 | 11 | ### Changed 12 | 13 | - Avoid use extensive of String in CWWebServer.cpp 14 | 15 | 16 | ## [1.4.2] - 2024-04-21 17 | 18 | ### Added 19 | 20 | - New Display Rotation param. Thanks @Xefan. 21 | - Added ntp sync in main loop. Thanks @vandalon 22 | - Add api documentation 23 | - Pacman clockface: Add library.json to import using platformio 24 | - Canvas: Feature/add sprite loop and frame delay. Thanks @robegamesios 25 | 26 | 27 | ## [1.4.1] - 2023-08-27 28 | 29 | ### Added 30 | 31 | - New Manual Posix param to avoid the `timezoned.rop.nl` ezTime's timezone service. Thanks @JeffWDH! 32 | 33 | ### Changed 34 | 35 | - Set `time.google.com` as a default NTP server - `pool.ntp.org` is a slug 36 | 37 | ### Fixed 38 | 39 | - A bug in the Canvas clockface - Commit a216c29c4f15b1b3cadbd89805d150c2f551562b 40 | 41 | 42 | ## [1.4.0] - 2023-07-01 43 | 44 | ### Added 45 | 46 | - Canvas clockface 47 | - Created a method to make use of the ezTime formating string 48 | - Possibility to change Wifi user/pwd via API (must be connected) 49 | - A helper to make HTTP requests 50 | 51 | ### Changed 52 | 53 | - RGB icons used in the startup, it was replaced by one-bit images that reduce used flash 54 | 55 | 56 | ## [1.3.0] - 2023-06-11 57 | 58 | ### Added 59 | - Configure the NTP Server 60 | - Firmware version displayed on settings page 61 | - LDR GPIO configuration 62 | - Added a link in Settings page to read any pin on ESP32 (located in LDR Pin card) 63 | 64 | ### Changed 65 | 66 | - [ABC] It's possible to turnoff the display if the LDR reading < minBright 67 | 68 | 69 | ## [1.2.0] - 2023-05-14 70 | 71 | ### Added 72 | 73 | - Automatic bright control using LDR 74 | - Restart if offline for 5 minutes 75 | 76 | ### Fixed 77 | 78 | - Clockface 0x06 (Pokedex): show AM PM only when is not using 24h format 79 | - Restart endpoint returns HTTP 204 before restarting 80 | 81 | 82 | ## [1.1.0] - 2023-04-02 83 | 84 | ### Added 85 | 86 | - Implements Improv protocol to configure wifi 87 | - Create a Settings Page where user can set up: 88 | - swap blue/green pins 89 | - use 24h format or not 90 | - timezone 91 | - display bright 92 | 93 | ### Removed 94 | 95 | - Unused variables in main.cpp 96 | -------------------------------------------------------------------------------- /CHECKLIST.md: -------------------------------------------------------------------------------- 1 | # Release Checklist 2 | 3 | Just a checklist to follow the steps to be done when releasing a new version of Clockwise. I know, I must automate this. 4 | 5 | - [ ] Update website in [gh-pages](https://github.com/jnthas/clockwise/blob/gh-pages/index.md) 6 | - [ ] Update message at the top with most significant feature released 7 | - [ ] Update the [CHANGELOG.md](https://github.com/jnthas/clockwise/blob/main/CHANGELOG.md) 8 | - [ ] Update [README.md](https://github.com/jnthas/clockwise/blob/main/README.md) 9 | - [ ] Update message at the top with most significant feature released 10 | - [ ] Update Params section if new ones were added 11 | - [ ] Update firmware version in [platformio.ini](https://github.com/jnthas/clockwise/blob/aa22923dc195a3ffff8e95766e7ec9acda82e090/firmware/platformio.ini#L39) 12 | - [ ] For new clockfaces 13 | - [ ] Add new folder with the clockface code in the branch [gh-pages](https://github.com/jnthas/clockwise/tree/gh-pages/static/firmware/cw-cf-0x01) 14 | - [ ] Include the new clockface in the [CI/CD file](https://github.com/jnthas/clockwise/blob/main/.github/workflows/clockwise-ci.yml) 15 | - [ ] Add the [CMakeLists.txt](https://github.com/jnthas/cw-cf-0x07/blob/12eb5e70f0d4993d8531b871ad02f3964b76e582/CMakeLists.txt) in the new clokface repository 16 | - [ ] Check if [ESP-IDF CI](https://github.com/jnthas/clockwise/actions) is passing without errors 17 | - [ ] Create a branch called releases/1.x.x (it will trigger the build and release) 18 | - [ ] Create a tag (add the same description of the CHANGELOG) 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | set(EXTRA_COMPONENT_DIRS "firmware/clockfaces" "firmware/lib") 4 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 5 | project(clockwise) 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present Jonathas Amaral Barbosa (@jnthas) 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 | > ![News 90s GIF](https://github.com/jnthas/clockwise/raw/gh-pages/static/images/news.gif)[2024-04-21] [Version 1.4.2 released!](https://github.com/jnthas/clockwise/releases/tag/v1.4.2) Check the [change log](https://github.com/jnthas/clockwise/blob/main/CHANGELOG.md#142---2024-04-21) to see the fixes and new features added. Be part of the [Clock Club](https://github.com/jnthas/clock-club) and create your own clockface using Canvas. 2 | 3 | [![Clockwise CI/CD](https://github.com/jnthas/clockwise/actions/workflows/clockwise-ci.yml/badge.svg)](https://github.com/jnthas/clockwise/actions/workflows/clockwise-ci.yml) 4 | 5 | ![Logo](https://github.com/jnthas/clockwise/blob/gh-pages/static/images/clockwise_logo.png "Logo") 6 | 7 | > The DIY smart wall clock device 8 | 9 | Clockwise was an idea I had while working with 64x64 LED matrices. 10 | These displays are about the size of a wall clock and with the ESP32, besides controlling the content presented on the display we also gain the functionality of 11 | WiFi, Bluetooth, touch buttons and other sensors, which gives us basically a smart wall clock. 12 | From there I started to develop a platform to create the _Clockfaces_, or skins that the clock can have. The possibilities are many and I hope that with help from contributors, we can grow the options even more. 13 | 14 | ### ⏰ New Clockfaces 15 | Create a new custom Clockface starting from [here](https://github.com/jnthas/cw-cf-0x00) or take a look at the [Clock Club](https://github.com/jnthas/clock-club) and discover how to create new ones using just a JSON file with no coding. 16 | 17 | 18 | ## Available clockfaces 19 | 20 | Mario Bros. Clock | Time in Words 21 | :----------------:|:------------: 22 | ![Mario Bros. Clockface](https://github.com/jnthas/cw-cf-0x01/blob/main/cf_0x01_thumb.jpg "Mario Bros. Clockface") | ![Time in Words Clockface](https://github.com/jnthas/cw-cf-0x02/blob/main/cf_0x02_thumb.jpg "Time in Words Clockface") 23 | https://github.com/jnthas/cw-cf-0x01 | https://github.com/jnthas/cw-cf-0x02 24 | 25 | World Map Clock | Castlevania Clock Tower 26 | :--------------:|:----------------------: 27 | ![World Map Clockface](https://github.com/jnthas/cw-cf-0x03/blob/main/cf_0x03_thumb.jpg "World Map Clockface") | ![Castlevania Clockface](https://github.com/jnthas/cw-cf-0x04/blob/main/cf_0x04_thumb.jpg "Castlevania Clockface") 28 | https://github.com/jnthas/cw-cf-0x03 | https://github.com/jnthas/cw-cf-0x04 29 | 30 | Pacman | Pokedex 31 | :-----:|:------: 32 | ![Pacman Clockface](https://github.com/jnthas/cw-cf-0x05/blob/main/cf_0x05_thumb.jpg "Pacman Clockface") | ![Pokedex Clockface](https://github.com/jnthas/cw-cf-0x06/blob/main/cf_0x06_thumb.jpg "Pokedex Clockface") 33 | https://github.com/jnthas/cw-cf-0x05 | https://github.com/jnthas/cw-cf-0x06 34 | 35 | Canvas | Description 36 | :-----:|:------: 37 | Canvas Clockface | Canvas is a special type of Clockface
that is capable of rendering different
themes described in a JSON file.
Find out more [here](https://github.com/jnthas/clockwise/wiki/Canvas-Clockface). 38 | https://github.com/jnthas/cw-cf-0x07 | 39 | 40 | 41 | ## Driving the led matrix 42 | 43 | The three main hardware components of Clockwise are: 44 | - HUB75/HUB75E compatible LED matrix 64x64 45 | - an ESP32; and 46 | - a power supply of 3A or more 47 | 48 | With these components in hand, just follow the wiring instructions according to the library used, by default Clockwise uses the [ESP32-HUB75-MatrixPanel-I2S-DMA](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA#2-wiring-esp32-with-the-led-matrix-panel) but any Adafruit GFX compatible library should work. The default wiring connection is showed below. 49 | 50 | ![ESP32-HUB75-MatrixPanel-I2S-DMA wiring](https://github.com/jnthas/clockwise/blob/gh-pages/static/images/display_esp32_wiring_thumb.png "ESP32-HUB75-MatrixPanel-I2S-DMA wiring") 51 | 52 | [Full size](https://github.com/jnthas/clockwise/blob/gh-pages/static/images/display_esp32_wiring_bb.png) 53 | 54 | - In case you want something ready to use, I recommend Brian Lough's [ESP32 Trinity](https://github.com/witnessmenow/ESP32-Trinity), basically it's connecting the board and uploading the firmware, as simple as that. 55 | - If you want a designed PCB, I recommend this project from @Alexvanheu. It's compatible with HUB75/HUB75E led matrices and already tested with Clockwise https://github.com/Alexvanheu/Mario-Clock-PCB-ESP32 56 | - [ESP32 D1 Mini D1 RGB Matrix Shield](https://github.com/hallard/WeMos-Matrix-Shield-DMA) from @hallard is another option 57 | 58 | 59 | ## How to change the clockface (web flashing) 60 | 61 | 1) Go to https://clockwise.page/ and select the desired clockface 62 | 2) Connect the ESP32 device on your computer's USB port 63 | 3) Click on the Flash button 64 | 4) A dialog will appear, select the correct USB port and click in Connect ([screenshot](https://github.com/jnthas/clockwise/raw/gh-pages/static/images/usb-step1.png)) 65 | 5) Select the INSTALL and INSTALL again ([screenshot](https://github.com/jnthas/clockwise/raw/gh-pages/static/images/usb-step2.png)) 66 | 6) Wait while the flash tool uploads the firmware and finish ([screenshot](https://github.com/jnthas/clockwise/raw/gh-pages/static/images/usb-step3.png)) 67 | 7) From the version 1.1.0, click in NEXT on step 6, Improv will start looking for available WiFi networks to connect 68 | 8) Select your local network (must be a 2.4GHz) and enter with your password ([screenshot](https://github.com/jnthas/clockwise/raw/gh-pages/static/images/usb-step4.png)) 69 | 9) If connection was successful, a message with button VISIT DEVICE will pop up and you can visit the Clockwise setting page ([screenshot](https://github.com/jnthas/clockwise/raw/gh-pages/static/images/usb-step5.png)) 70 | 71 | 72 | ### Configuring only WiFi 73 | After flashing your clockface, you will have a step to configure the WiFi. But in case you change your access point or password, you can set up just the WiFi connecting the Clockwise on USB, opening https://clockwise.page and clicking in Flash button, a window will pop up with a few options where you can re-configure your WiFi network ([screenshot](https://github.com/jnthas/clockwise/raw/gh-pages/static/images/usb-step6.png)) as well as open the Settings page to change preferences using button VISIT DEVICE. Remember: it is important to use a 2.4GHz WiFi, it will not work on 5GHz. 74 | 75 | 76 | ### Settings page 77 | The settings page have the following options 78 | - *Timezone*: The timezone must be in the format America/New_York, America/Sao_Paulo, Europe/Paris, Asia/Dubai, etc. so that the clock can connect to an NTP server to get the correct time. 79 | - *NTP Server*: By default the clock will sync with `pool.ntp.org`, but you can configure your own (local) NTP server to be used. 80 | - *Swap Blue/Green pins*: Some displays have the RGB order different, in this case RBG. You can use this options to change the order. 81 | - *Display Bright*: Change the display bright. 82 | - *Use 24h format*: You can choose between 20:00 or 8:00PM in your device. 83 | - *Automatic Bright*: Once you connect a LDR in the ESP32, Clockwise will be able to control the display bright based on the ambient light. Check the [Wiki]( 84 | https://github.com/jnthas/clockwise/wiki/Connecting-the-LDR) about that. 85 | - *NTP Server*: Configure your prefered NTP Server. You can use one of the [NTP Pool Project](https://www.ntppool.org/) pools or a local one. Default is `time.google.com`. 86 | - *LDR Pin*: The ESP32 GPIO pin where the LDR is connected to. The default is 35. There is a link there where you can read the current value of LDR and test if it's working. 87 | - *Posix Timezone String*: To avoid remote lookups of ezTime, provide a Posix string that corresponds to your timezone ([explanation](https://github.com/ropg/ezTime#timezones-1)). Leave empty to obtain this automatically from the server. 88 | - *Display Rotation*: Allows you to rotate the display. This is useful if you need to adjust the direction in which cables protrude relative to the displayed image. 89 | 90 | ## How to change the clockface (PlatformIO) 91 | 92 | Clockwise uses PlatformIO as IDE, so the configuration is already done if you use the same. The Clockwise structure consists mainly of three folders 93 | - clockfaces: contains the collection of available clockfaces. This folder is not included when compiling 94 | - lib: contains the basic code for Clockwise to work and in addition a symbolic link to the current clockface 95 | - src: contains the entry point for the clock code 96 | 97 | ``` 98 | . 99 | ├── clockfaces 100 | │ ├── cw-cf-0x01 101 | │ ├── cw-cf-0x02 102 | │ └── cw-cf-0x03 103 | ├── lib 104 | │ ├── cw-commons 105 | │ ├── cw-gfx-engine 106 | │ └── timeinwords -> ../clockfaces/cw-cf-0x02/ 107 | └── src 108 | └── main.cpp 109 | 110 | ``` 111 | Clone this repository and then run the following command to clone the clockface submodules 112 | 113 | ``.../clockwise$ git submodule update --init firmware/clockfaces`` 114 | 115 | To create the symbolic link run the following command inside lib/ folder: 116 | 117 | ``.../clockwise/firmware/lib$ ln -s ../clockfaces/cw-cf-0x02/ timeinwords`` 118 | 119 | Or, if you prefer, you can get the same result by copying the desired clockface folder into lib/ 120 | 121 | The same way as web flashing, when connecting for the first time you will have to configure the wifi, follow the instructions in Configuring WiFi section above. 122 | 123 | ## How to change the clockface (esp-idf) 124 | 125 | You can use the [official Esspressif IoT Development Framekwork (aka esp-idf)](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/) to build and upload this project to an ESP32 device, including the [ESP32-Trinity board](https://esp32trinity.com/). 126 | 127 | ### Install esp-idf 128 | Follow the [Step By Step installation instructions](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/#installation-step-by-step). 129 | 130 | ### Setup the environment variables 131 | Follow the [instructions here](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/#step-4-set-up-the-environment-variables). 132 | 133 | ### Clone and build this project 134 | * `git clone --recurse-submodules https://github.com/jnthas/clockwise.git` 135 | * `idf.py reconfigure` 136 | * `idf.py menuconfig` (select `Clockwise Configuration` and choose the clockface) 137 | * `idf.py flash` 138 | * `idf.py monitor` 139 | -------------------------------------------------------------------------------- /firmware/.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/c++,platformio,vscode 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=c++,platformio,vscode 3 | 4 | ### C++ ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Compiled Object files 9 | *.slo 10 | *.lo 11 | *.o 12 | *.obj 13 | 14 | # Precompiled Headers 15 | *.gch 16 | *.pch 17 | 18 | # Compiled Dynamic libraries 19 | *.so 20 | *.dylib 21 | *.dll 22 | 23 | # Fortran module files 24 | *.mod 25 | *.smod 26 | 27 | # Compiled Static libraries 28 | *.lai 29 | *.la 30 | *.a 31 | *.lib 32 | 33 | # Executables 34 | *.exe 35 | *.out 36 | *.app 37 | 38 | ### PlatformIO ### 39 | .pioenvs 40 | .piolibdeps 41 | .clang_complete 42 | .gcc-flags.json 43 | .pio 44 | 45 | ### vscode ### 46 | .vscode/* 47 | !.vscode/settings.json 48 | !.vscode/tasks.json 49 | !.vscode/launch.json 50 | !.vscode/extensions.json 51 | *.code-workspace 52 | 53 | # End of https://www.toptal.com/developers/gitignore/api/c++,platformio,vscode -------------------------------------------------------------------------------- /firmware/.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 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /firmware/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // AUTOMATICALLY GENERATED FILE. PLEASE DO NOT MODIFY IT MANUALLY 2 | // 3 | // PlatformIO Debugging Solution 4 | // 5 | // Documentation: https://docs.platformio.org/en/latest/plus/debugging.html 6 | // Configuration: https://docs.platformio.org/en/latest/projectconf/sections/env/options/debug/index.html 7 | 8 | { 9 | "version": "0.2.0", 10 | "configurations": [ 11 | { 12 | "type": "platformio-debug", 13 | "request": "launch", 14 | "name": "PIO Debug", 15 | "executable": "/home/jonathas/projects/clockwise/firmware/.pio/build/esp32dev/firmware.elf", 16 | "projectEnvName": "esp32dev", 17 | "toolchainBinDir": "/home/jonathas/.platformio/packages/toolchain-xtensa-esp32/bin", 18 | "internalConsoleOptions": "openOnSessionStart", 19 | "preLaunchTask": { 20 | "type": "PlatformIO", 21 | "task": "Pre-Debug" 22 | } 23 | }, 24 | { 25 | "type": "platformio-debug", 26 | "request": "launch", 27 | "name": "PIO Debug (skip Pre-Debug)", 28 | "executable": "/home/jonathas/projects/clockwise/firmware/.pio/build/esp32dev/firmware.elf", 29 | "projectEnvName": "esp32dev", 30 | "toolchainBinDir": "/home/jonathas/.platformio/packages/toolchain-xtensa-esp32/bin", 31 | "internalConsoleOptions": "openOnSessionStart" 32 | }, 33 | { 34 | "type": "platformio-debug", 35 | "request": "launch", 36 | "name": "PIO Debug (without uploading)", 37 | "executable": "/home/jonathas/projects/clockwise/firmware/.pio/build/esp32dev/firmware.elf", 38 | "projectEnvName": "esp32dev", 39 | "toolchainBinDir": "/home/jonathas/.platformio/packages/toolchain-xtensa-esp32/bin", 40 | "internalConsoleOptions": "openOnSessionStart", 41 | "loadMode": "manual" 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /firmware/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "atomic": "cpp", 5 | "*.tcc": "cpp", 6 | "cctype": "cpp", 7 | "clocale": "cpp", 8 | "cmath": "cpp", 9 | "cstdarg": "cpp", 10 | "cstddef": "cpp", 11 | "cstdint": "cpp", 12 | "cstdio": "cpp", 13 | "cstdlib": "cpp", 14 | "cstring": "cpp", 15 | "ctime": "cpp", 16 | "cwchar": "cpp", 17 | "cwctype": "cpp", 18 | "deque": "cpp", 19 | "unordered_map": "cpp", 20 | "unordered_set": "cpp", 21 | "vector": "cpp", 22 | "exception": "cpp", 23 | "algorithm": "cpp", 24 | "functional": "cpp", 25 | "iterator": "cpp", 26 | "map": "cpp", 27 | "memory": "cpp", 28 | "memory_resource": "cpp", 29 | "numeric": "cpp", 30 | "optional": "cpp", 31 | "random": "cpp", 32 | "string": "cpp", 33 | "string_view": "cpp", 34 | "system_error": "cpp", 35 | "tuple": "cpp", 36 | "type_traits": "cpp", 37 | "utility": "cpp", 38 | "fstream": "cpp", 39 | "initializer_list": "cpp", 40 | "iomanip": "cpp", 41 | "iosfwd": "cpp", 42 | "istream": "cpp", 43 | "limits": "cpp", 44 | "new": "cpp", 45 | "ostream": "cpp", 46 | "sstream": "cpp", 47 | "stdexcept": "cpp", 48 | "streambuf": "cpp", 49 | "cinttypes": "cpp", 50 | "typeinfo": "cpp" 51 | } 52 | } -------------------------------------------------------------------------------- /firmware/include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /firmware/lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | # ezTime 4 | # This can be removed if https://github.com/ropg/ezTime/pull/157 is merged in. 5 | set (SRC_DIRS ${PROJECT_DIR}/components/ezTime/src) 6 | set (INCLUDE_DIRS ${PROJECT_DIR}/components/ezTime/src) 7 | 8 | 9 | idf_component_register( 10 | INCLUDE_DIRS "." ${SRC_DIRS} 11 | SRC_DIRS "." ${INCLUDE_DIRS} 12 | REQUIRES arduino 13 | ) 14 | 15 | project(cw-gfx-commons) 16 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/CWDateTime.cpp: -------------------------------------------------------------------------------- 1 | #include "CWDateTime.h" 2 | 3 | void CWDateTime::begin(const char *timeZone, bool use24format, const char *ntpServer = NTP_SERVER, const char *posixTZ = "") 4 | { 5 | Serial.printf("[Time] NTP Server: %s, Timezone: %s\n", ntpServer, timeZone); 6 | ezt::setServer(String(ntpServer)); 7 | 8 | if (strlen(posixTZ) > 1) { 9 | // An empty value still contains a null character so not empty is a value greater than 1. 10 | // Set to defined Posix TZ 11 | myTZ.setPosix(posixTZ); 12 | } else { 13 | // Use automatic eztime remote lookup 14 | myTZ.setLocation(timeZone); 15 | } 16 | 17 | this->use24hFormat = use24format; 18 | ezt::updateNTP(); 19 | waitForSync(10); 20 | } 21 | 22 | String CWDateTime::getFormattedTime() 23 | { 24 | return myTZ.dateTime(); 25 | } 26 | 27 | String CWDateTime::getFormattedTime(const char *format) 28 | { 29 | return myTZ.dateTime(format); 30 | } 31 | 32 | char *CWDateTime::getHour(const char *format) 33 | { 34 | static char buffer[3] = {'\0'}; 35 | strncpy(buffer, myTZ.dateTime((use24hFormat ? "H" : "h")).c_str(), sizeof(buffer)); 36 | return buffer; 37 | } 38 | 39 | char *CWDateTime::getMinute(const char *format) 40 | { 41 | static char buffer[3] = {'\0'}; 42 | strncpy(buffer, myTZ.dateTime("i").c_str(), sizeof(buffer)); 43 | return buffer; 44 | } 45 | 46 | int CWDateTime::getHour() 47 | { 48 | return myTZ.dateTime((use24hFormat ? "H" : "h")).toInt(); 49 | } 50 | 51 | int CWDateTime::getMinute() 52 | { 53 | return myTZ.dateTime("i").toInt(); 54 | } 55 | 56 | int CWDateTime::getSecond() 57 | { 58 | return myTZ.dateTime("s").toInt(); 59 | } 60 | 61 | int CWDateTime::getDay() 62 | { 63 | return myTZ.dateTime("d").toInt(); 64 | } 65 | int CWDateTime::getMonth() 66 | { 67 | return myTZ.dateTime("m").toInt(); 68 | } 69 | int CWDateTime::getWeekday() 70 | { 71 | return myTZ.dateTime("w").toInt()-1; 72 | } 73 | 74 | long CWDateTime::getMilliseconds() 75 | { 76 | return myTZ.ms(TIME_NOW); 77 | } 78 | 79 | bool CWDateTime::isAM() 80 | { 81 | return myTZ.isAM(); 82 | } 83 | 84 | bool CWDateTime::is24hFormat() 85 | { 86 | return this->use24hFormat; 87 | } -------------------------------------------------------------------------------- /firmware/lib/cw-commons/CWDateTime.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | class CWDateTime 8 | { 9 | private: 10 | Timezone myTZ; 11 | bool use24hFormat = true; 12 | 13 | public: 14 | void begin(const char *timeZone, bool use24format, const char *ntpServer, const char *posixTZ); 15 | String getFormattedTime(); 16 | String getFormattedTime(const char* format); 17 | 18 | char *getHour(const char *format); 19 | char *getMinute(const char *format); 20 | int getHour(); 21 | int getMinute(); 22 | int getSecond(); 23 | long getMilliseconds(); 24 | 25 | int getDay(); 26 | int getMonth(); 27 | int getWeekday(); 28 | 29 | bool isAM(); 30 | bool is24hFormat(); 31 | }; 32 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/CWHttpClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | struct ClockwiseHttpClient 7 | { 8 | 9 | static ClockwiseHttpClient *getInstance() 10 | { 11 | static ClockwiseHttpClient base; 12 | return &base; 13 | } 14 | 15 | void httpGet(WiFiClientSecure *client, const char *host, const char *path, const uint16_t port) 16 | { 17 | Serial.printf("[HTTP] GET request to '%s%s' on port %d\n", host, path, port); 18 | 19 | if (WiFi.status() != WL_CONNECTED) 20 | { 21 | Serial.println("Not connected"); 22 | return; 23 | } 24 | 25 | client->setInsecure(); 26 | client->setTimeout(10000); 27 | if (!client->connect(host, port)) 28 | { 29 | Serial.println(F("Connection failed")); 30 | return; 31 | } 32 | 33 | // Send HTTP request 34 | client->printf("GET %s HTTP/1.1\r\n", path); 35 | client->printf("Host: %s\r\n", host); 36 | client->println(F("Connection: close")); 37 | if (client->println() == 0) 38 | { 39 | Serial.println(F("Failed to send request")); 40 | client->stop(); 41 | return; 42 | } 43 | 44 | // char arrCode[4]; 45 | // memcpy(arrCode, status + 8, 3); //HTTP/1.1 404 Not Found 46 | // arrCode[3] = 0; 47 | // uint16_t httpCode = atoi(arrCode); 48 | 49 | // Check HTTP status 50 | char status[32] = {0}; 51 | client->readBytesUntil('\r', status, sizeof(status)); 52 | 53 | if (strstr(status, "200 OK") == NULL) 54 | { 55 | Serial.print(F("Unexpected response: ")); 56 | Serial.println(status); 57 | client->stop(); 58 | return; 59 | } 60 | 61 | // Skip HTTP headers 62 | char endOfHeaders[] = "\r\n\r\n"; 63 | if (!client->find(endOfHeaders)) 64 | { 65 | Serial.println(F("Invalid response")); 66 | client->stop(); 67 | return; 68 | } 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/CWPreferences.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef CW_PREF_DB_NAME 6 | #define CW_PREF_DB_NAME "clockwise" 7 | #endif 8 | 9 | 10 | struct ClockwiseParams 11 | { 12 | Preferences preferences; 13 | 14 | const char* const PREF_SWAP_BLUE_GREEN = "swapBlueGreen"; 15 | const char* const PREF_USE_24H_FORMAT = "use24hFormat"; 16 | const char* const PREF_DISPLAY_BRIGHT = "displayBright"; 17 | const char* const PREF_DISPLAY_ABC_MIN = "autoBrightMin"; 18 | const char* const PREF_DISPLAY_ABC_MAX = "autoBrightMax"; 19 | const char* const PREF_LDR_PIN = "ldrPin"; 20 | const char* const PREF_TIME_ZONE = "timeZone"; 21 | const char* const PREF_WIFI_SSID = "wifiSsid"; 22 | const char* const PREF_WIFI_PASSWORD = "wifiPwd"; 23 | const char* const PREF_NTP_SERVER = "ntpServer"; 24 | const char* const PREF_CANVAS_FILE = "canvasFile"; 25 | const char* const PREF_CANVAS_SERVER = "canvasServer"; 26 | const char* const PREF_MANUAL_POSIX = "manualPosix"; 27 | const char* const PREF_DISPLAY_ROTATION = "displayRotation"; 28 | 29 | bool swapBlueGreen; 30 | bool use24hFormat; 31 | uint8_t displayBright; 32 | uint16_t autoBrightMin; 33 | uint16_t autoBrightMax; 34 | uint8_t ldrPin; 35 | String timeZone; 36 | String wifiSsid; 37 | String wifiPwd; 38 | String ntpServer; 39 | String canvasFile; 40 | String canvasServer; 41 | String manualPosix; 42 | uint8_t displayRotation; 43 | 44 | 45 | ClockwiseParams() { 46 | preferences.begin("clockwise", false); 47 | //preferences.clear(); 48 | } 49 | 50 | static ClockwiseParams* getInstance() { 51 | static ClockwiseParams base; 52 | return &base; 53 | } 54 | 55 | 56 | void save() 57 | { 58 | preferences.putBool(PREF_SWAP_BLUE_GREEN, swapBlueGreen); 59 | preferences.putBool(PREF_USE_24H_FORMAT, use24hFormat); 60 | preferences.putUInt(PREF_DISPLAY_BRIGHT, displayBright); 61 | preferences.putUInt(PREF_DISPLAY_ABC_MIN, autoBrightMin); 62 | preferences.putUInt(PREF_DISPLAY_ABC_MAX, autoBrightMax); 63 | preferences.putUInt(PREF_LDR_PIN, ldrPin); 64 | preferences.putString(PREF_TIME_ZONE, timeZone); 65 | preferences.putString(PREF_WIFI_SSID, wifiSsid); 66 | preferences.putString(PREF_WIFI_PASSWORD, wifiPwd); 67 | preferences.putString(PREF_NTP_SERVER, ntpServer); 68 | preferences.putString(PREF_CANVAS_FILE, canvasFile); 69 | preferences.putString(PREF_CANVAS_SERVER, canvasServer); 70 | preferences.putString(PREF_MANUAL_POSIX, manualPosix); 71 | preferences.putUInt(PREF_DISPLAY_ROTATION, displayRotation); 72 | } 73 | 74 | void load() 75 | { 76 | swapBlueGreen = preferences.getBool(PREF_SWAP_BLUE_GREEN, false); 77 | use24hFormat = preferences.getBool(PREF_USE_24H_FORMAT, true); 78 | displayBright = preferences.getUInt(PREF_DISPLAY_BRIGHT, 32); 79 | autoBrightMin = preferences.getUInt(PREF_DISPLAY_ABC_MIN, 0); 80 | autoBrightMax = preferences.getUInt(PREF_DISPLAY_ABC_MAX, 0); 81 | ldrPin = preferences.getUInt(PREF_LDR_PIN, 35); 82 | timeZone = preferences.getString(PREF_TIME_ZONE, "America/Sao_Paulo"); 83 | wifiSsid = preferences.getString(PREF_WIFI_SSID, ""); 84 | wifiPwd = preferences.getString(PREF_WIFI_PASSWORD, ""); 85 | ntpServer = preferences.getString(PREF_NTP_SERVER, "time.google.com"); 86 | canvasFile = preferences.getString(PREF_CANVAS_FILE, ""); 87 | canvasServer = preferences.getString(PREF_CANVAS_SERVER, "raw.githubusercontent.com"); 88 | manualPosix = preferences.getString(PREF_MANUAL_POSIX, ""); 89 | displayRotation = preferences.getUInt(PREF_DISPLAY_ROTATION, 0); 90 | } 91 | 92 | }; 93 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/CWWebServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "StatusController.h" 6 | #include "SettingsWebPage.h" 7 | 8 | #ifndef CLOCKFACE_NAME 9 | #define CLOCKFACE_NAME "UNKNOWN" 10 | #endif 11 | 12 | WiFiServer server(80); 13 | 14 | struct ClockwiseWebServer 15 | { 16 | String httpBuffer; 17 | bool force_restart; 18 | const char* HEADER_TEMPLATE_D = "X-%s: %d\r\n"; 19 | const char* HEADER_TEMPLATE_S = "X-%s: %s\r\n"; 20 | 21 | static ClockwiseWebServer *getInstance() 22 | { 23 | static ClockwiseWebServer base; 24 | return &base; 25 | } 26 | 27 | void startWebServer() 28 | { 29 | server.begin(); 30 | StatusController::getInstance()->blink_led(100, 3); 31 | } 32 | 33 | void stopWebServer() 34 | { 35 | server.stop(); 36 | } 37 | 38 | void handleHttpRequest() 39 | { 40 | if (force_restart) 41 | StatusController::getInstance()->forceRestart(); 42 | 43 | 44 | WiFiClient client = server.available(); 45 | if (client) 46 | { 47 | StatusController::getInstance()->blink_led(100, 1); 48 | 49 | while (client.connected()) 50 | { 51 | if (client.available()) 52 | { 53 | char c = client.read(); 54 | httpBuffer.concat(c); 55 | 56 | if (c == '\n') 57 | { 58 | uint8_t method_pos = httpBuffer.indexOf(' '); 59 | uint8_t path_pos = httpBuffer.indexOf(' ', method_pos + 1); 60 | 61 | String method = httpBuffer.substring(0, method_pos); 62 | String path = httpBuffer.substring(method_pos + 1, path_pos); 63 | String key = ""; 64 | String value = ""; 65 | 66 | if (path.indexOf('?') > 0) 67 | { 68 | key = path.substring(path.indexOf('?') + 1, path.indexOf('=')); 69 | value = path.substring(path.indexOf('=') + 1); 70 | path = path.substring(0, path.indexOf('?')); 71 | } 72 | 73 | processRequest(client, method, path, key, value); 74 | httpBuffer = ""; 75 | break; 76 | } 77 | } 78 | } 79 | delay(1); 80 | client.stop(); 81 | } 82 | } 83 | 84 | void processRequest(WiFiClient client, String method, String path, String key, String value) 85 | { 86 | if (method == "GET" && path == "/") { 87 | client.println("HTTP/1.0 200 OK"); 88 | client.println("Content-Type: text/html"); 89 | client.println(); 90 | client.println(SETTINGS_PAGE); 91 | } else if (method == "GET" && path == "/get") { 92 | getCurrentSettings(client); 93 | } else if (method == "GET" && path == "/read") { 94 | if (key == "pin") { 95 | readPin(client, key, value.toInt()); 96 | } 97 | } else if (method == "POST" && path == "/restart") { 98 | client.println("HTTP/1.0 204 No Content"); 99 | force_restart = true; 100 | } else if (method == "POST" && path == "/set") { 101 | ClockwiseParams::getInstance()->load(); 102 | //a baby seal has died due this ifs 103 | if (key == ClockwiseParams::getInstance()->PREF_DISPLAY_BRIGHT) { 104 | ClockwiseParams::getInstance()->displayBright = value.toInt(); 105 | } else if (key == ClockwiseParams::getInstance()->PREF_WIFI_SSID) { 106 | ClockwiseParams::getInstance()->wifiSsid = value; 107 | } else if (key == ClockwiseParams::getInstance()->PREF_WIFI_PASSWORD) { 108 | ClockwiseParams::getInstance()->wifiPwd = value; 109 | } else if (key == "autoBright") { //autoBright=0010,0800 110 | ClockwiseParams::getInstance()->autoBrightMin = value.substring(0,4).toInt(); 111 | ClockwiseParams::getInstance()->autoBrightMax = value.substring(5,9).toInt(); 112 | } else if (key == ClockwiseParams::getInstance()->PREF_SWAP_BLUE_GREEN) { 113 | ClockwiseParams::getInstance()->swapBlueGreen = (value == "1"); 114 | } else if (key == ClockwiseParams::getInstance()->PREF_USE_24H_FORMAT) { 115 | ClockwiseParams::getInstance()->use24hFormat = (value == "1"); 116 | } else if (key == ClockwiseParams::getInstance()->PREF_LDR_PIN) { 117 | ClockwiseParams::getInstance()->ldrPin = value.toInt(); 118 | } else if (key == ClockwiseParams::getInstance()->PREF_TIME_ZONE) { 119 | ClockwiseParams::getInstance()->timeZone = value; 120 | } else if (key == ClockwiseParams::getInstance()->PREF_NTP_SERVER) { 121 | ClockwiseParams::getInstance()->ntpServer = value; 122 | } else if (key == ClockwiseParams::getInstance()->PREF_CANVAS_FILE) { 123 | ClockwiseParams::getInstance()->canvasFile = value; 124 | } else if (key == ClockwiseParams::getInstance()->PREF_CANVAS_SERVER) { 125 | ClockwiseParams::getInstance()->canvasServer = value; 126 | } else if (key == ClockwiseParams::getInstance()->PREF_MANUAL_POSIX) { 127 | ClockwiseParams::getInstance()->manualPosix = value; 128 | } else if (key == ClockwiseParams::getInstance()->PREF_DISPLAY_ROTATION) { 129 | ClockwiseParams::getInstance()->displayRotation = value.toInt(); 130 | } 131 | ClockwiseParams::getInstance()->save(); 132 | client.println("HTTP/1.0 204 No Content"); 133 | } 134 | } 135 | 136 | 137 | 138 | void readPin(WiFiClient client, String key, uint16_t pin) { 139 | ClockwiseParams::getInstance()->load(); 140 | 141 | client.println("HTTP/1.0 204 No Content"); 142 | client.printf(HEADER_TEMPLATE_D, key, analogRead(pin)); 143 | 144 | client.println(); 145 | } 146 | 147 | 148 | void getCurrentSettings(WiFiClient client) { 149 | ClockwiseParams::getInstance()->load(); 150 | 151 | client.println("HTTP/1.0 204 No Content"); 152 | 153 | client.printf(HEADER_TEMPLATE_D, ClockwiseParams::getInstance()->PREF_DISPLAY_BRIGHT, ClockwiseParams::getInstance()->displayBright); 154 | client.printf(HEADER_TEMPLATE_D, ClockwiseParams::getInstance()->PREF_DISPLAY_ABC_MIN, ClockwiseParams::getInstance()->autoBrightMin); 155 | client.printf(HEADER_TEMPLATE_D, ClockwiseParams::getInstance()->PREF_DISPLAY_ABC_MAX, ClockwiseParams::getInstance()->autoBrightMax); 156 | client.printf(HEADER_TEMPLATE_D, ClockwiseParams::getInstance()->PREF_SWAP_BLUE_GREEN, ClockwiseParams::getInstance()->swapBlueGreen); 157 | client.printf(HEADER_TEMPLATE_D, ClockwiseParams::getInstance()->PREF_USE_24H_FORMAT, ClockwiseParams::getInstance()->use24hFormat); 158 | client.printf(HEADER_TEMPLATE_D, ClockwiseParams::getInstance()->PREF_LDR_PIN, ClockwiseParams::getInstance()->ldrPin); 159 | client.printf(HEADER_TEMPLATE_S, ClockwiseParams::getInstance()->PREF_TIME_ZONE, ClockwiseParams::getInstance()->timeZone.c_str()); 160 | client.printf(HEADER_TEMPLATE_S, ClockwiseParams::getInstance()->PREF_WIFI_SSID, ClockwiseParams::getInstance()->wifiSsid.c_str()); 161 | client.printf(HEADER_TEMPLATE_S, ClockwiseParams::getInstance()->PREF_NTP_SERVER, ClockwiseParams::getInstance()->ntpServer.c_str()); 162 | client.printf(HEADER_TEMPLATE_S, ClockwiseParams::getInstance()->PREF_CANVAS_FILE, ClockwiseParams::getInstance()->canvasFile.c_str()); 163 | client.printf(HEADER_TEMPLATE_S, ClockwiseParams::getInstance()->PREF_CANVAS_SERVER, ClockwiseParams::getInstance()->canvasServer.c_str()); 164 | client.printf(HEADER_TEMPLATE_S, ClockwiseParams::getInstance()->PREF_MANUAL_POSIX, ClockwiseParams::getInstance()->manualPosix.c_str()); 165 | client.printf(HEADER_TEMPLATE_D, ClockwiseParams::getInstance()->PREF_DISPLAY_ROTATION, ClockwiseParams::getInstance()->displayRotation); 166 | 167 | client.printf(HEADER_TEMPLATE_S, "CW_FW_VERSION", CW_FW_VERSION); 168 | client.printf(HEADER_TEMPLATE_S, "CW_FW_NAME", CW_FW_NAME); 169 | client.printf(HEADER_TEMPLATE_S, "CLOCKFACE_NAME", CLOCKFACE_NAME); 170 | client.println(); 171 | } 172 | 173 | }; 174 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/IClockface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CWDateTime.h" 4 | 5 | class IClockface { 6 | 7 | //virtual void setup(DateTime *dateTime) = 0; 8 | virtual void setup(CWDateTime *dateTime) = 0; 9 | virtual void update() = 0; 10 | 11 | }; 12 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/Icons.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #if defined(__AVR__) 6 | #include 7 | #elif defined(__PIC32MX__) 8 | #define PROGMEM 9 | #elif defined(__arm__) 10 | #define PROGMEM 11 | #endif 12 | 13 | 14 | const unsigned short WIFI[64] PROGMEM={ 15 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, // 0x0010 (16) pixels 16 | 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0xFFFF, // 0x0020 (32) pixels 17 | 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0030 (48) pixels 18 | 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0040 (64) pixels 19 | }; 20 | 21 | const unsigned short MAIL[64] PROGMEM={ 22 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, // 0x0010 (16) pixels 23 | 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, // 0x0020 (32) pixels 24 | 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x0030 (48) pixels 25 | 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0040 (64) pixels 26 | }; 27 | 28 | const unsigned short WEATHER_CLOUDY_SUN[64] PROGMEM={ 29 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 0x0010 (16) pixels 30 | 0x0000, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, // 0x0020 (32) pixels 31 | 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, // 0x0030 (48) pixels 32 | 0xFFFF, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0x0000, // 0x0040 (64) pixels 33 | }; 34 | 35 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/README.md: -------------------------------------------------------------------------------- 1 | # cw-commons 2 | Clockwise common resources 3 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/SettingsWebPage.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // TODO: Move the whole JS to a CDN soon 6 | 7 | const char SETTINGS_PAGE[] PROGMEM = R""""( 8 | 9 | 10 | Clockwise Settings 11 | 12 | 13 | 14 | 16 | 17 | 18 |
19 | Logo 22 |
23 | 24 |
25 |
26 |
27 |
Restart
28 | 29 |
30 | 31 |
32 | 51 |
52 | 238 | 239 | 240 | )""""; 241 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/StatusController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "picopixel.h" 6 | 7 | #define ESP32_LED_BUILTIN 2 8 | 9 | const uint8_t CW_STATUS_NTP[] PROGMEM = { 10 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x40, 0x00, 0x08, 0x01, 11 | 0x4b, 0x8e, 0xe8, 0x03, 0x58, 0xaa, 0xa8, 0x07, 0x4b, 0x8a, 0xa8, 0x67, 0x4a, 0x2a, 0xa8, 0xf7, 12 | 0x4b, 0x8e, 0xe9, 0xff, 0x40, 0x00, 0x0b, 0xff, 0x3f, 0xff, 0xf3, 0xff, 0x00, 0x00, 0x03, 0xff, 13 | 0x00, 0x0f, 0xc1, 0xff, 0x00, 0x3f, 0xf0, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0xff, 0xf8, 0x70, 14 | 0x01, 0xff, 0xfd, 0xfc, 0x19, 0xff, 0xff, 0xfe, 0x3d, 0xff, 0xff, 0xfe, 0x7f, 0xff, 0xff, 0xff, 15 | 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xfe, 16 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 18 | }; 19 | 20 | const uint8_t CW_STATUS_WIFI[] PROGMEM = { 21 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 22 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xff, 0xc0, 23 | 0x0f, 0xff, 0xff, 0xf0, 0x1f, 0xff, 0xff, 0xf8, 0x7f, 0xf8, 0x1f, 0xfe, 0xff, 0xc0, 0x03, 0xff, 24 | 0x7f, 0x00, 0x00, 0xfe, 0x3c, 0x07, 0xe0, 0x3c, 0x18, 0x3f, 0xfc, 0x18, 0x00, 0xff, 0xff, 0x00, 25 | 0x01, 0xff, 0xff, 0x80, 0x03, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0x80, 0x00, 0xf0, 0x0f, 0x00, 26 | 0x00, 0x60, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 27 | 0x00, 0x07, 0xe0, 0x00, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 29 | }; 30 | 31 | const uint8_t CW_STATUS_DINO[] PROGMEM = { 32 | 0x00, 0xff, 0xff, 0x00, 0x03, 0xf8, 0x1f, 0xc0, 0x07, 0xc0, 0x03, 0xe0, 0x0f, 0x00, 0x00, 0xf0, 33 | 0x1e, 0x00, 0x00, 0x78, 0x3c, 0x00, 0x00, 0x3c, 0x78, 0x1f, 0xff, 0x9e, 0x70, 0x3f, 0xff, 0xee, 34 | 0xe0, 0x33, 0xff, 0xe7, 0xe0, 0x73, 0xff, 0xe7, 0xc0, 0x7f, 0xff, 0xe3, 0xc0, 0x7f, 0xff, 0xe3, 35 | 0xc0, 0x7f, 0xff, 0xe3, 0x80, 0x7f, 0xff, 0xe1, 0x80, 0x7f, 0xff, 0xe1, 0x80, 0x7f, 0xff, 0xe1, 36 | 0x80, 0x7f, 0x80, 0x01, 0x80, 0x7f, 0x80, 0x01, 0x80, 0x3f, 0xfe, 0x01, 0xc0, 0x3f, 0xfe, 0x03, 37 | 0xc0, 0x3e, 0x00, 0x03, 0xc0, 0xfe, 0x00, 0x03, 0xe0, 0xfe, 0x00, 0x07, 0xe7, 0xfe, 0x00, 0x07, 38 | 0x7f, 0xff, 0xf0, 0x0e, 0x7f, 0xff, 0xf0, 0x1e, 0x3f, 0xfe, 0x30, 0x3c, 0x1f, 0xfe, 0x00, 0x78, 39 | 0x0f, 0xfe, 0x00, 0xf0, 0x07, 0xfc, 0x03, 0xe0, 0x03, 0xfc, 0x1f, 0xc0, 0x00, 0xff, 0xff, 0x00 40 | }; 41 | 42 | // 'clockwise64', 63x21px 43 | const uint16_t epd_bitmap_clockwise64[] PROGMEM = { 44 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 45 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 46 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 47 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 48 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 49 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 50 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 51 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 52 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xe71c, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 53 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 54 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 55 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 56 | 0x0000, 0xffff, 0xef5d, 0xef5d, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 57 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 58 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 59 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 60 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 61 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 62 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 63 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 64 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 65 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 66 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 67 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 68 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 69 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 70 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 71 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xf79e, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 72 | 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xf79e, 0x0000, 0x0000, 0x0000, 73 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 74 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 75 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 76 | 0xef5d, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 77 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 78 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 79 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 80 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0xf79e, 0xf79e, 0xffff, 81 | 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 82 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 83 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 84 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xef5d, 0x0000, 0xef5d, 0x0000, 0x0000, 0x0000, 0x0000, 85 | 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xf79e, 0x0000, 0x0000, 0xdedb, 0x0000, 0xffff, 0x0000, 0x0000, 86 | 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 87 | 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0xffff, 0xffff, 88 | 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xef5d, 0x0000, 0xef5d, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 89 | 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xf79e, 0x0000, 0x0000, 0xdedb, 0x0000, 0xf800, 0x0000, 0x0000, 0x0000, 90 | 0xf800, 0x0000, 0x0000, 0x0000, 0xf800, 0x0000, 0x0000, 0xf800, 0x0000, 0x0000, 0xf800, 0x0000, 0x0020, 0x0000, 0x0000, 0xf800, 91 | 0x0000, 0x0000, 0xf800, 0x0000, 0xe71c, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 92 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 93 | 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xef5d, 0xdefb, 0xdefb, 0x0000, 0x0000, 0x0000, 0x07e0, 0x0000, 0x0000, 0x07e0, 94 | 0x0000, 0x0000, 0x07e0, 0x0000, 0x0000, 0x0000, 0x07e0, 0x0000, 0x0000, 0x0000, 0x07e0, 0x0020, 0x0000, 0x0000, 0x07e0, 0x07e0, 95 | 0x07e0, 0x07e0, 0x0000, 0x0000, 0xdefb, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 96 | 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xf79e, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xd6ba, 0x0000, 0x0000, 97 | 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xe71c, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0x001f, 0x0000, 0x0000, 0x001f, 0x0000, 98 | 0x0000, 0x001f, 0x0000, 0x0000, 0x0000, 0x001f, 0x0000, 0x0000, 0x0000, 0x0000, 0x001f, 0x0000, 0x0000, 0x001f, 0x0000, 0x0000, 99 | 0x0000, 0x0000, 0x0000, 0xdefb, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 100 | 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0xe71c, 0x0000, 0x0000, 0xffff, 0xffff, 0xef5d, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 101 | 0xffff, 0xffff, 0xffff, 0x0000, 0xffff, 0x0000, 0x0000, 0x0000, 0xffff, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0xffff, 0xffff, 102 | 0x0000, 0x0000, 0x0000, 0x0000, 0xfffd, 0x0000, 0x0000, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0xffdf, 0xf79e, 0x0000, 103 | 0x0000, 0x0000, 0x0000, 0xdefb, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 104 | 0x0000, 0xdefb, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 105 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 106 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 107 | 0x0000, 0x0000, 0x0000, 0xdefb, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xffff, 108 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 109 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 110 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 111 | 0x0000, 0x0000, 0x0000, 0xef5d, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xdefb, 0xffff, 0xffff, 0x0000, 0x0000, 112 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 113 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 114 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 115 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xe71c, 0xef5d, 0xffff, 0xffff, 0xffff, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 116 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 117 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 118 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 119 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 120 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 121 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 122 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 123 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 124 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 125 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 126 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000}; 127 | 128 | 129 | struct StatusController 130 | { 131 | 132 | static StatusController *getInstance() 133 | { 134 | static StatusController base; 135 | return &base; 136 | } 137 | 138 | void clockwiseLogo() 139 | { 140 | Locator::getDisplay()->drawRGBBitmap(1, 1, epd_bitmap_clockwise64, 63, 21); 141 | } 142 | 143 | void wifiConnecting() 144 | { 145 | Locator::getDisplay()->fillRect(0, 24, 64, 52, 0); 146 | Locator::getDisplay()->drawBitmap(16, 24, CW_STATUS_WIFI, 32, 32, 0x2459); 147 | printCenter("Connecting WiFi", 61); 148 | } 149 | 150 | void wifiConnectionFailed(const char *msg) 151 | { 152 | Locator::getDisplay()->fillRect(0, 24, 64, 52, 0); 153 | Locator::getDisplay()->drawBitmap(16, 24, CW_STATUS_WIFI, 32, 32, 0xFA28); 154 | printCenter(msg, 61); 155 | } 156 | 157 | void ntpConnecting() 158 | { 159 | Locator::getDisplay()->fillRect(0, 24, 64, 52, 0); 160 | Locator::getDisplay()->drawBitmap(16, 24, CW_STATUS_NTP, 32, 32, 0xBCBF); 161 | printCenter("NTP Server", 61); 162 | } 163 | 164 | void printCenter(const char *buf, int y) 165 | { 166 | int16_t x1, y1; 167 | uint16_t w, h; 168 | Locator::getDisplay()->setFont(&Picopixel); 169 | Locator::getDisplay()->getTextBounds(buf, 0, y, &x1, &y1, &w, &h); 170 | Locator::getDisplay()->setCursor(32 - (w / 2), y); 171 | Locator::getDisplay()->setTextColor(0xffff); 172 | Locator::getDisplay()->print(buf); 173 | } 174 | 175 | void blink_led(int d, int times) 176 | { 177 | for (int j = 0; j < times; j++) 178 | { 179 | digitalWrite(ESP32_LED_BUILTIN, HIGH); 180 | delay(d); 181 | digitalWrite(ESP32_LED_BUILTIN, LOW); 182 | delay(d); 183 | } 184 | } 185 | 186 | void forceRestart() 187 | { 188 | delay(1000); 189 | ESP.restart(); 190 | } 191 | }; -------------------------------------------------------------------------------- /firmware/lib/cw-commons/WiFiController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ImprovWiFiLibrary.h" 4 | #include 5 | #include 6 | #include "CWWebServer.h" 7 | #include "StatusController.h" 8 | #include 9 | 10 | ImprovWiFi improvSerial(&Serial); 11 | 12 | struct WiFiController 13 | { 14 | long elapsedTimeOffline = 0; 15 | bool connectionSucessfulOnce; 16 | 17 | static void onImprovWiFiErrorCb(ImprovTypes::Error err) 18 | { 19 | ClockwiseWebServer::getInstance()->stopWebServer(); 20 | StatusController::getInstance()->blink_led(2000, 3); 21 | } 22 | 23 | static void onImprovWiFiConnectedCb(const char *ssid, const char *password) 24 | { 25 | ClockwiseParams::getInstance()->load(); 26 | ClockwiseParams::getInstance()->wifiSsid = String(ssid); 27 | ClockwiseParams::getInstance()->wifiPwd = String(password); 28 | ClockwiseParams::getInstance()->save(); 29 | 30 | ClockwiseWebServer::getInstance()->startWebServer(); 31 | 32 | if (MDNS.begin("clockwise")) 33 | { 34 | MDNS.addService("http", "tcp", 80); 35 | } 36 | } 37 | 38 | bool isConnected() 39 | { 40 | if (improvSerial.isConnected()) { 41 | elapsedTimeOffline = 0; 42 | return true; 43 | } else { 44 | if (elapsedTimeOffline == 0 && !connectionSucessfulOnce) 45 | elapsedTimeOffline = millis(); 46 | 47 | if ((millis() - elapsedTimeOffline) > 1000 * 60 * 5) // restart if clockface is not showed and is 5min offline 48 | StatusController::getInstance()->forceRestart(); 49 | 50 | return false; 51 | } 52 | } 53 | 54 | static void handleImprovWiFi() 55 | { 56 | improvSerial.handleSerial(); 57 | } 58 | 59 | bool alternativeSetupMethod() 60 | { 61 | WiFiManager wifiManager; 62 | wifiManager.setConfigPortalTimeout(300); //Wait 5min to configure wifi via AP 63 | 64 | bool success = wifiManager.startConfigPortal("Clockwise-Wifi"); 65 | 66 | if (success) 67 | { 68 | onImprovWiFiConnectedCb(WiFi.SSID().c_str(), WiFi.psk().c_str()); 69 | Serial.printf("[WiFi] Connected via WiFiManager to %s, IP address %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); 70 | connectionSucessfulOnce = success; 71 | } 72 | 73 | return success; 74 | } 75 | 76 | bool begin() 77 | { 78 | WiFi.mode(WIFI_STA); 79 | WiFi.disconnect(); 80 | 81 | improvSerial.setDeviceInfo(ImprovTypes::ChipFamily::CF_ESP32, CW_FW_NAME, CW_FW_VERSION, "Clockwise"); 82 | improvSerial.onImprovError(onImprovWiFiErrorCb); 83 | improvSerial.onImprovConnected(onImprovWiFiConnectedCb); 84 | 85 | ClockwiseParams::getInstance()->load(); 86 | 87 | if (!ClockwiseParams::getInstance()->wifiSsid.isEmpty()) 88 | { 89 | if (improvSerial.tryConnectToWifi(ClockwiseParams::getInstance()->wifiSsid.c_str(), ClockwiseParams::getInstance()->wifiPwd.c_str())) 90 | { 91 | connectionSucessfulOnce = true; 92 | ClockwiseWebServer::getInstance()->startWebServer(); 93 | Serial.printf("[WiFi] Connected to %s, IP address %s\n", WiFi.SSID().c_str(), WiFi.localIP().toString().c_str()); 94 | return true; 95 | } 96 | 97 | StatusController::getInstance()->wifiConnectionFailed("Setup WiFi via AP"); 98 | alternativeSetupMethod(); 99 | } 100 | 101 | StatusController::getInstance()->wifiConnectionFailed("WiFi Failed"); 102 | return false; 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /firmware/lib/cw-commons/picopixel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // Picopixel by Sebastian Weber. A tiny font 7 | // with all characters within a 6 pixel height. 8 | 9 | const uint8_t PicopixelBitmaps[] PROGMEM = { 10 | 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, 11 | 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, 12 | 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, 13 | 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, 14 | 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, 15 | 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, 16 | 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, 17 | 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, 18 | 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, 19 | 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, 20 | 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, 21 | 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, 22 | 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, 23 | 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, 24 | 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A}; 25 | 26 | const GFXglyph PicopixelGlyphs[] PROGMEM = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' 27 | {0, 1, 5, 2, 0, -4}, // 0x21 '!' 28 | {1, 3, 2, 4, 0, -4}, // 0x22 '"' 29 | {2, 5, 5, 6, 0, -4}, // 0x23 '#' 30 | {6, 3, 6, 4, 0, -4}, // 0x24 '$' 31 | {9, 3, 5, 4, 0, -4}, // 0x25 '%' 32 | {11, 4, 5, 5, 0, -4}, // 0x26 '&' 33 | {14, 1, 2, 2, 0, -4}, // 0x27 ''' 34 | {15, 2, 5, 3, 0, -4}, // 0x28 '(' 35 | {17, 2, 5, 3, 0, -4}, // 0x29 ')' 36 | {19, 3, 3, 4, 0, -3}, // 0x2A '*' 37 | {21, 3, 3, 4, 0, -3}, // 0x2B '+' 38 | {23, 2, 2, 3, 0, 0}, // 0x2C ',' 39 | {24, 3, 1, 4, 0, -2}, // 0x2D '-' 40 | {25, 1, 1, 2, 0, 0}, // 0x2E '.' 41 | {26, 3, 5, 4, 0, -4}, // 0x2F '/' 42 | {28, 3, 5, 4, 0, -4}, // 0x30 '0' 43 | {30, 2, 5, 3, 0, -4}, // 0x31 '1' 44 | {32, 3, 5, 4, 0, -4}, // 0x32 '2' 45 | {34, 3, 5, 4, 0, -4}, // 0x33 '3' 46 | {36, 3, 5, 4, 0, -4}, // 0x34 '4' 47 | {38, 3, 5, 4, 0, -4}, // 0x35 '5' 48 | {40, 3, 5, 4, 0, -4}, // 0x36 '6' 49 | {42, 3, 5, 4, 0, -4}, // 0x37 '7' 50 | {44, 3, 5, 4, 0, -4}, // 0x38 '8' 51 | {46, 3, 5, 4, 0, -4}, // 0x39 '9' 52 | {48, 1, 3, 2, 0, -3}, // 0x3A ':' 53 | {49, 2, 4, 3, 0, -3}, // 0x3B ';' 54 | {50, 2, 3, 3, 0, -3}, // 0x3C '<' 55 | {51, 3, 3, 4, 0, -3}, // 0x3D '=' 56 | {53, 2, 3, 3, 0, -3}, // 0x3E '>' 57 | {54, 3, 5, 4, 0, -4}, // 0x3F '?' 58 | {56, 3, 5, 4, 0, -4}, // 0x40 '@' 59 | {58, 3, 5, 4, 0, -4}, // 0x41 'A' 60 | {60, 3, 5, 4, 0, -4}, // 0x42 'B' 61 | {62, 3, 5, 4, 0, -4}, // 0x43 'C' 62 | {64, 3, 5, 4, 0, -4}, // 0x44 'D' 63 | {66, 3, 5, 4, 0, -4}, // 0x45 'E' 64 | {68, 3, 5, 4, 0, -4}, // 0x46 'F' 65 | {70, 3, 5, 4, 0, -4}, // 0x47 'G' 66 | {72, 3, 5, 4, 0, -4}, // 0x48 'H' 67 | {74, 1, 5, 2, 0, -4}, // 0x49 'I' 68 | {75, 3, 5, 4, 0, -4}, // 0x4A 'J' 69 | {77, 3, 5, 4, 0, -4}, // 0x4B 'K' 70 | {79, 3, 5, 4, 0, -4}, // 0x4C 'L' 71 | {81, 5, 5, 6, 0, -4}, // 0x4D 'M' 72 | {85, 4, 5, 5, 0, -4}, // 0x4E 'N' 73 | {88, 3, 5, 4, 0, -4}, // 0x4F 'O' 74 | {90, 3, 5, 4, 0, -4}, // 0x50 'P' 75 | {92, 3, 6, 4, 0, -4}, // 0x51 'Q' 76 | {95, 3, 5, 4, 0, -4}, // 0x52 'R' 77 | {97, 3, 5, 4, 0, -4}, // 0x53 'S' 78 | {99, 3, 5, 4, 0, -4}, // 0x54 'T' 79 | {101, 3, 5, 4, 0, -4}, // 0x55 'U' 80 | {103, 3, 5, 4, 0, -4}, // 0x56 'V' 81 | {105, 5, 5, 6, 0, -4}, // 0x57 'W' 82 | {109, 3, 5, 4, 0, -4}, // 0x58 'X' 83 | {111, 3, 5, 4, 0, -4}, // 0x59 'Y' 84 | {113, 3, 5, 4, 0, -4}, // 0x5A 'Z' 85 | {115, 2, 5, 3, 0, -4}, // 0x5B '[' 86 | {117, 3, 5, 4, 0, -4}, // 0x5C '\' 87 | {119, 2, 5, 3, 0, -4}, // 0x5D ']' 88 | {121, 3, 2, 4, 0, -4}, // 0x5E '^' 89 | {122, 4, 1, 4, 0, 1}, // 0x5F '_' 90 | {123, 2, 2, 3, 0, -4}, // 0x60 '`' 91 | {124, 3, 4, 4, 0, -3}, // 0x61 'a' 92 | {126, 3, 5, 4, 0, -4}, // 0x62 'b' 93 | {128, 3, 3, 4, 0, -2}, // 0x63 'c' 94 | {130, 3, 5, 4, 0, -4}, // 0x64 'd' 95 | {132, 3, 4, 4, 0, -3}, // 0x65 'e' 96 | {134, 2, 5, 3, 0, -4}, // 0x66 'f' 97 | {136, 3, 5, 4, 0, -3}, // 0x67 'g' 98 | {138, 3, 5, 4, 0, -4}, // 0x68 'h' 99 | {140, 1, 5, 2, 0, -4}, // 0x69 'i' 100 | {141, 2, 6, 3, 0, -4}, // 0x6A 'j' 101 | {143, 3, 5, 4, 0, -4}, // 0x6B 'k' 102 | {145, 2, 5, 3, 0, -4}, // 0x6C 'l' 103 | {147, 5, 3, 6, 0, -2}, // 0x6D 'm' 104 | {149, 3, 3, 4, 0, -2}, // 0x6E 'n' 105 | {151, 3, 3, 4, 0, -2}, // 0x6F 'o' 106 | {153, 3, 4, 4, 0, -2}, // 0x70 'p' 107 | {155, 3, 4, 4, 0, -2}, // 0x71 'q' 108 | {157, 2, 3, 3, 0, -2}, // 0x72 'r' 109 | {158, 3, 4, 4, 0, -3}, // 0x73 's' 110 | {160, 2, 5, 3, 0, -4}, // 0x74 't' 111 | {162, 3, 3, 4, 0, -2}, // 0x75 'u' 112 | {164, 3, 3, 4, 0, -2}, // 0x76 'v' 113 | {166, 5, 3, 6, 0, -2}, // 0x77 'w' 114 | {168, 3, 3, 4, 0, -2}, // 0x78 'x' 115 | {170, 3, 4, 4, 0, -2}, // 0x79 'y' 116 | {172, 3, 4, 4, 0, -3}, // 0x7A 'z' 117 | {174, 3, 5, 4, 0, -4}, // 0x7B '{' 118 | {176, 1, 6, 2, 0, -4}, // 0x7C '|' 119 | {177, 3, 5, 4, 0, -4}, // 0x7D '}' 120 | {179, 4, 2, 5, 0, -3}}; // 0x7E '~' 121 | 122 | const GFXfont Picopixel PROGMEM = {(uint8_t *)PicopixelBitmaps, 123 | (GFXglyph *)PicopixelGlyphs, 0x20, 0x7E, 7}; 124 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | idf_component_register( 4 | INCLUDE_DIRS "." 5 | SRC_DIRS "." 6 | REQUIRES arduino Adafruit-GFX-Library 7 | ) 8 | project(cw-gfx-engine) 9 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/ColorUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "Macros.h" 4 | 5 | 6 | const struct 7 | { 8 | static uint16_t adjustBright(uint16_t color, uint8_t bright) { 9 | 10 | uint8_t r = ((color >> 11) & 0x1F); 11 | uint8_t g = ((color >> 5) & 0x3F); 12 | uint8_t b = (color & 0x1F); 13 | 14 | 15 | uint8_t newRed = trunc(r + bright); 16 | uint8_t newGreen = trunc(g + bright); 17 | uint8_t newBlue = trunc(b + bright); 18 | 19 | return color565(newRed, newGreen, newBlue); 20 | } 21 | 22 | static uint16_t brighter(uint16_t color, uint8_t factor) { 23 | 24 | uint8_t r = ((color >> 11) & 0x1F); 25 | uint8_t g = ((color >> 5) & 0x3F); 26 | uint8_t b = (color & 0x1F); 27 | 28 | uint8_t newRed = MIN(trunc(r * (factor / 10)), 255); 29 | uint8_t newGreen = MIN(trunc(g * (factor / 10)), 255); 30 | uint8_t newBlue = MIN(trunc(b * (factor / 10)), 255); 31 | 32 | 33 | // Serial.printf("Old RGB %d %d %d\n", r, g, b); 34 | // Serial.printf("New RGB %d %d %d\n", newRed, newGreen, newBlue); 35 | // delay(1000); 36 | 37 | 38 | 39 | return color565(newRed, newGreen, newBlue); 40 | 41 | } 42 | 43 | static uint16_t color565(uint8_t r, uint8_t g, uint8_t b) { 44 | return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3); 45 | } 46 | } COLOR_UTIL; 47 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/EventBus.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "EventBus.h" 3 | 4 | void EventBus::broadcast(EventType event, Sprite* sender) { 5 | for (uint8_t i = 0; i < _subNum; i++) { 6 | _subscriptions[i]->execute(event, sender); 7 | } 8 | } 9 | 10 | 11 | void EventBus::subscribe(EventTask* task) { 12 | 13 | if (_subNum < 5) { 14 | _subscriptions[_subNum] = task; 15 | _subNum++; 16 | } else { 17 | Serial.println("Out of space"); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/EventBus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "EventTask.h" 5 | #include "Sprite.h" 6 | 7 | class EventBus { 8 | private: 9 | EventTask* _subscriptions[5]; 10 | uint8_t _subNum = 0; 11 | 12 | public: 13 | void broadcast(EventType event, Sprite* sender); 14 | void subscribe(EventTask* task); 15 | 16 | }; 17 | 18 | 19 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/EventTask.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Sprite.h" 5 | 6 | enum EventType { 7 | MOVE, 8 | COLLISION 9 | }; 10 | 11 | class EventTask { 12 | public: 13 | virtual void execute(EventType event, Sprite* caller) = 0; 14 | }; 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | const int DISPLAY_WIDTH = 64; 4 | const int DISPLAY_HEIGHT = 64; 5 | 6 | enum Direction { 7 | RIGHT, 8 | LEFT, 9 | UP, 10 | DOWN 11 | }; 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/ImageUtils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | struct ImageUtils { 7 | 8 | 9 | static void flipHorizontally(uint16_t *image, uint8_t width, uint8_t height) { 10 | //13x16 11 | 12 | // 0*13 - 0 1 2 3 4 5 6 7 8 9 0 1 2 13 | // 1*13 - 3 4 5 6 7 8 9 0 1 2 3 4 5 14 | 15 | int idx = 0; 16 | uint16_t pixel_temp; 17 | 18 | for (uint8_t h = 0; h < height; h++) { 19 | idx = h*width; 20 | for (uint8_t w = 0; w < width/2; w++) { 21 | pixel_temp = image[idx+w]; 22 | image[idx+w] = image[idx-w+(width-1)]; 23 | image[idx-w+(width-1)] = pixel_temp; 24 | } 25 | } 26 | } 27 | 28 | 29 | static void flipHorizontallyClone(const uint16_t *image, uint16_t *dest, uint8_t width, uint8_t height) { 30 | //13x16 31 | 32 | // 0*13 - 0 1 2 3 4 5 6 7 8 9 0 1 2 33 | // 1*13 - 3 4 5 6 7 8 9 0 1 2 3 4 5 34 | 35 | int idx = 0; 36 | 37 | for (uint8_t h = 0; h < height; h++) { 38 | idx = h*width; 39 | for (uint8_t w = 0; w < width; w++) { 40 | dest[idx+w] = image[idx-w+(width-1)]; 41 | } 42 | } 43 | } 44 | 45 | static void clone(uint16_t* src, uint16_t* dst, int len) { 46 | memcpy(dst, src, sizeof(src[0])*len); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Locator.cpp: -------------------------------------------------------------------------------- 1 | #include "Locator.h" 2 | 3 | Adafruit_GFX* Locator::_display; 4 | EventBus* Locator::_eventBus; 5 | 6 | void Locator::provide(Adafruit_GFX* display) 7 | { 8 | _display = display; 9 | } 10 | 11 | void Locator::provide(EventBus* eventBus) 12 | { 13 | _eventBus = eventBus; 14 | } 15 | 16 | Adafruit_GFX* Locator::getDisplay() 17 | { 18 | return _display; 19 | } 20 | 21 | EventBus* Locator::getEventBus() 22 | { 23 | return _eventBus; 24 | } 25 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Locator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "EventBus.h" 6 | 7 | class Locator { 8 | private: 9 | static Adafruit_GFX* _display; 10 | static EventBus* _eventBus; 11 | 12 | public: 13 | static Adafruit_GFX* getDisplay(); 14 | static EventBus* getEventBus(); 15 | static void provide(Adafruit_GFX* display); 16 | static void provide(EventBus* eventBus); 17 | }; 18 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Macros.h: -------------------------------------------------------------------------------- 1 | #define MAX(x, y) (((x) > (y)) ? (x) : (y)) 2 | #define MIN(x, y) (((x) < (y)) ? (x) : (y)) -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Object.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Locator.h" 3 | 4 | struct Object { 5 | const unsigned short *_image; 6 | int _width; 7 | int _height; 8 | 9 | Object(const unsigned short *image, int width, int height) { 10 | this->_image = image; 11 | this->_width = width; 12 | this->_height = height; 13 | } 14 | 15 | void draw(int x, int y) { 16 | Locator::getDisplay()->drawRGBBitmap(x, y, _image, _width, _height); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/README.md: -------------------------------------------------------------------------------- 1 | # cw-gfx-engine 2 | Clockwise graphics engine 3 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Sprite.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "Sprite.h" 3 | 4 | boolean Sprite::collidedWith(Sprite* sprite) { 5 | return (this->_x < sprite->_x + sprite->_width && 6 | this->_x + this->_width > sprite->_x && 7 | this->_y < sprite->_y + sprite->_height && 8 | this->_y + this->_height > sprite->_y); 9 | } 10 | 11 | void Sprite::logPosition() { 12 | Serial.print("x = "); Serial.print(_x); Serial.print(", y = "); Serial.print(_y); 13 | Serial.print(", w = "); Serial.print(_width); Serial.print(", h = "); Serial.println(_height); 14 | } 15 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Sprite.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class Sprite { 6 | 7 | protected: 8 | int8_t _x = 0; 9 | int8_t _y = 0; 10 | uint8_t _width = 0; 11 | uint8_t _height = 0; 12 | 13 | public: 14 | boolean collidedWith(Sprite* sprite); 15 | void logPosition(); 16 | 17 | virtual const char* name(); 18 | }; 19 | -------------------------------------------------------------------------------- /firmware/lib/cw-gfx-engine/Tile.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Locator.h" 4 | #include "Game.h" 5 | 6 | struct Tile { 7 | const unsigned short *_image; 8 | int _width; 9 | int _height; 10 | 11 | Tile(const unsigned short *image, int width, int height) { 12 | this->_image = image; 13 | this->_width = width; 14 | this->_height = height; 15 | } 16 | 17 | void draw(int x, int y) { 18 | Locator::getDisplay()->drawRGBBitmap(x, y, _image, _width, _height); 19 | } 20 | 21 | void fillRow(int y) { 22 | for (int xx = 0; xx < DISPLAY_WIDTH; xx += _width) { 23 | draw(xx, y); 24 | } 25 | } 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /firmware/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 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = esp32dev 13 | name = Clockwise 14 | 15 | [env:native] 16 | platform = native 17 | test_framework = unity 18 | test_ignore = test_embedded 19 | 20 | [env:esp32dev] 21 | platform = espressif32 22 | board = esp32doit-devkit-v1 23 | framework = arduino 24 | test_ignore = test_native 25 | monitor_speed = 115200 26 | lib_deps = 27 | https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA.git 28 | adafruit/Adafruit GFX Library@^1.10.1 29 | adafruit/Adafruit BusIO@^1.5.0 30 | SPI 31 | Wire 32 | ropg/ezTime@^0.8.3 33 | https://github.com/jnthas/Improv-WiFi-Library 34 | https://github.com/tzapu/WiFiManager 35 | bblanchon/ArduinoJson@^6.21.2 36 | bitbank2/PNGdec@^1.0.1 37 | build_src_filter = +<*> -<.git/> -<.svn/> - - - - - 38 | build_flags = 39 | -D CW_FW_VERSION="\"1.4.2\"" 40 | -D CW_FW_NAME="\"${sysenv.FW_NAME}\"" 41 | -------------------------------------------------------------------------------- /firmware/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Clockface 5 | #include 6 | // Commons 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define MIN_BRIGHT_DISPLAY_ON 4 14 | #define MIN_BRIGHT_DISPLAY_OFF 0 15 | 16 | #define ESP32_LED_BUILTIN 2 17 | 18 | MatrixPanel_I2S_DMA *dma_display = nullptr; 19 | 20 | Clockface *clockface; 21 | 22 | WiFiController wifi; 23 | CWDateTime cwDateTime; 24 | 25 | bool autoBrightEnabled; 26 | long autoBrightMillis = 0; 27 | uint8_t currentBrightSlot = -1; 28 | 29 | void displaySetup(bool swapBlueGreen, uint8_t displayBright, uint8_t displayRotation) 30 | { 31 | HUB75_I2S_CFG mxconfig(64, 64, 1); 32 | 33 | if (swapBlueGreen) 34 | { 35 | // Swap Blue and Green pins because the panel is RBG instead of RGB. 36 | mxconfig.gpio.b1 = 26; 37 | mxconfig.gpio.b2 = 12; 38 | mxconfig.gpio.g1 = 27; 39 | mxconfig.gpio.g2 = 13; 40 | } 41 | 42 | mxconfig.gpio.e = 18; 43 | mxconfig.clkphase = false; 44 | 45 | // Display Setup 46 | dma_display = new MatrixPanel_I2S_DMA(mxconfig); 47 | dma_display->begin(); 48 | dma_display->setBrightness8(displayBright); 49 | dma_display->clearScreen(); 50 | dma_display->setRotation(displayRotation); 51 | } 52 | 53 | void automaticBrightControl() 54 | { 55 | if (autoBrightEnabled) { 56 | if (millis() - autoBrightMillis > 3000) 57 | { 58 | int16_t currentValue = analogRead(ClockwiseParams::getInstance()->ldrPin); 59 | 60 | uint16_t ldrMin = ClockwiseParams::getInstance()->autoBrightMin; 61 | uint16_t ldrMax = ClockwiseParams::getInstance()->autoBrightMax; 62 | 63 | const uint8_t minBright = (currentValue < ldrMin ? MIN_BRIGHT_DISPLAY_OFF : MIN_BRIGHT_DISPLAY_ON); 64 | uint8_t maxBright = ClockwiseParams::getInstance()->displayBright; 65 | 66 | uint8_t slots = 10; //10 slots 67 | uint8_t mapLDR = map(currentValue > ldrMax ? ldrMax : currentValue, ldrMin, ldrMax, 1, slots); 68 | uint8_t mapBright = map(mapLDR, 1, slots, minBright, maxBright); 69 | 70 | // Serial.printf("LDR: %d, mapLDR: %d, Bright: %d\n", currentValue, mapLDR, mapBright); 71 | if(abs(currentBrightSlot - mapLDR ) >= 2 || mapBright == 0){ 72 | dma_display->setBrightness8(mapBright); 73 | currentBrightSlot=mapLDR; 74 | // Serial.printf("setBrightness: %d , Update currentBrightSlot to %d\n", mapBright, mapLDR); 75 | } 76 | autoBrightMillis = millis(); 77 | } 78 | } 79 | } 80 | 81 | void setup() 82 | { 83 | Serial.begin(115200); 84 | pinMode(ESP32_LED_BUILTIN, OUTPUT); 85 | 86 | StatusController::getInstance()->blink_led(5, 100); 87 | 88 | ClockwiseParams::getInstance()->load(); 89 | 90 | pinMode(ClockwiseParams::getInstance()->ldrPin, INPUT); 91 | 92 | displaySetup(ClockwiseParams::getInstance()->swapBlueGreen, ClockwiseParams::getInstance()->displayBright, ClockwiseParams::getInstance()->displayRotation); 93 | clockface = new Clockface(dma_display); 94 | 95 | autoBrightEnabled = (ClockwiseParams::getInstance()->autoBrightMax > 0); 96 | 97 | StatusController::getInstance()->clockwiseLogo(); 98 | delay(1000); 99 | 100 | StatusController::getInstance()->wifiConnecting(); 101 | if (wifi.begin()) 102 | { 103 | StatusController::getInstance()->ntpConnecting(); 104 | cwDateTime.begin(ClockwiseParams::getInstance()->timeZone.c_str(), 105 | ClockwiseParams::getInstance()->use24hFormat, 106 | ClockwiseParams::getInstance()->ntpServer.c_str(), 107 | ClockwiseParams::getInstance()->manualPosix.c_str()); 108 | clockface->setup(&cwDateTime); 109 | } 110 | } 111 | 112 | void loop() 113 | { 114 | wifi.handleImprovWiFi(); 115 | 116 | if (wifi.isConnected()) 117 | { 118 | ClockwiseWebServer::getInstance()->handleHttpRequest(); 119 | ezt::events(); 120 | } 121 | 122 | if (wifi.connectionSucessfulOnce) 123 | { 124 | clockface->update(); 125 | } 126 | 127 | automaticBrightControl(); 128 | } 129 | -------------------------------------------------------------------------------- /firmware/test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /firmware/test/test_embedded/CWPreferences.cpp: -------------------------------------------------------------------------------- 1 | #define CW_PREF_DB_NAME "clockwise_test" 2 | 3 | #include "unity.h" 4 | #include "Arduino.h" 5 | #include "CWPreferences.h" 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | void setUp(void) { 12 | } 13 | 14 | void tearDown(void) { 15 | // clean stuff up here 16 | } 17 | 18 | void test_function_should_get_default_values(void) { 19 | ClockwiseParams::getInstance()->load(); 20 | TEST_ASSERT_EQUAL_STRING("raw.githubusercontent.com", ClockwiseParams::getInstance()->canvasServer.c_str()); 21 | TEST_ASSERT_EQUAL_STRING("America/Sao_Paulo", ClockwiseParams::getInstance()->timeZone.c_str()); 22 | TEST_ASSERT_EQUAL_STRING(NTP_SERVER, ClockwiseParams::getInstance()->ntpServer.c_str()); 23 | TEST_ASSERT_EQUAL(32, ClockwiseParams::getInstance()->displayBright); 24 | TEST_ASSERT_EQUAL(true, ClockwiseParams::getInstance()->use24hFormat); 25 | } 26 | 27 | int runUnityTests(void) { 28 | UNITY_BEGIN(); 29 | RUN_TEST(test_function_should_get_default_values); 30 | return UNITY_END(); 31 | } 32 | 33 | void setup() 34 | { 35 | delay(2000); 36 | runUnityTests(); 37 | } 38 | void loop() {} 39 | 40 | -------------------------------------------------------------------------------- /firmware/test/test_native/SimpleTests.cpp: -------------------------------------------------------------------------------- 1 | #include "unity.h" 2 | 3 | void setUp(void) { 4 | // set stuff up here 5 | } 6 | 7 | void tearDown(void) { 8 | // clean stuff up here 9 | } 10 | 11 | void test_function_should_be_true(void) { 12 | TEST_ASSERT_TRUE(true); 13 | } 14 | 15 | int runUnityTests(void) { 16 | UNITY_BEGIN(); 17 | RUN_TEST(test_function_should_be_true); 18 | return UNITY_END(); 19 | } 20 | 21 | 22 | int main() { 23 | runUnityTests(); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CLOCKFACES cw-cf-0x01 cw-cf-0x02 cw-cf-0x03 cw-cf-0x04 cw-cf-0x05 cw-cf-0x06) 2 | 3 | idf_component_register( 4 | SRC_DIRS "." 5 | INCLUDE_DIRS ${PROJECT_DIR}/firmware/src 6 | PRIV_REQUIRES ESP32-HUB75-MatrixPanel-I2S-DMA cw-commons cw-gfx-engine WiFiManager Improv-WiFi-Library arduino Adafruit-GFX-Library ${CLOCKFACES} 7 | ) 8 | 9 | target_compile_options(${COMPONENT_TARGET} PUBLIC -DCW_FW_VERSION="1.2.2" -DCW_FW_NAME="Clockwise") 10 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Clockwise Configuration" 2 | 3 | config HUB75_BLUE_GREEN_SWAP 4 | bool "Swap blue and green colors" 5 | help 6 | Some 64x64 HUB75 displays have the green and blue pins reversed. Enable this option to swap these pins. 7 | 8 | choice CLOCKFACE 9 | prompt "Clockwise display" 10 | default CLOCKFACE_MARIO 11 | 12 | config CLOCKFACE_MARIO 13 | bool "Mario" 14 | 15 | config CLOCKFACE_TIME_IN_WORDS 16 | bool "Time in words" 17 | 18 | config CLOCKFACE_WORLDMAP 19 | bool "World map" 20 | 21 | config CLOCKFACE_CASTLEVANIA 22 | bool "Castlevania" 23 | 24 | config CLOCKFACE_PACMAN 25 | bool "Pacman" 26 | 27 | 28 | endchoice # CLOCKFACE 29 | 30 | endmenu 31 | -------------------------------------------------------------------------------- /main/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define HUB75_BLUE_GREEN_SWAP CONFIG_HUB75_BLUE_GREEN_SWAP 4 | 5 | #include "../firmware/src/main.cpp" 6 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_AUTOSTART_ARDUINO=y 2 | CONFIG_FREERTOS_HZ=1000 3 | --------------------------------------------------------------------------------