-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathTallly-Core2-V12.ino
executable file
·1297 lines (1079 loc) · 49.7 KB
/
Tallly-Core2-V12.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
#include <M5Core2.h>
#include <Fonts/EVA_20px.h>
#include <stdio.h>
#include <WiFi.h>
#include <EEPROM.h>
/* Version .6 includes reading the eeprom for config data
* Version .7 added EEPROM config capability and CRC32 checking
* Version .8 added EEPROM config encyption
* Version .9 added static client ip address and addressed heap corruption issue
* Version 1.0 added Battery status
* Version 1.1 Updated to support new Touch library (Version .003+) & new SD & FS libraries (Version: 2.0.0).
* Version 1.2 Added Stream status (On Air) indicator
*/
// Config File Reader header file from
// https://github.com/arduino-libraries/Arduino_CRC32
#include <Arduino_CRC32.h>
// Config File Reader header file from
// https://github.com/bneedhamia/sdconfigfile
#include <SDConfigFile.h>
// Cipher header/cpp files from
// https://github.com/josephpal/esp32-Encrypt
#include <Cipher.h>
// ATEM header files from
// https://github.com/kasperskaarhoj/SKAARHOJ-Open-Engineering/tree/master/ArduinoLibs
#include <SkaarhojPgmspace.h>
#include <ATEMstd.h>
// Define Cipher pre-key
#define CIPHER_PKEY "M5C2"
// Create adruino crc32 object
Arduino_CRC32 crc32;
// Config file name, function, config file max length and return value
boolean postConfig = false; // Global boolean to allow event handler to determine how to handle button events
long fileNumber = -1; // Global variable to hold selected file array offset
const uint8_t CONFIG_LINE_LENGTH = 120; // Max config line length
boolean readSDconfig(String configFile, int configFileLength);
boolean readEEconfig(int configFileLength);
char *readEEPROMString(int baseAddress, int line_length, int stringNumber);
boolean addToEEPROM(int baseAddress, const char *text);
boolean writeEEconfig();
boolean didReadConfig;
void updateBattery();
//On Air Status (OAS) locations
#define OAS_XLOC 135 // Right of center second row
#define OAS_YLOC 7 // First Row
#define OAS_SIZE 6 // Standard Size
// ATEM Connection and Battery Status Locations
#define ACS_XLOC 120 // Center in second column
#define ACS_XLOC_OAS 105 // Left of center when OAS is enabled
#define ACS_YLOC 7 // 7 First Row
#define ACS_SIZE 6 // 7 Standard Size:
#define BS_VYLOC 0 // 110 Second Row : 0 First Row
#define BS_VXLOC 170 // 210 Left of Battery : 170 Third Column
#define BS_BYLOC 0 // 109 Second Row : 0 First Row
#define BS_BXLOC 265 // 285 Far Right : 265 Forth Column
// First address of EEPROM to write to.
const int START_ADDRESS = 0;
// Marks the end of the data written to EEPROM
const byte EEPROM_END_MARK = 255;
// Maximum bytes (including null) of any string we support.
const int EEPROM_MAX_STRING_LENGTH = 120;
// Define starting point for EEPROM write
int nextEEPROMaddress = START_ADDRESS;
// Define if eeprom is to be written to when SD card missing or config file missing
boolean weeProm = false;
// Used to define if client static ip data is to be used
boolean staticConfig = false;
// Used to define if configuration options data is to be used
boolean optionsConfig = false;
// Used to define if oas was enabled in configuration
boolean oasEnabled = false;
// Configuration data
// Client ID and Network info and switch ip address defined in config file
const char *cfgVer;
const char *M5id;
const char *ssid;
const char *password;
const char *atemIp;
const char *tallyIp;
const char *subMask;
const char *gatewayIp;
const char *dnsIp;
// Debug wait times defined in config file
boolean waitEnable = false;
int waitMS = 0;
// Options defined in config file, currently only oas is supported
const char *oas;
// Place holder for ip conversion
uint8_t ip[4];
unsigned int tip[4];
// Define current running macro and tallyStates array
int currentRunningMacro = -1;
int tallyStates[4];
/* macroOffset - Used to determine macro number based on button number + offset value
* 0 for page 1, values 0 thru 3
* 4 for page 2, values 4 thru 7
* 8 for page 3, values 8 thru 11
*/
int macroOffset = 0;
#define CAMERA_OFF 0
#define CAMERA_PREVIEW 1
#define CAMERA_PROGRAM 2
// Defined button sizes and locations
#define BUTTON_SIZE 78
#define BUTTON_SPACE 2
#define CAMERA_BUTTON_Y 20 // 42
#define MACRO_BUTTON_Y 130 // 150
int buttonOneLocationX = BUTTON_SPACE;
int buttonTwoLocationX = (BUTTON_SPACE * 2) + BUTTON_SIZE;
int buttonThreeLocationX = (BUTTON_SPACE * 3) + (BUTTON_SIZE * 2);
int buttonFourLocationX = (BUTTON_SPACE * 4) + (BUTTON_SIZE * 3);
// Create ATEM object
ATEMstd AtemSwitcher;
void HaltProgram() {
// M5.Lcd.fillScreen(BLACK);
M5.Lcd.fillRect(1, (M5.Lcd.height() / 2) - (M5.Lcd.fontHeight() / 2), M5.Lcd.width(), 50, TFT_BLACK);
M5.Lcd.drawCentreString("Shutting Down", (M5.Lcd.width() / 2), (M5.Lcd.height() / 2) - (M5.Lcd.fontHeight() / 2), 1);
for (int x = 10; x > 0; x--) {
M5.Lcd.fillRect(10, 180, 300, 50, TFT_BLACK);
M5.Lcd.progressBar(10, 180, 300, 50, x * 10);
delay(1000);
}
M5.shutdown();
}
// Update Battery Status Indication
void updateBattery() {
static float batVoltage;
static float lastVoltage = 0;
static float batPercentage;
static int screenVoltage;
static uint16_t batColor;
// Determine Battery State
batVoltage = (round(M5.Axp.GetBatVoltage() * 10) / 10); // We're not interested in the last digit thus round it down to 0. This helps limit the amount of refreshes
// Only refresh the battery indication if it's changed
if (lastVoltage != batVoltage) {
lastVoltage = batVoltage;
batPercentage = (batVoltage <= 3.2) ? .1 : (batVoltage - 3.2);
batColor = batPercentage <= .20 ? TFT_RED : (batPercentage > .20 && batPercentage < .30) ? TFT_YELLOW
: TFT_GREEN;
M5.Lcd.fillRect(BS_VXLOC, BS_VYLOC, 60, 17, TFT_BLACK);
M5.Lcd.fillRect(BS_BXLOC, BS_BYLOC + 5, 7, 7, TFT_LIGHTGREY); // Draw positive end of battery
M5.Lcd.drawRect(BS_BXLOC + 5, BS_BYLOC, 30, 17, TFT_LIGHTGREY); // Draw battery outline
M5.Lcd.fillRect(BS_BXLOC + 6, BS_BYLOC + 1, 28, 15, batColor); // Fill the box green
M5.Lcd.fillRect(BS_BXLOC + 6, BS_BYLOC + 1, int(28 - (28 * batPercentage)), 15, TFT_BLACK); // Remove portion drained
if (M5.Axp.isACIN()) {
M5.Lcd.setTextColor(TFT_GREEN); // Set color to green
M5.Lcd.setTextSize(2);
M5.Lcd.drawChar(BS_VXLOC, BS_VYLOC + 1, '+', TFT_GREEN, TFT_BLACK, 2); // Indicate charging
M5.Lcd.drawFloat(batVoltage, 2, BS_VXLOC + 40, BS_VYLOC, 1); // Indicate voltage
} else {
M5.Lcd.setTextColor(TFT_WHITE); // Set color to white
M5.Lcd.setTextSize(2);
M5.Lcd.drawChar(BS_VXLOC, BS_VYLOC + 1, '-', TFT_LIGHTGREY, TFT_BLACK, 2); // Indicate discharging
M5.Lcd.drawFloat(batVoltage, 2, BS_VXLOC + 40, BS_VYLOC, 1); // Indicate Voltage
}
// Adjust screen voltage based on current battery level 2600 - 3300, nominal 3000
screenVoltage = (batColor == TFT_GREEN) ? 3300 : (batColor == TFT_YELLOW) ? 3000
: 2700;
M5.Axp.SetLcdVoltage(screenVoltage);
}
} // End updateBattery
// Beginning SD readFiles funtcion
int readFiles(File dir, String fileNames[], int sizeOfArray, String extension) {
int fileCount = 0;
String fileName[1];
while (fileCount < sizeOfArray) {
File entry = dir.openNextFile();
if (!entry) return (fileCount);
fileName[0] = entry.path(); // Updated to support the new 2.0.0 FS::path functionality to mimic old 1.0.5 SD / FS Libraries.
if (fileName[0].endsWith(extension)) {
fileNames[fileCount] = entry.path(); // Updated to support the new 2.0.0 FS::path functionality to mimic old 1.0.5 SD / FS Libraries.
fileCount++;
}
}
return (fileCount);
}
// End readFiles function
// Choose Configuration function
String chooseConfigFile() {
#define FILE_EXTENSION ".cfg" // Define config file extension
#define ARRAYSIZE 4 // Maximum size of array to hold file names read from sd card
#define FBUTTON_XSIZE 300 // Filename button x size
#define FBUTTON_YSIZE 50 // Filename button y size
#define FBUTTON_XLOC 10 // Filename buttons x starting location
#define FBUTTON_YLOC 60 // Filename buttons y location multiplier
#define FBUTTON_RADIUS 6 // Filename button corner radius size
int font = 1; // Text font number
String results[ARRAYSIZE]; // Array to hold filenames
int count = 0; // Used to hold filename count returned from readFiles function
M5.begin(true, true, false, true); // Start the M5 subsystem
M5.Lcd.fillScreen(BLACK); // Clear the screen
M5.Lcd.setTextSize(3); // Set text size to 30mm
M5.Lcd.setTextColor(WHITE); // Set color to white
// Define the buttons
Button f0(FBUTTON_XLOC, 1, FBUTTON_XSIZE, FBUTTON_YSIZE, false, "f0");
Button f1(FBUTTON_XLOC, FBUTTON_YLOC, FBUTTON_XSIZE, FBUTTON_YSIZE, false, "f1");
Button f2(FBUTTON_XLOC, (FBUTTON_YLOC * 2), FBUTTON_XSIZE, FBUTTON_YSIZE, false, "f2");
Button f3(FBUTTON_XLOC, (FBUTTON_YLOC * 3), FBUTTON_XSIZE, FBUTTON_YSIZE, false, "f3");
// Read the SD card
File root = SD.open("/"); // Open the root file system
count = readFiles(root, results, ARRAYSIZE, FILE_EXTENSION); // Read the filenames that end with defined extension
switch (count) {
case 0:
// No config files found on SD card
return ("");
case 1:
// If there is only one config file forgo the menu and just set fileNumber
fileNumber = 0;
break;
case 2:
// Two files found on disk so create a two file menu system
M5.Lcd.fillRoundRect(FBUTTON_XLOC, 1, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[0], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), 1 + 12, font);
M5.Lcd.fillRoundRect(FBUTTON_XLOC, FBUTTON_YLOC, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[1], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), FBUTTON_YLOC + 12, font);
break;
case 3:
// Three files found on disk so create a three file menu system
M5.Lcd.fillRoundRect(FBUTTON_XLOC, 1, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[0], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), 1 + 12, font);
M5.Lcd.fillRoundRect(FBUTTON_XLOC, FBUTTON_YLOC, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[1], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), FBUTTON_YLOC + 12, font);
M5.Lcd.fillRoundRect(FBUTTON_XLOC, FBUTTON_YLOC * 2, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[2], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), (FBUTTON_YLOC * 2) + 12, font);
break;
default:
// Covers cases where count is >= 4. In this case we limit the list to the first 4
M5.Lcd.fillRoundRect(FBUTTON_XLOC, 1, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[0], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), 1 + 12, font);
M5.Lcd.fillRoundRect(FBUTTON_XLOC, FBUTTON_YLOC, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[1], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), FBUTTON_YLOC + 12, font);
M5.Lcd.fillRoundRect(FBUTTON_XLOC, FBUTTON_YLOC * 2, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[2], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), (FBUTTON_YLOC * 2) + 12, font);
M5.Lcd.fillRoundRect(FBUTTON_XLOC, FBUTTON_YLOC * 3, FBUTTON_XSIZE, FBUTTON_YSIZE, FBUTTON_RADIUS, TFT_DARKGREY);
M5.Lcd.drawCentreString(results[3], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), (FBUTTON_YLOC * 3) + 12, font);
break;
}
while (fileNumber == -1) {
delay(50);
M5.update();
}
M5.Lcd.fillScreen(BLACK);
M5.Lcd.drawCentreString(results[fileNumber], FBUTTON_XLOC + (FBUTTON_XSIZE / 2), (M5.Lcd.height() / 2) - (M5.Lcd.fontHeight() / 2), font);
delay(500);
return (results[fileNumber]);
} // End chooseConfig function
/*
* Read configuration settings from EEProm.
* Returns true if successful, false if it failed.
* Failures can include: no data, missing data or failed CRC
*/
boolean readEEconfig() {
uint32_t crc32_results = 0; // Calculated CRC32
uint32_t eeCRC = 0; // CRC
char *eeCRCEncrypted = nullptr; // Encrypted EEPROM CRC
char *dataExists = nullptr; // Used to probe for EE CRC data
// Create cipher object, key and working place holders
Cipher *cipher = new Cipher();
char cipherKey[17];
// Defined String to hold decrypted data
String decryptedString;
// Defined new string arrays to hold decrypted data copied from decrypted String. This solves the decrypted String scope and heap corruption problems.
char *ssidDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *passwordDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *atemIpDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *cfgVerDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *M5idDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *eeCRCDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *tallyIpDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *subMaskDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *gatewayIpDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *dnsIpDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
char *oasDecrypted = new char[EEPROM_MAX_STRING_LENGTH + 1];
// Generate second part of key based on 12 byte mac address
uint64_t chipid = ESP.getEfuseMac(); // The chip ID is essentially its MAC address(length: 6 bytes).
uint16_t chip = (uint16_t)(chipid >> 32); // Shift right 16 bits and then then cast to 16 bits
// Generate full key based on pre-key + hardware mac address
snprintf(cipherKey, 17, CIPHER_PKEY "%04X%08X", chip, (uint32_t)chipid);
// Set the key
cipher->setKey(cipherKey);
// Read standard config vaulues from EEPROM
cfgVer = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 0);
M5id = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 1);
ssid = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 2);
password = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 3);
atemIp = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 4);
eeCRCEncrypted = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 5);
// Test results, should not have returned a nullptr
if (!cfgVer || !M5id || !ssid || !password || !atemIp || !eeCRCEncrypted) {
Serial.println(F("EEPROM has not yet been initialized or incomplete dataset."));
return (false);
} else {
// Test to see if any data exists at line 10. If so that means both static ip data exists as well as the oas option
dataExists = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 10);
if (dataExists) {
optionsConfig = true;
staticConfig = true;
tallyIp = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 5);
subMask = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 6);
gatewayIp = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 7);
dnsIp = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 8);
oas = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 9);
eeCRCEncrypted = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 10);
if (!tallyIp || !gatewayIp || !dnsIp) {
Serial.println(F("EEPROM incomplete static ip dataset."));
return (false);
}
if (!oas) {
Serial.println(F("EEPROM incomplete options dataset."));
return (false);
}
} else {
// Test to see if any data exists at line 9. If so that means just static ip data exists
dataExists = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 9);
if (dataExists) {
staticConfig = true;
tallyIp = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 5);
subMask = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 6);
gatewayIp = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 7);
dnsIp = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 8);
eeCRCEncrypted = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 9);
if (!tallyIp || !gatewayIp || !dnsIp) {
Serial.println(F("EEPROM incomplete static ip dataset."));
return (false);
}
} else {
// Test to see if any data exists at line 6. If so that means that just options data exists
dataExists = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 6);
if (dataExists) {
optionsConfig = true;
oas = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 5);
eeCRCEncrypted = readEEPROMString(START_ADDRESS, CONFIG_LINE_LENGTH, 6);
if (!oas) {
Serial.println(F("EEPROM incomplete options dataset."));
return (false);
}
}
}
}
}
// If static ip config data was detected during EEProm read go ahead and allocate the memory to hold the decrypted data. Decrypted data should always be shorter than encrypted data.
if (staticConfig) {
tallyIpDecrypted = new char[strlen(tallyIp) + 1];
subMaskDecrypted = new char[strlen(subMask) + 1];
gatewayIpDecrypted = new char[strlen(gatewayIp) + 1];
dnsIpDecrypted = new char[strlen(dnsIp) + 1];
}
// If options data was detected during EEProm read go ahead and allocate the memory to hold the decrypted data.
if (optionsConfig) {
oasDecrypted = new char[strlen(oas) + 1];
}
// Decrypt the config strings, then copy decrtypedString to allocated memory then assign global pointer to allocated memory
decryptedString = cipher->decryptString(String(cfgVer));
decryptedString.toCharArray(cfgVerDecrypted, EEPROM_MAX_STRING_LENGTH + 1); // Done this way to avoid issues with VLA
cfgVer = cfgVerDecrypted;
decryptedString = cipher->decryptString(String(M5id));
decryptedString.toCharArray(M5idDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
M5id = M5idDecrypted;
decryptedString = cipher->decryptString(String(ssid));
decryptedString.toCharArray(ssidDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
ssid = ssidDecrypted;
decryptedString = cipher->decryptString(String(password));
decryptedString.toCharArray(passwordDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
password = passwordDecrypted;
decryptedString = cipher->decryptString(String(atemIp));
decryptedString.toCharArray(atemIpDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
atemIp = atemIpDecrypted;
decryptedString = cipher->decryptString(String(eeCRCEncrypted));
decryptedString.toCharArray(eeCRCDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
// If static IP data was detected during EEProm read go ahead and decrypt the data
if (staticConfig) {
decryptedString = cipher->decryptString(String(tallyIp));
decryptedString.toCharArray(tallyIpDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
tallyIp = tallyIpDecrypted;
decryptedString = cipher->decryptString(String(subMask));
decryptedString.toCharArray(subMaskDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
subMask = subMaskDecrypted;
decryptedString = cipher->decryptString(String(gatewayIp));
decryptedString.toCharArray(gatewayIpDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
gatewayIp = gatewayIpDecrypted;
decryptedString = cipher->decryptString(String(dnsIp));
decryptedString.toCharArray(dnsIpDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
dnsIp = dnsIpDecrypted;
}
// If options data was detected during EEProm read go ahead and decrypt the data
if (optionsConfig) {
decryptedString = cipher->decryptString(String(oas));
decryptedString.toCharArray(oasDecrypted, EEPROM_MAX_STRING_LENGTH + 1);
oas = oasDecrypted;
// Convert CRC back to unsigned long after decryption
eeCRC = strtoul(eeCRCDecrypted, NULL, 0);
// Calculate CRC values.
// Unlike Zlib's crc32_combine this simply adds the values together to provide a reasonable assurance
// the values were not corrupted while writing the EEPROM.
crc32_results = crc32.calc((uint8_t const *)cfgVer, strlen(cfgVer)); // strlen(cfgVer));
crc32_results = crc32_results + crc32.calc((uint8_t const *)M5id, strlen(M5id));
crc32_results = crc32_results + crc32.calc((uint8_t const *)ssid, strlen(ssid));
crc32_results = crc32_results + crc32.calc((uint8_t const *)password, strlen(password));
crc32_results = crc32_results + crc32.calc((uint8_t const *)atemIp, strlen(atemIp));
if (staticConfig) {
crc32_results = crc32_results + crc32.calc((uint8_t const *)tallyIp, strlen(tallyIp));
crc32_results = crc32_results + crc32.calc((uint8_t const *)subMask, strlen(subMask));
crc32_results = crc32_results + crc32.calc((uint8_t const *)gatewayIp, strlen(gatewayIp));
crc32_results = crc32_results + crc32.calc((uint8_t const *)dnsIp, strlen(dnsIp));
}
if (optionsConfig) {
crc32_results = crc32_results + crc32.calc((uint8_t const *)oas, strlen(oas));
}
if (crc32_results != eeCRC) {
Serial.print(crc32_results);
Serial.print(" : ");
Serial.println(eeCRC);
Serial.println(F("Calculated vs saved CRC not equal"));
return (false);
}
}
return (true);
}
// EEProm Reader
char *readEEPROMString(int baseAddress, int maxLength, int stringNumber) {
int start; // EEPROM address of the first byte of the string to return.
int blength; // Length (bytes) of the string to return, less the terminating null.
char ch;
int nextAddress; // Next address to read from EEPROM.
char *result; // Points to the dynamically-allocated result to return.
int i;
#if defined(ESP8266) || defined(ESP32)
EEPROM.begin(512);
#endif
nextAddress = baseAddress;
for (i = 0; i < stringNumber; ++i) {
// If the first byte is an end mark, we've run out of strings too early.
ch = (char)EEPROM.read(nextAddress++);
if (ch == (char)EEPROM_END_MARK) {
#if defined(ESP8266) || defined(ESP32)
EEPROM.end();
#endif
return (nullptr); // Return nullptr if not enough strings are in EEPROM.
}
// Read through the string's terminating null (0).
int blength = 0;
while (ch != '\0' && blength < maxLength - 1) {
++blength;
ch = EEPROM.read(nextAddress++);
}
}
// We're now at the start of what should be our string.
start = nextAddress;
// If the first byte is an end mark, we've run out of strings too early.
ch = (char)EEPROM.read(nextAddress++);
if (ch == (char)EEPROM_END_MARK) {
#if defined(ESP8266) || defined(ESP32)
EEPROM.end();
#endif
return (nullptr); // Return nullptr if not enough strings are in EEPROM.
}
// Count to the end of this string.
blength = 0;
while (ch != '\0' && blength < maxLength - 1) {
++blength;
ch = EEPROM.read(nextAddress++);
}
// Allocate space for the string, then copy it.
result = new char[blength + 1];
nextAddress = start;
for (i = 0; i < blength; ++i) {
result[i] = (char)EEPROM.read(nextAddress++);
}
result[i] = '\0';
return (result);
} // End EEProm Reader
boolean writeEEconfig() {
// Writes configuration data + crc32 to EEPROM
uint32_t crc32_results = 0;
const char *encryptedData = nullptr;
String workingData;
// Used to convert crc2_reults to string to be written to EEPROM
char cfgCRC[sizeof(unsigned int) * 8 + 1];
// Create cipher object and key
Cipher *cipher = new Cipher();
char cipherKey[17];
// Generate second part of key based on 12 byte mac address
uint64_t chipid = ESP.getEfuseMac(); // The chip ID is essentially its MAC address(length: 6 bytes).
uint16_t chip = (uint16_t)(chipid >> 32);
// Generate full key based on pre-key + hardware mac address
snprintf(cipherKey, 17, CIPHER_PKEY "%04X%08X", chip, (uint32_t)chipid);
// Set the key
cipher->setKey(cipherKey);
// Generate CRC32 value for each value
// Unlike Zlib's crc32_combine this simply adds the values together to provide a reasonable assurance
// the values were not corruped while writing the EEPROM.
crc32_results = crc32.calc((uint8_t const *)cfgVer, strlen(cfgVer));
crc32_results = crc32_results + crc32.calc((uint8_t const *)M5id, strlen(M5id));
crc32_results = crc32_results + crc32.calc((uint8_t const *)ssid, strlen(ssid));
crc32_results = crc32_results + crc32.calc((uint8_t const *)password, strlen(password));
crc32_results = crc32_results + crc32.calc((uint8_t const *)atemIp, strlen(atemIp));
if (staticConfig) {
crc32_results = crc32_results + crc32.calc((uint8_t const *)tallyIp, strlen(tallyIp));
crc32_results = crc32_results + crc32.calc((uint8_t const *)subMask, strlen(subMask));
crc32_results = crc32_results + crc32.calc((uint8_t const *)gatewayIp, strlen(gatewayIp));
crc32_results = crc32_results + crc32.calc((uint8_t const *)dnsIp, strlen(dnsIp));
}
if (optionsConfig) {
crc32_results = crc32_results + crc32.calc((uint8_t const *)oas, strlen(oas));
}
// Convert crc restults to string to write to eeprom
sprintf(cfgCRC, "%u", crc32_results);
// Write to eprom
// Add cfgVer
workingData = cipher->encryptString(String(cfgVer));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write cfgVer failed. Reset to try again."));
return (false);
}
// Add M5id
workingData = cipher->encryptString(String(M5id));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write M5id failed. Reset to try again."));
return (false);
}
// Add ssid
workingData = cipher->encryptString(String(ssid));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write ssid failed. Reset to try again."));
return (false);
}
// Add password
workingData = cipher->encryptString(String(password));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write password failed. Reset to try again."));
return (false);
}
// Add atemIp
workingData = cipher->encryptString(String(atemIp));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write atemIp failed. Reset to try again."));
return (false);
}
// If static config has been enabled write the static ip data to EEPROM
if (staticConfig) {
// Add tallyIp
workingData = cipher->encryptString(String(tallyIp));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write tallyIp failed. Reset to try again."));
return (false);
}
// Add subMask
workingData = cipher->encryptString(String(subMask));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write subMask failed. Reset to try again."));
return (false);
}
// Add gatewayIp
workingData = cipher->encryptString(String(gatewayIp));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write gatewayIp failed. Reset to try again."));
return (false);
}
// Add dnsIp
workingData = cipher->encryptString(String(dnsIp));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write dnsIp failed. Reset to try again."));
return (false);
}
}
// If configuration options has been enabled, write the options data to EEPROM
if (optionsConfig) {
// Add oas which is currently the only option supported
workingData = cipher->encryptString(String(oas));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write oas failed. Reset to try again."));
return (false);
}
} // End optionsConfig data
// Add CRC Data
workingData = cipher->encryptString(String(cfgCRC));
encryptedData = workingData.c_str();
if (!addToEEPROM(START_ADDRESS, encryptedData)) {
Serial.println(F("Write CRC failed. Reset to try again."));
return (false);
}
// Add end of data mark
if (!addToEEPROM(EEPROM_END_MARK, "EEPROM_END_MARK")) {
Serial.println(F("Write EEPROM_END_MARK failed. Reset to try again."));
return (false);
}
return (true);
} // End writeEEconfig()
/*
* Append the given null-terminated string to the EEPROM.
* Return true if successful; false otherwise.
*/
boolean addToEEPROM(int baseAddress, const char *text) {
// Unfortunately, we can't know how much EEPROM is left.
static int nextEEPROMaddress = baseAddress;
#if defined(ESP8266) || defined(ESP32)
/*
* The ESP8266 and ESP32 EEPROM library differs from
* the standard Arduino library. It is a cached model,
* I assume to minimize limited EEPROM write cycles.
*/
EEPROM.begin(512);
#endif
if (baseAddress == EEPROM_END_MARK) {
EEPROM.write(nextEEPROMaddress++, EEPROM_END_MARK);
} else {
do {
EEPROM.write(nextEEPROMaddress++, (byte)*text);
} while (*text++ != '\0');
}
#if defined(ESP8266) || defined(ESP32)
EEPROM.end();
#endif
return (true);
} // End addToEEPROM()
/*
* Read our settings from our SD configuration file.
* Returns true if successful, false if it failed.
*/
boolean readSDconfig(String configFile, int configLineLength) {
// Defined config file object
SDConfigFile cfg;
// Convert string to constant charater
const char *cfName = configFile.c_str();
// Define keyCount count;
int keyCount = 0; // Minimum keys required to work
int staticCount = 0; // Required number of static parameters to work
int optionsCount = 0; // Required number of optional parameters
// Open the configuration file.
if (!cfg.begin(cfName, configLineLength)) {
Serial.print("Failed to open configuration file: ");
Serial.println(cfName);
return (false);
}
// Read each setting from the file.
while (cfg.readNextSetting()) {
if (cfg.nameIs("weeProm")) {
weeProm = cfg.getBooleanValue();
} else if (cfg.nameIs("cfgVer")) {
cfgVer = cfg.copyValue();
keyCount++;
} else if (cfg.nameIs("waitEnable")) {
waitEnable = cfg.getBooleanValue();
// waitMS integer
} else if (cfg.nameIs("waitMS")) {
waitMS = cfg.getIntValue();
// Tally ID (char *)
} else if (cfg.nameIs("M5id")) {
M5id = cfg.copyValue();
keyCount++;
// ssid string (char *)
} else if (cfg.nameIs("ssid")) {
ssid = cfg.copyValue();
keyCount++;
// Password string (char *)
} else if (cfg.nameIs("password")) {
password = cfg.copyValue();
keyCount++;
// Wwitch string (char *)
} else if (cfg.nameIs("atemIp")) {
atemIp = cfg.copyValue();
keyCount++;
} else if (cfg.nameIs("tallyIp")) {
tallyIp = cfg.copyValue();
staticCount++;
} else if (cfg.nameIs("subMask")) {
subMask = cfg.copyValue();
staticCount++;
} else if (cfg.nameIs("gatewayIp")) {
gatewayIp = cfg.copyValue();
staticCount++;
} else if (cfg.nameIs("dnsIp")) {
dnsIp = cfg.copyValue();
staticCount++;
} else if (cfg.nameIs("oas")) {
oas = cfg.copyValue();
optionsCount++;
} else {
// Report unrecognized names.
Serial.print("Unknown key name in config: ");
Serial.println(cfg.getName());
}
}
// Clean up
cfg.end();
if (staticCount == 4) staticConfig = true; // Set tally client staticConfig to true if all four parameters (tally ip, subnet mask, gateway and dns) are provided
if (optionsCount == 1) optionsConfig = true; // Set optionsConfig to true if all options have been provided (currently limited to oas)
// Required key config values is five as of version .8
if (keyCount < 5) return (false);
else return (true);
} // End readSDConfig
// Function to convert IPaddress to strings
String ip2Str(IPAddress ip) {
String s = "";
for (int i = 0; i < 4; i++) {
s += i ? "." + String(ip[i]) : String(ip[i]);
}
return s;
} // End of ip2St
// Button press function
void buttonWasPressed(Event &e) {
Button &tbtn = *e.button;
const char *buttonName = e.objName();
char buttonType = buttonName[0];
int buttonNumber = buttonName[1] - '0';
if (!postConfig) { // Update file selection button appropriately
fileNumber = buttonName[1] - 48; // Hack to convert ascii char to long
M5.Lcd.fillRect(tbtn.x, tbtn.y, tbtn.w, tbtn.h, tbtn.isPressed() ? WHITE : BLACK);
} else {
if ( e.type == E_TOUCH) { // If a touch event, set ATEM appropriately and update the button if a macro
if (buttonType == 'c') {
AtemSwitcher.changeProgramInput(buttonNumber);
} else {
updateMacroButton(buttonNumber, macroOffset, tbtn.isPressed());
AtemSwitcher.setMacroAction(buttonNumber + macroOffset, 0);
}
} else { // Release handler for macros to update button appropriately
if (buttonType == 'm') {
updateMacroButton(buttonNumber, macroOffset, tbtn.isPressed());
}
}
} // End camera/macro button handling
} // End buttonWasPressed
// Draw camera buttons
void drawCameraButton(int buttonNumber, int state) {
int font = 1;
M5.Lcd.setTextSize(6);
M5.Lcd.setTextColor(state == CAMERA_OFF ? WHITE : BLACK);
int buttonColor = TFT_DARKGREY;
switch (state) {
case CAMERA_OFF:
buttonColor = TFT_DARKGREY;
break;
case CAMERA_PREVIEW:
buttonColor = TFT_GREEN;
break;
case CAMERA_PROGRAM:
buttonColor = TFT_RED;
break;
}
switch (buttonNumber) {
case 1:
M5.Lcd.fillRoundRect(buttonOneLocationX, CAMERA_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("1", 2 + buttonOneLocationX + (BUTTON_SIZE / 2), CAMERA_BUTTON_Y + 18, font);
break;
case 2:
M5.Lcd.fillRoundRect(buttonTwoLocationX, CAMERA_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("2", 2 + buttonTwoLocationX + (BUTTON_SIZE / 2), CAMERA_BUTTON_Y + 18, font);
break;
case 3:
M5.Lcd.fillRoundRect(buttonThreeLocationX, CAMERA_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("3", 2 + buttonThreeLocationX + (BUTTON_SIZE / 2), CAMERA_BUTTON_Y + 18, font);
break;
case 4:
M5.Lcd.fillRoundRect(buttonFourLocationX, CAMERA_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("4", 2 + buttonFourLocationX + (BUTTON_SIZE / 2), CAMERA_BUTTON_Y + 18, font);
break;
}
} // End drawCameraButton
// Update Macro button function
void updateMacroButton(int buttonNumber, int macroOffset, bool isPressed) {
int font = 1;
M5.Lcd.setTextSize(6);
M5.Lcd.setTextColor(isPressed ? BLACK : WHITE);
int buttonColor = (isPressed ? WHITE : TFT_DARKGREY);
buttonNumber = buttonNumber + macroOffset;
switch (buttonNumber) {
case 0:
M5.Lcd.fillRoundRect(buttonOneLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("0", 2 + buttonOneLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 1:
M5.Lcd.fillRoundRect(buttonTwoLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("1", 2 + buttonTwoLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 2:
M5.Lcd.fillRoundRect(buttonThreeLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("2", 2 + buttonThreeLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 3:
M5.Lcd.fillRoundRect(buttonFourLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("3", 2 + buttonFourLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 4:
M5.Lcd.fillRoundRect(buttonOneLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("4", 2 + buttonOneLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 5:
M5.Lcd.fillRoundRect(buttonTwoLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("5", 2 + buttonTwoLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 6:
M5.Lcd.fillRoundRect(buttonThreeLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("6", 2 + buttonThreeLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 7:
M5.Lcd.fillRoundRect(buttonFourLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("7", 2 + buttonFourLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 8:
M5.Lcd.fillRoundRect(buttonOneLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("8", 2 + buttonOneLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 9:
M5.Lcd.fillRoundRect(buttonTwoLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("9", 2 + buttonTwoLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 10:
M5.Lcd.fillRoundRect(buttonThreeLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("10", 2 + buttonThreeLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
case 11:
M5.Lcd.fillRoundRect(buttonFourLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, buttonColor);
M5.Lcd.drawCentreString("11", 2 + buttonFourLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);
break;
}
} // End updateMacroButton
// Draw Macro button function
void drawMacroButtons(int macroOffset) {
int font = 1;
M5.Lcd.setTextSize(6);
M5.Lcd.setTextColor(WHITE);
switch (macroOffset) {
case 0:
M5.Lcd.fillRoundRect(buttonOneLocationX, MACRO_BUTTON_Y, BUTTON_SIZE, BUTTON_SIZE, 6, TFT_DARKGREY);
M5.Lcd.drawCentreString("0", 2 + buttonOneLocationX + (BUTTON_SIZE / 2), MACRO_BUTTON_Y + 18, font);