项目分享| FIFA世界杯赛程监控器,真球迷在狂欢
-
除了在比赛时摇旗呐喊,真球迷还要实时监控比赛日程,不能错过任何一场比赛。下面这个赛程监控器就是一个很好的工具,电子工程师在狂欢的同时,还可以完成一个有意思的项目,一箭双雕。
项目功能描述:世界杯赛程监控器的功能就是在一个屏幕上显示FIFA World Cup Qatar 2022 赛程,比如今天有什么比赛,比赛双方的队名、国旗,比赛时间等信息。
项目使用的材料很简单:
1)Raspberry Pi RP2040
2)WIZnet WizFi360-EVB-Pico
3)圆形屏幕GC9A01
在这个项目中,使用足球数据 API 来获取 FIFA 世界杯 2022 比赛日程信息。足球数据football-data API的网站是: https://www.football-data.org/
Football-data.org 可以提供足球数据和统计数据(实时比分、赛程、表格、球队、阵容/替补等)。football-data有多牛就不说了,欢迎大家自己去了解。顶级的比赛都可以查得到数据,而且是免费的。
其实,项目实现过程非常简单。使用 Wizfi360 作为 http 客户端来发送足球数据 API 请求并获取足球数据参数。WizFi360是一个WiFi模块,可以通过命令连接WiFi,进行TCP或TCP(SSL)连接。RP2040作为MCU,从Wizfi360获取足球比赛信息后,进行数据处理,将内容显示在屏幕上。
因此,这个项目可以分为四步,以下是分步说明:
第一步:在football-data网站上创建新账户并获得API TOKEN;
在网站创建账户后,可以在“我的账户”页面看到API TOKEN。记录下来,后面的页面访问都需要这个TOKEN。
免费版的服务就可以满足项目的需求,不需要购买。使用文档可以在下面链接获取: https://www.football-data.org/documentation/quickstart 第二步:在Arduino IDE中安装库文件和板卡支持;
为ArduinoIDE添加“WIZnetWizFi360-EVB-PICO”。打开 Arduino IDE 并转到“文件”->“首选项”,在弹出的对话框中,在“Additional Boards Manager URLs”字段中输入以下 URL: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json
通过“Board Manager”搜索“WizFi360”并安装 Board 支持:
Tools->Board:"***"-> Raspberry Pi RP2040 Boards(2.6.1)”选择“WIZnet WizFi360-EVB-PICO”。
添加“GFX Library for rduino”,该库支持圆屏GC9A01。
因为需要显示球队的logo,所以需要加载一个PNG库“PNGdec”来解码图像。
第三步:通过WizFi360从football-data API获取参数;
-
#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()”中查看wifi的wizfi360 Link状态
-
// 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": "Croatia",
"awayTeam"-"name": "Belgium",
这是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]);
}第四步:在屏幕上显示足球数据比赛信息(GC9A01); #include <Arduino_GFX_Library.h>
Arduino_GFX *tft = create_default_Arduino_GFX();在“libraries\GFX_Library_for_Arduino\src\Arduino_GFX_Library.h”中定义 GC9A01 使用的引脚
#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中,具体的可以在源码中查看:display_country_icon(StringCountry) 最终显示效果如下:
-
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;
原文链接:
https://www.hackster.io/gavinchiong/fifa-world-cup-2022-schedule-monitor-b67b5f
作者:Gavin
原理图及代码回复:2022世界杯 -
-