Compare commits
	
		
			No commits in common. "6e53521b8406a67c1acaa93980814fa6b6b93674" and "a563cc45b5069a93ba88e85b6d68cd227911a695" have entirely different histories.
		
	
	
		
			6e53521b84
			...
			a563cc45b5
		
	
		
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							| @ -2,21 +2,13 @@ | |||||||
| 
 | 
 | ||||||
| ## TODO | ## TODO | ||||||
| 
 | 
 | ||||||
| ### Server |  | ||||||
| 
 |  | ||||||
| - [x] Use timeout for fetching departures | - [x] Use timeout for fetching departures | ||||||
| - [x] Add basic auth | - [x] Add basic auth | ||||||
| - [x] Create Nix package | - [x] Create Nix package | ||||||
| - [x] Create container | - [x] Create container | ||||||
|  | - [x] Write ESP8266 client | ||||||
|  | - [ ] Make port configurable | ||||||
| - [x] Transfer using JSON | - [x] Transfer using JSON | ||||||
| - [x] Correctly implement basic auth | - [x] Correctly implement basic auth | ||||||
| - [ ] Make port configurable | - [ ] Use unidecode to replace non-ascii stuff in the backend | ||||||
| - [ ] Use unidecode to replace non-ascii stuff |  | ||||||
| - [ ] Add query parameter for selecting EFAClient | - [ ] Add query parameter for selecting EFAClient | ||||||
| - [ ] Return current time for displaying clock |  | ||||||
| 
 |  | ||||||
| ### Client |  | ||||||
| 
 |  | ||||||
| - [x] Write ESP8266 client |  | ||||||
| - [x] Add code for handling button presses |  | ||||||
| - [ ] Add clock |  | ||||||
|  | |||||||
| @ -21,10 +21,10 @@ | |||||||
| #define FRAME_DELAY 80 | #define FRAME_DELAY 80 | ||||||
| #define COLOR_BG (rgb(0, 0, 0)) | #define COLOR_BG (rgb(0, 0, 0)) | ||||||
| #define COLOR_DIVIDER (rgb(0xff, 0x60, 0x0d)) | #define COLOR_DIVIDER (rgb(0xff, 0x60, 0x0d)) | ||||||
| #define COLOR_TOP (rgb(0xa0, 0xa0, 0xa0)) |  | ||||||
| #define COLOR_TEXT (rgb(0xff, 0x60, 0x0d)) | #define COLOR_TEXT (rgb(0xff, 0x60, 0x0d)) | ||||||
| 
 | 
 | ||||||
| // Layout
 | // Layout
 | ||||||
|  | 
 | ||||||
| #define WIDTH 160 | #define WIDTH 160 | ||||||
| #define HEIGHT 128 | #define HEIGHT 128 | ||||||
| 
 | 
 | ||||||
| @ -34,48 +34,24 @@ | |||||||
| #define MARGIN_LEFT 5 | #define MARGIN_LEFT 5 | ||||||
| #define MARGIN_RIGHT 5 | #define MARGIN_RIGHT 5 | ||||||
| 
 | 
 | ||||||
| #define TOP_X MARGIN_LEFT |  | ||||||
| #define TOP_Y 4 |  | ||||||
| #define TOP_WIDTH (WIDTH - 2 * MARGIN_LEFT) |  | ||||||
| #define TOP_HEIGHT CY |  | ||||||
| 
 |  | ||||||
| #define MAIN_X MARGIN_LEFT | #define MAIN_X MARGIN_LEFT | ||||||
| #define MAIN_Y 19 | #define MAIN_Y 9 | ||||||
| #define MAIN_WIDTH (WIDTH - 2 * MARGIN_LEFT) | #define MAIN_WIDTH (WIDTH - 2 * MARGIN_LEFT) | ||||||
| #define MAIN_HEIGHT (8 * CY) | #define MAIN_HEIGHT (8 * CY) | ||||||
| 
 | 
 | ||||||
| #define CLOCK_X MARGIN_LEFT | #define CLOCK_X MARGIN_LEFT | ||||||
| #define CLOCK_Y 113 | #define CLOCK_Y 113 | ||||||
| #define CLOCK_WIDTH (5 * CX) |  | ||||||
| #define CLOCK_HEIGHT CY |  | ||||||
| 
 | 
 | ||||||
| #define STATUS_X 83 | #define STATUS_X 83 | ||||||
| #define STATUS_Y CLOCK_Y | #define STATUS_Y CLOCK_Y | ||||||
| #define STATUS_WIDTH (12 * CX) | #define STATUS_WIDTH (12 * CX) | ||||||
| #define STATUS_HEIGHT CY | #define STATUS_HEIGHT CY | ||||||
| 
 | 
 | ||||||
| struct Timetable { |  | ||||||
|   const char *label; |  | ||||||
|   const char *path; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // Selectable timetables
 |  | ||||||
| Timetable timetables[] = { |  | ||||||
|   {"Johanneskirche, Bstg. 1", "/departures?stop_id=de:08311:30104&platform=1"}, |  | ||||||
|   {"Johanneskirche, Bstg. 2", "/departures?stop_id=de:08311:30104&platform=2"}, |  | ||||||
|   {"Blumenthalstr., Bstg. A", "/departures?stop_id=de:08221:1225&platform=A"}, |  | ||||||
|   {"Blumenthalstr., Bstg. B", "/departures?stop_id=de:08221:1225&platform=B"} |  | ||||||
| }; |  | ||||||
| size_t selectedTimetable = 0; |  | ||||||
| 
 |  | ||||||
| // Function definitions
 | // Function definitions
 | ||||||
|  | 
 | ||||||
| void drawLayout(); | void drawLayout(); | ||||||
| void clearTop(); |  | ||||||
| void clearStatus(); |  | ||||||
| void clearMain(); |  | ||||||
| void clearClock(); |  | ||||||
| uint16_t rgb(uint16_t, uint16_t, uint16_t); | uint16_t rgb(uint16_t, uint16_t, uint16_t); | ||||||
| String fetchDepartures(String); | String fetchDepartures(); | ||||||
| 
 | 
 | ||||||
| // Peripherals
 | // Peripherals
 | ||||||
| 
 | 
 | ||||||
| @ -83,40 +59,10 @@ Adafruit_ST7735 display(TFT_CS, TFT_DC, TFT_RESET); | |||||||
| 
 | 
 | ||||||
| // App state interface
 | // App state interface
 | ||||||
| 
 | 
 | ||||||
| //           init                                                   
 |  | ||||||
| //             │                                                    
 |  | ||||||
| //             │                                                   
 |  | ||||||
| //             │                                                    
 |  | ||||||
| //             │      ┌───┐                                         
 |  | ||||||
| //          ┌──▼──────▼┐  │                                         
 |  | ||||||
| //          │Connecting│  │!connected                               
 |  | ||||||
| //          └──┬───▲──┬┘  │                                         
 |  | ||||||
| //             │   │  └───┘                                         
 |  | ||||||
| //             │   │                                                
 |  | ||||||
| //    connected│   │!connected                                      
 |  | ||||||
| //             │   │                                                
 |  | ||||||
| //             │   │                                                
 |  | ||||||
| //             │   │                             ┌───┐              
 |  | ||||||
| //          ┌──▼───┴─┐   timeout (3s)   ┌────────▼┐  │              
 |  | ||||||
| //          │Fetching◄──────────────────┤Selecting│  │              
 |  | ||||||
| //          └──┬───▲─┘                  └────▲───┬┘  │button pressed
 |  | ||||||
| //             │   │                         │   └───┘              
 |  | ||||||
| // got response│   │timeout (25s)            │                      
 |  | ||||||
| //             │   │                         │                      
 |  | ||||||
| //             │   │                         │                      
 |  | ||||||
| //             │   │                         │button pressed        
 |  | ||||||
| //        ┌────▼───┴─────────┐               │                      
 |  | ||||||
| //        │Showing Departures│───────────────┘                      
 |  | ||||||
| //        └──────────────────┘                                      
 |  | ||||||
| 
 |  | ||||||
| class State { | class State { | ||||||
| public: | public: | ||||||
|   // When the state is entered
 |   virtual void enter() = 0; | ||||||
|   virtual void enter(); |   virtual void tick() = 0; | ||||||
|   // Called in the business loop
 |  | ||||||
|   virtual void tick(); |  | ||||||
|   // Called when the button is pushed
 |  | ||||||
|   virtual void button(); |  | ||||||
|   virtual ~State() = default; |   virtual ~State() = default; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| @ -129,6 +75,7 @@ public: | |||||||
| class StateFetching : public State { | class StateFetching : public State { | ||||||
| public: | public: | ||||||
|   void enter() override; |   void enter() override; | ||||||
|  |   void tick() override; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class StateShowingDepartures : public State { | class StateShowingDepartures : public State { | ||||||
| @ -140,24 +87,8 @@ public: | |||||||
|   StateShowingDepartures(String&); |   StateShowingDepartures(String&); | ||||||
|   void enter() override; |   void enter() override; | ||||||
|   void tick() override; |   void tick() override; | ||||||
|   void button() override; |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| class StateSelecting : public State { |  | ||||||
| private: |  | ||||||
|   unsigned long entered; |  | ||||||
| public: |  | ||||||
|   void enter() override; |  | ||||||
|   void tick() override; |  | ||||||
|   void button() override; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| // Empty default implementations
 |  | ||||||
| 
 |  | ||||||
| void State::enter() {} |  | ||||||
| void State::tick() {} |  | ||||||
| void State::button() {} |  | ||||||
| 
 |  | ||||||
| // App state implementation
 | // App state implementation
 | ||||||
| 
 | 
 | ||||||
| uint64_t currentTick = 0; | uint64_t currentTick = 0; | ||||||
| @ -176,7 +107,6 @@ void StateConnecting::enter() { | |||||||
| 
 | 
 | ||||||
|   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); |   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); | ||||||
|   display.setCursor(STATUS_X, STATUS_Y); |   display.setCursor(STATUS_X, STATUS_Y); | ||||||
|   display.setTextColor(COLOR_TEXT); |  | ||||||
|   display.printf("connecting"); |   display.printf("connecting"); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -184,7 +114,6 @@ void StateConnecting::tick() { | |||||||
|   if (WiFi.status() != WL_CONNECTED) { |   if (WiFi.status() != WL_CONNECTED) { | ||||||
|     display.fillRect(STATUS_X + 11 * CX, STATUS_Y, CX, CY, COLOR_BG); |     display.fillRect(STATUS_X + 11 * CX, STATUS_Y, CX, CY, COLOR_BG); | ||||||
|     display.setCursor(STATUS_X + 11 * CX, STATUS_Y); |     display.setCursor(STATUS_X + 11 * CX, STATUS_Y); | ||||||
|     display.setTextColor(COLOR_TEXT); |  | ||||||
|     display.print("|/-\\"[currentTick % 4]); |     display.print("|/-\\"[currentTick % 4]); | ||||||
|     delay(125); |     delay(125); | ||||||
|     return; |     return; | ||||||
| @ -204,16 +133,19 @@ void StateFetching::enter() { | |||||||
|     setState<StateConnecting>(); |     setState<StateConnecting>(); | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); |   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); | ||||||
|   display.setTextColor(COLOR_TEXT); |  | ||||||
|   display.setCursor(STATUS_X, STATUS_Y); |   display.setCursor(STATUS_X, STATUS_Y); | ||||||
|   display.print("    fetching"); |   display.print("    fetching"); | ||||||
|    |    | ||||||
|   String departuresRaw = fetchDepartures(timetables[selectedTimetable].path); |   String departuresRaw = fetchDepartures(); | ||||||
|    |    | ||||||
|   setState<StateShowingDepartures>(departuresRaw); |   setState<StateShowingDepartures>(departuresRaw); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | void StateFetching::tick() { | ||||||
|  | } | ||||||
|  | 
 | ||||||
| StateShowingDepartures::StateShowingDepartures(String &departuresRaw) { | StateShowingDepartures::StateShowingDepartures(String &departuresRaw) { | ||||||
|   deserializeJson(departures, departuresRaw); |   deserializeJson(departures, departuresRaw); | ||||||
| } | } | ||||||
| @ -222,26 +154,16 @@ void StateShowingDepartures::enter() { | |||||||
|   Serial.println("Entering StateShowingDepartures"); |   Serial.println("Entering StateShowingDepartures"); | ||||||
|   entered = millis(); |   entered = millis(); | ||||||
| 
 | 
 | ||||||
|   clearStatus(); |   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); | ||||||
|   clearMain(); |   display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG); | ||||||
| 
 | 
 | ||||||
|   // draw timetable
 |  | ||||||
|   display.setTextColor(COLOR_TEXT); |  | ||||||
|   int line = 0; |   int line = 0; | ||||||
|   for (JsonVariant departure : departures["departures"].as<JsonArray>()) { |   for (JsonVariant departure : departures["departures"].as<JsonArray>()) { | ||||||
|     if (line > 8) { |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|      |  | ||||||
| 
 |  | ||||||
|     char symbol[3] = {0}; |  | ||||||
|     char direction[16] = {0}; |  | ||||||
| 
 |  | ||||||
|     utf8ToCP437German(departure["symbol"].as<const char *>(), symbol, std::size(symbol)); |  | ||||||
|     utf8ToCP437German(departure["direction"].as<const char *>(), direction, std::size(direction)); |  | ||||||
| 
 |  | ||||||
|     display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); |     display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); | ||||||
|     display.printf("%2s %-15.15s ", symbol, direction); |     display.printf("%2s %-15.15s ", | ||||||
|  |       departure["symbol"].as<const char*>(), | ||||||
|  |       departure["direction"].as<const char *>() | ||||||
|  |     ); | ||||||
|     if (departure["leaving"].as<String>().equals("sofort")) { |     if (departure["leaving"].as<String>().equals("sofort")) { | ||||||
|       int16_t x = display.getCursorX(); |       int16_t x = display.getCursorX(); | ||||||
|       int16_t y = display.getCursorY(); |       int16_t y = display.getCursorY(); | ||||||
| @ -261,36 +183,6 @@ void StateShowingDepartures::tick() { | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void StateShowingDepartures::button() { |  | ||||||
|   setState<StateSelecting>(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void StateSelecting::enter() { |  | ||||||
|   Serial.println("Entering StateSelecting"); |  | ||||||
|   entered = millis(); |  | ||||||
|    |  | ||||||
|   clearMain(); |  | ||||||
|   clearStatus(); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void StateSelecting::tick() { |  | ||||||
|   unsigned long elapsed = millis() - entered; |  | ||||||
|   if (elapsed > 3000) { |  | ||||||
|     setState<StateFetching>(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void StateSelecting::button() { |  | ||||||
|   entered = millis(); |  | ||||||
|   selectedTimetable = (selectedTimetable + 1) % (sizeof timetables / sizeof timetables[0]); |  | ||||||
|   clearTop(); |  | ||||||
|   display.setCursor(TOP_X, TOP_Y); |  | ||||||
|   display.setTextColor(COLOR_TOP); |  | ||||||
|   display.print(timetables[selectedTimetable].label); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // button ISR setup
 |  | ||||||
| 
 |  | ||||||
| int buttonPushed = false; | int buttonPushed = false; | ||||||
| 
 | 
 | ||||||
| void ICACHE_RAM_ATTR onButtonFalling() { | void ICACHE_RAM_ATTR onButtonFalling() { | ||||||
| @ -303,15 +195,13 @@ void setup() { | |||||||
|   Serial.println("serial init"); |   Serial.println("serial init"); | ||||||
|   display.initR(); |   display.initR(); | ||||||
|   display.setSPISpeed(40000000); |   display.setSPISpeed(40000000); | ||||||
|   display.cp437(true); |  | ||||||
|   display.setRotation(3); |   display.setRotation(3); | ||||||
|   Serial.println("display initialized"); |   Serial.println("display initialized"); | ||||||
|   Serial.printf("display dimensions: %" PRId16 "x%" PRId16 "\n", display.width(), display.height()); |   Serial.printf("display dimensions: %" PRId16 "x%" PRId16 "\n", display.width(), display.height()); | ||||||
| 
 | 
 | ||||||
|  |   display.setTextSize(1); | ||||||
|  |   display.setTextColor(COLOR_TEXT); | ||||||
|   drawLayout(); |   drawLayout(); | ||||||
|   display.setCursor(TOP_X, TOP_Y); |  | ||||||
|   display.setTextColor(COLOR_TOP); |  | ||||||
|   display.print(timetables[selectedTimetable].label); |  | ||||||
| 
 | 
 | ||||||
|   WiFi.begin(WIFI_SSID, WIFI_PASSWORD); |   WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | ||||||
|    |    | ||||||
| @ -321,16 +211,13 @@ void setup() { | |||||||
|   state = std::make_unique<StateConnecting>(); |   state = std::make_unique<StateConnecting>(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #define QUERY_STRING "stop_id=de:08221:1225&platform=A" | ||||||
|  | 
 | ||||||
| void loop() { | void loop() { | ||||||
|   if (stateChanged) { |   if (stateChanged) { | ||||||
|     stateChanged = false; |     stateChanged = false; | ||||||
|     // Note: enter() may call setState(). In that case we want to end up right back here.
 |     // Note: enter() may call setState(). In that case we want to end up right back here.
 | ||||||
|     state->enter(); |     state->enter(); | ||||||
|   } else if (buttonPushed) { |  | ||||||
|     // "Handle" jitter
 |  | ||||||
|     delay(100); |  | ||||||
|     buttonPushed = false; |  | ||||||
|     state->button(); |  | ||||||
|   } else { |   } else { | ||||||
|     state->tick(); |     state->tick(); | ||||||
|     currentTick++; |     currentTick++; | ||||||
| @ -344,36 +231,22 @@ uint16_t rgb(uint16_t r, uint16_t g, uint16_t b) { | |||||||
|   return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111); |   return ((b >> 3) & 0b11111) << 11 | ((g >> 2) & 0b111111) << 5 | ((r >> 3) & 0b11111); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void clearTop() { |  | ||||||
|   display.fillRect(TOP_X, TOP_Y, TOP_WIDTH, TOP_HEIGHT, COLOR_BG); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void clearMain() { |  | ||||||
|   display.fillRect(MAIN_X, MAIN_Y, MAIN_WIDTH, MAIN_HEIGHT, COLOR_BG); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void clearStatus() { |  | ||||||
|   display.fillRect(STATUS_X, STATUS_Y, STATUS_WIDTH, STATUS_HEIGHT, COLOR_BG); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void clearClock() { |  | ||||||
|   display.fillRect(CLOCK_X, CLOCK_Y, CLOCK_WIDTH, CLOCK_HEIGHT, COLOR_BG); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void drawLayout() { | void drawLayout() { | ||||||
|   display.fillScreen(COLOR_BG); |   display.fillScreen(COLOR_BG); | ||||||
|   display.drawFastHLine(5, 15, 150, COLOR_DIVIDER); |   display.drawFastHLine(5, 4, 150, COLOR_DIVIDER); | ||||||
|   display.drawFastHLine(5, 109, 150, COLOR_DIVIDER); |   display.drawFastHLine(5, 109, 150, COLOR_DIVIDER); | ||||||
|   display.drawFastHLine(5, 123, 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 path) { | String fetchDepartures() { | ||||||
|   std::unique_ptr<BearSSL::WiFiClientSecure> https(new BearSSL::WiFiClientSecure); |   std::unique_ptr<BearSSL::WiFiClientSecure> https(new BearSSL::WiFiClientSecure); | ||||||
|   https->setInsecure(); |   https->setInsecure(); | ||||||
|    |    | ||||||
|   HTTPClient client; |   HTTPClient client; | ||||||
|   if (!client.begin(*https, "vrnp.beany.club", 443, path)) { |   if (!client.begin(*https, "vrnp.beany.club", 443, "/departures?" QUERY_STRING)) { | ||||||
|     display.fillScreen(COLOR_BG); |     display.fillScreen(COLOR_BG); | ||||||
|     display.setCursor(0, 0); |     display.setCursor(0, 0); | ||||||
|     display.print("begin failed"); |     display.print("begin failed"); | ||||||
| @ -392,70 +265,3 @@ String fetchDepartures(String path) { | |||||||
| 
 | 
 | ||||||
|   return client.getString(); |   return client.getString(); | ||||||
| } | } | ||||||
| 
 |  | ||||||
| char mapUtf8CodepointToCP437German(uint32_t codepoint) { |  | ||||||
|   switch (codepoint) { |  | ||||||
|     case 0x00e4: return 0x84; // ä
 |  | ||||||
|     case 0x00c4: return 0x8e; // Ä
 |  | ||||||
|     case 0x00f6: return 0x94; // ö
 |  | ||||||
|     case 0x00d6: return 0x99; // Ö
 |  | ||||||
|     case 0x00fc: return 0x81; // ü
 |  | ||||||
|     case 0x00dc: return 0x9a; // Ü
 |  | ||||||
|     case 0x00df: return 0xe1; // ß
 |  | ||||||
|     default: return '?'; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| /*
 |  | ||||||
| Maps german umlauts and sharp s from utf8 to cp437 encoding. |  | ||||||
| Other utf8 characters and malformed utf8 are replaced by '?'. |  | ||||||
| The resulting string is truncated to cp437StrSize. |  | ||||||
| */ |  | ||||||
| void utf8ToCP437German(const char *utf8Str, char *cp437Str, size_t cp437StrSize) { |  | ||||||
|   size_t utf8StrLength = strlen(utf8Str); |  | ||||||
|   size_t utf8StrIndex = 0; |  | ||||||
|   size_t cp437StrIndex = 0; |  | ||||||
| 
 |  | ||||||
|   char c; |  | ||||||
| 
 |  | ||||||
|   // One char at the end of the cp437Str is reserved to the terminating null
 |  | ||||||
|   while (utf8StrIndex < utf8StrLength && cp437StrIndex + 1 < cp437StrSize) { |  | ||||||
|     uint8_t cu0 = utf8Str[utf8StrIndex]; |  | ||||||
| 
 |  | ||||||
|     if (cu0 < 0x80) { |  | ||||||
|       // ASCII maps to ASCII
 |  | ||||||
|       c = cu0; |  | ||||||
|       utf8StrIndex++; |  | ||||||
|     } else if ((cu0 & 0b11100000) == 0b11000000) { |  | ||||||
|       // codepoints encoded as two code units may contain german special characters
 |  | ||||||
|       if (utf8StrIndex + 1 >= utf8StrLength) { |  | ||||||
|         // if there's no 10xxxxxxx byte after this one, we've reached the end (malformed)
 |  | ||||||
|         c = '?'; |  | ||||||
|         break; |  | ||||||
|       } else { |  | ||||||
|         // otherwise decode the codepoint and map it to cp437
 |  | ||||||
|         uint8_t cu1 = utf8Str[utf8StrIndex + 1]; |  | ||||||
|         uint32_t cp = (cu0 & 0x1f << 6) | cu1 & 0x3f; |  | ||||||
|         c = mapUtf8CodepointToCP437German(cp); |  | ||||||
|         utf8StrIndex += 2; |  | ||||||
|       } |  | ||||||
|     // for 3 and 4-code unit codepoints just put a ? and skip all their code units
 |  | ||||||
|     // here we dont care whether the string is malformed
 |  | ||||||
|     } else if ((cu0 & 0b11110000) == 0b11100000) { |  | ||||||
|       c = '?'; |  | ||||||
|       utf8StrIndex += 3; |  | ||||||
|     } else if ((cu0 & 0b11111000) == 0b11110000) { |  | ||||||
|       c = '?'; |  | ||||||
|       utf8StrIndex += 4; |  | ||||||
|     } else { |  | ||||||
|       // catch all for malformed utf8
 |  | ||||||
|       c = '?'; |  | ||||||
|       utf8StrIndex++; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     cp437Str[cp437StrIndex] = c; |  | ||||||
|     cp437StrIndex++; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   cp437Str[cp437StrIndex] = '\0'; |  | ||||||
| } |  | ||||||
| @ -16,7 +16,7 @@ | |||||||
| 
 | 
 | ||||||
|       vrnp-static = pkgs.buildGoModule { |       vrnp-static = pkgs.buildGoModule { | ||||||
|         pname = "vrnp"; |         pname = "vrnp"; | ||||||
|         version = "0.0.10"; |         version = "0.0.9"; | ||||||
|         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. | ||||||
|  | |||||||
							
								
								
									
										9
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								main.go
									
									
									
									
									
								
							| @ -17,7 +17,6 @@ import "time" | |||||||
| // JSON unmarshaling types for departure monitor API | // JSON unmarshaling types for departure monitor API | ||||||
| 
 | 
 | ||||||
| type DMResponse struct { | type DMResponse struct { | ||||||
| 	DateTime DMDateTime `json:"dateTime"` |  | ||||||
| 	DepartureList []DMDeparture `json:"departureList"` | 	DepartureList []DMDeparture `json:"departureList"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -214,7 +213,6 @@ func FetchDepartures(c EFAClient, stopId string) (DMResponse, error) { | |||||||
| 
 | 
 | ||||||
| type Departures struct { | type Departures struct { | ||||||
| 	Departures []Departure `json:"departures"` | 	Departures []Departure `json:"departures"` | ||||||
| 	ServerTime string      `json:"serverTime"` |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type Departure struct { | type Departure struct { | ||||||
| @ -275,12 +273,7 @@ func ParseDepartures(response DMResponse, allowedPlatform *string) (Departures, | |||||||
| 		return cmp.Compare(a.Countdown, b.Countdown) | 		return cmp.Compare(a.Countdown, b.Countdown) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	dt := response.DateTime | 	return Departures{ds}, nil | ||||||
| 
 |  | ||||||
| 	return Departures{ |  | ||||||
| 		ds, |  | ||||||
| 		fmt.Sprintf("%02s:%02s", dt.Hour, dt.Minute), |  | ||||||
| 	}, nil |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user