├── .gitignore
├── .DS_Store
├── images
├── 320x240
│ ├── Imola.png
│ ├── 19 - USA.png
│ ├── 1 - Bahrain.png
│ ├── 14 - Dutch.png
│ ├── 15 - Monza.png
│ ├── 17 - Japan.png
│ ├── 18 - Qatar.png
│ ├── 20 - Mexico.png
│ ├── 21 - Brazil.png
│ ├── 22 - Vegas.png
│ ├── 5 - Miami.png
│ ├── 7- Monaco.png
│ ├── 8 - Spain.png
│ ├── 9 - Canada.png
│ ├── calledOff.png
│ ├── 10 - Austria.png
│ ├── 11 - British.png
│ ├── 12 - Hungary.png
│ ├── 13 - Belgium.png
│ ├── 23 - AbuDhabi.png
│ ├── 3 - Australia.png
│ ├── imageNotFound.png
│ ├── 16 - Singapore.png
│ ├── 2 - SaudiArabia.png
│ └── 4 - Azerbaijan.png
└── Readme.md
├── F1-Notifications
├── util.h
├── display.h
├── githubCert.h
├── wifiManagerHandler.h
├── config.h
├── cheapYellowLCD.h
├── getImage.h
├── matrixDisplay.h
├── F1-Notifications.ino
├── raceLogic.h
└── races.h
├── GitHubPages
├── ESPWebTools
│ └── manifest.json
└── index.html
├── LICENSE
├── .github
└── workflows
│ ├── test-build.yml
│ └── build-deploy-webflash.yml
├── platformio.ini
├── README.md
└── DisplayConfig
└── User_Setup.h
/.gitignore:
--------------------------------------------------------------------------------
1 | .pio
2 | .vscode/*
3 | .DS_Store
4 |
5 |
--------------------------------------------------------------------------------
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/.DS_Store
--------------------------------------------------------------------------------
/images/320x240/Imola.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/Imola.png
--------------------------------------------------------------------------------
/images/320x240/19 - USA.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/19 - USA.png
--------------------------------------------------------------------------------
/images/320x240/1 - Bahrain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/1 - Bahrain.png
--------------------------------------------------------------------------------
/images/320x240/14 - Dutch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/14 - Dutch.png
--------------------------------------------------------------------------------
/images/320x240/15 - Monza.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/15 - Monza.png
--------------------------------------------------------------------------------
/images/320x240/17 - Japan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/17 - Japan.png
--------------------------------------------------------------------------------
/images/320x240/18 - Qatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/18 - Qatar.png
--------------------------------------------------------------------------------
/images/320x240/20 - Mexico.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/20 - Mexico.png
--------------------------------------------------------------------------------
/images/320x240/21 - Brazil.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/21 - Brazil.png
--------------------------------------------------------------------------------
/images/320x240/22 - Vegas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/22 - Vegas.png
--------------------------------------------------------------------------------
/images/320x240/5 - Miami.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/5 - Miami.png
--------------------------------------------------------------------------------
/images/320x240/7- Monaco.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/7- Monaco.png
--------------------------------------------------------------------------------
/images/320x240/8 - Spain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/8 - Spain.png
--------------------------------------------------------------------------------
/images/320x240/9 - Canada.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/9 - Canada.png
--------------------------------------------------------------------------------
/images/320x240/calledOff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/calledOff.png
--------------------------------------------------------------------------------
/images/320x240/10 - Austria.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/10 - Austria.png
--------------------------------------------------------------------------------
/images/320x240/11 - British.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/11 - British.png
--------------------------------------------------------------------------------
/images/320x240/12 - Hungary.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/12 - Hungary.png
--------------------------------------------------------------------------------
/images/320x240/13 - Belgium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/13 - Belgium.png
--------------------------------------------------------------------------------
/images/320x240/23 - AbuDhabi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/23 - AbuDhabi.png
--------------------------------------------------------------------------------
/images/320x240/3 - Australia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/3 - Australia.png
--------------------------------------------------------------------------------
/images/320x240/imageNotFound.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/imageNotFound.png
--------------------------------------------------------------------------------
/images/320x240/16 - Singapore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/16 - Singapore.png
--------------------------------------------------------------------------------
/images/320x240/2 - SaudiArabia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/2 - SaudiArabia.png
--------------------------------------------------------------------------------
/images/320x240/4 - Azerbaijan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/witnessmenow/F1-Arduino-Notifications/HEAD/images/320x240/4 - Azerbaijan.png
--------------------------------------------------------------------------------
/F1-Notifications/util.h:
--------------------------------------------------------------------------------
1 |
2 | const char *convertRaceName(const char *raceName)
3 | {
4 | if (strcmp(raceName, "Emilia Romagna Grand Prix") == 0)
5 | {
6 | return "Imola";
7 | }
8 | else
9 | {
10 | return raceName;
11 | }
12 | }
--------------------------------------------------------------------------------
/images/Readme.md:
--------------------------------------------------------------------------------
1 | ## To Get The Images
2 |
3 | - Go to the Forumula1.com website and go to the schedule page. This year is https://www.formula1.com/en/racing/2024.html
4 | - Right click on the track layout under the race you want and click "copy image"
5 | - Paste the image into Paint.net, and resize the canvas to 320x240 pixels.
6 | - Save the image.
7 | - Upload the image to imgur.com
8 | - Click the 3 dots when you hover over the image and click "Get Share links"
9 | - Click "copy link" on the BBCode option
10 | - Add an entry for the race based on the race name from the race source (this year is https://raw.githubusercontent.com/sportstimes/f1/main/_db/f1/2024.json) to the `getImage.h` file, Remove the "[img]" tags around the URL
11 |
--------------------------------------------------------------------------------
/GitHubPages/ESPWebTools/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "F1 Notifications Firmware",
3 | "new_install_prompt_erase": false,
4 | "builds": [
5 | {
6 | "chipFamily": "ESP32",
7 | "parts": [
8 | {
9 | "path": "boot_app0.bin",
10 | "offset": 57344
11 | },
12 | {
13 | "path": "firmware.bin",
14 | "offset": 65536
15 | },
16 | {
17 | "path": "bootloader.bin",
18 | "offset": 4096
19 | },
20 | {
21 | "path": "partitions.bin",
22 | "offset": 32768
23 | }
24 | ]
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Brian Lough
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/F1-Notifications/display.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef F1DISPLAY_H
3 | #define F1DISPLAY_H
4 |
5 | enum F1DisplaySate
6 | {
7 | unset,
8 | placeholder,
9 | raceweek
10 | };
11 |
12 | class F1Display {
13 | public:
14 | virtual void displaySetup() = 0;
15 |
16 | // For when its more than a week before race day
17 | virtual void displayPlaceHolder(const char* raceName, JsonObject races_sessions) = 0;
18 | virtual void displayRaceWeek(const char* raceName, JsonObject races_sessions) = 0;
19 | virtual void drawWifiManagerMessage(WiFiManager *myWiFiManager) = 0;
20 |
21 | F1DisplaySate state;
22 |
23 | void setRaceName(const char* raceName){
24 | strcpy(_currentRaceName, raceName);
25 | }
26 |
27 | bool isSameRace(const char* raceName){
28 | return strcmp(_currentRaceName, raceName) == 0;
29 | }
30 |
31 | void setWidth(int w) {
32 | screenWidth = w;
33 | screenCenterX = screenWidth / 2;
34 | }
35 |
36 | void setHeight(int h) {
37 | screenHeight = h;
38 | }
39 |
40 | protected:
41 | int screenWidth;
42 | int screenHeight;
43 | int screenCenterX;
44 | char _currentRaceName[200];
45 | };
46 | #endif
47 |
--------------------------------------------------------------------------------
/.github/workflows/test-build.yml:
--------------------------------------------------------------------------------
1 | name: Test compiling arduino code
2 |
3 | on:
4 | # Runs on all branches but main, when a arduino file is changed
5 | push:
6 | branches-ignore: ["main"]
7 | paths:
8 | - "**.ino"
9 | - "**.h"
10 | - "**.yml"
11 |
12 | # Allows you to run this workflow manually from the Actions tab
13 | workflow_dispatch:
14 |
15 | jobs:
16 | # Build job
17 | build:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v4
21 | - uses: actions/cache@v3
22 | - name: Cache pip
23 | uses: actions/cache@v3
24 | with:
25 | path: ~/.cache/pip
26 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
27 | restore-keys: |
28 | ${{ runner.os }}-pip-
29 | - name: Cache PlatformIO
30 | uses: actions/cache@v3
31 | with:
32 | path: ~/.platformio
33 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
34 | - name: Set up Python
35 | uses: actions/setup-python@v4
36 | with:
37 | python-version: "3.10"
38 | - name: Install PlatformIO
39 | run: |
40 | python -m pip install --upgrade pip
41 | pip install --upgrade platformio
42 |
43 | #Build CYD
44 | - name: Build CYD
45 | run: platformio run -e cyd
46 |
47 | #Build CYD2USB
48 | - name: Build CYD2USB
49 | run: platformio run -e cyd2usb
50 |
51 | #Build Matrix
52 | - name: Build Matrix
53 | run: platformio run -e trinity
54 |
--------------------------------------------------------------------------------
/GitHubPages/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | F1 Notifications
7 |
8 | Display F1 Session times and get notified by telegram
9 |
10 |
11 |
12 |
After flashing the project, please follow the steps on the project's Github page for setting it up
13 |
14 |
15 |
16 |
17 |
CYD
18 |
19 |
20 |
21 |
22 |
23 |
CYD2USB
24 |
25 |
26 |
27 |
28 |
29 |
Matrix
30 |
31 |
33 |
34 |
NOTE: Make sure to close anything using your devices com port (e.g. Serial monitor)
35 |
36 |
37 | Your browser does not support the Web Serial API.
38 |
39 |
40 |
41 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/platformio.ini:
--------------------------------------------------------------------------------
1 | ; PlatformIO Project Configuration File
2 | ;
3 | ; Build options: build flags, source filter
4 | ; Upload options: custom upload port, speed and extra flags
5 | ; Library options: dependencies, extra library storages
6 | ; Advanced options: extra scripting
7 | ;
8 | ; Please visit documentation for the other options and examples
9 | ; https://docs.platformio.org/page/projectconf.html
10 |
11 | [platformio]
12 | src_dir = F1-Notifications
13 | default_envs = cyd
14 |
15 | [env]
16 | platform = espressif32@6.5.0
17 | board = esp32dev
18 | framework = arduino
19 | lib_deps =
20 | khoih-prog/ESP_DoubleResetDetector@^1.3.2
21 | bblanchon/ArduinoJson@^6.21.3
22 | https://github.com/tzapu/WiFiManager.git#v2.0.17
23 | ropg/ezTime@^0.8.3
24 | witnessmenow/UniversalTelegramBot@^1.3.0
25 | https://github.com/witnessmenow/file-fetcher-arduino.git
26 | monitor_speed = 115200
27 | monitor_filters = esp32_exception_decoder
28 | upload_speed = 921600
29 | #lib_ldf_mode = deep+
30 |
31 | [common_cyd]
32 | lib_deps =
33 | ${env.lib_deps}
34 | bodmer/TFT_eSPI@^2.5.33
35 | bitbank2/PNGdec@^1.0.2
36 | build_flags =
37 | -DYELLOW_DISPLAY
38 | -DUSER_SETUP_LOADED
39 | -DILI9341_2_DRIVER
40 | -DTFT_WIDTH=240
41 | -DTFT_HEIGHT=320
42 | -DTFT_MISO=12
43 | -DTFT_MOSI=13
44 | -DTFT_SCLK=14
45 | -DTFT_CS=15
46 | -DTFT_DC=2
47 | -DTFT_RST=-1
48 | -DTFT_BL=21
49 | -DTFT_BACKLIGHT_ON=HIGH
50 | -DTFT_BACKLIGHT_OFF=LOW
51 | -DLOAD_GLCD
52 | -DSPI_FREQUENCY=55000000
53 | -DSPI_READ_FREQUENCY=20000000
54 | -DSPI_TOUCH_FREQUENCY=2500000
55 | -DLOAD_FONT2
56 | -DLOAD_FONT4
57 | -DLOAD_FONT6
58 | -DLOAD_FONT7
59 | -DLOAD_FONT8
60 | -DLOAD_GFXFF
61 | -DUSE_HSPI_PORT
62 |
63 | [env:cyd]
64 | lib_deps =
65 | ${common_cyd.lib_deps}
66 | build_flags =
67 | ${common_cyd.build_flags}
68 | -DTFT_INVERSION_OFF
69 |
70 | [env:cyd2usb]
71 | lib_deps =
72 | ${common_cyd.lib_deps}
73 | build_flags =
74 | ${common_cyd.build_flags}
75 | -DTFT_INVERSION_ON
76 |
77 | [env:trinity]
78 | lib_deps =
79 | ${env.lib_deps}
80 | mrfaptastic/ESP32 HUB75 LED MATRIX PANEL DMA Display@^3.0.9
81 | adafruit/Adafruit GFX Library@^1.11.9
82 | build_flags =
83 | -DMATRIX_DISPLAY
84 |
--------------------------------------------------------------------------------
/F1-Notifications/githubCert.h:
--------------------------------------------------------------------------------
1 | // USERTrust RSA Certification Authority - as of 21/05/2025
2 | const char *github_server_cert = "-----BEGIN CERTIFICATE-----\n"
3 | "MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB\n"
4 | "iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\n"
5 | "cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\n"
6 | "BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw\n"
7 | "MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV\n"
8 | "BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU\n"
9 | "aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy\n"
10 | "dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n"
11 | "AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B\n"
12 | "3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY\n"
13 | "tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/\n"
14 | "Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2\n"
15 | "VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT\n"
16 | "79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6\n"
17 | "c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT\n"
18 | "Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l\n"
19 | "c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee\n"
20 | "UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE\n"
21 | "Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd\n"
22 | "BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G\n"
23 | "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF\n"
24 | "Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO\n"
25 | "VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3\n"
26 | "ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs\n"
27 | "8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR\n"
28 | "iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze\n"
29 | "Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ\n"
30 | "XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/\n"
31 | "qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB\n"
32 | "VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB\n"
33 | "L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG\n"
34 | "jjxDah2nGN59PRbxYvnKkKj9\n"
35 | "-----END CERTIFICATE-----\n";
36 |
--------------------------------------------------------------------------------
/F1-Notifications/wifiManagerHandler.h:
--------------------------------------------------------------------------------
1 |
2 | // Number of seconds after reset during which a
3 | // subseqent reset will be considered a double reset.
4 | #define DRD_TIMEOUT 10
5 |
6 | // RTC Memory Address for the DoubleResetDetector to use
7 | #define DRD_ADDRESS 0
8 |
9 | #define WM_F1_TIME_ZONE_LABEL "timeZone"
10 | #define WM_F1_TIME_FORMAT_LABEL "timeFormat"
11 | #define WM_F1_BOT_TOKEN_LABEL "botToken"
12 | #define WM_F1_CHAT_ID_LABEL "chatId"
13 | #define WM_F1_NOTIFCATION_LABEL "notification"
14 |
15 | DoubleResetDetector* drd;
16 | F1Display* wm_Display;
17 |
18 | //flag for saving data
19 | bool shouldSaveConfig = false;
20 |
21 | //callback notifying us of the need to save config
22 | void saveConfigCallback () {
23 | Serial.println("Should save config");
24 | shouldSaveConfig = true;
25 | }
26 |
27 | void configModeCallback (WiFiManager *myWiFiManager) {
28 | wm_Display->drawWifiManagerMessage(myWiFiManager);
29 | }
30 |
31 | void setupWiFiManager(bool forceConfig, F1Config f1Config, F1Display* theDisplay) {
32 | wm_Display = theDisplay;
33 | WiFiManager wm;
34 | //set config save notify callback
35 | wm.setSaveConfigCallback(saveConfigCallback);
36 | //set callback that gets called when connecting to previous WiFi fails, and enters Access Point mode
37 | wm.setAPCallback(configModeCallback);
38 |
39 | WiFiManagerParameter timeZoneParam(WM_F1_TIME_ZONE_LABEL, "Time Zone", f1Config.timeZone.c_str(), 60);
40 | WiFiManagerParameter timeFormatParam(WM_F1_TIME_FORMAT_LABEL, "Time Format", f1Config.timeFormat.c_str(), 40);
41 | WiFiManagerParameter telegramBotParam(WM_F1_BOT_TOKEN_LABEL, "Bot Token", f1Config.botToken.c_str(), 60);
42 | WiFiManagerParameter telegramChatIdParam(WM_F1_CHAT_ID_LABEL, "Chat ID", f1Config.chatId.c_str(), 40);
43 |
44 | char checkBox[] = "type=\"checkbox\"";
45 | char checkBoxChecked[] = "type=\"checkbox\" checked";
46 | char* customHtml;
47 |
48 | if (f1Config.currentRaceNotification) {
49 | customHtml = checkBoxChecked;
50 | } else {
51 | customHtml = checkBox;
52 | }
53 | WiFiManagerParameter isNotificationSent(WM_F1_NOTIFCATION_LABEL, "Notification Sent", "T", 2, customHtml);
54 |
55 | wm.addParameter(&timeZoneParam);
56 | wm.addParameter(&timeFormatParam);
57 | wm.addParameter(&telegramBotParam);
58 | wm.addParameter(&telegramChatIdParam);
59 | wm.addParameter(&isNotificationSent);
60 |
61 | if (forceConfig) {
62 | // IF we forced config this time, lets stop the double reset so it doesn't get stuck in a loop
63 | drd->stop();
64 | if (!wm.startConfigPortal("f1Thing", "nomikey1")) {
65 | Serial.println("failed to connect and hit timeout");
66 | delay(3000);
67 | //reset and try again, or maybe put it to deep sleep
68 | ESP.restart();
69 | delay(5000);
70 | }
71 | } else {
72 | if (!wm.autoConnect("f1Thing", "nomikey1")) {
73 | Serial.println("failed to connect and hit timeout");
74 | delay(3000);
75 | // if we still have not connected restart and try all over again
76 | ESP.restart();
77 | delay(5000);
78 | }
79 | }
80 |
81 | //save the custom parameters to FS
82 | if (shouldSaveConfig)
83 | {
84 |
85 | f1Config.timeZone = String(timeZoneParam.getValue());
86 | f1Config.timeFormat = String(timeFormatParam.getValue());
87 | f1Config.botToken = String(telegramBotParam.getValue());
88 | f1Config.chatId = String(telegramChatIdParam.getValue());
89 | f1Config.currentRaceNotification = (strncmp(isNotificationSent.getValue(), "T", 1) == 0);
90 |
91 | f1Config.saveConfigFile();
92 | drd->stop();
93 | ESP.restart();
94 | delay(5000);
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/F1-Notifications/config.h:
--------------------------------------------------------------------------------
1 | #define F1_CONFIG_JSON "/f1_notification_config.json"
2 |
3 | #define F1_TIME_ZONE_LABEL "timeZone"
4 | #define F1_TIME_FORMAT_LABEL "timeFormat"
5 | #define F1_BOT_TOKEN_LABEL "botToken"
6 | #define F1_CHAT_ID_LABEL "chatId"
7 | #define F1_ROUND_OFFSET_LABEL "roundOffset"
8 | #define F1_CURRENT_RACE_NOTIFICATION_LABEL "currentRaceNotification"
9 |
10 | class F1Config
11 | {
12 | public:
13 | // How the time will be displayed, see here for more info: https://github.com/ropg/ezTime#datetime
14 | String timeFormat = "D, H:i"; // Fri, 00:30
15 | String timeZone = "Europe/London"; // seems to be something wrong with Europe/Dublin
16 |
17 | // Telegram BOT Token (Get from Botfather)
18 | String botToken = "";
19 |
20 | // Use @myidbot (IDBot) to find out the chat ID of an individual or a group
21 | // Also note that you need to click "start" on a bot before it can
22 | // message you
23 | String chatId = "";
24 |
25 | int roundOffset = 0;
26 |
27 | bool currentRaceNotification = false;
28 |
29 | bool isTelegramConfigured()
30 | {
31 | return (botToken != "") && (chatId != "");
32 | }
33 |
34 | bool fetchConfigFile()
35 | {
36 | if (SPIFFS.exists(F1_CONFIG_JSON))
37 | {
38 | // file exists, reading and loading
39 | Serial.println("reading config file");
40 | File configFile = SPIFFS.open(F1_CONFIG_JSON, "r");
41 | if (configFile)
42 | {
43 | Serial.println("opened config file");
44 | StaticJsonDocument<1024> json;
45 | DeserializationError error = deserializeJson(json, configFile);
46 | serializeJsonPretty(json, Serial);
47 | if (!error)
48 | {
49 | Serial.println("\nparsed json");
50 |
51 | if (json.containsKey(F1_TIME_ZONE_LABEL))
52 | {
53 | timeZone = String(json[F1_TIME_ZONE_LABEL].as());
54 | }
55 |
56 | if (json.containsKey(F1_TIME_FORMAT_LABEL))
57 | {
58 | timeFormat = String(json[F1_TIME_FORMAT_LABEL].as());
59 | }
60 |
61 | if (json.containsKey(F1_BOT_TOKEN_LABEL))
62 | {
63 | botToken = String(json[F1_BOT_TOKEN_LABEL].as());
64 | }
65 |
66 | if (json.containsKey(F1_CHAT_ID_LABEL))
67 | {
68 | chatId = String(json[F1_CHAT_ID_LABEL].as());
69 | }
70 |
71 | if (json.containsKey(F1_ROUND_OFFSET_LABEL))
72 | {
73 | roundOffset = json[F1_ROUND_OFFSET_LABEL].as();
74 | }
75 |
76 | if (json.containsKey(F1_CURRENT_RACE_NOTIFICATION_LABEL))
77 | {
78 | currentRaceNotification = json[F1_CURRENT_RACE_NOTIFICATION_LABEL].as();
79 | }
80 |
81 | return true;
82 | }
83 | else
84 | {
85 | Serial.println("failed to load json config");
86 | return false;
87 | }
88 | }
89 | }
90 |
91 | Serial.println("Config file does not exist");
92 | return false;
93 | }
94 |
95 | bool saveConfigFile()
96 | {
97 | Serial.println(F("Saving config"));
98 | StaticJsonDocument<1024> json;
99 | json[F1_TIME_ZONE_LABEL] = timeZone;
100 | json[F1_TIME_FORMAT_LABEL] = timeFormat;
101 | json[F1_BOT_TOKEN_LABEL] = botToken;
102 | json[F1_CHAT_ID_LABEL] = chatId;
103 | json[F1_ROUND_OFFSET_LABEL] = roundOffset;
104 | json[F1_CURRENT_RACE_NOTIFICATION_LABEL] = currentRaceNotification;
105 |
106 | File configFile = SPIFFS.open(F1_CONFIG_JSON, "w");
107 | if (!configFile)
108 | {
109 | Serial.println("failed to open config file for writing");
110 | return false;
111 | }
112 |
113 | serializeJsonPretty(json, Serial);
114 | if (serializeJson(json, configFile) == 0)
115 | {
116 | Serial.println(F("Failed to write to file"));
117 | return false;
118 | }
119 | configFile.close();
120 | return true;
121 | }
122 | };
123 |
--------------------------------------------------------------------------------
/.github/workflows/build-deploy-webflash.yml:
--------------------------------------------------------------------------------
1 | # Sample workflow for building and deploying a Jekyll site to GitHub Pages
2 | name: Build firmware and deploy GitHub Page
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["main"]
8 | paths-ignore:
9 | - "**.md"
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
15 | permissions:
16 | contents: read
17 | pages: write
18 | id-token: write
19 |
20 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
21 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
22 | concurrency:
23 | group: "pages"
24 | cancel-in-progress: false
25 |
26 | jobs:
27 | # Build job
28 | build:
29 | runs-on: ubuntu-latest
30 | steps:
31 | - uses: actions/checkout@v4
32 | - uses: actions/cache@v3
33 | - name: Cache pip
34 | uses: actions/cache@v3
35 | with:
36 | path: ~/.cache/pip
37 | key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
38 | restore-keys: |
39 | ${{ runner.os }}-pip-
40 | - name: Cache PlatformIO
41 | uses: actions/cache@v3
42 | with:
43 | path: ~/.platformio
44 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
45 | - name: Set up Python
46 | uses: actions/setup-python@v4
47 | with:
48 | python-version: "3.10"
49 | - name: Install PlatformIO
50 | run: |
51 | python -m pip install --upgrade pip
52 | pip install --upgrade platformio
53 |
54 | #Build CYD
55 | - name: Build CYD
56 | run: platformio run -e cyd
57 | - name: Upload artifact
58 | uses: actions/upload-artifact@v4
59 | with:
60 | name: CYD Firmware
61 | path: .pio/build/cyd/firmware.bin
62 | if-no-files-found: error
63 | - name: Copy compiled binaries for webflash
64 | run: |
65 | mkdir GitHubPages/ESPWebTools/cyd
66 |
67 | # Copy the manifest file for the CYD
68 | cp GitHubPages/ESPWebTools/manifest.json GitHubPages/ESPWebTools/cyd
69 |
70 | cp .pio/build/cyd/bootloader.bin GitHubPages/ESPWebTools/cyd
71 | cp .pio/build/cyd/partitions.bin GitHubPages/ESPWebTools/cyd
72 | cp /home/runner/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin GitHubPages/ESPWebTools/cyd
73 | cp .pio/build/cyd/firmware.bin GitHubPages/ESPWebTools/cyd
74 |
75 | #Build CYD2USB
76 | - name: Build CYD2USB
77 | run: platformio run -e cyd2usb
78 | - name: Upload artifact
79 | uses: actions/upload-artifact@v4
80 | with:
81 | name: CYD2USB Firmware
82 | path: .pio/build/cyd2usb/firmware.bin
83 | if-no-files-found: error
84 | - name: Copy compiled binaries for webflash
85 | run: |
86 | mkdir GitHubPages/ESPWebTools/cyd2usb
87 |
88 | # Copy the manifest file for the cyd2usb
89 | cp GitHubPages/ESPWebTools/manifest.json GitHubPages/ESPWebTools/cyd2usb
90 |
91 | cp .pio/build/cyd2usb/bootloader.bin GitHubPages/ESPWebTools/cyd2usb
92 | cp .pio/build/cyd2usb/partitions.bin GitHubPages/ESPWebTools/cyd2usb
93 | cp /home/runner/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin GitHubPages/ESPWebTools/cyd
94 | cp .pio/build/cyd2usb/firmware.bin GitHubPages/ESPWebTools/cyd2usb
95 |
96 | #Build Matrix
97 | - name: Build Matrix
98 | run: platformio run -e trinity
99 | - name: Upload artifact
100 | uses: actions/upload-artifact@v4
101 | with:
102 | name: Matrix Firmware
103 | path: .pio/build/trinity/firmware.bin
104 | if-no-files-found: error
105 | - name: Copy compiled binaries for webflash
106 | run: |
107 | mkdir GitHubPages/ESPWebTools/matrix
108 |
109 | # Copy the manifest file for the Matrix
110 | cp GitHubPages/ESPWebTools/manifest.json GitHubPages/ESPWebTools/matrix
111 |
112 | cp .pio/build/trinity/bootloader.bin GitHubPages/ESPWebTools/matrix
113 | cp .pio/build/trinity/partitions.bin GitHubPages/ESPWebTools/matrix
114 | cp /home/runner/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin GitHubPages/ESPWebTools/matrix
115 | cp .pio/build/trinity/firmware.bin GitHubPages/ESPWebTools/matrix
116 |
117 | # Build GitHub Page
118 | - name: Setup Github Page
119 | uses: actions/configure-pages@v4
120 |
121 | - name: Build GitHub Page with Jekyll
122 | uses: actions/jekyll-build-pages@v1
123 | with:
124 | source: ./GitHubPages
125 | destination: ./_site
126 |
127 | - name: Upload GitHub Page Artifact
128 | uses: actions/upload-pages-artifact@v3
129 |
130 | # Deployment job
131 | deploy:
132 | environment:
133 | name: github-pages
134 | url: ${{ steps.deployment.outputs.page_url }}
135 | runs-on: ubuntu-latest
136 | needs: build
137 | steps:
138 | - name: Deploy to GitHub Pages
139 | id: deployment
140 | uses: actions/deploy-pages@v4
141 |
--------------------------------------------------------------------------------
/F1-Notifications/cheapYellowLCD.h:
--------------------------------------------------------------------------------
1 | #include "display.h"
2 | #include "getImage.h"
3 | #include "util.h"
4 |
5 | #include
6 | // A library for interfacing with LCD displays
7 | //
8 | // Can be installed from the library manager (Search for "TFT_eSPI")
9 | // https://github.com/Bodmer/TFT_eSPI
10 |
11 | #include
12 | // For decoding png files
13 | //
14 | // Can be installed from the library manager (Search for "PNGdec")
15 | // https://github.com/bitbank2/PNGdec
16 |
17 | // -------------------------------
18 | // Putting this stuff outside the class because
19 | // I can't easily pass member functions in as callbacks for pngdec
20 |
21 | // -------------------------------
22 |
23 | #define SESSION_TEXT_SIZE 4
24 |
25 | TFT_eSPI tft = TFT_eSPI();
26 | PNG png;
27 |
28 | fs::File myfile;
29 |
30 | void *myOpen(const char *filename, int32_t *size)
31 | {
32 | myfile = SPIFFS.open(filename);
33 | *size = myfile.size();
34 | return &myfile;
35 | }
36 | void myClose(void *handle)
37 | {
38 | if (myfile)
39 | myfile.close();
40 | }
41 | int32_t myRead(PNGFILE *handle, uint8_t *buffer, int32_t length)
42 | {
43 | if (!myfile)
44 | return 0;
45 | return myfile.read(buffer, length);
46 | }
47 | int32_t mySeek(PNGFILE *handle, int32_t position)
48 | {
49 | if (!myfile)
50 | return 0;
51 | return myfile.seek(position);
52 | }
53 |
54 | void PNGDraw(PNGDRAW *pDraw)
55 | {
56 | uint16_t usPixels[320];
57 |
58 | png.getLineAsRGB565(pDraw, usPixels, PNG_RGB565_BIG_ENDIAN, 0xffffffff);
59 | tft.pushImage(0, pDraw->y, pDraw->iWidth, 1, usPixels);
60 | }
61 |
62 | class CheapYellowDisplay : public F1Display
63 | {
64 | public:
65 | void displaySetup()
66 | {
67 |
68 | Serial.println("cyd display setup");
69 | setWidth(320);
70 | setHeight(240);
71 |
72 | // Start the tft display and set it to black
73 | tft.init();
74 | tft.setRotation(1);
75 | tft.fillScreen(TFT_BLACK);
76 |
77 | state = unset;
78 | }
79 |
80 | void displayPlaceHolder(const char *raceName, JsonObject races_sessions)
81 | {
82 |
83 | if (!isSameRace(raceName) || state != placeholder)
84 | {
85 | setRaceName(raceName);
86 | int imageFileStatus = getImage(raceName);
87 | if (imageFileStatus)
88 | {
89 | int imageDisplayStatus = displayImage(TRACK_IMAGE);
90 | if (imageDisplayStatus == PNG_SUCCESS)
91 | {
92 | // Image is displayed
93 | tft.setTextColor(TFT_WHITE, TFT_BLACK);
94 | int yPos = 215;
95 | String gpStartDateStr = String(getConvertedTime(races_sessions["gp"], "M d"));
96 | String displayMessage = String(convertRaceName(raceName)) + " | " + gpStartDateStr;
97 | tft.drawCentreString(displayMessage, screenCenterX, yPos, 4);
98 | state = placeholder;
99 | return;
100 | }
101 | }
102 | // Failed to display the image
103 | displayRaceWeek(raceName, races_sessions); // For now
104 | }
105 |
106 | // if we reach here, the screen doesn't need to be updated
107 | Serial.println("No need to update display");
108 | }
109 |
110 | void displayRaceWeek(const char *raceName, JsonObject races_sessions)
111 | {
112 | Serial.println("prts");
113 | tft.fillRect(0, 0, screenWidth, screenHeight, TFT_BLACK);
114 |
115 | tft.setTextColor(TFT_WHITE, TFT_BLACK);
116 | int yPos = 5;
117 | String gpStartDateStr = String(getConvertedTime(races_sessions["gp"], "M d"));
118 | String displayMessage = String(convertRaceName(raceName)) + " | " + gpStartDateStr;
119 | tft.drawCentreString(displayMessage, screenCenterX, yPos, 4);
120 |
121 | int yValue = 46;
122 | for (JsonPair kv : races_sessions)
123 | {
124 | printSession(4,
125 | yValue,
126 | sessionCodeToString(kv.key().c_str()),
127 | getConvertedTime(kv.value().as()));
128 | yValue += (SESSION_TEXT_SIZE) * 8;
129 | }
130 |
131 | state = raceweek;
132 | }
133 |
134 | int displayImage(char *imageFileUri)
135 | {
136 | tft.fillScreen(TFT_BLACK);
137 | unsigned long lTime = millis();
138 | lTime = millis();
139 | Serial.println(imageFileUri);
140 |
141 | int rc = png.open((const char *)imageFileUri, myOpen, myClose, myRead, mySeek, PNGDraw);
142 | if (rc == PNG_SUCCESS)
143 | {
144 | Serial.printf("image specs: (%d x %d), %d bpp, pixel type: %d\n", png.getWidth(), png.getHeight(), png.getBpp(), png.getPixelType());
145 | rc = png.decode(NULL, 0);
146 | png.close();
147 | }
148 | else
149 | {
150 | Serial.print("error code: ");
151 | Serial.println(rc);
152 | }
153 |
154 | Serial.print("Time taken to decode and display Image (ms): ");
155 | Serial.println(millis() - lTime);
156 |
157 | return rc;
158 | }
159 |
160 | void drawWifiManagerMessage(WiFiManager *myWiFiManager)
161 | {
162 | Serial.println("Entered Conf Mode");
163 | tft.fillScreen(TFT_BLACK);
164 | tft.setTextColor(TFT_WHITE, TFT_BLACK);
165 | tft.drawCentreString("Entered Conf Mode:", screenCenterX, 5, 2);
166 | tft.drawString("Connect to the following WIFI AP:", 5, 28, 2);
167 | tft.setTextColor(TFT_BLUE, TFT_BLACK);
168 | tft.drawString(myWiFiManager->getConfigPortalSSID(), 20, 48, 2);
169 | tft.setTextColor(TFT_WHITE, TFT_BLACK);
170 | tft.drawString("Password:", 5, 64, 2);
171 | tft.setTextColor(TFT_BLUE, TFT_BLACK);
172 | tft.drawString("nomikey1", 20, 82, 2);
173 | tft.setTextColor(TFT_WHITE, TFT_BLACK);
174 |
175 | tft.drawString("If it doesn't AutoConnect, use this IP:", 5, 110, 2);
176 | tft.setTextColor(TFT_BLUE, TFT_BLACK);
177 | tft.drawString(WiFi.softAPIP().toString(), 20, 128, 2);
178 | tft.setTextColor(TFT_WHITE, TFT_BLACK);
179 | }
180 |
181 | private:
182 | void printSession(int x, int y, const char *sessionName, String sessionStartTime)
183 | {
184 | String tempStr = String(sessionName);
185 | tempStr += " ";
186 | tempStr += sessionStartTime;
187 | tft.drawString(tempStr, x, y, SESSION_TEXT_SIZE);
188 | }
189 | };
190 |
--------------------------------------------------------------------------------
/F1-Notifications/getImage.h:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | // i.imgur.com
4 | // USERTrust RSA Certification Authority
5 |
6 | const char IMGUR_CERTIFICATE_ROOT[] = R"=EOF=(
7 | -----BEGIN CERTIFICATE-----
8 | MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB
9 | iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl
10 | cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV
11 | BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw
12 | MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV
13 | BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU
14 | aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy
15 | dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK
16 | AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B
17 | 3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY
18 | tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/
19 | Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2
20 | VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT
21 | 79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6
22 | c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT
23 | Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l
24 | c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee
25 | UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE
26 | Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd
27 | BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G
28 | A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF
29 | Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO
30 | VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3
31 | ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs
32 | 8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR
33 | iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze
34 | Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ
35 | XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/
36 | qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB
37 | VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB
38 | L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG
39 | jjxDah2nGN59PRbxYvnKkKj9
40 | -----END CERTIFICATE-----
41 | )=EOF=";
42 |
43 | // file name for where to save the image.
44 | #define TRACK_IMAGE "/track.png"
45 |
46 | const char *getImageUrlForRace(const char *raceName)
47 | {
48 | if (strcmp(raceName, "Bahrain") == 0)
49 | {
50 | return "https://i.imgur.com/zUqArqi.png";
51 | }
52 | else if (strcmp(raceName, "Saudi Arabian") == 0)
53 | {
54 | return "https://i.imgur.com/vx6MDSF.png";
55 | }
56 | else if (strcmp(raceName, "Australian") == 0)
57 | {
58 | return "https://i.imgur.com/ewrhVKU.png";
59 | }
60 | else if (strcmp(raceName, "Azerbaijan") == 0)
61 | {
62 | return "https://i.imgur.com/H2C6G2Q.png";
63 | }
64 | else if (strcmp(raceName, "Miami") == 0)
65 | {
66 | return "https://i.imgur.com/mwoQzCm.png";
67 | }
68 | else if (strcmp(raceName, "Emilia Romagna Grand Prix") == 0)
69 | {
70 | return "https://i.imgur.com/fm6IygV.png";
71 | }
72 | else if (strcmp(raceName, "Monaco") == 0)
73 | {
74 | return "https://i.imgur.com/Q48IRF1.png";
75 | }
76 | else if (strcmp(raceName, "Spanish") == 0)
77 | {
78 | return "https://i.imgur.com/GdnHo69.png";
79 | }
80 | else if (strcmp(raceName, "Canadian") == 0)
81 | {
82 | return "https://i.imgur.com/QNAli6L.png";
83 | }
84 | else if (strcmp(raceName, "Austrian") == 0)
85 | {
86 | return "https://i.imgur.com/Xu4I91f.png";
87 | }
88 | else if (strcmp(raceName, "British") == 0)
89 | {
90 | return "https://i.imgur.com/R0snf2W.png";
91 | }
92 | else if (strcmp(raceName, "Hungarian") == 0)
93 | {
94 | return "https://i.imgur.com/R0snf2W.png";
95 | }
96 | else if (strcmp(raceName, "Belgian") == 0)
97 | {
98 | return "https://i.imgur.com/Hr3HUGP.png";
99 | }
100 | else if (strcmp(raceName, "Dutch") == 0)
101 | {
102 | return "https://i.imgur.com/fwyHAy5.png";
103 | }
104 | else if (strcmp(raceName, "Italian") == 0)
105 | {
106 | return "https://i.imgur.com/KrRzWhh.png";
107 | }
108 | else if (strcmp(raceName, "Singapore") == 0)
109 | {
110 | return "https://i.imgur.com/di1xFkV.png";
111 | }
112 | else if (strcmp(raceName, "Japanese") == 0)
113 | {
114 | return "https://i.imgur.com/BINBSn3.png";
115 | }
116 | else if (strcmp(raceName, "Qatar") == 0)
117 | {
118 | return "https://i.imgur.com/YdpmY5o.png";
119 | }
120 | else if (strcmp(raceName, "United States") == 0)
121 | {
122 | return "https://i.imgur.com/NzZNjF6.png";
123 | }
124 | else if (strcmp(raceName, "Mexican") == 0)
125 | {
126 | return "https://i.imgur.com/gvUauKO.png";
127 | }
128 | else if (strcmp(raceName, "Brazilian") == 0)
129 | {
130 | return "https://i.imgur.com/3g4zz17.png";
131 | }
132 | else if (strcmp(raceName, "Las Vegas") == 0)
133 | {
134 | return "https://i.imgur.com/er9A6G8.png";
135 | }
136 | else if (strcmp(raceName, "Abu Dhabi") == 0)
137 | {
138 | return "https://i.imgur.com/QDwWXna.png";
139 | }
140 | else if (strcmp(raceName, "Chinese") == 0)
141 | {
142 | return "https://i.imgur.com/dpoPdoD.png";
143 | }
144 |
145 | // Image not found
146 | return "https://i.imgur.com/FRXJ4do.png";
147 | //"https://i.imgur.com/tvLjDeo.png"; // This is a called off image
148 | }
149 |
150 | int getImage(const char *raceName)
151 | {
152 |
153 | const char *imageUrl = getImageUrlForRace(raceName);
154 |
155 | // In this example I reuse the same filename
156 | // over and over
157 | if (SPIFFS.exists(TRACK_IMAGE) == true)
158 | {
159 | Serial.println("Removing existing image");
160 | SPIFFS.remove(TRACK_IMAGE);
161 | }
162 |
163 | fs::File f = SPIFFS.open(TRACK_IMAGE, "w+");
164 | if (!f)
165 | {
166 | Serial.println("file open failed");
167 | return -1;
168 | }
169 |
170 | secured_client.setCACert(IMGUR_CERTIFICATE_ROOT);
171 | bool gotImage = fileFetcher.getFile((char *)imageUrl, &f);
172 |
173 | // Make sure to close the file!
174 | f.close();
175 |
176 | return gotImage;
177 | }
178 |
--------------------------------------------------------------------------------
/F1-Notifications/matrixDisplay.h:
--------------------------------------------------------------------------------
1 | #include "display.h"
2 |
3 | #include "util.h"
4 |
5 | #include
6 | // This is the library for interfacing with the display
7 |
8 | // Can be installed from the library manager (Search for "ESP32 MATRIX DMA")
9 | // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA
10 |
11 | // ----------------------------
12 | // Dependency Libraries - each one of these will need to be installed.
13 | // ----------------------------
14 |
15 | // Adafruit GFX library is a dependency for the matrix Library
16 | // Can be installed from the library manager
17 | // https://github.com/adafruit/Adafruit-GFX-Library
18 |
19 | // -------------------------------------
20 | // ------- Matrix Config ------
21 | // -------------------------------------
22 |
23 | const int panelResX = 64; // Number of pixels wide of each INDIVIDUAL panel module.
24 | const int panelResY = 64; // Number of pixels tall of each INDIVIDUAL panel module.
25 | const int panel_chain = 1; // Total number of panels chained one to another
26 |
27 | MatrixPanel_I2S_DMA *dma_display = nullptr;
28 |
29 | uint16_t myBLACK = dma_display->color565(0, 0, 0);
30 | uint16_t myWHITE = dma_display->color565(255, 255, 255);
31 | uint16_t myRED = dma_display->color565(255, 0, 0);
32 | uint16_t myGREEN = dma_display->color565(0, 255, 0);
33 | uint16_t myBLUE = dma_display->color565(0, 0, 255);
34 |
35 | class MatrixDisplay : public F1Display
36 | {
37 | public:
38 | void displaySetup()
39 | {
40 |
41 | Serial.println("matrix display setup");
42 | setWidth(panelResX * panel_chain);
43 | setHeight(panelResY);
44 |
45 | HUB75_I2S_CFG mxconfig(
46 | panelResX, // module width
47 | panelResY, // module height
48 | panel_chain // Chain length
49 | );
50 |
51 | // If you are using a 64x64 matrix you need to pass a value for the E pin
52 | // The trinity connects GPIO 18 to E.
53 | // This can be commented out for any smaller displays (but should work fine with it)
54 | mxconfig.gpio.e = 18;
55 |
56 | // May or may not be needed depending on your matrix
57 | // Example of what needing it looks like:
58 | // https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA/issues/134#issuecomment-866367216
59 | mxconfig.clkphase = false;
60 |
61 | // Some matrix panels use different ICs for driving them and some of them have strange quirks.
62 | // If the display is not working right, try this.
63 | // mxconfig.driver = HUB75_I2S_CFG::FM6126A;
64 |
65 | dma_display = new MatrixPanel_I2S_DMA(mxconfig);
66 | dma_display->begin();
67 | }
68 | void displayRaceWeek(const char *raceName, JsonObject races_sessions)
69 | {
70 |
71 | const char *raceNameChanged = convertRaceName(raceName);
72 |
73 | // It's race week!
74 | dma_display->fillScreen(myBLACK);
75 | dma_display->setTextSize(1); // size 2 == 16 pixels high
76 | dma_display->setTextWrap(false); // N.B!! Don't wrap at end of line
77 |
78 | int16_t xOne, yOne;
79 | uint16_t w, h;
80 |
81 | // This method updates the variables with what width (w) and height (h)
82 | // the give text will have.
83 |
84 | dma_display->getTextBounds(raceNameChanged, 0, 0, &xOne, &yOne, &w, &h);
85 |
86 | int xPosition = screenCenterX - w / 2;
87 | dma_display->setTextColor(myBLUE);
88 | dma_display->setCursor(xPosition, 2);
89 | dma_display->print(raceNameChanged);
90 |
91 | int yValue = 12;
92 | for (JsonPair kv : races_sessions)
93 | {
94 | printSession(yValue,
95 | matrixSessionCodeToString(kv.key().c_str()),
96 | getConvertedTime(kv.value().as(), "H:i"));
97 | yValue += 10;
98 | }
99 | }
100 |
101 | void displayPlaceHolder(const char *raceName, JsonObject races_sessions)
102 | {
103 |
104 | const char *raceNameChanged = convertRaceName(raceName);
105 |
106 | // Not yet race week
107 | dma_display->fillScreen(myBLACK);
108 | dma_display->setTextSize(1); // size 2 == 16 pixels high
109 | dma_display->setTextWrap(false); // N.B!! Don't wrap at end of line
110 |
111 | int16_t xOne, yOne;
112 | uint16_t w, h;
113 |
114 | // This method updates the variables with what width (w) and height (h)
115 | // the give text will have.
116 | dma_display->getTextBounds("Next Race:", 0, 0, &xOne, &yOne, &w, &h);
117 | int xPosition = screenCenterX - w / 2;
118 | dma_display->setTextColor(myGREEN);
119 | dma_display->setCursor(xPosition, 2);
120 | dma_display->print("Next Race:");
121 |
122 | // This method updates the variables with what width (w) and height (h)
123 | // the give text will have.
124 |
125 | dma_display->getTextBounds(raceNameChanged, 0, 0, &xOne, &yOne, &w, &h);
126 |
127 | xPosition = screenCenterX - w / 2;
128 | dma_display->setTextColor(myBLUE);
129 | dma_display->setCursor(xPosition, 10);
130 | dma_display->print(raceNameChanged);
131 |
132 | printSession(20,
133 | "GP:",
134 | getConvertedTime(races_sessions["gp"], "M d"));
135 | }
136 |
137 | int displayImage(char *imageFileUri)
138 | {
139 | return 0;
140 | }
141 |
142 | void drawWifiManagerMessage(WiFiManager *myWiFiManager)
143 | {
144 | Serial.println("Entered Conf Mode");
145 | dma_display->fillScreen(myBLACK);
146 | dma_display->setTextSize(1); // size 1 == 8 pixels high
147 | dma_display->setTextWrap(false);
148 | dma_display->setTextColor(myBLUE);
149 | dma_display->setCursor(0, 0);
150 | dma_display->print(myWiFiManager->getConfigPortalSSID());
151 |
152 | dma_display->setTextWrap(true);
153 | dma_display->setTextColor(myRED);
154 | dma_display->setCursor(0, 8);
155 | dma_display->print(WiFi.softAPIP());
156 | }
157 |
158 | private:
159 | const char *matrixSessionCodeToString(const char *sessionCode)
160 | {
161 | if (strcmp(sessionCode, "fp1") == 0)
162 | {
163 | return "FP1:";
164 | }
165 | else if (strcmp(sessionCode, "fp2") == 0)
166 | {
167 | return "FP2:";
168 | }
169 | else if (strcmp(sessionCode, "fp3") == 0)
170 | {
171 | return "FP3:";
172 | }
173 | else if (strcmp(sessionCode, "qualifying") == 0)
174 | {
175 | return "Qual:";
176 | }
177 | else if (strcmp(sessionCode, "sprintQualifying") == 0)
178 | {
179 | return "Sp Q:";
180 | }
181 | else if (strcmp(sessionCode, "sprint") == 0)
182 | {
183 | return "Spr:";
184 | }
185 | else if (strcmp(sessionCode, "gp") == 0)
186 | {
187 | return "Race:";
188 | }
189 |
190 | return "UNKNOWN";
191 | }
192 |
193 | void printSession(int y, const char *sessionName, String sessionStartTime)
194 | {
195 |
196 | // Print Session Name on the left
197 | dma_display->setTextColor(myRED);
198 | dma_display->setCursor(1, y);
199 | dma_display->print(sessionName);
200 |
201 | Serial.println(sessionName);
202 |
203 | // Print time on the right
204 |
205 | int16_t xOne, yOne;
206 | uint16_t w, h;
207 |
208 | // This method updates the variables with what width (w) and height (h)
209 | // the give text will have.
210 | Serial.println(sessionStartTime);
211 | dma_display->getTextBounds(sessionStartTime, 0, 0, &xOne, &yOne, &w, &h);
212 |
213 | int xPosition = screenWidth - w;
214 |
215 | Serial.println(xPosition);
216 |
217 | dma_display->setCursor(xPosition, y);
218 | dma_display->print(sessionStartTime);
219 | }
220 | };
221 |
--------------------------------------------------------------------------------
/F1-Notifications/F1-Notifications.ino:
--------------------------------------------------------------------------------
1 | /*******************************************************************
2 | An Arduino Project for notifiying you of the start time of
3 | upcoming F1 sessions in your local timezone.
4 |
5 | Tested on an ESP32.
6 |
7 | If you find what I do useful and would like to support me,
8 | please consider becoming a sponsor on Github
9 | https://github.com/sponsors/witnessmenow/
10 |
11 | Written by Brian Lough
12 | YouTube: https://www.youtube.com/brianlough
13 | Twitter: https://twitter.com/witnessmenow
14 | *******************************************************************/
15 | // ----------------------------
16 | // Display type
17 | // ---------------------------
18 |
19 | // This project currently supports the following displays
20 | // (Uncomment the required #define)
21 |
22 | // 1. Cheap yellow display (Using TFT-eSPI library)
23 | // #define YELLOW_DISPLAY
24 |
25 | // 2. Matrix Displays (Like the ESP32 Trinity)
26 | // #define MATRIX_DISPLAY
27 |
28 | // If no defines are set, it will default to CYD
29 | #if !defined(YELLOW_DISPLAY) && !defined(MATRIX_DISPLAY)
30 | #define YELLOW_DISPLAY // Default to Yellow Display for display type
31 | #endif
32 |
33 | // ----------------------------
34 | // Library Defines - Need to be defined before library import
35 | // ----------------------------
36 |
37 | #define ESP_DRD_USE_SPIFFS true
38 |
39 | // ----------------------------
40 | // Standard Libraries
41 | // ----------------------------
42 |
43 | #include
44 |
45 | #include
46 |
47 | #include
48 | #include "SPIFFS.h"
49 |
50 | // ----------------------------
51 | // Additional Libraries - each one of these will need to be installed.
52 | // ----------------------------
53 |
54 | #include
55 | // Captive portal for configuring the WiFi
56 |
57 | // If installing from the library manager (Search for "WifiManager")
58 | // https://github.com/tzapu/WiFiManager
59 |
60 | #include
61 | // A library for checking if the reset button has been pressed twice
62 | // Can be used to enable config mode
63 | // Can be installed from the library manager (Search for "ESP_DoubleResetDetector")
64 | // https://github.com/khoih-prog/ESP_DoubleResetDetector
65 |
66 | #include
67 | // Library used for parsing Json from the API responses
68 |
69 | // Search for "Arduino Json" in the Arduino Library manager
70 | // https://github.com/bblanchon/ArduinoJson
71 |
72 | #include
73 | // Library used for getting the time and converting session time
74 | // to users timezone
75 |
76 | // Search for "ezTime" in the Arduino Library manager
77 | // https://github.com/ropg/ezTime
78 |
79 | #include
80 | // Library used to send Telegram Message
81 |
82 | // Search for "Universal Telegram" in the Arduino Library manager
83 | // https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot
84 |
85 | #include
86 | // Library used to get files or images
87 |
88 | // Not on library manager yet
89 | // https://github.com/witnessmenow/file-fetcher-arduino
90 |
91 | // ----------------------------
92 | // Internal includes
93 | // ----------------------------
94 |
95 | #include "githubCert.h"
96 |
97 | #include "display.h"
98 |
99 | #include "config.h"
100 |
101 | #include "raceLogic.h"
102 |
103 | #include "wifiManagerHandler.h"
104 |
105 | WiFiClientSecure secured_client;
106 |
107 | FileFetcher fileFetcher(secured_client);
108 |
109 | // ----------------------------
110 | // Display Handling Code
111 | // ----------------------------
112 |
113 | #if defined YELLOW_DISPLAY
114 |
115 | #include "cheapYellowLCD.h"
116 | CheapYellowDisplay cyd;
117 | F1Display *f1Display = &cyd;
118 |
119 | #elif defined MATRIX_DISPLAY
120 |
121 | #include "matrixDisplay.h"
122 | MatrixDisplay matrixDisplay;
123 | F1Display *f1Display = &matrixDisplay;
124 |
125 | #endif
126 | // ----------------------------
127 |
128 | UniversalTelegramBot bot("", secured_client);
129 |
130 | F1Config f1Config;
131 |
132 | void setup()
133 | {
134 | // put your setup code here, to run once:
135 |
136 | Serial.begin(115200);
137 |
138 | f1Display->displaySetup();
139 |
140 | bool forceConfig = false;
141 |
142 | drd = new DoubleResetDetector(DRD_TIMEOUT, DRD_ADDRESS);
143 | if (drd->detectDoubleReset())
144 | {
145 | Serial.println(F("Forcing config mode as there was a Double reset detected"));
146 | forceConfig = true;
147 | }
148 |
149 | // Initialise SPIFFS, if this fails try .begin(true)
150 | // NOTE: I believe this formats it though it will erase everything on
151 | // spiffs already! In this example that is not a problem.
152 | // I have found once I used the true flag once, I could use it
153 | // without the true flag after that.
154 | bool spiffsInitSuccess = SPIFFS.begin(false) || SPIFFS.begin(true);
155 | if (!spiffsInitSuccess)
156 | {
157 | Serial.println("SPIFFS initialisation failed!");
158 | while (1)
159 | yield(); // Stay here twiddling thumbs waiting
160 | }
161 | Serial.println("\r\nInitialisation done.");
162 |
163 | if (!f1Config.fetchConfigFile())
164 | {
165 | // Failed to fetch config file, need to launch Wifi Manager
166 | forceConfig = true;
167 | }
168 |
169 | setupWiFiManager(forceConfig, f1Config, f1Display);
170 | raceLogicSetup(f1Config);
171 | bot.updateToken(f1Config.botToken);
172 |
173 | // Set WiFi to station mode and disconnect from an AP if it was Previously
174 | // connected
175 | // WiFi.mode(WIFI_STA);
176 | // WiFi.begin(ssid, password);
177 |
178 | while (WiFi.status() != WL_CONNECTED)
179 | {
180 | Serial.print(".");
181 | delay(500);
182 | }
183 |
184 | Serial.println("");
185 | Serial.println("WiFi connected");
186 | Serial.print("IP address: ");
187 | Serial.println(WiFi.localIP());
188 |
189 | secured_client.setCACert(github_server_cert);
190 | while (fetchRaceJson(fileFetcher) != 1)
191 | {
192 | Serial.println("failed to get Race Json");
193 | Serial.println("will try again in 10 seconds");
194 | delay(1000 * 10);
195 | }
196 |
197 | Serial.println("Fetched races.json File");
198 |
199 | Serial.println("Waiting for time sync");
200 |
201 | waitForSync();
202 |
203 | Serial.println();
204 | Serial.println("UTC: " + UTC.dateTime());
205 |
206 | myTZ.setLocation(f1Config.timeZone);
207 | Serial.print(f1Config.timeZone);
208 | Serial.print(F(": "));
209 | Serial.println(myTZ.dateTime());
210 | Serial.println("-------------------------");
211 |
212 | // sendNotificationOfNextRace(&bot, f1Config.roundOffset);
213 | }
214 |
215 | bool notificaitonEventRaised = false;
216 |
217 | void sendNotification()
218 | {
219 | // Cause it could be set to the image one
220 | if (f1Config.isTelegramConfigured())
221 | {
222 | secured_client.setCACert(TELEGRAM_CERTIFICATE_ROOT);
223 | Serial.println("Sending notifcation");
224 | f1Config.currentRaceNotification = sendNotificationOfNextRace(&bot);
225 | if (!f1Config.currentRaceNotification)
226 | {
227 | // Notificaiton failed, raise event again
228 | Serial.println("Notfication failed");
229 | setEvent(sendNotification, getNotifyTime());
230 | }
231 | else
232 | {
233 | notificaitonEventRaised = false;
234 | f1Config.saveConfigFile();
235 | }
236 | }
237 | else
238 | {
239 |
240 | Serial.println("Would have sent Notification now, but telegram is not configured");
241 |
242 | notificaitonEventRaised = false;
243 | f1Config.currentRaceNotification = true;
244 | f1Config.saveConfigFile();
245 | }
246 | }
247 |
248 | bool first = true;
249 |
250 | int minuteCounter = 60; // kick off fetch first time
251 |
252 | void loop()
253 | {
254 | drd->loop();
255 |
256 | // Every hour we will refresh the Race JSON from Github
257 | if (minuteCounter >= 60)
258 | {
259 | secured_client.setCACert(github_server_cert);
260 | while (fetchRaceJson(fileFetcher) != 1)
261 | {
262 | Serial.println("failed to get Race Json");
263 | Serial.println("will try again in 10 seconds");
264 | delay(1000 * 10);
265 | }
266 | minuteCounter = 0;
267 | }
268 |
269 | if (first || minuteChanged())
270 | {
271 | minuteCounter++;
272 | bool newRace = getNextRace(f1Config.roundOffset, f1Config.currentRaceNotification, f1Display, first);
273 | if (newRace)
274 | {
275 | f1Config.saveConfigFile();
276 | }
277 | if (!f1Config.currentRaceNotification && !notificaitonEventRaised)
278 | {
279 | // we have never notified about this race yet, so we'll raise an event
280 | setEvent(sendNotification, getNotifyTime());
281 | notificaitonEventRaised = true;
282 | Serial.print("Raised event for: ");
283 | Serial.println(myTZ.dateTime(getNotifyTime(), UTC_TIME, f1Config.timeFormat));
284 | }
285 | first = false;
286 | }
287 |
288 | events();
289 | }
290 |
--------------------------------------------------------------------------------
/F1-Notifications/raceLogic.h:
--------------------------------------------------------------------------------
1 | #ifndef RACELOGIC_H
2 | #define RACELOGIC_H
3 |
4 | #define RACE_FILE_NAME "/races.json"
5 | #define CURRENT_RACE_FILE_NAME "/current_races.json"
6 |
7 | // path to the races schedule, needs to be updated each year
8 | #define RACE_JSON_URL "https://raw.githubusercontent.com/sportstimes/f1/main/_db/f1/2025.json"
9 | // Number of days before the race to display circuit image rather than sessions schedule
10 | #define DaysBeforeRace 3
11 |
12 | time_t nextRaceStartUtc;
13 |
14 | Timezone myTZ;
15 | F1Config rl_f1Config;
16 |
17 | void raceLogicSetup(F1Config f1Config)
18 | {
19 | rl_f1Config = f1Config;
20 | }
21 |
22 | bool isSessionInFuture(const char *sessionStartTime)
23 | {
24 | struct tm tm = {0};
25 | // Parse date from UTC and convert to an epoch
26 | strptime(sessionStartTime, "%Y-%m-%dT%H:%M:%S", &tm);
27 | time_t sessionEpoch = mktime(&tm);
28 |
29 | return UTC.now() < sessionEpoch;
30 | }
31 |
32 | bool isRaceWeek(const char *sessionStartTime)
33 | {
34 | struct tm tm = {0};
35 | // Parse date from UTC and convert to an epoch
36 | strptime(sessionStartTime, "%Y-%m-%dT%H:%M:%S", &tm);
37 |
38 | time_t DaysBeforeRaceEpoch = mktime(&tm) - (DaysBeforeRace * SECS_PER_DAY);
39 | return UTC.now() > DaysBeforeRaceEpoch;
40 | }
41 |
42 | String getConvertedTime(const char *sessionStartTime, const char *timeFormat = "")
43 | {
44 | struct tm tm = {0};
45 | // Parse date from UTC and convert to an epoch
46 | strptime(sessionStartTime, "%Y-%m-%dT%H:%M:%S", &tm);
47 | time_t sessionEpoch = mktime(&tm);
48 |
49 | String timeFormatStr = rl_f1Config.timeFormat;
50 | if (timeFormat[0] != 0)
51 | {
52 | timeFormatStr = String(timeFormat);
53 | }
54 | return myTZ.dateTime(sessionEpoch, UTC_TIME, timeFormatStr);
55 | }
56 |
57 | void printConvertedTime(const char *sessionName, const char *sessionStartTime)
58 | {
59 |
60 | String timeStr = getConvertedTime(sessionStartTime, "");
61 | Serial.print(sessionName);
62 | Serial.print(": ");
63 | Serial.println(timeStr);
64 | }
65 |
66 | const char *sessionCodeToString(const char *sessionCode)
67 | {
68 | if (strcmp(sessionCode, "fp1") == 0)
69 | {
70 | return "FP1: ";
71 | }
72 | else if (strcmp(sessionCode, "fp2") == 0)
73 | {
74 | return "FP2: ";
75 | }
76 | else if (strcmp(sessionCode, "fp3") == 0)
77 | {
78 | return "FP3: ";
79 | }
80 | else if (strcmp(sessionCode, "qualifying") == 0)
81 | {
82 | return "Qualifying: ";
83 | }
84 | else if (strcmp(sessionCode, "sprint") == 0)
85 | {
86 | return "Sprint: ";
87 | }
88 | else if (strcmp(sessionCode, "sprintQualifying") == 0)
89 | {
90 | return "Sprint Quali: ";
91 | }
92 | else if (strcmp(sessionCode, "gp") == 0)
93 | {
94 | return "Race: ";
95 | }
96 |
97 | return "UNKNOWN";
98 | }
99 |
100 | void printRaceTimes(const char *raceName, JsonObject races_sessions)
101 | {
102 | Serial.print("Next Race: ");
103 | Serial.println(raceName);
104 |
105 | for (JsonPair kv : races_sessions)
106 | {
107 | printConvertedTime(sessionCodeToString(kv.key().c_str()),
108 | kv.value().as());
109 | }
110 | }
111 |
112 | String createTelegramMessageString(const char *raceName, JsonObject races_sessions)
113 | {
114 | String message = "Next Race: ";
115 | message += raceName;
116 | message += "\n";
117 | message += "---------------------\n";
118 |
119 | for (JsonPair kv : races_sessions)
120 | {
121 | String sessionName = String(sessionCodeToString(kv.key().c_str()));
122 | message += sessionName;
123 | message += getConvertedTime(kv.value().as(), "");
124 | message += "\n";
125 | }
126 | return message;
127 | }
128 |
129 | bool sendNotificationOfNextRace(UniversalTelegramBot *bot)
130 | {
131 |
132 | StaticJsonDocument<112> filter;
133 | filter["name"] = true;
134 | filter["location"] = true;
135 | filter["round"] = true;
136 | filter["sessions"] = true;
137 |
138 | File racesJson = SPIFFS.open(CURRENT_RACE_FILE_NAME);
139 | DynamicJsonDocument race(1000);
140 |
141 | DeserializationError error = deserializeJson(race, racesJson, DeserializationOption::Filter(filter));
142 |
143 | if (error)
144 | {
145 | Serial.print("deserializeJson() failed: ");
146 | Serial.println(error.c_str());
147 | racesJson.close();
148 | return false;
149 | }
150 |
151 | const char *races_name = race["name"];
152 | JsonObject races_sessions = race["sessions"];
153 |
154 | printRaceTimes(races_name, races_sessions);
155 |
156 | Serial.print("Sending message to ");
157 | Serial.println(rl_f1Config.chatId);
158 | racesJson.close();
159 | return bot->sendPhoto(rl_f1Config.chatId, "https://i.imgur.com/q3qsfSi.png", createTelegramMessageString(races_name, races_sessions));
160 | }
161 |
162 | int fetchRaceJson(FileFetcher fileFetcher)
163 | {
164 | // In this example I reuse the same filename
165 | // over and over
166 | if (SPIFFS.exists(RACE_FILE_NAME) == true)
167 | {
168 | Serial.println("Removing existing image");
169 | SPIFFS.remove(RACE_FILE_NAME);
170 | }
171 |
172 | fs::File f = SPIFFS.open(RACE_FILE_NAME, "w+");
173 | if (!f)
174 | {
175 | Serial.println("file open failed");
176 | return -1;
177 | }
178 |
179 | bool gotFile = fileFetcher.getFile(RACE_JSON_URL, &f);
180 |
181 | // Make sure to close the file!
182 | f.close();
183 |
184 | return gotFile;
185 | }
186 |
187 | bool saveCurrentRaceToFile(const JsonObject &raceJson)
188 | {
189 |
190 | if (raceJson.isNull())
191 | {
192 | Serial.println("Race data is null, nothing to save");
193 | return false;
194 | }
195 |
196 | File currentRaceFile = SPIFFS.open(CURRENT_RACE_FILE_NAME, "w");
197 | if (!currentRaceFile)
198 | {
199 | Serial.println("failed to open config file for writing");
200 | return false;
201 | }
202 |
203 | Serial.println("Saving Race Json");
204 | serializeJsonPretty(raceJson, Serial);
205 | if (serializeJson(raceJson, currentRaceFile) == 0)
206 | {
207 | Serial.println(F("Failed to write to file"));
208 | return false;
209 | }
210 | currentRaceFile.close();
211 | return true;
212 | }
213 |
214 | bool getNextRace(int &offset, bool ¬ificationSent, F1Display *f1Display, bool forceRaceFileSave)
215 | {
216 |
217 | StaticJsonDocument<112> filter;
218 |
219 | JsonObject filter_races_0 = filter["races"].createNestedObject();
220 | filter_races_0["name"] = true;
221 | filter_races_0["location"] = true;
222 | filter_races_0["round"] = true;
223 | filter_races_0["sessions"] = true;
224 | filter_races_0["canceled"] = true;
225 |
226 | File racesJson = SPIFFS.open(RACE_FILE_NAME);
227 | DynamicJsonDocument doc(12288);
228 |
229 | DeserializationError error = deserializeJson(doc, racesJson, DeserializationOption::Filter(filter));
230 |
231 | if (error)
232 | {
233 | Serial.print("deserializeJson() failed: ");
234 | Serial.println(error.c_str());
235 | return false;
236 | }
237 | JsonArray races = doc["races"];
238 |
239 | int racesAmount = races.size();
240 | time_t timeNow = UTC.now();
241 | Serial.println();
242 | Serial.println("UTC: " + UTC.dateTime());
243 | for (int i = 0; i < racesAmount; i++)
244 | {
245 |
246 | // serializeJsonPretty(races[i], Serial);
247 |
248 | const char *races_name = races[i]["name"];
249 | JsonObject races_sessions = races[i]["sessions"];
250 | const char *race_sessions_gp = races_sessions["gp"]; // "2023-03-05T15:00:00Z"
251 | bool raceCanceled = races[i]["canceled"].as();
252 |
253 | struct tm tm = {0};
254 |
255 | // Convert to tm struct
256 | // Sample format: 2023-03-17T13:30:00Z
257 | strptime(race_sessions_gp, "%Y-%m-%dT%H:%M:%S", &tm);
258 |
259 | nextRaceStartUtc = mktime(&tm);
260 | if (!raceCanceled && timeNow < nextRaceStartUtc)
261 | {
262 | bool newRace = false;
263 | int roundNumber = races[i]["round"];
264 | if (roundNumber != offset)
265 | {
266 | if (saveCurrentRaceToFile(races[i]))
267 | {
268 | offset = roundNumber;
269 | notificationSent = false;
270 | Serial.println("New Race");
271 | newRace = true;
272 | }
273 | else
274 | {
275 | Serial.println("Got new race, but couldn't save JSON to file");
276 | }
277 | }
278 | else
279 | {
280 | Serial.println("Same Race as before");
281 | if (forceRaceFileSave)
282 | {
283 | if (saveCurrentRaceToFile(races[i]))
284 | {
285 | Serial.println("(Forced Save) Saved race to file");
286 | }
287 | else
288 | {
289 | Serial.println("(Forced Save) Couldn't save JSON to file");
290 | }
291 | }
292 | }
293 |
294 | if (isRaceWeek(race_sessions_gp))
295 | {
296 | f1Display->displayRaceWeek(races_name, races_sessions);
297 | }
298 | else
299 | {
300 | f1Display->displayPlaceHolder(races_name, races_sessions);
301 | }
302 |
303 | printRaceTimes(races_name, races_sessions);
304 | racesJson.close();
305 | return newRace;
306 | }
307 | }
308 | racesJson.close();
309 | return false;
310 | }
311 |
312 | time_t getNotifyTime()
313 | {
314 |
315 | time_t t = nextRaceStartUtc - (6 * SECS_PER_DAY);
316 | // Probably should make this smarter so it's not sending notifications in the middle of the night!
317 | return t;
318 | }
319 |
320 | #endif
321 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # F1-Arduino-Notifications
2 |
3 | An ESP32 project to display and notify of when F1 races are and what time the sessions start at in your local timezone.
4 |
5 | 
6 |
7 | 
8 |
9 | 
10 |
11 | This project can flashed directly from a webpage, so can be setup in minutes!
12 |
13 | ## Features
14 |
15 | - Connects to your Wifi to automatically fetch data about the next race
16 | - When the race is more than a week away, it will display the date of the next race (with an image on the "Cheap Yellow Display")
17 | - When it's race week, it will display the start time of all the sessions in your local timezone
18 | - It will send you notification on Telegram on the Monday of race week with the start times.
19 |
20 | This project is a Work in Progress!
21 |
22 | ## Help Support what I do!
23 |
24 | [If you enjoy my work, please consider becoming a Github sponsor!](https://github.com/sponsors/witnessmenow/)
25 |
26 | ## Hardware Required
27 |
28 | This project is designed to make use of basically ready to go hardware, so is very easy to get up and running
29 |
30 | Currently this project runs on two types of hardware:
31 |
32 | ### "Cheap Yellow Display" (CYD)
33 |
34 | An ESP32 With Built in 320x240 LCD with Touch Screen (ESP32-2432S028R), buy from wherever works out cheapest for you:
35 |
36 | - [Aliexpress\*](https://s.click.aliexpress.com/e/_DkSpIjB)
37 | - [Aliexpress\*](https://s.click.aliexpress.com/e/_DkcmuCh)
38 | - [Aliexpress](https://www.aliexpress.com/item/1005004502250619.htm)
39 | - [Makerfabs](https://www.makerfabs.com/sunton-esp32-2-8-inch-tft-with-touch.html)
40 |
41 | ### Matrix panel
42 |
43 | It's built to work with the [ESP32 Trinity](https://github.com/witnessmenow/ESP32-Trinity), an open source board I created for controlling Hub75 Matrix panels, but it will does work with any ESP32 that breaks out enough pins.
44 |
45 | The display it uses is a 64x64 HUB75 Matrix Panel.
46 |
47 | All the parts can be purchased from Makerfabs.com:
48 |
49 | - [ESP32 Trinity](https://www.makerfabs.com/esp32-trinity.html)
50 | - [64 x 64 Matrix Panel](https://www.makerfabs.com/64x64-rgb-led-matrix-3mm-pitch.html)
51 | - Optional: [5V Power Supply](https://www.makerfabs.com/5v-6a-ac-dc-power-adapter-with-cable.html) - You can alternatively use a USB-C power supply
52 |
53 | \* = Affilate Link
54 |
55 | ### BYOD (Bring your own display)
56 |
57 | I've tried to design this project to be modular and have abstracted the display code behind an interface, so it should be pretty easy to get it up and running with a different type of display.
58 |
59 | ## Project Setup
60 |
61 | These steps will only need to be run once.
62 |
63 | ### Step 1 - Telegram Setup (Optional)
64 |
65 | To get notified about the races, you will need some things on Telegram.
66 |
67 | - Install Telegram and setup an account.
68 | - Search for a user called "botFather" and follow instructions to create a new bot. Keep note of this bot token
69 | - Start the chat with the created bot.
70 | - Search for a user called "myIdBot" and follow instructions to get your chat ID. Keep note of the chatID.
71 |
72 | ### Step 2 - Flash the Project
73 |
74 | This project can be flashed directly from your browser [here](https://witnessmenow.github.io/F1-Arduino-Notifications/) (Chrome & Edge only)
75 |
76 | - For the "Cheap Yellow Display" (CYD):
77 | - If your CYD has one USB port, Click the "CYD" button
78 | - If it has two USB ports, Click the "CYD2USB" button
79 | - For ESP32 Trinity/Matrix panel
80 | - Click the "Matrix" button
81 |
82 | This webflash code is automatically built from the main branch of this repo, so it will always be up to date.
83 |
84 | Note: If you want to program this project yourself without the webflash, follow the **Code** steps below.
85 |
86 | ### Step 3 - Adding your Wifi, Timezone and Telegram Details
87 |
88 | In order to enter your wifi details, the project will host it's own wifi network. Connect to it using your phone.
89 |
90 | - SSID: f1Thing
91 | - Password: nomikey1
92 |
93 | You should be automatically redirected to the config page.
94 |
95 | - Click Config
96 | - Enter your WIfi details
97 | - Enter your **Time Zone** - More info [here](https://github.com/ropg/ezTime#setlocation)
98 | - (Optional) Enter your **Bot Token** that you retrieved at the earlier step.
99 | - (Optional) Enter your **Chat ID** that you retrieved at the earlier step.
100 | - You can leave the other options
101 | - Click save
102 |
103 | Note: If you ever need to get back into WiFiManager, click reset button twice.
104 |
105 | The project should now be setup and start displaying the next race details!
106 |
107 | ## Code
108 |
109 | If you want to program this project manually, there are two options
110 |
111 | ### PlatformIO
112 |
113 | PlatformIO is the easiest way to code this project.
114 |
115 | In the [platformio.ini](platformio.ini), there are several environments defined for the different boards
116 |
117 | | Environment | Description |
118 | | ----------- | ---------------------------------------------------------------------------------------------------------------------------- |
119 | | env:cyd | For the [Cheap Yellow Display](https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display) |
120 | | env:cyd2usb | For the Cheap Yellow Display with two USB ports |
121 | | env:trinity | For the [ESP32 Trinity](https://github.com/witnessmenow/ESP32-Trinity) (or generic ESP32 wired to the matrix panel the same) |
122 |
123 | When you select the environment, it will automatically install the right libraries and set the configurations in the code.
124 |
125 | ### Arduino IDE
126 |
127 | If you want to use the Arduino IDE, you will need to do the following to get it workin
128 |
129 | The following libraries need to be installed for this project to work:
130 |
131 | | Library Name/Link | Purpose | Library manager |
132 | | -------------------------------------------------------------------------------------- | ------------------------------------------ | ------------------------------- |
133 | | [WifiManager - By Tzapu](https://github.com/tzapu/WiFiManager) | Captive portal for configuring the WiFi | Yes ("WifiManager" |
134 | | [ESP_DoubleResetDetector](https://github.com/khoih-prog/ESP_DoubleResetDetector) | Detecting double pressing the reset button | Yes ("ESP_DoubleResetDetector") |
135 | | [ArduinoJson](https://github.com/bblanchon/ArduinoJson) | For parsing JSON | Yes ("Arduino Json") |
136 | | [ezTime](https://github.com/ropg/ezTime) | For handling timezones | Yes ("eztime") |
137 | | [UniversalTelegramBot](https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot) | Telegram bots for your ESP | Yes ("UniversalTelegramBot") |
138 | | [FileFetcher](https://github.com/witnessmenow/file-fetcher-arduino) | For fetching files/images from the web | No, download from Github |
139 |
140 | #### Cheap Yellow Display Specific libraries
141 |
142 | | Library Name/Link | Purpose | Library manager |
143 | | ---------------------------------------------- | ------------------------------- | ---------------- |
144 | | [TFT_eSPI](https://github.com/Bodmer/TFT_eSPI) | For controlling the LCD Display | Yes ("tft_espi") |
145 | | [PNGdec](https://github.com/bitbank2/PNGdec) | For decoding png images | Yes ("PNGdec") |
146 |
147 | #### Matrix Panel Specific libraries
148 |
149 | | Library Name/Link | Purpose | Library manager |
150 | | ------------------------------------------------------------------------------------------------- | -------------------------------- | ------------------------ |
151 | | [ESP32-HUB75-MatrixPanel-I2S-DMA](https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-I2S-DMA) | For controlling the LED Matrix | Yes ("ESP32 MATRIX DMA") |
152 | | [Adafruit GFX library](https://github.com/adafruit/Adafruit-GFX-Library) | Dependancy of the Matrix library | Yes ("Adafruit GFX") |
153 |
154 | #### Cheap Yellow Display Display Config
155 |
156 | The CYD version of the project makes use of [TFT_eSPI library by Bodmer](https://github.com/Bodmer/TFT_eSPI).
157 |
158 | TFT_eSPI is configured using a "User_Setup.h" file in the library folder, you will need to replace this file with the one from the CYD repo.
159 |
160 | - CYD (CYD with single USB): https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/blob/main/DisplayConfig/User_Setup.h
161 | - CYD2USB (CYD with 2 USB): https://github.com/witnessmenow/ESP32-Cheap-Yellow-Display/blob/main/DisplayConfig/CYD2USB/User_Setup.h
162 |
163 | #### Display Selection
164 |
165 | At the top of the `F1-Notifications.ino` file, there is a section labeled "Display Type", follow the instructions there for how to enable the different displays.
166 |
167 | By default it will use the Cheap Yellow Display
168 |
169 | Note: CYD and CYD2USB both use the `YELLOW_DISPLAY` define.
170 |
--------------------------------------------------------------------------------
/F1-Notifications/races.h:
--------------------------------------------------------------------------------
1 | // This file is old and not used anymore, it now fetches this file from Github
2 | // but it can be useful for debugging, so I'll leave it here
3 |
4 | R"(
5 | {
6 | "races": [
7 | {
8 | "name": "Bahrain",
9 | "location": "Sakhir",
10 | "latitude": 26.037,
11 | "longitude": 50.5112,
12 | "round": 1,
13 | "slug": "bahrain-grand-prix",
14 | "localeKey": "bahrain-grand-prix",
15 | "circuitImage": "https://imgur.com/zUqArqi.png",
16 | "sessions": {
17 | "fp1": "2023-03-03T11:30:00Z",
18 | "fp2": "2023-03-03T15:00:00Z",
19 | "fp3": "2023-03-04T11:30:00Z",
20 | "qualifying": "2023-03-04T15:00:00Z",
21 | "gp": "2023-03-05T15:00:00Z"
22 | }
23 | },
24 | {
25 | "name": "Saudi Arabian",
26 | "location": "Jeddah",
27 | "latitude": 21.485811,
28 | "longitude": 39.192505,
29 | "round": 2,
30 | "slug": "saudi-arabia-grand-prix",
31 | "localeKey": "saudi-arabia-grand-prix",
32 | "circuitImage": "https://imgur.com/vx6MDSF.png",
33 | "sessions": {
34 | "fp1": "2023-03-17T13:30:00Z",
35 | "fp2": "2023-03-17T17:00:00Z",
36 | "fp3": "2023-03-18T13:30:00Z",
37 | "qualifying": "2023-03-18T17:00:00Z",
38 | "gp": "2023-03-19T17:00:00Z"
39 | }
40 | },
41 | {
42 | "name": "Australian",
43 | "location": "Melbourne",
44 | "latitude": -37.8373,
45 | "longitude": 144.9666,
46 | "round": 3,
47 | "slug": "australian-grand-prix",
48 | "localeKey": "australian-grand-prix",
49 | "circuitImage": "https://i.imgur.com/ewrhVKU.png",
50 | "sessions": {
51 | "fp1": "2023-03-31T01:30:00Z",
52 | "fp2": "2023-03-31T05:00:00Z",
53 | "fp3": "2023-04-01T01:30:00Z",
54 | "qualifying": "2023-04-01T05:00:00Z",
55 | "gp": "2023-04-02T05:00:00Z"
56 | }
57 | },
58 | {
59 | "name": "Azerbaijan",
60 | "location": "Baku",
61 | "latitude": 40.3699,
62 | "longitude": 49.8433,
63 | "round": 5,
64 | "slug": "azerbaijan-grand-prix",
65 | "localeKey": "azerbaijan-grand-prix",
66 | "circuitImage": "https://imgur.com/H2C6G2Q.png",
67 | "sessions": {
68 | "fp1": "2023-04-28T09:30:00Z",
69 | "qualifying": "2023-04-28T13:00:00Z",
70 | "fp2": "2023-04-29T09:30:00Z",
71 | "sprint": "2023-04-29T13:30:00Z",
72 | "gp": "2023-04-30T11:00:00Z"
73 | }
74 | },
75 | {
76 | "name": "Miami",
77 | "location": "Miami",
78 | "latitude": 25.957764,
79 | "longitude": -80.238835,
80 | "round": 6,
81 | "slug": "miami-grand-prix",
82 | "localeKey": "miami-grand-prix",
83 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245035/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Miami%20carbon.png.transform/3col/image.png",
84 | "sessions": {
85 | "fp1": "2023-05-05T18:30:00Z",
86 | "fp2": "2023-05-05T22:00:00Z",
87 | "fp3": "2023-05-06T16:30:00Z",
88 | "qualifying": "2023-05-06T20:00:00Z",
89 | "gp": "2023-05-07T19:30:00Z"
90 | }
91 | },
92 | {
93 | "name": "Emilia Romagna Grand Prix",
94 | "location": "Imola",
95 | "latitude": 44.344576,
96 | "longitude": 11.713808,
97 | "round": 7,
98 | "slug": "emilia-romagna-grand-prix",
99 | "localeKey": "emilia-romagna-grand-prix",
100 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245032/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Emilia%20Romagna%20carbon.png.transform/3col/image.png",
101 | "sessions": {
102 | "fp1": "2023-05-19T11:30:00Z",
103 | "fp2": "2023-05-19T15:00:00Z",
104 | "fp3": "2023-05-20T10:30:00Z",
105 | "qualifying": "2023-05-20T14:00:00Z",
106 | "gp": "2023-05-21T13:00:00Z"
107 | }
108 | },
109 | {
110 | "name": "Monaco",
111 | "location": "Monte Carlo",
112 | "latitude": 43.7338,
113 | "longitude": 7.4215,
114 | "round": 8,
115 | "slug": "monaco-grand-prix",
116 | "localeKey": "monaco-grand-prix",
117 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245032/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Monte%20Carlo%20carbon.png.transform/3col/image.png",
118 | "sessions": {
119 | "fp1": "2023-05-26T11:30:00Z",
120 | "fp2": "2023-05-26T15:00:00Z",
121 | "fp3": "2023-05-27T10:30:00Z",
122 | "qualifying": "2023-05-27T14:00:00Z",
123 | "gp": "2023-05-28T13:00:00Z"
124 | }
125 | },
126 | {
127 | "name": "Spanish",
128 | "location": "Catalunya",
129 | "latitude": 41.5638,
130 | "longitude": 2.2585,
131 | "round": 9,
132 | "slug": "spanish-grand-prix",
133 | "localeKey": "spanish-grand-prix",
134 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245030/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Spain%20carbon.png.transform/3col/image.png",
135 | "sessions": {
136 | "fp1": "2023-06-02T11:30:00Z",
137 | "fp2": "2023-06-02T15:00:00Z",
138 | "fp3": "2023-06-03T10:30:00Z",
139 | "qualifying": "2023-06-03T14:00:00Z",
140 | "gp": "2023-06-04T13:00:00Z"
141 | }
142 | },
143 | {
144 | "name": "Canadian",
145 | "location": "Montreal",
146 | "latitude": 45.5034,
147 | "longitude": -73.5267,
148 | "round": 10,
149 | "slug": "canadian-grand-prix",
150 | "localeKey": "canadian-grand-prix",
151 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Canada%20carbon.png.transform/3col/image.png",
152 | "sessions": {
153 | "fp1": "2023-06-16T17:30:00Z",
154 | "fp2": "2023-06-16T21:00:00Z",
155 | "fp3": "2023-06-17T16:30:00Z",
156 | "qualifying": "2023-06-17T20:00:00Z",
157 | "gp": "2023-06-18T18:00:00Z"
158 | }
159 | },
160 | {
161 | "name": "Austrian",
162 | "location": "Spielberg",
163 | "latitude": 47.2225,
164 | "longitude": 14.7607,
165 | "round": 11,
166 | "slug": "austrian-grand-prix",
167 | "localeKey": "austrian-grand-prix",
168 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Austria%20carbon.png.transform/3col/image.png",
169 | "sessions": {
170 | "fp1": "2023-06-30T11:30:00Z",
171 | "qualifying": "2023-06-30T15:00:00Z",
172 | "fp2": "2023-07-01T10:30:00Z",
173 | "sprint": "2023-07-01T14:30:00Z",
174 | "gp": "2023-07-02T13:00:00Z"
175 | }
176 | },
177 | {
178 | "name": "British",
179 | "location": "Silverstone",
180 | "latitude": 52.0706,
181 | "longitude": -1.0174,
182 | "round": 12,
183 | "slug": "british-grand-prix",
184 | "localeKey": "british-grand-prix",
185 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Great%20Britain%20carbon.png.transform/3col/image.png",
186 | "sessions": {
187 | "fp1": "2023-07-07T11:30:00Z",
188 | "fp2": "2023-07-07T15:00:00Z",
189 | "fp3": "2023-07-08T10:30:00Z",
190 | "qualifying": "2023-07-08T14:00:00Z",
191 | "gp": "2023-07-09T14:00:00Z"
192 | }
193 | },
194 | {
195 | "name": "Hungarian",
196 | "location": "Budapest",
197 | "latitude": 47.583,
198 | "longitude": 19.2526,
199 | "round": 13,
200 | "slug": "hungarian-grand-prix",
201 | "localeKey": "hungarian-grand-prix",
202 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Hungar%20carbon.png.transform/3col/image.png",
203 | "sessions": {
204 | "fp1": "2023-07-21T11:30:00Z",
205 | "fp2": "2023-07-21T15:00:00Z",
206 | "fp3": "2023-07-22T10:30:00Z",
207 | "qualifying": "2023-07-22T14:00:00Z",
208 | "gp": "2023-07-23T13:00:00Z"
209 | }
210 | },
211 | {
212 | "name": "Belgian",
213 | "location": "Spa-Francorchamps",
214 | "latitude": 50.444,
215 | "longitude": 5.9687,
216 | "round": 14,
217 | "slug": "belgian-grand-prix",
218 | "localeKey": "belgian-grand-prix",
219 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Belgium%20carbon.png.transform/3col/image.png",
220 | "sessions": {
221 | "fp1": "2023-07-28T11:30:00Z",
222 | "qualifying": "2023-07-28T15:00:00Z",
223 | "fp2": "2023-07-29T10:30:00Z",
224 | "sprint": "2023-07-29T14:30:00Z",
225 | "gp": "2023-07-30T13:00:00Z"
226 | }
227 | },
228 | {
229 | "name": "Dutch",
230 | "location": "Zandvoort",
231 | "latitude": 52.388408,
232 | "longitude": 4.547122,
233 | "round": 15,
234 | "slug": "dutch-grand-prix",
235 | "localeKey": "dutch-grand-prix",
236 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245033/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Netherlands%20carbon.png.transform/3col/image.png",
237 | "sessions": {
238 | "fp1": "2023-08-25T10:30:00Z",
239 | "fp2": "2023-08-25T14:00:00Z",
240 | "fp3": "2023-08-26T09:30:00Z",
241 | "qualifying": "2023-08-26T13:00:00Z",
242 | "gp": "2023-08-27T13:00:00Z"
243 | }
244 | },
245 | {
246 | "name": "Italian",
247 | "location": "Monza",
248 | "latitude": 45.6169,
249 | "longitude": 9.2825,
250 | "round": 16,
251 | "slug": "italian-grand-prix",
252 | "localeKey": "italian-grand-prix",
253 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245031/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Italy%20carbon.png.transform/3col/image.png",
254 | "sessions": {
255 | "fp1": "2023-09-01T11:30:00Z",
256 | "fp2": "2023-09-01T15:00:00Z",
257 | "fp3": "2023-09-02T10:30:00Z",
258 | "qualifying": "2023-09-02T14:00:00Z",
259 | "gp": "2023-09-03T13:00:00Z"
260 | }
261 | },
262 | {
263 | "name": "Singapore",
264 | "location": "Singapore",
265 | "latitude": 1.2857,
266 | "longitude": 103.8575,
267 | "round": 17,
268 | "slug": "singapore-grand-prix",
269 | "localeKey": "singapore-grand-prix",
270 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245031/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Singapor%20carbon.png.transform/3col/image.png",
271 | "sessions": {
272 | "fp1": "2023-09-15T09:30:00Z",
273 | "fp2": "2023-09-15T13:00:00Z",
274 | "fp3": "2023-09-16T09:30:00Z",
275 | "qualifying": "2023-09-16T13:00:00Z",
276 | "gp": "2023-09-17T12:00:00Z"
277 | }
278 | },
279 | {
280 | "name": "Japanese",
281 | "location": "Suzuka",
282 | "latitude": 35.3689,
283 | "longitude": 138.9256,
284 | "round": 18,
285 | "slug": "japanese-grand-prix",
286 | "localeKey": "japanese-grand-prix",
287 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245031/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Japan%20carbon.png.transform/3col/image.png",
288 | "sessions": {
289 | "fp1": "2023-09-22T02:30:00Z",
290 | "fp2": "2023-09-22T06:00:00Z",
291 | "fp3": "2023-09-23T02:30:00Z",
292 | "qualifying": "2023-09-23T06:00:00Z",
293 | "gp": "2023-09-24T05:00:00Z"
294 | }
295 | },
296 | {
297 | "name": "Qatar",
298 | "location": "Doha",
299 | "latitude": 25.490292,
300 | "longitude": 51.453030,
301 | "round": 19,
302 | "slug": "qatar-grand-prix",
303 | "localeKey": "qatar-grand-prix",
304 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245031/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Qatar%20carbon.png.transform/3col/image.png",
305 | "sessions": {
306 | "fp1": "2023-10-06T13:30:00Z",
307 | "qualifying": "2023-10-06T17:00:00Z",
308 | "fp2": "2023-10-07T13:30:00Z",
309 | "sprint": "2023-10-07T17:30:00Z",
310 | "gp": "2023-10-08T17:00:00Z"
311 | }
312 | },
313 | {
314 | "name": "United States",
315 | "location": "Austin",
316 | "latitude": 30.1328,
317 | "longitude": -97.6411,
318 | "round": 20,
319 | "slug": "us-grand-prix",
320 | "localeKey": "us-grand-prix",
321 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677245035/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/USA%20carbon.png.transform/3col/image.png",
322 | "sessions": {
323 | "fp1": "2023-10-20T17:30:00Z",
324 | "qualifying": "2023-10-20T21:00:00Z",
325 | "fp2": "2023-10-21T18:00:00Z",
326 | "sprint": "2023-10-21T22:00:00Z",
327 | "gp": "2023-10-22T19:00:00Z"
328 | }
329 | },
330 | {
331 | "name": "Mexican",
332 | "location": "Mexico City",
333 | "latitude": 19.4028,
334 | "longitude": -99.0986,
335 | "round": 21,
336 | "slug": "mexican-grand-prix",
337 | "localeKey": "mexico-grand-prix",
338 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Mexico%20carbon.png.transform/3col/image.png",
339 | "sessions": {
340 | "fp1": "2023-10-27T18:30:00Z",
341 | "fp2": "2023-10-27T22:00:00Z",
342 | "fp3": "2023-10-28T17:30:00Z",
343 | "qualifying": "2023-10-28T21:00:00Z",
344 | "gp": "2023-10-29T20:00:00Z"
345 | }
346 | },
347 | {
348 | "name": "Brazilian",
349 | "location": "Sao Paulo",
350 | "latitude": -23.7014,
351 | "longitude": -46.6969,
352 | "round": 22,
353 | "slug": "brazilian-grand-prix",
354 | "localeKey": "brazilian-grand-prix",
355 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Brazil%20carbon.png.transform/3col/image.png",
356 | "sessions": {
357 | "fp1": "2023-11-03T14:30:00Z",
358 | "qualifying": "2023-11-03T18:00:00Z",
359 | "fp2": "2023-11-04T14:30:00Z",
360 | "sprint": "2023-11-04T18:30:00Z",
361 | "gp": "2023-11-05T17:00:00Z"
362 | }
363 | },
364 | {
365 | "name": "Las Vegas",
366 | "location": "Las Vegas",
367 | "latitude": 36.166747,
368 | "longitude": -115.148708,
369 | "round": 23,
370 | "slug": "las-vegas-grand-prix",
371 | "localeKey": "las-vegas-grand-prix",
372 | "circuitImage": "https://media.formula1.com/image/upload/f_auto/q_auto/v1677249931/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Las%20Vegas%20carbon.png.transform/3col/image.png",
373 | "sessions": {
374 | "fp1": "2023-11-17T04:30:00Z",
375 | "fp2": "2023-11-17T08:00:00Z",
376 | "fp3": "2023-11-18T04:30:00Z",
377 | "qualifying": "2023-11-18T08:00:00Z",
378 | "gp": "2023-11-19T06:00:00Z"
379 | }
380 | },
381 | {
382 | "name": "Abu Dhabi",
383 | "location": "Yas Marina",
384 | "latitude": 24.4821,
385 | "longitude": 54.3482,
386 | "round": 24,
387 | "slug": "abu-dhabi-grand-prix",
388 | "localeKey": "abu-dhabi-grand-prix",
389 | "circuitImage": "https://media.formula1.com/image/upload/content/dam/fom-website/2018-redesign-assets/Track%20icons%204x3/Abu%20Dhab%20carbon.png.transform/3col/image.png",
390 | "sessions": {
391 | "fp1": "2023-11-24T09:30:00Z",
392 | "fp2": "2023-11-24T13:00:00Z",
393 | "fp3": "2023-11-25T10:30:00Z",
394 | "qualifying": "2023-11-25T14:00:00Z",
395 | "gp": "2023-11-26T13:00:00Z"
396 | }
397 | }
398 | ]
399 | }
400 | )"
401 |
--------------------------------------------------------------------------------
/DisplayConfig/User_Setup.h:
--------------------------------------------------------------------------------
1 | // USER DEFINED SETTINGS
2 | // Set driver type, fonts to be loaded, pins used and SPI control method etc
3 | //
4 | // See the User_Setup_Select.h file if you wish to be able to define multiple
5 | // setups and then easily select which setup file is used by the compiler.
6 | //
7 | // If this file is edited correctly then all the library example sketches should
8 | // run without the need to make any more changes for a particular hardware setup!
9 | // Note that some sketches are designed for a particular TFT pixel width/height
10 |
11 | // User defined information reported by "Read_User_Setup" test & diagnostics example
12 | #define USER_SETUP_INFO "User_Setup"
13 |
14 | // Define to disable all #warnings in library (can be put in User_Setup_Select.h)
15 | //#define DISABLE_ALL_LIBRARY_WARNINGS
16 |
17 | // ##################################################################################
18 | //
19 | // Section 1. Call up the right driver file and any options for it
20 | //
21 | // ##################################################################################
22 |
23 | // Define STM32 to invoke optimised processor support (only for STM32)
24 | //#define STM32
25 |
26 | // Defining the STM32 board allows the library to optimise the performance
27 | // for UNO compatible "MCUfriend" style shields
28 | //#define NUCLEO_64_TFT
29 | //#define NUCLEO_144_TFT
30 |
31 | // STM32 8 bit parallel only:
32 | // If STN32 Port A or B pins 0-7 are used for 8 bit parallel data bus bits 0-7
33 | // then this will improve rendering performance by a factor of ~8x
34 | //#define STM_PORTA_DATA_BUS
35 | //#define STM_PORTB_DATA_BUS
36 |
37 | // Tell the library to use parallel mode (otherwise SPI is assumed)
38 | //#define TFT_PARALLEL_8_BIT
39 | //#defined TFT_PARALLEL_16_BIT // **** 16 bit parallel ONLY for RP2040 processor ****
40 |
41 | // Display type - only define if RPi display
42 | //#define RPI_DISPLAY_TYPE // 20MHz maximum SPI
43 |
44 | // Only define one driver, the other ones must be commented out
45 | //#define ILI9341_DRIVER // Generic driver for common displays
46 | #define ILI9341_2_DRIVER // Alternative ILI9341 driver, see https://github.com/Bodmer/TFT_eSPI/issues/1172
47 | //#define ST7735_DRIVER // Define additional parameters below for this display
48 | //#define ILI9163_DRIVER // Define additional parameters below for this display
49 | //#define S6D02A1_DRIVER
50 | //#define RPI_ILI9486_DRIVER // 20MHz maximum SPI
51 | //#define HX8357D_DRIVER
52 | //#define ILI9481_DRIVER
53 | //#define ILI9486_DRIVER
54 | //#define ILI9488_DRIVER // WARNING: Do not connect ILI9488 display SDO to MISO if other devices share the SPI bus (TFT SDO does NOT tristate when CS is high)
55 | //#define ST7789_DRIVER // Full configuration option, define additional parameters below for this display
56 | //#define ST7789_2_DRIVER // Minimal configuration option, define additional parameters below for this display
57 | //#define R61581_DRIVER
58 | //#define RM68140_DRIVER
59 | //#define ST7796_DRIVER
60 | //#define SSD1351_DRIVER
61 | //#define SSD1963_480_DRIVER
62 | //#define SSD1963_800_DRIVER
63 | //#define SSD1963_800ALT_DRIVER
64 | //#define ILI9225_DRIVER
65 | //#define GC9A01_DRIVER
66 |
67 | // Some displays support SPI reads via the MISO pin, other displays have a single
68 | // bi-directional SDA pin and the library will try to read this via the MOSI line.
69 | // To use the SDA line for reading data from the TFT uncomment the following line:
70 |
71 | // #define TFT_SDA_READ // This option is for ESP32 ONLY, tested with ST7789 and GC9A01 display only
72 |
73 | // For ST7735, ST7789 and ILI9341 ONLY, define the colour order IF the blue and red are swapped on your display
74 | // Try ONE option at a time to find the correct colour order for your display
75 |
76 | // #define TFT_RGB_ORDER TFT_RGB // Colour order Red-Green-Blue
77 | // #define TFT_RGB_ORDER TFT_BGR // Colour order Blue-Green-Red
78 |
79 | // For M5Stack ESP32 module with integrated ILI9341 display ONLY, remove // in line below
80 |
81 | // #define M5STACK
82 |
83 | // For ST7789, ST7735, ILI9163 and GC9A01 ONLY, define the pixel width and height in portrait orientation
84 | // #define TFT_WIDTH 80
85 | // #define TFT_WIDTH 128
86 | // #define TFT_WIDTH 172 // ST7789 172 x 320
87 | #define TFT_WIDTH 240 // ST7789 240 x 240 and 240 x 320
88 | // #define TFT_HEIGHT 160
89 | // #define TFT_HEIGHT 128
90 | // #define TFT_HEIGHT 240 // ST7789 240 x 240
91 | #define TFT_HEIGHT 320 // ST7789 240 x 320
92 | // #define TFT_HEIGHT 240 // GC9A01 240 x 240
93 |
94 | // For ST7735 ONLY, define the type of display, originally this was based on the
95 | // colour of the tab on the screen protector film but this is not always true, so try
96 | // out the different options below if the screen does not display graphics correctly,
97 | // e.g. colours wrong, mirror images, or stray pixels at the edges.
98 | // Comment out ALL BUT ONE of these options for a ST7735 display driver, save this
99 | // this User_Setup file, then rebuild and upload the sketch to the board again:
100 |
101 | // #define ST7735_INITB
102 | // #define ST7735_GREENTAB
103 | // #define ST7735_GREENTAB2
104 | // #define ST7735_GREENTAB3
105 | // #define ST7735_GREENTAB128 // For 128 x 128 display
106 | // #define ST7735_GREENTAB160x80 // For 160 x 80 display (BGR, inverted, 26 offset)
107 | // #define ST7735_ROBOTLCD // For some RobotLCD arduino shields (128x160, BGR, https://docs.arduino.cc/retired/getting-started-guides/TFT)
108 | // #define ST7735_REDTAB
109 | // #define ST7735_BLACKTAB
110 | // #define ST7735_REDTAB160x80 // For 160 x 80 display with 24 pixel offset
111 |
112 | // If colours are inverted (white shows as black) then uncomment one of the next
113 | // 2 lines try both options, one of the options should correct the inversion.
114 |
115 | // #define TFT_INVERSION_ON
116 | // #define TFT_INVERSION_OFF
117 |
118 |
119 | // ##################################################################################
120 | //
121 | // Section 2. Define the pins that are used to interface with the display here
122 | //
123 | // ##################################################################################
124 |
125 | // If a backlight control signal is available then define the TFT_BL pin in Section 2
126 | // below. The backlight will be turned ON when tft.begin() is called, but the library
127 | // needs to know if the LEDs are ON with the pin HIGH or LOW. If the LEDs are to be
128 | // driven with a PWM signal or turned OFF/ON then this must be handled by the user
129 | // sketch. e.g. with digitalWrite(TFT_BL, LOW);
130 |
131 | #define TFT_BL 21 // LED back-light control pin
132 | #define TFT_BACKLIGHT_ON HIGH // Level to turn ON back-light (HIGH or LOW)
133 |
134 |
135 |
136 | // We must use hardware SPI, a minimum of 3 GPIO pins is needed.
137 | // Typical setup for ESP8266 NodeMCU ESP-12 is :
138 | //
139 | // Display SDO/MISO to NodeMCU pin D6 (or leave disconnected if not reading TFT)
140 | // Display LED to NodeMCU pin VIN (or 5V, see below)
141 | // Display SCK to NodeMCU pin D5
142 | // Display SDI/MOSI to NodeMCU pin D7
143 | // Display DC (RS/AO)to NodeMCU pin D3
144 | // Display RESET to NodeMCU pin D4 (or RST, see below)
145 | // Display CS to NodeMCU pin D8 (or GND, see below)
146 | // Display GND to NodeMCU pin GND (0V)
147 | // Display VCC to NodeMCU 5V or 3.3V
148 | //
149 | // The TFT RESET pin can be connected to the NodeMCU RST pin or 3.3V to free up a control pin
150 | //
151 | // The DC (Data Command) pin may be labelled AO or RS (Register Select)
152 | //
153 | // With some displays such as the ILI9341 the TFT CS pin can be connected to GND if no more
154 | // SPI devices (e.g. an SD Card) are connected, in this case comment out the #define TFT_CS
155 | // line below so it is NOT defined. Other displays such at the ST7735 require the TFT CS pin
156 | // to be toggled during setup, so in these cases the TFT_CS line must be defined and connected.
157 | //
158 | // The NodeMCU D0 pin can be used for RST
159 | //
160 | //
161 | // Note: only some versions of the NodeMCU provide the USB 5V on the VIN pin
162 | // If 5V is not available at a pin you can use 3.3V but backlight brightness
163 | // will be lower.
164 |
165 |
166 | // ###### EDIT THE PIN NUMBERS IN THE LINES FOLLOWING TO SUIT YOUR ESP8266 SETUP ######
167 |
168 | // For NodeMCU - use pin numbers in the form PIN_Dx where Dx is the NodeMCU pin designation
169 | //#define TFT_CS PIN_D8 // Chip select control pin D8
170 | //#define TFT_DC PIN_D3 // Data Command control pin
171 | //#define TFT_RST PIN_D4 // Reset pin (could connect to NodeMCU RST, see next line)
172 | //#define TFT_RST -1 // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V
173 |
174 | //#define TFT_BL PIN_D1 // LED back-light (only for ST7789 with backlight control pin)
175 |
176 | //#define TOUCH_CS PIN_D2 // Chip select pin (T_CS) of touch screen
177 |
178 | //#define TFT_WR PIN_D2 // Write strobe for modified Raspberry Pi TFT only
179 |
180 |
181 | // ###### FOR ESP8266 OVERLAP MODE EDIT THE PIN NUMBERS IN THE FOLLOWING LINES ######
182 |
183 | // Overlap mode shares the ESP8266 FLASH SPI bus with the TFT so has a performance impact
184 | // but saves pins for other functions. It is best not to connect MISO as some displays
185 | // do not tristate that line when chip select is high!
186 | // Note: Only one SPI device can share the FLASH SPI lines, so a SPI touch controller
187 | // cannot be connected as well to the same SPI signals.
188 | // On NodeMCU 1.0 SD0=MISO, SD1=MOSI, CLK=SCLK to connect to TFT in overlap mode
189 | // On NodeMCU V3 S0 =MISO, S1 =MOSI, S2 =SCLK
190 | // In ESP8266 overlap mode the following must be defined
191 |
192 | //#define TFT_SPI_OVERLAP
193 |
194 | // In ESP8266 overlap mode the TFT chip select MUST connect to pin D3
195 | //#define TFT_CS PIN_D3
196 | //#define TFT_DC PIN_D5 // Data Command control pin
197 | //#define TFT_RST PIN_D4 // Reset pin (could connect to NodeMCU RST, see next line)
198 | //#define TFT_RST -1 // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V
199 |
200 |
201 | // ###### EDIT THE PIN NUMBERS IN THE LINES FOLLOWING TO SUIT YOUR ESP32 SETUP ######
202 |
203 | // For ESP32 Dev board (only tested with ILI9341 display)
204 | // The hardware SPI can be mapped to any pins
205 |
206 | #define TFT_MISO 12
207 | #define TFT_MOSI 13
208 | #define TFT_SCLK 14
209 | #define TFT_CS 15 // Chip select control pin
210 | #define TFT_DC 2 // Data Command control pin
211 | //#define TFT_RST 4 // Reset pin (could connect to RST pin)
212 | #define TFT_RST -1 // Set TFT_RST to -1 if display RESET is connected to ESP32 board RST
213 |
214 | // For ESP32 Dev board (only tested with GC9A01 display)
215 | // The hardware SPI can be mapped to any pins
216 |
217 | //#define TFT_MOSI 15 // In some display driver board, it might be written as "SDA" and so on.
218 | //#define TFT_SCLK 14
219 | //#define TFT_CS 5 // Chip select control pin
220 | //#define TFT_DC 27 // Data Command control pin
221 | //#define TFT_RST 33 // Reset pin (could connect to Arduino RESET pin)
222 | //#define TFT_BL 22 // LED back-light
223 |
224 | //#define TOUCH_CS 21 // Chip select pin (T_CS) of touch screen
225 |
226 | //#define TFT_WR 22 // Write strobe for modified Raspberry Pi TFT only
227 |
228 | // For the M5Stack module use these #define lines
229 | //#define TFT_MISO 19
230 | //#define TFT_MOSI 23
231 | //#define TFT_SCLK 18
232 | //#define TFT_CS 14 // Chip select control pin
233 | //#define TFT_DC 27 // Data Command control pin
234 | //#define TFT_RST 33 // Reset pin (could connect to Arduino RESET pin)
235 | //#define TFT_BL 32 // LED back-light (required for M5Stack)
236 |
237 | // ###### EDIT THE PINs BELOW TO SUIT YOUR ESP32 PARALLEL TFT SETUP ######
238 |
239 | // The library supports 8 bit parallel TFTs with the ESP32, the pin
240 | // selection below is compatible with ESP32 boards in UNO format.
241 | // Wemos D32 boards need to be modified, see diagram in Tools folder.
242 | // Only ILI9481 and ILI9341 based displays have been tested!
243 |
244 | // Parallel bus is only supported for the STM32 and ESP32
245 | // Example below is for ESP32 Parallel interface with UNO displays
246 |
247 | // Tell the library to use 8 bit parallel mode (otherwise SPI is assumed)
248 | //#define TFT_PARALLEL_8_BIT
249 |
250 | // The ESP32 and TFT the pins used for testing are:
251 | //#define TFT_CS 33 // Chip select control pin (library pulls permanently low
252 | //#define TFT_DC 15 // Data Command control pin - must use a pin in the range 0-31
253 | //#define TFT_RST 32 // Reset pin, toggles on startup
254 |
255 | //#define TFT_WR 4 // Write strobe control pin - must use a pin in the range 0-31
256 | //#define TFT_RD 2 // Read strobe control pin
257 |
258 | //#define TFT_D0 12 // Must use pins in the range 0-31 for the data bus
259 | //#define TFT_D1 13 // so a single register write sets/clears all bits.
260 | //#define TFT_D2 26 // Pins can be randomly assigned, this does not affect
261 | //#define TFT_D3 25 // TFT screen update performance.
262 | //#define TFT_D4 17
263 | //#define TFT_D5 16
264 | //#define TFT_D6 27
265 | //#define TFT_D7 14
266 |
267 | // ###### EDIT THE PINs BELOW TO SUIT YOUR STM32 SPI TFT SETUP ######
268 |
269 | // The TFT can be connected to SPI port 1 or 2
270 | //#define TFT_SPI_PORT 1 // SPI port 1 maximum clock rate is 55MHz
271 | //#define TFT_MOSI PA7
272 | //#define TFT_MISO PA6
273 | //#define TFT_SCLK PA5
274 |
275 | //#define TFT_SPI_PORT 2 // SPI port 2 maximum clock rate is 27MHz
276 | //#define TFT_MOSI PB15
277 | //#define TFT_MISO PB14
278 | //#define TFT_SCLK PB13
279 |
280 | // Can use Ardiuno pin references, arbitrary allocation, TFT_eSPI controls chip select
281 | //#define TFT_CS D5 // Chip select control pin to TFT CS
282 | //#define TFT_DC D6 // Data Command control pin to TFT DC (may be labelled RS = Register Select)
283 | //#define TFT_RST D7 // Reset pin to TFT RST (or RESET)
284 | // OR alternatively, we can use STM32 port reference names PXnn
285 | //#define TFT_CS PE11 // Nucleo-F767ZI equivalent of D5
286 | //#define TFT_DC PE9 // Nucleo-F767ZI equivalent of D6
287 | //#define TFT_RST PF13 // Nucleo-F767ZI equivalent of D7
288 |
289 | //#define TFT_RST -1 // Set TFT_RST to -1 if the display RESET is connected to processor reset
290 | // Use an Arduino pin for initial testing as connecting to processor reset
291 | // may not work (pulse too short at power up?)
292 |
293 | // ##################################################################################
294 | //
295 | // Section 3. Define the fonts that are to be used here
296 | //
297 | // ##################################################################################
298 |
299 | // Comment out the #defines below with // to stop that font being loaded
300 | // The ESP8366 and ESP32 have plenty of memory so commenting out fonts is not
301 | // normally necessary. If all fonts are loaded the extra FLASH space required is
302 | // about 17Kbytes. To save FLASH space only enable the fonts you need!
303 |
304 | #define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
305 | #define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
306 | #define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
307 | #define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm
308 | #define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-.
309 | #define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-.
310 | //#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
311 | #define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
312 |
313 | // Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded
314 | // this will save ~20kbytes of FLASH
315 | #define SMOOTH_FONT
316 |
317 |
318 | // ##################################################################################
319 | //
320 | // Section 4. Other options
321 | //
322 | // ##################################################################################
323 |
324 | // For RP2040 processor and SPI displays, uncomment the following line to use the PIO interface.
325 | //#define RP2040_PIO_SPI // Leave commented out to use standard RP2040 SPI port interface
326 |
327 | // For RP2040 processor and 8 or 16 bit parallel displays:
328 | // The parallel interface write cycle period is derived from a division of the CPU clock
329 | // speed so scales with the processor clock. This means that the divider ratio may need
330 | // to be increased when overclocking. I may also need to be adjusted dependant on the
331 | // display controller type (ILI94341, HX8357C etc). If RP2040_PIO_CLK_DIV is not defined
332 | // the library will set default values which may not suit your display.
333 | // The display controller data sheet will specify the minimum write cycle period. The
334 | // controllers often work reliably for shorter periods, however if the period is too short
335 | // the display may not initialise or graphics will become corrupted.
336 | // PIO write cycle frequency = (CPU clock/(4 * RP2040_PIO_CLK_DIV))
337 | //#define RP2040_PIO_CLK_DIV 1 // 32ns write cycle at 125MHz CPU clock
338 | //#define RP2040_PIO_CLK_DIV 2 // 64ns write cycle at 125MHz CPU clock
339 | //#define RP2040_PIO_CLK_DIV 3 // 96ns write cycle at 125MHz CPU clock
340 |
341 | // For the RP2040 processor define the SPI port channel used (default 0 if undefined)
342 | //#define TFT_SPI_PORT 1 // Set to 0 if SPI0 pins are used, or 1 if spi1 pins used
343 |
344 | // For the STM32 processor define the SPI port channel used (default 1 if undefined)
345 | //#define TFT_SPI_PORT 2 // Set to 1 for SPI port 1, or 2 for SPI port 2
346 |
347 | // Define the SPI clock frequency, this affects the graphics rendering speed. Too
348 | // fast and the TFT driver will not keep up and display corruption appears.
349 | // With an ILI9341 display 40MHz works OK, 80MHz sometimes fails
350 | // With a ST7735 display more than 27MHz may not work (spurious pixels and lines)
351 | // With an ILI9163 display 27 MHz works OK.
352 |
353 | // #define SPI_FREQUENCY 1000000
354 | // #define SPI_FREQUENCY 5000000
355 | // #define SPI_FREQUENCY 10000000
356 | // #define SPI_FREQUENCY 20000000
357 | //#define SPI_FREQUENCY 27000000
358 | // #define SPI_FREQUENCY 40000000
359 | #define SPI_FREQUENCY 55000000 // STM32 SPI1 only (SPI2 maximum is 27MHz)
360 | // #define SPI_FREQUENCY 80000000
361 |
362 | // Optional reduced SPI frequency for reading TFT
363 | #define SPI_READ_FREQUENCY 20000000
364 |
365 | // The XPT2046 requires a lower SPI clock rate of 2.5MHz so we define that here:
366 | #define SPI_TOUCH_FREQUENCY 2500000
367 |
368 | // The ESP32 has 2 free SPI ports i.e. VSPI and HSPI, the VSPI is the default.
369 | // If the VSPI port is in use and pins are not accessible (e.g. TTGO T-Beam)
370 | // then uncomment the following line:
371 | //#define USE_HSPI_PORT
372 |
373 | // Comment out the following #define if "SPI Transactions" do not need to be
374 | // supported. When commented out the code size will be smaller and sketches will
375 | // run slightly faster, so leave it commented out unless you need it!
376 |
377 | // Transaction support is needed to work with SD library but not needed with TFT_SdFat
378 | // Transaction support is required if other SPI devices are connected.
379 |
380 | // Transactions are automatically enabled by the library for an ESP32 (to use HAL mutex)
381 | // so changing it here has no effect
382 |
383 | // #define SUPPORT_TRANSACTIONS
384 |
--------------------------------------------------------------------------------