首页-达尔闻    全部    项目分享| FIFA世界杯赛程监控器,真球迷在狂欢

项目分享| 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世界杯
Control Render Error!ControlType:productSlideBind,StyleName:Style1,ColorName:Item0,Message:InitError, ControlType:productSlideBind Error:未将对象引用设置到对象的实例。