-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathESP32_Tracker.ino
5187 lines (4747 loc) · 189 KB
/
ESP32_Tracker.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
// Code Revision History:
//
// MM-DD-YYYY---Version---Description-----------------------------------------------------------------------
// 06-06-2019 v0.1.0 Cleaned up code for publication to https://M5stack.hackster.io
// 05-31-2019 v0.0.13 Added ability to have a power on and off splash screen. They are /PowerOn.jpg and
// /PowerOff.jpg stored on the SD card. If they are missing, the M5Stack logo will
// be displayed instead from the jpeg file /M5_Logo.jpg. This meant that I could
// remove the bmp_map.c file from the project which saved 153572 bytes when compiling.
// The .jpg files must not be larger than 320 x 240 in size.
// 05-29-2019 v0.0.12 Finally added Read_Config_File() to read external configuration from SD card.
// 05-28-2019 v0.0.11 Added code to make the OTA Update process nicer on the M5Stack.
// 05-27-2019 v0.0.10 Merged the code to work with either the M5Stack module or a simple ESP32 module.
// Created the Fritzing diagram, schematic and PCB board.
// 05-24-2019 v0.0.9 Added code to display images in a lightbox or popup modal window. You select
// which method from the /Config.txt file. When disabled, you get the popup version,
// otherwise the lightbox version.
// 05-17-2019 v0.0.8 Added Get_Location() function when viewing a waypoint along a route.
// Corrected bug where it was not properly disconnecting from WiFi AP after
// upgrading Espressif's ESP32 libraries from 1.0.1 to version 1.0.2
// 05-15-2019 v0.0.7 Removed function GetStops() and added code to GPX_FileInfo() function. This way I
// only need to scan the GPX file once when sending the Daily Ride page. Added code
// to display a web page with an iframe containing a stop location along a daily ride.
// This uses Google's Reverse Geocoding and requires you to sign up for it. Check your
// Google Dashboard here; https://console.cloud.google.com/google/maps-apis/overview
// 05-11-2019 v0.0.6 Added code for oil change. Modified some calendar formatting and added code to
// list stops in the Daily Ride web page. Added condition to only send link to
// weather location if current location is not home location. This variable will
// to be added to the /Config.txt file.
// 05-02-2019 v0.0.5 Made modifications to TinyGPS++ and moved to local directory to better track and
// keep a GPS fix up to date.
// 05-01-2019 v0.0.4 Statistics page completed.
// 04-27-2019 v0.0.3 Finished adding the motorcycle alarm code. After playing with it for a while,
// I decided I did not want to combine the two projects together. There's enough
// CPU power to handle both codes but it would mean that I would need to build an
// expansion unit that would connect to the M5Stack using the side pin connectors.
// This would make it messy again like the A9 Pudding board did. Not something I
// want right now.
// 04-24-2019 v0.0.2 Added motorcycle alarm functions to code. This was another project I completed
// over the winter and wanted to see how it would work with the M5Stack or a simple
// ESP32 board.
// 04-20-2019 v0.0.1 Re-Start of project (forth version). Simplified version of the M5Stack GPSTracker
// code. Also decided to drop the A9 Pudding board as I could simply use the M5Stack
// built-in WiFi to connect to my cell phone (mobile hotspot). This required the use
// of the M5Stack GPS module board, which made the finished product much nicer and
// simpler to use for end users.
// 05-09-2018 v0.0.0 Start of M5Stack project. Worked on it all summer to get it ready for an
// East Coast Roadtrip I had been planning for the month of August. The first
// version involved working with a GPRS/GSM module called a 'A9 Pudding' board.
// I wanted to be able to post my location on Dweet.io so that my worry-wart
// sister would know where I am while on this roadtrip. Had that working pretty
// well and started making enhancements to the web-interface. Over the winter I
// did two complete re-write of the code for better performance and tracking.
//
// NOTE: Working with the DOIT ESP32 DEVKIT V1 board I've had to modify the boards.txt file
// and change the line;
// esp32doit-devkit-v1.build.board=ESP32_DEV
// to
// esp32doit-devkit-v1.build.board=DOIT_ESP32_DEVKIT
// so that I can differentiate between boards. Some boards don't have the builtin LED defined.
//
// Define firmware version and device type
//
#define FIRMWARE "v0.1.0"
#define DEVICETYPE 0xCB06 // Unique project identifier for EEPROM data structures
// Uncomment next line(s) for debugging...
#define SHOW_LOGFILE_MSG // Output log file messages to serial comm port
//#define TIMINGS // Display function call timing information
//#define WEB_REQUEST // Display web interface calls
//#define DEBUG // Additional debug info
//
// If using a U-BLOX GPS module, uncomment next line(s) as needed
// M5Stack GPS module uses a U-Blox NEO-M8N-0 module.
//
#define UBLOX // You're using a U-Blox GPS module
//#define UBLOX_DEBUG // Output data sent and received from U-Blox module during InitUBLOX()
//
// Include these to disable brownouts conditions and to get reset reason.
// This was added because of the DOIT ESP_32 Dev Kit board which is *very* sensitive to brownouts.
// Comment out or remove these lines if your board does not have brownout issues when turning on WiFi.
//
#include <soc/rtc_cntl_reg.h> // For brownouts
#include <rom/rtc.h> // For reset reason
//
// Include files needed
//
#ifdef ARDUINO_M5Stack_Core_ESP32
#include <M5Stack.h>
// Make sure UBLOX is defined for the M5Stack GPS module
#ifndef UBLOX
#define UBLOX
#endif
// Include these for the M5Stack startup music.
extern const unsigned char m5stack_startup_music[];
#endif
//#include <SoftwareSerial.h> // Include this file if you are using Software Serial for the GPS port
#include <EEPROM.h>
#include <ArduinoOTA.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <SD.h>
#include <TimeLib.h> // Repository: /~https://github.com/PaulStoffregen/Time
#include <Timezone.h> // Repository: /~https://github.com/JChristensen/Timezone
#include "TinyGPSMod.h" // Modified TinyGPS++ library in project directory
#include "polylineencoder.h"
extern "C" { unsigned short CRC16(const char* data, int length); } // Part of SD card libary
//
// Eastern Time Zone rule (this will need to be changed for other time zones and daylight savings rules)
//
TimeChangeRule myEDT = {"EDT", Second, Sun, Mar, 2, -240}; // Eastern Daylight Time = UTC - 4 hours
TimeChangeRule myEST = {"EST", First, Sun, Nov, 2, -300}; // Eastern Standard Time = UTC - 5 hours
Timezone myTZ(myEDT, myEST);
//
// Define PINs used by sketch
//
#ifdef ARDUINO_ESP32_DEV || ARDUINO_DOIT_ESP32_DEVKIT
#define SDCard A13 // SD Card detect pin (input) labeled D15 or IO15
#define Buzzer A14 // Output buzzer pin (output) labeled D13 or IO13
#define Power_GPS A10 // Pin to power GPS (input) labeled D4 or IO4
#define BUTTON A11 // Load Button labeled IO0 or not exposed on other boards
#define GPS_RX 3 // Pin used for SoftwareSerial RX
#define GPS_TX 4 // Pin used for SoftwareSerial TX
#ifndef LED_BUILTIN // Some boards don't have the LED defined in the pin_arduino.h file
#define LED_BUILTIN 2 // Pin 2 is what's used most often
#define LED_ON 0 // Define logic level that turns ON the LED
#define LED_OFF 1 // Define logic level that turns OFF the LED
#endif
#elif ARDUINO_M5Stack_Core_ESP32
#define GPS_Port Serial2
#else
#error Unsupported board type!
#endif
#ifdef ARDUINO_M5Stack_Core_ESP32
//
// Define M5Stack warning tones
//
enum WARNING_TONES
{
NoMotionTone = 0,
TrackingTone,
ConnectedWiFiTone,
LostWiFiTone,
NoSDCardTone,
NoGPSFixTone,
EEPROMTone
};
// Since the .beep() function on the M5Stack is non blocking, you need to call M5.update() in order to have it stopped.
// This I think is bad programming as a beep tone should be blocking and not allow other processes to run.
// But to each their own... Define a blocking beep here.
#define Beep() { M5.Speaker.tone(1000); delay(100); M5.Speaker.mute(); }
#define LowBeep() { M5.Speaker.tone(400); delay(250); M5.Speaker.mute(); }
#else
//
// Define error beeps (long beep followed by error beep code)
//
enum ERROR_BEEPS
{
SETUP_BEEP = 1,
NO_GPS_ERROR,
SD_CARD_ERROR,
EEPROM_ERROR
};
//
// Define status beeps (one short beep + one long beep followed by status beep code)
//
enum STATUS_BEEPS
{
DEEPSLEEP = 0,
TRACKING,
NOT_TRACKING,
WIFI_CONNECTED,
WIFI_DISCONNECT
};
#endif
//
// Define GPX file state
//
enum GPX_FILE_STATES
{
FILE_CREATED = 1, // Daily GPX file has been created but nothing is in it
TRACK_SEGMENT, // Writing track segments to file
TRACK_WAYPOINT, // Way point was just written
FILE_CLOSED // Final tracking info written to file
};
//
// Define constants
//
#define OneSecond 1000 // One second
#define OneMinute (60 * OneSecond) // One minute
#define OneHour (60 * OneMinute) // One hour
#define OneDay (24 * OneHour) // One day
#define DisplayDelay (2 * OneMinute) // Delay before turning the display off (M5Stack)
#define ScreenDelay (20 * OneSecond) // Delay waiting for a button press in a display screen
#define HTTP_Timeout (15 * OneSecond) // Defaut timeout for HTTP requests
#define BlinkDelay (2 * OneSecond) // Delay between status blinks
#define MotionDelay (3 * OneMinute) // Delay when stopped before writing a "No motion" waypoint
#define TrackDelay (10 * OneSecond) // Delay between tracking entries in GPX file
#define DweetDelay (30 * OneSecond) // Delay between post to Dweet.io
#define WiFiDelay (45 * OneSecond) // Delay between WiFi network scans
#define SDCardDelay (15 * OneSecond) // Delay between error tones when no SD card present
#define ForecastDelay (30 * OneMinute) // Time to wait before getting a new weather forecast
#define OilChangeInterval (5000 * 1000.0) // Oil change interval in meters
#define MinSatellites 5 // Minimum satellites needed for a valid GPS fix
//
// Define data structure stored in EEPROM memory
//
struct CONFIG_RECORD
{
unsigned int Init; // Set to DEVICETYPE if valid data in structure
unsigned long Mileage; // Odometer reading in meters
unsigned long DailyRiding; // Daily riding time in milliseconds
unsigned long OilChange; // Mileage of last oil change in meters
float AvgSpeed; // Average riding speed
unsigned long AvgCntr; // Counter to calculate average speed
float MaxSpeed; // Maximum riding speed
char WiFi_SSID[32]; // Wifi SSID name
char WiFi_PASS[64]; // Wifi password
double HomeLatitude; // Home latitude position
double HomeLongitude; // Home longitude position
unsigned int HomeRadius; // Distance from home to be considered no longer home
byte GPXState; // State of the GPX file
time_t TripStart; // Trip start date
unsigned long TripDistance; // Trip distance in meters
unsigned long TripRiding; // Trip riding time in seconds
float TripAvgSpeed; // Trip Average speed
unsigned long TripAvgCntr; // Trip Counter to calculate average speed
float TripMaxSpeed; // Trip Maximum speed
float TripExpenses; // Trip expenses recorded
time_t SeasonStart; // Season start date
unsigned long SeasonDistance; // Trip distance in meters
unsigned long SeasonRiding; // Season riding time in seconds
float SeasonAvgSpeed; // Season Average speed
unsigned long SeasonAvgCntr; // Season Counter to calculate average speed
float SeasonMaxSpeed; // Season Maximum speed
float SeasonExpenses; // Sum of all trip expenses
unsigned short CRC;
};
struct DATA_RECORD
{
unsigned int Init; // Set to DEVICETYPE if valid data in structure
char HostName[20]; // OTA HostName
char OTA_Pass[20]; // OTA Password
char SSID1[20]; // Cell phone access point
char PASS1[20]; // Cell phone password
char SSID2[20]; // Home access point
char PASS2[20]; // Home password
char UserName[32]; // User name
char Bike[32]; // Motorcycle type
char DweetName[20]; // dweet.io posting name
char OWappid[64]; // OpenWeatherMap.org credentials
char GoogleAPI[64]; // Google API key
bool DailyShutdown; // Shutdown device after running nightly scan?
bool LightBox; // View pictures with a lightbox style display?
unsigned int MinDailyDistance; // Minimum daily distance to keep GPX file
unsigned short CRC;
};
//
// Define EEPROM memory offsets for EEPROM.put function
//
#define CONFIG_OFFSET 0
#define CONFIG_CRC ((int)&Config.CRC - (int)&Config.Init)
#define DATA_OFFSET sizeof(CONFIG_RECORD)
#define DATA_CRC ((int)&DataRec.CRC - (int)&DataRec.Init)
//
// Create objects needed
//
//SoftwareSerial GPS_Port(GPS_RX, GPS_TX); // Using SoftwareSerial
#ifndef ARDUINO_M5Stack_Core_ESP32
HardwareSerial GPS_Port(2); // Hardware serial uses pin 16 as RX and 17 as TX
#endif
TinyGPSPlus GPS;
HTTPClient http;
WiFiServer WebServer(80);
WiFiClient WebClient;
//
// Define global variables
//
int TrackSec;
char DailyGPXFile[32];
String DailyForecast = "";
String CurrentWeather = "";
String OTAError = "";
String City = "Location Unknown";
String CityCode = "";
String PGM = "";
byte GPXDay, GPSCntr, GPSSymbols[] = "|/-\\";
unsigned long CurrentTime, BlinkTime, WiFiTime, DweetTime, ForecastTime, TrackTime, QuietTime, GPSTime;
unsigned long SDCardTime, WebClientTime, DisplayTime, MovingTime, UITime;
double Latitude, Longitude, LastLAT, LastLON, Speed, LastSpeed, Distance, DailyDistance, DistanceHome, DweetSpeed;
bool SDCardMissing, OTA_Update, Tracking, WiFiConnected, WasConnected, DailyScan, DailyWeather;
bool GPSEncoding, LostGPS, ShowNMEA, DisplayOn, TextDisplay;
File Running;
CONFIG_RECORD Config;
DATA_RECORD DataRec;
//
// Write line to log file
//
void WriteLogFile(String Msg, bool Time=true)
{
// Add time to message
if(Time)
{
if(timeStatus() == timeSet)
Msg = "[" + String(getTime(false)) + "] " + Msg;
else
Msg = "[??:??:??] " + Msg;
}
// Print message to serial output
#ifdef SHOW_LOGFILE_MSG || TIMINGS || WEB_REQUESTS || DEBUG
Serial.println(Msg);
#endif
// Is the SD card still inserted
#ifdef ARDUINO_M5Stack_Core_ESP32
SDCardMissing = !SD.exists("/Weather");
#else
SDCardMissing = !digitalRead(SDCard);
#endif
if(SDCardMissing) return;
// Now write to file
File file = SD.open("/LogFile.txt", FILE_APPEND);
if(!file) return;
file.println(Msg);
file.close();
}
//
// Setup function
//
void setup()
{
char StartTime[32], EndTime[32], Buf[64];
time_t t;
TimeElements tm;
int Cntr, Segments, Tracks, Stops;
double Meters, AvgSpeed, MaxSpeed, OilChange;
unsigned long Riding, Elapsed;
String Card, StopStr;
// Disable brownout detector
#ifdef RTC_CNTL_BROWN_OUT_REG
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0);
#endif
#ifndef ARDUINO_M5Stack_Core_ESP32
// Set pin modes
pinMode(LED_BUILTIN, OUTPUT);
pinMode(Buzzer, OUTPUT);
pinMode(SDCard, INPUT_PULLUP);
pinMode(Power_GPS, OUTPUT);
pinMode(BUTTON, INPUT_PULLUP);
// Beep to indicate startup
delay(1000);
Buzzer_Beep(SETUP_BEEP, true);
#else
// Initialize M5Stack
delay(1000);
M5.begin();
startupLogo("/PowerOn.jpg");
M5.Speaker.begin();
delay(500);
// Initialize display
M5.lcd.setBrightness(200);
M5.Lcd.clearDisplay();
M5.Lcd.setTextColor(WHITE, BLACK);
M5.Lcd.setCursor(0, 0);
M5.Lcd.setTextSize(2);
#endif
// Initialize serial ports
Serial.begin(115200); while(!Serial);
GPS_Port.begin(9600); while(!GPS_Port);
// Initialize some variables
SDCardMissing = OTA_Update = ShowNMEA = GPSEncoding = TextDisplay = false;
CurrentTime = millis();
#ifndef ARDUINO_M5Stack_Core_ESP32
// Initialize SD card module
if(!SD.begin() || !digitalRead(SDCard)) SDCardMissing = true;
#endif
// Ok, let's start...
WriteLogFile("\n--------------------------------------------------", false);
#ifdef _ROM_RTC_H_
WriteLogFile("CPU0 reset reason = " + Get_Reset_Reason(rtc_get_reset_reason(PRO_CPU_NUM)), false);
WriteLogFile("CPU1 reset reason = " + Get_Reset_Reason(rtc_get_reset_reason(APP_CPU_NUM)), false);
#endif
PGM = __FILE__; PGM = PGM.substring(PGM.lastIndexOf("\\")+1); PGM = PGM.substring(0, PGM.indexOf("."));
WriteLogFile("\n" + PGM + " " + String(FIRMWARE) + " dated " + String(__DATE__), false);
WriteLogFile("Running on : " + String(ARDUINO_BOARD), false);
// Print the type of card
Card = "SD Card Type : ";
switch(SD.cardType())
{
case CARD_NONE:
Card += "SD Card Missing!";
SDCardMissing = true;
break;
case CARD_MMC:
Card += "MMC";
break;
case CARD_SD:
Card += "SD";
break;
case CARD_SDHC:
Card += "SDHC";
break;
default:
Card += "UNKNOWN";
break;
}
WriteLogFile(Card, false);
if(!SDCardMissing)
{
sprintf(Buf, "Total space : %ul MB", SD.totalBytes() / (1024 * 1024));
Card = String(Buf);
sprintf(Buf, "\nUsed space : %ul MB", SD.usedBytes() / (1024 * 1024));
Card += String(Buf) + "\n";
WriteLogFile(Card, false);
}
else
{
// Failed to initialize the SD card reader or card not inserted. Hang here beeping...
#ifndef ARDUINO_M5Stack_Core_ESP32
for(int i=0; i<5; i++) { Buzzer_Beep(SD_CARD_ERROR, true); delay(1000); }
#else
strcpy(Buf, "SD Card Missing!");
M5.Lcd.clearDisplay();
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.setTextSize(3);
M5.Lcd.drawString(Buf, 160 - (M5.Lcd.textWidth(Buf)/2), 60);
for(int i=0; i<5; i++) { WarningTone(NoSDCardTone); delay(1000); }
#endif
PowerOff();
while(1) {}
}
// Initialize EEPROM memory
if(!EEPROM.begin(sizeof(Config)+sizeof(DataRec)))
{
// Unable to allocate EEPROM memory
Card = "Failed to allocate EEPROM memory (" + String(sizeof(Config)+sizeof(DataRec)) + " bytes)!";
WriteLogFile(Card, false);
#ifndef ARDUINO_M5Stack_Core_ESP32
for(int i=0; i<5; i++) { Buzzer_Beep(EEPROM_ERROR, true); delay(1000); }
#else
M5.Lcd.clearDisplay();
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.setTextSize(3);
strcpy(Buf, "Insufficient");
M5.Lcd.drawString(Buf, 160 - (M5.Lcd.textWidth(Buf)/2), 60);
strcpy(Buf, "EEPROM Memory!");
M5.Lcd.drawString(Buf, 160 - (M5.Lcd.textWidth(Buf)/2), 90);
for(int i=0; i<5; i++) { WarningTone(EEPROMTone); delay(1000); }
#endif
PowerOff();
while(1) {}
}
// Get EEPROM data
memset(&Config, NULL, sizeof(Config));
memset(&DataRec, NULL, sizeof(DataRec));
EEPROM.get(CONFIG_OFFSET, Config);
EEPROM.get(DATA_OFFSET, DataRec);
// Make sure config record is valid
unsigned short CRC = CRC16((char *)&Config, CONFIG_CRC);
if(Config.Init != DEVICETYPE || Config.CRC != CRC)
{
// Initialize configuration record
WriteLogFile("Configuration record initialized (" + String(sizeof(Config)) + " bytes)", false);
memset(&Config, NULL, sizeof(Config));
Config.Init = DEVICETYPE;
Save_Config();
}
// Check if I have a /Config.txt file to read
if(!Read_Config_File())
{
// I don't have a file (or it's not in the proper format)
// make sure the one I have from EEPROM is okay. Otherwise hang...
unsigned short CRC = CRC16((char *)&DataRec, DATA_CRC);
if(DataRec.Init != DEVICETYPE || DataRec.CRC != CRC)
{
Card = "Invalid DataRec in EEPROM memory (" + String(sizeof(DataRec)) + " bytes)!";
WriteLogFile(Card, false);
#ifndef ARDUINO_M5Stack_Core_ESP32
for(int i=0; i<5; i++) { Buzzer_Beep(EEPROM_ERROR, true); delay(1000); }
#else
M5.Lcd.clearDisplay();
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.setTextSize(3);
strcpy(Buf, "Invalid DataRec!");
M5.Lcd.drawString(Buf, 160 - (M5.Lcd.textWidth(Buf)/2), 60);
for(int i=0; i<5; i++) { WarningTone(EEPROMTone); delay(1000); }
#endif
PowerOff();
while(1) {}
}
}
// Make sure default access point is the cell phone if defined
// Otherwise make it the Home WiFi
if(strlen(DataRec.SSID1) != 0)
{ strcpy(Config.WiFi_SSID, DataRec.SSID1); strcpy(Config.WiFi_PASS, DataRec.PASS1); }
else
{ strcpy(Config.WiFi_SSID, DataRec.SSID2); strcpy(Config.WiFi_PASS, DataRec.PASS2); }
#ifndef ARDUINO_M5Stack_Core_ESP32
// Power up GPS
WriteLogFile("Turning on GPS...", false);
digitalWrite(Power_GPS, HIGH);
delay(1000);
#endif
#ifdef UBLOX
Card = "Initializing GPS module";
WriteLogFile(Card, false);
#ifdef ARDUINO_M5Stack_Core_ESP32
M5.Lcd.println(Card);
Beep();
#endif
if(!InitUBLOX()) WriteLogFile("Failed to set all NMEA messages...", false);
#endif
// Loop here until I have a valid GPS fix
unsigned long entry = millis();
ShowNMEA = false;
Card = "Waiting for GPS fix...";
WriteLogFile(Card, false);
#ifdef ARDUINO_M5Stack_Core_ESP32
M5.Lcd.clearDisplay();
M5.Lcd.setCursor(0, 0);
M5.Lcd.println(" " + Card);
#endif
while(!GPS.location.isValid() || !GPS.date.isValid() || !GPS.time.isValid() ||
GPS.satellites.value() < MinSatellites || GPS.sentencesWithFix() < 3 || millis() - entry < (5 * OneSecond))
{
// Feed TinyGPS++ waiting for a GPS fix
while(GPS_Port.available() > 0)
{
char c = GPS_Port.read();
if(ShowNMEA) Serial.print(c);
GPSEncoding = (GPS.sentencesWithFix() > 3);
if(GPS.encode(c)) { if(GPS.sentenceType() == GPS_SENTENCE_GPRMC) if(++GPSCntr > 3) GPSCntr = 0; }
}
#ifdef ARDUINO_M5Stack_Core_ESP32
M5.update();
M5.Lcd.setCursor(0, 0); M5.Lcd.printf("%c", GPSSymbols[GPSCntr]);
M5.Lcd.setCursor(0, 20);
if((millis() - entry) % 1000 == 0 || millis() - entry < 500)
{
M5.Lcd.printf(" Time: %s\n", UpTime(millis() - entry, false));
M5.Lcd.printf(" Sats: %02d\n\n", (int)GPS.satellites.value());
M5.Lcd.printf(" WiFi: %-20s\n", Config.WiFi_SSID);
M5.Lcd.printf(" Ver: %s %s\n", FIRMWARE, ShowNMEA ? "NMEA" : " ");
}
#else
// Blink status LED
if((millis() - CurrentTime) >= BlinkDelay)
{
Blink_LED(GPSEncoding ? 1 : 3, false);
digitalWrite(Buzzer, HIGH); delay(100); digitalWrite(Buzzer, LOW);
CurrentTime = millis();
}
#endif
// If it's been more than 5 minutes and still no GPS fix, error beep
if(millis() - entry >= (5 * OneMinute))
{
Card = "Unable to get GPS fix...";
WriteLogFile(Card, false);
#ifdef ARDUINO_M5Stack_Core_ESP32
M5.Lcd.setTextColor(RED, BLACK);
M5.Lcd.setTextSize(3);
strcpy(Buf, "Unable to get");
M5.Lcd.drawString(Buf, 160 - (M5.Lcd.textWidth(Buf)/2), 160);
strcpy(Buf, "valid GPS fix!");
M5.Lcd.drawString(Buf, 160 - (M5.Lcd.textWidth(Buf)/2), 190);
for(int i=0; i<5; i++) { WarningTone(NoGPSFixTone); delay(1000); }
#else
for(int i=0; i<5; i++) { Buzzer_Beep(NO_GPS_ERROR, true); delay(1000); }
#endif
PowerOff();
while(1) {}
}
// Was a button pressed
#ifdef ARDUINO_M5Stack_Core_ESP32
if(M5.BtnA.wasPressed())
#else
if(!digitalRead(BUTTON))
#endif
{
// Switch between Home and Cell WiFi APs
if(strcmp(Config.WiFi_SSID, DataRec.SSID2) != 0)
{
strcpy(Config.WiFi_SSID, DataRec.SSID2);
strcpy(Config.WiFi_PASS, DataRec.PASS2);
}
else
{
strcpy(Config.WiFi_SSID, DataRec.SSID1);
strcpy(Config.WiFi_PASS, DataRec.PASS1);
}
#ifdef ARDUINO_M5Stack_Core_ESP32
Beep();
#else
digitalWrite(Buzzer, HIGH);
#endif
entry = millis();
}
#ifdef ARDUINO_M5Stack_Core_ESP32
if(M5.BtnB.wasPressed()) { Beep(); ShowNMEA = !ShowNMEA; }
#else
else digitalWrite(Buzzer, LOW);
#endif
}
// Set time from GPS module
Card = "GPS fix took " + String(UpTime(millis() - entry, false)) + " to get " + String(GPS.satellites.value()) + " satellites.";
WriteLogFile(Card, false);
memset(&tm, NULL, sizeof(tm));
if(GPS.date.isValid() && GPS.time.isValid())
{
tm.Year = GPS.date.year() - 1970;
tm.Month = GPS.date.month();
tm.Day = GPS.date.day();
tm.Hour = GPS.time.hour();
tm.Minute = GPS.time.minute();
tm.Second = GPS.time.second();
t = makeTime(tm);
t = myTZ.toLocal(t);
setTime(t);
WriteLogFile("Clock set to " + getDateTime(), false);
// If the config record was just initialized, set Trip and Season start to now()
if(Config.TripStart == 0) Config.TripStart = now();
if(Config.SeasonStart == 0) Config.SeasonStart = now();
} else WriteLogFile("Unable to set date/time...");
// Initialize GPX file for today
GPXDay = GPX_StartDaily();
GPX_FileInfo(year(), month(), day(), &Meters, &Segments, &Tracks, &Stops, &Riding, &Elapsed, StartTime, EndTime, &AvgSpeed, &MaxSpeed, &StopStr);
Card = "GPX file state = ";
switch(Config.GPXState)
{
case FILE_CREATED: Card += "File created"; break;
case TRACK_SEGMENT: Card += "Tracking"; break;
case TRACK_WAYPOINT: Card += "Waypoint"; break;
case FILE_CLOSED: Card += "File closed"; break;
default: Card += "Unknown"; break;
}
WriteLogFile(Card);
Card = "Daily distance = ";
if(DailyDistance >= 1000.0)
Card += String(DailyDistance / 1000.0, 1) + " Km";
else
Card += String(DailyDistance) + " m";
WriteLogFile(Card);
// Log mileage info
Card = "Total mileage = ";
if(Config.Mileage + (Meters * 1000.0) >= 1000)
Card += String((Config.Mileage + (Meters * 1000.0)) / 1000.0, 1) + " Km";
else
Card += String(Config.Mileage + (Meters * 1000.0)) + " m";
WriteLogFile(Card);
OilChange = OilChangeInterval - (Config.Mileage + (Meters * 1000.0) - Config.OilChange);
Card = "Next oil change = ";
if(OilChange >= 1000)
Card += String(OilChange / 1000.0, 1) + " Km";
else
{
if(OilChange >= 0)
Card += String(OilChange, 0) + " m";
else
{
Card += "DUE ";
if(abs(OilChange) >= 1000)
Card += String(abs(OilChange) / 1000.0, 1) + " K";
else
Card += String(abs(OilChange), 0) + " ";
Card += "m ago!";
}
}
WriteLogFile(Card);
Card = "WiFi Network = " + String(Config.WiFi_SSID);
WriteLogFile(Card);
Card = "OTA HostName = " + String(DataRec.HostName);
WriteLogFile(Card);
// Init WiFi
WiFi.disconnect(); // Clear Wifi Credentials
WiFi.mode(WIFI_STA); // Make WiFi mode is Station
// Initialize OTA callbacks
InitOTA();
// Default some program variables
BlinkTime = DweetTime = DisplayTime = UITime = millis();
SDCardTime = (SDCardMissing ? 0 : millis());
WasConnected = WiFiConnected = Tracking = false;
DisplayOn = true;
LastLAT = Latitude = GPS.location.lat();
LastLON = Longitude = GPS.location.lng();
WiFiTime = MovingTime = Speed = Distance = DistanceHome = 0;
// We're done, run main loop
#ifdef ARDUINO_M5Stack_Core_ESP32
M5.Lcd.clearDisplay();
UI();
#endif
WriteLogFile("Running main program loop...");
WriteLogFile("--------------------------------------------------\n", false);
}
//
// Main program loop
//
void loop()
{
int WiFiStatus;
// Save time
CurrentTime = millis();
// Handle OTA process and do nothing else if I'm in an OTA Update
ArduinoOTA.handle();
if(OTA_Update) return;
// Time to blink status LED?
if((millis() - BlinkTime) >= BlinkDelay)
{
if(GPS.location.age() >= (15 * OneSecond) && GPSTime != WiFiTime)
{
// Assume here that either I'm no longer getting NMEA messages or that the GPS location is invalid
if(!LostGPS)
{
WriteLogFile("Lost GPS signal!");
#ifndef ARDUINO_M5Stack_Core_ESP32
digitalWrite(Buzzer, HIGH); delay(100); digitalWrite(Buzzer, LOW);
#endif
LostGPS = true;
GPSTime = BlinkTime = millis();
}
#ifndef ARDUINO_M5Stack_Core_ESP32
else Blink_LED(3, false);
#endif
}
else
{
// Have I restored the GPS signal yet?
if(LostGPS && GPS.satellites.value() >= MinSatellites && GPS.sentencesWithFix() > 3)
{
WriteLogFile("GPS signal restored in " + String(UpTime(millis() - GPSTime, false)));
LostGPS = false;
}
#ifndef ARDUINO_M5Stack_Core_ESP32
else Blink_LED(GPSEncoding ? (Tracking ? 2 : 1) : 3, false);
if(!WiFiConnected && GPSEncoding && !Tracking) { delay(150); Blink_LED(2, true); }
#endif
}
BlinkTime = millis();
// Check if card is still there?
#ifdef ARDUINO_M5Stack_Core_ESP32
if(!SDCardMissing) SDCardMissing = !SD.exists("/Weather");
#else
if(!SDCardMissing) SDCardMissing = !digitalRead(SDCard);
#endif
}
// Check if I've lost my WiFi connection
WiFiStatus = WiFi.status();
if(WiFiStatus != WL_CONNECTED && WiFiConnected)
{
WriteLogFile("WiFi connection lost!");
#ifdef ARDUINO_M5Stack_Core_ESP32
WarningTone(LostWiFiTone);
#else
Buzzer_Beep(WIFI_DISCONNECT, false);
#endif
WebServer.stop();
WiFi.disconnect();
WiFiConnected = false;
WasConnected = true;
WiFiTime = millis();
}
// If not connected to WiFi and not tracking, try to connect
if(WiFiStatus != WL_CONNECTED && (millis() - WiFiTime >= WiFiDelay || WiFiTime == 0) && !Tracking)
{
#ifdef ARDUINO_M5Stack_Core_ESP32
StatusLine("WiFi Scan");
Hide_Buttons();
#endif
int net = WiFi.scanNetworks();
for(int i=0; i<net; i++)
{
if(WiFi.SSID(i) == String(Config.WiFi_SSID))
{
#ifdef ARDUINO_M5Stack_Core_ESP32
if(!DisplayOn) { M5.Lcd.wakeup(); TextDisplay = false; UI(); M5.Lcd.setBrightness(200); }
DisplayOn = true; DisplayTime = millis();
StatusLine(Config.WiFi_SSID);
#endif
int Cntr = 0;
WriteLogFile(String(WasConnected ? "Re-c" : "C") + "onnecting to \"" + String(Config.WiFi_SSID) + "\"");
if(WasConnected)
WiFi.reconnect();
else
WiFiStatus = WiFi.begin(Config.WiFi_SSID, Config.WiFi_PASS);
while(WiFiStatus != WL_CONNECTED)
{
delay(500);
WiFiStatus = WiFi.status();
if(++Cntr > 10 || WiFiStatus == WL_CONNECTED) break;
}
if(WiFiStatus != WL_CONNECTED)
{
WriteLogFile("Failed to connect (err=" + String(WiFiStatus) + ")");
WiFi.disconnect(true);
break;
}
else
{
WriteLogFile("IP Address: " + WiFi.localIP().toString() + " (" + String(WiFi.RSSI()) + " dBm)");
#ifdef ARDUINO_M5Stack_Core_ESP32
WarningTone(ConnectedWiFiTone);
#else
Buzzer_Beep(WIFI_CONNECTED, false);
#endif
WiFiConnected = WasConnected = true;
ForecastTime = 0; // This will force a new weather fetch
Get_Location(Latitude, Longitude, &City, &CityCode);
WriteLogFile("After WiFi connect, you are in \"" + City + " / " + CityCode + "\"");
WebServer.begin();
}
}
}
// If GPX file is in tracking, start timers to continue tracking
// This condition will only occur after a device reboot while it was in tracking mode.
if(Config.GPXState == TRACK_SEGMENT)
{
Speed = (GPS.speed.isValid() ? GPS.speed.kmph() : 0);
WriteLogFile("Tracking after reboot at " + String(Speed, 1) + " Km/h");
MovingTime = millis();
TrackTime = 0;
Tracking = true;
LastSpeed = Speed;
DweetPost(Speed);
} else DweetPost(-1);
DweetTime = millis();
// Clean up memory used by scan
WiFi.scanDelete();
WiFiTime = millis();
#ifdef ARDUINO_M5Stack_Core_ESP32
Display_Buttons();
#endif
}
// Feed GPS
while(GPS_Port.available() > 0)
{
char c = GPS_Port.read();
if(ShowNMEA) Serial.print(c);
GPSEncoding = (GPS.sentencesWithFix() > 3);
if(GPS.encode(c))
{
// A NMEA sentence has been properly encoded
if(GPS.location.isValid())
{
Latitude = GPS.location.lat(); Longitude = GPS.location.lng();
DistanceHome = TinyGPSPlus::distanceBetween(Latitude, Longitude, Config.HomeLatitude, Config.HomeLongitude);
if(LastLAT != 0.0 && LastLON != 0.0)
Distance = TinyGPSPlus::distanceBetween(Latitude, Longitude, LastLAT, LastLON);
else
Distance = 0;
}
if(GPS.speed.isValid()) Speed = GPS.speed.kmph();
// If Speed is < 5 Km/h, set speed and distance to zero
if(Speed < 5.0) Speed = Distance = 0.0;
// On speed sentences if speed is not zero, increment tracking counter
// This will trigger the Tracking process if I've had more than 5 seconds of movement
if(GPS.sentenceType() == GPS_SENTENCE_GPRMC)
{
if(Speed != 0) ++TrackSec; else TrackSec = 0;
if(++GPSCntr > 3) GPSCntr = 0;
}
// Calc avg speed and check if I have a new maximum speed
if(Speed != 0.0 && Tracking)
{
Config.AvgSpeed += Speed; ++Config.AvgCntr;
Config.TripAvgSpeed += Speed; ++Config.TripAvgCntr;
Config.SeasonAvgSpeed += Speed; ++Config.SeasonAvgCntr;
if(Config.MaxSpeed < Speed) Config.MaxSpeed = Speed;
if(Config.TripMaxSpeed < Speed) Config.TripMaxSpeed = Speed;
if(Config.SeasonMaxSpeed < Speed) Config.SeasonMaxSpeed = Speed;
}
}
}
// Is there a web client to process? (or update UI on M5Stack)
if(millis() - WebClientTime >= OneSecond)
{
if(WiFiConnected && !Tracking) ProcessClient();
#ifdef ARDUINO_M5Stack_Core_ESP32
UI();
#endif
WebClientTime = millis();
}
// If I've been stopped for a while, log a "No motion" waypoint
if(Speed != 0 || LastSpeed != 0 || LostGPS) QuietTime = millis();
if(millis() - QuietTime >= MotionDelay)
{
// No motion, was I tracking?
if(Tracking)
{
#ifdef ARDUINO_M5Stack_Core_ESP32
WarningTone(NoMotionTone);
#else
Buzzer_Beep(NOT_TRACKING, false);
#endif
// Get my current location after stopping
if(WiFiConnected)
{
Get_Location(Latitude, Longitude, &City, &CityCode);
WriteLogFile("After tracking, you are in \"" + City + " / " + CityCode + "\"");
}
// Write waypoint and reset some variables
if(MovingTime != 0) Config.DailyRiding += millis() - MovingTime;
GPX_WayPoint("No motion"); // Do not change the title of this entry. It is used as a special marker
Tracking = false;
TrackSec = TrackTime = WiFiTime = 0;
ForecastTime = 0; // This will force a new weather fetch
LastLAT = Latitude;
LastLON = Longitude;
DweetPost(-1);
DweetTime = millis();
}
}
else
{
// If I have motion for more than 5 seconds and not tracking, start tracking
if(!Tracking && TrackSec >= 5 && Speed != 0 && Distance != 0 && GPS.location.isValid())
{
WriteLogFile("Tracking at " + String(Speed, 1) + " Km/h with " + String(GPS.satellites.value()) + " satellites");
#ifdef ARDUINO_M5Stack_Core_ESP32
WarningTone(TrackingTone);
Hide_Buttons();
#else
Buzzer_Beep(TRACKING, false);
#endif
TrackTime = DweetTime = 0;
MovingTime = millis();
Tracking = true;
}
}
// Time to log current position in GPX file?
if(Tracking && GPS.location.isValid() && millis() - TrackTime >= TrackDelay && (Speed != 0 || (Speed == 0 && LastSpeed != 0)))
{
// Save new position and increment distance travelled
GPX_TrackPoint();