Compare commits
	
		
			No commits in common. "a563cc45b5069a93ba88e85b6d68cd227911a695" and "72734c7faa5cb6e704f4a469f95ff7980ce27d3e" have entirely different histories.
		
	
	
		
			a563cc45b5
			...
			72734c7faa
		
	
		
| @ -11,4 +11,3 @@ | |||||||
| - [x] Transfer using JSON | - [x] Transfer using JSON | ||||||
| - [x] Correctly implement basic auth | - [x] Correctly implement basic auth | ||||||
| - [ ] Use unidecode to replace non-ascii stuff in the backend | - [ ] Use unidecode to replace non-ascii stuff in the backend | ||||||
| - [ ] Add query parameter for selecting EFAClient |  | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ | |||||||
| #include <Adafruit_ST7735.h> | #include <Adafruit_ST7735.h> | ||||||
| #include <ESP8266WiFi.h> | #include <ESP8266WiFi.h> | ||||||
| #include <ESP8266HTTPClient.h> | #include <ESP8266HTTPClient.h> | ||||||
| #include <memory> |  | ||||||
| #include <SPI.h> | #include <SPI.h> | ||||||
| #include <WiFiClientSecureBearSSL.h> | #include <WiFiClientSecureBearSSL.h> | ||||||
| 
 | 
 | ||||||
| @ -42,153 +41,49 @@ | |||||||
| #define CLOCK_X MARGIN_LEFT | #define CLOCK_X MARGIN_LEFT | ||||||
| #define CLOCK_Y 113 | #define CLOCK_Y 113 | ||||||
| 
 | 
 | ||||||
| #define STATUS_X 83 | #define STATUS_X 95 | ||||||
| #define STATUS_Y CLOCK_Y | #define STATUS_Y CLOCK_Y | ||||||
| #define STATUS_WIDTH (12 * CX) | #define STATUS_WIDTH (10 * CX) | ||||||
| #define STATUS_HEIGHT CY | #define STATUS_HEIGHT CY | ||||||
| 
 | 
 | ||||||
| // Function definitions
 |  | ||||||
| 
 |  | ||||||
| void drawLayout(); |  | ||||||
| uint16_t rgb(uint16_t, uint16_t, uint16_t); |  | ||||||
| String fetchDepartures(); |  | ||||||
| 
 |  | ||||||
| // Peripherals
 | // Peripherals
 | ||||||
| 
 | 
 | ||||||
| Adafruit_ST7735 display(TFT_CS, TFT_DC, TFT_RESET); | Adafruit_ST7735 display(TFT_CS, TFT_DC, TFT_RESET); | ||||||
| 
 | 
 | ||||||
| // App state interface
 | // App state
 | ||||||
| 
 | 
 | ||||||
| class State { | typedef enum { | ||||||
| public: |   CONNECTING, | ||||||
|   virtual void enter() = 0; |   SHOWING_DEPARTURES | ||||||
|   virtual void tick() = 0; | } State; | ||||||
|   virtual ~State() = default; |  | ||||||
| }; |  | ||||||
| 
 | 
 | ||||||
| class StateConnecting : public State { | State state = CONNECTING; | ||||||
| public: | State previous_state = state; | ||||||
|   void enter() override; | int frame = 0; | ||||||
|   void tick() override; | // Use this variable to trigger state enter effects
 | ||||||
| }; | int state_changed = 1; | ||||||
| 
 |  | ||||||
| class StateFetching : public State { |  | ||||||
| public: |  | ||||||
|   void enter() override; |  | ||||||
|   void tick() override; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| class StateShowingDepartures : public State { |  | ||||||
| private: |  | ||||||
|   // Todo: discard JsonDocument asap, parse into better format
 |  | ||||||
|   JsonDocument departures; |  | ||||||
|   unsigned long entered; |  | ||||||
| public: |  | ||||||
|   StateShowingDepartures(String&); |  | ||||||
|   void enter() override; |  | ||||||
|   void tick() override; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // App state implementation
 |  | ||||||
| 
 |  | ||||||
| uint64_t currentTick = 0; |  | ||||||
| // Initially true so that enter() of the initial state is called.
 |  | ||||||
| bool stateChanged = true; |  | ||||||
| std::unique_ptr<State> state = std::make_unique<StateConnecting>(); |  | ||||||
| 
 |  | ||||||
| template <typename StateType, typename... Args> |  | ||||||
| void setState(Args&&... args) { |  | ||||||
|   state = std::make_unique<StateType>(std::forward<Args>(args)...); |  | ||||||
|   stateChanged = true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void StateConnecting::enter() { |  | ||||||
|   Serial.println("Entering StateConnecting"); |  | ||||||
| 
 |  | ||||||
|   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); |  | ||||||
|   display.setCursor(STATUS_X, STATUS_Y); |  | ||||||
|   display.printf("connecting"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void StateConnecting::tick() { |  | ||||||
|   if (WiFi.status() != WL_CONNECTED) { |  | ||||||
|     display.fillRect(STATUS_X + 11 * CX, STATUS_Y, CX, CY, COLOR_BG); |  | ||||||
|     display.setCursor(STATUS_X + 11 * CX, STATUS_Y); |  | ||||||
|     display.print("|/-\\"[currentTick % 4]); |  | ||||||
|     delay(125); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   setState<StateFetching>(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // Fetching is a bit of an ugly duckling; it performs all its logic in enter().
 |  | ||||||
| // This is because the request it does is synchronous.
 |  | ||||||
| // Sadly this means no animation is possible right now.
 |  | ||||||
| // At least the fetch call still does yield() under the hood :)
 |  | ||||||
| void StateFetching::enter() { |  | ||||||
|   Serial.println("Entering StateFetching"); |  | ||||||
| 
 |  | ||||||
|   if (WiFi.status() != WL_CONNECTED) { |  | ||||||
|     setState<StateConnecting>(); |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); |  | ||||||
|   display.setCursor(STATUS_X, STATUS_Y); |  | ||||||
|   display.print("    fetching"); |  | ||||||
|    |  | ||||||
|   String departuresRaw = fetchDepartures(); |  | ||||||
|    |  | ||||||
|   setState<StateShowingDepartures>(departuresRaw); |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void StateFetching::tick() { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| StateShowingDepartures::StateShowingDepartures(String &departuresRaw) { |  | ||||||
|   deserializeJson(departures, departuresRaw); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void StateShowingDepartures::enter() { |  | ||||||
|   Serial.println("Entering StateShowingDepartures"); |  | ||||||
|   entered = millis(); |  | ||||||
| 
 |  | ||||||
|   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); |  | ||||||
|   display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG); |  | ||||||
| 
 |  | ||||||
|   int line = 0; |  | ||||||
|   for (JsonVariant departure : departures["departures"].as<JsonArray>()) { |  | ||||||
|     display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); |  | ||||||
|     display.printf("%2s %-15.15s ", |  | ||||||
|       departure["symbol"].as<const char*>(), |  | ||||||
|       departure["direction"].as<const char *>() |  | ||||||
|     ); |  | ||||||
|     if (departure["leaving"].as<String>().equals("sofort")) { |  | ||||||
|       int16_t x = display.getCursorX(); |  | ||||||
|       int16_t y = display.getCursorY(); |  | ||||||
|       display.drawBitmap(x + 6 * CX - tiny_train_dims[0], y, tiny_train[0], tiny_train_dims[0], tiny_train_dims[1], COLOR_TEXT); |  | ||||||
|     } else { |  | ||||||
|       display.printf("%6s", departure["leaving"].as<const char*>()); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     line++; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void StateShowingDepartures::tick() { |  | ||||||
|   unsigned long elapsed = millis() - entered; |  | ||||||
|   if (elapsed > 25000) { |  | ||||||
|     setState<StateFetching>(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| int buttonPushed = false; | int buttonPushed = false; | ||||||
|  | JsonDocument departures; | ||||||
| 
 | 
 | ||||||
| void ICACHE_RAM_ATTR onButtonFalling() { | void ICACHE_RAM_ATTR onButtonFalling() { | ||||||
|   buttonPushed = true; |   buttonPushed = true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | uint16_t rgb(uint16_t r, uint16_t g, uint16_t b) { | ||||||
|  |   // Color layout: RRRRRGGGGGGBBBBB (5, 6, 5)
 | ||||||
|  |   return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void drawLayout() { | ||||||
|  |   display.fillScreen(COLOR_BG); | ||||||
|  |   display.drawFastHLine(5, 4, 150, COLOR_DIVIDER); | ||||||
|  |   display.drawFastHLine(5, 109, 150, COLOR_DIVIDER); | ||||||
|  |   display.drawFastHLine(5, 123, 150, COLOR_DIVIDER); | ||||||
|  |   display.setCursor(CLOCK_X, CLOCK_Y); | ||||||
|  |   display.print("13:12"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void setup() { | void setup() { | ||||||
|   Serial.begin(9600); |   Serial.begin(9600); | ||||||
| 
 | 
 | ||||||
| @ -207,39 +102,10 @@ void setup() { | |||||||
|    |    | ||||||
|   pinMode(BUTTON_PIN, INPUT_PULLUP); |   pinMode(BUTTON_PIN, INPUT_PULLUP); | ||||||
|   attachInterrupt(BUTTON_PIN, onButtonFalling, FALLING); |   attachInterrupt(BUTTON_PIN, onButtonFalling, FALLING); | ||||||
| 
 |  | ||||||
|   state = std::make_unique<StateConnecting>(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #define QUERY_STRING "stop_id=de:08221:1225&platform=A" | #define QUERY_STRING "stop_id=de:08221:1225&platform=A" | ||||||
| 
 | 
 | ||||||
| void loop() { |  | ||||||
|   if (stateChanged) { |  | ||||||
|     stateChanged = false; |  | ||||||
|     // Note: enter() may call setState(). In that case we want to end up right back here.
 |  | ||||||
|     state->enter(); |  | ||||||
|   } else { |  | ||||||
|     state->tick(); |  | ||||||
|     currentTick++; |  | ||||||
|   } |  | ||||||
|   // Let background system do its thing
 |  | ||||||
|   yield(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| uint16_t rgb(uint16_t r, uint16_t g, uint16_t b) { |  | ||||||
|   // Color layout: RRRRRGGGGGGBBBBB (5, 6, 5)
 |  | ||||||
|   return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void drawLayout() { |  | ||||||
|   display.fillScreen(COLOR_BG); |  | ||||||
|   display.drawFastHLine(5, 4, 150, COLOR_DIVIDER); |  | ||||||
|   display.drawFastHLine(5, 109, 150, COLOR_DIVIDER); |  | ||||||
|   display.drawFastHLine(5, 123, 150, COLOR_DIVIDER); |  | ||||||
|   display.setCursor(CLOCK_X, CLOCK_Y); |  | ||||||
|   display.print("13:12"); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // TODO: Error handling
 | // TODO: Error handling
 | ||||||
| String fetchDepartures() { | String fetchDepartures() { | ||||||
|   std::unique_ptr<BearSSL::WiFiClientSecure> https(new BearSSL::WiFiClientSecure); |   std::unique_ptr<BearSSL::WiFiClientSecure> https(new BearSSL::WiFiClientSecure); | ||||||
| @ -265,3 +131,69 @@ String fetchDepartures() { | |||||||
| 
 | 
 | ||||||
|   return client.getString(); |   return client.getString(); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void loop() { | ||||||
|  |   if (state != previous_state) { | ||||||
|  |     state_changed = 1; | ||||||
|  |     previous_state = state; | ||||||
|  |   } | ||||||
|  |   logic_loop(); | ||||||
|  |   frame++; | ||||||
|  |   state_changed = 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void logic_loop() { | ||||||
|  |   switch (state) { | ||||||
|  |     case CONNECTING: { | ||||||
|  |       if (WiFi.status() != WL_CONNECTED) { | ||||||
|  |         display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); | ||||||
|  |         display.setCursor(STATUS_X, STATUS_Y); | ||||||
|  |         display.printf(" connect %c", "|/-\\"[frame % 4]); | ||||||
|  |         delay(FRAME_DELAY); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); | ||||||
|  |       display.setCursor(STATUS_X, STATUS_Y); | ||||||
|  |       display.print("  fetching"); | ||||||
|  | 
 | ||||||
|  |       String departuresRaw = fetchDepartures(); | ||||||
|  |       deserializeJson(departures, departuresRaw); | ||||||
|  |        | ||||||
|  |       state = SHOWING_DEPARTURES; | ||||||
|  |       frame = 0; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |        | ||||||
|  |     case SHOWING_DEPARTURES: | ||||||
|  |       if (WiFi.status() != WL_CONNECTED) { | ||||||
|  |         state = CONNECTING; | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); | ||||||
|  |       display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG); | ||||||
|  | 
 | ||||||
|  |       int line = 0; | ||||||
|  |       for (JsonVariant departure : departures["departures"].as<JsonArray>()) { | ||||||
|  |         display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); | ||||||
|  |         display.printf("%2s %-15.15s ", | ||||||
|  |           departure["symbol"].as<const char*>(), | ||||||
|  |           departure["direction"].as<const char *>() | ||||||
|  |         ); | ||||||
|  |         if (departure["leaving"].as<String>().equals("sofort")) { | ||||||
|  |           int16_t x = display.getCursorX(); | ||||||
|  |           int16_t y = display.getCursorY(); | ||||||
|  |           display.drawBitmap(x + 6 * CX - tiny_train_dims[0], y, tiny_train[0], tiny_train_dims[0], tiny_train_dims[1], COLOR_TEXT); | ||||||
|  |         } else { | ||||||
|  |           display.printf("%6s", departure["leaving"].as<const char*>()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         line++; | ||||||
|  |       } | ||||||
|  |        | ||||||
|  |       delay(25000); | ||||||
|  |       state = CONNECTING; | ||||||
|  |       return; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ | |||||||
| 
 | 
 | ||||||
|       vrnp-static = pkgs.buildGoModule { |       vrnp-static = pkgs.buildGoModule { | ||||||
|         pname = "vrnp"; |         pname = "vrnp"; | ||||||
|         version = "0.0.9"; |         version = "0.0.8"; | ||||||
|         vendorHash = null; |         vendorHash = null; | ||||||
| 
 | 
 | ||||||
|         # For building the package, we use only the files not ignored by Git as inputs. |         # For building the package, we use only the files not ignored by Git as inputs. | ||||||
|  | |||||||
							
								
								
									
										35
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										35
									
								
								main.go
									
									
									
									
									
								
							| @ -55,7 +55,6 @@ var allEfaClients []EFAClient = []EFAClient{ | |||||||
| 	BwegtEFAClient{}, | 	BwegtEFAClient{}, | ||||||
| 	VRNEFAClient{}, | 	VRNEFAClient{}, | ||||||
| 	KVVEFAClient{}, | 	KVVEFAClient{}, | ||||||
| 	VAGEFAClient{}, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type VRNEFAClient struct { | type VRNEFAClient struct { | ||||||
| @ -152,38 +151,6 @@ func (c BwegtEFAClient) BuildRequest(stopId string) (*http.Request, error) { | |||||||
| 	return req, nil | 	return req, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type VAGEFAClient struct { |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c VAGEFAClient) GetName() string { |  | ||||||
| 	return "VAG" |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (c VAGEFAClient) BuildRequest(stopId string) (*http.Request, error) { |  | ||||||
| 	// Create request object |  | ||||||
| 	req, err := http.NewRequest("GET", "https://efa.vagfr.de/vagfr3/XSLT_DM_REQUEST", nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	// Configure our request |  | ||||||
| 	query := url.Values{} |  | ||||||
| 	query.Set("coordOutputFormat", "EPSG:4326") |  | ||||||
| 	query.Set("depType", "stopEvents") |  | ||||||
| 	query.Set("includeCompleteStopSeq", "0") |  | ||||||
| 	query.Set("limit", "10") |  | ||||||
| 	query.Set("locationServerActive", "0") |  | ||||||
| 	query.Set("mode", "direct") |  | ||||||
| 	query.Set("name_dm", stopId) |  | ||||||
| 	query.Set("outputFormat", "json") |  | ||||||
| 	query.Set("type_dm", "stop") |  | ||||||
| 	query.Set("useOnlyStops", "1") |  | ||||||
| 	query.Set("useRealtime", "1") |  | ||||||
| 	req.URL.RawQuery = query.Encode() |  | ||||||
| 
 |  | ||||||
| 	return req, nil |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func FetchDepartures(c EFAClient, stopId string) (DMResponse, error) { | func FetchDepartures(c EFAClient, stopId string) (DMResponse, error) { | ||||||
| 	req, err := c.BuildRequest(stopId) | 	req, err := c.BuildRequest(stopId) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @ -314,7 +281,7 @@ func main() { | |||||||
| 			platform = &query["platform"][0] | 			platform = &query["platform"][0] | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		c := efaClients[(efaClient.Add(1) - 1) % uint64(len(efaClients))] | 		c := efaClients[(efaClient.Add(1)-1)%uint64(len(efaClients))] | ||||||
| 		ds, err := FetchDepartures(c, stopId) | 		ds, err := FetchDepartures(c, stopId) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			http.Error(w, err.Error(), http.StatusInternalServerError) | 			http.Error(w, err.Error(), http.StatusInternalServerError) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user