Author SHA1 Message Date
paul 72734c7faa Release 0.0.8
Build image / build-image (push) Successful in 3m2s
2025-04-25 18:27:42 +02:00
paul 5deb037fe5 Apply go fmt 2025-04-25 18:27:23 +02:00
paul 6e78235127 Add VRNP_CLIENT_ALLOWLIST env var 2025-04-25 18:26:20 +02:00
paul 5d5b893948 Sort departures by countdown
Build image / build-image (push) Successful in 2m45s
2025-04-25 17:09:29 +02:00
paul 4a73099460 Draw tiny train when train is arriving 2025-04-25 16:33:00 +02:00
paul addc90d856 Bump version
Build image / build-image (push) Successful in 2m58s
2025-04-25 13:39:17 +02:00
paul d23be3062a Merge pull request 'Send EFA requests to multiple servers' (#2) from feature/multiple-servers into main
Reviewed-on: #2
2025-04-25 13:38:30 +02:00
3 changed files with 34 additions and 12 deletions
+10 -5
View File
@@ -176,14 +176,19 @@ void logic_loop() {
int line = 0; int line = 0;
for (JsonVariant departure : departures["departures"].as<JsonArray>()) { for (JsonVariant departure : departures["departures"].as<JsonArray>()) {
const char *directionStr = departure["direction"].as<const char *>();
display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line); display.setCursor(MAIN_X, MAIN_Y + (CY + 3) * line);
display.printf("%2s %-15.15s %6s", display.printf("%2s %-15.15s ",
departure["symbol"].as<const char*>(), departure["symbol"].as<const char*>(),
directionStr, departure["direction"].as<const char *>()
departure["leaving"].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++; line++;
} }
+1 -1
View File
@@ -16,7 +16,7 @@
vrnp-static = pkgs.buildGoModule { vrnp-static = pkgs.buildGoModule {
pname = "vrnp"; pname = "vrnp";
version = "0.0.5"; 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.
+23 -6
View File
@@ -1,5 +1,6 @@
package main package main
import "cmp"
import "encoding/json" import "encoding/json"
import "errors" import "errors"
import "fmt" import "fmt"
@@ -50,6 +51,12 @@ type EFAClient interface {
BuildRequest(string) (*http.Request, error) BuildRequest(string) (*http.Request, error)
} }
var allEfaClients []EFAClient = []EFAClient{
BwegtEFAClient{},
VRNEFAClient{},
KVVEFAClient{},
}
type VRNEFAClient struct { type VRNEFAClient struct {
} }
@@ -97,6 +104,7 @@ func (c KVVEFAClient) BuildRequest(stopId string) (*http.Request, error) {
form.Set("useRealtime", "1") form.Set("useRealtime", "1")
form.Set("limit", "10") form.Set("limit", "10")
form.Set("mode", "direct") form.Set("mode", "direct")
form.Set("useRealtime", "1")
form.Set("outputFormat", "json") form.Set("outputFormat", "json")
body := strings.NewReader(form.Encode()) body := strings.NewReader(form.Encode())
@@ -179,6 +187,7 @@ type Departure struct {
Symbol string `json:"symbol"` Symbol string `json:"symbol"`
Direction string `json:"direction"` Direction string `json:"direction"`
Leaving string `json:"leaving"` Leaving string `json:"leaving"`
Countdown int `json:"-"`
} }
func ParseDepartures(response DMResponse, allowedPlatform *string) (Departures, error) { func ParseDepartures(response DMResponse, allowedPlatform *string) (Departures, error) {
@@ -223,24 +232,32 @@ func ParseDepartures(response DMResponse, allowedPlatform *string) (Departures,
d.ServingLine.Symbol, d.ServingLine.Symbol,
direction, direction,
leaving, leaving,
countdown,
}) })
} }
slices.SortFunc(ds, func(a Departure, b Departure) int {
return cmp.Compare(a.Countdown, b.Countdown)
})
return Departures{ds}, nil return Departures{ds}, nil
} }
func main() { func main() {
password := os.Getenv("VRNP_PASSWORD") password := os.Getenv("VRNP_PASSWORD")
if len(password) == 0 { if len(password) == 0 {
panic("Required environment variable VRNP_PASSWORD is not set") log.Fatal("Required environment variable VRNP_PASSWORD is not set")
} }
clientAllowlist := strings.Split(os.Getenv("VRNP_CLIENT_ALLOWLIST"), ",")
// Use round-robin to send incoming requests to different servers // Use round-robin to send incoming requests to different servers
var efaClient atomic.Uint64 var efaClient atomic.Uint64
efaClients := []EFAClient{ var efaClients []EFAClient
BwegtEFAClient{}, for _, efaClient := range allEfaClients {
VRNEFAClient{}, if len(clientAllowlist) != 0 && slices.Contains(clientAllowlist, efaClient.GetName()) {
KVVEFAClient{}, efaClients = append(efaClients, efaClient)
}
} }
http.HandleFunc("/departures", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/departures", func(w http.ResponseWriter, r *http.Request) {
@@ -264,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)