From 42e51a4a1c1095de7793acc339611d36f56d99bd Mon Sep 17 00:00:00 2001 From: Adam Bissen Date: Sun, 1 Nov 2026 15:29:53 -0600 Subject: [PATCH] Sample text and API query working. --- README.md | 3 +- platformio.ini | 6 +- src/F1_api.h | 50 +++++++++ src/main.c | 246 ------------------------------------------ src/main.cpp | 140 ++++++++++++++++++++++++ src/pico_arduino.cpp | 66 ------------ src/secrets.h.example | 8 ++ 7 files changed, 204 insertions(+), 315 deletions(-) create mode 100644 src/F1_api.h delete mode 100644 src/main.c create mode 100644 src/main.cpp delete mode 100644 src/pico_arduino.cpp diff --git a/README.md b/README.md index 55a5a91..fc19ce4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ through PlatformIO and the Waveshare 4.2-inch V2 display. The display GPIO assignments are in `include/config.h`. 1. Copy `src/secrets.h.example` to `src/secrets.h` and set your Wi-Fi SSID and - password. The actual `src/secrets.h` is ignored by Git. + password. The actual `src/secrets.h` is ignored by Git. It also contains + the public root certificate used to verify OpenF1's HTTPS connection. 2. Build with `pio run -e pico_w`. 3. Upload with `pio run -e pico_w -t upload`. 4. Open the serial monitor with `pio device monitor -e pico_w`. diff --git a/platformio.ini b/platformio.ini index 1644ede..eb6fbc7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -13,5 +13,7 @@ platform = https://github.com/maxgerhardt/platform-raspberrypi.git board = rpipicow framework = arduino monitor_speed = 115200 -build_flags = -I include -build_src_filter = + + +build_flags = + -I include + -DPIO_FRAMEWORK_ARDUINO_ENABLE_FREERTOS +build_src_filter = + diff --git a/src/F1_api.h b/src/F1_api.h new file mode 100644 index 0000000..1ec7de1 --- /dev/null +++ b/src/F1_api.h @@ -0,0 +1,50 @@ +#include +#include +#include "FreeRTOS.h" +#include "task.h" +#include "secrets.h" + +void get_next_race(void *pvParameters) { + TickType_t retryDelay = pdMS_TO_TICKS(10000); + while (true) { + if (WiFi.status() == WL_CONNECTED) { + retryDelay = pdMS_TO_TICKS(10000); + HTTPClient http; + http.setCACert(OPENF1_ROOT_CA); + const bool requestReady = http.begin( + "https://api.openf1.org/v1/sessions?date_start%3E=2026-11-01&date_start%3C=2026-12-01&session_type=Race"); + if (!requestReady) { + Serial.print(""); + Serial.println("Failed to initialize OpenF1 request."); + } else { + const int httpResponseCode = http.GET(); + if (httpResponseCode > 0) { + String response = http.getString(); + Serial.print(""); + Serial.println("Received response from OpenF1 API:"); + Serial.println(response); + retryDelay = pdMS_TO_TICKS(900000); + } else { + Serial.print(""); + Serial.printf( + "Error on GET: %d (%s)\r\n", + httpResponseCode, + HTTPClient::errorToString(httpResponseCode).c_str()); + retryDelay = pdMS_TO_TICKS(10000); + } + } + http.end(); + } else { + Serial.print(""); + Serial.println("WiFi not connected."); + } + vTaskDelay(retryDelay); + } +} + + +/* Example response from the OpenF1 API for upcoming races +[{"session_key":11404,"session_type":"Race","session_name":"Race","date_start":"2026-11-01T20:00:00+00:00","date_end":"2026-11-01T22:00:00+00:00","meeting_key":1298,"circuit_key":65,"circuit_short_name":"Mexico City","country_key":8,"country_code":"MEX","country_name":"Mexico","location":"Mexico City","gmt_offset":"-06:00:00","year":2026,"is_cancelled":false},{"session_key":11412,"session_type":"Race","session_name":"Race","date_start":"2026-11-08T17:00:00+00:00","date_end":"2026-11-08T19:00:00+00:00","meeting_key":1299,"circuit_key":14,"circuit_short_name":"Interlagos","country_key":10,"country_code":"BRA","country_name":"Brazil","location":"São Paulo","gmt_offset":"-03:00:00","year":2026,"is_cancelled":false},{"session_key":11420,"session_type":"Race","session_name":"Race","date_start":"2026-11-22T04:00:00+00:00","date_end":"2026-11-22T06:00:00+00:00","meeting_key":1300,"circuit_key":152,"circuit_short_name":"Las Vegas","country_key":19,"country_code":"USA","country_name":"United States","location":"Las Vegas","gmt_offset":"-08:00:00","year":2026,"is_cancelled":false},{"session_key":11428,"session_type":"Race","session_name":"Race","date_start":"2026-11-29T16:00:00+00:00","date_end":"2026-11-29T18:00:00+00:00","meeting_key":1301,"circuit_key":150,"circuit_short_name":"Lusail","country_key":149,"country_code":"QAT","country_name":"Qatar","location":"Lusail","gmt_offset":"03:00:00","year":2026,"is_cancelled":false}]% + +{"detail":"Live F1 session in progress. Global API access (including past sessions) is restricted to authenticated users until the session ends. Get an API key here: https://buy.stripe.com/eVqcN41BPekP0iIalBcEw02"} +*/ \ No newline at end of file diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 26f281a..0000000 --- a/src/main.c +++ /dev/null @@ -1,246 +0,0 @@ -#include - -#include "DEV_Config.h" -#include "EPD_4in2_V2.h" -#include "GUI_Paint.h" -#include "ImageData.h" - -void wifi_connect(void); -void sync_time(void); - -int EPD_4in2_V2_test(void) -{ - printf("EPD_4IN2_V2_test Demo\r\n"); - if(DEV_Module_Init()!=0){ - return -1; - } - - printf("e-Paper Init and Clear...\r\n"); - EPD_4IN2_V2_Init(); - printf("e-Paper initialization complete; clearing display...\r\n"); - EPD_4IN2_V2_Clear(); - printf("e-Paper clear complete.\r\n"); - DEV_Delay_ms(500); - - //Create a new image cache - UBYTE *BlackImage; - /* you have to edit the startup_stm32fxxx.s file and set a big enough heap size */ - UWORD Imagesize = ((EPD_4IN2_V2_WIDTH % 8 == 0)? (EPD_4IN2_V2_WIDTH / 8 ): (EPD_4IN2_V2_WIDTH / 8 + 1)) * EPD_4IN2_V2_HEIGHT; - if((BlackImage = (UBYTE *)malloc(Imagesize)) == NULL) { - printf("Failed to apply for black memory...\r\n"); - return -1; - } - printf("Paint_NewImage\r\n"); - Paint_NewImage(BlackImage, EPD_4IN2_V2_WIDTH, EPD_4IN2_V2_HEIGHT, 0, WHITE); - -#if 1 // show bmp - Paint_SelectImage(BlackImage); - Paint_Clear(WHITE); - Paint_DrawBitMap(gImage_4in2); - EPD_4IN2_V2_Display(BlackImage); - DEV_Delay_ms(2000); -#endif - -#if 0 // show image for array - EPD_4IN2_V2_Init_Fast(Seconds_1S); - printf("show image for array\r\n"); - Paint_SelectImage(BlackImage); - Paint_Clear(WHITE); - Paint_DrawBitMap(gImage_4in2); - EPD_4IN2_V2_Display_Fast(BlackImage); - DEV_Delay_ms(2000); -#endif - -#if 1 // Drawing on the image - - EPD_4IN2_V2_Init(); - //1.Select Image - printf("SelectImage:BlackImage\r\n"); - Paint_SelectImage(BlackImage); - Paint_Clear(WHITE); - - // 2.Drawing on the image - printf("Drawing:BlackImage\r\n"); - Paint_DrawPoint(10, 80, BLACK, DOT_PIXEL_1X1, DOT_STYLE_DFT); - Paint_DrawPoint(10, 90, BLACK, DOT_PIXEL_2X2, DOT_STYLE_DFT); - Paint_DrawPoint(10, 100, BLACK, DOT_PIXEL_3X3, DOT_STYLE_DFT); - Paint_DrawLine(20, 70, 70, 120, BLACK, DOT_PIXEL_1X1, LINE_STYLE_SOLID); - Paint_DrawLine(70, 70, 20, 120, BLACK, DOT_PIXEL_1X1, LINE_STYLE_SOLID); - Paint_DrawRectangle(20, 70, 70, 120, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY); - Paint_DrawRectangle(80, 70, 130, 120, BLACK, DOT_PIXEL_1X1, DRAW_FILL_FULL); - Paint_DrawCircle(45, 95, 20, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY); - Paint_DrawCircle(105, 95, 20, WHITE, DOT_PIXEL_1X1, DRAW_FILL_FULL); - Paint_DrawLine(85, 95, 125, 95, BLACK, DOT_PIXEL_1X1, LINE_STYLE_DOTTED); - Paint_DrawLine(105, 75, 105, 115, BLACK, DOT_PIXEL_1X1, LINE_STYLE_DOTTED); - Paint_DrawString_EN(10, 0, "waveshare", &Font16, BLACK, WHITE); - Paint_DrawString_EN(10, 20, "hello world", &Font12, WHITE, BLACK); - Paint_DrawNum(10, 33, 123456789, &Font12, BLACK, WHITE); - Paint_DrawNum(10, 50, 987654321, &Font16, WHITE, BLACK); - Paint_DrawString_CN(130, 0, " ???abc", &Font12CN, BLACK, WHITE); - Paint_DrawString_CN(130, 20, "??????", &Font24CN, WHITE, BLACK); - - printf("EPD_Display\r\n"); - // EPD_4IN2_V2_Display(BlackImage); - EPD_4IN2_V2_Display(BlackImage); - DEV_Delay_ms(2000); -#endif - -#if 1 - printf("Partial refresh\r\n"); - Paint_NewImage(BlackImage, 120, 50, 0, WHITE); - PAINT_TIME sPaint_time; - sPaint_time.Hour = 12; - sPaint_time.Min = 34; - sPaint_time.Sec = 56; - UBYTE num = 10; - for (;;) { - sPaint_time.Sec = sPaint_time.Sec + 1; - if (sPaint_time.Sec == 60) { - sPaint_time.Min = sPaint_time.Min + 1; - sPaint_time.Sec = 0; - if (sPaint_time.Min == 60) { - sPaint_time.Hour = sPaint_time.Hour + 1; - sPaint_time.Min = 0; - if (sPaint_time.Hour == 24) { - sPaint_time.Hour = 0; - sPaint_time.Min = 0; - sPaint_time.Sec = 0; - } - } - } - Paint_Clear(WHITE); - Paint_DrawRectangle(1, 1, 120, 50, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY); - Paint_DrawTime(10, 15, &sPaint_time, &Font20, WHITE, BLACK); - EPD_4IN2_V2_PartialDisplay(BlackImage, 200, 80, 320, 130); - DEV_Delay_ms(500);//Analog clock 1s - num = num - 1; - if(num == 0) { - break; - } - } -#endif - - -#if 1 - // EPD_4IN2_V2_Init(); - // EPD_4IN2_V2_Clear(); - EPD_4IN2_V2_Init_4Gray(); - printf("show Gray------------------------\r\n"); - free(BlackImage); - BlackImage = NULL; - Imagesize = ((EPD_4IN2_V2_WIDTH % 8 == 0)? (EPD_4IN2_V2_WIDTH / 4 ): (EPD_4IN2_V2_WIDTH / 4 + 1)) * EPD_4IN2_V2_HEIGHT; - if((BlackImage = (UBYTE *)malloc(Imagesize)) == NULL) { - printf("Failed to apply for black memory...\r\n"); - return -1; - } - Paint_NewImage(BlackImage, EPD_4IN2_V2_WIDTH, EPD_4IN2_V2_HEIGHT, 0, WHITE); - Paint_SetScale(4); - Paint_Clear(WHITE); - - Paint_DrawPoint(10, 80, BLACK, DOT_PIXEL_1X1, DOT_STYLE_DFT); - Paint_DrawPoint(10, 90, BLACK, DOT_PIXEL_2X2, DOT_STYLE_DFT); - Paint_DrawPoint(10, 100, BLACK, DOT_PIXEL_3X3, DOT_STYLE_DFT); - Paint_DrawLine(20, 70, 70, 120, BLACK, DOT_PIXEL_1X1, LINE_STYLE_SOLID); - Paint_DrawLine(70, 70, 20, 120, BLACK, DOT_PIXEL_1X1, LINE_STYLE_SOLID); - Paint_DrawRectangle(20, 70, 70, 120, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY); - Paint_DrawRectangle(80, 70, 130, 120, BLACK, DOT_PIXEL_1X1, DRAW_FILL_FULL); - Paint_DrawCircle(45, 95, 20, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY); - Paint_DrawCircle(105, 95, 20, WHITE, DOT_PIXEL_1X1, DRAW_FILL_FULL); - Paint_DrawLine(85, 95, 125, 95, BLACK, DOT_PIXEL_1X1, LINE_STYLE_DOTTED); - Paint_DrawLine(105, 75, 105, 115, BLACK, DOT_PIXEL_1X1, LINE_STYLE_DOTTED); - Paint_DrawString_EN(10, 0, "waveshare", &Font16, BLACK, WHITE); - Paint_DrawString_EN(10, 20, "hello world", &Font12, WHITE, BLACK); - Paint_DrawNum(10, 33, 123456789, &Font12, BLACK, WHITE); - Paint_DrawNum(10, 50, 987654321, &Font16, WHITE, BLACK); - Paint_DrawString_CN(140, 0, "???abc", &Font12CN, GRAY1, GRAY4); - Paint_DrawString_CN(140, 40, "???abc", &Font12CN, GRAY2, GRAY3); - Paint_DrawString_CN(140, 80, "???abc", &Font12CN, GRAY3, GRAY2); - Paint_DrawString_CN(140, 120, "???abc", &Font12CN, GRAY4, GRAY1); - - Paint_DrawString_CN(220, 0, "??????", &Font24CN, GRAY1, GRAY4); - Paint_DrawString_CN(220, 40, "??????", &Font24CN, GRAY2, GRAY3); - Paint_DrawString_CN(220, 80, "??????", &Font24CN, GRAY3, GRAY2); - Paint_DrawString_CN(220, 120, "??????", &Font24CN, GRAY4, GRAY1); - - EPD_4IN2_V2_Display_4Gray(BlackImage); - DEV_Delay_ms(2000); - - Paint_Clear(WHITE); - Paint_DrawBitMap(gImage_4in2_4Gray1); - EPD_4IN2_V2_Display_4Gray(BlackImage); - DEV_Delay_ms(2000); - -#endif - - -#if 0 - - free(BlackImage); - BlackImage = NULL; - Imagesize = ((EPD_4IN2_V2_WIDTH % 8 == 0)? (EPD_4IN2_V2_WIDTH / 4 ): (EPD_4IN2_V2_WIDTH / 4 + 1)) * EPD_4IN2_V2_HEIGHT; - if((BlackImage = (UBYTE *)malloc(Imagesize)) == NULL) { - printf("Failed to apply for black memory...\r\n"); - return -1; - } - Paint_NewImage(BlackImage, EPD_4IN2_V2_WIDTH, EPD_4IN2_V2_HEIGHT, 0, WHITE); - Paint_SetScale(4); - Paint_Clear(WHITE); - - int key=0; - - gpio_set_dir(KEY0, GPIO_IN); - gpio_pull_up(KEY0);//Need to pull up - - gpio_set_dir(KEY1, GPIO_IN); - gpio_pull_up(KEY1);//Need to pull up - - while(1) - { - - if(DEV_Digital_Read(KEY0 ) == 0 && key==0) - { - key=1; - EPD_4IN2_V2_Init(); - Paint_DrawBitMap(gImage_4in2); - EPD_4IN2_V2_Display(BlackImage); - DEV_Delay_ms(2000); - } - - if(DEV_Digital_Read(KEY1 ) == 0 && key==0) - { - key=1; - EPD_4IN2_V2_Init_4Gray(); - Paint_DrawBitMap(gImage_4in2_4Gray1); - EPD_4IN2_V2_Display_4Gray(BlackImage); - DEV_Delay_ms(2000); - } - - if(DEV_Digital_Read(KEY0 ) == 1 && DEV_Digital_Read(KEY1 ) == 1 && key == 1 ) - { - key=0; - EPD_4IN2_V2_Init(); - EPD_4IN2_V2_Clear(); - } - - } -#endif - - EPD_4IN2_V2_Init(); - EPD_4IN2_V2_Clear(); - printf("Goto Sleep...\r\n"); - EPD_4IN2_V2_Sleep(); - free(BlackImage); - BlackImage = NULL; - DEV_Delay_ms(2000);//important, at least 2s - // close 5V - printf("close 5V, Module enters 0 power consumption ...\r\n"); - DEV_Module_Exit(); - - return 0; -} - -void app_setup(void) { - wifi_connect(); - sync_time(); - EPD_4in2_V2_test(); -} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..d8a2343 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include "F1_api.h" +#include "FreeRTOS.h" +#include "task.h" + +#include "config.h" + +extern "C" { +#include "DEV_Config.h" +#include "EPD_4in2_V2.h" +#include "GUI_Paint.h" +#include "ImageData.h" +} + +static UBYTE displayBuffer[(EPD_4IN2_V2_WIDTH / 8) * EPD_4IN2_V2_HEIGHT]; +static constexpr UWORD COUNTER_WIDTH = 200; +static constexpr UWORD COUNTER_HEIGHT = 24; +static constexpr UWORD COUNTER_Y = 20; +static UBYTE counterBuffer[(COUNTER_WIDTH / 8) * COUNTER_HEIGHT]; +static int counter = 0; + +void wifi_connect() { + WiFi.setHostname(DEVICE_HOSTNAME); + Serial.println("Connecting to WiFi..."); + + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + for (int attempt = 0; attempt < WIFI_MAXIMUM_RETRY; ++attempt) { + const unsigned long start = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { + delay(250); + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.print("Connected. IP address: "); + Serial.println(WiFi.localIP()); + return; + } + + if (attempt + 1 < WIFI_MAXIMUM_RETRY) { + Serial.println("WiFi connection failed; retrying..."); + WiFi.disconnect(); + delay(500); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + } + } + + Serial.println("Failed to connect to WiFi."); +} + +void sync_time() { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("Skipping NTP synchronization because WiFi is disconnected."); + return; + } + + NTP.begin("pool.ntp.org", "time.nist.gov"); + const bool synchronized = NTP.waitSet(10000); + setenv("TZ", TIMEZONE, 1); + tzset(); + + struct tm timeinfo; + const time_t now = time(nullptr); + if (synchronized && localtime_r(&now, &timeinfo) != nullptr) { + Serial.print("Time synchronized: "); + Serial.println(asctime(&timeinfo)); + } else { + Serial.println("Failed to synchronize time within 10 seconds."); + } +} + +bool readLocalTime(struct tm* result) { + const time_t now = time(nullptr); + return localtime_r(&now, result) != nullptr; +} + +void display_loop(void *pvParameters) { + while (true) { + static unsigned long lastRefresh = 0; + static char counterStr[32]; + + const unsigned long elapsed = millis() - lastRefresh; + if (elapsed < 10000) { + vTaskDelay(pdMS_TO_TICKS(10000 - elapsed)); + continue; + } + lastRefresh = millis(); + + counter++; + snprintf(counterStr, sizeof(counterStr), "Counter: %d", counter); + Serial.print(""); + Serial.println(counterStr); + + Paint_Clear(WHITE); + Paint_DrawString_EN(10, 0, counterStr, &Font16, BLACK, WHITE); + + EPD_4IN2_V2_PartialDisplay( + counterBuffer, + 0, + COUNTER_Y, + COUNTER_WIDTH, + COUNTER_Y + COUNTER_HEIGHT); + + vTaskDelay(pdMS_TO_TICKS(100)); // Add a small delay to prevent busy looping + } +} + +void setup() { + Serial.begin(115200); + + if (DEV_Module_Init() != 0) { + Serial.println("Failed to initialize display module."); + return; + } + + EPD_4IN2_V2_Init(); + Serial.println("e-Paper initialization complete; clearing display..."); + EPD_4IN2_V2_Clear(); + + DEV_Delay_ms(500); + + wifi_connect(); + sync_time(); + printf("Begin display loop.\n"); + + Paint_NewImage(displayBuffer, EPD_4IN2_V2_WIDTH, EPD_4IN2_V2_HEIGHT, ROTATE_0, WHITE); + Paint_SelectImage(displayBuffer); + Paint_Clear(WHITE); + Paint_DrawString_EN(10, 0, "F1 Display Test", &Font16, BLACK, WHITE); + Paint_DrawString_EN(10, COUNTER_Y, "Counter: 0", &Font16, BLACK, WHITE); + EPD_4IN2_V2_Display(displayBuffer); + + Paint_NewImage(counterBuffer, COUNTER_WIDTH, COUNTER_HEIGHT, ROTATE_0, WHITE); + Paint_SelectImage(counterBuffer); + + xTaskCreate(display_loop, "display_loop", 1024, NULL, 5, NULL); + xTaskCreate(get_next_race, "get_next_race", 4096, NULL, 5, NULL); +} diff --git a/src/pico_arduino.cpp b/src/pico_arduino.cpp deleted file mode 100644 index 66c4435..0000000 --- a/src/pico_arduino.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include -#include -#include - -#include "config.h" -#include "secrets.h" - -extern "C" void app_setup(void); - -extern "C" void wifi_connect(void) { - WiFi.setHostname(DEVICE_HOSTNAME); - Serial.println("Connecting to WiFi..."); - - WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - for (int attempt = 0; attempt < WIFI_MAXIMUM_RETRY; ++attempt) { - const unsigned long start = millis(); - while (WiFi.status() != WL_CONNECTED && millis() - start < 10000) { - delay(250); - } - - if (WiFi.status() == WL_CONNECTED) { - Serial.print("Connected. IP address: "); - Serial.println(WiFi.localIP()); - return; - } - - if (attempt + 1 < WIFI_MAXIMUM_RETRY) { - Serial.println("WiFi connection failed; retrying..."); - WiFi.disconnect(); - delay(500); - WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - } - } - - Serial.println("Failed to connect to WiFi."); -} - -extern "C" void sync_time(void) { - if (WiFi.status() != WL_CONNECTED) { - Serial.println("Skipping NTP synchronization because WiFi is disconnected."); - return; - } - - NTP.begin("pool.ntp.org", "time.nist.gov"); - const bool synchronized = NTP.waitSet(10000); - setenv("TZ", TIMEZONE, 1); - tzset(); - - struct tm timeinfo; - const time_t now = time(nullptr); - if (synchronized && localtime_r(&now, &timeinfo) != nullptr) { - Serial.print("Time synchronized: "); - Serial.println(asctime(&timeinfo)); - } else { - Serial.println("Failed to synchronize time within 10 seconds."); - } -} - -void setup() { - Serial.begin(115200); - app_setup(); -} - -void loop() { - delay(1000); -} diff --git a/src/secrets.h.example b/src/secrets.h.example index 49a249a..15a9e59 100644 --- a/src/secrets.h.example +++ b/src/secrets.h.example @@ -1,3 +1,11 @@ #define WIFI_SSID "your-wifi-ssid" #define WIFI_PASSWORD "your-wifi-password" #define WIFI_MAXIMUM_RETRY 3 + +static const char OPENF1_ROOT_CA[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- + +// Place certificate text here + +-----END CERTIFICATE----- +)EOF";