I want to display the FIFA World Cup Qatar 2022 Schedule on a screen, such as What game is on today, home team, away team, game time and other information.
In this project, I use the football-data API to get FIFA World Cup 2022 match Schedule information.
The website of football-data API is “https://www.football-data.org/”.
Football-data.org provides football data and statistics (live scores, fixtures, tables, squads, lineups/subs, etc.) in a machine-readable way.
I won't announce how awesome football-data is, you're welcome to find out by yourself (or not). Access to the top competitions is and will be free forever.
I use wizfi360 as http client to send football-data API request and get football-data parameters. WizFi360 is a WiFi module, which can connect to WiFi through commands and perform TCP or TCP (SSL) connections. I have used it many times and it is very convenient.
RP2040 acts as an MCU, after get football match info from wizfi360, it performs data processing and displays the content on the screen.
This project is divided into four steps:
Step 1: Create New Account on the football-data website and get API TOKEN;
Step2: Install library files and board support in the Arduino IDE;
Step 3: Get parameters from football-data API through WizFi360;
Step 4: Displays the football-data match info on the screen(GC9A01);
The following are step by step instructions.
Step 1: Create New Account on the football-data website and get API TOKEN;After creat account in this website, you can see your API TOKEN on the "My accounts" page. Please record it, because this TOKEN will be required for future page visits.
I am using a free service with the following limitations, but a free account is sufficient for my needs.
Everyone could get Documentation at https://www.football-data.org/documentation/quickstart
Step2: Install library files and board support in the Arduino IDE;Add "WIZnet WizFi360-EVB-PICO" support to Arduino IDE
Open up the Arduino IDE and go to File->Preferences.
In the dialog that pops up, enter the following URL in the "Additional Boards Manager URLs" field:
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
Search "WizFi360" and Install Board support by "Board Manager"
"Tools->Board:"***"-> Raspberry Pi RP2040 Boards(2.6.1) " Select “WIZnet WizFi360-EVB-PICO”.
Add “GFX Library for rduino”, this library support the round screen GC9A01.
Because we need to display the team's icon, we need to load a PNG library“PNGdec” to decode the image.
Step 3: Get parameters from football-data API through WizFi360;#include "WizFi360.h"
// Wi-Fi info //
char ssid[] = "WIZNET_test"; // your network SSID (name)//
char pass[] = "********"; // your network password//
int status = WL_IDLE_STATUS; // the Wifi radio's status//
// Initialize the Ethernet client object
WiFiClient client;
Initialize serial port for WizFi360 module and change the baudrate to 2000000bps(MAX baudrate for wizfi360).
The first initialization is 115200, and then setting the baud rate (2000000) is added to the initialization part of the WiZfi360 library, and the second time is changed to 2000000bps.
// initialize serial for WizFi360 module
Serial2.setFIFOSize(4096);
Serial2.begin(2000000);
WiFi.init(&Serial2);
Check the wizfi360 Link status of wifi in the “void setup()”
// check for the presence of the shield
if (WiFi.status() == WL_NO_SHIELD) {
while (true);// don't continue
}
// attempt to connect to WiFi network
while ( status != WL_CONNECTED) {
status = WiFi.begin(ssid, pass);// Connect to WPA/WPA2 network
}
Create a SSL TCP Socket on port 443 of the “ api.football-data.org” and send request to API.
The format and examples of api requests are as follows:
https://api.football-data.org/v4/matches?date=TODAY
This is to query the current game information
HEADER need to contain TOKEN,
X-Auth-Token:[token]
case link_football_server:
{
// if you get a connection, report back via serial
if (client.connectSSL(football_server,443)) {
Serial.println("Connected to server");
// Make a HTTP request
client.println(String("GET /v4/matches?date=TODAY HTTP/1.1"));
client.println(String("Host:") + String(football_server));
client.println(String("X-Auth-Token:") + String(football_token));
client.println("Connection: close");
client.println();
data_now = 0;
}
currentState = get_football_data;
}
break;
After that, wizFi360 can get the football match information JSON returned by the API.
Example of API response
{📷
"filters": {📷
"dateFrom": "2022-12-01",
"dateTo": "2022-12-02",
"permission": "TIER_ONE"
},
"resultSet": {📷
"count": 4,
"competitions": "WC",
"first": "2022-12-01",
"last": "2022-12-01",
"played": 0
},
"matches": [📷
{📷},
{📷},
{📷},
{📷}
]
}
It contains the information of 4 games, and the format of each game is as follows:
"matches": [📷
{📷
"area": {📷},
"competition": {📷},
"season": {📷},
"id": 391915,
"utcDate": "2022-12-01T15:00:00Z",
"status": "TIMED",
"matchday": 3,
"stage": "GROUP_STAGE",
"group": "GROUP_F",
"lastUpdated": "2022-11-30T15:33:46Z",
"homeTeam": {📷
"id": 799,
"name": "Croatia",
"shortName": "Croatia",
"tla": "CRO",
"crest": "https://crests.football-data.org/799.svg"
},
"awayTeam": {📷
"id": 805,
"name": "Belgium",
"shortName": "Belgium",
"tla": "BEL",
"crest": "https://crests.football-data.org/805.svg"
},
"score": {📷},
"odds": {📷},
"referees": [📷]
},
The main information we need is:
"utcDate": "2022-12-01T15:00:00Z",
"homeTeam"-"name": "Croatia",
"awayTeam"-"name": "Belgium",
This is the receiving process of JSON:
case get_football_data:
{
while (client.available()) {
json_String += (char)client.read();
data_now =1;
}
if(data_now)
{
//Serial.println(json_String);
dataStart = json_String.indexOf("en-US") + strlen("en-US")+4;
dataEnd = json_String.indexOf("\r\n", dataStart);
dataStr = json_String.substring(dataStart, dataEnd);
football_api_len = HexStr2Int(dataStr);
json_String = json_String.substring(dataEnd+2, json_String.length());
uint16_t football_api_cnt;
football_api_cnt = football_api_len - json_String.length();
while(football_api_cnt>0)
{
while(client.available()){
json_String += (char)client.read();
football_api_cnt--;
}
if(football_api_cnt != 0)
{
dataEnd = json_String.lastIndexOf("\r\n")- strlen("\r\n");
dataStart = json_String.indexOf("\r\n", dataEnd-10)+ strlen("\r\n");
dataStr = json_String.substring(dataStart, dataEnd);
if(HexStr2Int(dataStr) == 0)
{
football_api_cnt = 0;
}
}
}
//Serial.println(json_String);
dataEnd = json_String.indexOf("utcDate");
currentState = display_wait_timeout;
tft->fillRect(0,65,240,100,WHITE);
client.stop();
}
}
break;
This is the process of obtaining the game time. It should be noted that the time is international standard time and needs to be converted to local time:
dataStart = json_String.indexOf("utcDate",dataEnd) + strlen("utcDate")+3;
dataEnd = json_String.indexOf("\",", dataStart);
football_match_day[i] = json_String.substring(dataStart+8, dataStart+10);
match_time_hour = json_String.substring(dataStart+11, dataStart+13);
match_time_minute = json_String.substring(dataStart+14, dataStart+16);
if((match_time_hour>="16"))
{
if(football_match_day[i].toInt()+1 < 10)
{
football_match_day[i] = (String("2022-12-0") + (String)(football_match_day[i].toInt()+1));
}
else
{
football_match_day[i] = (String("2022-12-") + (String)(football_match_day[i].toInt()+1));
}
football_match_time[i] = ("0"+String(match_time_hour.toInt()-16) + ":" + (String)match_time_minute);
}
else
{
if(football_match_day[i].toInt() < 10)
{
football_match_day[i] = (String("2022-12-0") + (String)(football_match_day[i].toInt()));
}
else
{
football_match_day[i] = (String("2022-12-") + (String)(football_match_day[i].toInt()));
}
if(match_time_hour.toInt()+8 > 10 )
{
football_match_time[i] = (String(match_time_hour.toInt()+8) + ":" + (String)match_time_minute);
}
else
{
football_match_time[i] = ("0"+String(match_time_hour.toInt()+8) + ":" + (String)match_time_minute);
}
}
Serial.print("football_match_time");
Serial.println(i);
Serial.println(football_match_day[i]);
Serial.println(football_match_time[i]);
This is the information acquisition process of the home team and the visiting team for the four games:
for(int i =0; i<4;i++)
{
dataStart = json_String.indexOf("name", dataEnd)+ strlen("name")+3;
dataEnd = json_String.indexOf("\",", dataStart);
football_match_homeTeam[i]= json_String.substring(dataStart, dataEnd);
Serial.print("football_match_homeTeam");
Serial.println(i);
Serial.println(football_match_homeTeam[i]);
dataStart = json_String.indexOf("name", dataEnd)+ strlen("name")+3;
dataEnd = json_String.indexOf("\",", dataStart);
football_match_awayTeam[i]= json_String.substring(dataStart, dataEnd);
Serial.print("football_match_awayTeam");
Serial.println(i);
Serial.println(football_match_awayTeam[i]);
}
Step 4: Displays the football-data match info on the screen(GC9A01);#include <Arduino_GFX_Library.h>
Arduino_GFX *tft = create_default_Arduino_GFX();
define of pin which is used by GC9A01 in the "libraries\GFX_Library_for_Arduino\src\Arduino_GFX_Library.h"
#elif defined(ARDUINO_RASPBERRY_PI_PICO)||defined(ARDUINO_WIZNET_WIZFI360_EVB_PICO)||defined(ARDUINO_WIZNET_5100S_EVB_PICO)
#define DF_GFX_SCK 26
#define DF_GFX_MOSI 27
#define DF_GFX_MISO GFX_NOT_DEFINED
#define DF_GFX_CS 25
#define DF_GFX_DC 23
#define DF_GFX_RST 28
#define DF_GFX_BL 22
Initialize the screen and open the backlight of the screen in the “void setup()”
tft->begin();
tft->fillScreen(WHITE);
pinMode(22, OUTPUT);
digitalWrite(22, HIGH);
During the process of connecting to WiFi, the connection status is displayed.
void display_wifi_status()
{
if( status != WL_CONNECTED)
{
tft->fillCircle(120,230,3,DARKGREY);
tft->fillArc(120,230, 5, 7, 225, 315, DARKGREY);
tft->fillArc(120,230, 9, 11, 225, 315, DARKGREY);
tft->fillArc(120,230, 13, 15, 225, 315, DARKGREY);
}
else
{
tft->fillCircle(120,230,3,GREEN);
tft->fillArc(120,230, 5, 7, 225, 315, GREEN);
tft->fillArc(120,230, 9, 11, 225, 315, GREEN);
tft->fillArc(120,230, 13, 15, 225, 315, GREEN);
}
}
show as:
In order to display the interface beautifully, we write the framework of the interface during "Setup()".
void display_dashboard()
{
image_x = 93;
image_y = 10;
int rc = png.openFLASH((uint8_t *)WorldCupIcon, sizeof(WorldCupIcon), PNGDraw);
if (rc == PNG_SUCCESS) {
char szTemp[256];
sprintf(szTemp, "image specs: (%d x %d), %d bpp, pixel type: %d\n", png.getWidth(), png.getHeight(), png.getBpp(), png.getPixelType());
Serial.print(szTemp);
rc = png.decode(NULL, 0); // no private structure and skip CRC checking
png.close();
} // png opened successfully
else
{
Serial.println("ERROR");
}
tft->setTextColor(LIGHTGREY);
tft->setTextSize(4);
tft->setCursor(76, 72);
tft->print("FIFA");
tft->setTextSize(2);
tft->setCursor(69, 111);
tft->print("World Cup");
tft->setCursor(65, 136);
tft->print("Qatar 2022");
tft->setTextSize(3);
tft->setCursor(52, 162);
tft->print("Schedule");
tft->setTextColor(DARKGREY);
tft->setTextSize(4);
tft->setCursor(74, 70);
tft->print("FIFA");
tft->setTextSize(2);
tft->setCursor(68, 110);
tft->print("World Cup");
tft->setCursor(64, 135);
tft->print("Qatar 2022");
tft->setTextSize(3);
tft->setCursor(50, 160);
tft->print("Schedule");
tft->drawRoundRect(40,157,160,30,20,DARKGREY);
}
After get the parameters in STEP3, through the following processing, update the screen display as football match info.
void display_match_info(uint8_t num)
{
image_x = 21;
image_y = 55;
display_country_icon(football_match_homeTeam[num]);
tft->fillArc(60,95, 40, 45, 0, 360, LIGHTGREY);
tft->fillArc(60,95, 42, 43, 0, 360, DARKGREY);
image_x = 141;
image_y = 55;
display_country_icon(football_match_awayTeam[num]);
tft->fillArc(180,95, 40, 45, 0, 360, LIGHTGREY);
tft->fillArc(180,95, 42, 43, 0, 360, DARKGREY);
tft->setTextColor(LIGHTGREY);
tft->setTextSize(2);
tft->setCursor(111, 111);
tft->print("VS");
tft->setTextColor(DARKGREY);
tft->setCursor(109, 109);
tft->print("VS");
tft->fillRect(0,139,240,70,WHITE);
tft->setTextColor(DARKGREY);
tft->setTextSize(2);
tft->setCursor(55-(football_match_homeTeam[num].length())*5, 145);
tft->print(football_match_homeTeam[num]);
tft->setTextColor(DARKGREY);
tft->setCursor(175-(football_match_awayTeam[num].length())*5, 145);
tft->print(football_match_awayTeam[num]);
tft->setTextColor(DARKGREY);
tft->setTextSize(2);
tft->setCursor(62, 175);
tft->print(football_match_day[num]);
tft->setTextSize(2);
tft->setCursor(91, 195);
tft->print(football_match_time[num]);
}
The flag of each country is stored in Flash,
The process of displaying the national flag:
void display_country_icon(String Country)
{
char szTemp[256];
int rc;
if(Country =="Argentina")
{
rc = png.openFLASH((uint8_t *)Argentina, sizeof(Argentina), PNGDraw);
}
else if(Country =="Australia")
{
rc = png.openFLASH((uint8_t *)Australia, sizeof(Australia), PNGDraw);
}
else if(Country =="Belgium")
{
rc = png.openFLASH((uint8_t *)Belgium, sizeof(Belgium), PNGDraw);
}
else if(Country =="Brazil")
{
rc = png.openFLASH((uint8_t *)Brazil, sizeof(Brazil), PNGDraw);
}
else if(Country =="Cameroon")
{
rc = png.openFLASH((uint8_t *)Cameroon, sizeof(Cameroon), PNGDraw);
}
else if(Country =="Canada")
{
rc = png.openFLASH((uint8_t *)Canada, sizeof(Canada), PNGDraw);
}
else if(Country =="Costa Rica")
{
rc = png.openFLASH((uint8_t *)Costa_rica, sizeof(Costa_rica), PNGDraw);
}
else if(Country =="Croatia")
{
rc = png.openFLASH((uint8_t *)Croatia, sizeof(Croatia), PNGDraw);
}
else if(Country =="Denmark")
{
rc = png.openFLASH((uint8_t *)Denmark, sizeof(Denmark), PNGDraw);
}
else if(Country =="Ecuador")
{
rc = png.openFLASH((uint8_t *)Ecuador, sizeof(Ecuador), PNGDraw);
}
else if(Country =="England")
{
rc = png.openFLASH((uint8_t *)England, sizeof(England), PNGDraw);
}
else if(Country =="France")
{
rc = png.openFLASH((uint8_t *)France, sizeof(France), PNGDraw);
}
else if(Country =="Germany")
{
rc = png.openFLASH((uint8_t *)Germany, sizeof(Germany), PNGDraw);
}
else if(Country =="Ghana")
{
rc = png.openFLASH((uint8_t *)Ghana, sizeof(Ghana), PNGDraw);
}
else if(Country =="Iran")
{
rc = png.openFLASH((uint8_t *)Iran, sizeof(Iran), PNGDraw);
}
else if(Country =="Japan")
{
rc = png.openFLASH((uint8_t *)Japan, sizeof(Japan), PNGDraw);
}
else if(Country =="Mexico")
{
rc = png.openFLASH((uint8_t *)Mexico, sizeof(Mexico), PNGDraw);
}
else if(Country =="Morocco")
{
rc = png.openFLASH((uint8_t *)Morocco, sizeof(Morocco), PNGDraw);
}
else if(Country =="Netherlands")
{
rc = png.openFLASH((uint8_t *)Netherlands, sizeof(Netherlands), PNGDraw);
}
else if(Country =="Poland")
{
rc = png.openFLASH((uint8_t *)Poland, sizeof(Poland), PNGDraw);
}
else if(Country =="Portugal")
{
rc = png.openFLASH((uint8_t *)Portugal, sizeof(Portugal), PNGDraw);
}
else if(Country =="Qatar")
{
rc = png.openFLASH((uint8_t *)Qatar, sizeof(Qatar), PNGDraw);
}
else if(Country =="Saudi Arabia")
{
rc = png.openFLASH((uint8_t *)Saudi_arabia, sizeof(Saudi_arabia), PNGDraw);
}
else if(Country =="Senegal")
{
rc = png.openFLASH((uint8_t *)Senegal, sizeof(Senegal), PNGDraw);
}
else if(Country =="Serbia")
{
rc = png.openFLASH((uint8_t *)Serbia, sizeof(Serbia), PNGDraw);
}
else if(Country =="South Korea")
{
rc = png.openFLASH((uint8_t *)South_korea, sizeof(South_korea), PNGDraw);
}
else if(Country =="Spain")
{
rc = png.openFLASH((uint8_t *)Spain, sizeof(Spain), PNGDraw);
}
else if(Country =="Switzerland")
{
rc = png.openFLASH((uint8_t *)Switzerland, sizeof(Switzerland), PNGDraw);
}
else if(Country =="Tunisia")
{
rc = png.openFLASH((uint8_t *)Tunisia, sizeof(Tunisia), PNGDraw);
}
else if(Country =="United States")
{
rc = png.openFLASH((uint8_t *)United_states, sizeof(United_states), PNGDraw);
}
else if(Country =="Uruguay")
{
rc = png.openFLASH((uint8_t *)Uruguay, sizeof(Uruguay), PNGDraw);
}
else if(Country =="Wales")
{
rc = png.openFLASH((uint8_t *)Wales, sizeof(Wales), PNGDraw);
}
if (rc == PNG_SUCCESS) {
sprintf(szTemp, "image specs: (%d x %d), %d bpp, pixel type: %d\n", png.getWidth(), png.getHeight(), png.getBpp(), png.getPixelType());
Serial.print(szTemp);
rc = png.decode(NULL, 0); // no private structure and skip CRC checking
png.close();
} // png opened successfully
else
{
Serial.println("ERROR");
}
}
The final display effect is as follows:
case display_wait_timeout:
{
Serial.print("team_num");
Serial.println(team_num);
display_match_info(team_num);
team_num++;
if(team_num == 4)
{
team_num = 0;
}
delay(6000);
}
break;
Below is a video demo of the project.
Done!
Comments