-
Notifications
You must be signed in to change notification settings - Fork 0
/
Footy_API_v1.22.ino
2323 lines (2111 loc) · 89.8 KB
/
Footy_API_v1.22.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Footy_API v1.22
~~~~~~~~~~~~~~~
- fixed the flicker on the screen every refresh, screen is now stable
Footy_API v1.21
~~~~~~~~~~~~~~~
- tweaks
- bug fixes
- fixed bug where GameID on the API is no longer chronological due to a change in the API. Order of games is now sorted by unixtime
- tips are sored by seeing if the tip matches either the home or away team.
Footy_API v1.2
~~~~~~~~~~~~~~
- got rid of the tips being set by time or day of week
- and rely wholly on the tips set button on the menu screen.
- ie. when you have published your own tips, touch tip set.
- API notification changed to TIPS, which can be touched to see the source screen
- touching the LIVE icon give an immediate refresh (LIVE text disappears)
- bug fixes
Footy_API v1.16
~~~~~~~~~~~~~~~
- changed the menu "Round Set" button to the bottom
- added a menu button to set or unset the tips saved feature.
- added a screen to show the tipping sources by ID
- sources screen: selected tipping source is highlighted red
- sources screen: saved tipping source is highlighted yellow
- bug fixes
Footy_API v1.15
~~~~~~~~~~~~~~~
- added an amount of dots next to the live score to show what quarter is being played.
- bug fixes
Footy_API v1.14
~~~~~~~~~~~~~~~
- minor bug fixes
- added a delHTTP to set up to three seconds if the API is slow. (500ms seems to work)
- added client.stop() to the return from functions if HTTP error, so it doesn' happen again.
- added a LIVE notification on the main screen that will go red if HTTP error
Footy_API v1.13
~~~~~~~~~~~~~~~
- minor bug fixes
- added 1 decimal place to the percentages on the ladder screen
- allowed for changeover from live tips to saved tips without a device reset
- User WiFi settings are in a different colour if not defined in code
Footy_API v1.12
~~~~~~~~~~~~~~~
- minor bug fixes
- added AFL logo on the main screen. logo.h has to be in the same directory as the sketch
- requires the <PNGDec.h> library
- added an option to see the current AFL ladder from the main screen
Footy_API v1.11
~~~~~~~~~~~~~~~
- allowed feature to scroll through tipping sources without messing with the current tips selected.
- tipping source is always current before the tipping time cut off (Wednesday 8pm)
- gathered the menu colour options together at the beginning of the menuScreen() function for easier adjustment if required
- added a feature that if the round, year or tipping source is not current, then the associated heading on the main screen goes to an alternate colour.
- added a feature that changes the round menu data to an alternative colour if the year option is changed
Footy_API v1.1 (320x480)
~~~~~~~~~~~~~~~~~~~~~~~
- adjusted for 320x480 display ILI9488 thouch screen (fonts, Screen Layout)
- added a touch screen that will adjust the round, tipping source, year and WiFi source
- a 'SET' button allows you to set the round as the current round. The current round is saved in LittleFS
- the tipping source and WiFi settings are saved in LittleFS
- added six WiFi settings Home, Phone, User 1-4. These are configurable in the sketch
- can now scroll through any round/year without upsetting the current round's tips
- can see which tipping source will tip what. (see https://api.squiggle.com.au/?q=sources)
Footy_API v1.04
~~~~~~~~~~~~~~~~
- final update for the 320x240 screen (ILI9341)
- added two colours for game times so that games days are grouped together.
- added a line between games/scores to group game days together.
- added OTA functionality.
- got rid of delay() function, replacing with millis().
Footy_API v1.03
~~~~~~~~~~~~~~~
- added a new score colour for game breaks.
- added game start time if the game is not yet started, adjusted for time zone and 12 hour clock (have to select tz difference from Melbourne).
- added option to change the tipping source. (options https://api.squiggle.com.au/?q=sources).
- TO DO: allow for changing the round to check future and past rounds. I want to do this by touch screen. Create a settings menu.
- tried to add touch screen functionality. Didn't work. Assume screen is not touchable, despite pins being available. New screen ordered.
Footy_API v1.02
~~~~~~~~~~~~~~~
- Added font/background colour options toward the beginning.
- Removed much of the code from loop() and setup() and put them in functions.
- Changed the cutoff time for accessing tips from the API to 8pm Wednesday.
- Added a small notification bottom right if the tips are coming from the API.
Footy_API v1.01
~~~~~~~~~~~~~~
Added a way to save the footy tips after Wednesday so that if the API changes the tips, my registered tips are still used.
- required LittleFS.
- required time and ntp libraries.
- added a startup screen.
- tidied up the serial port output.
Footy_API v1.00
~~~~~~~~~~~~~~
The first version to retrieve live tips and compare them with the actual scores, giving a tally.
- changed the variables to char[] from const char* so that they didn't get lost when the JSON doc got destroyed.
*/
#include <ArduinoOTA.h> // OTA library
#include <WiFi.h> // Wifi library
#include <WiFiClientSecure.h> // HTTP Client for ESP32 (comes with the ESP32 core download)
#include <ArduinoJson.h> // Required for parsing the JSON coming from the API calls
#include <TFT_eSPI.h> // Required library for TFT Dsplay. Pin settings found in User_Setup.h in the library directory
#include <SPI.h> // Communicate with the TFT
#include "Free_Fonts.h" // Free fonts library in same directory as sketch
#include <LittleFS.h> // for saving tips data
#include <time.h> // for using time functions
#include <sntp.h> // for synchronising clock online
#include <PNGdec.h> // png decoder library
#include "logo.h" // contains the code for the logo
TFT_eSPI tft = TFT_eSPI(); // Activate TFT device
#define TFT_GREY 0x5AEB // New colour
PNG png; // PNG decoder instance for displaying the logo
#define MAX_IMAGE_WIDTH 70 // width of logo
#define HOST "api.squiggle.com.au" // Base URL of the API
char ssid0[] = "YOURSSID"; // network SSID (Home)
char password0[] = "YOURPASSWORD"; // network key (Home)
char ssid1[] = ""; // network SSID (Phone)
char password1[] = ""; // network key (Phone)
char ssid2[] = ""; // network SSID (User 1)
char password2[] = ""; // network key (User 1)
char ssid3[] = ""; // network SSID (User 2)
char password3[] = ""; // network key (User 2)
char ssid4[] = ""; // network SSID (User 3)
char password4[] = ""; // network key (User 3)
char ssid5[] = ""; // network SSID (User 4)
char password5[] = ""; // network key (User 4)
/* Main Screen COLOUR OPTIONS:
* TFT_BLACK TFT_NAVY TFT_DARKGREEN TFT_DARKCYAN TFT_MAROON TFT_PURPLE TFT_OLIVE
* TFT_LIGHTGREY TFT_DARKGREY TFT_BLUE TFT_GREEN TFT_CYAN TFT_RED
* TFT_WHITE TFT_ORANGE TFT_GREENYELLOW TFT_MAGENTA TFT_YELLOW
* TFT_PINK TFT_BROWN TFT_GOLD TFT_SILVER TFT_SKYBLUE TFT_VIOLET */
uint16_t titleColor = TFT_GREEN; // colour of the title. Default: TFT_GREEN
uint16_t titleAltColor = TFT_RED; // colour of the title when not current round or year. Default: TFT_RED
uint16_t teamColor = TFT_YELLOW; // colour of the teams. Default: TFT_YELLOW
uint16_t pickedColor = TFT_ORANGE; // colour of the picked teams. Default: TFT_ORANGE
uint16_t tallyColor = TFT_GREEN; // colour of the tally. Default: TFT_GREEN
uint16_t background = TFT_BLACK; // colour of the background. Default: TFT_BLACK
uint16_t timeColor1 = TFT_LIGHTGREY; // colour of the game time 1. Default: TFT_LIGHTGREY Make the same to deactivate
uint16_t timeColor2 = TFT_LIGHTGREY; // colour of the game time 2. Default: TFT_DARKGREY Make the same to deactivate
uint16_t pickedScoreColor = TFT_GREEN; // colour of the score - tipped. Default: TFT_GREEN
uint16_t wrongScoreColor = TFT_RED; // colour of the score - not tipped. Default: TFT_RED
uint16_t liveScoreColor = TFT_CYAN; // colour of the live score. Default: TFT_GREEN
uint16_t gamebreakColor = TFT_WHITE; // colour of the live score during a break time. Default: TFT_WHITE
uint16_t notifColor = TFT_CYAN; // colour of the API notification. Default: TFT_CYAN
uint16_t lineColor = TFT_SKYBLUE; // colour of the lines that group the days. Default: TFT_BROWN Not Used: TFT_BLACK
// Main Screen Layout Options
int titleX = 120; // These are the parameters for the setting out of the display. Title x position
int titleY = 30; // title y position
int line1Start = 68; // location of the first line location under the title
int lineSpacing = 25; // Line spacing
int vsLoc = 185; // Horizontal location of the "vs."
int hteamLoc = 20; // Horizontal location of the Home Team
int ateamLoc = 250; // Horizontal location of the Away Team
int scoreLoc = 398; // Horizontal location of the score
int timeLoc = 410; // Horizontal location of the game time
int tallyX = 115; // Horizontal location of the tips tally
int tallyY = 315; // Vertical location of the tips tally
int warnX = 108; // Horizontal location of the startup notification
int warnY = 100; // Vertical location of the startup notification
int notifX = 393; // Horizontal location of the API notification
int notifY = 310; // Horizontal location of the API notification
int LnStart = 405; // x Start position of lines marking the days
int LnEnd = 460; // x End position of lines marking the days
int16_t xpos = 20; // logo position X
int16_t ypos = 0; // logo position Y
// Time servers 1 and 2
const char* ntpServer1 = "pool.ntp.org";
const char* ntpServer2 = "time.nist.gov";
//Time Zone
char time_zone[] = "ACST-9:30ACDT,M10.1.0,M4.1.0/3"; // TimeZone rule for Adelaide including daylight adjustment rules
int tz = -30; // Time zone in minutes relative to AEST (Melbourne)
// Request string parts for accessing the API
char requestGames[30] = "/?q=games;year="; // building blocks of the API URL (less base)
char part2Games[] = ";round="; // for getting games and scores
char requestTips[40] = "/?q=tips;year="; // building blocks of the API URL (less base)
char part2Tips[] = ";round="; // for getting tips
char part3Tips[] = ";source="; // source is the tipping source. 1 = Squiggle etc.
// Arrays for parsing data
char get_winner[9][30]; // global variable array for the winning team
int get_hscore[9]; // global variable array for the home score
int get_ascore[9]; // global variable array for the away score
int get_complete[9]; // global variable array for the percent complete of each game
char get_roundname[9][10]; // global variable array for the Round Name
char get_hteam[9][30]; // global variable array for the home team
char get_ateam[9][30]; // global variable array for the awat team
long get_id[9]; // global variable array for the game id. used for sorting games into chronological order
int orderG[9]; // global variable array for the reordering the games into chronological order
int orderT[9]; // global variable array for the reordering the games into chronological order
char get_tip[9][30]; // global variable array for the tipped teams
long get_tipId[9]; // global variable array for the game id. used for sorting tips into chronological order
char rawData[8000]; // for storing the tips JSON buffer
char get_gtime[9][20]; // global variable array used for retrieving the game start time
char gameTime[9][15]; // global variable array used for retrieving the game start time, adjusted for time zone and 12 hour clock
char gameDate[9][15]; // global variable array used for retrieving the game date
int startLive[9]; // a flag to say whether a given game is live
// Global variable list
int picked = 0; // counts the successful tips
int totalGames = 0; // counts the games in each round
int totalTips = 0; // counts the tips in each round
int played = 0; // counts the games played in each round
int prevPlayed = 0; // used for seeing if there is an update to the games played (ie. a new result)
int gameLive = 0; // used to test if a game is live to shorten the loop delay
int delLive = 30000; // delay between API calls if there is a live game (ms)
int delNoGames = 120000; // delay between API calls if there is no live game (ms)
int del = 120000; // for working out the delay in the main loop wihtout the delay() function
int delHTTP = 1000; // delay when using the GET command for tips, games and ladder if the API is slow (500ms is best)
int weekDay = 10; // global variable to hold the day of the week when set to 10, it will wait until the time is retrieved
int hour = 0; // global variable to hold the current hour
int month = 13; // global variable to hold the current month (0-11)
int date = 0; // global variable to hold the current date (1-31)
int minute = 0; // global variable to hold the current minute
int tipsLive = 1; // global variable 1 = tips are retrieved from API, 0= tips are retrieved from LittleFS
int tipSet = 0; // global variable that toggles the tipsLive variable
unsigned long previousMillis = 0; // will store the last time the score was updated
int newdate = 0; // global variable for noting the game dates during display routine
int rnd = 0; // global variable for round
int currentRnd = 0; // global variable for the current round
int year = 0; // global variable for the year
int currentYear = 0; // global variable for the current year
int tipSource = 0; // global variable for the tipping source for options)
int savedSource = 0; // global variable to check if the saved tips source matches currently selected.
int wifi = 0; // global variable for the wifisettings
char wifihead[8]; // holds the menu headings for WiFi options
char ssid[20]; // global variable for the ssid
char password[20]; // global variable for the WiFi password
int error = 0; // flag for HTTP error
int fromSetup = 1; // flag for the get time function to not change the year variable in the call doesn't come from Setup
int fromLoop = 0;
// global variables for the menu settings screen layout
int row1Y = 80;
int row2Y = 200;
int col1X = 50;
int col2X = 153;
int col3X = 256;
int col4X = 359;
int butSizeX = 70;
int butSizeY = 70;
int offX = 22;
int offY = 45;
int dataY = 185;
int dataXoff = 17;
int botButtSizeY = 30;
int botButtSizeX = 103;
int botButt1X = 120;
int botButt2X = 256;
int botButtY = 290;
// global setting variables for the menu colours
uint16_t buttonColor = TFT_SKYBLUE;
uint16_t dataColor = TFT_YELLOW;
uint16_t dataAltColor = TFT_RED;
uint16_t headingColor = TFT_CYAN;
WiFiClientSecure client; // starts the client for HTTPS requests
void setup() {
Serial.begin(115200); // for debugging
if (!LittleFS.begin()) { //LittleFS set up. Put true first time running on a new esp32 to format the LittleFS
Serial.println("Error mounting LittleFS");
}
sntp_set_time_sync_notification_cb(timeavailable); // set notification call-back function
sntp_servermode_dhcp(1); // (optional)
configTzTime(time_zone, ntpServer1, ntpServer2); // these all need to be done before connecting to WiFi
readWifi();
// allows the selection of various different WiFi settings depending on the menu setting
if (wifi == 0) {
strcpy(ssid, ssid0);
strcpy(password, password0);
}
if (wifi == 1) {
strcpy(ssid, ssid1);
strcpy(password, password1);
}
if (wifi == 2) {
strcpy(ssid, ssid2);
strcpy(password, password2);
}
if (wifi == 3) {
strcpy(ssid, ssid3);
strcpy(password, password3);
}
if (wifi == 4) {
strcpy(ssid, ssid4);
strcpy(password, password4);
}
if (wifi == 5) {
strcpy(ssid, ssid5);
strcpy(password, password5);
}
Serial.println(ssid);
Serial.println(password);
WiFi.begin(ssid, password); // Connect to the WiFI
Serial.println("Connecting to WiFi");
int k = 0;
while ((WiFi.status() != WL_CONNECTED) && (k < 100)) { // Wait for connection. Allowed for an option to end this loop if no WiFi. Can then set a valid setting from the menu
delay(50);
Serial.print(".");
k++;
}
Serial.println("");
Serial.print("Connected to ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Serial.println();
ArduinoOTA.begin(); //Start OTA
client.setInsecure(); //bypasses certificates
tft.init(); // initialises the TFT
tft.setRotation(3); // sets the rotation to match the device
uint16_t calData[5] = { 255, 3673, 278, 3500, 1 }; // inputs the calibration data for the particular screen from TFT_eSPI examples
tft.setTouch(calData);
startScreenTFT(); // sets out the loading messages in a rectangle while waiting for the time
if (k > 99) { // If there is no WiFi, k will be 100 and the error screen is displayed and menu is selected.
noWifi();
delay(5000);
menuScreen();
}
Serial.print("Getting time "); // waits for the interupt function to get the time.
while ((weekDay == 10) && (k < 100)) { //Allowed for an option to end this loop if no WiFi. Can then set a valid setting from the menu
Serial.print(".");
delay(100);
}
readSource(); // recovers the saved tipping source from LittleFS
tipSource = savedSource; // allows for saving the tips on boot
readRound(); // recovers the saved round from LittleFS
tft.fillScreen(background);
readTipsSet(); // recovers whether the tips are set (saved)
if (tipSet == 0) {
tipsLive = 1; // 1 = get tips from API
Serial.println("Getting tips from API and writing to LittleFS");
} else {
tipsLive = 0; // 0 = get tips from LittleFS
Serial.println("Getting tips by reading from LittleFS");
}
stringBuild(); // build the strings for the URLs for games, scores and tips using the year and rnd variable.
gameRequest(); // calls the gameRequest() function
timeZoneDiff(); // adjusts the game time for time zone and change to 12 hour clock
sortG(get_id, totalGames); // sorts the matches into chronological order using the sortG() function
tipsRequest(); // calls the function to get the tips
sortT(totalTips); // sorts the matches into chronological order using the sortT() function
printTFT(); // sends the data to the screen
printSerial(); // sends data to the serial monitor
}
void gameRequest() { // function to get the games and scores
if (!client.connect(HOST, 443)) { //opening connection to server
Serial.println(F("Connection failed"));
client.stop();
error = 1;
return;
}
yield(); // give the esp a breather
client.print(F("GET ")); // Send HTTP request
client.print(requestGames); // This is the second half of a request (everything that comes after the base URL)
client.println(F(" HTTP/1.1"));
client.print(F("Host: ")); //Headers
client.println(HOST);
client.println(F("Cache-Control: no-cache"));
client.println("User-Agent: Arduino Footy Project ds@ds..com"); // Required by API for contact if anything goes wrong
if (client.println() == 0) {
Serial.println(F("Failed to send request"));
client.stop();
error = 1;
return;
}
delay(delHTTP);
char status[32] = { 0 }; // Check HTTP status
client.readBytesUntil('\r', status, sizeof(status));
if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
Serial.print(F("Unexpected response: "));
Serial.println(status);
client.stop();
error = 1;
return;
}
Serial.print("Games Request: ");
Serial.println(status);
error = 0;
char endOfHeaders[] = "\r\n\r\n"; // Skip HTTP headers
if (!client.find(endOfHeaders)) {
Serial.println(F("Invalid response"));
client.stop();
error = 1;
return;
}
while (client.available() && client.peek() != '{') { // This is needed to deal with random characters coming back before the body of the response.
char c = 0;
client.readBytes(&c, 1);
// Serial.print(c); // uncomment to see the bad characters
// Serial.println("BAD"); // as above
}
int i = 0; // for counting during parsing
totalGames = 0; // counts the total games
char game_roundname[10]; // variable for the round name
char game_ateam[30]; // variable for the away team
char game_hteam[30]; // variable for the home team
char game_winner[30]; // variable for the game winner
char game_gtime[25]; // variable for the game start (local time)
JsonDocument filter; // begin filtering the JSON data from the API. The following code was made with the ArduinoJSON Assistant (v7)
JsonObject filter_games_0 = filter["games"].add<JsonObject>();
filter_games_0["roundname"] = true;
filter_games_0["unixtime"] = true;
filter_games_0["ateam"] = true;
filter_games_0["date"] = true;
filter_games_0["ascore"] = true;
filter_games_0["hscore"] = true;
filter_games_0["winner"] = true;
filter_games_0["hteam"] = true;
filter_games_0["complete"] = true;
JsonDocument doc; //deserialization begins
DeserializationError error = deserializeJson(doc, client, DeserializationOption::Filter(filter));
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
for (JsonObject game : doc[F("games")].as<JsonArray>()) {
strcpy(game_roundname, game[F("roundname")]); // Variable changed from const char* to char[] // "Round 7", "Round 7", "Round 7", "Round 7", "Round ...
int game_hscore = game[F("hscore")]; // 85, 95, 118, 112, 113, 42, 81, 82, 42
strcpy(game_ateam, game[F("ateam")]); // Variable changed from const char* to char[] // "Collingwood", "Western Bulldogs", "Carlton", "West ...
long game_id = game[F("unixtime")]; // 35755, 35760, 35759, 35761, 35756, 35762, 35758, 35757, 35754
if (game[F("winner")]) { // deals with null when there's no winners
strcpy(game_winner, game[F("winner")]); // Variable changed from const char* to char[] // nullptr, "Fremantle", "Geelong", "Gold Coast", "Greater ...
}
int game_ascore = game[F("ascore")]; // 85, 71, 105, 75, 59, 118, 138, 72, 85
int game_complete = game[F("complete")]; // 100, 100, 100, 100, 100, 100, 100, 100, 100
strcpy(game_hteam, game[F("hteam")]); // Variable changed from const char* to char[] // "Essendon", "Fremantle", "Geelong", "Gold Coast", "Greater ...
strcpy(game_gtime, game[F("date")]); // Variable changed from const char* to char[] // "2024-05-09 19:30:00", "2024-05-10 19:10:00", ...
// Importing temp variables from JSON doc into global arrays to be used outside the function.
if (!(strcmp(game_winner, "NULL") == 0)) {
strcpy(get_winner[i], game_winner);
if (!(strstr(get_winner[i], "Greater Western") == NULL)) {
strcpy(get_winner[i], "GWS Giants");
}
}
get_hscore[i] = game_hscore;
get_ascore[i] = game_ascore;
get_complete[i] = game_complete;
strcpy(get_roundname[i], game_roundname);
strcpy(get_hteam[i], game_hteam);
if (!(strstr(get_hteam[i], "Greater Western") == NULL)) {
strcpy(get_hteam[i], "GWS Giants");
}
strcpy(get_ateam[i], game_ateam);
if (!(strstr(get_ateam[i], "Greater Western") == NULL)) {
strcpy(get_ateam[i], "GWS Giants");
}
strcpy(get_gtime[i], game_gtime); // get the game time variable string from "date"
get_id[i] = game_id;
orderG[i] = i;
totalGames = totalGames + 1;
i++;
}
client.stop(); // fixes bug where client.print fails every second time.
}
void tipsRequest() { // function to get the current tips
Serial.print("rnd = ");
Serial.println(rnd);
Serial.print("currentRnd = ");
Serial.println(currentRnd);
Serial.print("tipSource = ");
Serial.println(tipSource);
Serial.print("savedSource = ");
Serial.println(savedSource);
Serial.print("year = ");
Serial.println(year);
Serial.print("currentYear = ");
Serial.println(currentYear);
Serial.print("tipsLive = ");
Serial.println(tipsLive);
Serial.print("tipSet = ");
Serial.println(tipSet);
for (int i = 0; i < 9; i++) { // Deletes all the existing tip data so tips aren't carried over if no tips are returned from the API
strcpy(get_tip[i], "");
orderT[i] = 10;
}
if ((tipsLive == 1) || (!(rnd == currentRnd)) || (!(year == currentYear)) || (!(savedSource == tipSource))) { // start of the HTTP get to retrieve tips from the API
if (!client.connect(HOST, 443)) { //opening connection to server
Serial.println(F("Connection failed"));
client.stop();
return;
}
yield(); // give the esp a breather
client.print(F("GET ")); // Send HTTP request
client.print(requestTips); // This is the second half of a request (everything that comes after the base URL)
client.println(F(" HTTP/1.1"));
client.print(F("Host: ")); //Headers
client.println(HOST);
client.println(F("Cache-Control: no-cache"));
client.println("User-Agent: Arduino Footy Project ds@ds.com"); // Required by API for contact if anything goes wrong
if (client.println() == 0) {
Serial.println(F("Failed to send request"));
client.stop();
error = 1;
return;
}
delay(delHTTP); // This delay gives a better response from the API
char status[32] = { 0 }; // Check HTTP status
client.readBytesUntil('\r', status, sizeof(status));
if (strcmp(status, "HTTP/1.1 200 OK") != 0) {
Serial.print(F("Unexpected response: "));
Serial.println(status);
client.stop();
error = 1;
return;
}
Serial.print("Tips Request: ");
Serial.println(status);
error = 0;
char endOfHeaders[] = "\r\n\r\n"; // Skip HTTP headers
if (!client.find(endOfHeaders)) {
Serial.println(F("Invalid response"));
client.stop();
error = 1;
return;
}
while (client.available() && client.peek() != '{') { // This is needed to deal with random characters coming back before the body of the response.
char c = 0;
client.readBytes(&c, 1);
// Serial.print(c); // uncomment to see the bad characters
// Serial.println("BAD"); // as above
}
int j = 0;
while (client.available()) { // reads the API call client into the global array rawData[]
char c = 0;
client.readBytes(&c, 1);
rawData[j] = c;
j++;
}
rawData[j] = 0; // adds the required null character to the end of the string rawData[]
if ((rnd == currentRnd) && (year == currentYear) && (savedSource == tipSource)) {
File file = LittleFS.open("/tip.txt", FILE_WRITE); // opens file in LittleFS to write the API call data in the file /tip.txt
if (!file) {
Serial.println("- failed to open file for writing");
return;
}
if (file.print(rawData)) {
Serial.println("LittleFS Tips JSON File - file written");
} else {
Serial.println("- write failed");
}
file.close();
}
} else {
File file = LittleFS.open("/tip.txt", FILE_READ); // routine to retrieve tips from the LittleFS file /tip.txt into rawData[]
if (!file) {
Serial.println(" - failed to open file for reading");
}
int j = 0;
while (file.available()) {
rawData[j] = (file.read()); //reads the file one character at a time and feeds it into the char array rawData[] as j increments
j++;
}
file.close();
Serial.println("LittleFS Tips JSON File - file read");
}
int i = 0; // count for parsing
char tip_tip[30]; // temp variable to read the tips
totalTips = 0; // global variable to count the games in the round
JsonDocument filter; // begin filtering the JSON data from the API. The following code was made with the ArduinoJSON Assistant (v7)
JsonObject filter_tips_0 = filter["tips"].add<JsonObject>();
filter_tips_0["gameid"] = true;
filter_tips_0["tip"] = true;
filter_tips_0["sourceid"] = true;
JsonDocument doc; //deserialization begins
DeserializationError error = deserializeJson(doc, rawData, DeserializationOption::Filter(filter));
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;
}
for (JsonObject tip : doc[F("tips")].as<JsonArray>()) {
long tip_gameid = tip[F("gameid")]; // 35763, 35771, 35764, 35767, 35769, 35766, 35765, 35768, 35770
strcpy(tip_tip, tip[F("tip")]); // Variable changed from const char* to char[] // "Port Adelaide", "Brisbane Lions", "Carlton", "Melbourne", ...
int tip_sourceid = tip["sourceid"]; // 33
// Importing temp variables from JSON doc into global arrays to be used outside the function.
strcpy(get_tip[i], tip_tip);
if (!(strstr(get_tip[i], "Greater Western") == NULL)) {
strcpy(get_tip[i], "GWS Giants");
}
get_tipId[i] = tip_gameid;
//orderT[i] = i;
i++;
totalTips = totalTips + 1;
}
client.stop(); // fixes bug where client.print fails every second time.
}
void sortG(long ia[], int len) { // sortG() function to bubble sort the games into their correct order based on game_id
for (int x = 0; x < len; x++) {
for (int y = 0; y < (len - 1); y++) {
if (ia[y] > ia[y + 1]) {
long tmp = ia[y + 1];
int ord = orderG[y + 1];
ia[y + 1] = ia[y];
orderG[y + 1] = orderG[y];
ia[y] = tmp;
orderG[y] = ord;
}
}
}
}
void sortT(int len) { // sortT() function to bubble sort the tips into their correct order based on game_id
for (int x = 0; x < len; x++) {
for (int y = 0; y < len; y++) {
if ((strcmp(get_tip[y], get_ateam[orderG[x]]) == 0) || (strcmp(get_tip[y], get_hteam[orderG[x]])) == 0) {
orderT[x] = y;
Serial.print("HIT: ");
Serial.print(x);
Serial.print(" ");
Serial.println(y);
}
}
}
}
void getLocalTime() { // function to retrieve the local time according to the set time zone rules
struct tm timeinfo;
while (!getLocalTime(&timeinfo)) {
Serial.println("No time available (yet)");
}
hour = timeinfo.tm_hour; // sets the hour into the variable. 24 hour clock.
if (hour > 12) { hour = hour - 12; }
weekDay = timeinfo.tm_wday; // sets the weekday into the variable. (0-6) 0 = Sunday.
minute = timeinfo.tm_min;
month = timeinfo.tm_mon;
month = month + 1;
date = timeinfo.tm_mday;
currentYear = timeinfo.tm_year;
currentYear = currentYear + 1900;
if (fromSetup == 1) {
year = currentYear;
}
Serial.print("Year = ");
Serial.println(year);
Serial.print("Weekday = ");
Serial.println(weekDay);
Serial.print("Hour = ");
Serial.println(hour);
Serial.print("Minutes = ");
Serial.println(minute);
Serial.println();
}
void timeavailable(struct timeval* t) { // Callback function (gets called when time adjusts via NTP)
Serial.println("Got time adjustment from NTP!");
getLocalTime();
}
void printTFT() { // a function for printing the main data to the screen
// routine to decode and display the AFL logo
int16_t rc = png.openFLASH((uint8_t*)logo, sizeof(logo), pngDraw);
if (rc == PNG_SUCCESS) {
tft.startWrite();
rc = png.decode(NULL, 0);
tft.endWrite();
}
if ((tipsLive == 1) || (!(year == currentYear)) || (!(rnd == currentRnd)) || (!(savedSource == tipSource))) { //Adds a small "TIPS" notification to indicate that tips were accessed from the API
tft.setTextColor(titleAltColor, background);
} else {
tft.setTextColor(notifColor, background);
}
tft.setCursor(notifX, notifY, 1);
tft.print("TIPS");
if (!(error)) {
tft.setTextColor(notifColor);
} else {
tft.setTextColor(titleAltColor);
}
tft.setCursor(50, notifY, 1);
tft.print("LIVE");
tft.setCursor(titleX, titleY);
tft.setFreeFont(FSSB18);
tft.setTextColor(titleColor, background);
tft.print("AFL ");
if (!(rnd == currentRnd) || (!(year == currentYear))) {
tft.setTextColor(titleAltColor, background);
}
tft.print(get_roundname[0]);
tft.print(" ");
if (!(year == currentYear)) {
tft.setTextColor(titleAltColor, background);
} else {
tft.setTextColor(titleColor, background);
}
tft.print(year);
picked = 0;
played = 0;
gameLive = 0;
// display routine using the layout parameters defined at the beginning
for (int i = 0; i < totalGames; i++) {
if (get_hteam[orderG[i]]) {
tft.setTextColor(teamColor, background); // resets the colour for printing the team
tft.setCursor(hteamLoc, ((i * lineSpacing) + line1Start));
tft.setFreeFont(FSS9);
if (strcmp(get_hteam[orderG[i]], get_tip[orderT[i]]) == 0) {
tft.setTextColor(pickedColor, background); // set tipped team colour
}
tft.print(get_hteam[orderG[i]]);
tft.setTextColor(teamColor, background);
tft.setCursor(vsLoc, ((i * lineSpacing) + line1Start));
tft.print(" vs. ");
tft.setCursor(ateamLoc, ((i * lineSpacing) + line1Start));
if (strcmp(get_ateam[orderG[i]], get_tip[orderT[i]]) == 0) {
tft.setTextColor(pickedColor, background); // set tipped team colour
}
tft.print(get_ateam[orderG[i]]);
tft.setTextColor(teamColor, background);
// set font colour to wrongScoreColor if complete, liveScoreColor if playing, pickedScoreColor if complete and picked, pickedScoreColor if a draw
if ((get_complete[orderG[i]] != 0) || (startLive[orderG[i]] == 1)) {
if (get_complete[orderG[i]] == 100) {
played = played + 1;
tft.setTextColor(wrongScoreColor, background); // wrongScoreColor score for a completed game, not tipped
} else {
tft.setTextColor(liveScoreColor, background); // liveScoreColor score for a live game
if ((get_complete[orderG[i]] == 25) || (get_complete[orderG[i]] == 50) || (get_complete[orderG[i]] == 75)) {
tft.setTextColor(gamebreakColor, background); // gamebreakColor score for quarter, half, three quarter time
}
gameLive = 1;
tft.fillRect(LnStart - 13, (line1Start - 16 + (i * lineSpacing)), (LnEnd - LnStart + 32), 20, background);
}
if (strcmp(get_winner[orderG[i]], get_tip[orderT[i]]) == 0) { // pickedScoreColor for a correct tip
tft.setTextColor(pickedScoreColor, background);
picked = picked + 1;
}
if ((get_hscore[orderG[i]] == get_ascore[orderG[i]]) && (get_complete[orderG[i]] == 100)) { // if there's a tie, it counts as a tipped win
tft.setTextColor(pickedScoreColor, background);
picked = picked + 1;
}
tft.setCursor(scoreLoc, ((i * lineSpacing) + line1Start)); // scores are lined up and printed if the game has started
if ((get_hscore[orderG[i]] < 100) && (get_hscore[orderG[i]] > 19)) { // moves the home score over a bit if the home score has two digits
tft.setCursor((scoreLoc + 10), ((i * lineSpacing) + line1Start));
}
if ((get_hscore[orderG[i]] < 20) && (get_hscore[orderG[i]] > 9)) { // moves the home score over a bit if the first digit is a 1
tft.setCursor((scoreLoc + 7), ((i * lineSpacing) + line1Start));
}
if (get_hscore[orderG[i]] < 10) { // moves the home score over a bit if the home score is single digit
tft.setCursor((scoreLoc + 16), ((i * lineSpacing) + line1Start));
}
if ((get_hscore[orderG[i]] < 120) && (get_hscore[orderG[i]] > 109)) { // moves the home score over a bit if the home score has a 1 as the second digit
tft.setCursor((scoreLoc + 3), ((i * lineSpacing) + line1Start));
}
if (!(prevPlayed == played)) {
tft.fillRect(LnEnd + 10, (line1Start - 16 + (i * lineSpacing)), (LnEnd + 32), 20, background);
}
tft.print(get_hscore[orderG[i]]);
tft.setCursor((scoreLoc + 38), ((i * lineSpacing) + line1Start));
tft.print(get_ascore[orderG[i]]);
if ((get_complete[orderG[i]] > 0) && (get_complete[orderG[i]] <= 25)) { // displays 1 dot if it is the first quarter
tft.setCursor((scoreLoc + 76), ((i * lineSpacing) + line1Start - 8), 1);
tft.print(".");
}
if ((get_complete[orderG[i]] > 25) && (get_complete[orderG[i]] <= 50)) { // displays 2 dots if it is the second quarter
tft.setCursor((scoreLoc + 76), ((i * lineSpacing) + line1Start - 8), 1);
tft.print(".");
tft.setCursor((scoreLoc + 76), ((i * lineSpacing) + line1Start - 14), 1);
tft.print(".");
}
if ((get_complete[orderG[i]] > 50) && (get_complete[orderG[i]] <= 75)) { // displays 3 dots if it is the third quarter
tft.setCursor((scoreLoc + 76), ((i * lineSpacing) + line1Start - 8), 1);
tft.print(".");
tft.setCursor((scoreLoc + 76), ((i * lineSpacing) + line1Start - 14), 1);
tft.print(".");
tft.setCursor((scoreLoc + 70), ((i * lineSpacing) + line1Start - 8), 1);
tft.print(".");
}
if ((get_complete[orderG[i]] > 75) && (get_complete[orderG[i]] < 100)) { // displays 4 dots if it is the fourth quarter
tft.setCursor((scoreLoc + 76), ((i * lineSpacing) + line1Start - 8), 1);
tft.print(".");
tft.setCursor((scoreLoc + 76), ((i * lineSpacing) + line1Start - 14), 1);
tft.print(".");
tft.setCursor((scoreLoc + 70), ((i * lineSpacing) + line1Start - 8), 1);
tft.print(".");
tft.setCursor((scoreLoc + 70), ((i * lineSpacing) + line1Start - 14), 1);
tft.print(".");
}
tft.setTextColor(teamColor, background);
if ((i == 0) || (strcmp(gameDate[orderG[i]], gameDate[orderG[i - 1]]) != 0)) { // a line is drawn under the last game of the day
newdate = newdate + 1; // indicates that a new day has begun
if (i != 0) { tft.drawLine(LnStart, (((i - 1) * lineSpacing) + (line1Start + 6)), LnEnd, (((i - 1) * lineSpacing) + (line1Start + 6)), lineColor); }
}
} else { // if the game hasn't started, the start time is listed
tft.setCursor(timeLoc, ((i * lineSpacing) + line1Start));
if ((gameTime[orderG[i]][0]) != ' ') {
tft.setCursor((timeLoc - 4), ((i * lineSpacing) + line1Start));
}
if (((gameTime[orderG[i]][0]) == ' ') && ((gameTime[orderG[i]][1]) == '1')) {
tft.setCursor((timeLoc - 1), ((i * lineSpacing) + line1Start));
}
if ((i == 0) || (strcmp(gameDate[orderG[i]], gameDate[orderG[i - 1]]) != 0)) { // a line is drawn under the last game time of the day
newdate = newdate + 1; // indicates that a new day has begun
if (i != 0) { tft.drawLine(LnStart, (((i - 1) * lineSpacing) + (line1Start + 6)), LnEnd, (((i - 1) * lineSpacing) + (line1Start + 6)), lineColor); }
}
if (newdate == 1) { // changes the games time colour based on grouping same days together
tft.setTextColor(timeColor1, background);
}
if (newdate == 2) {
tft.setTextColor(timeColor2, background);
}
if (newdate == 3) {
tft.setTextColor(timeColor1, background);
}
if (newdate == 4) {
tft.setTextColor(timeColor2, background);
}
if (newdate == 5) {
tft.setTextColor(timeColor1, background);
}
if (newdate == 6) {
tft.setTextColor(timeColor2, background);
}
tft.print(gameTime[orderG[i]]); // prints the game time
}
}
}
// Places the tally of correct tips at the bottom
if (savedSource == tipSource) {
tft.setTextColor(tallyColor, background);
} else {
tft.setTextColor(titleAltColor, background);
}
tft.setCursor(tallyX, tallyY);
tft.setFreeFont(FSSB18);
if (!(prevPlayed == played)) {
tft.fillRect(tallyX - 15, 285, 280, 80, background); //blanks out the bottom data
prevPlayed = played;
}
if (played == 0) {
tft.print("No Results Yet");
} else {
tft.setCursor(tallyX - 15, tallyY);
tft.print(picked);
tft.print(" correct from ");
tft.print(played);
}
tft.setCursor(0, 310, 1);
tft.setTextColor(notifColor, background);
tft.print("MENU");
tft.setCursor(440, 310, 1);
tft.setTextColor(notifColor, background);
tft.print("LADDER");
newdate = 0;
}
void printSerial() { // Serial printing for debugging
Serial.println();
Serial.print("Delay = ");
Serial.print((del + delHTTP) / 1000);
Serial.println(" seconds");
Serial.println();
Serial.print("AFL ");
Serial.println(get_roundname[1]);
for (int i = 0; i < totalGames; i++) {
if (get_hteam[orderG[i]] != "") {
Serial.print("Game ");
Serial.print(i + 1);
Serial.print(" = ");
Serial.print(get_hteam[orderG[i]]);
Serial.print(" vs. ");
Serial.print(get_ateam[orderG[i]]);
if (get_complete[orderG[i]]) {
Serial.print(" Score: ");
Serial.print(get_hscore[orderG[i]]);
Serial.print(" ");
Serial.print(get_ascore[orderG[i]]);
}
Serial.print(" Tip = ");
Serial.println(get_tip[orderT[i]]);
Serial.print("Game start time = ");
Serial.print(gameTime[orderG[i]]);
Serial.print(" Game date = ");
Serial.print(gameDate[orderG[i]]);
Serial.print(" Game complete % = ");
Serial.println(get_complete[orderG[i]]);
Serial.println();
Serial.print("Game Order = ");
Serial.println(orderG[i]);
Serial.print("Tip Order = ");
Serial.println(orderT[i]);
}
}
}
void startScreenTFT() { // sets out the loading messages in a rectangle while waiting for the time
tft.fillScreen(TFT_BLACK);
tft.fillRect(warnX - 10, warnY - 10, 280, 80, TFT_WHITE);
tft.setTextColor(TFT_RED, TFT_WHITE);
tft.setCursor(warnX, warnY, 4);
tft.println("Connected to internet");
tft.setCursor(warnX, warnY + 30, 4);
tft.print("Getting time... ");
}
void noWifi() { // sets out the loading messages in a rectangle while waiting for the time