Cristian369 / MVVProject

0 stars 0 forks source link

Final (not working) code from Reddit discussion #1

Open schnoog opened 10 months ago

schnoog commented 10 months ago

Hi, that's my final code to our discussion https://www.reddit.com/r/arduino/comments/18dltx3/comment/kcjwlqg/?context=3

#define SETPINSHERE
#include <Arduino.h>
//Quellen: 
// https://github.com/Bodmer/TFT_eSPI/blob/master/examples/320%20x%20240/Free_Font_Demo/Free_Font_Demo.ino
// https://github.com/Xinyuan-LilyGO/TTGO-T-Display
#include <esp_task_wdt.h>
#include <WiFi.h>
#include <HTTPClient.h>
//mvg api timestamp in ms needs long long
#define ARDUINOJSON_USE_LONG_LONG 1
//geops api timestamp in ms needs double
#define ARDUINOJSON_USE_DOUBLE 1
//geops api uses unicode characters
#define ARDUINOJSON_DECODE_UNICODE 1
#include <ArduinoJson.h>
#include <Arduino.h>
#include "Wire.h"
#include "time.h"
#include <ArduinoWebsockets.h>
#include <list>
#include <iterator>
using namespace std;
#include <TFT_eSPI.h>
#include <SPI.h>
#include "WiFi.h"
#include <Wire.h>
#include "Free_Fonts.h"
#include "Free_Sans.h"
#include <Button2.h>

#define MAX_INCLUDE_TYPE 10
#define MAX_INCLUDE_LINE 10
#define MAX_EXCLUDE_DESTINATION 10
#define MAX_JSON_DOCUMENT 20000
#define MAX_DEPARTURE_LIST_LENGTH 100

#ifndef TFT_DISPOFF
#define TFT_DISPOFF 0x28
#endif

#ifndef TFT_SLPIN
#define TFT_SLPIN   0x10
#endif

#ifdef SETPINSHERE
#define TFT_MOSI            19    // real physical pin on esp32 is 23
#define TFT_SCLK            18    // real physical pin is called SCK and is located at 18
#define TFT_CS              5     // real physical pin 15 
#define TFT_DC              16    // real physical pin 2
#define TFT_RST             23    // real physical pin 4
#endif
// LCD is on 3v3, VCC is on VIN, and GND is on GND of course.
// I have no idea how tf this seems to be making my display work but it does...
// Also just to note that I tried removing every cable one by one and see if it changes the screen output. 
// Removing any of these cables leads to display not working. The only useless cable turned out to be MISO
// Link to display pinouts for convenience: https://thesolaruniverse.files.wordpress.com/2021/03/092_figure_04_96_dpi.png
// Link to ESP32 pinout: https://m.media-amazon.com/images/I/61g5+xqvYhL._AC_SL1500_.jpg

#define TFT_BL          0  // Display backlight control pin
#define ADC_EN          14
#define ADC_PIN         34
#define BUTTON_1        17 // <- changed
#define BUTTON_2        0

enum types {
  mvg_api,
  geops_api
};

typedef struct {
  const char *wifi_name;
  const char *wifi_pass;
  const enum types type;
  const char *bahnhof;
  const char *include_type[MAX_INCLUDE_TYPE];
  const char *include_line[MAX_INCLUDE_LINE];
  const char *exclude_destinations[MAX_EXCLUDE_DESTINATION];
} Config;
#include "config.h"

typedef struct {
  unsigned long long aimed_time;
  unsigned long long estimated_time;
  String line;
  String destination;
  int platform;
  int wagon;
} Departure;

list <Departure> departure_list;

websockets::WebsocketsClient client;

TFT_eSPI tft = TFT_eSPI(240, 320); // Invoke custom library
TFT_eSprite img = TFT_eSprite(&tft);

Button2 btn1(BUTTON_1);
Button2 btn2(BUTTON_2);

StaticJsonDocument<MAX_JSON_DOCUMENT> doc;

const char* ntpServer = "pool.ntp.org";
const long  gmtOffset_sec = 3600;
const int   daylightOffset_sec = 3600;
const int   number_of_configs = sizeof(configs) / sizeof(*configs);
int config_number = -1;

int connect_wifi();
void handle_mvg_api(Config config);
void init_geops_api(Config config);
void handle_geops_api(Config config);
void ping_geops_api();

void button_init()
{
    btn1.setPressedHandler([](Button2 & b) {
        Serial.println("btn2 pressed");
        int r = digitalRead(TFT_BL);
        tft.fillScreen(TFT_BLACK);
        tft.setTextColor(TFT_GREEN, TFT_BLACK);
        tft.setTextDatum(MC_DATUM);
        tft.drawString("Shutting down...",  tft.width() / 2, tft.height() / 2 );
        delay(3000);
        digitalWrite(TFT_BL, !r);

        tft.writecommand(TFT_DISPOFF);
        tft.writecommand(TFT_SLPIN); 
        esp_sleep_enable_ext1_wakeup(GPIO_SEL_35, ESP_EXT1_WAKEUP_ALL_LOW);
        esp_deep_sleep_start();
        delay(100);
    });

    btn2.setPressedHandler([](Button2 & b) {
        Serial.println("btn2 pressed");
    });
}

void button_loop()
{
    btn1.loop();
    btn2.loop();
}

//! Long time delay, it is recommended to use shallow sleep, which can effectively reduce the current consumption
void espDelay(int ms)
{   
    esp_sleep_enable_timer_wakeup(ms * 1000);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH,ESP_PD_OPTION_ON);
    esp_light_sleep_start();
}

void drawTopLine() {
    img.setTextDatum(TL_DATUM);
    img.fillRect(0, 0, 240, 16, TFT_YELLOW);  // Increase the height of the top bar
    img.setTextFont(GLCD);
    img.setTextColor(0x005, TFT_YELLOW);
    img.drawString("Linie", 1, 2);
    img.drawString("Ziel", 33, 2);
    img.drawString("Gleis", 128, 2);
    img.drawString("A", 169, 2);
    img.drawString("B", 184, 2);
    img.drawString("C", 200, 2);
    img.drawString("Min", 220, 2);
    img.pushSprite(0, 0);
}

void setup_display() {
    tft.init();
    tft.setRotation(3);
    tft.fillScreen(0x005);

    if (TFT_BL > 0) {
        pinMode(TFT_BL, OUTPUT);
        digitalWrite(TFT_BL, HIGH);
    }

    img.createSprite(240, 320);  // Change the sprite size to match the new display dimensions
    img.fillSprite(0x005);
}

void drawDeparture(int display_line, String line, String destination, int track, int wagon, int minutes) {
    int line_height = 15;
    int offset = 9;
    int offest_string = 7;
    int y_display = display_line * line_height + offset;
    int y_display_string = display_line * line_height + offest_string;

    // Declare rect_color and font_color here
    uint16_t rect_color = 0x005;
    uint16_t font_color = TFT_WHITE;

    //line
    if (line[0] == 'S') // Sbahn
    {
        switch (line[1])
        {
        case '1':
            rect_color = 0x05FF;
            break;

        case '2':
            rect_color = 0x05F0;
            break;

        case '3':
            rect_color = TFT_MAGENTA;
            break;

        case '4':
            rect_color = 0xE800;
            break;

        case '6':
            rect_color = 0x03E0;
            break;

        case '7':
            rect_color = 0xB963;
            break;

        case '8':
            rect_color = TFT_BLACK;
            font_color = TFT_YELLOW;
            break;

        default:
            break;
        }
        img.fillRoundRect(0, y_display, 36, 18, 9, rect_color); // Adjusted for a larger round rectangle
        img.fillRect(1, y_display + 1, 36, 16, rect_color);    // Adjusted for a larger filled rectangle
        img.setTextColor(font_color);
        img.drawString(line, 3, y_display_string);
    }
    else if (line[0] == 'U') // Ubahn
    {
        // Handle Ubahn lines similar to Sbahn
        switch (line[1])
        {
        case '1':
            rect_color = 0x03E0;
            break;

        case '2':
            rect_color = 0xE800;
            break;

        case '3':
            rect_color = TFT_ORANGE;
            break;

        case '4':
            rect_color = 0x05F0;
            break;

        case '5':
            rect_color = 0xB963;
            break;

        case '6':
            rect_color = TFT_BLUE;
            break;

        case '7':
            img.fillTriangle(38, y_display, 1, y_display, 1, y_display + 18, 0x03E0);   // Green Half
            img.fillTriangle(1, y_display + 18, 38, y_display, 38, y_display + 18, 0xE800); // Red Half
            break;

        case '8':
            img.fillTriangle(38, y_display, 1, y_display, 1, y_display + 18, 0xE800); // Red Half
            img.fillTriangle(1, y_display + 18, 38, y_display, 38, y_display + 18, TFT_ORANGE); // Orange Half
            break;

        default:
            break;
        }

        if (!(line[1] == '7' || line[1] == '8')) // line U1-U6
        {
            img.fillRect(1, y_display + 1, 36, 16, rect_color);
        }
        img.setTextColor(font_color);
        img.drawString(line, 3, y_display_string);
    }
    else
    {
        // Default handling for other lines
        img.setTextColor(TFT_WHITE);
        img.drawString(line, 3, y_display_string);
    }

    //destination
    img.setTextColor(TFT_WHITE);
    if (track == 0 && wagon == 0)
    {
        img.drawString(destination.substring(0, 20), 38, y_display_string); // Adjusted for a larger distance
    }
    else
    {
        img.drawString(destination.substring(0, 11), 38, y_display_string); // Adjusted for a larger distance
    }

 //track
if (track != 0)
{
    char str_buffer[10];
    sprintf(str_buffer, "%u", track);
    img.drawString(str_buffer, 150, y_display_string); // Adjusted position for track display
}

    //wagon
    switch (wagon)
    {
    case 3:
        img.fillRoundRect(195, y_display + 4, 14, 11, 3, TFT_WHITE); // rechts
    case 2:
        img.fillRoundRect(165, y_display + 4, 14, 11, 3, TFT_WHITE); // links
    case 1:
        img.fillRoundRect(180, y_display + 4, 14, 11, 3, TFT_WHITE); // mitte
    default:
        break;
    }

    //minutes
    img.setTextDatum(TR_DATUM);
    char str_buffer[10];
    sprintf(str_buffer, "%u", minutes);
    img.drawString(str_buffer, 240, y_display_string);
}

void setup()
{
  Serial.begin(115200);

  esp_task_wdt_init(10, true);

  setup_display();

  button_init();

  delay(4000);   //Delay needed before calling the WiFi.begin

  while (config_number == -1) {
    config_number = connect_wifi();
  }
  Config loaded_config = configs[config_number];

  switch (loaded_config.type) {
    case mvg_api:
      break;
    case geops_api:
      init_geops_api(loaded_config);
      break;
    default:
      Serial.println("Unkown config type");
      break;
  }

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);

}

void loop() {
  button_loop();
  Config loaded_config = configs[config_number];
  if (WiFi.status() == WL_CONNECTED) {
    switch (loaded_config.type)
    {
      case mvg_api:
        handle_mvg_api(loaded_config);
        break;
      case geops_api:
        static unsigned long last_time = 0;
        handle_geops_api(loaded_config);
        if(millis() > last_time + 10000)
        {
          last_time = millis();
          ping_geops_api();     
        }
        break;
      default:
        Serial.println("Unkown config type");
    }
  }
  else
  {
    Serial.println("Error in WiFi connection");
    Serial.println("Try to Reconnect WiFi");
    WiFi.disconnect();
    WiFi.mode(WIFI_OFF);
    WiFi.mode(WIFI_STA);
      while (config_number == -1) {
    config_number = connect_wifi();
      }
  }
}

int connect_wifi()
{
  int number_of_networks = WiFi.scanNetworks();
  if (number_of_networks == -1 ) {
    Serial.println("No networks available");
  }
  int wait = 0;
  int wifi_retry = 0;
  for (int i = 0; i < number_of_networks; ++i) {
    String ssid = WiFi.SSID(i);
    // is this network in config...?
    for (int j = 0; j < number_of_configs; ++j) {
      if (strcmp(ssid.c_str(), configs[j].wifi_name) == 0) {
        // ... yes it is
        WiFi.begin(configs[j].wifi_name, configs[j].wifi_pass);
        while (WiFi.status() != WL_CONNECTED  && wait < 5 )
        {
        delay(1000);
        ++wait;
        }
        while (WiFi.status() != WL_CONNECTED  && wifi_retry < 5 ) {
          ++wifi_retry;
          Serial.println("WiFi not connected. Try to reconnect");
          WiFi.disconnect();
          WiFi.mode(WIFI_OFF);
          WiFi.mode(WIFI_STA);
          WiFi.begin(configs[j].wifi_name, configs[j].wifi_pass);
          Serial.println("Connecting to WiFi...");
          delay(5000);
        }
        if(wifi_retry >= 5) 
        {
          Serial.println("\nReboot");
          ESP.restart();
        }
        else
        {
          Serial.printf("Connected to the WiFi network: %s\n", ssid.c_str());
          IPAddress dns1 = WiFi.dnsIP(0);
              Serial.printf("DNS: %s\n", dns1.toString().c_str());
          return j;
        }
      }
    }
  }
  return -1;
}

void handle_mvg_api(Config config)
{
  HTTPClient http;
  http.setConnectTimeout(15000);
  //String url = "https://www.mvg.de/api/fahrinfo/departure/" + String(config.bahnhof) + "?footway=0";
  //String url = "https://dns.google/";
  String url = "https://www.mvg.de/api/fib/v2/departure?limit=10&offsetInMinutes=0&transportTypes=UBAHN,TRAM,BUS,SBAHN,SCHIFF&globalId=" + String(config.bahnhof);
  http.begin(url);
  int httpResponseCode = http.GET();

  if (httpResponseCode > 0) {
    String response = http.getString();
    Serial.println("New data");
    Serial.println(httpResponseCode);
    Serial.println(response);

    Serial.println("Parsing JSON...");
    DeserializationError error = deserializeJson(doc, response);
    if (error) {
      Serial.print(F("deserializeJson() failed: "));
      Serial.println(error.c_str());
    } else {
      Serial.println("No errors");
      String servings = doc[0];
      Serial.println(servings);

      time_t now;
      time(&now);
      Serial.println(now);

      img.fillSprite(0x005);
      drawTopLine();
      unsigned int departures_length = doc.size();
      unsigned int i = 0;
      unsigned int cnt = 0;

      while (i < departures_length && cnt < 8) {
        // Extract what we are interested in from response
        // based on config
        bool interesting_type = false;
        bool interesting_line = false;
        bool interesting_destination = true;

        for (int j = 0; j < MAX_INCLUDE_TYPE; ++j) {
          Serial.println(config.include_type[j]);
          if (config.include_type[j] && strcmp(config.include_type[j], "*") == 0) {
            // We want to see all types
            interesting_type = true;
            break;
          }
          if (config.include_type[j] && strcmp(config.include_type[j], doc[i]["product"]) == 0) {
            // We want to see this type
            interesting_type = true;
            break;
          }
        }

        for (int j = 0; j < MAX_INCLUDE_LINE; ++j) {
          Serial.println(config.include_line[j]);
          if (config.include_line[j] && strcmp(config.include_line[j], "*") == 0) {
            // We want to see all lines
            interesting_line = true;
            break;
          }
          if (config.include_line[j] && strcmp(config.include_line[j], doc[i]["label"]) == 0) {
            // We want to see this line
            interesting_line = true;
            break;
          }
        }
        if (interesting_type && interesting_line) {
          for (int j = 0; j < MAX_EXCLUDE_DESTINATION; ++j) {
            if (config.exclude_destinations[j] && strcmp(config.exclude_destinations[j], doc[i]["destination"]) == 0) {
              interesting_destination = false;
              break;
            }
          }
        }
        if (interesting_type && interesting_line && interesting_destination) {
          //Calc minutes until departure
          unsigned long departure = doc[i]["realtimeDepartureTime"].as<long long>() / 1000; //ms to seconds
          Serial.println(departure);

          unsigned long minutes = 0;
          if ( departure > now) {
            unsigned long wait = departure - now;
            Serial.println(wait);
            minutes = wait / 60;
            //if (wait % 60 > 30) ++minutes;
            minutes +=  doc[i]["delay"].as<int>();
          }
          Serial.println(minutes);

          drawDeparture(cnt, doc[i]["label"].as<String>(), doc[i]["destination"].as<String>(), doc[i]["platform"].as<int>(), 0, minutes);

          ++cnt;
        }
        ++i;
      }
      img.pushSprite(0, 0);
    }
  } else {
    Serial.print("Error: Couldn't send GET: ");
    Serial.println(httpResponseCode);
    Serial.println((String)"[HTTP] GET... failed, error: " + http.errorToString(httpResponseCode).c_str());
  }
  http.end();
  if(httpResponseCode > 0)delay(30000);  //Send a request every 30 seconds
}

void init_geops_api(Config config)
{
  bool connected = client.connect("wss://api.geops.io:443/realtime-ws/v1/?key=5cc87b12d7c5370001c1d655306122aa0a4743c489b497cb1afbec9b");
  if (connected) {
    Serial.println("Connecetd!");
    client.send("GET timetable_" + String(config.bahnhof));
    client.send("SUB timetable_" + String(config.bahnhof)); //Subscribe for Departures at Hauptbahnhof
  } else {
    Serial.println("Not Connected!");
  }

  client.onEvent([&](websockets::WebsocketsEvent event, String data) {
        if(event == websockets::WebsocketsEvent::ConnectionOpened) {
        Serial.println("Connnection Opened");
    } else if(event == websockets::WebsocketsEvent::ConnectionClosed) {
        Serial.println("Connnection Closed");
        // Config loaded_config = configs[config_number];
        // init_geops_api(loaded_config);
    } else if(event == websockets::WebsocketsEvent::GotPing) {
        Serial.println("Got a Ping!");
    } else if(event == websockets::WebsocketsEvent::GotPong) {
        Serial.println("Got a Pong!");
    }
  });

  // run callback when messages are received
  client.onMessage([&](websockets::WebsocketsMessage message) {
    Serial.println(message.data());
    Serial.println("Parsing JSON...");
    DeserializationError error = deserializeJson(doc, message.data());
    if (error) {
      Serial.print(F("deserializeJson() failed: "));
      Serial.println(error.c_str());
    }
    else
    {
      Serial.println("No errors");
      Serial.print("Cheking for ");
      Config loaded_config = configs[config_number];
      Serial.println(String(loaded_config.bahnhof));
      if (doc["source"] == ("timetable_" + String(loaded_config.bahnhof)))
      {
        Departure received_departure;
        received_departure.aimed_time= doc["content"]["ris_aimed_time"].as<double>(); 
        received_departure.estimated_time =  doc["content"]["time"].as<double>();
        received_departure.line = doc["content"]["line"]["name"].as<String>();
        received_departure.destination = doc["content"]["to"][0].as<String>();
        received_departure.platform = doc["content"]["platform"].as<int>();
        received_departure.wagon = doc["content"]["train_type"].as<int>();

         /*
        Serial.println((unsigned long)(received_departure.aimed_time/1000.));
        Serial.println((unsigned long)(received_departure.estimated_time/1000.));
        Serial.println(received_departure.line);
        Serial.println(received_departure.destination);
        Serial.println(received_departure.platform);
        Serial.println(received_departure.wagon);
        */
        if (departure_list.empty())
        {
          Serial.println("EMPTY");
          departure_list.push_back(received_departure);
        }
        else
        {
          Serial.println("Not empty");
          list<Departure>::iterator it1;
          for (it1 = departure_list.begin(); it1 != departure_list.end(); ++it1)
          { 
            Serial.println("For loop");

            if (it1->aimed_time == received_departure.aimed_time && it1->line == received_departure.line && it1->destination == received_departure.destination) //Departure schon vorhanden => Update
            {
              Serial.println("Update");
              *it1 = received_departure;
              //Sorting to be sure we are sill in correct order
              departure_list.sort([](const Departure & a, const Departure & b) { return a.estimated_time < b.estimated_time; });
              break;
            }
            if ( next(it1, 1) == departure_list.end() && departure_list.size() < MAX_DEPARTURE_LIST_LENGTH) //Departure nicht vorhanden
            {
              Serial.println("Departure nicht vorhanden");
              list<Departure>::iterator it2;
              for (it2 = departure_list.begin(); it2 != departure_list.end(); ++it2)
              {
                if (it2->estimated_time > received_departure.estimated_time) //Element richtig einsortieren
                {
                  Serial.println("Departure insert");
                  departure_list.insert(it2, received_departure);
                  break;
                }
                if (next(it2,1) == departure_list.end()) //Element ganz hinten einfügen da größter Wert
                {
                  Serial.println("Departure push_back");
                  departure_list.push_back(received_departure);
                  break; //needed cause otherwise it2 != departure_list.end() will never be true
                }
              }
            }
          }  
        }  
      }
    }
  });
}

void handle_geops_api(Config config)
{
  if (client.available()) {
    client.poll();
  }

  time_t now;
  time(&now);

  //Serial.println("New List");
  img.fillSprite(0x005);
  drawTopLine();
  list <Departure> :: iterator it;
  it = departure_list.begin();
  int cnt = 0;
  while ( !departure_list.empty() && it != departure_list.end() && cnt < 8)
  {
    unsigned long estimated_time_s = it->estimated_time/1000;
    unsigned long minutes = 0;

    if (estimated_time_s > now) 
    {
      unsigned long wait = estimated_time_s - now;
      minutes = wait / 60;
      //if (wait % 60 > 30) ++minutes;
    }
    else //abfahrt in der vergangenheit => aus der liste entfernen
    {
      departure_list.erase (it);
    }

    if (it != departure_list.end())
    {
      // Serial.print(it->line);
      // Serial.print("\t");
      // Serial.print(it->destination);
      // Serial.print("\t");
      // Serial.println(minutes);
      drawDeparture(cnt, it->line, it->destination, it->platform, it->wagon, minutes);
    }
    ++cnt;
    ++it;
  }
  img.pushSprite(0, 0);
}

void ping_geops_api()
{
  if(client.available())
  {
    client.send("PING");
    Serial.println("Send Ping");
  }
  else
  { 
    Serial.println("Try to Reconnect geops!");
    Config loaded_config = configs[config_number];
    init_geops_api(loaded_config);
  }

}
kwit98 commented 10 months ago

define TFT_BL 0 // Display backlight control pin

if (TFT_BL > 0) { pinMode(TFT_BL, OUTPUT); digitalWrite(TFT_BL, HIGH); }

U changed your black light pin to 0 that doesn't exist. U shouldn't see even blacklight (nothing displayed but still lighten up). If u see that's because TFT_eSPI is taking care of it (and u didn't disabled it there)

kwit98 commented 10 months ago

don't write manually screen resolution, create variables and write them there. Code will work exactly the same but it's: easier to read, harder to mess up

const int display_x = [write your value here];

Const means that variable cannot change and will always have the same value (not always, but u will not touch code hard enough to feel that).

schnoog commented 10 months ago

Sorry for the confusion. My TFT is hardwired to an Esp32s3 (for a home cockpit project) and the back light is controlled externally.

That's why I set it to 0.

In the reddit discussion I understood that Cristian hooked it up to 3.3 and didn't use the back light on/off feature.