Advertisement
Guest User

GPS-T code

a guest
Oct 2nd, 2023
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Arduino 64.73 KB | None | 0 0
  1. #include "TFT_eSPI.h"
  2. #include "U8g2_for_TFT_eSPI.h"
  3. #include <Preferences.h>
  4.  
  5. #include "SPI.h"
  6. #include "SD.h"
  7. #include "FS.h"
  8. #include "Wire.h"
  9.  
  10. #include <NMEAGPS.h>
  11. #include <GPSport.h>
  12.  
  13. // ------- [ GPS ] -------
  14. NMEAGPS  gps; // This parses the GPS characters
  15. gps_fix  fix; // This holds on to the latest values
  16.  
  17.  
  18. int UTCOffset = 2;
  19.  
  20. int headingLookupTable[8][2];
  21. const String markers[8] = {
  22.   "S",
  23.   "SE",
  24.   "E",
  25.   "NE",
  26.   "N",
  27.   "NW",
  28.   "W",
  29.   "SW"
  30. };
  31.  
  32. enum GPSspeed { HZ_1,
  33.                 HZ_2,
  34.                 HZ_4,
  35.                 HZ_5,
  36.                 HZ_10 };
  37. const String GPScommands[5][2] = {
  38.   { "$PCAS02,1000*2E", "1HZ" },  // 1HZ  - 0
  39.   { "$PCAS02,500*1A", "2HZ" },   // 2HZ  - 1
  40.   { "$PCAS02,250*18", "4HZ" },   // 4HZ  - 2
  41.   { "$PCAS02,200*1D", "5HZ" },   // 5HZ  - 3
  42.   { "$PCAS02,100*1E", "10HZ" }   //10 HZ  - 4
  43. };
  44.  
  45. #define SAT_RADIUS 5
  46. #define OFFSET 180
  47.  
  48. // ------- [ TEXT ] -------
  49. #define fontBig u8g2_font_fub49_tn        // 61px
  50. #define fontMid u8g2_font_crox5tb_tf      // 16px
  51. #define fontSml u8g2_font_helvR14_tr      // 15px
  52. #define fontxSml u8g2_font_6x13_tf        // ??px
  53. #define fontSpecial u8g2_font_gb24st_t_3  // 13px
  54.  
  55. #define paddingL 5
  56. #define paddingR 10
  57.  
  58. #define clearPaddingL 4
  59. #define clearPaddingR 5
  60.  
  61. enum directions { LEFT, RIGHT, CENTER, CENTER_NO }; //  CENTER_NO doesn't have 15px offset to account for sidebar
  62.  
  63. // ------- [ IP5306 ] -------
  64. #define chargeCurrent 10    //  1050 mA charging current
  65. #define IP5306_REG_CHG_4 0x24
  66.  
  67. #define IP5306_REG_READ_0 0x70
  68. #define IP5306_REG_READ_1 0x71
  69. #define IP5306_REG_READ_4 0x78
  70.  
  71. // ------- [ BUTTONS ] -------
  72. TaskHandle_t handleButtons;
  73. int clicked = -1;
  74. unsigned long lastPressed = 0;
  75.  
  76. // ------- [ SCREEN ] -------
  77. TFT_eSPI tft = TFT_eSPI();
  78. U8g2_for_TFT_eSPI u8f;
  79. TFT_eSprite spr = TFT_eSprite(&tft);
  80.  
  81. bool refreshScreen = true;
  82. const int refreshRate = 100;   // global refreshrate, ms
  83.  
  84. int screenWidth, screenHeight;
  85.  
  86. unsigned long lastRefresh;
  87.  
  88. bool screenOn = true;
  89.  
  90. byte mainScreenIndex = 0;
  91. const byte numScreens = 5;
  92.  
  93. int pallete = 2;
  94. const int numPalletes = 7;
  95.  
  96. // [0]UI, [1]ACCENT, [2]TEXT
  97. const uint16_t palletes[numPalletes][3] = {
  98.   //    UI          ACCENT        TEXT
  99.   {TFT_DARKCYAN,  TFT_GREEN,    TFT_CYAN},        // OCEAN
  100.   {TFT_BROWN,     TFT_GOLD,     TFT_WHITE},       // BEAR
  101.   {TFT_LIGHTGREY, TFT_DARKGREY, TFT_LIGHTGREY},   // DARK
  102.   {TFT_DARKGREY,  TFT_DARKGREY, TFT_DARKGREY},    // DARKER
  103.   {0x0260,        0x05E0,       TFT_WHITE},       // <3
  104.   {TFT_SKYBLUE,   TFT_BLUE,     TFT_NAVY},        //  BLU
  105.   {0x268E,        0x9EB4,       TFT_WHITE}        // MINT
  106. };
  107. const String palleteNames[numPalletes] = {
  108.   "Ocean",
  109.   "Bear",
  110.   "Dark",
  111.   "Darker",
  112.   "<3",
  113.   "Blu",
  114.   "Mint"
  115. };
  116.  
  117. const uint16_t barColors[6] = {
  118.   TFT_DARKGREEN,
  119.   TFT_GREEN,
  120.   TFT_YELLOW,
  121.   TFT_ORANGE,
  122.   TFT_RED,
  123. };
  124.  
  125. const int graphSize = 600;
  126. const int gRefreshRate = 100;
  127.  
  128. bool runSettings = false;
  129. bool bootRdy = false;
  130. Preferences preferences;
  131.  
  132. #include "settingsArray.h"
  133.  
  134. // this is where core 0 stores all the GPS data
  135. struct GPS{
  136.   int fixStatus;
  137.  
  138.   float lat;
  139.   float lon;
  140.   float alt;
  141.  
  142.   long latP;
  143.   long lonP;
  144.  
  145.   long lonDMS;
  146.   long latDMS;
  147.   // int latErr;
  148.   // int lonErr;
  149.   // int altErr;
  150.  
  151.   float spd;    //  speed in km/h
  152.   float spdS;   //  speed in m/s
  153.   int velN;
  154.   int velE;
  155.   int velD;
  156.  
  157.   float hdg;
  158.  
  159.   int sats;        //  amount of TRACKED sats
  160.   int totalSats;   //  amount of VISIBLE sats
  161.  
  162.   int SNR;
  163.  
  164.   String time;
  165.   int timeSeconds;    //  keep seconds seperate to not break other stuff
  166.  
  167.   uint8_t GPSmonth;
  168.   uint8_t GPSday;
  169.   uint8_t GPSyear;
  170.   uint8_t GPSdow; // day of week
  171.  
  172. };
  173. GPS gpsData;
  174.  
  175. const int32_t latOne = xxxxxxxxx;
  176. const int32_t lonOne = xxxxxxxxx;
  177.  
  178. const int32_t latTwo = xxxxxxxxx;
  179. const int32_t lonTwo = xxxxxxxxx;
  180. NeoGPS::Location_t posOne(latOne, lonOne);
  181. NeoGPS::Location_t posTwo(latTwo, lonTwo );
  182.  
  183. NeoGPS::Location_t liveMeassureLoc;
  184.  
  185. int32_t liveMLat;
  186. int32_t liveMLon;
  187.  
  188. bool canSleep = true;
  189.  
  190. const String fixTypes[8] = {
  191.   "NO FIX",
  192.   "EST",
  193.   "TIME ONLY",
  194.   "STD",
  195.   "DGPS",
  196.   "RTK FLOAT",
  197.   "RTK FIXED",
  198.   "PPS"
  199. };
  200.  
  201. const String monthNames[12] = {
  202.   "Dec",
  203.   "Jan",
  204.   "Feb",
  205.   "Mar",
  206.   "Apr",
  207.   "May",
  208.   "Jun",
  209.   "Jul",
  210.   "Aug",
  211.   "Sep",
  212.   "Oct",
  213.   "Nov"
  214. };
  215.  
  216. //  [x][0] - hour hand X
  217. //  [x][1] - hour hand Y
  218. //
  219. //  [x][2] - Minute hand X
  220. //  [x][3] - minute hand Y
  221. //
  222. //  [x][4] - marker START X
  223. //  [x][5] - marker START Y
  224. //
  225. //  [x][6] - marker STOP X
  226. //  [x][7] - marker STOP Y
  227. int clockPositions[60][8];
  228. int clockCounterPositions[60][2];
  229.  
  230. //  length of analog clock hands, as radius of a circle
  231. #define hrRadius  75  //  hour hand
  232. #define mnRadius  105 //  minute hand
  233. #define scRadius  105 //  seconds hand ( currently overriden to mnRadius )
  234. #define cnRadius  22  //  hour hand other side
  235.  
  236.  
  237. void setup() {
  238.   //  first get screen orientation!
  239.   preferences.begin("settings", false);
  240.   landscape = preferences.getBool("screenOri", true);
  241.  
  242.   tft.init();
  243.  
  244.   u8f.begin(spr);
  245.   u8f.setFont(u8g2_font_t0_22_me);
  246.   tft.setTextWrap(false, false);
  247.  
  248.   tft.setRotation(landscape);
  249.  
  250.   screenHeight = tft.height();
  251.   screenWidth = tft.width();
  252.  
  253.   spr.createSprite(screenWidth, screenHeight);
  254.  
  255.   drawText("GPS TRACKER", 20, CENTER_NO, palletes[pallete][3]);
  256.   drawText("Booting..", 50, CENTER_NO, TFT_WHITE);
  257.   spr.pushSprite(0, 0);
  258.  
  259.   Serial.begin(115200);
  260.  
  261.   drawText("Getting settings..", 80, LEFT, TFT_WHITE);
  262.   spr.pushSprite(0, 0);
  263.  
  264.   getSettings();
  265.  
  266.   esp_sleep_enable_ext0_wakeup(GPIO_NUM_39, 0);  //R button
  267.  
  268.   drawText("SD init..", 105, LEFT, TFT_WHITE);
  269.   spr.pushSprite(0, 0);
  270.  
  271.   SPI.begin(14, 2, 15, 13);
  272.  
  273.   hasSD = SD.begin(13);
  274.   (hasSD) ? Serial.println("SD opened!") : Serial.println("Issue with SD!");
  275.   (hasSD) ? drawText("OK", 105, RIGHT, TFT_GREEN) : drawText("FAIL", 105, RIGHT, TFT_RED);
  276.   spr.pushSprite(0, 0);
  277.  
  278.   pinMode(21, OUTPUT);    // pinMode(TFT_BL OUTPUT);
  279.  
  280.   Wire.begin();
  281.  
  282.   //set battery charging current to 1000mA
  283.   ip5306_set_bits(IP5306_REG_CHG_4, 0, 5, chargeCurrent);
  284.  
  285.   String current = String((ip5306_get_bits(IP5306_REG_CHG_4, 0, 5) * 100) + 50) + "mA";
  286.   drawText("charging current:", 125, LEFT, TFT_WHITE);
  287.   drawText(current, 125, RIGHT, TFT_WHITE);
  288.  
  289.   xTaskCreatePinnedToCore(
  290.     checkButtons,   /* Task function. */
  291.     "Task1",        /* name of task. */
  292.     10000,          /* Stack size of task */
  293.     NULL,           /* parameter of the task */
  294.     1,              /* priority of the task */
  295.     &handleButtons, /* Task handle to keep track of created task */
  296.     0);             /* pin task to core 0 */
  297.  
  298.   for (int i = 0; i < 8; i++) {
  299.     headingLookupTable[i][0] = sin(radians((i * 45) + 90));
  300.     headingLookupTable[i][1] = cos(radians((i * 45) + 90));
  301.   }
  302.  
  303.   // calculate positions for analog clock and save them, to save resources
  304.   for(int i = 0; i < 60; i++){
  305.  
  306.     //  HOUR
  307.     clockPositions[i][0] = sin(radians((i*6))) * hrRadius; // calculate hour hand X pos
  308.     clockPositions[i][1] = -cos(radians((i*6))) * hrRadius;  // calculate hour hand Y pos
  309.  
  310.     //  MINUTE
  311.     clockPositions[i][2] = sin(radians((i*6))) * mnRadius; // calculate min hand X pos
  312.     clockPositions[i][3] = -cos(radians((i*6))) * mnRadius;  // calculate min hand Y pos
  313.  
  314.     //  COUNTER ( inverted )
  315.     clockCounterPositions[i][0] = -sin(radians((i*6))) * cnRadius; // calculate counter hand X pos
  316.     clockCounterPositions[i][1] = cos(radians((i*6))) * cnRadius;  // calculate counter hand Y pos
  317.   }
  318.  
  319.   for(int i = 0; i < 12; i++){
  320.     // MARKERS START
  321.     clockPositions[i][4] = -sin(radians((i*30))) * 117; // calculate min hand X pos
  322.     clockPositions[i][5] = cos(radians((i*30))) * 117;  // calculate min hand Y pos
  323.  
  324.     // MARKERS END
  325.     clockPositions[i][6] = -sin(radians((i*30))) * 115; // calculate min hand X pos
  326.     clockPositions[i][7] = cos(radians((i*30))) * 115;  // calculate min hand Y pos
  327.   }
  328.  
  329.   Serial.println("Core 1 RDY!");
  330.   spr.pushSprite(0, 0);
  331.   drawText("Core 1 RDY!", 145, LEFT, TFT_GREEN);
  332.   while (!bootRdy) {
  333.     delay(1);
  334.   }
  335.   drawText("Core 2 RDY!", 165, LEFT, TFT_GREEN);
  336.   spr.pushSprite(0, 0);
  337.   delay(1000);
  338.  
  339.   spr.fillSprite(TFT_BLACK);
  340. }
  341.  
  342. void loop() {
  343.  
  344.   // show correct home screen
  345.   if (clicked == 1 && screenOn) {
  346.     if (mainScreenIndex + 1 < numScreens) {
  347.       mainScreenIndex++;
  348.       refreshScreen = true;
  349.     }
  350.     clicked = -1;
  351.   }
  352.   if (clicked == 3 && screenOn) {
  353.     if (mainScreenIndex - 1 > -1) {
  354.       mainScreenIndex--;
  355.       refreshScreen = true;
  356.     }
  357.     clicked = -1;
  358.   }
  359.  
  360.   if(!canSleep){
  361.     lastPressed = millis();
  362.   }
  363.  
  364.   // screen autosleep
  365.   if (autoScreenOff && millis() - lastPressed > (screenTimer * 1000)) {
  366.     screenOn = false;
  367.     if(useAOD){
  368.       setScreenBrightness(AODBrightness);
  369.       AOD(false);
  370.     }
  371.     else{ setScreenBrightness(0); }
  372.   }
  373.   else if (!screenOn) {
  374.     screenOn = true;
  375.     setScreenBrightness(screenBrightness);
  376.     refreshScreen = true;
  377.     clicked = -1;
  378.   }
  379.  
  380.   if (autoSleep && millis() - lastPressed > (sleepTimer * 1000)) {
  381.     setScreenBrightness(0);
  382.     esp_deep_sleep_start();
  383.   }
  384.  
  385.   // enter settings menu
  386.   if (clicked == 2 && screenOn) {
  387.     clicked = -1;
  388.     settings();
  389.   }
  390.  
  391.   if (millis() - lastRefresh > refreshRate) {
  392.     lastRefresh = millis();
  393.     refreshScreen = true;
  394.   }
  395.   // refresh screen
  396.   if (refreshScreen && screenOn) {
  397.  
  398.     refreshScreen = false;
  399.     switch (mainScreenIndex) {
  400.       case 0:
  401.         homeScreen();
  402.         break;
  403.  
  404.       case 1:
  405.         drawGPSData();
  406.         break;
  407.  
  408.       case 2:
  409.         drawSatConstellation();
  410.         break;
  411.      
  412.       case 3:
  413.         drawBars();
  414.         break;
  415.      
  416.       case numScreens-1:  // graph screen is always last screen
  417.         drawGraph();
  418.         break;
  419.     }
  420.   }
  421.  
  422.   delay(1);
  423. }
  424.  
  425. // --------------- UI - GPS ---------------
  426. void homeScreen(){
  427.   tft.setRotation(landscape);
  428.   spr.fillSprite(TFT_BLACK);
  429.  
  430.   canSleep = true;
  431.  
  432.  // drawStatusBar(String(gpsData.sats), "");
  433.  
  434.   u8f.setFont(fontMid);
  435.  
  436.   String dateString = gpsData.time + "  " + String(gpsData.GPSday) + " " + monthNames[gpsData.GPSmonth];
  437.   drawText(dateString, 25, CENTER_NO, palletes[pallete][2]);
  438.   spr.drawFastHLine(0, 40, screenWidth, palletes[pallete][1]);
  439.  
  440.   drawBattery(15, 13, 10, 7);
  441.  
  442.   u8f.setFont(fontMid);
  443.  
  444.   if(usePrecision){
  445.     drawText("lat: " + String(gpsData.latP), 90, CENTER_NO, palletes[pallete][2]);
  446.     drawText("lon: " + String(gpsData.lonP), 110, CENTER_NO, palletes[pallete][2]);
  447.     drawText("alt: " + String(gpsData.alt), 130, CENTER_NO, palletes[pallete][2]);
  448.  
  449.   }
  450.   else if(useDMS){
  451.     drawText("lat: " + String(gpsData.latDMS, 6), 90, CENTER_NO, palletes[pallete][2]);
  452.     drawText("lon: " + String(gpsData.lonDMS, 6), 110, CENTER_NO, palletes[pallete][2]);
  453.     drawText("alt: " + String(gpsData.alt), 130, CENTER_NO, palletes[pallete][2]);
  454.   }
  455.   else{
  456.     drawText("lat: " + String(gpsData.lat, 6), 90, CENTER_NO, palletes[pallete][2]);
  457.     drawText("lon: " + String(gpsData.lon, 6), 110, CENTER_NO, palletes[pallete][2]);
  458.     drawText("alt: " + String(gpsData.alt), 130, CENTER_NO, palletes[pallete][2]);
  459.   }
  460.  
  461.   if(gpsData.spd >= minCardinalSpeed){
  462.     String spdText;
  463.     if(useKmS){
  464.       spdText = String(gpsData.spd) + "km/h - ";
  465.     }
  466.     else { spdText = String(gpsData.spdS) + "m/s - "; }
  467.  
  468.     spdText += String(gpsData.hdg, 0) + "°";
  469.  
  470.     drawText(spdText, 160, CENTER_NO, palletes[pallete][2]);
  471.   }
  472.  
  473.   u8f.setFont(fontxSml);
  474.  
  475.   drawText(fixTypes[gpsData.fixStatus], 15, RIGHT, palletes[pallete][2]);
  476.   if (gpsData.sats > 0) { drawText(String(gpsData.sats), 30, RIGHT, palletes[pallete][2]); }
  477.  
  478.   u8f.setFont(fontSml);
  479.   if(gpsData.fixStatus > 2){
  480.     if(liveMeassure){
  481.  
  482.       String liveDistance = String(fix.location.DistanceKm(liveMeassureLoc), 3) + "km, " + String(fix.location.BearingToDegrees(liveMeassureLoc), 0) + "°";
  483.       drawText("Live Meassure: ", 220, CENTER_NO, palletes[pallete][2]);
  484.       drawText(liveDistance, 240, CENTER_NO, palletes[pallete][2]);
  485.     }
  486.     else{
  487.       String mikaDistance = String(fix.location.DistanceKm(mikaHome)) + "km from home";
  488.       drawText(mikaDistance, 220, CENTER_NO, palletes[pallete][2]);
  489.  
  490.       String hestiaDistance = String(String(fix.location.DistanceKm(hestiaHome)));
  491.       (fix.location.DistanceKm(hestiaHome) > 3 ) ? hestiaDistance += "km too far from her" : hestiaDistance += "km";
  492.       drawText(hestiaDistance, 240, CENTER_NO, palletes[pallete][2]);
  493.     }
  494.   }
  495.   else{
  496.     drawText("Waiting for fix..", 220, CENTER_NO, palletes[pallete][2]);
  497.   }
  498.  
  499.   spr.pushSprite(0, 0);
  500. }
  501.  
  502. void drawGPSData() {
  503.   tft.setRotation(landscape);
  504.  
  505.   canSleep = false;
  506.  
  507.   unsigned long lastUpdate, last;
  508.  
  509.   bool refresh = true;
  510.  
  511.   clicked = -1;
  512.  
  513.   if(gpsData.fixStatus < 3) {
  514.     noFixScreen();
  515.     return;
  516.   }
  517.  
  518.   if (millis() - lastUpdate > 100) {
  519.     lastUpdate = millis();
  520.     refresh = false;
  521.  
  522.     spr.fillSprite(TFT_BLACK);
  523.  
  524.     drawStatusBar(String(gpsData.sats), "");
  525.  
  526.     //  draw speed compenents cross
  527.     //  spr.drawWideLine((screenWidth/2)+15, 5, screenWidth/2)+15, 100, 5, palletes[pallete][2]);
  528.  
  529.     //  u8f.setFont(fontMid);
  530.     // if(landscape){
  531.    
  532.     //   drawText(String(gpsData.velN) + "N", 50, CENTER, palletes[pallete][2]);
  533.     //   drawText(String(gpsData.velE) + "E", 70, CENTER, palletes[pallete][2]);
  534.     //   drawText(String(gpsData.velD) + "D", 90, CENTER, palletes[pallete][2]);        
  535.     // }
  536.     // else{
  537.  
  538.     //   drawText(String(gpsData.sats), 20, LEFT, palletes[pallete][2]);
  539.  
  540.     //   drawText(String(gpsData.velN) + "N", 70, CENTER, palletes[pallete][2]);
  541.     //   drawText(String(gpsData.velE) + "E", 90, CENTER, palletes[pallete][2]);
  542.     //   drawText(String(gpsData.velD) + "D", 110, CENTER, palletes[pallete][2]);
  543.     // }
  544.  
  545.     //  drawHeadingCircle(gpsData.hdg, gpsData.spd);
  546.  
  547.     drawAltHeading(gpsData.hdg, gpsData.spd);
  548.    
  549.     spr.pushSprite(0, 0);
  550.   }
  551. }
  552.  
  553. void drawHeadingCircle(int heading, double speed) {
  554.   tft.setRotation(landscape);
  555.  
  556.   int radius;
  557.   (landscape) ? radius = 130 : radius = 100;
  558.  
  559.   int x;
  560.   (landscape) ? x = (screenWidth/2)+15 : x = screenWidth/2;
  561.  
  562.   int y = 320 - (radius / 3);
  563.  
  564.   String cardinalString[3];
  565.  
  566.   drawText("^", (y - radius), CENTER, palletes[pallete][2]);
  567.   spr.drawSmoothCircle(x, y, radius, palletes[pallete][0], TFT_BLACK);
  568.  
  569.   if(landscape){
  570.     drawText(String(heading) + "°", y-57, CENTER, palletes[pallete][2]);
  571.     (useKmS) ? drawText(String(speed) + "km/h", y-37, CENTER, palletes[pallete][2]) : drawText(String(speed) + "m/s", y-37, CENTER, palletes[pallete][2]);
  572.   }
  573.   else{
  574.     drawText(String(heading) + "°", y, CENTER, palletes[pallete][2]);
  575.     (useKmS) ? drawText(String(speed) + "km/h", screenHeight-10, CENTER, palletes[pallete][2]) : drawText(String(speed) + "m/s", screenHeight-10, CENTER, palletes[pallete][2]);
  576.   }
  577.  
  578.   int textX;
  579.   int textY;
  580.   int w;
  581.   uint16_t tColor;
  582.   for (int i = 0; i < 8; i++) {
  583.  
  584.     if (i % 2) { // for 45, 135, 225, 315 deg
  585.       u8f.setFont(fontSpecial);
  586.       tColor = palletes[pallete][1];
  587.     }  
  588.     else {      //for 0, 90, 180, 270 deg
  589.       u8f.setFont(fontMid);
  590.       tColor = palletes[pallete][2];
  591.     }
  592.  
  593.     w = u8f.getUTF8Width(markers[i].c_str());
  594.     textX = sin(radians(heading + (i * 45))) * radius + x;
  595.     textY = cos(radians(heading + (i * 45))) * radius + y;
  596.     drawText(markers[i], textX - (w / 2), textY + 7, tColor);
  597.   }
  598. }
  599.  
  600. void drawAltHeading(int heading, double speed){
  601.   tft.setRotation(landscape);
  602.   int radius = (240/2)-8;
  603.  
  604.   int x = (screenWidth/2);
  605.   int y = (screenHeight/2);
  606.  
  607.   (landscape) ? x += 15 : y += 15;
  608.  
  609.   int dotX = sin(radians(heading)) * radius + x;
  610.   int dotY = -cos(radians(heading)) * radius + y;
  611.  
  612.   int armX = sin(radians(heading)) * (radius-10) + x;
  613.   int armY = -cos(radians(heading)) * (radius-10) + y;
  614.  
  615.   int armCounterX = -sin(radians(heading)) * (radius-10) + x;
  616.   int armCounterY = cos(radians(heading)) * (radius-10) + y;
  617.  
  618.   //  spr.drawSmoothCircle(x, y, radius, palletes[pallete][0], TFT_BLACK);
  619.   //  spr.drawFastHLine(x-radius+5, y, (radius-5)*2, palletes[pallete][0]);
  620.   //  spr.drawFastVLine(x, y-radius+5, (radius-5)*2, palletes[pallete][0]);
  621.  
  622.   uint16_t tColor;
  623.   byte w = 0;
  624.  
  625.   u8f.setFont(fontSpecial);
  626.   for(int i = 0; i < 8; i++) {
  627.  
  628.     (i % 2) ? tColor = palletes[pallete][1] : tColor = palletes[pallete][2];
  629.  
  630.     w = u8f.getUTF8Width(markers[i].c_str());
  631.     int textX = sin(radians(i * 45)) * radius + x;
  632.     int textY = cos(radians(i * 45)) * radius + y;
  633.     //  if(i % 2 == 0) { spr.drawSmoothCircle(textX, textY, 10, tColor, TFT_BLACK); }
  634.     drawText(markers[i], textX - (w / 2), textY + 7, tColor);
  635.   }
  636.  
  637.   String headingS = String(heading);
  638.  
  639.   if(gpsData.spd >= minCardinalSpeed){
  640.     spr.drawSpot(x, y, 5, palletes[pallete][0], TFT_BLACK);
  641.  
  642.     spr.drawWedgeLine(x, y, armCounterX, armCounterY, 5, 1, TFT_WHITE);
  643.     spr.drawWedgeLine(x, y, armX, armY, 5, 1, TFT_RED);
  644.   }
  645.   else{
  646.     spr.drawSpot(x, y, 5, TFT_BLACK, TFT_BLACK);
  647.  
  648.     spr.drawWedgeLine(x, y, armX, armY, 5, 1, TFT_DARKGREY);
  649.     spr.drawWedgeLine(x, y, armCounterX, armCounterY, 5, 1, TFT_LIGHTGREY);
  650.   }
  651. }
  652.  
  653. void drawSatConstellation() {
  654.   tft.setRotation(landscape);
  655.  
  656.   canSleep = true;
  657.  
  658.   if(gpsData.fixStatus < 3) { noFixScreen(); }
  659.   else
  660.   {
  661.     // only here, for now
  662.     bool drawMarkers = true;
  663.  
  664.     int outerRadius = 118;
  665.     int circleX,circleY;
  666.  
  667.     if(landscape){
  668.       circleX = 175;    // add width of status bar
  669.       circleY = 120;
  670.     }
  671.     else{
  672.       circleX = 120;
  673.       circleY = 195;
  674.     }
  675.  
  676.     int totalCount;
  677.    
  678.     spr.fillSprite(TFT_BLACK);
  679.  
  680.     drawStatusBar(String(gpsData.sats), "");
  681.  
  682.     // draw main circles, one at 0deg, and one at 45deg altitude
  683.  
  684.     spr.drawSmoothCircle(circleX, circleY, outerRadius, palletes[pallete][0], TFT_BLACK);
  685.     spr.drawSmoothCircle(circleX, circleY, outerRadius/2, palletes[pallete][0], TFT_BLACK);
  686.     // tft.drawCircle(120, 200, 5, TFT_WHITE);
  687.  
  688.     // draw lines at 0, 45, 90, 135 etc degrees azimuth
  689.     for (int i = 0; i < 8; i++) {
  690.       int xPos = sin(radians((i * 45) + 90)) * outerRadius;
  691.       int yPos = cos(radians((i * 45) + 90)) * outerRadius;
  692.  
  693.       spr.drawLine(circleX, circleY, xPos + circleX, yPos + circleY, palletes[pallete][1]);
  694.  
  695.       u8f.setFont(fontxSml);
  696.       int markerIndex = i + 2;
  697.       markerIndex %= 8;
  698.  
  699.       int w = u8f.getUTF8Width(markers[markerIndex].c_str());
  700.  
  701.       drawText(markers[markerIndex], xPos - (w / 2) + circleX, (yPos + 6) + circleY, palletes[pallete][2]);
  702.     }
  703.  
  704.     // draw the positions of the sats
  705.     for (int i = 0; i < gpsData.totalSats; i++) {
  706.       int xPos = -sin(radians(gps.satellites[i].azimuth + 180)) * map(gps.satellites[i].elevation, 0, 90, outerRadius, 1);
  707.       int yPos = cos(radians(gps.satellites[i].azimuth + 180)) * map(gps.satellites[i].elevation, 0, 90, outerRadius, 1);
  708.  
  709.      
  710.       if(gps.satellites[i].tracked){
  711.         int snr = gps.satellites[i].snr;
  712.         spr.drawSpot(xPos + circleX, yPos + circleY, SAT_RADIUS, SNRToColor(snr));
  713.       }
  714.       else{ spr.drawSpot(xPos + circleX, yPos + circleY, SAT_RADIUS, TFT_DARKGREY); }
  715.  
  716.       String satMarker;
  717.       satMarker = String(gps.satellites[i].id);
  718.  
  719.       int w = u8f.getUTF8Width(satMarker.c_str());
  720.       int textY = yPos+(circleY-SAT_RADIUS)-2;
  721.       int textX = xPos+circleX-(w/2);
  722.  
  723.       if(landscape && textY < 15){ textY = yPos+(circleY+SAT_RADIUS) + 15; }
  724.       (gps.satellites[i].tracked) ? drawText(satMarker, textX, textY, palletes[pallete][2]) :  drawText(satMarker, textX, textY, TFT_DARKGREY);
  725.      
  726.     }
  727.     spr.pushSprite(0, 0);
  728.   }
  729. }
  730.  
  731. void drawBars(){
  732.   tft.setRotation(1);
  733.  
  734.   canSleep = true;
  735.  
  736.   spr.fillSprite(TFT_BLACK);
  737.  
  738.   if(gpsData.fixStatus < 3) { noFixScreen(); }
  739.   else
  740.   {
  741.     int numSatsHalf = gpsData.totalSats/2;
  742.     bool odd = false;
  743.    
  744.     byte spacing = 10;
  745.     byte barHeight = 100;
  746.    
  747.     byte startOffset;
  748.     (landscape) ? startOffset = 32 : startOffset = 0;
  749.  
  750.     int screenWidthLocal = screenWidth;
  751.     if(landscape){ screenWidthLocal -= 35; }
  752.  
  753.     screenWidthLocal -= startOffset;
  754.    
  755.     // set odd flag
  756.     if((gpsData.sats) % 2){
  757.       odd = true;
  758.     }
  759.  
  760.     byte colorIndex;
  761.  
  762.     drawStatusBar(String(gpsData.sats), "");
  763.  
  764.     u8f.setFont(fontSml);
  765.  
  766.     int x, y, barWidth, topCnt, btmCnt;
  767.     topCnt = numSatsHalf;
  768.  
  769.     if(odd){
  770.       barWidth = (screenWidthLocal/(numSatsHalf+1))-spacing;
  771.       btmCnt = numSatsHalf+1;
  772.     }
  773.     else{
  774.       barWidth = (screenWidthLocal/numSatsHalf)-spacing;
  775.       btmCnt = numSatsHalf;
  776.     }
  777.  
  778.     for(int i = 0; i < topCnt; i++){
  779.       (landscape) ? y = 20 : y = 50;
  780.  
  781.       if(!landscape){
  782.         if(i > 0 && i < topCnt-2) { (odd) ? x = ((screenWidthLocal/(numSatsHalf+1))*i) + (spacing/2) + startOffset : x = ((screenWidthLocal/numSatsHalf)*i) + (spacing/2) + startOffset; }
  783.         else { (odd) ? x = ((screenWidthLocal/(numSatsHalf+1))*i) + spacing + startOffset : x = ((screenWidthLocal/numSatsHalf)*i) + spacing + startOffset; }
  784.       }
  785.       else{ (odd) ? x = ((screenWidthLocal/(numSatsHalf+1))*i) + spacing + startOffset : x = ((screenWidthLocal/numSatsHalf)*i) + spacing + startOffset; }
  786.  
  787.       byte SNR = gps.satellites[i].snr;
  788.       byte barFill = map(SNR, 0, 99, 0, barHeight-1);
  789.  
  790.       (SNR) ? spr.drawRect(x, y, barWidth, barHeight, SNRToColor(SNR)) : spr.drawRect(x, y, barWidth, barHeight, TFT_DARKGREY);
  791.  
  792.       // fillRect() starts at passed X/Y, and works downwards (!)
  793.       // because of this, we first move the top left corner of the filled bar down to the amount it should be filled.
  794.       // then, we set the height ( from the top left corner ) to what is left between the moved start, and the bottom of the bar
  795.      
  796.       spr.fillRect(x+1, y+(barHeight-barFill)-1, barWidth-2, barFill, SNRToColor(SNR));
  797.  
  798.       String text;
  799.       byte textW;
  800.       if(SNR){
  801.         text = String(SNR);
  802.         textW = u8f.getUTF8Width(text.c_str());
  803.  
  804.         drawText(text, x+(barWidth/2)-(textW/2), y+20, palletes[pallete][2]);
  805.       }
  806.  
  807.       text = String(gps.satellites[i].id);
  808.  
  809.       textW = u8f.getUTF8Width(text.c_str());
  810.      
  811.      (gps.satellites[i].tracked) ? drawText(text, x+(barWidth/2)-(textW/2), y-2, palletes[pallete][2]) :  drawText(text, x+(barWidth/2)-(textW/2), y-2, TFT_DARKGREY);
  812.     }
  813.  
  814.     // draw bottom row of bars
  815.     for(int i = 0; i < btmCnt; i++){
  816.       (landscape) ? y = 140 : y = 170;
  817.  
  818.       // calculate X position for each bar
  819.       if(!landscape){
  820.         if(i > 0 && i < topCnt-2) { (odd) ? x = ((screenWidthLocal/(numSatsHalf+1))*i) + (spacing/2) + startOffset : x = ((screenWidthLocal/numSatsHalf)*i) + (spacing/2) + startOffset; }
  821.         else { (odd) ? x = ((screenWidthLocal/(numSatsHalf+1))*i) + spacing + startOffset : x = ((screenWidthLocal/numSatsHalf)*i) + spacing + startOffset; }
  822.       }
  823.       else{ (odd) ? x = ((screenWidthLocal/(numSatsHalf+1))*i) + spacing + startOffset : x = ((screenWidthLocal/numSatsHalf)*i) + spacing + startOffset; }
  824.  
  825.       byte SNR = gps.satellites[i+numSatsHalf].snr;
  826.       byte barFill = map(SNR, 0, 99, 0, barHeight-1);
  827.  
  828.       (SNR) ? spr.drawRect(x, y, barWidth, barHeight, SNRToColor(SNR)) : spr.drawRect(x, y, barWidth, barHeight, TFT_DARKGREY);
  829.  
  830.       // fillRect() starts at passed X/Y, and works downwards (!)
  831.       // because of this, we first move the top left corner of the filled bar down to the amount it should be filled.
  832.       // then, we set the height ( from the top left corner ) to what is left between the moved start, and the bottom of the bar
  833.       spr.fillRect(x+1, y+(barHeight-barFill)-1, barWidth-2, barFill, SNRToColor(SNR));
  834.  
  835.       String text;
  836.       byte textW;
  837.       if(SNR){
  838.         text = String(SNR);
  839.  
  840.         textW = u8f.getUTF8Width(text.c_str());
  841.        
  842.         drawText(text, x+(barWidth/2)-(textW/2), y+20, palletes[pallete][2]);
  843.       }
  844.  
  845.       text = String(gps.satellites[i+numSatsHalf].id);
  846.  
  847.       textW = u8f.getUTF8Width(text.c_str());
  848.      
  849.       (gps.satellites[i+numSatsHalf].tracked) ? drawText(text, x+(barWidth/2)-(textW/2), y-2, palletes[pallete][2]) : drawText(text, x+(barWidth/2)-(textW/2), y-2, TFT_DARKGREY);
  850.     }
  851.  
  852.     String avgSNRs = String(gpsData.SNR);
  853.  
  854.     // PRINT AVG SNR
  855.     if(landscape){
  856.       int avgBarFill = map(gpsData.SNR, 0, 99, 0, 239);
  857.  
  858.       spr.fillRect(301, (239-avgBarFill), 18, avgBarFill, SNRToColor(gpsData.SNR));
  859.      
  860.       spr.drawRect(300, 0, 20, 240, palletes[pallete][0]);
  861.       drawText(avgSNRs, 320-(u8f.getUTF8Width(avgSNRs.c_str())), 20, SNRToColor(gpsData.SNR));
  862.     }
  863.     else{
  864.       spr.drawRect(0, screenHeight-25, screenWidth, 25, palletes[pallete][0]);
  865.  
  866.       int avgBarFill = map(gpsData.SNR, 0, 99, 0, screenWidth-1);
  867.  
  868.       drawText(avgSNRs, screenHeight-25, CENTER, palletes[pallete][2]);
  869.       spr.fillRect(1, screenHeight-24, avgBarFill, 23, SNRToColor(gpsData.SNR));
  870.     }
  871.  
  872.     spr.pushSprite(0, 0);
  873.   }
  874. }
  875.  
  876. void drawGraph(){
  877.   canSleep = false;
  878.  
  879.   float gData[graphSize];
  880.   float datapointsMapped[graphSize];
  881.  
  882.   u8f.setFont(fontSml);
  883.  
  884.   for(int i = 0; i < graphSize; i++){
  885.     gData[i] = -99;
  886.   }
  887.  
  888.   bool refresh = true;
  889.   bool drawnUI = false;
  890.   bool emptyArray = false;
  891.  
  892.   unsigned long last;
  893.  
  894.   int x, y, xe, ye;
  895.   static int dataIndex = 0;
  896.  
  897.   float minOld, maxOld;
  898.  
  899.   String dataMarker = "";
  900.   String markerOld = "";
  901.  
  902.   while(clicked < 2){
  903.  
  904.     // switch data types
  905.     if(clicked == 1){
  906.       dataIndex++;
  907.       if(dataIndex > 3){ dataIndex = 0; }
  908.  
  909.       emptyArray = true;
  910.       clicked = -1;
  911.     }
  912.  
  913.     if(emptyArray){
  914.       emptyArray = false;
  915.       for(int i = 0; i < graphSize; i++){
  916.         gData[i] = -99;
  917.       }
  918.     }
  919.  
  920.     if(millis() - last > gRefreshRate){
  921.       last = millis();
  922.  
  923.       spr.fillSprite(TFT_BLACK);
  924.  
  925.       // left shift new data into array
  926.       for(int i = 0; i < graphSize-1; i++){
  927.         gData[i] = gData[i+1];
  928.       }
  929.  
  930.      switch (dataIndex){
  931.         case 0:
  932.           gData[graphSize-1] = gpsData.SNR;
  933.           dataMarker = "SNR";
  934.           break;
  935.        
  936.         case 1:
  937.           gData[graphSize-1] = gpsData.sats;
  938.           dataMarker = "Sats";
  939.           break;
  940.        
  941.         case 2:
  942.           gData[graphSize-1] = gpsData.spd;
  943.           dataMarker = "SPD ";
  944.           (useKmS) ? dataMarker += "(Km/h)" : dataMarker += "(m/s)";
  945.           break;
  946.  
  947.         case 3:
  948.           if(fix.valid.altitude){
  949.             (useGeoidHeight) ? gData[graphSize-1] = fix.geoidHeight() : gData[graphSize-1] = fix.altitude();
  950.           }
  951.           else{ gData[graphSize-1] = 0; }
  952.           dataMarker = "ALT (m)";
  953.           break;
  954.  
  955.         default:
  956.           gData[graphSize-1] = gpsData.SNR;
  957.           dataMarker = "SNR";
  958.           break;
  959.     }
  960.    
  961.     float minVal = 10000.0;
  962.     float maxVal = -10000.0;
  963.  
  964.     // draw scales
  965.     spr.drawWideLine(30, 0, 30, screenHeight-28, 2, TFT_WHITE, TFT_BLACK);
  966.     spr.drawWideLine(30, screenHeight-28, screenWidth, screenHeight-28, 2, TFT_WHITE, TFT_BLACK);
  967.  
  968.     // draw timescale on X axis and get min/max
  969.     for(int i = 0; i < graphSize; i++){
  970.       if(i % (graphSize/4) == 0){
  971.         u8f.setFont(fontxSml);
  972.  
  973.         String marker = String(gRefreshRate*(graphSize - (i))/1000.0, 1) + "s";
  974.         int markerW = u8f.getUTF8Width(marker.c_str());
  975.         int tPos = 30 + map(i+1, 0, graphSize, 0, screenWidth-30);
  976.         drawText(marker, tPos-(markerW/2), screenHeight-10, TFT_WHITE);
  977.  
  978.         spr.drawFastVLine(tPos, screenHeight-33, 10, TFT_WHITE);  
  979.       }
  980.       if(gData[i] > -99){
  981.         if(gData[i] < minVal) { minVal = gData[i]; }
  982.         if(gData[i] > maxVal) { maxVal = gData[i]; }
  983.       }
  984.     }
  985.  
  986.     // prevent graph line from touching top/bottom by adding 5% of the graph range
  987.     int paddingVal = ((maxVal - minVal)/100)*10;
  988.  
  989.     minVal -= paddingVal;
  990.     maxVal += paddingVal;
  991.     if(maxVal - minVal < 5){
  992.       maxVal += 2.5;
  993.       minVal -= 2.5;
  994.     }
  995.  
  996.     byte dataMarkerWidth = u8f.getUTF8Width(dataMarker.c_str());
  997.     drawText(dataMarker, 15, CENTER, TFT_WHITE);
  998.  
  999.     u8f.setFont(fontSpecial);
  1000.     drawText(String(minVal+paddingVal, 0), 0, screenHeight-28, TFT_WHITE);
  1001.     drawText(String(maxVal-paddingVal, 0), 0, 20, TFT_WHITE);
  1002.  
  1003.     u8f.setFont(fontMid);
  1004.  
  1005.     // transform values to coordinates on screen
  1006.     for(int i = 0; i < graphSize; i++){
  1007.       if(gData[i] == -99) { datapointsMapped[i] = -99; }
  1008.       else{ datapointsMapped[i] = mapF(gData[i], minVal, maxVal, screenHeight-31, 1); }
  1009.     }
  1010.  
  1011.     // draw lines on screen
  1012.       for(int i = 0; i < graphSize-1; i++){
  1013.  
  1014.         if(datapointsMapped[i] > -99 && datapointsMapped[i+1] > -99){
  1015.  
  1016.           x = map(i, 0, graphSize, 32, screenWidth);
  1017.           xe = map(i+1, 0, graphSize, 32, screenWidth);
  1018.  
  1019.           y = datapointsMapped[i];
  1020.           ye = datapointsMapped[i+1];
  1021.  
  1022.           spr.drawLine(xe, ye, x, y, palletes[pallete][1]);
  1023.         }
  1024.       }
  1025.  
  1026.       String text = String(gData[graphSize-1], 1);
  1027.  
  1028.       u8f.setFont(fontSml);
  1029.       int textW = u8f.getUTF8Width(text.c_str());
  1030.  
  1031.       if(datapointsMapped[graphSize-1] < 20){ drawText(text, screenWidth-textW, datapointsMapped[graphSize-1]+17, TFT_WHITE); }
  1032.       else { drawText(text, screenWidth-textW, datapointsMapped[graphSize-1]-3, TFT_WHITE); }
  1033.  
  1034.       delay(1); // WD
  1035.       spr.pushSprite(0, 0);
  1036.     }
  1037.   }
  1038. }
  1039.  
  1040. // --------------- UI - OTHER ---------------
  1041.  
  1042. void noFixScreen(){
  1043.   spr.fillSprite(TFT_BLACK);
  1044.  
  1045.   drawStatusBar("", "");
  1046.  
  1047.   u8f.setFont(fontMid);
  1048.   drawText("Waiting for fix..", 70, CENTER, palletes[pallete][2]);
  1049.  
  1050.   u8f.setFont(fontSml);
  1051.   drawText("SI: " + String(mainScreenIndex), 20, RIGHT, TFT_WHITE);
  1052.   drawText(fixTypes[gpsData.fixStatus], 40, RIGHT, palletes[pallete][2]);
  1053.  
  1054.   spr.pushSprite(0, 0);
  1055. }
  1056.  
  1057. void AOD(bool forceRefresh){
  1058.   byte cPallete;
  1059.   (useAODPalette) ? cPallete = clockPallete : cPallete = pallete;
  1060.  
  1061.   if(altAOD){
  1062.     //  draw analog clock
  1063.  
  1064.     spr.fillSprite(TFT_BLACK);
  1065.  
  1066.     byte s, m, h;
  1067.     String gpsTime = gpsData.time;
  1068.  
  1069.     m = gpsTime.substring(gpsTime.indexOf(":")+1).toInt();
  1070.     h = gpsTime.substring(0, gpsTime.indexOf(":")).toInt();
  1071.  
  1072.     s = gpsData.timeSeconds;
  1073.  
  1074.     h %= 12;  // convert to 12hr time
  1075.     h *= 5;
  1076.  
  1077.     h += map(m, 0, 60, 0, 5); // this makes the hour hand move towards the next hour instead of sticking
  1078.  
  1079.     int x = screenWidth/2;
  1080.     int y = screenHeight/2;
  1081.  
  1082.     // draw seconds hand
  1083.     if(showSeconds) {
  1084.       spr.drawWideLine(x, y, clockPositions[s][2] + x, clockPositions[s][3] + y, 2, TFT_WHITE);
  1085.       // spr.drawWideLine(x, y, clockCounterPositions[s][0] + x, clockCounterPositions[s][1] + y, 2, TFT_WHITE);
  1086.     }
  1087.  
  1088.     // draw minute hand
  1089.     spr.drawWedgeLine(x, y, clockPositions[m][2] + x, clockPositions[m][3] + y, 2, 1, palletes[cPallete][1]);
  1090.     spr.drawWedgeLine(x, y, clockCounterPositions[m][0] + x, clockCounterPositions[m][1] + y, 2, 1, palletes[cPallete][1]);
  1091.    
  1092.     // draw markers
  1093.     for(int i = 0; i < 12; i++){
  1094.       spr.drawWideLine(clockPositions[i][4] + x, clockPositions[i][5] + y, clockPositions[i][6] + x, clockPositions[i][7] + y, 3, palletes[cPallete][0]);
  1095.     }
  1096.  
  1097.     //  spr.drawSmoothCircle(x, y, 119, TFT_WHITE, TFT_BLACK);
  1098.  
  1099.     // draw center ring
  1100.     spr.drawSpot(x, y, 6, TFT_BLACK);                   //  clear area
  1101.     //spr.drawSpot(x, y, 10, TFT_TRANSPARENT);            // draw shadow
  1102.     spr.drawSpot(x, y, 7, palletes[cPallete][1]);       //  draw center
  1103.  
  1104.     // draw hour hand
  1105.     spr.drawWedgeLine(x, y, clockPositions[h][0] + x, clockPositions[h][1] + y, 3, 1, palletes[cPallete][1]);
  1106.     spr.drawWedgeLine(x, y, clockCounterPositions[h][0] + x, clockCounterPositions[h][1] + y, 3, 2, palletes[cPallete][1]);
  1107.  
  1108.     spr.drawSpot(x, y, 3, TFT_BLACK);                   //  clear center
  1109.  
  1110.     if(showBattery) { drawBattery(x, y, 10, 5); }
  1111.     else{
  1112.       u8f.setFont(fontxSml);
  1113.       byte textLen;
  1114.  
  1115.       if(getCharging()){
  1116.        
  1117.         textLen = u8f.getUTF8Width("Charging");
  1118.         drawText("Charging", (screenWidth/2) - (textLen/2), y+17, TFT_WHITE);
  1119.       }
  1120.     }
  1121.  
  1122.       spr.pushSprite(0, 0);
  1123.     }
  1124.   else{
  1125.     //  draw digital clock
  1126.  
  1127.     spr.fillSprite(TFT_BLACK);
  1128.  
  1129.     tft.setRotation(landscape);
  1130.    
  1131.     u8f.setFont(fontBig);
  1132.    
  1133.     setScreenBrightness(1);
  1134.  
  1135.     // draw time
  1136.     String timeWhole = gpsData.time;
  1137.     String time = timeWhole.substring(0, timeWhole.indexOf(":"));
  1138.     int len = u8f.getUTF8Width(time.c_str());
  1139.     drawText(time, (screenWidth/2)-(len/2), (screenHeight/2)-10,  TFT_DARKGREY);
  1140.  
  1141.     time = timeWhole.substring(timeWhole.indexOf(":")+1);
  1142.     len = u8f.getUTF8Width(time.c_str());
  1143.     drawText(time, (screenWidth/2)-(len/2), (screenHeight/2)+60, TFT_DARKGREY);
  1144.  
  1145.     drawBattery(screenWidth/2, screenHeight/2, 100, 90);
  1146.  
  1147.     spr.pushSprite(0, 0);
  1148.   }
  1149. }
  1150.  
  1151. void drawBattery(int x, int y, int r, int ir){
  1152.   int bat = 0;
  1153.  
  1154.   bat = getBatLevel();  
  1155.  
  1156.   int arcEnd;
  1157.  
  1158.   switch (bat){
  1159.     case 100:
  1160.       arcEnd = 180;
  1161.       break;
  1162.    
  1163.     case 75:
  1164.       arcEnd = 90;
  1165.       break;
  1166.    
  1167.     case 50:
  1168.       arcEnd = 0;
  1169.       break;
  1170.    
  1171.     case 25:
  1172.       arcEnd = 270;
  1173.       break;
  1174.   }
  1175.  
  1176.   spr.drawSmoothArc(x, y, r, ir, 180, 180, TFT_DARKGREY, TFT_BLACK);
  1177.   spr.drawSmoothArc(x, y, r, ir, 180, arcEnd, TFT_SILVER, TFT_BLACK);
  1178.  
  1179.   if(getCharging()){
  1180.     u8f.setFont(fontxSml);
  1181.  
  1182.     byte textLen = u8f.getUTF8Width("CHG");
  1183.     drawText("CHG", x - (textLen/2), y + r + 11, TFT_WHITE);
  1184.   }
  1185. }
  1186.  
  1187. void drawStatusBar(String itemTop, String itemBot) {
  1188.   tft.setRotation(landscape);
  1189.  
  1190.   u8f.setFont(fontSml);
  1191.  
  1192.   if(landscape){
  1193.     String wholeTime = gpsData.time;
  1194.  
  1195.     drawText(wholeTime.substring(0, wholeTime.indexOf(":")), (screenHeight/2)-10, LEFT, palletes[pallete][2]);
  1196.     drawText(wholeTime.substring(wholeTime.indexOf(":")+1), (screenHeight/2)+10, LEFT, palletes[pallete][2]);
  1197.  
  1198.     int textW;
  1199.     textW = u8f.getUTF8Width(itemTop.c_str());
  1200.     drawText(itemTop, (30/2)-(textW/2)-2, (screenHeight/4) + 7, palletes[pallete][2]);
  1201.  
  1202.     textW = u8f.getUTF8Width(itemBot.c_str());
  1203.     drawText(itemBot, (30/2)-(textW/2)-2, (screenHeight/4)*3 + 7, palletes[pallete][2]);
  1204.  
  1205.     u8f.setFont(fontxSml);
  1206.  
  1207.     if(hasSD){
  1208.       if (logGPS) {
  1209.         textW = u8f.getUTF8Width("GPS");
  1210.         drawText("GPS", (30/2)-(textW/2)-2, screenHeight-10, palletes[pallete][2]);
  1211.       }
  1212.       if(logSPD) {
  1213.         textW = u8f.getUTF8Width("SPD");
  1214.         drawText("SPD", (30/2)-(textW/2)-2, screenHeight-20, palletes[pallete][2]);
  1215.       }
  1216.  
  1217.       if(!logSPD && !logGPS){
  1218.         textW = u8f.getUTF8Width("SD");
  1219.         drawText("SD", (30/2)-(textW/2)-2, screenHeight-10, palletes[pallete][2]);
  1220.       }
  1221.     }
  1222.     drawBattery(15, 13, 10, 7);
  1223.     spr.fillRect(29, 0, 3, screenHeight, palletes[pallete][1]);
  1224.   }
  1225.   else{
  1226.     drawText(itemTop, 20, LEFT, palletes[pallete][2]);
  1227.     drawText(gpsData.time, 20, CENTER, palletes[pallete][2]);
  1228.  
  1229.     if(hasSD){
  1230.       u8f.setFont(fontxSml);
  1231.       int textW;
  1232.  
  1233.       if (logGPS) {
  1234.         textW = u8f.getUTF8Width("GPS");
  1235.         drawText("GPS", screenWidth-textW-2, 10, palletes[pallete][2]);
  1236.       }
  1237.       if(logSPD) {
  1238.         textW = u8f.getUTF8Width("SPD");
  1239.         drawText("SPD", screenWidth-textW-2, 20, palletes[pallete][2]);
  1240.       }
  1241.  
  1242.       if(!logSPD && !logGPS){
  1243.         textW = u8f.getUTF8Width("SD");
  1244.         drawText("SD", screenWidth-textW-2, 10, palletes[pallete][2]);
  1245.       }
  1246.     }
  1247.      
  1248.     int bat = getBatLevel();
  1249.     spr.drawRect(0, 25, 240, 5, palletes[pallete][1]);
  1250.     spr.fillRect(1, 26, map(bat, 0, 100, 1, 240), 4, palletes[pallete][0]);
  1251.   }
  1252. }
  1253.  
  1254. // --------------- SETTINGS - MENUS ---------------
  1255. void settings() {
  1256.   tft.setRotation(landscape);
  1257.  
  1258.   byte cursor = 0;
  1259.  
  1260.   int oldCursorIndex = 1;
  1261.   int subSettingsIndex = 0;
  1262.   runSettings = true;
  1263.  
  1264.   bool forceRefresh = false;
  1265.  
  1266.   delay(200);
  1267.  
  1268.   while (runSettings) {
  1269.     cursor = handleCursorIndex(false, numSettings);
  1270.  
  1271.     switch (subSettingsIndex) {
  1272.       case 1:
  1273.         powerSettings();
  1274.         subSettingsIndex = 0;
  1275.         oldCursorIndex = -1;  // force screen to update
  1276.         handleCursorIndex(true, numSettings);
  1277.         break;
  1278.  
  1279.       case 2:
  1280.         screenSettings();
  1281.         subSettingsIndex = 0;
  1282.         oldCursorIndex = -1;  // force screen to update
  1283.         handleCursorIndex(true, numSettings);
  1284.         break;
  1285.  
  1286.       case 3:
  1287.         AODSettings();
  1288.         subSettingsIndex = 0;
  1289.         oldCursorIndex = -1;
  1290.         handleCursorIndex(true, numSettings);
  1291.         break;
  1292.        
  1293.       case 4:
  1294.         GPSSettings();
  1295.         subSettingsIndex = 0;
  1296.         handleCursorIndex(true, numSettings);
  1297.         break;
  1298.  
  1299.       case 5:
  1300.         loggingSettings();
  1301.         subSettingsIndex = 0;
  1302.         handleCursorIndex(true, numSettings);
  1303.         break;
  1304.     }
  1305.  
  1306.     // ENTER was clicked, check what to run
  1307.     if (clicked == 2) {
  1308.       Serial.println(settingsText[cursor]);
  1309.  
  1310.       if (settingsText[cursor].equals("Power settings")) {
  1311.         subSettingsIndex = 1;
  1312.       }
  1313.       else if(settingsText[cursor].equals("Screen settings")){
  1314.         subSettingsIndex = 2;
  1315.       }
  1316.       else if(settingsText[cursor].equals("AOD settings")){
  1317.         subSettingsIndex = 3;
  1318.       }
  1319.       else if (settingsText[cursor].equals("GPS settings")) {
  1320.         subSettingsIndex = 4;
  1321.       }
  1322.       else if(settingsText[cursor].equals("Deep sleep")) {
  1323.         setScreenBrightness(0);
  1324.         esp_deep_sleep_start();
  1325.       }
  1326.       else if (settingsText[cursor].equals("Pallete")) {
  1327.         pallete++;
  1328.         if (pallete > numPalletes - 1) {
  1329.           pallete = 0;
  1330.         }
  1331.         forceRefresh = true;
  1332.       }
  1333.       else if (settingsText[cursor].equals("Logging settings")) {
  1334.         subSettingsIndex = 5;
  1335.       }
  1336.       else if(settingsText[cursor].equals("Start live meassure")){
  1337.         if(gpsData.sats > 0){
  1338.           settingsText[6] = "Stop live meassure";
  1339.  
  1340.           liveMeassureLoc = fix.location;
  1341.  
  1342.           liveMeassure = true;
  1343.           forceRefresh = true;
  1344.         }
  1345.       }
  1346.       else if(settingsText[cursor].equals("Stop live meassure")){
  1347.         settingsText[6] = "Start live meassure";
  1348.  
  1349.         liveMeassure = false;
  1350.         forceRefresh = true;
  1351.       }
  1352.       else if (settingsText[cursor].equals("Exit")) {
  1353.         setSettings();  //save settings
  1354.         runSettings = false;
  1355.         handleCursorIndex(true, numSettings);
  1356.         clicked = -1;
  1357.         refreshScreen = true;
  1358.       }
  1359.       clicked = -1;
  1360.     }
  1361.  
  1362.     // only draw screen if something changed
  1363.     if (oldCursorIndex != cursor || forceRefresh) {
  1364.       spr.fillSprite(TFT_BLACK);
  1365.  
  1366.       oldCursorIndex = cursor;
  1367.       forceRefresh = false;
  1368.  
  1369.       drawSettingOptions(settingsPtr, numSettings, cursor);
  1370.  
  1371.       drawText(palleteNames[pallete], 120, RIGHT, TFT_WHITE);
  1372.  
  1373.       String chargingCurrent = String((ip5306_get_bits(IP5306_REG_CHG_4, 0, 5) * 100) + 50) + "mA";
  1374.       drawText("Charging current: " + chargingCurrent, screenHeight-5, LEFT, TFT_WHITE);
  1375.  
  1376.       spr.pushSprite(0, 0);
  1377.     }
  1378.     delay(1);  // keep WD happy
  1379.   }
  1380. }
  1381.  
  1382. void powerSettings() {
  1383.   tft.setRotation(landscape);
  1384.  
  1385.   byte cursor = 0;
  1386.   byte oldCursor = 0;
  1387.   bool forceRefresh = true;
  1388.   bool ignoreButtons = false;
  1389.  
  1390.   delay(200);
  1391.   handleCursorIndex(true, numPowerSettings);
  1392.  
  1393.   while (runSettings) {
  1394.     if (!ignoreButtons) { cursor = handleCursorIndex(false, numPowerSettings); }
  1395.  
  1396.     if (clicked == 2 || ignoreButtons) {
  1397.       if (powerSettingsText[cursor].equals("Auto-sleep")) {
  1398.         autoSleep = !autoSleep;
  1399.         forceRefresh = true;
  1400.         clicked = -1;
  1401.       }
  1402.       else if (powerSettingsText[cursor].equals("Sleep timer")) {
  1403.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &sleepTimer, 0, 1, 900000);
  1404.       }
  1405.       else if (powerSettingsText[cursor].equals("Screen timer")) {
  1406.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &screenTimer, 0, 1, 900000);
  1407.       }
  1408.       else if (powerSettingsText[cursor].equals("Screen auto-off")) {
  1409.         autoScreenOff = !autoScreenOff;
  1410.         forceRefresh = true;
  1411.         clicked = -1;
  1412.       }
  1413.       else if (powerSettingsText[cursor].equals("Screen brightness")) {
  1414.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &screenBrightness, 0, 1, 10);
  1415.       }
  1416.       else if (powerSettingsText[cursor].equals("Back")) {
  1417.         clicked = -1;
  1418.         return;
  1419.       }
  1420.     }
  1421.  
  1422.     if (cursor != oldCursor || forceRefresh) {
  1423.       oldCursor = cursor;
  1424.       forceRefresh = false;
  1425.       spr.fillSprite(TFT_BLACK);
  1426.  
  1427.       drawSettingOptions(powerArrayPtr, numPowerSettings, cursor);
  1428.       (autoSleep) ? drawText("ON", 20, RIGHT, TFT_GREEN) : drawText("OFF", 20, RIGHT, TFT_RED);
  1429.  
  1430.       drawText(String(sleepTimer) + "s", 40, RIGHT, TFT_WHITE);
  1431.       spr.pushSprite(0, 0);
  1432.     }
  1433.     delay(1);
  1434.   }
  1435. }
  1436.  
  1437. void screenSettings(){
  1438.   tft.setRotation(landscape);
  1439.  
  1440.   byte cursor = 0;
  1441.   byte oldCursor = 0;
  1442.   bool forceRefresh = true;
  1443.   bool ignoreButtons = false;
  1444.  
  1445.   delay(200);
  1446.  
  1447.   handleCursorIndex(true, numScreenSettings);
  1448.  
  1449.   while (runSettings) {
  1450.     if (!ignoreButtons) { cursor = handleCursorIndex(false, numScreenSettings); }
  1451.  
  1452.     if (clicked == 2 || ignoreButtons) {
  1453.       if (screenSettingsText[cursor].equals("Screen auto-off")) {
  1454.         autoScreenOff = !autoScreenOff;
  1455.         forceRefresh = true;
  1456.         clicked = -1;
  1457.       }
  1458.       else if(screenSettingsText[cursor].equals("Screen timer")) {
  1459.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &screenTimer, 0, 1, 900000);
  1460.       }  
  1461.       else if (screenSettingsText[cursor].equals("Screen brightness")) {
  1462.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &screenBrightness, 0, 1, 10);
  1463.       }
  1464.       else if(screenSettingsText[cursor].equals("Screen orientation")){
  1465.         landscape = !landscape;
  1466.         forceRefresh = true;
  1467.         tft.setRotation(landscape);
  1468.  
  1469.         screenHeight = tft.height();
  1470.         screenWidth = tft.width();
  1471.         clicked = -1;
  1472.         // make new virtual canvas with correct dimensions
  1473.         spr.deleteSprite();
  1474.         spr.createSprite(screenWidth, screenHeight);
  1475.       }
  1476.       else if(screenSettingsText[cursor].equals("Use pallete for SNR")){
  1477.         SNRPallete = !SNRPallete;
  1478.         forceRefresh = true;
  1479.         clicked = -1;
  1480.       }
  1481.       else if(screenSettingsText[cursor].equals("Back")) {
  1482.         clicked = -1;
  1483.         return;
  1484.       }
  1485.     }
  1486.       if (cursor != oldCursor || forceRefresh) {
  1487.       oldCursor = cursor;
  1488.       forceRefresh = false;
  1489.       spr.fillSprite(TFT_BLACK);
  1490.  
  1491.       drawSettingOptions(screenArrayPtr, numScreenSettings, cursor);
  1492.  
  1493.       (autoScreenOff) ? drawText("ON", 20, RIGHT, TFT_GREEN) : drawText("OFF", 20, RIGHT, TFT_RED);
  1494.       drawText(String(screenTimer) + "s", 40, RIGHT, TFT_WHITE);
  1495.  
  1496.       drawText(String(screenBrightness), 60, RIGHT, TFT_WHITE);
  1497.  
  1498.       if(landscape){
  1499.         (landscape) ? drawText("Landscape", 80, RIGHT, TFT_WHITE) : drawText("Portrait", 80, RIGHT, TFT_WHITE);
  1500.       }
  1501.       else{
  1502.         (landscape) ? drawText("L", 80, RIGHT, TFT_WHITE) : drawText("P", 80, RIGHT, TFT_WHITE);
  1503.       }
  1504.       (SNRPallete) ? drawText("ON", 100, RIGHT, TFT_GREEN) : drawText("OFF", 100, RIGHT, TFT_RED);
  1505.  
  1506.       spr.pushSprite(0, 0);
  1507.     }
  1508.     delay(1);
  1509.   }
  1510. }
  1511.  
  1512. void AODSettings(){
  1513.   tft.setRotation(landscape);
  1514.  
  1515.   byte cursor = 0;
  1516.   byte oldCursor = 0;
  1517.   bool forceRefresh = true;
  1518.   bool ignoreButtons = false;
  1519.  
  1520.   delay(200);
  1521.  
  1522.   handleCursorIndex(true, numAODSettings);
  1523.  
  1524.   while(runSettings){
  1525.     if (!ignoreButtons) { cursor = handleCursorIndex(false, numAODSettings); }
  1526.  
  1527.     if (clicked == 2 || ignoreButtons) {
  1528.       if (AODSettingsText[cursor].equals("AOD")) {
  1529.         useAOD  = !useAOD;
  1530.         forceRefresh = true;
  1531.         clicked = -1;
  1532.       }
  1533.       if(AODSettingsText[cursor].equals("Clock mode")){
  1534.         altAOD = !altAOD;
  1535.         forceRefresh = true;
  1536.         clicked = -1;
  1537.       }
  1538.       if(AODSettingsText[cursor].equals("Seconds hand")){
  1539.         showSeconds = !showSeconds;
  1540.         forceRefresh = true;
  1541.         clicked = -1;
  1542.       }
  1543.       if(AODSettingsText[cursor].equals("AOD brightness")){
  1544.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &AODBrightness, 1, 1, 10);
  1545.       }
  1546.       if(AODSettingsText[cursor].equals("Battery icon")){
  1547.         showBattery = !showBattery;
  1548.         forceRefresh = true;
  1549.         clicked = -1;
  1550.       }
  1551.       if(AODSettingsText[cursor].equals("Seperate palette")){
  1552.         useAODPalette = !useAODPalette;
  1553.         forceRefresh = true;
  1554.         clicked = -1;
  1555.       }
  1556.       if(AODSettingsText[cursor].equals("AOD palette")){
  1557.         clockPallete++;
  1558.         if (clockPallete > numPalletes - 1) {
  1559.           clockPallete = 0;
  1560.         }
  1561.         forceRefresh = true;
  1562.         clicked = -1;
  1563.       }
  1564.       if(AODSettingsText[cursor].equals("Back")){
  1565.         clicked = -1;
  1566.         return;
  1567.       }
  1568.     }
  1569.  
  1570.     if (cursor != oldCursor || forceRefresh) {
  1571.       oldCursor = cursor;
  1572.       forceRefresh = false;
  1573.       spr.fillSprite(TFT_BLACK);
  1574.  
  1575.       drawSettingOptions(AODArrayPtr, numAODSettings, cursor);
  1576.  
  1577.       (useAOD) ? drawText("ON", 20, RIGHT, TFT_GREEN) : drawText("OFF", 20, RIGHT, TFT_RED);
  1578.       (altAOD) ? drawText("Analog", 40, RIGHT, TFT_WHITE) : drawText("Digital", 40, RIGHT, TFT_WHITE);
  1579.       (showSeconds) ? drawText("ON", 60, RIGHT, TFT_GREEN) : drawText("OFF", 60, RIGHT, TFT_RED);
  1580.       drawText(String(AODBrightness), 80, RIGHT, TFT_WHITE);
  1581.       (showBattery) ? drawText("ON", 100, RIGHT, TFT_GREEN) : drawText("OFF", 100, RIGHT, TFT_RED);
  1582.  
  1583.       (useAODPalette) ? drawText("ON", 120, RIGHT, TFT_GREEN) : drawText("OFF", 120, RIGHT, TFT_RED);
  1584.       drawText(palleteNames[clockPallete], 140, RIGHT, TFT_WHITE);
  1585.  
  1586.       spr.pushSprite(0, 0);
  1587.     }
  1588.   }
  1589. }
  1590.  
  1591. void GPSSettings() {
  1592.   tft.setRotation(landscape);
  1593.  
  1594.   byte cursor = 10;
  1595.   byte oldCursor = 1;
  1596.   bool forceRefresh = false;
  1597.   bool ignoreButtons = false;
  1598.   delay(200);
  1599.   cursor = handleCursorIndex(true, numGPSSettings);
  1600.  
  1601.   while (runSettings) {
  1602.     if (!ignoreButtons) { cursor = handleCursorIndex(false, numGPSSettings); }
  1603.  
  1604.     if (clicked == 2 || ignoreButtons) {
  1605.       if (GPSSettingsText[cursor].equals("Speed metric")) {
  1606.         clicked = -1;
  1607.         useKmS = !useKmS;
  1608.         forceRefresh = true;
  1609.       }
  1610.       if (GPSSettingsText[cursor].equals("UTC offset")) {
  1611.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &UTCOffset, 0, 1, 14);
  1612.       }
  1613.       if (GPSSettingsText[cursor].equals("Minimum speed")) {
  1614.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &minCardinalSpeed, 0, 1, 100);
  1615.       }
  1616.       if(GPSSettingsText[cursor].equals("Precision")){
  1617.         clicked = -1;
  1618.         usePrecision = !usePrecision;
  1619.         forceRefresh = true;
  1620.       }
  1621.       if (GPSSettingsText[cursor].equals("Back")) {
  1622.         clicked = -1;
  1623.         return;
  1624.       }
  1625.     }
  1626.  
  1627.     if (cursor != oldCursor || forceRefresh) {
  1628.       oldCursor = cursor;
  1629.       forceRefresh = false;
  1630.       spr.fillSprite(TFT_BLACK);
  1631.  
  1632.       drawSettingOptions(gpsArrayPtr, numGPSSettings, cursor);
  1633.  
  1634.       (useKmS) ? drawText("km/h", 20, RIGHT, TFT_WHITE) : drawText("m/s", 20, RIGHT, TFT_WHITE);
  1635.       (UTCOffset > 0) ? drawText("+" + String(UTCOffset), 40, RIGHT, TFT_WHITE) : drawText(String(UTCOffset), 40, RIGHT, TFT_WHITE);
  1636.       drawText(String(minCardinalSpeed) + "km/h", 60, RIGHT, TFT_WHITE);
  1637.       //  (usePrecision) ? drawText("HIGH", 80, RIGHT, TFT_WHITE) : drawText("LOW", 80, RIGHT, TFT_WHITE);
  1638.  
  1639.       // (randomiseData) ? drawText("TRUE", 80, RIGHT, TFT_GREEN) : drawText("FALSE", 0, RIGHT, TFT_RED);
  1640.      
  1641.       spr.pushSprite(0, 0);
  1642.     }
  1643.     delay(1);
  1644.   }
  1645. }
  1646.  
  1647. void loggingSettings() {
  1648.   tft.setRotation(landscape);
  1649.  
  1650.   byte cursor = 10;
  1651.   byte oldCursor = 1;
  1652.   bool forceRefresh = false;
  1653.   bool ignoreButtons = false;
  1654.  
  1655.   delay(200);
  1656.  
  1657.   cursor = handleCursorIndex(true, numLoggingSettings);
  1658.  
  1659.   while (runSettings) {
  1660.     if (!ignoreButtons) { cursor = handleCursorIndex(false, numLoggingSettings); }
  1661.  
  1662.     if (clicked == 2 || ignoreButtons) {
  1663.       if (loggingSettingsText[cursor].equals("Log position")) {
  1664.         clicked = -1;
  1665.         logGPS = !logGPS;
  1666.         forceRefresh = true;
  1667.         if(logGPS) { logData("/GPSLog.txt", ""); }  // if logging was enabled, insert a NL
  1668.       }
  1669.       if (loggingSettingsText[cursor].equals("Log speed")) {
  1670.         clicked = -1;
  1671.         logSPD = !logSPD;
  1672.         forceRefresh = true;
  1673.         if(logSPD) { logData("/SPDLog.txt", ""); }  // if logging was enabled, insert a NL
  1674.       }
  1675.       if(loggingSettingsText[cursor].equals("Logging freq")) {
  1676.         handleSettingNumbers(&ignoreButtons, &forceRefresh, &logGPSfreq, 1, 1, 300);
  1677.       }
  1678.       if (loggingSettingsText[cursor].equals("Back")) {
  1679.         clicked = -1;
  1680.         return;
  1681.       }
  1682.     }
  1683.  
  1684.     if (cursor != oldCursor || forceRefresh) {
  1685.       oldCursor = cursor;
  1686.       forceRefresh = false;
  1687.       spr.fillSprite(TFT_BLACK);
  1688.  
  1689.       drawSettingOptions(loggingArrayPtr, numLoggingSettings, cursor);
  1690.  
  1691.       (logGPS) ? drawText("ON", 20, RIGHT, TFT_GREEN) : drawText("OFF", 20, RIGHT, TFT_RED);
  1692.       (logSPD) ? drawText("ON", 40, RIGHT, TFT_GREEN) : drawText("OFF", 40, RIGHT, TFT_RED);
  1693.  
  1694.       drawText(String(logGPSfreq) + "s", 60, RIGHT, TFT_WHITE);
  1695.       spr.pushSprite(0, 0);
  1696.     }
  1697.   }
  1698. }
  1699. // --------------- SETTINGS - HELPERS ---------------
  1700.  
  1701. uint16_t SNRToColor(byte SNRIn){
  1702.   uint16_t color;
  1703.  
  1704.   if(!SNRPallete){
  1705.     if(SNRIn >= 50) { color = barColors[0]; }
  1706.     else if(SNRIn >= 30) { color = barColors[1]; }
  1707.     else if(SNRIn >= 20) { color = barColors[2]; }
  1708.     else if(SNRIn >= 10) { color = barColors[3]; }
  1709.     else { color = barColors[4]; }
  1710.   }
  1711.   else {
  1712.     if(SNRIn >= 30) { color = palletes[pallete][1]; }
  1713.     else { color = palletes[pallete][0]; }
  1714.   }
  1715.   return color;
  1716. }
  1717.  
  1718. void setSettings() {
  1719.   // save power settings
  1720.   preferences.putBool("sleep", autoSleep);
  1721.   preferences.putInt("sleepTime", sleepTimer);
  1722.  
  1723.   // save screen settings
  1724.   preferences.putBool("screenSleep", autoScreenOff);
  1725.   preferences.putInt("screenTime", screenTimer);
  1726.   preferences.putInt("screenPower", screenBrightness);
  1727.   preferences.putBool("screenOri", landscape);
  1728.   preferences.putBool("SNRCMode", SNRPallete);
  1729.  
  1730.   // save pallete settings
  1731.   preferences.putInt("currentPallete", pallete);
  1732.  
  1733.   // save GPS settings
  1734.   preferences.putBool("speedUnit", useKmS);
  1735.   preferences.putInt("timeOffset", UTCOffset);
  1736.   preferences.putInt("cardinalSpeed", minCardinalSpeed);
  1737.   preferences.putBool("prec", usePrecision);
  1738.  
  1739.   // save logging settings
  1740.   preferences.putBool("GPSLogging", logGPS);
  1741.   preferences.putBool("SPDLogging", logSPD);
  1742.   preferences.putInt("LogFreq", logGPSfreq);
  1743.  
  1744.   // save AOD settings
  1745.   preferences.putBool("AODEnabled", useAOD);
  1746.   preferences.putBool("clockMode", altAOD);
  1747.   preferences.putBool("secondsHand", showSeconds);
  1748.   preferences.putBool("batteryIcon", showBattery);
  1749.   preferences.putInt("AODBright", AODBrightness);
  1750.   preferences.putBool("useCustomPallete", useAODPalette);
  1751.   preferences.putInt("cPallete", clockPallete);
  1752. }
  1753.  
  1754. void getSettings() {
  1755.   // get power settings
  1756.   autoSleep = preferences.getBool("sleep", false);
  1757.   sleepTimer = preferences.getInt("sleepTime", 60);
  1758.  
  1759.   // get screen settings
  1760.   autoScreenOff = preferences.getBool("screenSleep", false);
  1761.   screenTimer = preferences.getInt("screenTime", 60);
  1762.   screenBrightness = preferences.getInt("screenPower", 10);
  1763.   landscape = preferences.getBool("screenOri", true);
  1764.   SNRPallete = preferences.getBool("SNRCMode", false);
  1765.  
  1766.   // get pallete settings
  1767.   pallete = preferences.getInt("currentPallete", 0);
  1768.  
  1769.   // get GPS settings
  1770.   useKmS = preferences.getBool("speedUnit", false);
  1771.   UTCOffset = preferences.getInt("timeOffset", 2);
  1772.   minCardinalSpeed = preferences.getInt("cardinalSpeed", 5);
  1773.   usePrecision = preferences.getBool("prec", false);
  1774.   // get logging settings
  1775.   logGPS = preferences.getBool("GPSLogging", false);
  1776.   logSPD = preferences.getBool("SPDLogging", false);
  1777.   logGPSfreq = preferences.getInt("logFreq", 10);
  1778.  
  1779.   // get AOD settings
  1780.   useAOD = preferences.getBool("AODEnabled", true);
  1781.   altAOD = preferences.getBool("clockMode", true);
  1782.   showSeconds = preferences.getBool("secondsHand", false);
  1783.   showBattery = preferences.getBool("batteryIcon", false);
  1784.   AODBrightness = preferences.getInt("AODBright", 1);
  1785.   useAODPalette = preferences.getBool("useCustomPallete", true);
  1786.   clockPallete = preferences.getInt("cPallete", 0);
  1787. }
  1788.  
  1789. void handleSettingNumbers(bool* ignoreButtonBool, bool* forceUpdateBool, int* longToChange, int minVal, int increment, unsigned long maxVal) {
  1790.   if (clicked == 2 && !*ignoreButtonBool) {
  1791.     *ignoreButtonBool = true;
  1792.     clicked = -1;
  1793.   }
  1794.   if (clicked == 1) {
  1795.     if (*longToChange - increment >= minVal) {
  1796.       *longToChange -= increment;
  1797.       *forceUpdateBool = true;
  1798.     }
  1799.     clicked = -1;
  1800.   }
  1801.   if (clicked == 2 && ignoreButtonBool) {
  1802.     *ignoreButtonBool = false;
  1803.     clicked = -1;
  1804.   }
  1805.   if (clicked == 3) {
  1806.     if (*longToChange + increment <= maxVal) {
  1807.       *longToChange += increment;
  1808.       clicked = -1;
  1809.       *forceUpdateBool = true;
  1810.     }
  1811.   }
  1812. }
  1813.  
  1814. void drawSettingOptions(const String* arrayPointer, int arrayLen, int cursor) {
  1815.  
  1816.   u8f.setFont(fontSpecial);
  1817.   for (int i = 0; i < arrayLen; i++) {
  1818.     if (cursor == i) {
  1819.       drawText(arrayPointer[i], (20 * i) + 20, LEFT, palletes[pallete][1]);
  1820.       spr.drawRect(0, (i*20)+4, screenWidth, 20, palletes[pallete][1]);
  1821.     }
  1822.     else {
  1823.       drawText(arrayPointer[i], (20 * i) + 20, LEFT, TFT_WHITE);
  1824.     }
  1825.   }
  1826. }
  1827.  
  1828. byte handleCursorIndex(bool resetIndex, byte maxSetting) {
  1829.   static byte cursorIndex = 0;
  1830.  
  1831.   if(landscape){  // invert button dir when in landscape mode
  1832.     switch (clicked) {
  1833.  
  1834.       case 1:
  1835.         if(cursorIndex < maxSetting - 1) { cursorIndex++; }
  1836.         clicked = -1;
  1837.         break;
  1838.      
  1839.       case 3:
  1840.         if(cursorIndex > 0) { cursorIndex--; }
  1841.         clicked = -1;
  1842.         break;
  1843.     }
  1844.   }
  1845.   else{
  1846.     switch (clicked) {
  1847.  
  1848.       case 1:  // LEFT BUTTON
  1849.         if (cursorIndex > 0) { cursorIndex--; }
  1850.         clicked = -1;
  1851.         break;
  1852.  
  1853.       case 3:  // RIGHT BUTTON
  1854.         if (cursorIndex < maxSetting - 1) { cursorIndex++; }
  1855.         clicked = -1;
  1856.         break;
  1857.     }
  1858.   }
  1859.  
  1860.   if (resetIndex) { cursorIndex = 0; }
  1861.   return cursorIndex;
  1862. }
  1863.  
  1864. // --------------- BUTTONS ------------------
  1865. void checkButtons(void* pvParameters) {
  1866.  
  1867.   byte bLeft = 38;
  1868.   byte bCentre = 37;
  1869.   byte bRight = 39;
  1870.  
  1871.   pinMode(bLeft, INPUT);
  1872.   pinMode(bCentre, INPUT);
  1873.   pinMode(bRight, INPUT);
  1874.  
  1875.   bool pressedLeft = false;
  1876.   bool pressedCentre = false;
  1877.   bool pressedRight = false;
  1878.  
  1879.   gpsPort.begin(9600, SERIAL_8N1, 35, 33);
  1880.  
  1881.   while (gpsPort.available()) { delay(1); }
  1882.   gpsPort.println("$PCAS01,5*19");
  1883.  
  1884.  
  1885.   Serial.println("Command sent!");
  1886.   delay(200);
  1887.  
  1888.   gpsPort.flush();
  1889.   gpsPort.end();
  1890.   delay(1000);
  1891.   gpsPort.begin(115200, SERIAL_8N1, 35, 33);
  1892.   gpsPort.println("$PCAS02,100*1E");
  1893.  
  1894.   Serial.println("Core 0 RDY!");
  1895.   bootRdy = true;
  1896.   unsigned long lastLog;
  1897.   for (;;) {
  1898.  
  1899.     checkGPS();
  1900.  
  1901.     if (digitalRead(bLeft) && !pressedLeft) {
  1902.       pressedLeft = true;
  1903.       if(screenOn) { clicked = 1; }
  1904.       buttonTasks();
  1905.     } else if (!digitalRead(bLeft) && pressedLeft) {
  1906.       pressedLeft = false;
  1907.     }
  1908.  
  1909.     if (digitalRead(bCentre) && !pressedCentre) {
  1910.       pressedCentre = true;
  1911.       if(screenOn) { clicked = 2; }
  1912.       buttonTasks();
  1913.     } else if (!digitalRead(bCentre) && pressedCentre) {
  1914.       pressedCentre = false;
  1915.     }
  1916.  
  1917.     if (digitalRead(bRight) && !pressedRight) {
  1918.       pressedRight = true;
  1919.       if(screenOn) { clicked = 3; }
  1920.       buttonTasks();
  1921.     } else if (!digitalRead(bRight) && pressedRight) {
  1922.       pressedRight = false;
  1923.     }
  1924.     delay(1);
  1925.   }
  1926. }
  1927.  
  1928. // tasks that all 3 buttons need to run when pressed
  1929. void buttonTasks() {
  1930.   lastPressed = millis();
  1931.   setScreenBrightness(screenBrightness);
  1932. }
  1933.  
  1934. void checkGPS() {
  1935.   static unsigned long lastLogging;
  1936.  
  1937.   while (gps.available( gpsPort )) {
  1938.     fix = gps.read();
  1939.  
  1940.     gpsData.fixStatus = fix.status;
  1941.  
  1942.     // populate struct with GPS data
  1943.  
  1944.     //  lat/lon/alt
  1945.     if(fix.valid.location){
  1946.       gpsData.lat = fix.latitude();
  1947.       gpsData.lon = fix.longitude();
  1948.  
  1949.       gpsData.latP = fix.latitudeL();
  1950.       gpsData.lonP = fix.longitudeL();
  1951.  
  1952.       gpsData.latDMS = fix.latitudeDMS.degrees;
  1953.       gpsData.lonDMS = fix.longitudeDMS.degrees;
  1954.     }
  1955.     else{
  1956.       gpsData.lat = -1;
  1957.       gpsData.lon = -1;
  1958.  
  1959.       gpsData.lonP = -1;
  1960.       gpsData.latP = -1;
  1961.  
  1962.       gpsData.latDMS = -1;
  1963.       gpsData.lonDMS = -1;
  1964.     }
  1965.  
  1966.     (fix.valid.altitude) ? gpsData.alt = fix.altitude() : gpsData.alt = -1;
  1967.  
  1968.     //  speed/hdg
  1969.     if(fix.valid.speed) {
  1970.       gpsData.spd = fix.speed_kph();          //  km/h
  1971.       gpsData.spdS = fix.speed_kph() / 3.6;   //  m/s
  1972.     }
  1973.     else{
  1974.       gpsData.spd = -1;
  1975.       gpsData.spdS = -1;
  1976.     }
  1977.  
  1978.     (fix.valid.heading) ? gpsData.hdg = fix.heading() : gpsData.hdg = -1;
  1979.     //  if(gpsData.spd > 0 && !useKmS){ gpsData.spd /= 3,6; }
  1980.  
  1981.    // if(fix.valid.velned){
  1982.       fix.calculateNorthAndEastVelocityFromSpeedAndHeading();
  1983.       if(useKmS){
  1984.         gpsData.velN = fix.velocity_north / 27,778;
  1985.         gpsData.velE = fix.velocity_east / 27,778;
  1986.         gpsData.velD = fix.velocity_down / 27,778;
  1987.       }
  1988.       else{
  1989.         gpsData.velN = fix.velocity_north / 100;
  1990.         gpsData.velE = fix.velocity_east / 100;
  1991.         gpsData.velD = fix.velocity_down / 100;    
  1992.       }
  1993.   //  }
  1994.     // else{
  1995.     //   gpsData.velN = -1;
  1996.     //   gpsData.velE = -1;
  1997.     //   gpsData.velD = -1;
  1998.     // }
  1999.  
  2000.     // (fix.valid.lat_err) ? gpsData.latErr = fix.lat_err() : gpsData.latErr = -1;
  2001.     // (fix.valid.lon_err) ? gpsData.lonErr = fix.lon_err() : gpsData.lonErr = -1;
  2002.     // (fix.valid.alt_err) ? gpsData.altErr = fix.alt_err() : gpsData.altErr = -1;
  2003.  
  2004.     // update time
  2005.     if(fix.valid.time){
  2006.  
  2007.       //  add UTC offset to date/time
  2008.       NeoGPS::clock_t seconds = fix.dateTime;
  2009.  
  2010.       seconds += (UTCOffset*3600);
  2011.       fix.dateTime = seconds;
  2012.  
  2013.       int GPShr = fix.dateTime.hours;
  2014.       //GPShr %= 24;
  2015.  
  2016.       String GPStimeTemp;
  2017.  
  2018.       if(GPShr < 10) { GPStimeTemp += "0"; } // add leading 0 if needed
  2019.       GPStimeTemp += String(GPShr) + ":";
  2020.  
  2021.       if(fix.dateTime.minutes < 10) { GPStimeTemp += "0"; }
  2022.       GPStimeTemp += String(fix.dateTime.minutes);
  2023.  
  2024.       gpsData.time = GPStimeTemp;
  2025.       gpsData.timeSeconds = fix.dateTime.seconds;
  2026.     }
  2027.     else { gpsData.time = "inv"; }
  2028.    
  2029.     if(fix.valid.date){
  2030.       gpsData.GPSmonth = fix.dateTime.month;
  2031.       gpsData.GPSday = fix.dateTime.date;
  2032.       gpsData.GPSyear = fix.dateTime.year;
  2033.       //gpsData.GPSdow = fix.dateTime.day;
  2034.     }
  2035.     else{
  2036.       gpsData.GPSmonth = -1;
  2037.       gpsData.GPSdow = -1;
  2038.       gpsData.GPSyear = -1;
  2039.       gpsData.GPSdow = -1;
  2040.     }
  2041.  
  2042.     // get ( tracked ) sat count
  2043.     if (fix.valid.satellites) {
  2044.       gpsData.totalSats = fix.satellites; // + 1; //  seems to be an extra entry in last position of array?
  2045.  
  2046.       if(fix.satellites > 0){
  2047.         byte trackedNum = 0;
  2048.  
  2049.         for(int i = 0; i < fix.satellites; i++){
  2050.           if(gps.satellites[i].tracked) { trackedNum++; }
  2051.         }
  2052.         gpsData.sats = trackedNum;
  2053.       }
  2054.     }
  2055.     else {
  2056.       gpsData.sats = -1;
  2057.       gpsData.totalSats = -1;
  2058.     }
  2059.  
  2060.     // calculate avg SNR if fix available
  2061.     if(gpsData.sats > 0){
  2062.       byte inactive = 0;
  2063.       int tempAvgSNR = 0;
  2064.  
  2065.       for(int i = 0; i < gpsData.totalSats; i++){
  2066.         byte snr = gps.satellites[i].snr;
  2067.         (snr) ? tempAvgSNR += snr : inactive++;
  2068.       }
  2069.       if(gpsData.totalSats - inactive > 0){
  2070.         tempAvgSNR /= (gpsData.totalSats - inactive);
  2071.         gpsData.SNR = tempAvgSNR;
  2072.       }
  2073.      
  2074.     }
  2075.     else { gpsData.SNR = -1; }
  2076.  
  2077.     // log data if setting calls for it
  2078.     if(millis() - lastLogging > (logGPSfreq*1000)){
  2079.       lastLogging = millis();
  2080.  
  2081.       if(logGPS){
  2082.         String log;
  2083.  
  2084.         if (fix.valid.location) {
  2085.           log += "[" + String(fix.latitude(), 6) + "," + String(fix.longitude(), 6) + "],";
  2086.         }
  2087.         else{ log += "[NO VALID LOCATION],"; }
  2088.  
  2089.         if (fix.valid.altitude) {
  2090.           log += "[" + String(fix.altitude()) + "],";
  2091.         }
  2092.         else { log += "[NO VALID ALTITUDE],"; }
  2093.  
  2094.         if(fix.valid.time){
  2095.           log += "[" + gpsData.time + ":" + String(fix.dateTime.seconds) + "]";
  2096.         }
  2097.         else { log += "[NO VALID TIME]"; }
  2098.  
  2099.         logData("/GPSLog.txt", log);
  2100.       }
  2101.  
  2102.       if(logSPD){
  2103.         String log;
  2104.  
  2105.         if (fix.valid.speed) {
  2106.           log += "[" + String(fix.speed_kph()) + "],";
  2107.         }
  2108.         else { log += "[NO VALID SPEED],"; }
  2109.  
  2110.         if (fix.valid.heading) {
  2111.           log += "[" + String(fix.heading()) + "],";
  2112.         }
  2113.         else{ log += "[NO VALID HDG],"; }
  2114.  
  2115.         if(fix.valid.time){
  2116.           log += "[" + gpsData.time + ":" + String(fix.dateTime.seconds) + "]";
  2117.         }
  2118.         else { log += "[NO VALID TIME]"; }
  2119.  
  2120.         logData("/SPDLog.txt", log);
  2121.       }
  2122.     }
  2123.   }
  2124. }
  2125.  
  2126. // --------------- SD ------------------
  2127. void logData(const char* path, String message) {
  2128.   if (!hasSD) { return; }
  2129.  
  2130.   File file = SD.open(path, FILE_APPEND);
  2131.  
  2132.   file.println(message);
  2133.   file.close();
  2134. }
  2135.  
  2136. // --------------- TEXT ------------------
  2137.  
  2138. // only passes right x/y to function below
  2139. void drawText(String text, int y, directions dir, uint16_t color) {
  2140.   u8f.setForegroundColor(color);
  2141.   int textW = u8f.getUTF8Width(text.c_str());
  2142.  
  2143.   switch (dir) {
  2144.  
  2145.     case LEFT:
  2146.       drawText(text, 0 + paddingL, y, color);
  2147.       break;
  2148.  
  2149.     case RIGHT:
  2150.       drawText(text, (screenWidth - textW) - paddingR, y, color);
  2151.       break;
  2152.  
  2153.     case CENTER:  //  TODO: make offset toggleable
  2154.       (landscape) ? drawText(text, (screenWidth / 2) + 15 - (textW / 2), y, color) : drawText(text, (screenWidth / 2) - textW / 2, y, color);
  2155.       break;
  2156.    
  2157.     case CENTER_NO:
  2158.       drawText(text, (screenWidth / 2) - (textW / 2), y, color);
  2159.       break;
  2160.   }
  2161. }
  2162.  
  2163. // draws the actual text
  2164. void drawText(String text, int x, int y, uint16_t color) {
  2165.   u8f.setForegroundColor(color);
  2166.   u8f.setCursor(x, y);
  2167.  
  2168.   int textW = u8f.getUTF8Width(text.c_str());
  2169.   int textH = u8f.getFontAscent() - u8f.getFontDescent();
  2170.  
  2171.   //tft.fillRect(x - clearPaddingL, y - u8f.getFontAscent() - 1, textW + (clearPaddingR * 2), textH, TFT_BLACK);
  2172.   u8f.print(text);
  2173. }
  2174.  
  2175. // 0 - 10 ( 0 - 255)
  2176. void setScreenBrightness(int brightness) {
  2177.   int bValue = map(brightness, 0, 10, 0, 255);
  2178.  
  2179.   if (brightness == 0) {
  2180.     tft.fillScreen(TFT_BLACK);
  2181.     analogWrite(TFT_BL, 0);
  2182.   } else {
  2183.     //ledcWrite(0, bValue);
  2184.     analogWrite(TFT_BL, bValue);
  2185.   }
  2186. }
  2187.  
  2188. // --------------- GPS ------------------
  2189. void sendGPSCommand(GPSspeed spd) {
  2190.   while (Serial2.available()) { delay(1); }  //wait for GPS to be silent
  2191.   Serial2.println(GPScommands[spd][0]);      //send command
  2192.   Serial.println("Command sent: " + GPScommands[spd][0]);
  2193. }
  2194.  
  2195. // --------------- IP5306 ------------------
  2196. void ip5306_set_bits(uint8_t reg, uint8_t index, uint8_t bits, uint8_t value) {
  2197.   uint8_t mask = (1 << bits) - 1;
  2198.   int v = ip5306_get_reg(reg);
  2199.   if (v < 0) {
  2200.     Serial.printf("ip5306_get_reg fail: 0x%02x\n", reg);
  2201.     return;
  2202.   }
  2203.   v &= ~(mask << index);
  2204.   v |= ((value & mask) << index);
  2205.   if (ip5306_set_reg(reg, v)) {
  2206.     Serial.printf("ip5306_set_bits fail: 0x%02x\n", reg);
  2207.   }
  2208. }
  2209.  
  2210. uint8_t ip5306_get_bits(uint8_t reg, uint8_t index, uint8_t bits) {
  2211.   int value = ip5306_get_reg(reg);
  2212.   if (value < 0) {
  2213.     Serial.printf("ip5306_get_bits fail: 0x%02x\n", reg);
  2214.     return 0;
  2215.   }
  2216.   return (value >> index) & ((1 << bits) - 1);
  2217. }
  2218.  
  2219. int ip5306_get_reg(uint8_t reg) {
  2220.   Wire.beginTransmission(0x75);
  2221.   Wire.write(reg);
  2222.   if (Wire.endTransmission(false) == 0 && Wire.requestFrom(0x75, 1)) {
  2223.     return Wire.read();
  2224.   }
  2225.   return -1;
  2226. }
  2227.  
  2228. int ip5306_set_reg(uint8_t reg, uint8_t value) {
  2229.   Wire.beginTransmission(0x75);
  2230.   Wire.write(reg);
  2231.   Wire.write(value);
  2232.   if (Wire.endTransmission(true) == 0) {
  2233.     return 0;
  2234.   }
  2235.   return -1;
  2236. }
  2237.  
  2238. // returns battery lvl in %
  2239. byte getBatLevel() {
  2240.   static byte batLvl = 0;
  2241.   static unsigned long last = 0;
  2242.  
  2243.   //  only check status once per 5s
  2244.   if(millis() - last > 5000){
  2245.     last = millis();
  2246.     uint8_t bytes = (~ip5306_get_bits(IP5306_REG_READ_4, 4, 4)) & 0x0F;
  2247.  
  2248.     batLvl = 0; //  reset before next reading
  2249.  
  2250.     if (bytes & 0x01) { batLvl += 25; }
  2251.     if (bytes & 0x02) { batLvl += 25; }
  2252.     if (bytes & 0x04) { batLvl += 25; }
  2253.     if (bytes & 0x08) { batLvl += 25; }
  2254.   }
  2255.  
  2256.   return batLvl;
  2257. }
  2258.  
  2259. bool getCharging(){
  2260.   static bool charging = false;
  2261.   static unsigned long last = 0;
  2262.  
  2263.   //  only check status once per 5s
  2264.   if(millis() - last > 5000){
  2265.     last = millis();
  2266.  
  2267.     // if we're powered from VCC ( plugged in ) and the battery isn't full, we're charging
  2268.     (ip5306_get_bits(IP5306_REG_READ_0, 3, 1) && !ip5306_get_bits(IP5306_REG_READ_1, 3, 1)) ? charging = true : charging = false;
  2269.   }
  2270.   return charging;
  2271. }
  2272.  
  2273. float mapF(float x, float in_min, float in_max, float out_min, float out_max){
  2274.   return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
  2275. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement