转发: FIFA World Cup 2022 Schedule Monitor(Raspberry Pi Pico & WizFi360 &Football-data)
项目介绍
我想在屏幕上显示2022年卡塔尔世界杯赛程,比如今天有什么比赛、主队、客队、比赛时间等信息。
在这个项目中,我使用 Football-data API 来获取 FIFA 2022 世界杯比赛日程信息。
足球数据API的网站是 “football-data.org - ur src for machine readable football data”.
Football-data.org 以机器可读的方式提供足球数据和统计数据(实时比分、赛程、积分表、球队、阵容/替补等)。
我不会宣布足球数据有多棒,欢迎您自己去发现(或不去发现)。 参加顶级比赛现在和将来都是免费的。
我使用wizfi360作为http客户端发送足球数据API请求并获取足球数据参数。 WizFi360是一款WiFi模块,可以通过命令连接WiFi,并进行TCP或TCP(SSL)连接。 我用过很多次了,非常方便。
RP2040作为MCU,从wizfi360获取足球比赛信息后,进行数据处理并将内容显示在屏幕上。
该项目分为四个步骤:
第一步:在足球数据网站创建新账户并获取API TOKEN;
Step2:在Arduino IDE中安装库文件和板卡支持;
步骤3:通过WizFi360从football-data API获取参数;
第四步:在屏幕上显示足球数据比赛信息(GC9A01);
在本网站创建账户后,您可以在“我的账户”页面看到您的API TOKEN。 请记录下来,因为以后的页面访问都需要这个TOKEN。
我使用的免费服务有以下限制,但免费帐户足以满足我的需求。
每个人都可以在以下位置获取文档 football-data.org - API Quickstart
为 Arduino IDE 添加“WIZnet WizFi360-EVB-PICO”支持
打开 Arduino IDE 并转到“文件”->“首选项”。
在弹出的对话框中,在“其他 Boards Manager URL”字段中输入以下 URL:
https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
搜索“WizFi360”并通过“板管理器”安装板支持
“工具->开发板:“***”-> Raspberry Pi RP2040 开发板(2.6.1) " 选择“WIZnet WizFi360-EVB-PICO”。
添加“GFX Library for rduino”,该库支持圆屏GC9A01。
因为我们需要显示球队的图标,所以我们需要加载一个PNG库“PNGdec”来解码图像。
#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;
初始化WizFi360模块的串口,并将波特率更改为2000000bps(wizfi360的最大波特率)。
第一次初始化为115200,然后在WiZfi360库的初始化部分添加设置波特率(2000000),第二次改为2000000bps。
// initialize serial for WizFi360 module
Serial2.setFIFOSize(4096);
Serial2.begin(2000000);
WiFi.init(&Serial2);
在“void setup()”中检查wizfi360 wifi的链接状态
// 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
}
在“api.football-data.org”的端口 443 上创建 SSL TCP 套接字并向 API 发送请求。
api请求的格式及示例如下:
https://api.football-data.org/v4/matches?date=TODAY
这是查询当前游戏信息
HEADER需要包含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;
之后wizFi360就可以获取API返回的足球比赛信息JSON。
API 响应示例
{
"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": [
{},
{},
{},
{}
]
}
包含4场比赛的信息,每场比赛的格式如下:
"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": []
},
我们需要的主要信息是:
"utcDate": "2022-12-01T15:00:00Z",
"homeTeam"-"name": "克罗地亚",
"awayTeam"-"name": "比利时",
这是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;
这就是获取游戏时间的过程。 需要注意的是,时间为国际标准时间,需要转换为当地时间:
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]);
这是四场比赛主队和客队的信息获取过程:
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]);
}
#include
Arduino_GFX *tft = create_default_Arduino_GFX();
GC9A01使用的引脚定义 "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
在“void setup()”中初始化屏幕并打开屏幕背光
tft->begin();
tft->fillScreen(WHITE);
pinMode(22, OUTPUT);
digitalWrite(22, HIGH);
连接WiFi过程中,会显示连接状态。
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);
}
}
显示为:
为了美观地显示界面,我们在“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);
}
在STEP3中得到参数后,通过以下处理,将屏幕显示更新为足球比赛信息。
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]);
}
各个国家的国旗存储在Flash中,
展示国旗的过程:
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");
}
}
最终显示效果如下:
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;