├── .gitignore
├── .vscode
├── extensions.json
└── settings.json
├── README.md
├── assets
├── icons
│ ├── lpehacker.png
│ ├── lpenote.png
│ ├── lpesip.png
│ ├── lpetantrum.png
│ └── lpethink.png
└── images
│ ├── bg_bubble.png
│ ├── bg_cat.png
│ ├── bg_lpe_bubble.png
│ ├── bg_pablo.png
│ ├── bg_splash.png
│ ├── bg_stonks.png
│ └── bg_what_a_week.png
├── docs
├── front.jpg
├── led_shroud.png
├── makerworld_logo.png
├── select_preset.jpg
├── timer_paused.jpg
└── timer_running.jpg
├── platformio.ini
├── poetry.lock
├── pyproject.toml
├── scripts
└── gen_assets.py
├── src
├── GxEPD2_display_selection_new_style.h
├── GxEPD2_selection_check.h
├── anniversary.h
├── button.cpp
├── button.h
├── checkbox.cpp
├── checkbox.h
├── debug.h
├── defs.h
├── fonts
│ ├── FunnelDisplay_Bold18pt7b.h
│ ├── FunnelDisplay_Bold24pt7b.h
│ ├── FunnelDisplay_Bold32pt7b.h
│ ├── FunnelDisplay_Bold48pt7b.h
│ ├── FunnelDisplay_Bold60pt7b.h
│ ├── FunnelDisplay_Regular14pt7b.h
│ ├── HelvetiPixel16pt7b.h
│ └── HelvetiPixel24pt7b.h
├── gfx_utils.cpp
├── gfx_utils.h
├── icon.h
├── icon_provider.cpp
├── icon_provider.h
├── icons.h
├── icons
│ └── icons.cpp
├── images.h
├── images
│ ├── image_bg_bubble.h
│ ├── image_bg_cat.h
│ ├── image_bg_lpe_bubble.h
│ ├── image_bg_lpehacker_bubble.h
│ ├── image_bg_pablo.h
│ ├── image_bg_splash.h
│ ├── image_bg_stonks.h
│ └── image_bg_what_a_week.h
├── led.cpp
├── led.h
├── lpe.h
├── main.cpp
├── menu.cpp
├── menu.h
├── preferences_manager.cpp
├── preferences_manager.h
├── splashscreen.cpp
├── splashscreen.h
├── states
│ ├── timer_running.cpp
│ ├── timer_running_break.cpp
│ ├── timer_selecting_preset.cpp
│ └── timer_waiting_for_confirmation.cpp
├── statistics.cpp
├── statistics.h
├── strings.cpp
├── strings.h
├── timer.cpp
└── timer.h
├── stl
├── Pomodoro - Cover.stl
├── Pomodoro - Housing.stl
├── Pomodoro - Knob.stl
└── Pomodoro - LEDCase.stl
├── test
└── README.md
└── uv.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | .pio
2 | .vscode/.browse.c_cpp.db*
3 | .vscode/c_cpp_properties.json
4 | .vscode/launch.json
5 | .vscode/ipch
6 |
7 | assets/fonts/*.ttf
8 | assets/icons/*.png
9 | !assets/icons/lpe*.png
10 |
11 | src/strings_private.cpp
12 | src/anniversary.cpp
13 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.associations": {
3 | "*.tcc": "cpp",
4 | "algorithm": "cpp",
5 | "array": "cpp",
6 | "string": "cpp",
7 | "string_view": "cpp",
8 | "vector": "cpp",
9 | "iosfwd": "cpp",
10 | "functional": "cpp",
11 | "new": "cpp",
12 | "optional": "cpp",
13 | "istream": "cpp",
14 | "ostream": "cpp",
15 | "system_error": "cpp",
16 | "tuple": "cpp",
17 | "type_traits": "cpp",
18 | "utility": "cpp"
19 | }
20 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | This is the repository for an ESP32 based focus timer. It uses an ePaper display and a rotary dial for input.
4 | The code in this repository will not be ready-to-use, as some assets and fonts have been removed. However, if you really want to you should be able to adapt the code to your needs.
5 |
6 |
7 |
8 |
9 |
10 | View on MakerWorld
11 |
12 |
13 |
14 | ## Parts List
15 |
16 | - ESP32 (I used an [AZDelivery ESP32 NodeMCU](https://www.az-delivery.de/en/products/esp32-developmentboard))
17 | - WaveShare 4.26inch e-Paper display HAT, 800x480 ([link](https://www.waveshare.com/4.26inch-e-paper-hat.htm))
18 | - Other displays will work but the UI is designed for this specific resolution
19 | - KY-040 rotary encoder with button
20 | - A single WS2812 LED (could be replaced with a simple RGB LED)
21 | - A USB-C connector (like [this one](https://amzn.eu/d/8UpvqWe))
22 | - Note: if you use a 2-wired USB-C connector, you might need to use an USB A to USB C cable to power the device (my best guess is because of the missing power delivery negotiation)
23 | - 3d printed case ([`onshape` file](https://cad.onshape.com/documents/06055e629740267835bb7660/w/df56eb93ab74e2f4d61e5097/e/21a7853695e4900200750891?renderMode=0&uiState=67e6e3924368850ba92069f6))
24 | - Some resistors (for the LED and a pullup resistor for the switch) and 0.1uF capacitors (to smooth out the rotary encoder signal)
25 | - Optional: tire balancing weights and rubber feet
26 |
27 | ## Project Origin
28 |
29 | I love trying out different productivity techniques - some say that the quest to optimize your productivity is the ultimate procrastination method, so maybe that is what drove me to this project. I also have a habit of committing time (around a month of work outside my normal job) once a year to a project that benefits someone else. Last year, I bought a 3D printer (BambuLab X1C) and wanted to put it to good use. I have previously finished an apprenticeship as an electronics
30 | engineer before pivoting to software engineering, so I also wanted to come back to my roots and build something physical.
31 | My friend struggles with time management throughout the day sometimes - lots of different tasks to organize, and little focus. So I thought to myself: Why not make them a focus timer? So, I set out with a few goals:
32 |
33 | - It should be a physical device
34 | - It should be _fun_
35 | - It should be intuitive to use
36 |
37 | There are some cool projects out there (arguably much cooler than this, for example the [Focus Dial by Salim Benbouziyane](https://www.youtube.com/watch?v=nZa-Vqu-_fU)), but I wanted to build something from scratch. I also
38 | never built something with an ePaper display and thought it might be a good fit for something that is mostly idling and doesn't require a backlight.
39 |
40 | ### Why these parts?
41 |
42 | This was my second dive back into building things with microcontrollers in a long time. I knew ESP32 well enough to feel comfortable diving back in, so that was the main choice here. I did some research before to see what kinds of displays would be supported.
43 |
44 | #### ePaper Display
45 |
46 | I needed some sort of display, or at least I _wanted_ some sort of display. One of the main motivations for this project was that it should be out of your way - until it is time to finish your current focus and move on. For me, this meant that I wanted a display without any backlight.
47 |
48 | The display should also be large enough that you can put the whole device somewhere on your desk and still be able to read it. After ordering and playing around with a few WaveShare ePaper displays, I settled on the 4.26" variant for multiple reasons:
49 |
50 | - Great resolution (which seems to be really hard to find for "hobbyist" displays)
51 | - The size felt right
52 | - The display supports partial refreshes (0.3s, no distracting "black and white flashes" while refreshing)
53 |
54 | Initially, I really wanted to use a black/white/red display and found one that I liked, but the refresh time
55 | was a whopping 16 seconds with no support for partial refreshes which was a dealbreaker for me.
56 |
57 | The final bonus feature: it won't work at night. If your desk is not bright enough, you won't be able to read the display. This is a feature, not a bug. Too dark outside? Stop working already!
58 |
59 | #### Rotary Encoder
60 |
61 | From the start, I knew that I wanted some sort of dial as an input - it made the most sense to me. This came at the cost of some complexity when designing the menus, and you really need to make sure that you debounce the input correctly. I also added .1uF capacitors to the CLK and DT pins to help with smoothing out the signal.
62 |
63 | #### LED
64 |
65 | In the first few iterations, there was no plan for an LED. My genius plan of having a display without backlight came at a cost: it could be _too_ subtle when your current focus time ended. I experimented with a few different ideas:
66 |
67 | - A buzzer: this would just make you jump. A truly horrible experience
68 | - Speakers: I don't know why, but speakers felt _hard_. So much noise and whining with the setup I tried, but I will blame this on a skill issue
69 | - LED: I had some WS2812 LEDs lying around and thought they might be a good fit. You can animate them with the NeoPixel library, and they are really easy to use. The additional benefit of not needing to commit many more output pins was also a big plus
70 |
71 | 
72 |
73 | The LED ended up working great, allowing me to display different states. It might be subtle, but I also added a little shroud to the case and added a diffusion layer in front of the LED to make it look nicer.
74 |
75 | ### Building the Case
76 |
77 | The case comes in two parts: the base and a lid. One unfortunate design choice I made is that the display frame is printed as one piece as part of the base, so the top edge tends to warp a little bit during printing. Since CAD (or product design) isn't my strongest suit, there will certainly be better choices to design this for a better final look.
78 |
79 | One thing that I wished I learned earlier is that it might not have been the best idea to put the dial in the front: because the print and electronics are so lightweight, pressing the switch on the dial will tend to just slide the whole device back. Luckily, I could solve this by adding some rubber feet and weights (the ones usually used to balance tires) to the bottom of the case. This worked out great, and I am happy with how it turned out.
80 |
81 | ### Software
82 |
83 | The software is written in C++ and uses the Arduino framework. I used PlatformIO to manage the project (at least that is what seemed to be a popular choice, but I am not so sure about that anymore). This project relies heavily
84 | on the GxEPD2 library for the display. I won't lie, the code in this repository is a bit of a mess - I had to get things done in time, which led to quite a bit of copy and pasting and not revisiting earlier parts of the code.
85 | Some parts were generated by AI (Claude, for the most part) to help me finish the project in the deadline I set myself.
86 |
87 | 
88 |
89 | Since this was a project for my friend, I also wanted to include some easter eggs and fun. You would think that adding some random facts _while you are supposed to be focused_ would be a bad idea, but I think it is a fun little addition.
90 |
91 | ## Using the Device
92 |
93 | When the device starts up, you can either change some settings or go into preset selection mode. From there, you can choose one of three presets:
94 |
95 | 
96 |
97 | The timer will then start and let you know once the time is up (by flashing the LED and displaying a message on the screen). You can keep working (not recommended, but necessary if you want to finish something) and then start the break.
98 |
99 | 
100 |
101 | During the pause, you can view some statistics. Every few iterations (4 by default), your pause will be longer to give you some time to recover.
102 |
103 | 
104 |
105 | ## Development
106 |
107 | ### Prerequisites
108 |
109 | - PlatformIO (I used the VSCode extension)
110 | - Python 3.13+ for asset (re)generation
111 |
112 | ### Generating Assets
113 |
114 | In order to prepare images, icons, and fonts, you will need to run the `generate_assets.py` script. This script will take care of resizing images, converting them to the correct format, and generating the necessary C++ code to include them in the project.
115 |
116 | ```bash
117 | # install dependencies with uv or a different package manager
118 | uv sync
119 |
120 | uv run scripts/generate_assets.py
121 | ```
122 |
123 | ### Customizing Presets
124 |
125 | The presets are defined in `src/main.cpp`:
126 |
127 | ```cpp
128 | timer.addPreset(iconProvider->getPresetIcon("Emails"), iconProvider->getTimerRunningBackgroundImage(), "Emails", 15 * MINUTE, 5 * MINUTE, 15 * MINUTE);
129 | timer.addPreset(iconProvider->getPresetIcon("Coding"), iconProvider->getTimerRunningBackgroundImage(), "Coding", 45 * MINUTE, 15 * MINUTE, 30 * MINUTE, 2);
130 | timer.addPreset(iconProvider->getPresetIcon("Focus"), iconProvider->getTimerRunningBackgroundImage(), "Focus", 25 * MINUTE, 5 * MINUTE, 20 * MINUTE);
131 | ```
132 |
133 | If you want to customize this, I would start there and keep looking for references to these presets.
134 |
135 | ## Pin Mapping
136 |
137 | #### Rotary Encoder (KY-040)
138 |
139 | | PIN | # |
140 | | --- | --- |
141 | | CLK | 32 |
142 | | DT | 21 |
143 | | SW | 14 |
144 |
145 | #### ePaper Display (GxEPD2_426_GDEQ0426T82, WaveShare 4.26" b/w)
146 |
147 | | PIN | # |
148 | | ---- | --- |
149 | | BUSY | 4 |
150 | | RST | 16 |
151 | | DC | 17 |
152 | | CS | 5 |
153 | | CLK | 18 |
154 | | DIN | 23 |
155 |
156 | #### LED (WS2812)
157 |
158 | | PIN | # |
159 | | --- | --- |
160 | | DIN | 25 |
161 |
--------------------------------------------------------------------------------
/assets/icons/lpehacker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/icons/lpehacker.png
--------------------------------------------------------------------------------
/assets/icons/lpenote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/icons/lpenote.png
--------------------------------------------------------------------------------
/assets/icons/lpesip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/icons/lpesip.png
--------------------------------------------------------------------------------
/assets/icons/lpetantrum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/icons/lpetantrum.png
--------------------------------------------------------------------------------
/assets/icons/lpethink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/icons/lpethink.png
--------------------------------------------------------------------------------
/assets/images/bg_bubble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/images/bg_bubble.png
--------------------------------------------------------------------------------
/assets/images/bg_cat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/images/bg_cat.png
--------------------------------------------------------------------------------
/assets/images/bg_lpe_bubble.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/images/bg_lpe_bubble.png
--------------------------------------------------------------------------------
/assets/images/bg_pablo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/images/bg_pablo.png
--------------------------------------------------------------------------------
/assets/images/bg_splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/images/bg_splash.png
--------------------------------------------------------------------------------
/assets/images/bg_stonks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/images/bg_stonks.png
--------------------------------------------------------------------------------
/assets/images/bg_what_a_week.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/assets/images/bg_what_a_week.png
--------------------------------------------------------------------------------
/docs/front.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/docs/front.jpg
--------------------------------------------------------------------------------
/docs/led_shroud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/docs/led_shroud.png
--------------------------------------------------------------------------------
/docs/makerworld_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/docs/makerworld_logo.png
--------------------------------------------------------------------------------
/docs/select_preset.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/docs/select_preset.jpg
--------------------------------------------------------------------------------
/docs/timer_paused.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/docs/timer_paused.jpg
--------------------------------------------------------------------------------
/docs/timer_running.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Rukenshia/pomodoro/2c339876d54325ed6505e066731f024be315d8aa/docs/timer_running.jpg
--------------------------------------------------------------------------------
/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 | [env:az-delivery-devkit-v4]
12 | platform = espressif32
13 | board = az-delivery-devkit-v4
14 | framework = arduino
15 | monitor_speed = 115200
16 | lib_deps =
17 | zinggjm/GxEPD2@^1.6.2
18 | madhephaestus/ESP32Encoder@^0.11.7
19 | makuna/NeoPixelBus@^2.8.3
20 |
--------------------------------------------------------------------------------
/poetry.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand.
2 |
3 | [[package]]
4 | name = "pillow"
5 | version = "11.1.0"
6 | description = "Python Imaging Library (Fork)"
7 | optional = false
8 | python-versions = ">=3.9"
9 | groups = ["main"]
10 | files = [
11 | {file = "pillow-11.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:e1abe69aca89514737465752b4bcaf8016de61b3be1397a8fc260ba33321b3a8"},
12 | {file = "pillow-11.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c640e5a06869c75994624551f45e5506e4256562ead981cce820d5ab39ae2192"},
13 | {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a07dba04c5e22824816b2615ad7a7484432d7f540e6fa86af60d2de57b0fcee2"},
14 | {file = "pillow-11.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e267b0ed063341f3e60acd25c05200df4193e15a4a5807075cd71225a2386e26"},
15 | {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:bd165131fd51697e22421d0e467997ad31621b74bfc0b75956608cb2906dda07"},
16 | {file = "pillow-11.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:abc56501c3fd148d60659aae0af6ddc149660469082859fa7b066a298bde9482"},
17 | {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:54ce1c9a16a9561b6d6d8cb30089ab1e5eb66918cb47d457bd996ef34182922e"},
18 | {file = "pillow-11.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:73ddde795ee9b06257dac5ad42fcb07f3b9b813f8c1f7f870f402f4dc54b5269"},
19 | {file = "pillow-11.1.0-cp310-cp310-win32.whl", hash = "sha256:3a5fe20a7b66e8135d7fd617b13272626a28278d0e578c98720d9ba4b2439d49"},
20 | {file = "pillow-11.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:b6123aa4a59d75f06e9dd3dac5bf8bc9aa383121bb3dd9a7a612e05eabc9961a"},
21 | {file = "pillow-11.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:a76da0a31da6fcae4210aa94fd779c65c75786bc9af06289cd1c184451ef7a65"},
22 | {file = "pillow-11.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e06695e0326d05b06833b40b7ef477e475d0b1ba3a6d27da1bb48c23209bf457"},
23 | {file = "pillow-11.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96f82000e12f23e4f29346e42702b6ed9a2f2fea34a740dd5ffffcc8c539eb35"},
24 | {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3cd561ded2cf2bbae44d4605837221b987c216cff94f49dfeed63488bb228d2"},
25 | {file = "pillow-11.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f189805c8be5ca5add39e6f899e6ce2ed824e65fb45f3c28cb2841911da19070"},
26 | {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:dd0052e9db3474df30433f83a71b9b23bd9e4ef1de13d92df21a52c0303b8ab6"},
27 | {file = "pillow-11.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:837060a8599b8f5d402e97197d4924f05a2e0d68756998345c829c33186217b1"},
28 | {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa8dd43daa836b9a8128dbe7d923423e5ad86f50a7a14dc688194b7be5c0dea2"},
29 | {file = "pillow-11.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0a2f91f8a8b367e7a57c6e91cd25af510168091fb89ec5146003e424e1558a96"},
30 | {file = "pillow-11.1.0-cp311-cp311-win32.whl", hash = "sha256:c12fc111ef090845de2bb15009372175d76ac99969bdf31e2ce9b42e4b8cd88f"},
31 | {file = "pillow-11.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbd43429d0d7ed6533b25fc993861b8fd512c42d04514a0dd6337fb3ccf22761"},
32 | {file = "pillow-11.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f7955ecf5609dee9442cbface754f2c6e541d9e6eda87fad7f7a989b0bdb9d71"},
33 | {file = "pillow-11.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2062ffb1d36544d42fcaa277b069c88b01bb7298f4efa06731a7fd6cc290b81a"},
34 | {file = "pillow-11.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a85b653980faad27e88b141348707ceeef8a1186f75ecc600c395dcac19f385b"},
35 | {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9409c080586d1f683df3f184f20e36fb647f2e0bc3988094d4fd8c9f4eb1b3b3"},
36 | {file = "pillow-11.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fdadc077553621911f27ce206ffcbec7d3f8d7b50e0da39f10997e8e2bb7f6a"},
37 | {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:93a18841d09bcdd774dcdc308e4537e1f867b3dec059c131fde0327899734aa1"},
38 | {file = "pillow-11.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:9aa9aeddeed452b2f616ff5507459e7bab436916ccb10961c4a382cd3e03f47f"},
39 | {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3cdcdb0b896e981678eee140d882b70092dac83ac1cdf6b3a60e2216a73f2b91"},
40 | {file = "pillow-11.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36ba10b9cb413e7c7dfa3e189aba252deee0602c86c309799da5a74009ac7a1c"},
41 | {file = "pillow-11.1.0-cp312-cp312-win32.whl", hash = "sha256:cfd5cd998c2e36a862d0e27b2df63237e67273f2fc78f47445b14e73a810e7e6"},
42 | {file = "pillow-11.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:a697cd8ba0383bba3d2d3ada02b34ed268cb548b369943cd349007730c92bddf"},
43 | {file = "pillow-11.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:4dd43a78897793f60766563969442020e90eb7847463eca901e41ba186a7d4a5"},
44 | {file = "pillow-11.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ae98e14432d458fc3de11a77ccb3ae65ddce70f730e7c76140653048c71bfcbc"},
45 | {file = "pillow-11.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cc1331b6d5a6e144aeb5e626f4375f5b7ae9934ba620c0ac6b3e43d5e683a0f0"},
46 | {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:758e9d4ef15d3560214cddbc97b8ef3ef86ce04d62ddac17ad39ba87e89bd3b1"},
47 | {file = "pillow-11.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b523466b1a31d0dcef7c5be1f20b942919b62fd6e9a9be199d035509cbefc0ec"},
48 | {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:9044b5e4f7083f209c4e35aa5dd54b1dd5b112b108648f5c902ad586d4f945c5"},
49 | {file = "pillow-11.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3764d53e09cdedd91bee65c2527815d315c6b90d7b8b79759cc48d7bf5d4f114"},
50 | {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31eba6bbdd27dde97b0174ddf0297d7a9c3a507a8a1480e1e60ef914fe23d352"},
51 | {file = "pillow-11.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b5d658fbd9f0d6eea113aea286b21d3cd4d3fd978157cbf2447a6035916506d3"},
52 | {file = "pillow-11.1.0-cp313-cp313-win32.whl", hash = "sha256:f86d3a7a9af5d826744fabf4afd15b9dfef44fe69a98541f666f66fbb8d3fef9"},
53 | {file = "pillow-11.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:593c5fd6be85da83656b93ffcccc2312d2d149d251e98588b14fbc288fd8909c"},
54 | {file = "pillow-11.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:11633d58b6ee5733bde153a8dafd25e505ea3d32e261accd388827ee987baf65"},
55 | {file = "pillow-11.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:70ca5ef3b3b1c4a0812b5c63c57c23b63e53bc38e758b37a951e5bc466449861"},
56 | {file = "pillow-11.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8000376f139d4d38d6851eb149b321a52bb8893a88dae8ee7d95840431977081"},
57 | {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ee85f0696a17dd28fbcfceb59f9510aa71934b483d1f5601d1030c3c8304f3c"},
58 | {file = "pillow-11.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:dd0e081319328928531df7a0e63621caf67652c8464303fd102141b785ef9547"},
59 | {file = "pillow-11.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e63e4e5081de46517099dc30abe418122f54531a6ae2ebc8680bcd7096860eab"},
60 | {file = "pillow-11.1.0-cp313-cp313t-win32.whl", hash = "sha256:dda60aa465b861324e65a78c9f5cf0f4bc713e4309f83bc387be158b077963d9"},
61 | {file = "pillow-11.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ad5db5781c774ab9a9b2c4302bbf0c1014960a0a7be63278d13ae6fdf88126fe"},
62 | {file = "pillow-11.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:67cd427c68926108778a9005f2a04adbd5e67c442ed21d95389fe1d595458756"},
63 | {file = "pillow-11.1.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:bf902d7413c82a1bfa08b06a070876132a5ae6b2388e2712aab3a7cbc02205c6"},
64 | {file = "pillow-11.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c1eec9d950b6fe688edee07138993e54ee4ae634c51443cfb7c1e7613322718e"},
65 | {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e275ee4cb11c262bd108ab2081f750db2a1c0b8c12c1897f27b160c8bd57bbc"},
66 | {file = "pillow-11.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4db853948ce4e718f2fc775b75c37ba2efb6aaea41a1a5fc57f0af59eee774b2"},
67 | {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:ab8a209b8485d3db694fa97a896d96dd6533d63c22829043fd9de627060beade"},
68 | {file = "pillow-11.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:54251ef02a2309b5eec99d151ebf5c9904b77976c8abdcbce7891ed22df53884"},
69 | {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5bb94705aea800051a743aa4874bb1397d4695fb0583ba5e425ee0328757f196"},
70 | {file = "pillow-11.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:89dbdb3e6e9594d512780a5a1c42801879628b38e3efc7038094430844e271d8"},
71 | {file = "pillow-11.1.0-cp39-cp39-win32.whl", hash = "sha256:e5449ca63da169a2e6068dd0e2fcc8d91f9558aba89ff6d02121ca8ab11e79e5"},
72 | {file = "pillow-11.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:3362c6ca227e65c54bf71a5f88b3d4565ff1bcbc63ae72c34b07bbb1cc59a43f"},
73 | {file = "pillow-11.1.0-cp39-cp39-win_arm64.whl", hash = "sha256:b20be51b37a75cc54c2c55def3fa2c65bb94ba859dde241cd0a4fd302de5ae0a"},
74 | {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8c730dc3a83e5ac137fbc92dfcfe1511ce3b2b5d7578315b63dbbb76f7f51d90"},
75 | {file = "pillow-11.1.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:7d33d2fae0e8b170b6a6c57400e077412240f6f5bb2a342cf1ee512a787942bb"},
76 | {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8d65b38173085f24bc07f8b6c505cbb7418009fa1a1fcb111b1f4961814a442"},
77 | {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:015c6e863faa4779251436db398ae75051469f7c903b043a48f078e437656f83"},
78 | {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d44ff19eea13ae4acdaaab0179fa68c0c6f2f45d66a4d8ec1eda7d6cecbcc15f"},
79 | {file = "pillow-11.1.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3d8da4a631471dfaf94c10c85f5277b1f8e42ac42bade1ac67da4b4a7359b73"},
80 | {file = "pillow-11.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4637b88343166249fe8aa94e7c4a62a180c4b3898283bb5d3d2fd5fe10d8e4e0"},
81 | {file = "pillow-11.1.0.tar.gz", hash = "sha256:368da70808b36d73b4b390a8ffac11069f8a5c85f29eff1f1b01bcf3ef5b2a20"},
82 | ]
83 |
84 | [package.extras]
85 | docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
86 | fpx = ["olefile"]
87 | mic = ["olefile"]
88 | tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "trove-classifiers (>=2024.10.12)"]
89 | typing = ["typing-extensions"]
90 | xmp = ["defusedxml"]
91 |
92 | [metadata]
93 | lock-version = "2.1"
94 | python-versions = ">=3.13"
95 | content-hash = "fe85fcbda1e6175d6a7bf926f2d5ea63838bcb5e6404c2353753bd532ac87b1e"
96 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [project]
2 | name = "scripts"
3 | version = "0.1.0"
4 | description = ""
5 | authors = [
6 | {name = "Rukenshia",email = "code@chrstphrsn.cc"}
7 | ]
8 | readme = "README.md"
9 | requires-python = ">=3.13"
10 | dependencies = [
11 | "pillow (>=11.1.0,<12.0.0)"
12 | ]
13 |
14 |
--------------------------------------------------------------------------------
/scripts/gen_assets.py:
--------------------------------------------------------------------------------
1 | """
2 | This script processes the assets directory to generate relevant C files
3 | """
4 |
5 | import os
6 | import sys
7 | import shutil
8 | from PIL import Image
9 |
10 | # Directories
11 | ICON_INPUT_DIR = sys.argv[1] if len(sys.argv) > 1 else "assets/icons"
12 | IMAGE_INPUT_DIR = "assets/images" # Background images (800x480 PNGs)
13 | OUTPUT_DIR = "src"
14 | ICONS_DIR = os.path.join(OUTPUT_DIR, "icons")
15 | IMAGES_DIR = os.path.join(OUTPUT_DIR, "images")
16 | ICONS_HEADER_FILE = os.path.join(OUTPUT_DIR, "icons.h")
17 | ICONS_SOURCE_FILE = os.path.join(ICONS_DIR, "icons.cpp")
18 | IMAGES_AGGREGATE_HEADER_FILE = os.path.join(OUTPUT_DIR, "images.h")
19 |
20 |
21 | def image_to_c_array(image):
22 | width, height = image.size
23 | byte_data = []
24 | for y in range(height):
25 | byte_val, bit_count = 0, 0
26 | for x in range(width):
27 | if image.getpixel((x, y)) == 0: # BLACK pixel
28 | byte_val |= 1 << (7 - bit_count)
29 | bit_count += 1
30 | if bit_count == 8:
31 | byte_data.append(byte_val)
32 | byte_val, bit_count = 0, 0
33 | if bit_count > 0:
34 | byte_data.append(byte_val)
35 | return byte_data
36 |
37 |
38 | def process_icon_image(image_path):
39 | img = Image.open(image_path)
40 | if img.mode in ("RGBA", "LA"):
41 | img = img.convert("RGBA")
42 | new_img = Image.new("RGB", img.size, (255, 255, 255))
43 | new_img.paste(img, mask=img.split()[3])
44 | img = new_img.convert("L")
45 | else:
46 | img = img.convert("L")
47 | sizes = [192, 128, 64, 48]
48 | c_arrays = {}
49 | for size in sizes:
50 | resized = img.resize((size, size), Image.LANCZOS)
51 | bw_img = resized.convert("1", dither=Image.FLOYDSTEINBERG)
52 | c_arrays[size] = image_to_c_array(bw_img)
53 | return c_arrays
54 |
55 |
56 | def process_icons_directory(input_directory):
57 | if not os.path.exists(input_directory):
58 | print(f"❌ Directory '{input_directory}' does not exist.")
59 | return
60 |
61 | files = [
62 | f for f in os.listdir(input_directory) if f.lower().endswith((".bmp", ".png"))
63 | ]
64 | if not files:
65 | print(f"⚠ No BMP or PNG files found in '{input_directory}'.")
66 | return
67 |
68 | os.makedirs(OUTPUT_DIR, exist_ok=True)
69 | os.makedirs(ICONS_DIR, exist_ok=True)
70 |
71 | icon_names = []
72 | icon_data_entries = []
73 |
74 | with open(ICONS_SOURCE_FILE, "w") as cpp_file:
75 | cpp_file.write('#include "icons.h"\n\n')
76 | for filename in files:
77 | file_path = os.path.join(input_directory, filename)
78 | icon_slug = os.path.splitext(filename)[0]
79 | print(f"🔄 Processing icon {filename}...")
80 | c_arrays = process_icon_image(file_path)
81 | icon_names.append(icon_slug)
82 | for size, data in c_arrays.items():
83 | cpp_file.write(
84 | f"const unsigned char icon_{icon_slug}_{size}[] = {{\n "
85 | )
86 | for i, byte in enumerate(data):
87 | cpp_file.write(f"0x{byte:02X}, ")
88 | if (i + 1) % 12 == 0:
89 | cpp_file.write("\n ")
90 | cpp_file.write("\n};\n\n")
91 | icon_data_entries.append(
92 | f"Icon icon_{icon_slug}(icon_{icon_slug}_192, icon_{icon_slug}_128, icon_{icon_slug}_64, icon_{icon_slug}_48);"
93 | )
94 | cpp_file.write("\n".join(icon_data_entries))
95 | cpp_file.write("\n")
96 |
97 | with open(ICONS_HEADER_FILE, "w") as header_file:
98 | header_file.write("#ifndef ICONS_H\n#define ICONS_H\n\n")
99 | header_file.write('#include "icon.h"\n\n')
100 | for icon_slug in icon_names:
101 | header_file.write(f"extern Icon icon_{icon_slug};\n")
102 | header_file.write("\n#endif // ICONS_H\n")
103 |
104 | print(f"✔ Icons header file saved: {ICONS_HEADER_FILE}")
105 | print(f"✔ Icons source file saved: {ICONS_SOURCE_FILE}")
106 |
107 |
108 | def process_background_image(image_path):
109 | img = Image.open(image_path)
110 | if img.mode in ("RGBA", "LA"):
111 | img = img.convert("RGBA")
112 | new_img = Image.new("RGB", img.size, (255, 255, 255))
113 | new_img.paste(img, mask=img.split()[3])
114 | img = new_img.convert("L")
115 | else:
116 | img = img.convert("L")
117 | # Background images are assumed to be 800x480; no resizing.
118 | bw_img = img.convert("1", dither=Image.FLOYDSTEINBERG)
119 | return image_to_c_array(bw_img)
120 |
121 |
122 | def process_images_directory(input_directory):
123 | if not os.path.exists(input_directory):
124 | print(f"❌ Directory '{input_directory}' does not exist.")
125 | return
126 |
127 | files = [f for f in os.listdir(input_directory) if f.lower().endswith(".png")]
128 | if not files:
129 | print(f"⚠ No PNG files found in '{input_directory}'.")
130 | return
131 |
132 | os.makedirs(OUTPUT_DIR, exist_ok=True)
133 | os.makedirs(IMAGES_DIR, exist_ok=True)
134 |
135 | image_slugs = []
136 |
137 | for filename in files:
138 | file_path = os.path.join(input_directory, filename)
139 | image_slug = os.path.splitext(filename)[0]
140 | image_slugs.append(image_slug)
141 | print(f"🔄 Processing background image {filename}...")
142 | c_array = process_background_image(file_path)
143 | header_filename = f"image_{image_slug}.h"
144 | header_filepath = os.path.join(IMAGES_DIR, header_filename)
145 | include_guard = f"IMAGE_{image_slug.upper()}_H"
146 | with open(header_filepath, "w") as header_file:
147 | header_file.write(f"#ifndef {include_guard}\n#define {include_guard}\n\n")
148 | header_file.write(f"const unsigned char image_{image_slug}[] = {{\n ")
149 | for i, byte in enumerate(c_array):
150 | header_file.write(f"0x{byte:02X}, ")
151 | if (i + 1) % 12 == 0:
152 | header_file.write("\n ")
153 | header_file.write("\n};\n\n")
154 | header_file.write(f"#endif // {include_guard}\n")
155 | print(f"✔ Background image header saved: {header_filepath}")
156 |
157 | # Create a single aggregated header for all background images.
158 | with open(IMAGES_AGGREGATE_HEADER_FILE, "w") as agg_header:
159 | agg_header.write("#ifndef IMAGES_H\n#define IMAGES_H\n\n")
160 | for slug in image_slugs:
161 | agg_header.write(f'#include "images/image_{slug}.h"\n')
162 | agg_header.write("\n#endif // IMAGES_H\n")
163 | print(f"✔ Aggregated images header saved: {IMAGES_AGGREGATE_HEADER_FILE}")
164 |
165 |
166 | def process_font(font_name, sizes):
167 | """Process a font file in multiple sizes using fontconvert."""
168 | font_path = os.path.join("assets/fonts", font_name)
169 | if not os.path.exists(font_path):
170 | print(f"❌ Font file not found: {font_path}")
171 | return
172 |
173 | output_dir = os.path.join(OUTPUT_DIR, "fonts")
174 | os.makedirs(output_dir, exist_ok=True)
175 |
176 | for size in sizes:
177 | # Replace hyphens with underscores in the output filename
178 | base_name = os.path.splitext(font_name)[0].replace("-", "_")
179 | output_name = f"{base_name}{size}pt7b"
180 | output_path = os.path.join(output_dir, f"{output_name}.h")
181 |
182 | # Run fontconvert tool
183 | cmd = f"fontconvert {font_path} {size} > {output_path}"
184 | ret = os.system(cmd)
185 | if ret == 0:
186 | print(f"✔ Generated font header: {output_path}")
187 | else:
188 | print(f"❌ Failed to generate font: {output_path}")
189 |
190 |
191 | def process_fonts():
192 | print("🔄 Processing fonts...")
193 |
194 | # Clean up existing fonts directory
195 | fonts_dir = os.path.join(OUTPUT_DIR, "fonts")
196 | if os.path.exists(fonts_dir):
197 | print(f"🗑 Removing existing fonts directory: {fonts_dir}")
198 | shutil.rmtree(fonts_dir)
199 |
200 | # Process only the fonts that are actually used in the project
201 | fonts_to_process = {
202 | "FunnelDisplay-Regular.ttf": [14],
203 | "FunnelDisplay-Bold.ttf": [18, 24, 32, 48, 60],
204 | "HelvetiPixel.ttf": [16, 24],
205 | }
206 |
207 | for font_name, sizes in fonts_to_process.items():
208 | process_font(font_name, sizes)
209 |
210 |
211 | # Process icons, background images, and fonts
212 | if __name__ == "__main__":
213 | process_icons_directory(ICON_INPUT_DIR)
214 | process_images_directory(IMAGE_INPUT_DIR)
215 | process_fonts()
216 |
--------------------------------------------------------------------------------
/src/GxEPD2_display_selection_new_style.h:
--------------------------------------------------------------------------------
1 | // Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
2 | // Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines!
3 | //
4 | // Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/
5 | //
6 | // Author: Jean-Marc Zingg
7 | //
8 | // Version: see library.properties
9 | //
10 | // Library: https://github.com/ZinggJM/GxEPD2
11 |
12 | // Supporting Arduino Forum Topics (closed, read only):
13 | // Good Display ePaper for Arduino: https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657
14 | // Waveshare e-paper displays with SPI: https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865
15 | //
16 | // Add new topics in https://forum.arduino.cc/c/using-arduino/displays/23 for new questions and issues
17 |
18 | // NOTE: you may need to adapt or select for your wiring in the processor specific conditional compile sections below
19 |
20 | #ifndef GxEPD2_DISPLAYS_H
21 | #define GxEPD2_DISPLAYS_H
22 |
23 | // select the display class (only one), matching the kind of display panel
24 | #define GxEPD2_DISPLAY_CLASS GxEPD2_BW
25 |
26 | #define GxEPD2_DRIVER_CLASS GxEPD2_426_GDEQ0426T82 // GDEQ0426T82 480x800, SSD1677 (P426010-MF1-A)
27 |
28 | // SS is usually used for CS. define here for easy change
29 | #ifndef EPD_CS
30 | #define EPD_CS SS
31 | #endif
32 |
33 | #if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS)
34 |
35 | // somehow there should be an easier way to do this
36 | #define GxEPD2_BW_IS_GxEPD2_BW true
37 | #define GxEPD2_3C_IS_GxEPD2_3C true
38 | #define GxEPD2_4C_IS_GxEPD2_4C true
39 | #define GxEPD2_7C_IS_GxEPD2_7C true
40 | #define GxEPD2_1248_IS_GxEPD2_1248 true
41 | #define GxEPD2_1248c_IS_GxEPD2_1248c true
42 | #define IS_GxEPD(c, x) (c##x)
43 | #define IS_GxEPD2_BW(x) IS_GxEPD(GxEPD2_BW_IS_, x)
44 | #define IS_GxEPD2_3C(x) IS_GxEPD(GxEPD2_3C_IS_, x)
45 | #define IS_GxEPD2_4C(x) IS_GxEPD(GxEPD2_4C_IS_, x)
46 | #define IS_GxEPD2_7C(x) IS_GxEPD(GxEPD2_7C_IS_, x)
47 | #define IS_GxEPD2_1248(x) IS_GxEPD(GxEPD2_1248_IS_, x)
48 | #define IS_GxEPD2_1248c(x) IS_GxEPD(GxEPD2_1248c_IS_, x)
49 |
50 | #include "GxEPD2_selection_check.h"
51 |
52 | // for my test use only
53 | // #if defined(_AVR)
54 | // #warning "defined(_AVR)"
55 | // #endif
56 | // #if defined(ESP32)
57 | // #warning "defined(ESP32)"
58 | // #endif
59 | // #if defined(ARDUINO_ARCH_ESP32)
60 | // #warning "defined(ARDUINO_ARCH_ESP32)"
61 | // #endif
62 |
63 | #if defined(ARDUINO_ARCH_AVR)
64 | #if defined(ARDUINO_AVR_MEGA2560) // Note: SS is on 53 on MEGA
65 | #define MAX_DISPLAY_BUFFER_SIZE 5000 // e.g. full height for 200x200
66 | #elif defined(ARDUINO_AVR_NANO_EVERY)
67 | #define EPD_CS 10
68 | #define MAX_DISPLAY_BUFFER_SIZE 5000 // e.g. full height for 200x200
69 | #else // Note: SS is on 10 on UNO, NANO
70 | #define MAX_DISPLAY_BUFFER_SIZE 800 //
71 | #endif
72 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
73 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
74 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
75 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
76 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
77 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
78 | #endif
79 | // adapt the constructor parameters to your wiring
80 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS, /*DC=*/8, /*RST=*/9, /*BUSY=*/7));
81 | // for Arduino Micro or Arduino Leonardo with CS on 10 on my proto boards (SS would be 17) uncomment instead:
82 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 10, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
83 | #endif
84 |
85 | #if defined(ARDUINO_ARCH_MEGAAVR)
86 | #if defined(ARDUINO_AVR_NANO_EVERY)
87 | #define MAX_DISPLAY_BUFFER_SIZE 5000 // e.g. full height for 200x200
88 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
89 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
90 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
91 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
92 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
93 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
94 | #endif
95 | // adapt the constructor parameters to your wiring
96 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/10, /*DC=*/8, /*RST=*/9, /*BUSY=*/7));
97 | #endif
98 | #endif
99 |
100 | #if defined(ARDUINO_ARCH_ESP32)
101 | #define MAX_DISPLAY_BUFFER_SIZE 65536ul // e.g.
102 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
103 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
104 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
105 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
106 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
107 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
108 | #endif
109 | // adapt the constructor parameters to your wiring
110 | #if !IS_GxEPD2_1248(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_1248c(GxEPD2_DRIVER_CLASS)
111 | #if defined(ARDUINO_NANO_ESP32) // uses Dx pin names
112 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/D10, /*DC=*/D8, /*RST=*/D9, /*BUSY=*/D7));
113 | #elif defined(ARDUINO_LOLIN_D32_PRO)
114 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/0, /*RST=*/2, /*BUSY=*/15)); // my LOLIN_D32_PRO proto board
115 | #elif defined(ARDUINO_LOLIN_S2_MINI)
116 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS*/ 33, /*DC=*/35, /*RST=*/37, /*BUSY=*/39)); // my LOLIN ESP32 S2 mini connection
117 | #else
118 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 27, /*DC=*/ 14, /*RST=*/ 12, /*BUSY=*/ 13)); // Good Display ESP32 Development Kit ESP32-L
119 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 27, /*DC=*/ 14, /*RST=*/ 12, /*BUSY=*/ 13, /*CS2=*/ 4)); // for GDEM1085T51 with ESP32-L
120 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/17, /*RST=*/16, /*BUSY=*/4)); // my suggested wiring and proto board
121 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ 5, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // LILYGO_T5_V2.4.1
122 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 19, /*RST=*/ 4, /*BUSY=*/ 34)); // LILYGO® TTGO T5 2.66
123 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 2, /*RST=*/ 0, /*BUSY=*/ 4)); // e.g. TTGO T8 ESP32-WROVER
124 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25)); // Waveshare ESP32 Driver Board
125 | #endif
126 | #else // GxEPD2_1248 or GxEPD2_1248c
127 | // Waveshare 12.48 b/w or b/w/r SPI display board and frame or Good Display 12.48 b/w panel GDEW1248T3 or b/w/r panel GDEY1248Z51
128 | // general constructor for use with all parameters, e.g. for Waveshare ESP32 driver board mounted on connection board
129 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*sck=*/13, /*miso=*/12, /*mosi=*/14,
130 | /*cs_m1=*/23, /*cs_s1=*/22, /*cs_m2=*/16, /*cs_s2=*/19,
131 | /*dc1=*/25, /*dc2=*/17, /*rst1=*/33, /*rst2=*/5,
132 | /*busy_m1=*/32, /*busy_s1=*/26, /*busy_m2=*/18, /*busy_s2=*/4));
133 | #endif
134 | #undef MAX_DISPLAY_BUFFER_SIZE
135 | #undef MAX_HEIGHT
136 | #endif
137 |
138 | #if defined(ARDUINO_ARCH_ESP8266)
139 | #define MAX_DISPLAY_BUFFER_SIZE (81920ul - 34000ul - 5000ul) // ~34000 base use, change 5000 to your application use
140 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
141 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
142 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
143 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
144 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
145 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
146 | #endif
147 | // adapt the constructor parameters to your wiring
148 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
149 | // mapping of Waveshare e-Paper ESP8266 Driver Board, new version
150 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=2*/ 2, /*BUSY=5*/ 5));
151 | // mapping of Waveshare e-Paper ESP8266 Driver Board, old version
152 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=5*/ 5, /*BUSY=16*/ 16));
153 | #undef MAX_DISPLAY_BUFFER_SIZE
154 | #undef MAX_HEIGHT
155 | #endif
156 |
157 | // can't use package "STMF1 Boards (STM32Duino.com)" (Roger Clark) anymore with Adafruit_GFX, use "STM32 Boards (selected from submenu)" (STMicroelectronics)
158 | #if defined(ARDUINO_ARCH_STM32)
159 | #define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise
160 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
161 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
162 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
163 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
164 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
165 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
166 | #endif
167 | // adapt the constructor parameters to your wiring
168 | // for Good Display STM32 Development Kit DESPI-L.
169 | // needs jumpers from PA5 (PIN_SPI_SCK) to SCK for EPD and PA7 (PIN_SPI_MOSI) to SDI for EPD. PD9 and PD10 are not HW SPI capable.
170 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ PD8, /*DC=*/ PE15, /*RST=*/ PE14, /*BUSY=*/ PE13));
171 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ PD8, /*DC=*/ PE15, /*RST=*/ PE14, /*BUSY=*/ PE13, /*CS2=*/ PD12)); // for GDEM1085T51
172 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=PA4*/ EPD_CS, /*DC=*/PA3, /*RST=*/PA2, /*BUSY=*/PA1)); // my proto board
173 | #undef MAX_DISPLAY_BUFFER_SIZE
174 | #undef MAX_HEIGHT
175 | #endif
176 |
177 | #if defined(ARDUINO_ARCH_RENESAS)
178 | #if defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI)
179 | #define MAX_DISPLAY_BUFFER_SIZE 16384ul // e.g. half of available RAM
180 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
181 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
182 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
183 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
184 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
185 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
186 | #endif
187 | // adapt the constructor parameters to your wiring
188 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS, /*DC=*/8, /*RST=*/9, /*BUSY=*/7));
189 | #endif
190 | #endif
191 |
192 | #if defined(ARDUINO_ARCH_SAM)
193 | #define MAX_DISPLAY_BUFFER_SIZE 32768ul // e.g., up to 96k
194 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
195 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
196 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
197 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
198 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
199 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
200 | #endif
201 | // adapt the constructor parameters to your wiring
202 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=10*/ EPD_CS, /*DC=*/8, /*RST=*/9, /*BUSY=*/7));
203 | #undef MAX_DISPLAY_BUFFER_SIZE
204 | #undef MAX_HEIGHT
205 | #endif
206 |
207 | #if defined(ARDUINO_ARCH_SAMD)
208 | #define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise
209 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
210 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
211 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
212 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
213 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
214 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
215 | #endif
216 | #if defined(ARDUINO_SAMD_NANO_33_IOT)
217 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=10*/ EPD_CS, /*DC=*/8, /*RST=*/9, /*BUSY=*/7));
218 | #else
219 | // adapt the constructor parameters to your wiring
220 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/7, /*RST=*/6, /*BUSY=*/5));
221 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); // my Seed XIOA0
222 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // my other Seed XIOA0
223 | #endif
224 | #undef MAX_DISPLAY_BUFFER_SIZE
225 | #undef MAX_HEIGHT
226 | #endif
227 |
228 | #if defined(ARDUINO_ARCH_RP2040)
229 | #define MAX_DISPLAY_BUFFER_SIZE 131072ul // e.g. half of available ram
230 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
231 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
232 | #elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
233 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
234 | #elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
235 | #define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
236 | #endif
237 | #if defined(ARDUINO_NANO_RP2040_CONNECT)
238 | // adapt the constructor parameters to your wiring
239 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/EPD_CS, /*DC=*/8, /*RST=*/9, /*BUSY=*/7));
240 | #endif
241 | #if defined(ARDUINO_RASPBERRY_PI_PICO)
242 | // adapt the constructor parameters to your wiring
243 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 5, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // my proto board
244 | // mapping of GoodDisplay DESPI-PICO. NOTE: uses alternate HW SPI pins!
245 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/3, /*DC=*/2, /*RST=*/1, /*BUSY=*/0)); // DESPI-PICO
246 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 3, /*DC=*/ 2, /*RST=*/ 11, /*BUSY=*/ 10)); // DESPI-PICO modified
247 | // GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/ 9, /*DC=*/ 8, /*RST=*/ 12, /*BUSY=*/ 13)); // Waveshare Pico-ePaper-2.9
248 | #endif
249 | #if defined(ARDUINO_RASPBERRY_PI_PICO_W)
250 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/9, /*DC=*/8, /*RST=*/12, /*BUSY=*/13)); // Waveshare Pico-ePaper-2.9
251 | #endif
252 | #if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040_THINKINK)
253 | // Adafruit Feather RP2040 ThinkInk used with package https://github.com/earlephilhower/arduino-pico
254 | GxEPD2_DISPLAY_CLASS display(GxEPD2_DRIVER_CLASS(/*CS=*/PIN_EPD_CS, /*DC=*/PIN_EPD_DC, /*RST=*/PIN_EPD_RESET, /*BUSY=*/PIN_EPD_BUSY));
255 | #endif
256 | #undef MAX_DISPLAY_BUFFER_SIZE
257 | #undef MAX_HEIGHT
258 | #endif
259 |
260 | #endif
261 |
262 | #endif
--------------------------------------------------------------------------------
/src/GxEPD2_selection_check.h:
--------------------------------------------------------------------------------
1 | // Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
2 | // Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines!
3 | //
4 | // Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/
5 | //
6 | // Author: Jean-Marc Zingg
7 | //
8 | // Version: see library.properties
9 | //
10 | // Library: https://github.com/ZinggJM/GxEPD2
11 |
12 | // Supporting Arduino Forum Topics (closed, read only):
13 | // Good Display ePaper for Arduino: https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657
14 | // Waveshare e-paper displays with SPI: https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865
15 | //
16 | // Add new topics in https://forum.arduino.cc/c/using-arduino/displays/23 for new questions and issues
17 |
18 | #define GxEPD2_102_IS_BW true
19 | #define GxEPD2_150_BN_IS_BW true
20 | #define GxEPD2_154_IS_BW true
21 | #define GxEPD2_154_D67_IS_BW true
22 | #define GxEPD2_154_T8_IS_BW true
23 | #define GxEPD2_154_M09_IS_BW true
24 | #define GxEPD2_154_M10_IS_BW true
25 | #define GxEPD2_154_GDEY0154D67_IS_BW true
26 | #define GxEPD2_213_IS_BW true
27 | #define GxEPD2_213_B72_IS_BW true
28 | #define GxEPD2_213_B73_IS_BW true
29 | #define GxEPD2_213_B74_IS_BW true
30 | #define GxEPD2_213_flex_IS_BW true
31 | #define GxEPD2_213_M21_IS_BW true
32 | #define GxEPD2_213_T5D_IS_BW true
33 | #define GxEPD2_213_BN_IS_BW true
34 | #define GxEPD2_213_GDEY0213B74_IS_BW true
35 | #define GxEPD2_260_IS_BW true
36 | #define GxEPD2_260_M01_IS_BW true
37 | #define GxEPD2_266_BN_IS_BW true
38 | #define GxEPD2_266_GDEY0266T90_IS_BW true
39 | #define GxEPD2_270_IS_BW true
40 | #define GxEPD2_270_GDEY027T91_IS_BW true
41 | #define GxEPD2_290_IS_BW true
42 | #define GxEPD2_290_T5_IS_BW true
43 | #define GxEPD2_290_T5D_IS_BW true
44 | #define GxEPD2_290_I6FD_IS_BW true
45 | #define GxEPD2_290_T94_IS_BW true
46 | #define GxEPD2_290_T94_V2_IS_BW true
47 | #define GxEPD2_290_BS_IS_BW true
48 | #define GxEPD2_290_M06_IS_BW true
49 | #define GxEPD2_290_GDEY029T94_IS_BW true
50 | #define GxEPD2_290_GDEY029T71H_IS_BW true
51 | #define GxEPD2_310_GDEQ031T10_IS_BW true
52 | #define GxEPD2_371_IS_BW true
53 | #define GxEPD2_370_TC1_IS_BW true
54 | #define GxEPD2_420_IS_BW true
55 | #define GxEPD2_420_M01_IS_BW true
56 | #define GxEPD2_420_GDEY042T81_IS_BW true
57 | #define GxEPD2_420_GYE042A87_IS_BW true
58 | #define GxEPD2_420_SE0420NQ04_IS_BW true
59 | #define GxEPD2_426_GDEQ0426T82_IS_BW true
60 | #define GxEPD2_579_GDEY0579T93_IS_BW true
61 | #define GxEPD2_583_IS_BW true
62 | #define GxEPD2_583_T8_IS_BW true
63 | #define GxEPD2_583_GDEQ0583T31_IS_BW true
64 | #define GxEPD2_750_IS_BW true
65 | #define GxEPD2_750_T7_IS_BW true
66 | #define GxEPD2_750_GDEY075T7_IS_BW true
67 | #define GxEPD2_1020_GDEM102T91_IS_BW true
68 | #define GxEPD2_1085_GDEM1085T51_IS_BW true
69 | #define GxEPD2_1160_T91_IS_BW true
70 | #define GxEPD2_1248_IS_BW true
71 | #define GxEPD2_1330_GDEM133T91_IS_BW true
72 | #define GxEPD2_it60_IS_BW true
73 | #define GxEPD2_it60_1448x1072_IS_BW true
74 | #define GxEPD2_it78_1872x1404_IS_BW true
75 | #define GxEPD2_it103_1872x1404_IS_BW true
76 | // 3-color e-papers
77 | #define GxEPD2_154c_IS_3C true
78 | #define GxEPD2_154_Z90c_IS_3C true
79 | #define GxEPD2_213c_IS_3C true
80 | #define GxEPD2_213_Z19c_IS_3C true
81 | #define GxEPD2_213_Z98c_IS_3C true
82 | #define GxEPD2_266c_IS_3C true
83 | #define GxEPD2_270c_IS_3C true
84 | #define GxEPD2_290c_IS_3C true
85 | #define GxEPD2_290_Z13c_IS_3C true
86 | #define GxEPD2_290_C90c_IS_3C true
87 | #define GxEPD2_420c_IS_3C true
88 | #define GxEPD2_420c_Z21_IS_3C true
89 | #define GxEPD2_420c_GDEY042Z98_IS_3C true
90 | #define GxEPD2_579c_GDEY0579Z93_IS_3C true
91 | #define GxEPD2_583c_IS_3C true
92 | #define GxEPD2_583c_Z83_IS_3C true
93 | #define GxEPD2_583c_GDEQ0583Z31_IS_3C true
94 | #define GxEPD2_750c_IS_3C true
95 | #define GxEPD2_750c_Z08_IS_3C true
96 | #define GxEPD2_750c_Z90_IS_3C true
97 | #define GxEPD2_1160c_GDEY116Z91_IS_3C true
98 | #define GxEPD2_1248c_IS_3C true
99 | #define GxEPD2_1330c_GDEM133Z91_IS_3C true
100 | // 4-color e-paper
101 | #define GxEPD2_213c_GDEY0213F51_IS_4C true
102 | #define GxEPD2_266c_GDEY0266F51H_IS_4C true
103 | #define GxEPD2_290c_GDEY029F51H_IS_4C true
104 | #define GxEPD2_300c_IS_4C true
105 | #define GxEPD2_420c_GDEY0420F51_IS_4C true
106 | #define GxEPD2_437c_IS_4C true
107 | #define GxEPD2_0579c_GDEY0579F51_IS_4C true
108 | #define GxEPD2_1160c_GDEY116F51_IS_4C true
109 | // 7-color e-paper
110 | #define GxEPD2_565c_IS_7C true
111 | #define GxEPD2_565c_GDEP0565D90_IS_7C true
112 | #define GxEPD2_730c_GDEY073D46_IS_7C true
113 | #define GxEPD2_730c_ACeP_730_IS_7C true
114 | #define GxEPD2_730c_GDEP073E01_IS_7C true
115 |
116 | #if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS)
117 | #define IS_GxEPD2_DRIVER(c, x) (c##x)
118 | #define IS_GxEPD2_DRIVER_BW(x) IS_GxEPD2_DRIVER(x, _IS_BW)
119 | #define IS_GxEPD2_DRIVER_3C(x) IS_GxEPD2_DRIVER(x, _IS_3C)
120 | #define IS_GxEPD2_DRIVER_4C(x) IS_GxEPD2_DRIVER(x, _IS_4C)
121 | #define IS_GxEPD2_DRIVER_7C(x) IS_GxEPD2_DRIVER(x, _IS_7C)
122 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS)
123 | #error "GxEPD2_BW used with 3-color driver class"
124 | #endif
125 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS)
126 | #error "GxEPD2_BW used with 4-color driver class"
127 | #endif
128 | #if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
129 | #error "GxEPD2_BW used with 7-color driver class"
130 | #endif
131 | #if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS)
132 | #error "GxEPD2_3C used with b/w driver class"
133 | #endif
134 | #if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS)
135 | #error "GxEPD2_3C used with 4-color driver class"
136 | #endif
137 | #if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
138 | #error "GxEPD2_3C used with 7-color driver class"
139 | #endif
140 | #if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS)
141 | #error "GxEPD2_4C used with b/w driver class"
142 | #endif
143 | #if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS)
144 | #error "GxEPD2_4C used with 3-color driver class"
145 | #endif
146 | #if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
147 | #error "GxEPD2_4C used with 7-color driver class"
148 | #endif
149 | #if IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) && !IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
150 | #error "GxEPD2_7C used with less colors driver class"
151 | #endif
152 | #if !IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
153 | #error "neither BW nor 3C nor 4C nor 7C kind defined for driver class (error in GxEPD2_selection_check.h)"
154 | #endif
155 |
156 | #endif
157 |
--------------------------------------------------------------------------------
/src/anniversary.h:
--------------------------------------------------------------------------------
1 | #ifndef ANNIVERSARY_H
2 | #define ANNIVERSARY_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "timer.h"
8 | #include "gfx_utils.h"
9 | #include "icons.h"
10 | #include "icon.h"
11 | #include "menu.h"
12 | #include "debug.h"
13 | #include "button.h"
14 | #include
15 |
16 | class Anniversary
17 | {
18 | private:
19 | DISPLAY_CLASS &display;
20 |
21 | Menu buttons = Menu(display, new MenuItem[1]{MenuItem("Weiter")}, 1);
22 |
23 | int page = 0;
24 | const uint16_t numPages = 17;
25 | unsigned long lastRedrawTime = 0;
26 |
27 | void drawPage(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
28 |
29 | public:
30 | Anniversary(DISPLAY_CLASS &display);
31 | ~Anniversary();
32 | void draw();
33 | void loop();
34 | };
35 |
36 | #endif
37 |
--------------------------------------------------------------------------------
/src/button.cpp:
--------------------------------------------------------------------------------
1 | #include "button.h"
2 |
3 | static const unsigned long DEBOUNCE_DELAY = 1000; // ms
4 |
5 | Button *Button::instance = nullptr;
6 | bool Button::instanceExists = false;
7 |
8 | Button::Button(int pin) : pin(pin)
9 | {
10 | if (instanceExists)
11 | {
12 | Serial.println("ERROR: Only one Button instance allowed!");
13 | return;
14 | }
15 |
16 | pinMode(pin, INPUT_PULLUP);
17 | attachInterrupt(pin, buttonInterruptHandler, FALLING);
18 | instance = this;
19 | instanceExists = true;
20 | }
21 |
22 | Button::~Button()
23 | {
24 | if (instance == this)
25 | {
26 | instance = nullptr;
27 | instanceExists = false;
28 | detachInterrupt(pin);
29 | }
30 | }
31 |
32 | void IRAM_ATTR Button::buttonInterruptHandler()
33 | {
34 | if (!instance)
35 | return; // Safety check
36 |
37 | unsigned long currentTime = millis();
38 |
39 | if ((currentTime - instance->lastPressTime) > DEBOUNCE_DELAY)
40 | {
41 | instance->pressed = true;
42 | instance->lastPressTime = currentTime;
43 | }
44 | }
45 |
46 | bool Button::checkAndClearButtonPress()
47 | {
48 | if (!instanceExists)
49 | return false; // Safety check
50 |
51 | if (pressed)
52 | {
53 | Serial.println("Button pressed");
54 | pressed = false;
55 | return true;
56 | }
57 | return false;
58 | }
--------------------------------------------------------------------------------
/src/button.h:
--------------------------------------------------------------------------------
1 | #ifndef BUTTON_H
2 | #define BUTTON_H
3 |
4 | #include
5 |
6 | class Button
7 | {
8 | private:
9 | int pin;
10 | static bool instanceExists;
11 |
12 | public:
13 | volatile bool pressed = false;
14 | volatile unsigned long lastPressTime = 0;
15 |
16 | static Button *instance;
17 |
18 | Button(int pin);
19 | ~Button();
20 |
21 | static void IRAM_ATTR buttonInterruptHandler();
22 | bool checkAndClearButtonPress();
23 | };
24 | #endif
--------------------------------------------------------------------------------
/src/checkbox.cpp:
--------------------------------------------------------------------------------
1 | #include "checkbox.h"
2 | #include "preferences_manager.h"
3 |
4 | extern Preferences preferences;
5 |
6 | #define NOVALUE 0
7 | #define TRUEVALUE -1
8 | #define FALSEVALUE 1
9 |
10 | Checkbox::Checkbox(Icon *icon, const char *name, const char *key, bool defaultValue) : icon(icon), name(name), key(key), defaultValue(defaultValue)
11 | {
12 | load();
13 | }
14 |
15 | Checkbox::~Checkbox()
16 | {
17 | save();
18 | }
19 |
20 | Icon *Checkbox::getIcon()
21 | {
22 | return icon;
23 | }
24 |
25 | const char *Checkbox::getName()
26 | {
27 | return name;
28 | }
29 |
30 | bool Checkbox::isChecked()
31 | {
32 | return checked;
33 | }
34 |
35 | void Checkbox::toggle()
36 | {
37 | checked = !checked;
38 | }
39 |
40 | void Checkbox::load()
41 | {
42 | checked = pref_getCheckbox(key, defaultValue);
43 |
44 | Serial.printf("Checkbox::load: key=%s, value=%s\n", key, checked ? "true" : "false");
45 | }
46 |
47 | void Checkbox::save()
48 | {
49 | pref_putCheckbox(key, checked);
50 |
51 | Serial.printf("Checkbox::save: key=%s, value=%s\n", key, checked ? "true" : "false");
52 | }
53 |
54 | void Checkbox::draw(
55 | DISPLAY_CLASS &display,
56 | uint16_t x,
57 | uint16_t y,
58 | uint16_t w,
59 | uint16_t h,
60 | bool selected)
61 | {
62 | const uint16_t padding = 12;
63 |
64 | display.fillRect(x, y, w, h, GxEPD_WHITE);
65 | display.drawRoundRect(x, y, w, h, 10, GxEPD_BLACK);
66 |
67 | if (selected)
68 | {
69 | drawPatternInRoundedArea(display, x, y, w, h, 10, Pattern::SparseDots);
70 | }
71 |
72 | ScaledIcon checkmark = icon_checkmark.scaled(48);
73 |
74 | const uint16_t iconSize = 64;
75 | if (icon)
76 | {
77 | ScaledIcon scaledIcon = icon->scaled(iconSize);
78 | display.drawBitmap(x + padding, y + padding, scaledIcon.data, scaledIcon.size, scaledIcon.size, GxEPD_BLACK);
79 | }
80 |
81 | Bounds bounds = getBounds(display, name, &MAIN_FONT);
82 | const uint16_t textX = x + padding + iconSize + padding;
83 | const uint16_t textY = y + h / 2 + bounds.h / 2;
84 |
85 | drawText(display, name, textX, textY, &MAIN_FONT, GxEPD_BLACK);
86 |
87 | display.drawRoundRect(x + w - padding - 64, y + h / 2 - 64 / 2, 64, 64, 10, GxEPD_BLACK);
88 |
89 | if (checked)
90 | {
91 | display.drawBitmap(x + w - padding - 64 / 2 - checkmark.size / 2, y + h / 2 - checkmark.size / 2, checkmark.data, checkmark.size, checkmark.size, GxEPD_BLACK);
92 | }
93 | }
--------------------------------------------------------------------------------
/src/checkbox.h:
--------------------------------------------------------------------------------
1 | #ifndef CHECKBOX_H
2 | #define CHECKBOX_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "icons.h"
9 | #include "icon.h"
10 | #include "gfx_utils.h"
11 | #include "debug.h"
12 | #include "defs.h"
13 |
14 | class Checkbox
15 | {
16 | private:
17 | Icon *icon;
18 | const char *name;
19 | const char *key;
20 | bool checked;
21 | bool defaultValue;
22 |
23 | Preferences preferences;
24 |
25 | public:
26 | Checkbox(Icon *icon, const char *name, const char *key, bool defaultValue = false);
27 | ~Checkbox();
28 | Icon *getIcon();
29 | const char *getName();
30 | bool isChecked();
31 | void toggle();
32 |
33 | void load();
34 | void save();
35 |
36 | void draw(
37 | DISPLAY_CLASS &display,
38 | uint16_t x,
39 | uint16_t y,
40 | uint16_t w,
41 | uint16_t h,
42 | bool selected);
43 | };
44 |
45 | #endif
--------------------------------------------------------------------------------
/src/debug.h:
--------------------------------------------------------------------------------
1 | #ifndef DEBUG_H
2 |
3 | #define DEBUG_H
4 |
5 | // #define DEBUG
6 |
7 | // #define ICON_SCALING_TEST 1
8 | // #define PATTERN_TEST 1
9 | // #define IMAGE_CYCLE_TEST 1
10 | // #define CHECKBOX_TEST 1
11 | // #define STRINGS_TEST 1
12 |
13 | #endif
--------------------------------------------------------------------------------
/src/defs.h:
--------------------------------------------------------------------------------
1 | #ifndef DEFS_H
2 | #define DEFS_H
3 |
4 | #include
5 | #include
6 | #include
7 |
8 | #include "fonts/FunnelDisplay_Bold24pt7b.h"
9 | #include "fonts/FunnelDisplay_Bold18pt7b.h"
10 | #include "fonts/FunnelDisplay_Regular14pt7b.h"
11 | #include "fonts/HelvetiPixel24pt7b.h"
12 | #include "fonts/HelvetiPixel16pt7b.h"
13 | #include "fonts/FunnelDisplay_Bold48pt7b.h"
14 | #include "fonts/FunnelDisplay_Bold32pt7b.h"
15 | #include "fonts/FunnelDisplay_Bold60pt7b.h"
16 |
17 | #define ANNIVERSARY_MODE false
18 |
19 | #define TITLE_FONT FunnelDisplay_Bold32pt7b
20 | #define MAIN_FONT FunnelDisplay_Bold24pt7b
21 | #define TEXT_FONT FunnelDisplay_Regular14pt7b
22 | #define SECONDARY_FONT FunnelDisplay_Bold18pt7b
23 | #define SUB_FONT HelvetiPixel24pt7b
24 | #define SMALL_FONT HelvetiPixel16pt7b
25 | #define SEMI_LARGE_FONT FunnelDisplay_Bold48pt7b
26 | #define LARGE_FONT FunnelDisplay_Bold60pt7b
27 |
28 | #define REDRAW_INTERVAL_SLOW 1000
29 |
30 | #define DISPLAY_CLASS GxEPD2_BW
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/src/fonts/FunnelDisplay_Regular14pt7b.h:
--------------------------------------------------------------------------------
1 | const uint8_t FunnelDisplay_Regular14pt7bBitmaps[] PROGMEM = {
2 | 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0xFF, 0x80, 0xC3, 0xC3, 0xC3,
3 | 0xC3, 0xC3, 0xC3, 0xC3, 0x07, 0x18, 0x0C, 0x30, 0x18, 0x60, 0x30, 0xC0,
4 | 0x63, 0x01, 0xC6, 0x0F, 0xFF, 0x9F, 0xFF, 0x0C, 0x30, 0x18, 0xC0, 0x21,
5 | 0x80, 0xC3, 0x0F, 0xFF, 0xDF, 0xFF, 0x86, 0x30, 0x0C, 0x60, 0x30, 0xC0,
6 | 0x61, 0x80, 0xC3, 0x00, 0x03, 0x00, 0x0C, 0x01, 0xFC, 0x0F, 0xFC, 0x70,
7 | 0x79, 0x80, 0xE6, 0x01, 0xD8, 0x00, 0x70, 0x00, 0xFC, 0x01, 0xFE, 0x00,
8 | 0xFE, 0x00, 0x38, 0x00, 0x7E, 0x01, 0xF8, 0x07, 0xF0, 0x1D, 0xFB, 0xE3,
9 | 0xFF, 0x01, 0xF0, 0x03, 0x00, 0x0C, 0x00, 0x1C, 0x01, 0x83, 0xF8, 0x18,
10 | 0x18, 0xC0, 0xC1, 0x87, 0x0C, 0x0C, 0x18, 0x60, 0x60, 0xC6, 0x03, 0x06,
11 | 0x30, 0x1C, 0x73, 0x00, 0x67, 0x38, 0x01, 0xF1, 0x8F, 0x00, 0x18, 0xFC,
12 | 0x00, 0xCC, 0x30, 0x0C, 0x61, 0x80, 0x63, 0x0C, 0x06, 0x18, 0x60, 0x70,
13 | 0xC3, 0x03, 0x06, 0x18, 0x30, 0x19, 0xC1, 0x80, 0xFC, 0x03, 0xE0, 0x0F,
14 | 0xF0, 0x0F, 0xF8, 0x0E, 0x00, 0x07, 0x00, 0x03, 0x80, 0x61, 0xC0, 0x30,
15 | 0x60, 0x18, 0x1F, 0xFF, 0x8F, 0xFF, 0xDF, 0x03, 0x0E, 0x01, 0x8E, 0x00,
16 | 0xC7, 0x00, 0x63, 0x80, 0x31, 0xC0, 0x18, 0x78, 0x0C, 0x1F, 0xFE, 0x07,
17 | 0xFF, 0x00, 0xFF, 0xFC, 0x1C, 0x63, 0x8C, 0x71, 0x86, 0x38, 0xE3, 0x8C,
18 | 0x30, 0xC3, 0x0E, 0x38, 0xE1, 0x87, 0x1C, 0x30, 0xE1, 0xC0, 0xC3, 0x87,
19 | 0x0C, 0x38, 0xE1, 0x86, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC6, 0x18, 0x63,
20 | 0x8C, 0x71, 0x8E, 0x00, 0x06, 0x00, 0x60, 0x06, 0x04, 0x63, 0xFF, 0xF3,
21 | 0xFC, 0x0F, 0x00, 0xF8, 0x19, 0x83, 0x9C, 0x30, 0xC0, 0x06, 0x00, 0x60,
22 | 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60,
23 | 0x06, 0x00, 0x60, 0x06, 0x00, 0x77, 0x73, 0x3E, 0xC0, 0xFF, 0xF0, 0x7F,
24 | 0x70, 0x01, 0xC0, 0x60, 0x38, 0x0C, 0x03, 0x01, 0xC0, 0x60, 0x18, 0x0E,
25 | 0x03, 0x00, 0xC0, 0x70, 0x18, 0x0E, 0x03, 0x00, 0xC0, 0x70, 0x18, 0x0E,
26 | 0x00, 0x07, 0x80, 0x7F, 0x83, 0xCF, 0x1C, 0x0C, 0x70, 0x39, 0x80, 0x6E,
27 | 0x01, 0xB8, 0x07, 0xE0, 0x1F, 0x80, 0x7E, 0x01, 0xF8, 0x07, 0xE0, 0x1B,
28 | 0x80, 0x66, 0x03, 0x9C, 0x0E, 0x38, 0x70, 0xFF, 0x80, 0xFC, 0x00, 0x03,
29 | 0x00, 0xF0, 0x1F, 0x07, 0xF0, 0xF7, 0x0C, 0x70, 0x87, 0x00, 0x70, 0x07,
30 | 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07,
31 | 0x07, 0xFF, 0x7F, 0xF0, 0x07, 0x80, 0xFF, 0x0F, 0x3C, 0xE0, 0x76, 0x03,
32 | 0xF0, 0x0C, 0x00, 0xE0, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0x00,
33 | 0x70, 0x07, 0x00, 0x70, 0x07, 0x00, 0x70, 0x07, 0xFF, 0xFF, 0xFE, 0x07,
34 | 0x81, 0xFF, 0x1F, 0x3C, 0xE0, 0x7E, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x06,
35 | 0x0F, 0xE0, 0x7F, 0x00, 0x3C, 0x00, 0x70, 0x01, 0x80, 0x0F, 0x80, 0x7C,
36 | 0x07, 0x70, 0x7B, 0xFF, 0x87, 0xF8, 0x00, 0x38, 0x00, 0xF0, 0x01, 0xE0,
37 | 0x07, 0xC0, 0x1F, 0x80, 0x77, 0x01, 0xCE, 0x03, 0x1C, 0x0C, 0x38, 0x38,
38 | 0x70, 0xE0, 0xE3, 0x81, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x1C,
39 | 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x3F, 0xF1, 0xFF, 0x8F, 0xFC, 0x60,
40 | 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0D, 0xF8, 0x7F, 0xF3, 0xC3, 0x98, 0x0E,
41 | 0x00, 0x30, 0x01, 0x80, 0x0F, 0x80, 0x7C, 0x07, 0x70, 0x73, 0xFF, 0x87,
42 | 0xF0, 0x07, 0xC0, 0x7F, 0xC3, 0xC7, 0x8E, 0x0E, 0x70, 0x1D, 0x80, 0x06,
43 | 0x00, 0x39, 0xF0, 0xEF, 0xF3, 0xE1, 0xEF, 0x03, 0xB8, 0x07, 0xE0, 0x1F,
44 | 0x80, 0x76, 0x01, 0xDC, 0x06, 0x38, 0x38, 0xFF, 0xC0, 0xFE, 0x00, 0xFF,
45 | 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x06, 0x00, 0x38, 0x01, 0xC0, 0x07, 0x00,
46 | 0x38, 0x00, 0xE0, 0x07, 0x00, 0x1C, 0x00, 0xE0, 0x03, 0x80, 0x1C, 0x00,
47 | 0x60, 0x03, 0x80, 0x0C, 0x00, 0x70, 0x03, 0x80, 0x00, 0x07, 0x80, 0xFF,
48 | 0x87, 0x8F, 0x1C, 0x0E, 0x60, 0x39, 0x80, 0x66, 0x03, 0x9C, 0x0C, 0x3F,
49 | 0xE0, 0x7F, 0x83, 0x8F, 0x1C, 0x0E, 0xE0, 0x1B, 0x80, 0x7E, 0x01, 0xF8,
50 | 0x06, 0x70, 0x78, 0xFF, 0xC1, 0xFE, 0x00, 0x0F, 0x01, 0xFE, 0x1F, 0x78,
51 | 0xE0, 0xEE, 0x03, 0xF0, 0x1F, 0x00, 0x7C, 0x07, 0xE0, 0x3B, 0x83, 0xDF,
52 | 0xF6, 0x3F, 0x30, 0x01, 0x80, 0x0F, 0x80, 0xFC, 0x06, 0x70, 0x71, 0xFF,
53 | 0x07, 0xF0, 0x7F, 0x70, 0x00, 0x00, 0x7F, 0x70, 0x7F, 0x70, 0x00, 0x00,
54 | 0x77, 0x73, 0x3E, 0xC0, 0x00, 0x30, 0x1F, 0x07, 0xC1, 0xF0, 0xF8, 0x0E,
55 | 0x00, 0xF0, 0x07, 0xE0, 0x0F, 0x80, 0x3E, 0x00, 0xF0, 0x01, 0xFF, 0xFF,
56 | 0xFF, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0xC0, 0x0F, 0x80,
57 | 0x3E, 0x00, 0xF8, 0x03, 0xE0, 0x07, 0x00, 0xF0, 0x7C, 0x1F, 0x07, 0xC0,
58 | 0xE0, 0x08, 0x00, 0x1F, 0x07, 0xFC, 0xF9, 0xEE, 0x06, 0xC0, 0x70, 0x07,
59 | 0x00, 0x60, 0x0E, 0x03, 0xC0, 0x78, 0x0F, 0x00, 0xC0, 0x0C, 0x00, 0x00,
60 | 0x00, 0x00, 0x00, 0x1C, 0x01, 0xC0, 0x1C, 0x00, 0x00, 0x3F, 0x00, 0x03,
61 | 0xFF, 0xE0, 0x07, 0xC1, 0xF0, 0x1E, 0x00, 0x38, 0x18, 0x00, 0x1C, 0x30,
62 | 0x38, 0x0E, 0x70, 0xFD, 0x86, 0x61, 0xC7, 0x87, 0x61, 0x83, 0x83, 0xE3,
63 | 0x81, 0x83, 0xE3, 0x81, 0x83, 0xE3, 0x81, 0x83, 0xE3, 0x81, 0x86, 0x61,
64 | 0x81, 0x86, 0x61, 0xC3, 0x8C, 0x60, 0xFD, 0xF8, 0x30, 0x78, 0xF0, 0x38,
65 | 0x00, 0x00, 0x1C, 0x00, 0x00, 0x0F, 0x80, 0x00, 0x03, 0xFF, 0xE0, 0x00,
66 | 0x7F, 0xE0, 0x00, 0xE0, 0x00, 0x1C, 0x00, 0x07, 0xC0, 0x00, 0xD8, 0x00,
67 | 0x3B, 0x80, 0x06, 0x30, 0x01, 0xC7, 0x00, 0x38, 0xE0, 0x06, 0x0C, 0x01,
68 | 0xC1, 0xC0, 0x30, 0x18, 0x0F, 0xFF, 0x81, 0xFF, 0xF0, 0x70, 0x07, 0x0E,
69 | 0x00, 0xE1, 0x80, 0x0C, 0x70, 0x01, 0xCC, 0x00, 0x1B, 0x80, 0x03, 0x80,
70 | 0xFF, 0xC0, 0xFF, 0xF8, 0xFF, 0xFC, 0xE0, 0x1C, 0xE0, 0x0E, 0xE0, 0x0E,
71 | 0xE0, 0x0C, 0xE0, 0x1C, 0xFF, 0xF8, 0xFF, 0xF8, 0xE0, 0x3C, 0xE0, 0x0E,
72 | 0xE0, 0x06, 0xE0, 0x07, 0xE0, 0x06, 0xE0, 0x0E, 0xE0, 0x1E, 0xFF, 0xFC,
73 | 0xFF, 0xF0, 0x01, 0xF0, 0x07, 0xFE, 0x07, 0xFF, 0xC7, 0x80, 0xE7, 0x80,
74 | 0x3B, 0x80, 0x0D, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0x38,
75 | 0x00, 0x1C, 0x00, 0x0E, 0x00, 0x03, 0x80, 0x0D, 0xC0, 0x0E, 0x70, 0x07,
76 | 0x3E, 0x0F, 0x07, 0xFF, 0x01, 0xFE, 0x00, 0xFF, 0x00, 0x7F, 0xF8, 0x3F,
77 | 0xFF, 0x1C, 0x03, 0xCE, 0x00, 0xE7, 0x00, 0x3B, 0x80, 0x1D, 0xC0, 0x06,
78 | 0xE0, 0x03, 0xF0, 0x01, 0xF8, 0x00, 0xFC, 0x00, 0x7E, 0x00, 0x37, 0x00,
79 | 0x3B, 0x80, 0x1D, 0xC0, 0x3C, 0xE0, 0x7C, 0x7F, 0xFC, 0x3F, 0xF8, 0x00,
80 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C,
81 | 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80,
82 | 0x1C, 0x00, 0xE0, 0x07, 0xFF, 0xFF, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
83 | 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0xFF, 0xBF,
84 | 0xFD, 0xC0, 0x0E, 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00,
85 | 0x38, 0x00, 0x01, 0xF0, 0x01, 0xFF, 0x81, 0xFF, 0xF0, 0xF0, 0x1E, 0x38,
86 | 0x03, 0x9C, 0x00, 0x66, 0x00, 0x07, 0x80, 0x00, 0xE0, 0x00, 0x38, 0x00,
87 | 0x0E, 0x07, 0xFB, 0x81, 0xFF, 0xE0, 0x01, 0xDC, 0x00, 0x77, 0x00, 0x1C,
88 | 0xE0, 0x0F, 0x1E, 0x0F, 0xC3, 0xFF, 0x70, 0x7F, 0x9C, 0xE0, 0x03, 0xE0,
89 | 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0,
90 | 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x03, 0xE0, 0x03, 0xE0,
91 | 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xE0, 0x03, 0xFF,
92 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x07, 0x07, 0x07, 0x07, 0x07,
93 | 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
94 | 0xFF, 0xFE, 0xE0, 0x0F, 0xE0, 0x1C, 0xE0, 0x38, 0xE0, 0x70, 0xE0, 0xE0,
95 | 0xE1, 0xC0, 0xE3, 0x80, 0xE7, 0x00, 0xEE, 0x00, 0xFF, 0x00, 0xFB, 0x80,
96 | 0xE3, 0xC0, 0xE1, 0xC0, 0xE0, 0xE0, 0xE0, 0x70, 0xE0, 0x38, 0xE0, 0x1C,
97 | 0xE0, 0x1E, 0xE0, 0x0F, 0xE0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E, 0x00,
98 | 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0x00, 0x38, 0x01, 0xC0, 0x0E,
99 | 0x00, 0x70, 0x03, 0x80, 0x1C, 0x00, 0xE0, 0x07, 0xFF, 0xFF, 0xFE, 0xF0,
100 | 0x00, 0x7F, 0xC0, 0x03, 0xFE, 0x00, 0x3F, 0xF0, 0x01, 0xFF, 0xC0, 0x1F,
101 | 0xF6, 0x00, 0xDF, 0xB8, 0x0E, 0xFD, 0xC0, 0x67, 0xE7, 0x03, 0x3F, 0x38,
102 | 0x39, 0xF8, 0xC1, 0x8F, 0xC7, 0x1C, 0x7E, 0x18, 0xC3, 0xF0, 0xEE, 0x1F,
103 | 0x87, 0x60, 0xFC, 0x1F, 0x07, 0xE0, 0xF8, 0x3F, 0x03, 0x81, 0xF8, 0x1C,
104 | 0x0E, 0xE0, 0x01, 0xF8, 0x00, 0xFE, 0x00, 0x7F, 0x00, 0x3F, 0xC0, 0x1F,
105 | 0x70, 0x0F, 0x9C, 0x07, 0xCE, 0x03, 0xE3, 0x81, 0xF0, 0xE0, 0xF8, 0x38,
106 | 0x7C, 0x1C, 0x3E, 0x07, 0x1F, 0x01, 0xCF, 0x80, 0x77, 0xC0, 0x3B, 0xE0,
107 | 0x0F, 0xF0, 0x03, 0xF8, 0x00, 0xE0, 0x01, 0xF0, 0x01, 0xFF, 0xC0, 0x7F,
108 | 0xFC, 0x1E, 0x03, 0xC7, 0x80, 0x3C, 0xE0, 0x03, 0x98, 0x00, 0x37, 0x00,
109 | 0x07, 0xE0, 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x76, 0x00,
110 | 0x0C, 0xE0, 0x03, 0x9C, 0x00, 0x71, 0xC0, 0x1C, 0x1E, 0x0F, 0x01, 0xFF,
111 | 0xC0, 0x1F, 0xF0, 0x00, 0xFF, 0x81, 0xFF, 0xF3, 0xFF, 0xF7, 0x00, 0xEE,
112 | 0x00, 0xFC, 0x01, 0xF8, 0x03, 0xF0, 0x07, 0xE0, 0x0F, 0xC0, 0x7B, 0xFF,
113 | 0xE7, 0xFF, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01,
114 | 0xC0, 0x03, 0x80, 0x00, 0x01, 0xF0, 0x01, 0xFF, 0xC0, 0x7C, 0xFC, 0x1E,
115 | 0x03, 0xC7, 0x80, 0x3C, 0xE0, 0x03, 0x98, 0x00, 0x37, 0x00, 0x07, 0xE0,
116 | 0x00, 0xFC, 0x00, 0x1F, 0x80, 0x03, 0xF0, 0x00, 0x76, 0x00, 0x0C, 0xE0,
117 | 0x03, 0x9C, 0x00, 0x71, 0xC0, 0x1C, 0x1E, 0x0F, 0x01, 0xFF, 0xC0, 0x0F,
118 | 0xE0, 0x00, 0x70, 0x00, 0x0E, 0x00, 0x01, 0xFE, 0x00, 0x1F, 0xC0, 0xFF,
119 | 0xC1, 0xFF, 0xF3, 0xFF, 0xF7, 0x00, 0x7E, 0x00, 0xFC, 0x00, 0xF8, 0x01,
120 | 0xF0, 0x07, 0xE0, 0x1D, 0xFF, 0xF3, 0xFF, 0xFF, 0x00, 0x3E, 0x00, 0x7C,
121 | 0x00, 0xF8, 0x01, 0xF0, 0x03, 0xE0, 0x07, 0xC0, 0x0F, 0x80, 0x18, 0x07,
122 | 0xC0, 0x3F, 0xE0, 0xF1, 0xF3, 0x80, 0xE7, 0x00, 0xEC, 0x01, 0xDC, 0x00,
123 | 0x3C, 0x00, 0x3F, 0x80, 0x3F, 0xF0, 0x07, 0xF0, 0x00, 0xF0, 0x00, 0xFC,
124 | 0x01, 0xF8, 0x03, 0xF8, 0x07, 0x78, 0x1C, 0x7F, 0xF8, 0x3F, 0xC0, 0xFF,
125 | 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x1C, 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0,
126 | 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0x1C, 0x00, 0x38, 0x00,
127 | 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x00, 0xE0,
128 | 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
129 | 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0, 0x07, 0xE0,
130 | 0x07, 0xE0, 0x07, 0xE0, 0x06, 0x70, 0x0E, 0x78, 0x3E, 0x3F, 0xFC, 0x0F,
131 | 0xF0, 0xE0, 0x00, 0xEE, 0x00, 0x19, 0xC0, 0x07, 0x18, 0x00, 0xE3, 0x80,
132 | 0x38, 0x70, 0x07, 0x07, 0x00, 0xC0, 0xE0, 0x38, 0x0C, 0x06, 0x01, 0xC1,
133 | 0xC0, 0x38, 0x38, 0x03, 0x86, 0x00, 0x71, 0xC0, 0x06, 0x30, 0x00, 0xEE,
134 | 0x00, 0x1D, 0xC0, 0x01, 0xF0, 0x00, 0x3E, 0x00, 0x03, 0x80, 0x00, 0xC0,
135 | 0x1C, 0x01, 0xF0, 0x0E, 0x01, 0xF8, 0x07, 0x00, 0xEC, 0x07, 0xC0, 0x67,
136 | 0x03, 0x60, 0x73, 0x81, 0xB0, 0x38, 0xC1, 0xDC, 0x1C, 0x60, 0xC6, 0x0C,
137 | 0x38, 0x63, 0x0E, 0x1C, 0x31, 0xC7, 0x06, 0x30, 0xE3, 0x03, 0x98, 0x31,
138 | 0x81, 0xCC, 0x19, 0xC0, 0x6E, 0x0E, 0xC0, 0x36, 0x03, 0x60, 0x1F, 0x01,
139 | 0xF0, 0x0F, 0x80, 0xF8, 0x03, 0x80, 0x38, 0x01, 0xC0, 0x1C, 0x00, 0x70,
140 | 0x03, 0x8E, 0x01, 0xC3, 0x80, 0x70, 0x70, 0x38, 0x0E, 0x1C, 0x01, 0x86,
141 | 0x00, 0x73, 0x80, 0x0F, 0xC0, 0x01, 0xE0, 0x00, 0x78, 0x00, 0x1E, 0x00,
142 | 0x0F, 0xC0, 0x07, 0x38, 0x03, 0x87, 0x00, 0xC1, 0xC0, 0x70, 0x38, 0x38,
143 | 0x07, 0x1C, 0x01, 0xCF, 0x00, 0x3C, 0xE0, 0x03, 0xB8, 0x03, 0x9E, 0x01,
144 | 0xC7, 0x01, 0xC1, 0xC0, 0xC0, 0xE0, 0xE0, 0x38, 0xE0, 0x0C, 0x70, 0x07,
145 | 0x70, 0x01, 0xF0, 0x00, 0xF8, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x0E, 0x00,
146 | 0x07, 0x00, 0x03, 0x80, 0x01, 0xC0, 0x00, 0xE0, 0x00, 0x70, 0x00, 0xFF,
147 | 0xFD, 0xFF, 0xFB, 0xFF, 0xF0, 0x01, 0xC0, 0x03, 0x80, 0x0E, 0x00, 0x38,
148 | 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x1C, 0x00, 0x70, 0x01, 0xC0, 0x03,
149 | 0x80, 0x0E, 0x00, 0x38, 0x00, 0xE0, 0x01, 0xFF, 0xFF, 0xFF, 0xF8, 0xFF,
150 | 0xFC, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3,
151 | 0x0C, 0x30, 0xC3, 0xFF, 0xC0, 0xE0, 0x18, 0x07, 0x00, 0xC0, 0x30, 0x0E,
152 | 0x01, 0x80, 0x60, 0x1C, 0x03, 0x00, 0xC0, 0x38, 0x06, 0x01, 0x80, 0x30,
153 | 0x0C, 0x03, 0x80, 0x60, 0x1C, 0xFF, 0xF1, 0xC7, 0x1C, 0x71, 0xC7, 0x1C,
154 | 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1F, 0xFF, 0xC0, 0x0E,
155 | 0x00, 0xF0, 0x0F, 0x01, 0xB8, 0x19, 0x83, 0x98, 0x30, 0xC7, 0x0C, 0x60,
156 | 0x66, 0x06, 0xC0, 0x7C, 0x03, 0xFF, 0xFF, 0xFF, 0xF0, 0xE1, 0xC3, 0x07,
157 | 0x1F, 0x81, 0xFF, 0x1C, 0x3D, 0xC0, 0xE0, 0x03, 0x00, 0x18, 0xFF, 0xCF,
158 | 0xFE, 0xE0, 0x36, 0x01, 0xB0, 0x1D, 0xC1, 0xF7, 0xF9, 0x9F, 0x8C, 0x70,
159 | 0x00, 0xE0, 0x01, 0xC0, 0x03, 0x80, 0x07, 0x00, 0x0E, 0x7C, 0x1D, 0xFE,
160 | 0x3E, 0x1E, 0x78, 0x1C, 0xE0, 0x1D, 0xC0, 0x3B, 0x80, 0x77, 0x00, 0xEE,
161 | 0x01, 0xDC, 0x03, 0xBC, 0x06, 0xFC, 0x3D, 0xDF, 0xF3, 0x9F, 0xC0, 0x0F,
162 | 0xC1, 0xFF, 0x1E, 0x1C, 0xE0, 0x7E, 0x01, 0xF0, 0x03, 0x80, 0x1C, 0x00,
163 | 0xE0, 0x07, 0x00, 0xDC, 0x0E, 0xF0, 0xF3, 0xFF, 0x0F, 0xF0, 0x00, 0x1C,
164 | 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0xC1, 0xF3, 0x8F, 0xF7, 0x3C,
165 | 0x3E, 0x70, 0x3D, 0xC0, 0x3B, 0x80, 0x77, 0x00, 0xEE, 0x01, 0xDC, 0x03,
166 | 0xB8, 0x07, 0x38, 0x1E, 0x78, 0x7E, 0x7F, 0xDC, 0x7F, 0x38, 0x0F, 0xC1,
167 | 0xFF, 0x1E, 0x1C, 0xE0, 0x7E, 0x01, 0xF0, 0x0F, 0xFF, 0xFF, 0xFF, 0xE0,
168 | 0x07, 0x00, 0x18, 0x0E, 0xF0, 0xF3, 0xFF, 0x07, 0xF0, 0x0F, 0x8F, 0xC7,
169 | 0x03, 0x81, 0xC7, 0xFF, 0xFE, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0,
170 | 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x00, 0x0F, 0x8E, 0x7F, 0xDD, 0xF1,
171 | 0xFB, 0x81, 0xEE, 0x01, 0xDC, 0x03, 0xB8, 0x07, 0x70, 0x0E, 0xE0, 0x1D,
172 | 0xC0, 0x39, 0xC0, 0xF3, 0xC3, 0xE3, 0xFD, 0xC1, 0xF3, 0x80, 0x07, 0x70,
173 | 0x0C, 0xE0, 0x38, 0xF1, 0xF0, 0xFF, 0x80, 0x7C, 0x00, 0xC0, 0x06, 0x00,
174 | 0x30, 0x01, 0x80, 0x0C, 0x00, 0x63, 0xE3, 0x7F, 0x9F, 0x1E, 0xE0, 0x77,
175 | 0x01, 0xF0, 0x0F, 0x80, 0x7C, 0x03, 0xE0, 0x1F, 0x00, 0xF8, 0x07, 0xC0,
176 | 0x3E, 0x01, 0xF0, 0x0E, 0x6E, 0xE0, 0x07, 0x77, 0x77, 0x77, 0x77, 0x77,
177 | 0x77, 0x70, 0x18, 0xE3, 0x80, 0x00, 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C,
178 | 0x71, 0xC7, 0x1C, 0x71, 0xC7, 0x1C, 0x71, 0xFE, 0xC0, 0x06, 0x00, 0x30,
179 | 0x01, 0x80, 0x0C, 0x00, 0x60, 0x3B, 0x03, 0x98, 0x38, 0xC3, 0x86, 0x38,
180 | 0x33, 0x01, 0xF8, 0x0F, 0xE0, 0x73, 0x83, 0x0E, 0x18, 0x38, 0xC0, 0xE6,
181 | 0x07, 0xB0, 0x1E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0xE3,
182 | 0xE1, 0xF1, 0xCF, 0xE7, 0xFB, 0xF1, 0xFC, 0x73, 0xC1, 0xE0, 0x77, 0x01,
183 | 0xC0, 0xEE, 0x03, 0x80, 0xDC, 0x07, 0x01, 0xB8, 0x0E, 0x03, 0x70, 0x1C,
184 | 0x06, 0xE0, 0x38, 0x0D, 0xC0, 0x70, 0x1B, 0x80, 0xE0, 0x37, 0x01, 0xC0,
185 | 0x6E, 0x03, 0x80, 0xC0, 0xE3, 0xE3, 0x9F, 0xEF, 0xC7, 0xDE, 0x07, 0x70,
186 | 0x0D, 0xC0, 0x37, 0x00, 0xDC, 0x03, 0x70, 0x0D, 0xC0, 0x37, 0x00, 0xDC,
187 | 0x03, 0x70, 0x0D, 0xC0, 0x30, 0x0F, 0xC0, 0xFF, 0xC7, 0x87, 0x9C, 0x0E,
188 | 0xE0, 0x1F, 0x80, 0x7E, 0x00, 0xF8, 0x03, 0xE0, 0x1F, 0x80, 0x77, 0x01,
189 | 0x9E, 0x1E, 0x3F, 0xF0, 0x3F, 0x80, 0xE3, 0xE1, 0xDF, 0xF3, 0xF0, 0xF3,
190 | 0xC0, 0xE7, 0x00, 0xEE, 0x01, 0xDC, 0x03, 0xB8, 0x07, 0x70, 0x0E, 0xE0,
191 | 0x1D, 0xE0, 0x33, 0xE1, 0xE7, 0x7F, 0x8E, 0x7E, 0x1C, 0x00, 0x38, 0x00,
192 | 0x70, 0x00, 0xE0, 0x01, 0xC0, 0x00, 0x0F, 0x8E, 0x7F, 0xDD, 0xE1, 0xFB,
193 | 0x81, 0xEE, 0x01, 0xDC, 0x03, 0xB8, 0x07, 0x70, 0x0E, 0xE0, 0x1D, 0xC0,
194 | 0x39, 0xC0, 0xF3, 0xC3, 0xE3, 0xFD, 0xC3, 0xF3, 0x80, 0x07, 0x00, 0x0E,
195 | 0x00, 0x1C, 0x00, 0x38, 0x00, 0x70, 0xE1, 0xF9, 0xFE, 0xE0, 0xE0, 0x38,
196 | 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x00,
197 | 0x1F, 0x83, 0xFC, 0x70, 0xE6, 0x07, 0x60, 0x07, 0x00, 0x7F, 0x81, 0xFE,
198 | 0x00, 0xF0, 0x07, 0xE0, 0x7E, 0x07, 0x7F, 0xE3, 0xFC, 0x1C, 0x0E, 0x07,
199 | 0x03, 0x8F, 0xFF, 0xFC, 0x70, 0x38, 0x1C, 0x0E, 0x07, 0x03, 0x81, 0xC0,
200 | 0xE0, 0x70, 0x38, 0x1F, 0x87, 0xC0, 0xC0, 0x3B, 0x00, 0xEC, 0x03, 0xB0,
201 | 0x0E, 0xC0, 0x3B, 0x00, 0xEC, 0x03, 0xB0, 0x0E, 0xC0, 0x3B, 0x00, 0xEE,
202 | 0x03, 0xBC, 0x3F, 0x7F, 0xDC, 0xFE, 0x70, 0xE0, 0x0E, 0xC0, 0x19, 0xC0,
203 | 0x73, 0x80, 0xC3, 0x83, 0x87, 0x06, 0x06, 0x1C, 0x0E, 0x38, 0x0C, 0x60,
204 | 0x1D, 0xC0, 0x3B, 0x00, 0x3E, 0x00, 0x78, 0x00, 0x70, 0x00, 0xE0, 0x38,
205 | 0x0D, 0x80, 0xE0, 0x77, 0x07, 0xC1, 0xDC, 0x1B, 0x06, 0x30, 0x6C, 0x18,
206 | 0xE3, 0xB8, 0xE3, 0x8C, 0x63, 0x86, 0x31, 0x8C, 0x1D, 0xC7, 0x70, 0x76,
207 | 0x0D, 0xC0, 0xF8, 0x36, 0x03, 0xE0, 0xF8, 0x0F, 0x01, 0xE0, 0x1C, 0x07,
208 | 0x00, 0x70, 0x1C, 0xC0, 0xE3, 0x83, 0x07, 0x1C, 0x0E, 0xE0, 0x1F, 0x00,
209 | 0x78, 0x01, 0xE0, 0x07, 0xC0, 0x3B, 0x81, 0xC7, 0x0E, 0x0C, 0x30, 0x3B,
210 | 0xC0, 0x70, 0xE0, 0x0E, 0xC0, 0x19, 0xC0, 0x71, 0x80, 0xC3, 0x83, 0x87,
211 | 0x06, 0x07, 0x1C, 0x0E, 0x38, 0x0C, 0x60, 0x1D, 0xC0, 0x1B, 0x00, 0x3E,
212 | 0x00, 0x38, 0x00, 0x70, 0x00, 0xE0, 0x01, 0x80, 0x07, 0x00, 0x7C, 0x00,
213 | 0xF8, 0x00, 0xFF, 0xFF, 0xFC, 0x03, 0x80, 0xE0, 0x38, 0x06, 0x01, 0xC0,
214 | 0x70, 0x1C, 0x07, 0x01, 0xC0, 0x30, 0x0F, 0xFF, 0xFF, 0xC0, 0x01, 0x0F,
215 | 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0xE0, 0xF0, 0x38,
216 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1C, 0x1F, 0x07, 0xFF, 0xFF, 0xFF,
217 | 0xFF, 0xFF, 0xFC, 0x80, 0xF0, 0xF8, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18,
218 | 0x18, 0x1C, 0x0F, 0x0F, 0x1C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x38,
219 | 0xF0, 0xE0, 0x7C, 0x1F, 0xFF, 0x87, 0xE8, 0x0C };
220 |
221 | const GFXglyph FunnelDisplay_Regular14pt7bGlyphs[] PROGMEM = {
222 | { 0, 1, 1, 7, 0, 0 }, // 0x20 ' '
223 | { 1, 3, 19, 7, 2, -18 }, // 0x21 '!'
224 | { 9, 8, 7, 11, 2, -18 }, // 0x22 '"'
225 | { 16, 15, 19, 15, 0, -18 }, // 0x23 '#'
226 | { 52, 14, 22, 16, 1, -19 }, // 0x24 '$'
227 | { 91, 21, 19, 24, 1, -18 }, // 0x25 '%'
228 | { 141, 17, 19, 18, 1, -18 }, // 0x26 '&'
229 | { 182, 2, 7, 6, 2, -18 }, // 0x27 '''
230 | { 184, 6, 23, 8, 1, -18 }, // 0x28 '('
231 | { 202, 6, 23, 8, 1, -18 }, // 0x29 ')'
232 | { 220, 12, 11, 14, 1, -18 }, // 0x2A '*'
233 | { 237, 12, 13, 16, 2, -15 }, // 0x2B '+'
234 | { 257, 4, 7, 6, 1, -2 }, // 0x2C ','
235 | { 261, 6, 2, 8, 1, -7 }, // 0x2D '-'
236 | { 263, 4, 3, 6, 1, -2 }, // 0x2E '.'
237 | { 265, 10, 19, 10, 0, -18 }, // 0x2F '/'
238 | { 289, 14, 19, 16, 1, -18 }, // 0x30 '0'
239 | { 323, 12, 19, 16, 2, -18 }, // 0x31 '1'
240 | { 352, 13, 19, 16, 1, -18 }, // 0x32 '2'
241 | { 383, 13, 19, 16, 1, -18 }, // 0x33 '3'
242 | { 414, 15, 19, 16, 0, -18 }, // 0x34 '4'
243 | { 450, 13, 19, 16, 1, -18 }, // 0x35 '5'
244 | { 481, 14, 19, 16, 1, -18 }, // 0x36 '6'
245 | { 515, 14, 19, 16, 1, -18 }, // 0x37 '7'
246 | { 549, 14, 19, 16, 1, -18 }, // 0x38 '8'
247 | { 583, 13, 19, 16, 1, -18 }, // 0x39 '9'
248 | { 614, 4, 11, 8, 2, -11 }, // 0x3A ':'
249 | { 620, 4, 15, 8, 2, -11 }, // 0x3B ';'
250 | { 628, 12, 12, 16, 2, -14 }, // 0x3C '<'
251 | { 646, 12, 7, 16, 2, -12 }, // 0x3D '='
252 | { 657, 12, 12, 16, 2, -14 }, // 0x3E '>'
253 | { 675, 12, 19, 13, 1, -18 }, // 0x3F '?'
254 | { 704, 24, 22, 26, 1, -18 }, // 0x40 '@'
255 | { 770, 19, 19, 19, 0, -18 }, // 0x41 'A'
256 | { 816, 16, 19, 19, 2, -18 }, // 0x42 'B'
257 | { 854, 17, 19, 19, 1, -18 }, // 0x43 'C'
258 | { 895, 17, 19, 20, 2, -18 }, // 0x44 'D'
259 | { 936, 13, 19, 16, 2, -18 }, // 0x45 'E'
260 | { 967, 13, 19, 15, 2, -18 }, // 0x46 'F'
261 | { 998, 18, 19, 20, 1, -18 }, // 0x47 'G'
262 | { 1041, 16, 19, 20, 2, -18 }, // 0x48 'H'
263 | { 1079, 3, 19, 7, 2, -18 }, // 0x49 'I'
264 | { 1087, 8, 19, 10, 0, -18 }, // 0x4A 'J'
265 | { 1106, 16, 19, 18, 2, -18 }, // 0x4B 'K'
266 | { 1144, 13, 19, 15, 2, -18 }, // 0x4C 'L'
267 | { 1175, 21, 19, 25, 2, -18 }, // 0x4D 'M'
268 | { 1225, 17, 19, 21, 2, -18 }, // 0x4E 'N'
269 | { 1266, 19, 19, 21, 1, -18 }, // 0x4F 'O'
270 | { 1312, 15, 19, 18, 2, -18 }, // 0x50 'P'
271 | { 1348, 19, 23, 21, 1, -18 }, // 0x51 'Q'
272 | { 1403, 15, 19, 19, 2, -18 }, // 0x52 'R'
273 | { 1439, 15, 19, 17, 1, -18 }, // 0x53 'S'
274 | { 1475, 15, 19, 17, 1, -18 }, // 0x54 'T'
275 | { 1511, 16, 19, 20, 2, -18 }, // 0x55 'U'
276 | { 1549, 19, 19, 19, 0, -18 }, // 0x56 'V'
277 | { 1595, 25, 19, 27, 1, -18 }, // 0x57 'W'
278 | { 1655, 18, 19, 18, 0, -18 }, // 0x58 'X'
279 | { 1698, 17, 19, 17, 0, -18 }, // 0x59 'Y'
280 | { 1739, 15, 19, 16, 1, -18 }, // 0x5A 'Z'
281 | { 1775, 6, 23, 9, 2, -18 }, // 0x5B '['
282 | { 1793, 10, 19, 10, 0, -18 }, // 0x5C '\'
283 | { 1817, 6, 23, 9, 1, -18 }, // 0x5D ']'
284 | { 1835, 12, 12, 16, 2, -14 }, // 0x5E '^'
285 | { 1853, 14, 2, 14, 0, 3 }, // 0x5F '_'
286 | { 1857, 6, 4, 14, 4, -19 }, // 0x60 '`'
287 | { 1860, 13, 14, 16, 1, -13 }, // 0x61 'a'
288 | { 1883, 15, 19, 17, 1, -18 }, // 0x62 'b'
289 | { 1919, 13, 14, 15, 1, -13 }, // 0x63 'c'
290 | { 1942, 15, 19, 17, 1, -18 }, // 0x64 'd'
291 | { 1978, 13, 14, 16, 1, -13 }, // 0x65 'e'
292 | { 2001, 9, 19, 10, 0, -18 }, // 0x66 'f'
293 | { 2023, 15, 20, 17, 1, -13 }, // 0x67 'g'
294 | { 2061, 13, 19, 17, 2, -18 }, // 0x68 'h'
295 | { 2092, 4, 19, 7, 1, -18 }, // 0x69 'i'
296 | { 2102, 6, 24, 6, -1, -18 }, // 0x6A 'j'
297 | { 2120, 13, 19, 15, 2, -18 }, // 0x6B 'k'
298 | { 2151, 3, 19, 7, 2, -18 }, // 0x6C 'l'
299 | { 2159, 23, 14, 26, 1, -13 }, // 0x6D 'm'
300 | { 2200, 14, 14, 17, 1, -13 }, // 0x6E 'n'
301 | { 2225, 14, 14, 16, 1, -13 }, // 0x6F 'o'
302 | { 2250, 15, 19, 17, 1, -13 }, // 0x70 'p'
303 | { 2286, 15, 19, 17, 1, -13 }, // 0x71 'q'
304 | { 2322, 10, 14, 11, 1, -13 }, // 0x72 'r'
305 | { 2340, 12, 14, 14, 1, -13 }, // 0x73 's'
306 | { 2361, 9, 18, 10, 0, -17 }, // 0x74 't'
307 | { 2382, 14, 14, 17, 2, -13 }, // 0x75 'u'
308 | { 2407, 15, 14, 15, 0, -13 }, // 0x76 'v'
309 | { 2434, 22, 14, 23, 0, -13 }, // 0x77 'w'
310 | { 2473, 14, 14, 15, 0, -13 }, // 0x78 'x'
311 | { 2498, 15, 19, 15, 0, -13 }, // 0x79 'y'
312 | { 2534, 11, 14, 13, 1, -13 }, // 0x7A 'z'
313 | { 2554, 8, 23, 10, 1, -18 }, // 0x7B '{'
314 | { 2577, 2, 23, 14, 6, -18 }, // 0x7C '|'
315 | { 2583, 8, 23, 10, 1, -18 }, // 0x7D '}'
316 | { 2606, 12, 4, 16, 2, -10 } }; // 0x7E '~'
317 |
318 | const GFXfont FunnelDisplay_Regular14pt7b PROGMEM = {
319 | (uint8_t *)FunnelDisplay_Regular14pt7bBitmaps,
320 | (GFXglyph *)FunnelDisplay_Regular14pt7bGlyphs,
321 | 0x20, 0x7E, 34 };
322 |
323 | // Approx. 3284 bytes
324 |
--------------------------------------------------------------------------------
/src/fonts/HelvetiPixel16pt7b.h:
--------------------------------------------------------------------------------
1 | const uint8_t HelvetiPixel16pt7bBitmaps[] PROGMEM = {
2 | 0x00, 0xFF, 0xFF, 0xFF, 0x0F, 0xCF, 0x3C, 0xF3, 0xCF, 0x30, 0x0C, 0xC0,
3 | 0xCC, 0x0C, 0xC0, 0xCC, 0xFF, 0xFF, 0xFF, 0x33, 0x03, 0x30, 0x33, 0x03,
4 | 0x30, 0xFF, 0xFF, 0xFF, 0x33, 0x03, 0x30, 0x33, 0x03, 0x30, 0x0C, 0x0F,
5 | 0xC3, 0xF3, 0x33, 0xCC, 0xF3, 0x0C, 0xC0, 0xFC, 0x3F, 0x03, 0x30, 0xCF,
6 | 0x33, 0xCC, 0xCF, 0xC3, 0xF0, 0x30, 0x0C, 0x00, 0x30, 0x30, 0x60, 0x63,
7 | 0x30, 0xC6, 0x61, 0x8C, 0xCC, 0x19, 0x98, 0x0C, 0x30, 0x18, 0x60, 0x03,
8 | 0x0C, 0x06, 0x18, 0x0C, 0xD8, 0x19, 0xB0, 0xC3, 0x61, 0x86, 0xC3, 0x03,
9 | 0x06, 0x06, 0x0F, 0x00, 0x3C, 0x03, 0x0C, 0x0C, 0x30, 0x30, 0xC0, 0xC3,
10 | 0x00, 0xF0, 0x03, 0xC0, 0x33, 0x0C, 0xCC, 0x3C, 0x0F, 0x30, 0x3C, 0xC0,
11 | 0xF3, 0x03, 0xC3, 0xF0, 0xCF, 0xC3, 0xFF, 0xF0, 0x0C, 0x33, 0x0C, 0x30,
12 | 0xCC, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x03, 0x0C, 0x30, 0xC0, 0xC3, 0xC3,
13 | 0x03, 0x0C, 0x30, 0xC0, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x33, 0x0C, 0x30,
14 | 0xCC, 0x30, 0xCC, 0xF3, 0x33, 0xF0, 0xFC, 0xCC, 0xF3, 0x30, 0x0C, 0x03,
15 | 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0x33,
16 | 0x33, 0xCC, 0xFF, 0xF0, 0xF0, 0x0C, 0x30, 0xC3, 0x0C, 0x33, 0x0C, 0x30,
17 | 0xC3, 0x0C, 0xC3, 0x0C, 0x30, 0xC3, 0x00, 0x3F, 0x0F, 0xCC, 0x0F, 0x03,
18 | 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0,
19 | 0x33, 0xF0, 0xFC, 0x0C, 0x33, 0xCF, 0xCF, 0x30, 0xC3, 0x0C, 0x30, 0xC3,
20 | 0x0C, 0x30, 0xC3, 0x3F, 0x0F, 0xCC, 0x0F, 0x03, 0x00, 0xC0, 0x30, 0x30,
21 | 0x0C, 0x0C, 0x03, 0x03, 0x00, 0xC0, 0xC0, 0x30, 0x0F, 0xFF, 0xFF, 0x3F,
22 | 0x0F, 0xCC, 0x0F, 0x03, 0x00, 0xC0, 0x30, 0xF0, 0x3C, 0x00, 0xC0, 0x30,
23 | 0x0C, 0x03, 0xC0, 0xF0, 0x33, 0xF0, 0xFC, 0x00, 0xC0, 0x0C, 0x03, 0xC0,
24 | 0x3C, 0x0C, 0xC0, 0xCC, 0x30, 0xC3, 0x0C, 0xC0, 0xCC, 0x0C, 0xFF, 0xFF,
25 | 0xFF, 0x00, 0xC0, 0x0C, 0x00, 0xC0, 0x0C, 0xFF, 0xFF, 0xFC, 0x03, 0x00,
26 | 0xC0, 0x30, 0x0F, 0xF3, 0xFC, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0xC0, 0xF0,
27 | 0x33, 0xF0, 0xFC, 0x3F, 0x0F, 0xCC, 0x0F, 0x03, 0xC0, 0x30, 0x0F, 0xF3,
28 | 0xFC, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x33, 0xF0, 0xFC, 0xFF,
29 | 0xFF, 0xF0, 0x0C, 0x03, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x0C, 0x03, 0x00,
30 | 0xC0, 0x30, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x3F, 0x0F, 0xCC, 0x0F, 0x03,
31 | 0xC0, 0xF0, 0x33, 0xF0, 0xFC, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0,
32 | 0x33, 0xF0, 0xFC, 0x3F, 0x0F, 0xCC, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F,
33 | 0x03, 0x3F, 0xCF, 0xF0, 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xF0, 0xFC, 0xF0,
34 | 0x00, 0xF0, 0x33, 0x00, 0x00, 0x00, 0x33, 0x33, 0xCC, 0x00, 0xF0, 0x0F,
35 | 0x0F, 0x00, 0xF0, 0xF0, 0x0F, 0x00, 0x0F, 0x00, 0xF0, 0x00, 0xF0, 0x0F,
36 | 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0xFF, 0xFF, 0xF0, 0xF0, 0x0F, 0x00, 0x0F,
37 | 0x00, 0xF0, 0x00, 0xF0, 0x0F, 0x0F, 0x00, 0xF0, 0xF0, 0x0F, 0x00, 0x3F,
38 | 0x0F, 0xCC, 0x0F, 0x03, 0xC0, 0xF0, 0x30, 0x30, 0x0C, 0x0C, 0x03, 0x00,
39 | 0xC0, 0x30, 0x00, 0x00, 0x00, 0xC0, 0x30, 0x0F, 0xF0, 0x1F, 0xE0, 0xC0,
40 | 0x31, 0x80, 0x6C, 0x3F, 0x78, 0x7E, 0xF3, 0x0D, 0xE6, 0x1B, 0xCC, 0x37,
41 | 0x98, 0x6F, 0x0F, 0xF6, 0x1F, 0xE3, 0x00, 0x66, 0x00, 0xC3, 0xFF, 0x07,
42 | 0xFE, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x0C, 0xC0, 0x33, 0x00,
43 | 0xCC, 0x03, 0x30, 0x30, 0x30, 0xC0, 0xC3, 0xFF, 0x0F, 0xFC, 0xC0, 0x0F,
44 | 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xFF, 0x3F, 0xCC, 0x0F, 0x03, 0xC0, 0xF0,
45 | 0x3F, 0xF3, 0xFC, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3F, 0xF3,
46 | 0xFC, 0x0F, 0xF0, 0x3F, 0xC3, 0x00, 0xCC, 0x03, 0xC0, 0x03, 0x00, 0x0C,
47 | 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0x30, 0x0C,
48 | 0xC0, 0x30, 0xFF, 0x03, 0xFC, 0xFF, 0x0F, 0xF0, 0xC0, 0xCC, 0x0C, 0xC0,
49 | 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0,
50 | 0xCC, 0x0C, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0xFC, 0x03, 0x00, 0xC0, 0x30,
51 | 0x0F, 0xF3, 0xFC, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0F, 0xFF,
52 | 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0x00, 0xC0, 0x30, 0x0F, 0xF3, 0xFC, 0xC0,
53 | 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0x0F, 0xF0, 0x3F,
54 | 0xC3, 0x00, 0xCC, 0x03, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0,
55 | 0xFF, 0x03, 0xFC, 0x00, 0xF0, 0x03, 0x30, 0x0C, 0xC0, 0x30, 0xFF, 0x03,
56 | 0xFC, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xFF, 0xFF,
57 | 0xFF, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C,
58 | 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
59 | 0x03, 0x03, 0x03, 0x03, 0x03, 0xC3, 0xC3, 0x3C, 0x3C, 0xC0, 0xCC, 0x0C,
60 | 0xC3, 0x0C, 0x30, 0xCC, 0x0C, 0xC0, 0xF0, 0x0F, 0x00, 0xCC, 0x0C, 0xC0,
61 | 0xC3, 0x0C, 0x30, 0xC0, 0xCC, 0x0C, 0xC0, 0x3C, 0x03, 0xC0, 0x30, 0x0C,
62 | 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00,
63 | 0xC0, 0x30, 0x0F, 0xFF, 0xFF, 0xC0, 0x0F, 0x00, 0x3F, 0x03, 0xFC, 0x0F,
64 | 0xF0, 0x3F, 0xC0, 0xFC, 0xCC, 0xF3, 0x33, 0xCC, 0xCF, 0x33, 0x3C, 0x30,
65 | 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0xF0, 0x3F,
66 | 0x0F, 0xC3, 0xF0, 0xFC, 0x3C, 0xCF, 0x33, 0xCC, 0xF3, 0x3C, 0x3F, 0x0F,
67 | 0xC3, 0xF0, 0xFC, 0x0F, 0x03, 0x0F, 0xC0, 0x3F, 0x03, 0x03, 0x0C, 0x0C,
68 | 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00,
69 | 0xF0, 0x03, 0x30, 0x30, 0xC0, 0xC0, 0xFC, 0x03, 0xF0, 0xFF, 0x3F, 0xCC,
70 | 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xFF, 0x3F, 0xCC, 0x03, 0x00,
71 | 0xC0, 0x30, 0x0C, 0x03, 0x00, 0x0F, 0xC0, 0x3F, 0x03, 0x03, 0x0C, 0x0C,
72 | 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0xC0, 0x0F, 0x00, 0x3C, 0x00,
73 | 0xF0, 0x03, 0x30, 0xF0, 0xC3, 0xC0, 0xFF, 0xC3, 0xFF, 0xFF, 0xCF, 0xFC,
74 | 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xFF, 0xCF, 0xFC,
75 | 0xC3, 0x0C, 0x30, 0xC0, 0xCC, 0x0C, 0xC0, 0x3C, 0x03, 0x3F, 0x0F, 0xCC,
76 | 0x0F, 0x03, 0xC0, 0x30, 0x03, 0xC0, 0xF0, 0x03, 0x00, 0xC0, 0x0C, 0x03,
77 | 0xC0, 0xF0, 0x33, 0xF0, 0xFC, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03,
78 | 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0,
79 | 0x30, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C,
80 | 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0x3F, 0xC3,
81 | 0xFC, 0xC0, 0x0F, 0x00, 0x3C, 0x00, 0xF0, 0x03, 0x30, 0x30, 0xC0, 0xC3,
82 | 0x03, 0x0C, 0x0C, 0x0C, 0xC0, 0x33, 0x00, 0xCC, 0x03, 0x30, 0x03, 0x00,
83 | 0x0C, 0x00, 0x30, 0x00, 0xC0, 0xC0, 0xC1, 0xE0, 0x60, 0xF0, 0x30, 0x78,
84 | 0x18, 0x3C, 0x0C, 0x1E, 0x06, 0x0C, 0xCC, 0xCC, 0x66, 0x66, 0x33, 0x33,
85 | 0x19, 0x99, 0x8C, 0xCC, 0xC6, 0x66, 0x60, 0xC0, 0xC0, 0x60, 0x60, 0x30,
86 | 0x30, 0x18, 0x18, 0xC0, 0xF0, 0x33, 0x30, 0xCC, 0x33, 0x0C, 0xC0, 0xC0,
87 | 0x30, 0x0C, 0x03, 0x03, 0x30, 0xCC, 0x33, 0x0C, 0xCC, 0x0F, 0x03, 0xC0,
88 | 0xF0, 0x3C, 0x0F, 0x03, 0x33, 0x0C, 0xC3, 0x30, 0xCC, 0x0C, 0x03, 0x00,
89 | 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xFF, 0x00, 0x30,
90 | 0x03, 0x00, 0xC0, 0x0C, 0x03, 0x00, 0x30, 0x0C, 0x00, 0xC0, 0x30, 0x03,
91 | 0x00, 0xC0, 0x0C, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, 0xCC, 0xCC, 0xCC,
92 | 0xCC, 0xCC, 0xCC, 0xCC, 0xFF, 0xC3, 0x0C, 0x30, 0xC3, 0x03, 0x0C, 0x30,
93 | 0xC3, 0x0C, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xFF, 0x33, 0x33, 0x33, 0x33,
94 | 0x33, 0x33, 0x33, 0x33, 0xFF, 0x0C, 0x03, 0x03, 0x30, 0xCC, 0x33, 0x0C,
95 | 0xCC, 0x0F, 0x03, 0xC0, 0xF0, 0x30, 0xFF, 0xFF, 0xFF, 0xCC, 0x33, 0x3F,
96 | 0x0F, 0xCC, 0x0F, 0x03, 0x3F, 0xCF, 0xFC, 0x0F, 0x03, 0x3F, 0xCF, 0xF0,
97 | 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0F, 0xF3, 0xFC, 0xC0, 0xF0,
98 | 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3F, 0xF3, 0xFC, 0x3F, 0x0F, 0xCC, 0x0F,
99 | 0x03, 0xC0, 0x30, 0x0C, 0x0F, 0x03, 0x3F, 0x0F, 0xC0, 0x00, 0xC0, 0x30,
100 | 0x0C, 0x03, 0x00, 0xC0, 0x33, 0xFC, 0xFF, 0xC0, 0xF0, 0x3C, 0x0F, 0x03,
101 | 0xC0, 0xF0, 0x33, 0xFC, 0xFF, 0x3F, 0x0F, 0xCC, 0x0F, 0x03, 0xFF, 0xFF,
102 | 0xFC, 0x03, 0x00, 0x3F, 0xCF, 0xF0, 0x0C, 0x33, 0x0C, 0x30, 0xCF, 0xFF,
103 | 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x3F, 0xCF, 0xFC, 0x0F, 0x03, 0xC0,
104 | 0xF0, 0x3C, 0x0F, 0x03, 0x3F, 0xCF, 0xF0, 0x0C, 0x03, 0x3F, 0x0F, 0xC0,
105 | 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0F, 0xF3, 0xFC, 0xC0, 0xF0,
106 | 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xF0, 0xFF, 0xFF, 0xF0,
107 | 0x33, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0xCC, 0xC0, 0xC0, 0xC0,
108 | 0xC0, 0xC0, 0xC0, 0xC3, 0xC3, 0xCC, 0xCC, 0xF0, 0xF0, 0xCC, 0xCC, 0xC3,
109 | 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0xF3, 0xF3, 0xCC, 0x30, 0xF0, 0xC3,
110 | 0xC3, 0x0F, 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xC3, 0x0F, 0x0C, 0x30, 0xFF,
111 | 0x3F, 0xCC, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x30,
112 | 0x3F, 0x0F, 0xCC, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x3F, 0x0F,
113 | 0xC0, 0xFF, 0x3F, 0xCC, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xFF,
114 | 0x3F, 0xCC, 0x03, 0x00, 0xC0, 0x30, 0x00, 0x3F, 0xCF, 0xFC, 0x0F, 0x03,
115 | 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x3F, 0xCF, 0xF0, 0x0C, 0x03, 0x00, 0xC0,
116 | 0x30, 0xCF, 0xCF, 0xF0, 0xF0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x3F,
117 | 0x3F, 0xC0, 0xC0, 0x3C, 0x3C, 0x03, 0x03, 0xFC, 0xFC, 0x30, 0xC3, 0x0C,
118 | 0xFF, 0xF3, 0x0C, 0x30, 0xC3, 0x0C, 0x0C, 0x30, 0xC0, 0xF0, 0x3C, 0x0F,
119 | 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x3F, 0xCF, 0xF0, 0xC0, 0xF0, 0x3C,
120 | 0x0F, 0x03, 0x33, 0x0C, 0xC3, 0x30, 0xCC, 0x0C, 0x03, 0x00, 0xC3, 0x0F,
121 | 0x0C, 0x3C, 0x30, 0xF0, 0xC3, 0xCC, 0xCF, 0x33, 0x33, 0x03, 0x0C, 0x0C,
122 | 0x30, 0x30, 0xC0, 0xC0, 0xC0, 0xF0, 0x33, 0x30, 0xCC, 0x0C, 0x03, 0x03,
123 | 0x30, 0xCC, 0xC0, 0xF0, 0x30, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x33, 0x0C,
124 | 0xC3, 0x30, 0xCC, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0xF0, 0x3C, 0x00, 0xFF,
125 | 0xFF, 0xF0, 0x30, 0x0C, 0x0C, 0x03, 0x03, 0x00, 0xC0, 0xFF, 0xFF, 0xF0,
126 | 0x0C, 0x33, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xCC, 0x30, 0x30, 0xC3, 0x0C,
127 | 0x30, 0xC3, 0x0C, 0x0C, 0x30, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x03,
128 | 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC0, 0xC3, 0x30, 0xC3, 0x0C, 0x30, 0xC3,
129 | 0x0C, 0xC3, 0x00, 0x33, 0x33, 0xCC, 0xCC };
130 |
131 | const GFXglyph HelvetiPixel16pt7bGlyphs[] PROGMEM = {
132 | { 0, 1, 1, 10, 0, 0 }, // 0x20 ' '
133 | { 1, 2, 16, 6, 2, -15 }, // 0x21 '!'
134 | { 5, 6, 6, 8, 0, -15 }, // 0x22 '"'
135 | { 10, 12, 16, 14, 0, -15 }, // 0x23 '#'
136 | { 34, 10, 17, 12, 0, -16 }, // 0x24 '$'
137 | { 56, 15, 16, 19, 2, -15 }, // 0x25 '%'
138 | { 86, 14, 16, 16, 0, -15 }, // 0x26 '&'
139 | { 114, 2, 6, 4, 0, -15 }, // 0x27 '''
140 | { 116, 6, 20, 8, 0, -15 }, // 0x28 '('
141 | { 131, 6, 20, 8, 0, -15 }, // 0x29 ')'
142 | { 146, 10, 6, 12, 0, -13 }, // 0x2A '*'
143 | { 154, 10, 10, 12, 0, -11 }, // 0x2B '+'
144 | { 167, 4, 6, 6, 0, -1 }, // 0x2C ','
145 | { 170, 6, 2, 8, 0, -5 }, // 0x2D '-'
146 | { 172, 2, 2, 6, 2, -1 }, // 0x2E '.'
147 | { 173, 6, 18, 8, 0, -15 }, // 0x2F '/'
148 | { 187, 10, 16, 12, 0, -15 }, // 0x30 '0'
149 | { 207, 6, 16, 10, 2, -15 }, // 0x31 '1'
150 | { 219, 10, 16, 12, 0, -15 }, // 0x32 '2'
151 | { 239, 10, 16, 12, 0, -15 }, // 0x33 '3'
152 | { 259, 12, 16, 14, 0, -15 }, // 0x34 '4'
153 | { 283, 10, 16, 12, 0, -15 }, // 0x35 '5'
154 | { 303, 10, 16, 12, 0, -15 }, // 0x36 '6'
155 | { 323, 10, 16, 12, 0, -15 }, // 0x37 '7'
156 | { 343, 10, 16, 12, 0, -15 }, // 0x38 '8'
157 | { 363, 10, 16, 12, 0, -15 }, // 0x39 '9'
158 | { 383, 2, 10, 6, 2, -9 }, // 0x3A ':'
159 | { 386, 4, 14, 6, 0, -9 }, // 0x3B ';'
160 | { 393, 12, 10, 14, 0, -11 }, // 0x3C '<'
161 | { 408, 10, 6, 12, 0, -9 }, // 0x3D '='
162 | { 416, 12, 10, 14, 0, -11 }, // 0x3E '>'
163 | { 431, 10, 16, 12, 0, -15 }, // 0x3F '?'
164 | { 451, 15, 16, 19, 2, -13 }, // 0x40 '@'
165 | { 481, 14, 16, 16, 0, -15 }, // 0x41 'A'
166 | { 509, 10, 16, 14, 2, -15 }, // 0x42 'B'
167 | { 529, 14, 16, 16, 0, -15 }, // 0x43 'C'
168 | { 557, 12, 16, 16, 2, -15 }, // 0x44 'D'
169 | { 581, 10, 16, 14, 2, -15 }, // 0x45 'E'
170 | { 601, 10, 16, 14, 2, -15 }, // 0x46 'F'
171 | { 621, 14, 16, 16, 0, -15 }, // 0x47 'G'
172 | { 649, 12, 16, 16, 2, -15 }, // 0x48 'H'
173 | { 673, 2, 16, 6, 2, -15 }, // 0x49 'I'
174 | { 677, 8, 16, 10, 0, -15 }, // 0x4A 'J'
175 | { 693, 12, 16, 16, 2, -15 }, // 0x4B 'K'
176 | { 717, 10, 16, 14, 2, -15 }, // 0x4C 'L'
177 | { 737, 14, 16, 17, 2, -15 }, // 0x4D 'M'
178 | { 765, 10, 16, 14, 2, -15 }, // 0x4E 'N'
179 | { 785, 14, 16, 16, 0, -15 }, // 0x4F 'O'
180 | { 813, 10, 16, 14, 2, -15 }, // 0x50 'P'
181 | { 833, 14, 16, 16, 0, -15 }, // 0x51 'Q'
182 | { 861, 12, 16, 16, 2, -15 }, // 0x52 'R'
183 | { 885, 10, 16, 14, 2, -15 }, // 0x53 'S'
184 | { 905, 10, 16, 12, 0, -15 }, // 0x54 'T'
185 | { 925, 12, 16, 16, 2, -15 }, // 0x55 'U'
186 | { 949, 14, 16, 16, 0, -15 }, // 0x56 'V'
187 | { 977, 17, 16, 19, 0, -15 }, // 0x57 'W'
188 | { 1011, 10, 16, 14, 2, -15 }, // 0x58 'X'
189 | { 1031, 10, 16, 12, 0, -15 }, // 0x59 'Y'
190 | { 1051, 12, 16, 14, 0, -15 }, // 0x5A 'Z'
191 | { 1075, 4, 20, 8, 2, -15 }, // 0x5B '['
192 | { 1085, 6, 18, 8, 0, -15 }, // 0x5C '\'
193 | { 1099, 4, 20, 6, 0, -15 }, // 0x5D ']'
194 | { 1109, 10, 10, 12, 0, -15 }, // 0x5E '^'
195 | { 1122, 12, 2, 14, 0, 3 }, // 0x5F '_'
196 | { 1125, 4, 4, 6, 0, -15 }, // 0x60 '`'
197 | { 1127, 10, 10, 12, 0, -9 }, // 0x61 'a'
198 | { 1140, 10, 16, 14, 2, -15 }, // 0x62 'b'
199 | { 1160, 10, 10, 12, 0, -9 }, // 0x63 'c'
200 | { 1173, 10, 16, 12, 0, -15 }, // 0x64 'd'
201 | { 1193, 10, 10, 12, 0, -9 }, // 0x65 'e'
202 | { 1206, 6, 16, 8, 0, -15 }, // 0x66 'f'
203 | { 1218, 10, 14, 12, 0, -9 }, // 0x67 'g'
204 | { 1236, 10, 16, 14, 2, -15 }, // 0x68 'h'
205 | { 1256, 2, 14, 6, 2, -13 }, // 0x69 'i'
206 | { 1260, 4, 18, 6, 0, -13 }, // 0x6A 'j'
207 | { 1269, 8, 16, 12, 2, -15 }, // 0x6B 'k'
208 | { 1285, 2, 16, 6, 2, -15 }, // 0x6C 'l'
209 | { 1289, 14, 10, 17, 2, -9 }, // 0x6D 'm'
210 | { 1307, 10, 10, 14, 2, -9 }, // 0x6E 'n'
211 | { 1320, 10, 10, 12, 0, -9 }, // 0x6F 'o'
212 | { 1333, 10, 14, 14, 2, -9 }, // 0x70 'p'
213 | { 1351, 10, 14, 12, 0, -9 }, // 0x71 'q'
214 | { 1369, 8, 10, 12, 2, -9 }, // 0x72 'r'
215 | { 1379, 8, 10, 10, 0, -9 }, // 0x73 's'
216 | { 1389, 6, 14, 8, 0, -13 }, // 0x74 't'
217 | { 1400, 10, 10, 14, 2, -9 }, // 0x75 'u'
218 | { 1413, 10, 10, 12, 0, -9 }, // 0x76 'v'
219 | { 1426, 14, 10, 16, 0, -9 }, // 0x77 'w'
220 | { 1444, 10, 10, 12, 0, -9 }, // 0x78 'x'
221 | { 1457, 10, 14, 12, 0, -9 }, // 0x79 'y'
222 | { 1475, 10, 10, 12, 0, -9 }, // 0x7A 'z'
223 | { 1488, 6, 22, 8, 0, -15 }, // 0x7B '{'
224 | { 1505, 2, 20, 6, 2, -15 }, // 0x7C '|'
225 | { 1510, 6, 22, 8, 0, -15 }, // 0x7D '}'
226 | { 1527, 8, 4, 12, 2, -9 } }; // 0x7E '~'
227 |
228 | const GFXfont HelvetiPixel16pt7b PROGMEM = {
229 | (uint8_t *)HelvetiPixel16pt7bBitmaps,
230 | (GFXglyph *)HelvetiPixel16pt7bGlyphs,
231 | 0x20, 0x7E, 30 };
232 |
233 | // Approx. 2203 bytes
234 |
--------------------------------------------------------------------------------
/src/gfx_utils.cpp:
--------------------------------------------------------------------------------
1 | #include "gfx_utils.h"
2 | #include "debug.h"
3 |
4 | void drawDebugCrosshair(DISPLAY_CLASS &display, int16_t x, int16_t y, int16_t length, uint16_t color)
5 | {
6 | #ifdef DEBUG
7 | display.drawFastHLine(x - length, y, 2 * length, color);
8 | display.drawFastVLine(x, y - length, 2 * length, color);
9 | #endif
10 | }
11 |
12 | void drawPattern(DISPLAY_CLASS &display, Pattern pattern, int16_t x, int16_t y, int16_t w, int16_t h)
13 | {
14 | const uint8_t *patternData = patterns[(int)pattern];
15 | for (int16_t i = 0; i < h; i++)
16 | {
17 | for (int16_t j = 0; j < w; j++)
18 | {
19 | if (patternData[i % 8] & (0x80 >> (j % 8)))
20 | {
21 | display.drawPixel(x + j, y + i, GxEPD_BLACK);
22 | }
23 | }
24 | }
25 | }
26 |
27 | void drawPatternInRoundedArea(DISPLAY_CLASS &display, int16_t startX, int16_t startY, int16_t areaWidth, int16_t areaHeight, int16_t radius, Pattern patternNo)
28 | {
29 | const int16_t patternWidth = 8;
30 | const int16_t patternHeight = 8;
31 | const uint8_t *pattern = patterns[(int)patternNo];
32 |
33 | // Pre-calculate squared radius for circle tests.
34 | const int16_t rSq = radius * radius;
35 |
36 | // Define centers for the four corners.
37 | const int16_t centerTL_x = startX + radius;
38 | const int16_t centerTL_y = startY + radius;
39 | const int16_t centerTR_x = startX + areaWidth - radius - 1;
40 | const int16_t centerTR_y = startY + radius;
41 | const int16_t centerBL_x = startX + radius;
42 | const int16_t centerBL_y = startY + areaHeight - radius - 1;
43 | const int16_t centerBR_x = startX + areaWidth - radius - 1;
44 | const int16_t centerBR_y = startY + areaHeight - radius - 1;
45 |
46 | // Loop through every pixel in the defined area.
47 | for (int16_t y = startY; y < startY + areaHeight; y++)
48 | {
49 | for (int16_t x = startX; x < startX + areaWidth; x++)
50 | {
51 | bool drawPixelFlag = true;
52 | // Top-left corner
53 | if (x < startX + radius && y < startY + radius)
54 | {
55 | int16_t dx = centerTL_x - x;
56 | int16_t dy = centerTL_y - y;
57 | if ((dx * dx + dy * dy) > rSq)
58 | drawPixelFlag = false;
59 | }
60 | // Top-right corner
61 | else if (x >= startX + areaWidth - radius && y < startY + radius)
62 | {
63 | int16_t dx = x - centerTR_x;
64 | int16_t dy = centerTR_y - y;
65 | if ((dx * dx + dy * dy) > rSq)
66 | drawPixelFlag = false;
67 | }
68 | // Bottom-left corner
69 | else if (x < startX + radius && y >= startY + areaHeight - radius)
70 | {
71 | int16_t dx = centerBL_x - x;
72 | int16_t dy = y - centerBL_y;
73 | if ((dx * dx + dy * dy) > rSq)
74 | drawPixelFlag = false;
75 | }
76 | // Bottom-right corner
77 | else if (x >= startX + areaWidth - radius && y >= startY + areaHeight - radius)
78 | {
79 | int16_t dx = x - centerBR_x;
80 | int16_t dy = y - centerBR_y;
81 | if ((dx * dx + dy * dy) > rSq)
82 | drawPixelFlag = false;
83 | }
84 |
85 | if (drawPixelFlag)
86 | {
87 | // Determine pattern coordinates.
88 | int patternX = (x - startX) % patternWidth;
89 | int patternY = (y - startY) % patternHeight;
90 | // Check the bit (assumes MSB first in each byte).
91 | if (pattern[patternY] & (0x80 >> patternX))
92 | {
93 | display.drawPixel(x, y, GxEPD_BLACK);
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
100 | void drawProgressBar(DISPLAY_CLASS &display, ProgressBarStyle style, int16_t x, int16_t y, int16_t width, int16_t height, int16_t radius, int16_t progress)
101 | {
102 | int16_t progressWidth;
103 | int16_t innerRadius = radius > 2 ? radius - 2 : 0;
104 | switch (style)
105 | {
106 | case ProgressBarStyle::Bordered:
107 | display.fillRoundRect(x, y, width, height, radius, GxEPD_WHITE);
108 | display.drawRoundRect(x, y, width, height, radius, GxEPD_BLACK);
109 | progressWidth = (width - 8) * progress / 100;
110 |
111 | // recalculate inner radius because it is smaller than the outer radius
112 |
113 | drawPatternInRoundedArea(display, x + 4, y + 4, progressWidth, height - 8, innerRadius, Pattern::Dots);
114 | break;
115 | case ProgressBarStyle::Borderless:
116 | display.fillRoundRect(x, y, width, height, radius, GxEPD_WHITE);
117 | progressWidth = width * progress / 100;
118 |
119 | display.fillRoundRect(x, y, progressWidth, height, radius, GxEPD_BLACK);
120 | break;
121 | }
122 | }
123 |
124 | Bounds getBounds(DISPLAY_CLASS &display, const char *text, const GFXfont *font)
125 | {
126 | display.setTextSize(1);
127 | display.setFont(font);
128 |
129 | int16_t x1, y1;
130 | uint16_t w, h;
131 | display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
132 |
133 | return {x1, y1, w, h};
134 | }
135 |
136 | Bounds drawText(DISPLAY_CLASS &display, const char *text, int16_t x, int16_t y, const GFXfont *font, uint16_t color)
137 | {
138 | display.setTextSize(1);
139 | display.setFont(font);
140 | display.setTextColor(color);
141 |
142 | int16_t x1, y1;
143 | uint16_t w, h;
144 | display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
145 | display.setCursor(x, y);
146 | display.print(text);
147 |
148 | return {x, y, w, h};
149 | }
150 |
151 | Bounds drawBottomAlignedText(DISPLAY_CLASS &display, const char *text, int16_t x, int16_t y, const GFXfont *font, uint16_t color)
152 | {
153 | display.setTextSize(1);
154 | display.setFont(font);
155 | display.setTextColor(color);
156 |
157 | int16_t x1, y1;
158 | uint16_t w, h;
159 | display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
160 | display.setCursor(x, y + h);
161 | display.print(text);
162 |
163 | return {static_cast(x), static_cast(y - h), w, h};
164 | }
165 |
166 | Bounds drawCenteredText(DISPLAY_CLASS &display, const char *text, int16_t x, int16_t y, const GFXfont *font, uint16_t color)
167 | {
168 | display.setTextSize(1);
169 | display.setFont(font);
170 | display.setTextColor(color);
171 |
172 | int16_t x1, y1;
173 | uint16_t w, h;
174 | display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
175 | // Correct the y coordinate considering y1 offset (usually negative)
176 | int16_t correctedY = y - h / 2 - y1;
177 |
178 | display.setCursor(x - w / 2, correctedY);
179 | display.print(text);
180 |
181 | return {static_cast(x - w / 2), correctedY, w, h};
182 | }
183 |
--------------------------------------------------------------------------------
/src/gfx_utils.h:
--------------------------------------------------------------------------------
1 | #ifndef GFXUTILS_H
2 | #define GFXUTILS_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | #include "defs.h"
10 |
11 | enum class ProgressBarStyle
12 | {
13 | Bordered,
14 | Borderless
15 | };
16 |
17 | enum class Pattern
18 | {
19 | Solid,
20 | Stripes,
21 | Dots,
22 | Checkerboard,
23 | DiagonalStripes,
24 | CrossHatch,
25 | SparseDots,
26 | VerySparseDots
27 | };
28 |
29 | // define 8x8 patterns
30 | const uint8_t pattern_solid[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
31 | // refined stripes: wider bands (upper half and lower half)
32 | const uint8_t pattern_stripes[8] = {0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F};
33 | // refined dots: softer dot effect
34 | const uint8_t pattern_dots[8] = {0x88, 0x44, 0x22, 0x11, 0x11, 0x22, 0x44, 0x88};
35 | // new sparse dots pattern: dots further apart
36 | const uint8_t pattern_sparse_dots[8] = {0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00};
37 | // new very sparse dots pattern: dots very far apart
38 | const uint8_t pattern_very_sparse_dots[8] = {0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00};
39 | // refined checkerboard: standard alternating bits
40 | const uint8_t pattern_checkerboard[8] = {0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55};
41 | // new diagonal stripes: repeated diagonal bands
42 | const uint8_t pattern_diagonal_stripes[8] = {0xC0, 0x30, 0x0C, 0x03, 0xC0, 0x30, 0x0C, 0x03};
43 | // new crosshatch: grid-like pattern with full horizontal bars on top, middle, and bottom
44 | const uint8_t pattern_crosshatch[8] = {0xFF, 0x92, 0x92, 0x92, 0xFF, 0x92, 0x92, 0xFF};
45 |
46 | // put all patterns in an array
47 | const std::vector patterns = {
48 | pattern_solid,
49 | pattern_stripes,
50 | pattern_dots,
51 | pattern_checkerboard,
52 | pattern_diagonal_stripes,
53 | pattern_crosshatch,
54 | pattern_sparse_dots,
55 | pattern_very_sparse_dots};
56 |
57 | void drawDebugCrosshair(DISPLAY_CLASS &display, int16_t x, int16_t y, int16_t length = 8, uint16_t color = GxEPD_BLACK);
58 |
59 | void drawPattern(DISPLAY_CLASS &display, Pattern pattern, int16_t x, int16_t y, int16_t w, int16_t h);
60 |
61 | void drawPatternInRoundedArea(
62 | DISPLAY_CLASS &display,
63 | int16_t startX, int16_t startY,
64 | int16_t areaWidth, int16_t areaHeight,
65 | int16_t radius,
66 | Pattern patternNo);
67 |
68 | void drawProgressBar(
69 | DISPLAY_CLASS &display,
70 | ProgressBarStyle style,
71 | int16_t x,
72 | int16_t y,
73 | int16_t width,
74 | int16_t height,
75 | int16_t radius,
76 | int16_t progress);
77 |
78 | struct Bounds
79 | {
80 | int16_t x;
81 | int16_t y;
82 | uint16_t w;
83 | uint16_t h;
84 | };
85 |
86 | Bounds getBounds(
87 | DISPLAY_CLASS &display,
88 | const char *text,
89 | const GFXfont *font);
90 |
91 | Bounds drawText(
92 | DISPLAY_CLASS &display,
93 | const char *text,
94 | int16_t x,
95 | int16_t y,
96 | const GFXfont *font,
97 | uint16_t color);
98 |
99 | Bounds drawBottomAlignedText(DISPLAY_CLASS &display, const char *text, int16_t x, int16_t y, const GFXfont *font, uint16_t color);
100 |
101 | Bounds drawCenteredText(DISPLAY_CLASS &display, const char *text, int16_t x, int16_t y, const GFXfont *font, uint16_t color);
102 |
103 | #endif
--------------------------------------------------------------------------------
/src/icon.h:
--------------------------------------------------------------------------------
1 | #ifndef ICON_H
2 | #define ICON_H
3 |
4 | #include
5 |
6 | class ScaledIcon
7 | {
8 | public:
9 | ScaledIcon(const unsigned char *data, uint16_t size)
10 | : data(data), size(size) {}
11 |
12 | const unsigned char *data;
13 | uint16_t size;
14 | };
15 |
16 | class Icon
17 | {
18 | public:
19 | Icon(const unsigned char *icon192, const unsigned char *icon128, const unsigned char *icon64, const unsigned char *icon48)
20 | : icon192(icon192), icon128(icon128), icon64(icon64), icon48(icon48) {}
21 |
22 | const unsigned char *icon192;
23 | const unsigned char *icon128;
24 | const unsigned char *icon64;
25 | const unsigned char *icon48;
26 |
27 | ScaledIcon scaled(uint16_t size)
28 | {
29 | switch (size)
30 | {
31 | case 192:
32 | return ScaledIcon(icon192, 192);
33 | case 128:
34 | return ScaledIcon(icon128, 128);
35 | case 64:
36 | return ScaledIcon(icon64, 64);
37 | case 48:
38 | return ScaledIcon(icon48, 48);
39 | default:
40 | return ScaledIcon(icon48, 48);
41 | }
42 | }
43 | };
44 |
45 | #endif // ICON_H
--------------------------------------------------------------------------------
/src/icon_provider.cpp:
--------------------------------------------------------------------------------
1 | #include "icon_provider.h"
2 |
3 | // Initialize the static member
4 | IconProvider *IconProvider::instance = nullptr;
--------------------------------------------------------------------------------
/src/icon_provider.h:
--------------------------------------------------------------------------------
1 | #ifndef ICON_PROVIDER_H
2 | #define ICON_PROVIDER_H
3 |
4 | #include "icon.h"
5 | #include "icons.h"
6 | #include "images.h"
7 |
8 | class IconProvider
9 | {
10 | private:
11 | static IconProvider *instance;
12 | bool lpeModeEnabled = false;
13 |
14 | public:
15 | static IconProvider *getInstance()
16 | {
17 | if (!instance)
18 | {
19 | instance = new IconProvider();
20 | }
21 | return instance;
22 | }
23 |
24 | void setLpeMode(bool enabled)
25 | {
26 | lpeModeEnabled = enabled;
27 | }
28 |
29 | bool isLpeModeEnabled() const
30 | {
31 | return lpeModeEnabled;
32 | }
33 |
34 | const unsigned char *getTimerRunningBackgroundImage()
35 | {
36 | if (lpeModeEnabled)
37 | {
38 | return image_bg_lpe_bubble;
39 | }
40 | else
41 | {
42 | return image_bg_bubble;
43 | }
44 | }
45 |
46 | Icon *getPresetIcon(const char *name)
47 | {
48 | if (strcmp(name, "Coding") == 0)
49 | {
50 | if (lpeModeEnabled)
51 | {
52 | return &icon_lpehacker;
53 | }
54 | else
55 | {
56 | return &icon_coding;
57 | }
58 | }
59 | else if (strcmp(name, "Emails") == 0)
60 | {
61 | if (lpeModeEnabled)
62 | {
63 | return &icon_lpetantrum;
64 | }
65 | else
66 | {
67 | return &icon_email;
68 | }
69 | }
70 | else if (strcmp(name, "Focus") == 0)
71 | {
72 | if (lpeModeEnabled)
73 | {
74 | return &icon_lpethink;
75 | }
76 | else
77 | {
78 | return &icon_focus;
79 | }
80 | }
81 |
82 | return &icon_lpehacker;
83 | }
84 | };
85 |
86 | #endif // ICON_PROVIDER_H
--------------------------------------------------------------------------------
/src/icons.h:
--------------------------------------------------------------------------------
1 | #ifndef ICONS_H
2 | #define ICONS_H
3 |
4 | #include "icon.h"
5 |
6 | extern Icon icon_email;
7 | extern Icon icon_warning;
8 | extern Icon icon_focus;
9 | extern Icon icon_coffee;
10 | extern Icon icon_lpetantrum;
11 | extern Icon icon_lpehacker;
12 | extern Icon icon_checkmark;
13 | extern Icon icon_lpenote;
14 | extern Icon icon_lpethink;
15 | extern Icon icon_lpesip;
16 | extern Icon icon_coding;
17 |
18 | #endif // ICONS_H
19 |
--------------------------------------------------------------------------------
/src/images.h:
--------------------------------------------------------------------------------
1 | #ifndef IMAGES_H
2 | #define IMAGES_H
3 |
4 | #include "images/image_bg_cat.h"
5 | #include "images/image_bg_stonks.h"
6 | #include "images/image_bg_splash.h"
7 | #include "images/image_bg_pablo.h"
8 | #include "images/image_bg_lpe_bubble.h"
9 | #include "images/image_bg_what_a_week.h"
10 | #include "images/image_bg_bubble.h"
11 |
12 | #endif // IMAGES_H
13 |
--------------------------------------------------------------------------------
/src/led.cpp:
--------------------------------------------------------------------------------
1 | #include "led.h"
2 | #include "button.h"
3 | #include "timer.h"
4 |
5 | #define WS2812_PIN 25
6 | #define LED_TASK_STACK_SIZE 2048
7 | #define LED_TASK_PRIORITY 2
8 | #define LED_CORE 0 // Run on core 0 since core 1 is used by Arduino loop
9 |
10 | TaskHandle_t ledTaskHandle = NULL;
11 | static NeoPixelBus strip(1, WS2812_PIN);
12 | static NeoPixelAnimator animations(2);
13 | static volatile LedMode currentMode = LedMode::Off;
14 | static volatile LedMode lastMode;
15 |
16 | static unsigned long lastFlashTime = 0;
17 | static boolean flashState = false;
18 |
19 | static bool isEncoderTriggeredFlash = false; // Track which input triggered the flash
20 |
21 | struct MyAnimationState
22 | {
23 | RgbColor StartingColor;
24 | RgbColor EndingColor;
25 | };
26 |
27 | static MyAnimationState animationState[1];
28 |
29 | static void BlendAnimUpdate(const AnimationParam ¶m)
30 | {
31 | RgbColor updatedColor = RgbColor::LinearBlend(
32 | animationState[param.index].StartingColor,
33 | animationState[param.index].EndingColor,
34 | param.progress);
35 |
36 | strip.SetPixelColor(0, updatedColor);
37 | }
38 |
39 | static void startConfirmationFadeIn()
40 | {
41 | animationState[0].StartingColor = strip.GetPixelColor(0);
42 | animationState[0].EndingColor = RgbColor(4, 0, 153);
43 | animations.StartAnimation(0, 1000, BlendAnimUpdate); // 1 second fade in
44 | }
45 |
46 | static void startConfirmationFadeOut()
47 | {
48 | animationState[0].StartingColor = strip.GetPixelColor(0);
49 | animationState[0].EndingColor = RgbColor(0);
50 | animations.StartAnimation(0, 1000, BlendAnimUpdate); // 1 second fade out
51 | }
52 |
53 | static void handleConfirmationFlash()
54 | {
55 | unsigned long now = millis();
56 | if (!animations.IsAnimating() && (now - lastFlashTime >= 2000))
57 | { // 2 second interval
58 | flashState = !flashState;
59 | lastFlashTime = now;
60 |
61 | if (flashState)
62 | {
63 | startConfirmationFadeIn();
64 | }
65 | else
66 | {
67 | startConfirmationFadeOut();
68 | }
69 | }
70 |
71 | if (animations.IsAnimating())
72 | {
73 | animations.UpdateAnimations();
74 | strip.Show();
75 | }
76 | }
77 |
78 | uint8_t splashscreenColorIndex = 0;
79 |
80 | static void handleQuickAcknowledgementFlash()
81 | {
82 | unsigned long now = millis();
83 | if (!animations.IsAnimating())
84 | {
85 | if (!flashState)
86 | {
87 | // Start the flash
88 | flashState = true;
89 | lastFlashTime = now;
90 |
91 | // Use different colors based on input source
92 | if (isEncoderTriggeredFlash)
93 | {
94 | animationState[0].StartingColor = RgbColor(179, 120, 20);
95 | }
96 | else
97 | {
98 | animationState[0].StartingColor = RgbColor(53, 105, 19);
99 | }
100 | animationState[0].EndingColor = strip.GetPixelColor(0);
101 |
102 | animations.StartAnimation(0, isEncoderTriggeredFlash ? 100 : 300, BlendAnimUpdate);
103 | }
104 | else
105 | {
106 | // Flash animation completed, reset everything
107 | flashState = false;
108 |
109 | setLedMode(lastMode);
110 | return;
111 | }
112 | }
113 |
114 | // Update animation if it's still running
115 | if (animations.IsAnimating())
116 | {
117 | animations.UpdateAnimations();
118 | strip.Show();
119 | }
120 | }
121 |
122 | const RgbColor splashscreenColors[] = {
123 | RgbColor(0),
124 | RgbColor(48, 48, 48),
125 | RgbColor(32, 32, 32),
126 | RgbColor(0)};
127 |
128 | static void handleSplashscreen()
129 | {
130 | if (!animations.IsAnimating())
131 | {
132 | switch (splashscreenColorIndex)
133 | {
134 | case 0:
135 | animationState[0].StartingColor = splashscreenColors[0];
136 | animationState[0].EndingColor = splashscreenColors[1];
137 | animations.StartAnimation(0, 6000, BlendAnimUpdate);
138 | break;
139 |
140 | case 1:
141 | animationState[0].StartingColor = splashscreenColors[1];
142 | animationState[0].EndingColor = splashscreenColors[2];
143 | animations.StartAnimation(0, 4000, BlendAnimUpdate);
144 | break;
145 |
146 | case 2:
147 | animationState[0].StartingColor = splashscreenColors[2];
148 | animationState[0].EndingColor = splashscreenColors[3];
149 | animations.StartAnimation(0, 6000, BlendAnimUpdate);
150 | break;
151 |
152 | case 3:
153 | animationState[0].StartingColor = strip.GetPixelColor(0);
154 | animationState[0].EndingColor = RgbColor(0);
155 | animations.StartAnimation(0, 4000, BlendAnimUpdate);
156 | break;
157 | }
158 |
159 | splashscreenColorIndex++;
160 | if (splashscreenColorIndex > 3)
161 | {
162 | splashscreenColorIndex = 0;
163 | }
164 | }
165 |
166 | if (animations.IsAnimating())
167 | {
168 | animations.UpdateAnimations();
169 | strip.Show();
170 | }
171 | }
172 |
173 | // track last time where we showed a quick acknowledgement flash
174 | // so that we dont trigger on the same button press multiple times
175 | static unsigned long lastQuickAcknowledgeFlashTime = 0;
176 |
177 | int lastEncoderCount = 0;
178 | volatile int *encoderCount;
179 |
180 | void ledSetupEncoder(volatile int *encoder)
181 | {
182 | encoderCount = encoder;
183 | lastEncoderCount = *encoder;
184 | }
185 |
186 | static void ledTask(void *parameter)
187 | {
188 | while (1)
189 | {
190 | // Show quick acknowledgement flash if button was pressed or the encoder was turned
191 | if (
192 | (currentMode != LedMode::QuickAcknowledgementFlash) &&
193 | (currentMode != LedMode::TimerPaused) &&
194 | lastEncoderCount != *encoderCount)
195 | {
196 | Serial.println("=== Led: Quick acknowledgement flash (encoder)");
197 | isEncoderTriggeredFlash = true;
198 | setLedMode(LedMode::QuickAcknowledgementFlash);
199 | lastQuickAcknowledgeFlashTime = Button::instance->lastPressTime;
200 | }
201 | else if (Button::instance &&
202 | (currentMode != LedMode::QuickAcknowledgementFlash) &&
203 | (currentMode != LedMode::TimerPaused) &&
204 | (Button::instance->lastPressTime > lastQuickAcknowledgeFlashTime)) // use time in case we miss a press due to the task
205 | {
206 | Serial.println("=== Led: Quick acknowledgement flash");
207 | isEncoderTriggeredFlash = false;
208 | setLedMode(LedMode::QuickAcknowledgementFlash);
209 | lastQuickAcknowledgeFlashTime = Button::instance->lastPressTime;
210 | }
211 |
212 | lastEncoderCount = *encoderCount;
213 |
214 | switch (currentMode)
215 | {
216 | case LedMode::Off:
217 | // if (strip.GetPixelColor(0) != RgbColor(0) && !animations.IsAnimating())
218 | // {
219 | // animationState[0].StartingColor = strip.GetPixelColor(0);
220 | // animationState[0].EndingColor = RgbColor(0);
221 | // animations.StartAnimation(0, 300, BlendAnimUpdate);
222 | // }
223 |
224 | // if (animations.IsAnimating())
225 | // {
226 | // animations.UpdateAnimations();
227 | // strip.Show();
228 | // }
229 |
230 | if (strip.GetPixelColor(0) != RgbColor(0))
231 | {
232 | strip.SetPixelColor(0, RgbColor(0));
233 | strip.Show();
234 | lastMode = LedMode::Off;
235 | }
236 | break;
237 |
238 | case LedMode::QuickAcknowledgementFlash:
239 | handleQuickAcknowledgementFlash();
240 | break;
241 |
242 | case LedMode::Splashscreen:
243 | handleSplashscreen();
244 | break;
245 |
246 | case LedMode::ConfirmationFlash:
247 | handleConfirmationFlash();
248 | break;
249 |
250 | case LedMode::TimerPaused:
251 | if (strip.GetPixelColor(0) != RgbColor(38 / 2, 99 / 2, 143 / 2))
252 | {
253 | strip.SetPixelColor(0, RgbColor(38 / 2, 99 / 2, 143 / 2)); // Dim blue
254 | strip.Show();
255 | }
256 | }
257 | // Small delay to prevent task from hogging CPU
258 | vTaskDelay(pdMS_TO_TICKS(10));
259 | }
260 | }
261 |
262 | void setLedMode(LedMode mode)
263 | {
264 | Serial.printf("=== Led: Setting mode from %d to %d\n", currentMode, mode);
265 | if (currentMode != LedMode::QuickAcknowledgementFlash)
266 | {
267 | Serial.printf("=== Led: setting last mode to %d\n", currentMode);
268 | lastMode = currentMode;
269 | }
270 |
271 | currentMode = mode;
272 | // Reset animation state when mode changes
273 | // ignore Animations in non-zero index
274 | animations.StopAll();
275 |
276 | // strip.SetPixelColor(0, RgbColor(0));
277 | // strip.Show();
278 | lastFlashTime = 0;
279 | flashState = false;
280 | }
281 |
282 | void setupLed()
283 | {
284 | strip.Begin();
285 | strip.SetPixelColor(0, RgbColor(0));
286 | strip.Show();
287 |
288 | startLedTask();
289 | }
290 |
291 | void stopLed()
292 | {
293 | if (ledTaskHandle != NULL)
294 | {
295 | vTaskDelete(ledTaskHandle);
296 | ledTaskHandle = NULL;
297 | }
298 | }
299 |
300 | void startLedTask()
301 | {
302 | xTaskCreatePinnedToCore(
303 | ledTask, // Task function
304 | "LED Task", // Name
305 | LED_TASK_STACK_SIZE, // Stack size
306 | NULL, // Parameters
307 | LED_TASK_PRIORITY, // Priority
308 | &ledTaskHandle, // Task handle
309 | LED_CORE // Core ID
310 | );
311 | }
--------------------------------------------------------------------------------
/src/led.h:
--------------------------------------------------------------------------------
1 | #ifndef LED_H
2 | #define LED_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | enum class LedMode
10 | {
11 | Off,
12 | Splashscreen,
13 | QuickAcknowledgementFlash,
14 | ConfirmationFlash,
15 | TimerPaused,
16 | };
17 |
18 | void setupLed();
19 | void ledSetupEncoder(volatile int *encoderCount);
20 | void stopLed();
21 | void startLedTask();
22 | void setLedMode(LedMode mode);
23 |
24 | extern TaskHandle_t ledTaskHandle;
25 |
26 | #endif
--------------------------------------------------------------------------------
/src/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include "GxEPD2_display_selection_new_style.h"
9 | #include "timer.h"
10 | #include "led.h"
11 | #include "debug.h"
12 | #include "images.h"
13 | #include "checkbox.h"
14 | #include "splashscreen.h"
15 | #include "button.h"
16 | #include "icon_provider.h"
17 | #include "anniversary.h"
18 | #include "preferences_manager.h"
19 |
20 | #if STRINGS_TEST
21 | #include
22 | #endif
23 |
24 | #define MINUTE 60 * 1000
25 |
26 | #define ENCODER_CLK 32
27 | #define ENCODER_DT 21
28 |
29 | #define ENCODER_STABILITY_DELAY 50 // ms to wait for stable position
30 | #define ENCODER_LOCK_TIME_AFTER_BUTTON 200 // ms to ignore encoder changes after button press
31 |
32 | ESP32Encoder encoder;
33 | volatile unsigned long lastEncoderUpdate = 0;
34 | const unsigned long encoderDebounceTime = 10; // ms
35 | volatile int debouncedCount = 0;
36 | volatile int lastCount = 0;
37 | volatile int tempCount = 0;
38 | volatile unsigned long tempCountTime = 0;
39 | volatile bool positionStable = true;
40 | volatile bool buttonPressed = false;
41 | volatile unsigned long lastButtonPressTime = 0;
42 |
43 | #if CHECKBOX_TEST
44 | Checkbox checkbox(&icon_lpehacker, "A test", "test");
45 | Checkbox checkbox2(&icon_lpetantrum, "Another test", "test2");
46 | #endif
47 |
48 | void IRAM_ATTR checkPosition(void *arg)
49 | {
50 | unsigned long currentTime = millis();
51 | int currentCount = encoder.getCount();
52 |
53 | // If there was a recent button press, ignore encoder changes
54 | if (currentTime - Button::instance->lastPressTime < ENCODER_STABILITY_DELAY)
55 | {
56 | return;
57 | }
58 |
59 | if (currentCount % 2 == 0 && currentCount != debouncedCount && (currentTime - lastEncoderUpdate >= encoderDebounceTime))
60 | {
61 | debouncedCount = (currentCount % 2 == 0 ? currentCount : currentCount - 1) / 2;
62 | lastEncoderUpdate = currentTime;
63 | }
64 | }
65 |
66 | void setupEncoder()
67 | {
68 | pinMode(ENCODER_CLK, INPUT);
69 | pinMode(ENCODER_DT, INPUT);
70 |
71 | encoder = ESP32Encoder(true, checkPosition);
72 | ESP32Encoder::useInternalWeakPullResistors = puType::none;
73 |
74 | encoder.attachHalfQuad(ENCODER_DT, ENCODER_CLK);
75 | encoder.setFilter(1023);
76 |
77 | encoder.clearCount();
78 | debouncedCount = 0;
79 | lastCount = 0;
80 | }
81 |
82 | Timer timer(display);
83 |
84 | void setup()
85 | {
86 | pinMode(2, OUTPUT);
87 | digitalWrite(2, HIGH);
88 |
89 | Button::instance = new Button(ENCODER_SW);
90 | ledSetupEncoder(&debouncedCount);
91 |
92 | setupLed();
93 |
94 | Serial.begin(115200);
95 |
96 | // Initialize the encoder
97 | setupEncoder();
98 |
99 | // Initialize preferences once
100 | initPreferences();
101 |
102 | // Initialize the display
103 | display.init(115200, true, 2, false);
104 | display.setRotation(0);
105 |
106 | // Load LPE mode setting from checkbox
107 | IconProvider::getInstance()->setLpeMode(pref_getCheckbox("lpe", true));
108 |
109 | #ifdef DEBUG
110 | #if ICON_SCALING_TEST
111 | const uint16_t padding = 20;
112 |
113 | const auto sizes = {48, 64, 128, 192};
114 | uint16_t xOffset = padding;
115 | for (const auto size : sizes)
116 | {
117 | const auto icon = icon_lpesip.scaled(size);
118 | const uint16_t yOffset = padding + 192 - size;
119 | // draw each icon size side by side left to right
120 | display.drawBitmap(xOffset, padding, icon.data, size, size, GxEPD_BLACK);
121 | drawCenteredText(display, String(size).c_str(), xOffset + size / 2, size + 2 * padding, &SUB_FONT, GxEPD_BLACK);
122 |
123 | drawCenteredText(display, "#autoscaling", display.width() / 2, display.height() / 2 + 48, &MAIN_FONT, GxEPD_BLACK);
124 |
125 | xOffset += size + padding;
126 | }
127 |
128 | display.display();
129 |
130 | while (true)
131 | ;
132 | #endif
133 |
134 | #if PATTERN_TEST
135 |
136 | // draw each pattern in a rounded area on a grid within display bounds
137 | const uint16_t padding = 10;
138 | const uint16_t w = display.width() / patterns.size() - 2 * padding;
139 | for (uint16_t i = 0; i < patterns.size(); i++)
140 | {
141 | drawPatternInRoundedArea(display, i * display.width() / patterns.size() + padding, padding, w, display.height() - 2 * padding, 10, Pattern(i));
142 |
143 | char buffer[2];
144 | sprintf(buffer, "%d", i);
145 | drawCenteredText(display, buffer, i * display.width() / patterns.size() + display.width() / patterns.size() / 2, display.height() - padding - 10, &SUB_FONT, GxEPD_BLACK);
146 | }
147 |
148 | display.display();
149 |
150 | while (true)
151 | ;
152 |
153 | #endif
154 |
155 | #if IMAGE_CYCLE_TEST
156 | std::vector images = {
157 | // image_bg_cat,
158 | image_bg_stonks,
159 | image_bg_pablo,
160 | image_bg_what_a_week,
161 | };
162 |
163 | display.fillScreen(GxEPD_WHITE);
164 | display.display();
165 |
166 | for (const auto image : images)
167 | {
168 |
169 | display.fillScreen(GxEPD_WHITE);
170 | display.drawBitmap(0, 0, image, display.width(), display.height(), GxEPD_BLACK);
171 | display.display(false);
172 | display.drawBitmap(0, 0, image, display.width(), display.height(), GxEPD_BLACK);
173 | display.display(true);
174 | display.drawBitmap(0, 0, image, display.width(), display.height(), GxEPD_BLACK);
175 | display.display(true);
176 |
177 | delay(5000);
178 | }
179 | #endif
180 |
181 | #if CHECKBOX_TEST
182 | display.fillScreen(GxEPD_WHITE);
183 |
184 | Checkbox *selected = &checkbox;
185 |
186 | checkbox.load();
187 | checkbox2.load();
188 |
189 | checkbox.draw(display, 0, 0, display.width(), 96, selected == &checkbox);
190 | checkbox2.draw(display, 0, 96 + 8, display.width(), 96, selected == &checkbox2);
191 | display.display();
192 | while (true)
193 | {
194 | if (!digitalRead(ENCODER_SW))
195 | {
196 | display.fillScreen(GxEPD_WHITE);
197 |
198 | selected->toggle();
199 | selected->save();
200 |
201 | checkbox.draw(display, 0, 0, display.width(), 96, selected == &checkbox);
202 | checkbox2.draw(display, 0, 96 + 8, display.width(), 96, selected == &checkbox2);
203 | display.display();
204 | delay(500);
205 | }
206 |
207 | if (debouncedCount != lastCount)
208 | {
209 | display.fillScreen(GxEPD_WHITE);
210 |
211 | selected->draw(display, 0, selected == &checkbox ? 0 : 96 + 8, display.width(), 96, false);
212 | selected = selected == &checkbox ? &checkbox2 : &checkbox;
213 | selected->draw(display, 0, selected == &checkbox ? 0 : 96 + 8, display.width(), 96, true);
214 |
215 | display.display();
216 | }
217 |
218 | lastCount = debouncedCount;
219 | }
220 | #endif
221 |
222 | #endif
223 |
224 | #if STRINGS_TEST
225 |
226 | // Space for message: 221,330 until 649,409
227 | const uint16_t messageMinX = 221;
228 | const uint16_t messageMaxX = 649;
229 | const uint16_t messageMinY = 330;
230 | const uint16_t messageMaxY = 409;
231 | const uint16_t messageW = messageMaxX - messageMinX;
232 | const uint16_t messageH = messageMaxY - messageMinY;
233 |
234 | Serial.println("--- Messages ---");
235 |
236 | for (auto msg : messageCache.getMessages())
237 | {
238 | // Split message by lines and print individually
239 | std::string messageStr(msg);
240 | std::istringstream iss(messageStr);
241 | std::string line;
242 | int lineIndex = 0;
243 | while (std::getline(iss, line, '\n'))
244 | {
245 | if (lineIndex >= 3)
246 | {
247 | Serial.printf("ERR: Too many lines in message \"%s\"\n", msg);
248 | break;
249 | }
250 |
251 | int yPos = messageMinY + lineIndex * 18 + 18; // + 18 because of the first line
252 | Bounds b = drawText(display, line.c_str(), messageMinX, yPos, &SMALL_FONT, GxEPD_BLACK);
253 | ++lineIndex;
254 |
255 | if (b.w > messageW || b.y + b.h > messageMaxY)
256 | {
257 | auto wOver = b.w - messageW;
258 | auto hOver = b.y + b.h - messageMaxY;
259 |
260 | Serial.printf("ERR: Line too long: \"%s\" {x: %d, y: %d, w: %d (+%d), h: %d (+%d)}\n", line.c_str(), b.x, b.y, b.w, wOver > 0 ? wOver : 0, b.h, hOver > 0 ? hOver : 0);
261 | }
262 | else
263 | {
264 | Serial.printf("OK: \"%s\" {x: %d, y: %d, w: %d, h: %d}\n", line.c_str(), b.x, b.y, b.w, b.h);
265 | }
266 | }
267 | }
268 | #endif
269 |
270 | #if ANNIVERSARY_MODE
271 | if (!pref_getCheckbox("anniversary", false))
272 | {
273 | Anniversary anniversary(display);
274 |
275 | anniversary.loop();
276 |
277 | pref_putCheckbox("anniversary", true);
278 | }
279 | #endif
280 |
281 | setLedMode(LedMode::Splashscreen);
282 |
283 | SplashScreen splashScreen(display, timer);
284 | splashScreen.draw();
285 | splashScreen.loop(&debouncedCount);
286 | setLedMode(LedMode::Off);
287 |
288 | auto iconProvider = IconProvider::getInstance();
289 |
290 | timer.addPreset(iconProvider->getPresetIcon("Emails"), iconProvider->getTimerRunningBackgroundImage(), "Emails", 15 * MINUTE, 5 * MINUTE, 15 * MINUTE);
291 | timer.addPreset(iconProvider->getPresetIcon("Coding"), iconProvider->getTimerRunningBackgroundImage(), "Coding", 45 * MINUTE, 15 * MINUTE, 30 * MINUTE, 2);
292 | timer.addPreset(iconProvider->getPresetIcon("Focus"), iconProvider->getTimerRunningBackgroundImage(), "Focus", 25 * MINUTE, 5 * MINUTE, 20 * MINUTE);
293 |
294 | timer.selectPreset(1);
295 | }
296 |
297 | void loop()
298 | {
299 | timer.loop(&debouncedCount);
300 | lastCount = debouncedCount;
301 | }
302 |
--------------------------------------------------------------------------------
/src/menu.cpp:
--------------------------------------------------------------------------------
1 | #include "menu.h"
2 |
3 | Menu::Menu(DISPLAY_CLASS &display, MenuItem *items, int itemCount) : display(display) // Update constructor
4 | {
5 | this->items = items;
6 | this->itemCount = itemCount;
7 | this->selectedIndex = 0;
8 | }
9 |
10 | Menu::~Menu()
11 | {
12 | }
13 |
14 | MenuItem *Menu::getSelected()
15 | {
16 | return &items[selectedIndex];
17 | }
18 |
19 | MenuItem *Menu::getItems()
20 | {
21 | return items;
22 | }
23 |
24 | int Menu::getSelectedIndex()
25 | {
26 | return selectedIndex;
27 | }
28 |
29 | int Menu::getItemCount()
30 | {
31 | return itemCount;
32 | }
33 |
34 | void Menu::setSelectedIndex(int index)
35 | {
36 | selectedIndex = index;
37 | }
38 |
39 | void Menu::setEncoderCount(int encoderCount)
40 | {
41 | lastEncoderCount = encoderCount;
42 | }
43 |
44 | void Menu::next()
45 | {
46 | selectedIndex = (selectedIndex + 1) % itemCount;
47 | }
48 |
49 | void Menu::previous()
50 | {
51 | selectedIndex = (selectedIndex - 1 + itemCount) % itemCount;
52 | }
53 |
54 | bool Menu::loop(volatile int *encoderCount)
55 | {
56 | if (*encoderCount != lastEncoderCount)
57 | {
58 | if (*encoderCount < lastEncoderCount)
59 | {
60 | previous();
61 | }
62 | else
63 | {
64 | next();
65 | }
66 |
67 | lastEncoderCount = *encoderCount;
68 | return true;
69 | }
70 |
71 | return false;
72 | }
73 |
74 | MenuItem::MenuItem(const char *text, Icon *icon) : text(text), icon(icon)
75 | {
76 | }
77 |
78 | MenuItem::~MenuItem()
79 | {
80 | }
81 |
82 | const char *MenuItem::getText()
83 | {
84 | return text;
85 | }
86 |
87 | void MenuItem::setText(const char *text)
88 | {
89 | this->text = text;
90 | }
91 |
92 | Icon *MenuItem::getIcon()
93 | {
94 | return icon;
95 | }
96 |
--------------------------------------------------------------------------------
/src/menu.h:
--------------------------------------------------------------------------------
1 | #ifndef MENU_H
2 | #define MENU_H
3 |
4 | #include "defs.h"
5 | #include
6 | #include
7 | #include
8 | #include "icon.h"
9 | #include "debug.h"
10 |
11 | enum class DrawStyle
12 | {
13 | Vertical,
14 | Horizontal
15 | };
16 |
17 | class MenuItem
18 | {
19 | private:
20 | const char *text;
21 | Icon *icon;
22 |
23 | public:
24 | MenuItem(const char *text, Icon *icon = nullptr);
25 | ~MenuItem();
26 |
27 | const char *getText();
28 | void setText(const char *text);
29 | Icon *getIcon();
30 | };
31 |
32 | class Menu
33 | {
34 | private:
35 | DISPLAY_CLASS &display; // Update display type
36 | MenuItem *items;
37 | int itemCount;
38 | int selectedIndex;
39 |
40 | int lastEncoderCount = 0;
41 |
42 | public:
43 | Menu(DISPLAY_CLASS &display, MenuItem *items, int itemCount); // Update constructor
44 | ~Menu();
45 |
46 | MenuItem *getSelected();
47 | MenuItem *getItems();
48 | int getSelectedIndex();
49 | int getItemCount();
50 |
51 | void setSelectedIndex(int index);
52 | void setEncoderCount(int encoderCount);
53 |
54 | void next();
55 | void previous();
56 |
57 | bool loop(volatile int *encoderCount);
58 | };
59 |
60 | #endif // MENU_H
61 |
--------------------------------------------------------------------------------
/src/preferences_manager.cpp:
--------------------------------------------------------------------------------
1 | #include "preferences_manager.h"
2 |
3 | Preferences preferences;
4 |
5 | void initPreferences()
6 | {
7 | // preferences.begin(PREFS_NAMESPACE, false);
8 | }
9 |
10 | bool pref_getCheckbox(const char *key, bool defaultValue)
11 | {
12 | preferences.begin(PREFS_NAMESPACE, true);
13 | char prefKey[32];
14 | snprintf(prefKey, sizeof(prefKey), "%s%s", PREF_CHECKBOX, key);
15 |
16 | auto v = preferences.getBool(prefKey, defaultValue);
17 |
18 | preferences.end();
19 |
20 | return v;
21 | }
22 |
23 | void pref_putCheckbox(const char *key, bool value)
24 | {
25 | preferences.begin(PREFS_NAMESPACE, false);
26 | char prefKey[32];
27 | snprintf(prefKey, sizeof(prefKey), "%s%s", PREF_CHECKBOX, key);
28 | preferences.putBool(prefKey, value);
29 | preferences.end();
30 | }
31 |
32 | unsigned int pref_getStatistic(const char *key, unsigned int defaultValue)
33 | {
34 | preferences.begin(PREFS_NAMESPACE, true);
35 | char prefKey[32];
36 | snprintf(prefKey, sizeof(prefKey), "%s%s", PREF_STATISTICS, key);
37 |
38 | auto v = preferences.getUInt(prefKey, defaultValue);
39 |
40 | preferences.end();
41 | return v;
42 | }
43 |
44 | void pref_putStatistic(const char *key, unsigned int value)
45 | {
46 | preferences.begin(PREFS_NAMESPACE, false);
47 | char prefKey[32];
48 | snprintf(prefKey, sizeof(prefKey), "%s%s", PREF_STATISTICS, key);
49 | preferences.putUInt(prefKey, value);
50 | preferences.end();
51 | }
52 |
53 | unsigned long pref_getStatistic(const char *key, unsigned long defaultValue)
54 | {
55 | preferences.begin(PREFS_NAMESPACE, true);
56 | char prefKey[32];
57 | snprintf(prefKey, sizeof(prefKey), "%s%s", PREF_STATISTICS, key);
58 |
59 | auto v = preferences.getULong(prefKey, defaultValue);
60 |
61 | preferences.end();
62 | return v;
63 | }
64 |
65 | void pref_putStatistic(const char *key, unsigned long value)
66 | {
67 | preferences.begin(PREFS_NAMESPACE, false);
68 | char prefKey[32];
69 | snprintf(prefKey, sizeof(prefKey), "%s%s", PREF_STATISTICS, key);
70 | preferences.putULong(prefKey, value);
71 | preferences.end();
72 | }
--------------------------------------------------------------------------------
/src/preferences_manager.h:
--------------------------------------------------------------------------------
1 | #ifndef PREFERENCES_MANAGER_H
2 | #define PREFERENCES_MANAGER_H
3 |
4 | #include
5 |
6 | // Global preferences namespace
7 | #define PREFS_NAMESPACE "pomodoro"
8 |
9 | // Preferences keys prefixes for different parts of the application
10 | #define PREF_CHECKBOX "cb."
11 | #define PREF_STATISTICS "stats."
12 | #define PREF_ANNIVERSARY "anniv."
13 |
14 | extern Preferences preferences;
15 |
16 | void initPreferences();
17 |
18 | bool pref_getCheckbox(const char *key, bool defaultValue);
19 | void pref_putCheckbox(const char *key, bool value);
20 |
21 | unsigned int pref_getStatistic(const char *key, unsigned int defaultValue);
22 | void pref_putStatistic(const char *key, unsigned int value);
23 |
24 | unsigned long pref_getStatistic(const char *key, unsigned long defaultValue);
25 | void pref_putStatistic(const char *key, unsigned long value);
26 |
27 | #endif
--------------------------------------------------------------------------------
/src/splashscreen.cpp:
--------------------------------------------------------------------------------
1 | #include "splashscreen.h"
2 | #include "button.h"
3 | #include "icon_provider.h"
4 |
5 | SplashScreen::SplashScreen(DISPLAY_CLASS &display, Timer &timer) : display(display), timer(timer)
6 | {
7 | for (int i = 0; i < checkboxes.size(); i++)
8 | {
9 | checkboxes[i].load();
10 | }
11 |
12 | selectedSettingsIndex = 0;
13 | selectedCheckbox = &checkboxes[selectedSettingsIndex];
14 | }
15 |
16 | SplashScreen::~SplashScreen()
17 | {
18 | }
19 |
20 | void SplashScreen::draw()
21 | {
22 | display.fillScreen(GxEPD_WHITE);
23 |
24 | display.drawBitmap(0, 0, image_bg_splash, display.width(), display.height(), GxEPD_BLACK);
25 |
26 | const uint16_t padding = 20;
27 | const uint16_t menuY = padding;
28 | const uint16_t paddingBetweenBoxes = 20;
29 | const uint16_t menuItemCount = buttons.getItemCount();
30 | const uint16_t menuItemWidth = (display.width() - paddingBetweenBoxes * (menuItemCount - 1) - padding * 2) / menuItemCount;
31 | const uint16_t menuHeight = 48;
32 |
33 | for (int i = 0; i < menuItemCount; i++)
34 | {
35 | const bool selected = i == buttons.getSelectedIndex();
36 | auto item = buttons.getItems()[i];
37 | const uint16_t xOffset = padding + i * (menuItemWidth + paddingBetweenBoxes);
38 |
39 | if (selected)
40 | {
41 | display.fillRoundRect(xOffset, menuY, menuItemWidth, menuHeight, 10, GxEPD_WHITE);
42 | drawPatternInRoundedArea(display, xOffset, menuY, menuItemWidth, menuHeight, 10, Pattern::SparseDots);
43 | }
44 | else
45 | {
46 | display.fillRoundRect(xOffset, menuY, menuItemWidth, menuHeight, 10, GxEPD_WHITE);
47 | }
48 | display.drawRoundRect(xOffset, menuY, menuItemWidth, menuHeight, 10, GxEPD_BLACK);
49 | drawCenteredText(display, item.getText(), xOffset + menuItemWidth / 2, menuY + menuHeight / 2, &SUB_FONT, GxEPD_BLACK);
50 | }
51 |
52 | display.display(true);
53 | }
54 |
55 | void SplashScreen::loop(volatile int *encoderCount)
56 | {
57 | while (true)
58 | {
59 | if (
60 | buttons.loop(encoderCount))
61 | {
62 | // Redraw
63 | draw();
64 | lastEncoderCount = *encoderCount;
65 | }
66 |
67 | if (!Button::instance)
68 | {
69 | Serial.println("Button instance is null");
70 | continue;
71 | }
72 |
73 | if (Button::instance->checkAndClearButtonPress())
74 | {
75 | if (buttons.getSelectedIndex() == 0)
76 | {
77 | timer.start();
78 | return;
79 | }
80 | else if (buttons.getSelectedIndex() == 1)
81 | {
82 | display.fillScreen(GxEPD_WHITE);
83 | drawSettings();
84 | loopSettings(encoderCount);
85 | }
86 | return;
87 | }
88 | }
89 | }
90 |
91 | void SplashScreen::drawSettings()
92 | {
93 | display.fillScreen(GxEPD_WHITE);
94 |
95 | for (int i = 0; i < checkboxes.size(); i++)
96 | {
97 | auto checkbox = checkboxes[i];
98 | const bool selected = checkbox.getName() == selectedCheckbox->getName();
99 |
100 | checkbox.draw(display, 16, i * 96 + 16 + i * 32, display.width() - 32, 96, selected);
101 | }
102 |
103 | display.display(true);
104 | }
105 |
106 | void SplashScreen::loopSettings(volatile int *encoderCount)
107 | {
108 | while (true)
109 | {
110 | if (lastEncoderCount != *encoderCount)
111 | {
112 | // select next or previous checkbox
113 | if (*encoderCount < lastEncoderCount)
114 | {
115 | selectedSettingsIndex = (selectedSettingsIndex - 1 + checkboxes.size()) % checkboxes.size();
116 | selectedCheckbox = &checkboxes[selectedSettingsIndex];
117 | }
118 | else
119 | {
120 | selectedSettingsIndex = (selectedSettingsIndex + 1) % checkboxes.size();
121 | selectedCheckbox = &checkboxes[selectedSettingsIndex];
122 | }
123 |
124 | // Redraw
125 | drawSettings();
126 |
127 | lastEncoderCount = *encoderCount;
128 | }
129 |
130 | if (Button::instance->checkAndClearButtonPress())
131 | {
132 | if (selectedCheckbox->getName() == "Reset Device")
133 | {
134 | Preferences preferences;
135 | preferences.begin("pomodoro", false);
136 | preferences.clear();
137 | preferences.end();
138 |
139 | display.fillScreen(GxEPD_BLACK);
140 | display.display();
141 |
142 | delay(5000);
143 | ESP.restart();
144 | }
145 |
146 | // save settings
147 | selectedCheckbox->toggle();
148 | selectedCheckbox->save();
149 |
150 | // Redraw
151 | drawSettings();
152 |
153 | delay(1000);
154 |
155 | ESP.restart();
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/src/splashscreen.h:
--------------------------------------------------------------------------------
1 | #ifndef SPLASHSCREEN_H
2 | #define SPLASHSCREEN_H
3 |
4 | #include
5 | #include
6 | #include
7 | #include "gfx_utils.h"
8 | #include "icons.h"
9 | #include "icon.h"
10 | #include "timer.h"
11 | #include "checkbox.h"
12 | #include "debug.h"
13 | #include "images.h"
14 | #include
15 |
16 | class SplashScreen
17 | {
18 | private:
19 | DISPLAY_CLASS &display;
20 | Timer &timer;
21 |
22 | Menu buttons = Menu(display, new MenuItem[2]{MenuItem("Start"), MenuItem("Einstellungen")}, 2);
23 |
24 | std::vector checkboxes = {
25 | Checkbox(&icon_lpehacker, "LPE Modus", "lpe", true),
26 | Checkbox(&icon_lpenote, "Ablenkende Nachrichten", "msgs", true),
27 | Checkbox(nullptr, "Reset Device", "reset"),
28 | };
29 |
30 | Checkbox *selectedCheckbox;
31 | int16_t selectedSettingsIndex = 0;
32 | int16_t lastEncoderCount = 0;
33 |
34 | public:
35 | SplashScreen(DISPLAY_CLASS &display, Timer &timer);
36 | ~SplashScreen();
37 | void setLastEncoderCount(int16_t count);
38 | void draw();
39 | void loop(volatile int *encoderCount);
40 |
41 | void drawSettings();
42 | void loopSettings(volatile int *encoderCount);
43 | };
44 |
45 | #endif
--------------------------------------------------------------------------------
/src/states/timer_running.cpp:
--------------------------------------------------------------------------------
1 | #include "../timer.h"
2 | #include "../strings.h"
3 | #include
4 |
5 | void Timer::handleRunning(volatile int *encoderCount)
6 | {
7 | if (state == TimerState::Running)
8 | {
9 | elapsed = millis() - startTime - totalPausedTime;
10 | }
11 |
12 | if (state == TimerState::Running && millis() - lastMessageUpdate >= RUNNING_MESSAGE_REFRESH_INTERVAL)
13 | {
14 | lastMessageUpdate = millis();
15 | messageCache.clearCache(Messages::Preset_Email_Message);
16 | messageCache.clearCache(Messages::Preset_Coding_Message);
17 | messageCache.clearCache(Messages::Preset_Focus_Message);
18 |
19 | needsRedraw = true;
20 | }
21 |
22 | if (state == TimerState::Running && elapsed >= currentPreset->getDuration())
23 | {
24 |
25 | longestEarnedPauseInShortCycles = max(currentPreset->getLongPauseDuration(), longestEarnedPauseInShortCycles);
26 |
27 | setLedMode(LedMode::ConfirmationFlash);
28 | state = TimerState::WaitingConfirmStartOfBreak;
29 |
30 | this->confirmationMenu = new Menu(display, new MenuItem[1]{MenuItem(messageCache.getMessage(Messages::MenuItem_StartBreak))}, 1);
31 | this->confirmationMenu->setEncoderCount(*encoderCount); // Sync the encoder count
32 | needsFullRedraw = true;
33 | return;
34 | }
35 |
36 | // Only trigger redraw once per second to avoid unnecessary updates
37 | if (millis() - lastRedrawTime >= redrawInterval)
38 | {
39 | needsRedraw = true;
40 | }
41 |
42 | // Handle menu input
43 | if (topMenu)
44 | {
45 | if (Button::instance->checkAndClearButtonPress())
46 | {
47 | // Handle menu selection
48 | if (topMenu->getSelectedIndex() == 0)
49 | {
50 | if (state == TimerState::Running)
51 | {
52 | // Pause
53 | pause();
54 | topMenu->getSelected()->setText(messageCache.getMessage(Messages::MenuItem_Resume));
55 | }
56 | else
57 | {
58 | // Resume
59 | resume();
60 | topMenu->getSelected()->setText(messageCache.getMessage(Messages::MenuItem_Pause));
61 | }
62 |
63 | needsRedraw = true;
64 | }
65 | else if (topMenu->getSelectedIndex() == 1)
66 | {
67 | // Break now
68 | startBreak();
69 | }
70 | else if (topMenu->getSelectedIndex() == 2)
71 | {
72 | // Cancel
73 | incrementTotalTime(elapsed);
74 | minutesWorked += elapsed / 1000 / 60;
75 | stop();
76 | enterPresetSelection();
77 |
78 | needsFullRedraw = true;
79 | }
80 | }
81 |
82 | if (topMenu->loop(encoderCount))
83 | {
84 | menuNeedsRedraw = true;
85 | }
86 | }
87 | }
88 |
89 | void Timer::drawRunning()
90 | {
91 | display.fillScreen(GxEPD_WHITE);
92 |
93 | if (showSpeechBubble)
94 | {
95 | display.drawBitmap(0, 0, currentPreset->getBackground(), display.width(), display.height(), GxEPD_BLACK);
96 | }
97 |
98 | drawMenuBar();
99 |
100 | unsigned int remainingUnit = 0;
101 | char buffer[32]; // Increased buffer size to be safe
102 |
103 | const unsigned int remainingMillis = currentPreset->getDuration() - elapsed;
104 | const unsigned int seconds = remainingMillis / 1000;
105 | const unsigned int minutes = max(seconds / 60, 1u);
106 | uint16_t roundedSeconds = (seconds + 9) / 10 * 10;
107 |
108 | if (roundedSeconds >= 60)
109 | {
110 | sprintf(buffer, "%d %s", minutes, messageCache.getMessage(Messages::TimeFormat_Minutes));
111 | }
112 | else
113 | {
114 | if (redrawInterval != REDRAW_INTERVAL_FAST)
115 | {
116 | redrawInterval = REDRAW_INTERVAL_FAST;
117 | needsRedraw = true;
118 | }
119 |
120 | // Round to nearest 10 seconds when below 1 minute
121 | sprintf(buffer, "%d %s", roundedSeconds, messageCache.getMessage(Messages::TimeFormat_Seconds));
122 | }
123 |
124 | const uint16_t progressBarHeight = 32;
125 |
126 | Bounds boundsMin = getBounds(display, buffer, &LARGE_FONT);
127 |
128 | if (!showSpeechBubble)
129 | {
130 | // Draw text in the center
131 | drawText(display, buffer, display.width() / 2 - boundsMin.w / 2, display.height() / 2 + boundsMin.h / 2, &LARGE_FONT, GxEPD_BLACK);
132 | return;
133 | }
134 |
135 | drawText(display, buffer, display.width() / 2 - boundsMin.w / 2, display.height() / 3 + boundsMin.h / 2, &LARGE_FONT, GxEPD_BLACK);
136 |
137 | const unsigned int progress = (elapsed * 100) / currentPreset->getDuration();
138 | const uint16_t progressBarWidth = display.width() / 2;
139 |
140 | drawProgressBar(display, ProgressBarStyle::Bordered, display.width() / 2 - progressBarWidth / 2, display.height() / 3 + boundsMin.h / 2 + 16, progressBarWidth, progressBarHeight, progressBarHeight / 2, progress);
141 |
142 | drawDebugCrosshair(display, display.width() / 2, display.height() / 2, 48);
143 |
144 | // Space for message: 221,330 until 649,409
145 | const uint16_t messageMinX = 221;
146 | const uint16_t messageMaxX = 649;
147 | const uint16_t messageMinY = 330;
148 | const uint16_t messageMaxY = 409;
149 | const uint16_t messageW = messageMaxX - messageMinX;
150 | const uint16_t messageH = messageMaxY - messageMinY;
151 |
152 | drawDebugCrosshair(display, 221, 330);
153 |
154 | // Split message by lines and print individually
155 | std::string messageStr(getRunningMessage());
156 | std::istringstream iss(messageStr);
157 | std::string line;
158 | int lineIndex = 0;
159 | while (std::getline(iss, line, '\n'))
160 | {
161 | int yPos = messageMinY + lineIndex * 18 + 18 + lineIndex * 2; // + 18 because of the first line
162 | drawText(display, line.c_str(), messageMinX, yPos, &SMALL_FONT, GxEPD_BLACK);
163 | ++lineIndex;
164 | }
165 | }
166 |
167 | const char *Timer::getRunningMessage()
168 | {
169 | if (currentPreset == nullptr)
170 | {
171 | return "No preset selected";
172 | }
173 |
174 | if (currentPreset->getName() == nullptr)
175 | {
176 | return "No name";
177 | }
178 |
179 | if (strcmp(currentPreset->getName(), "Emails") == 0)
180 | {
181 | return messageCache.getMessage(Messages::Preset_Email_Message);
182 | }
183 | else if (strcmp(currentPreset->getName(), "Coding") == 0)
184 | {
185 | return messageCache.getMessage(Messages::Preset_Coding_Message);
186 | }
187 | else if (strcmp(currentPreset->getName(), "Focus") == 0)
188 | {
189 | return messageCache.getMessage(Messages::Preset_Focus_Message);
190 | }
191 |
192 | return "Unknown preset";
193 | }
--------------------------------------------------------------------------------
/src/states/timer_running_break.cpp:
--------------------------------------------------------------------------------
1 | #include "../timer.h"
2 | #include "../strings.h"
3 |
4 | void Timer::handleRunningBreak(volatile int *encoderCount)
5 | {
6 | if (state == TimerState::RunningBreak)
7 | {
8 | elapsed = millis() - startTime - totalPausedTime;
9 | }
10 |
11 | if (state == TimerState::RunningBreak && millis() - startTime >= currentBreakDuration)
12 | {
13 | setLedMode(LedMode::ConfirmationFlash);
14 | state = TimerState::WaitingConfirmEndOfBreak;
15 |
16 | this->confirmationMenu = new Menu(display, new MenuItem[2]{MenuItem(messageCache.getMessage(Messages::MenuItem_RestartTimer)), MenuItem(messageCache.getMessage(Messages::MenuItem_BackToPresets))}, 2);
17 | this->confirmationMenu->setEncoderCount(*encoderCount); // Sync encoder count
18 | needsFullRedraw = true;
19 | return;
20 | }
21 |
22 | // Handle menu input
23 | if (topMenu)
24 | {
25 | if (Button::instance->checkAndClearButtonPress())
26 | {
27 | // Handle menu selection
28 | if (topMenu->getSelectedIndex() == 0)
29 | {
30 | if (state == TimerState::RunningBreak)
31 | {
32 | // Pause
33 | pause();
34 | }
35 | else
36 | {
37 | // Resume
38 | resume();
39 | }
40 | }
41 | else if (topMenu->getSelectedIndex() == 1)
42 | {
43 | // Skip break
44 | incrementTotalBreakTime(elapsed);
45 | minutesOnBreak += elapsed / 1000 / 60;
46 | reset();
47 | enterPresetSelection();
48 | }
49 | else if (topMenu->getSelectedIndex() == 2)
50 | {
51 | // Cancel
52 | incrementTotalBreakTime(elapsed);
53 | minutesOnBreak += elapsed / 1000 / 60;
54 | stop();
55 | selectPreset(1);
56 | enterPresetSelection();
57 | }
58 |
59 | needsFullRedraw = true;
60 | }
61 |
62 | if (topMenu->loop(encoderCount))
63 | {
64 | menuNeedsRedraw = true;
65 | }
66 | }
67 |
68 | if (millis() - lastRedrawTime >= redrawInterval)
69 | {
70 | needsRedraw = true;
71 | }
72 | }
73 |
74 | String formatDuration(unsigned long minutes)
75 | {
76 | unsigned long hours = minutes / 60;
77 | minutes = minutes % 60;
78 |
79 | char buffer[32];
80 | if (hours > 0)
81 | {
82 | sprintf(buffer, "%luh %lum", hours, minutes);
83 | }
84 | else
85 | {
86 | sprintf(buffer, "%lum", minutes);
87 | }
88 |
89 | return String(buffer);
90 | }
91 |
92 | void Timer::drawRunningBreak()
93 | {
94 | display.fillScreen(GxEPD_WHITE);
95 |
96 | if (isLongBreak)
97 | {
98 | display.drawBitmap(0, 8 + 48 + 4 + 8, breakImage, display.width(), display.height() - 8 - 48 - 4 - 8 - 64 - 4, GxEPD_BLACK);
99 | }
100 | else
101 | {
102 | const uint16_t w = 420;
103 | const uint16_t h = 340;
104 | Bounds boxBounds = {display.width() / 2 - w / 2, display.height() - h - 64, w, h};
105 | const uint16_t innerPadding = 8;
106 | uint16_t yOffset = boxBounds.y + innerPadding;
107 | Bounds statistics = getBounds(display, messageCache.getMessage(Messages::Statistics), &MAIN_FONT);
108 |
109 | display.drawRoundRect(boxBounds.x, boxBounds.y, boxBounds.w, boxBounds.h, 10, GxEPD_BLACK);
110 |
111 | yOffset += statistics.h;
112 | drawText(display, "Statistik", boxBounds.x + innerPadding, yOffset, &MAIN_FONT, GxEPD_BLACK);
113 |
114 | yOffset += innerPadding;
115 | drawPattern(display, Pattern::SparseDots, boxBounds.x, boxBounds.y, boxBounds.w, yOffset - boxBounds.y);
116 | display.drawFastHLine(boxBounds.x, yOffset, boxBounds.w, GxEPD_BLACK);
117 |
118 | // fetch statistics
119 | unsigned int totalCycles;
120 | unsigned long totalTime, totalBreakTime;
121 | getStatistics(&totalCycles, &totalTime, &totalBreakTime);
122 |
123 | yOffset += 1 + innerPadding;
124 |
125 | Bounds textBounds;
126 |
127 | /// Current session
128 |
129 | textBounds = drawBottomAlignedText(display, messageCache.getMessage(Messages::Statistics_CurrentCycle), boxBounds.x + innerPadding, yOffset, &SUB_FONT, GxEPD_BLACK);
130 | Bounds boundsCurrentCycles = getBounds(display, String(cycles).c_str(), &SUB_FONT);
131 | drawBottomAlignedText(display, String(cycles).c_str(), boxBounds.x + boxBounds.w - innerPadding - boundsCurrentCycles.w, yOffset, &SUB_FONT, GxEPD_BLACK);
132 |
133 | yOffset += textBounds.h + innerPadding;
134 |
135 | textBounds = drawBottomAlignedText(display, messageCache.getMessage(Messages::Statistics_CurrentTime), boxBounds.x + innerPadding, yOffset, &SUB_FONT, GxEPD_BLACK);
136 | Bounds boundsCurrentTime = getBounds(display, formatDuration(minutesWorked).c_str(), &SUB_FONT);
137 | drawBottomAlignedText(display, formatDuration(minutesWorked).c_str(), boxBounds.x + boxBounds.w - innerPadding - boundsCurrentTime.w, yOffset, &SUB_FONT, GxEPD_BLACK);
138 |
139 | yOffset += textBounds.h + innerPadding;
140 |
141 | textBounds = drawBottomAlignedText(display, messageCache.getMessage(Messages::Statistics_CurrentBreakTime), boxBounds.x + innerPadding, yOffset, &SUB_FONT, GxEPD_BLACK);
142 | Bounds boundsCurrentBreakTime = getBounds(display, formatDuration(minutesOnBreak).c_str(), &SUB_FONT);
143 | drawBottomAlignedText(display, formatDuration(minutesOnBreak).c_str(), boxBounds.x + boxBounds.w - innerPadding - boundsCurrentBreakTime.w, yOffset, &SUB_FONT, GxEPD_BLACK);
144 |
145 | yOffset += textBounds.h + innerPadding;
146 |
147 | // Divider
148 |
149 | yOffset += 2 * innerPadding;
150 |
151 | textBounds = drawBottomAlignedText(display, "Gesamt", boxBounds.x + innerPadding, yOffset + innerPadding, &SUB_FONT, GxEPD_BLACK);
152 | drawPattern(display, Pattern::Dots, boxBounds.x, yOffset, boxBounds.w, textBounds.h + 2 * innerPadding);
153 |
154 | yOffset += textBounds.h + 2 * innerPadding + innerPadding;
155 |
156 | // All time stats
157 |
158 | textBounds = drawBottomAlignedText(display, messageCache.getMessage(Messages::Statistics_TotalCycles), boxBounds.x + innerPadding, yOffset, &SUB_FONT, GxEPD_BLACK);
159 |
160 | Bounds boundsTotalCycles = getBounds(display, String(totalCycles).c_str(), &SUB_FONT);
161 | drawBottomAlignedText(display, String(totalCycles).c_str(), boxBounds.x + boxBounds.w - innerPadding - boundsTotalCycles.w, yOffset, &SUB_FONT, GxEPD_BLACK);
162 |
163 | yOffset += textBounds.h + innerPadding;
164 |
165 | textBounds = drawBottomAlignedText(display, messageCache.getMessage(Messages::Statistics_TotalTime), boxBounds.x + innerPadding, yOffset, &SUB_FONT, GxEPD_BLACK);
166 |
167 | auto totalTimeStr = formatDuration(totalTime);
168 | Bounds boundsTotalTime = getBounds(display, String(totalTimeStr).c_str(), &SUB_FONT);
169 | drawBottomAlignedText(display, String(totalTimeStr).c_str(), boxBounds.x + boxBounds.w - innerPadding - boundsTotalTime.w, yOffset, &SUB_FONT, GxEPD_BLACK);
170 |
171 | yOffset += textBounds.h + innerPadding;
172 |
173 | textBounds = drawBottomAlignedText(display, messageCache.getMessage(Messages::Statistics_TotalBreakTime), boxBounds.x + innerPadding, yOffset, &SUB_FONT, GxEPD_BLACK);
174 |
175 | auto totalBreakTimeStr = formatDuration(totalBreakTime);
176 | Bounds boundsTotalBreakTime = getBounds(display, String(totalBreakTimeStr).c_str(), &SUB_FONT);
177 | drawBottomAlignedText(display, String(totalBreakTimeStr).c_str(), boxBounds.x + boxBounds.w - innerPadding - boundsTotalBreakTime.w, yOffset, &SUB_FONT, GxEPD_BLACK);
178 |
179 | yOffset += textBounds.h + innerPadding;
180 | }
181 |
182 | drawMenuBar();
183 |
184 | unsigned int remainingUnit = 0;
185 | char buffer[64]; // Increased buffer size to be safe
186 |
187 | const unsigned int remainingMillis = currentBreakDuration - elapsed;
188 | const unsigned int seconds = remainingMillis / 1000;
189 | const unsigned int minutes = max(seconds / 60, 1u);
190 | uint16_t roundedSeconds = (seconds + 9) / 10 * 10;
191 |
192 | if (roundedSeconds >= 60)
193 | {
194 | sprintf(buffer, "%s - %d %s", messageCache.getMessage(isLongBreak ? Messages::Break_LongPauseText : Messages::Break_PauseText), minutes, messageCache.getMessage(Messages::TimeFormat_Minutes));
195 | }
196 | else
197 | {
198 | if (redrawInterval != REDRAW_INTERVAL_FAST)
199 | {
200 | redrawInterval = REDRAW_INTERVAL_FAST;
201 | needsRedraw = true;
202 | }
203 |
204 | // Round to nearest 10 seconds when below 1 minute
205 | sprintf(buffer, "%s - %d %s", messageCache.getMessage(isLongBreak ? Messages::Break_LongPauseText : Messages::Break_PauseText), roundedSeconds, messageCache.getMessage(Messages::TimeFormat_Seconds));
206 | }
207 |
208 | Bounds boundsMin = getBounds(display, buffer, &MAIN_FONT);
209 |
210 | // Draw text in the center
211 | drawText(display, buffer, display.width() / 2 - boundsMin.w / 2, display.height() - 64 / 2 + boundsMin.h / 2, &MAIN_FONT, GxEPD_BLACK);
212 | }
--------------------------------------------------------------------------------
/src/states/timer_selecting_preset.cpp:
--------------------------------------------------------------------------------
1 | #include "../timer.h"
2 |
3 | #define THRESHOLD 5
4 |
5 | void Timer::handleSelectingPreset(volatile int *encoderCount)
6 | {
7 | if (Button::instance->checkAndClearButtonPress())
8 | {
9 | Serial.printf("Timer::handleSelectingPreset: starting with preset %d\n", presetIndex);
10 | start();
11 | topMenu->setEncoderCount(*encoderCount);
12 | needsFullRedraw = true;
13 | }
14 |
15 | if (*encoderCount != lastEncoderCount)
16 | {
17 | int change = *encoderCount - lastEncoderCount;
18 | Serial.printf("Timer::handleSelectingPreset: encoder delta %d\n", change);
19 |
20 | if (change < 0)
21 | {
22 | previousPreset();
23 | }
24 | else
25 | {
26 | nextPreset();
27 | }
28 |
29 | Serial.printf("Timer::handleSelectingPreset: selected preset %d (%s)\n", presetIndex, currentPreset->getName());
30 | needsRedraw = true;
31 | lastEncoderCount = *encoderCount;
32 | }
33 | }
34 |
35 | void Timer::drawPresetSelection()
36 | {
37 | const unsigned int padding = 12;
38 | const unsigned int paddingBetweenBoxes = 18;
39 | const unsigned int paddingInsideBox = 10;
40 | const unsigned int totalWidth = display.width() - (padding * 2);
41 | const unsigned int boxWidth = (totalWidth + paddingBetweenBoxes) / presets.size() - paddingBetweenBoxes;
42 |
43 | unsigned int yOffset = padding;
44 |
45 | display.fillScreen(GxEPD_WHITE);
46 |
47 | for (int i = 0; i < presets.size(); i++)
48 | {
49 | auto &preset = presets[i];
50 | const unsigned int xOffset = padding + (i * (boxWidth + paddingBetweenBoxes));
51 | const unsigned int hCenter = xOffset + (boxWidth / 2);
52 |
53 | ScaledIcon icon = preset.getIcon()->scaled(192);
54 | const unsigned int iconSize = icon.size;
55 |
56 | #ifdef DEBUG
57 | display.drawLine(hCenter, 0, hCenter, display.height(), GxEPD_BLACK);
58 | #endif
59 |
60 | unsigned int yOffset = padding;
61 |
62 | display.drawRoundRect(xOffset, padding, boxWidth, display.height() - (padding * 2), 10, GxEPD_BLACK);
63 |
64 | if (currentPreset == &preset)
65 | {
66 | display.drawRoundRect(xOffset + 1, padding + 1, boxWidth - 2, display.height() - (padding * 2) - 2, 10, GxEPD_BLACK);
67 | drawPatternInRoundedArea(display, xOffset + 2, padding + 2, boxWidth - 4, display.height() - (padding * 2) - 4, 10, Pattern::VerySparseDots);
68 | }
69 |
70 | yOffset += paddingInsideBox;
71 | display.drawBitmap(hCenter - iconSize / 2, yOffset, icon.data, iconSize, iconSize, GxEPD_BLACK);
72 |
73 | #ifdef DEBUG
74 | display.fillRect(hCenter - iconSize / 2, yOffset, iconSize, iconSize, GxEPD_BLACK);
75 | #endif
76 |
77 | yOffset += iconSize + paddingInsideBox + 48;
78 |
79 | const Bounds bounds = getBounds(display, preset.getName(), &MAIN_FONT);
80 | drawText(display, preset.getName(), hCenter - bounds.w / 2, yOffset, &MAIN_FONT, GxEPD_BLACK);
81 |
82 | yOffset += 24 + 48;
83 | const unsigned int minutes = preset.getDuration() / 1000 / 60;
84 |
85 | // put the text on the right side of the box, on top of the progress bar
86 | {
87 | char buffer[3];
88 | sprintf(buffer, "%d", minutes);
89 | Bounds bounds = getBounds(display, buffer, &SECONDARY_FONT);
90 | Bounds boundsMin = getBounds(display, "min", &SUB_FONT);
91 | const int16_t paddingBetweenText = 4;
92 | const int16_t endOfBoxContent = xOffset + boxWidth - paddingInsideBox;
93 |
94 | yOffset += 24;
95 |
96 | drawText(display, buffer, endOfBoxContent - bounds.w - paddingBetweenText - boundsMin.w, yOffset, &SECONDARY_FONT, GxEPD_BLACK);
97 | drawText(display, "min", endOfBoxContent - boundsMin.w, yOffset, &SUB_FONT, GxEPD_BLACK);
98 |
99 | yOffset += (max(bounds.h, boundsMin.h) / 2) + 4;
100 |
101 | // display.drawFastHLine(xOffset + paddingInsideBox, yOffset, boxWidth - paddingBetweenBoxes - paddingInsideBox * 2, GxEPD_BLACK);
102 | drawPattern(display, Pattern::Dots, xOffset + paddingInsideBox + 4, yOffset, boxWidth - paddingBetweenBoxes - paddingInsideBox * 2 - 8, 2);
103 |
104 | yOffset += 8;
105 |
106 | // draw the pause duration
107 | const unsigned int pauseMinutes = preset.getPauseDuration() / 1000 / 60;
108 | char pauseBuffer[3];
109 | sprintf(pauseBuffer, "%d", pauseMinutes);
110 | Bounds pauseBounds = getBounds(display, pauseBuffer, &SECONDARY_FONT);
111 | Bounds pauseBoundsMin = getBounds(display, "min", &SUB_FONT);
112 |
113 | const auto breakIcon = icon_coffee.scaled(48);
114 | const unsigned int pauseIconSize = breakIcon.size;
115 | const unsigned int pauseIconX = xOffset + paddingInsideBox;
116 |
117 | display.drawBitmap(pauseIconX, yOffset, breakIcon.data, pauseIconSize, pauseIconSize, GxEPD_BLACK);
118 |
119 | drawText(display, pauseBuffer, endOfBoxContent - pauseBounds.w - paddingBetweenText - pauseBoundsMin.w, yOffset + pauseIconSize - 8, &SECONDARY_FONT, GxEPD_BLACK);
120 | drawText(display, "min", endOfBoxContent - pauseBoundsMin.w, yOffset + pauseIconSize - 8, &SUB_FONT, GxEPD_BLACK);
121 | }
122 | }
123 | }
--------------------------------------------------------------------------------
/src/states/timer_waiting_for_confirmation.cpp:
--------------------------------------------------------------------------------
1 | #include "../timer.h"
2 |
3 | void Timer::handleWaitingForConfirmation(volatile int *encoderCount)
4 | {
5 | elapsed = millis() - startTime - totalPausedTime;
6 |
7 | const int encoderDelta = *encoderCount - lastEncoderCount;
8 | if (encoderDelta != 0)
9 | {
10 | if (encoderDelta > 0)
11 | {
12 | confirmationMenu->next();
13 | }
14 | else
15 | {
16 | confirmationMenu->previous();
17 | }
18 | needsRedraw = true;
19 | lastEncoderCount = *encoderCount;
20 | }
21 |
22 | if (Button::instance->checkAndClearButtonPress())
23 | {
24 | if (state == TimerState::WaitingConfirmStartOfBreak)
25 | {
26 | Serial.println("Timer::handleWaitingForConfirmation: confirmed start of break");
27 | messageCache.clearCache(Messages::TimerWaitingForConfirmationStartOfBreak_Header);
28 | startBreak();
29 | needsFullRedraw = true;
30 | }
31 | else
32 | {
33 | Serial.println("Timer::handleWaitingForConfirmation: handling end of break");
34 | messageCache.clearCache(Messages::TimerWaitingForConfirmationEndOfBreak_Header);
35 |
36 | incrementTotalBreakTime(elapsed);
37 | minutesOnBreak += elapsed / 1000 / 60;
38 |
39 | // Handle menu selection
40 | if (confirmationMenu->getSelectedIndex() == 0)
41 | {
42 | // Restart timer
43 | start();
44 | }
45 | else
46 | {
47 | // Back to presets
48 | reset();
49 | enterPresetSelection();
50 | }
51 | needsFullRedraw = true;
52 | }
53 | }
54 |
55 | if (millis() - lastRedrawTime >= redrawInterval)
56 | {
57 | flashingIcon = !flashingIcon;
58 | needsRedraw = true;
59 | }
60 | }
61 |
62 | void Timer::drawWaitingForConfirmation()
63 | {
64 | // const auto foreground = flashingIcon ? GxEPD_BLACK : GxEPD_WHITE;
65 | // const auto background = flashingIcon ? GxEPD_WHITE : GxEPD_BLACK;
66 | const auto foreground = GxEPD_BLACK;
67 | const auto background = GxEPD_WHITE;
68 |
69 | display.fillScreen(background);
70 |
71 | ScaledIcon icon = icon_lpetantrum.scaled(128);
72 |
73 | const char *text = state == TimerState::WaitingConfirmStartOfBreak ? messageCache.getMessage(Messages::TimerWaitingForConfirmationStartOfBreak_Header) : messageCache.getMessage(Messages::TimerWaitingForConfirmationEndOfBreak_Header);
74 | Bounds textBounds = getBounds(display, text, &SEMI_LARGE_FONT);
75 |
76 | const int16_t padding = 20;
77 | const auto boxWidth = padding + icon.size + padding + textBounds.w + padding;
78 | const auto boxHeight = icon.size + padding * 2;
79 | const auto boxX = display.width() / 2 - boxWidth / 2;
80 | const auto boxY = display.height() / 2 - boxHeight / 2;
81 |
82 | // Draw main box with icon and text
83 | display.drawRoundRect(boxX, boxY, boxWidth, boxHeight, 10, foreground);
84 |
85 | // Draw icon on the left
86 | display.drawBitmap(boxX + padding, boxY + padding, icon.data, icon.size, icon.size, foreground);
87 |
88 | // Draw main text vertically centered with icon
89 | drawText(display, text,
90 | boxX + padding + icon.size + padding,
91 | boxY + boxHeight / 2 + textBounds.h / 2,
92 | &SEMI_LARGE_FONT, foreground);
93 |
94 | // Draw menu options - either single item or two items based on state
95 | const uint16_t menuY = boxY + boxHeight + padding;
96 | const uint16_t paddingBetweenBoxes = 20;
97 | const uint16_t menuItemCount = (state == TimerState::WaitingConfirmStartOfBreak) ? 1 : 2;
98 | const uint16_t menuItemWidth = (state == TimerState::WaitingConfirmStartOfBreak) ? boxWidth : (boxWidth - paddingBetweenBoxes) / 2;
99 | const uint16_t menuHeight = 48;
100 |
101 | for (int i = 0; i < menuItemCount; i++)
102 | {
103 | const bool selected = i == confirmationMenu->getSelectedIndex();
104 | auto item = confirmationMenu->getItems()[i];
105 | const uint16_t xOffset = (state == TimerState::WaitingConfirmStartOfBreak) ? boxX : boxX + i * (menuItemWidth + paddingBetweenBoxes);
106 |
107 | if (selected)
108 | {
109 | display.fillRoundRect(xOffset, menuY, menuItemWidth, menuHeight, 10, GxEPD_WHITE);
110 | drawPatternInRoundedArea(display, xOffset, menuY, menuItemWidth, menuHeight, 10, Pattern::SparseDots);
111 | }
112 | else
113 | {
114 | display.fillRoundRect(xOffset, menuY, menuItemWidth, menuHeight, 10, GxEPD_WHITE);
115 | }
116 | display.drawRoundRect(xOffset, menuY, menuItemWidth, menuHeight, 10, GxEPD_BLACK);
117 | drawCenteredText(display, item.getText(), xOffset + menuItemWidth / 2, menuY + menuHeight / 2, &SUB_FONT, GxEPD_BLACK);
118 | }
119 | }
--------------------------------------------------------------------------------
/src/statistics.cpp:
--------------------------------------------------------------------------------
1 | #include "statistics.h"
2 | #include "preferences_manager.h"
3 |
4 | extern Preferences preferences;
5 |
6 | void incrementTotalCycles()
7 | {
8 | Serial.printf("Statistics: incrementTotalCycles (+1)\n");
9 | unsigned int totalCycles = pref_getStatistic("tcs", (unsigned int)0);
10 | totalCycles++;
11 | pref_putStatistic("tcs", totalCycles);
12 | }
13 |
14 | void incrementTotalTime(unsigned long ms)
15 | {
16 | Serial.printf("Statistics: incrementTotalTime (%lu [+%lu])\n", ms, ms / 1000 / 60);
17 | unsigned long totalTime = pref_getStatistic("tt", (unsigned long)0);
18 | totalTime += ms / 1000 / 60;
19 | pref_putStatistic("tt", totalTime);
20 | }
21 |
22 | void incrementTotalBreakTime(unsigned long ms)
23 | {
24 | Serial.printf("Statistics: incrementTotalBreakTime (%lu [+%lu])\n", ms, ms / 1000 / 60);
25 | unsigned long totalBreakTime = pref_getStatistic("tbt", (unsigned long)0);
26 | totalBreakTime += ms / 1000 / 60;
27 | pref_putStatistic("tbt", totalBreakTime);
28 | }
29 |
30 | void getStatistics(unsigned int *totalCycles, unsigned long *totalTime, unsigned long *totalBreakTime)
31 | {
32 | *totalCycles = pref_getStatistic("tcs", (unsigned int)0);
33 | *totalTime = pref_getStatistic("tt", (unsigned long)0);
34 | *totalBreakTime = pref_getStatistic("tbt", (unsigned long)0);
35 |
36 | Serial.printf("Statistics: getStatistics: totalCycles=%d, totalTime=%lu, totalBreakTime=%lu\n", *totalCycles, *totalTime, *totalBreakTime);
37 | }
38 |
39 | void resetStatistics()
40 | {
41 | Serial.println("Statistics: resetStatistics");
42 | pref_putStatistic("tcs", (unsigned int)0);
43 | pref_putStatistic("tt", (unsigned long)0);
44 | pref_putStatistic("tbt", (unsigned long)0);
45 | }
--------------------------------------------------------------------------------
/src/statistics.h:
--------------------------------------------------------------------------------
1 | #ifndef STATISTICS_H
2 | #define STATISTICS_H
3 |
4 | #include
5 | #include
6 |
7 | void incrementTotalCycles();
8 | void incrementTotalTime(unsigned long ms);
9 | void incrementTotalBreakTime(unsigned long ms);
10 |
11 | void getStatistics(unsigned int *totalCycles, unsigned long *totalTime, unsigned long *totalBreakTime);
12 | void resetStatistics();
13 |
14 | #endif
--------------------------------------------------------------------------------
/src/strings.cpp:
--------------------------------------------------------------------------------
1 | #include "strings.h"
2 |
3 | MessageCache messageCache;
4 |
5 | bool MessageCache::isLpeModeEnabled()
6 | {
7 | return pref_getCheckbox("lpe", false);
8 | }
9 |
10 | static const char *randomMessage(const std::vector &messages)
11 | {
12 | if (messages.empty())
13 | return "???";
14 | return messages[random(0, messages.size())];
15 | }
16 |
17 | static const std::vector genericPresetMessages = {
18 | "Okaaay, let's go!",
19 | "Heute schon auf Reddit gewesen?",
20 | "Bist du hydriert?",
21 | "Vielleicht eine kleine Kaffeepause?",
22 | "Langsam ist aber auch Zeit fuer\nFeierabend, oder nicht?",
23 | "Schoen hier, aber warst du heute\nschon auf Reddit?",
24 | "Du schaffst das (hoffen wir)",
25 | "Schauen wir mal was wird\n\n was wird",
26 | };
27 |
28 | static const std::vector chatGptFacts = {
29 | "Das Gehirn eines Elefanten\nenthaelt ueber 257 Mrd. Neuronen\nund zeigt starke Emotionen.",
30 |
31 | "Tintenfische besitzen drei Herzen\nund ein ausgekluegeltes Nervensystem,\ndas Probleme effizient loest.",
32 |
33 | "Voegel besitzen Magnetrezeptoren,\ndie ihnen helfen, das Erdmagnetfeld\nals Kompass zu nutzen.",
34 |
35 | "Bienen kommunizieren durch einen\npraezisen Schwaenzeltanz, mit dem sie\nFutterquellen uebermitteln.",
36 |
37 | "Schlangen spueren Waerme ueber\nspezialisierte Rezeptoren,\ndie sie zu geschickten Jaegern machen.",
38 |
39 | "Lungenfische atmen mit Kiemen\nund primitiven Lungen,\nueberbruecken so Wasser und Land.",
40 |
41 | "Chamaeleons aendern ihre Farbe\nnicht nur zur Tarnung,\nsondern auch zur Kommunikation.",
42 |
43 | "Kojoten sind sehr anpassungsfaehig\nund leben in verschieden Umgebungen,\nvon Wuesten bis zu Staedten.",
44 |
45 | "Kolibris sind die einzigen Voegel,\ndie rueckwaerts fliegen koennen,\ndank spezieller Flugmuskulatur.",
46 |
47 | "Giraffen haben ein komplexes\nBlutkreislaufsystem, das ihren Kopf\nmit sauerstoffreichem Blut versorgt.",
48 |
49 | "Haie besitzen Lorenzinische Ampullen,\ndie ihnen helfen, schwache\nelektrische Felder zu spueren.",
50 |
51 | "Ein Ameisenstaat kann das Gewicht\nmehrerer Elefanten tragen",
52 |
53 | "Faultiere bewegen sich sehr langsam,\ndamit Algen sich ansiedeln\nund sie gut getarnt bleiben.",
54 |
55 | "Geparden erreichen Top-\nGeschwindigkeiten, koennen\naber nur kurz laufen.",
56 |
57 | "Seesterne haben kein Gehirn,\nsondern ein verteiltes Nervensystem,\ndas in ihren Armen wirkt.",
58 |
59 | "Wale nutzen Infraschall,\nderen Toene sich ueber viele km\nim Ozean ausbreiten.",
60 |
61 | "Axolotl regenerieren Gliedmaßen,\nwas sie zu interessanten\nForschungsobjekten macht.",
62 |
63 | "Pinguine speichern Waerme,\nindem sie ihre Federn eng anlegen\nund den Waermeverlust minimieren.",
64 |
65 | "Die DNA vieler Tiere zeigt erstaunliche\nGemeinsamkeiten, die evolutionaere\nVerwandtschaften offenbaren.",
66 |
67 | "Schmetterlinge haben ausgekluegelte\nFarberkennungssysteme",
68 |
69 | "Menschen und Bananen\nteilen etwa 60% ihrer Gene,\nein Hinweis auf geteilte Wurzeln.",
70 | "Quallen haben uralte Gene,\ndie sich kaum veraendert haben.",
71 | "Koalas haben\nfingerabdruckartige Rillen,\naehnlich wie Menschen.",
72 | "Haie und Rochen\nteilen genetische Wurzeln\nund gehoeren zur Knorpelfisch-Familie.",
73 |
74 | "Every move on the board\nis a rebellion; no piece is sacred,\nand the king is just another target.",
75 |
76 | "In chess, chaos is art,\neach pawn a revolutionary spark,\nevery check a call to arms.",
77 |
78 | "Google en passant\n\n holy hell!",
79 |
80 | "Als die Dinosaurier existierten,\ngab es Vulkane, die auf dem Mond\nausbrachen.",
81 |
82 | "Die einzigen Buchstaben, die\nnicht im Periodensystem vorkommen,\nsind 'J' und 'Q'.",
83 |
84 | "Wenn ein Eisbaer und ein\nGrizzlybaer sich paaren,\nwird 'Pizzy Bear' genannt.",
85 |
86 | "Daniel Radcliffe war allergisch gegen\nseine Harry-Potter-Brille,\ndoch Harry Potter traegt sie.",
87 |
88 | "Im Englischen heißt es 'French Exit',\nwenn man ohne Abschied geht",
89 |
90 | "In Arizona kann das Faellen eines\nSaguaro-Kaktus als Verbrechen\ngeahndet werden.",
91 |
92 | "Der in Statuen gezeigte Buddha\nist nicht der wahre Buddha;\nder echte war mager durch Askese.",
93 |
94 | "Ein einzelner Spaghetti-Strang\nwird als 'Spaghetto' bezeichnet,\neine kuriose Tatsache.",
95 |
96 | "Princess Peach blieb still\nbis 1988, da Designer\nsie nicht beweglich machten.",
97 |
98 | "Der erste Film mit Soundtrack\nwar Schneewittchen\nund die sieben Zwerge.",
99 |
100 | "Reichst du mit deinen Autoschluesseln\nan deinen Kopf, erhoeht sich\ndie Reichweite der Fernbedienung.",
101 |
102 | "Fruchtaufkleber sind essbar,\nwie auch das Obst selbst;\nVor dem Verzehr waschen!",
103 |
104 | "Der Name des Riesenameisbers\nist Myrmecophaga Tridactyla,\nwas 'Ameisenessend mit 3 Fingern' heißt.",
105 |
106 | "Das Wort 'Astronaut' kommt\naus dem Griechischen 'astro' = Stern,\nund 'naut' heißt Seefahrer."
107 |
108 | };
109 |
110 | static const std::vector
111 | genericStartBreakMessages = {"Break time!", "Take a rest", "Time to relax", "Well done!"};
112 |
113 | extern const std::vector lpeStartBreakMessages = {};
114 | static const std::vector lpeEmailPresetMessages = {};
115 | static const std::vector lpeCodingPresetMessages = {};
116 |
117 | static const char *generateMessage(Messages message)
118 | {
119 | const char *result = "";
120 | std::vector messages;
121 | messages.insert(messages.end(), chatGptFacts.begin(), chatGptFacts.end());
122 | messages.insert(messages.end(), genericPresetMessages.begin(), genericPresetMessages.end());
123 |
124 | bool lpeModeEnabled = messageCache.isLpeModeEnabled();
125 |
126 | switch (message)
127 | {
128 | case Messages::TimerWaitingForConfirmationStartOfBreak_Header:
129 | result = "Geschafft!";
130 | break;
131 |
132 | case Messages::TimerWaitingForConfirmationEndOfBreak_Header:
133 | result = "Pause vorbei";
134 | break;
135 |
136 | case Messages::Break_PauseText:
137 | result = "Pause";
138 | break;
139 |
140 | case Messages::Break_LongPauseText:
141 | result = "Lange Pause";
142 | break;
143 |
144 | // Menu items
145 | case Messages::MenuItem_Pause:
146 | if (lpeModeEnabled)
147 | {
148 | result = "Kurz weg";
149 | }
150 | else
151 | {
152 | result = "Pause";
153 | }
154 | break;
155 | case Messages::MenuItem_Resume:
156 | if (lpeModeEnabled)
157 | {
158 | result = "Wieder da";
159 | }
160 | else
161 | {
162 | result = "Resume";
163 | }
164 | break;
165 | case Messages::MenuItem_BreakNow:
166 | if (lpeModeEnabled)
167 | {
168 | result = "GENUG!";
169 | }
170 | else
171 | {
172 | result = "Break now";
173 | }
174 | break;
175 | case Messages::MenuItem_SkipBreak:
176 | if (lpeModeEnabled)
177 | {
178 | result = "Skip break";
179 | }
180 | else
181 | {
182 | result = "Skip break";
183 | }
184 | break;
185 | case Messages::MenuItem_Cancel:
186 | result = "Stop";
187 | break;
188 | case Messages::MenuItem_BackToPresets:
189 | result = "Zur Auswahl";
190 | break;
191 | case Messages::MenuItem_RestartTimer:
192 | if (lpeModeEnabled)
193 | {
194 | result = randomMessage({"Noch mal!", "AGAIN!", "Here we go again...", "Do it agane"});
195 | }
196 | else
197 | {
198 | result = randomMessage({"Restart", "Let's go again", "One more time"});
199 | }
200 | break;
201 | case Messages::MenuItem_StartBreak:
202 | if (lpeModeEnabled)
203 | {
204 | result = randomMessage(lpeStartBreakMessages);
205 | }
206 | else
207 | {
208 | result = randomMessage(genericStartBreakMessages);
209 | }
210 | break;
211 |
212 | // Timer states
213 | case Messages::TimerState_Paused:
214 | result = "- PAUSED -";
215 | break;
216 |
217 | // Time formats
218 | case Messages::TimeFormat_Minutes:
219 | result = "min";
220 | break;
221 | case Messages::TimeFormat_Seconds:
222 | result = "sec";
223 | break;
224 |
225 | // Preset specific
226 | case Messages::Preset_Email_Message:
227 | if (lpeModeEnabled)
228 | {
229 | messages.insert(messages.end(), lpeEmailPresetMessages.begin(), lpeEmailPresetMessages.end());
230 | }
231 | result = randomMessage(messages);
232 | break;
233 |
234 | case Messages::Preset_Coding_Message:
235 | if (lpeModeEnabled)
236 | {
237 | messages.insert(messages.end(), lpeCodingPresetMessages.begin(), lpeCodingPresetMessages.end());
238 | }
239 | result = randomMessage(messages);
240 | break;
241 |
242 | case Messages::Preset_Focus_Message:
243 | result = randomMessage(messages);
244 | break;
245 |
246 | case Messages::Statistics:
247 | result = "Statistiken";
248 | break;
249 |
250 | case Messages::Statistics_CurrentCycle:
251 | result = "Aktueller Zyklus";
252 | break;
253 |
254 | case Messages::Statistics_CurrentTime:
255 | result = "Arbeitszeit";
256 | break;
257 |
258 | case Messages::Statistics_CurrentBreakTime:
259 | result = "Pausenzeit";
260 | break;
261 |
262 | case Messages::Statistics_TotalCycles:
263 | result = "Zyklen";
264 | break;
265 |
266 | case Messages::Statistics_TotalTime:
267 | result = "Arbeitszeit";
268 | break;
269 |
270 | case Messages::Statistics_TotalBreakTime:
271 | result = "Pausenzeit";
272 | break;
273 |
274 | default:
275 | result = "???";
276 | break;
277 | }
278 |
279 | return result;
280 | }
281 |
282 | const char *MessageCache::getMessage(Messages message)
283 | {
284 | auto it = cache.find(message);
285 | if (it != cache.end())
286 | {
287 | return it->second;
288 | }
289 |
290 | const char *result = generateMessage(message);
291 | cache[message] = result;
292 | return result;
293 | }
294 |
295 | void MessageCache::clearCache(Messages message)
296 | {
297 | cache.erase(message);
298 | }
299 |
300 | void MessageCache::clearAllCache()
301 | {
302 | cache.clear();
303 | }
304 |
305 | std::vector MessageCache::getMessages()
306 | {
307 | std::vector messages;
308 | // append vectors
309 | messages.insert(messages.end(), genericPresetMessages.begin(), genericPresetMessages.end());
310 | messages.insert(messages.end(), genericStartBreakMessages.begin(), genericStartBreakMessages.end());
311 | messages.insert(messages.end(), lpeStartBreakMessages.begin(), lpeStartBreakMessages.end());
312 | messages.insert(messages.end(), lpeEmailPresetMessages.begin(), lpeEmailPresetMessages.end());
313 | messages.insert(messages.end(), lpeCodingPresetMessages.begin(), lpeCodingPresetMessages.end());
314 | messages.insert(messages.end(), chatGptFacts.begin(), chatGptFacts.end());
315 |
316 | return messages;
317 | }
318 |
--------------------------------------------------------------------------------
/src/strings.h:
--------------------------------------------------------------------------------
1 | #ifndef STRINGS_H
2 | #define STRINGS_H
3 |
4 | #include
5 | #include // for float_t, double_t
6 | #include // for String
7 | #include
8 | #include "preferences_manager.h"
9 | #include
10 | #include