make it work for my tram board

adds webserer with endpoint /line?number=3

you need to specify a post body text using b64-encoded, semicolon-separated names of the stops

index of stop name corresponds to returned value (led index)
This commit is contained in:
2026-07-03 11:41:25 +02:00
parent 3b84fad254
commit 96f8cfa55c
5 changed files with 194 additions and 29 deletions

View File

@@ -1,37 +1,62 @@
package main package main
import ( import (
// "fmt"
"log" "log"
"shellixa/puvetra/internal/maps" // "maps"
trmaps "shellixa/puvetra/internal/maps"
"shellixa/puvetra/internal/types" "shellixa/puvetra/internal/types"
/*"slices"
"strings" */
"time"
) )
func main() { func main() {
log.Printf("Starting up..\n") log.Printf("Starting up..\n")
trips := maps.GetTrips() trams := trmaps.GetTrips()
var allTrams []types.MapTrip var allTrams []types.MapTrip
for _, t := range trips { for _, t := range trams {
if t.Mode == "TRAM" { if t.Mode == "TRAM" && t.Trips[0].RouteShortName == "3" {
allTrams = append(allTrams, t) allTrams = append(allTrams, t)
} }
} }
var traminfo []map[string]string var traminfo = make(map[string]types.MapTrip)
now := time.Now()
for _, tr := range allTrams { for _, tr := range allTrams {
data := map[string]string{} // Überschreibe [tripId] nur, wenn (OR)
data["line"] = tr.Trips[0].RouteShortName // - [tripId] == nil
data["currentStation"] = tr.From.Name // - departure < now && arrival > [tripId].departure
data["nextStation"] = tr.To.Name storedTram := traminfo[tr.Trips[0].TripId]
data["departure"] = tr.Departure
data["arrival"] = tr.Arrival if tr.Departure.Sub(now).Abs() < storedTram.Departure.Sub(now).Abs() {
traminfo = append(traminfo, data) // Current is past now, Stored is future now
traminfo[tr.Trips[0].TripId] = tr
}
} }
log.Printf("%+v\n", traminfo) log.Printf("%+v\n", traminfo)
log.Printf("%d", len(traminfo)) log.Printf("%d", len(traminfo))
} /*
activeLights := make(map[int]bool)
for _, tram := range allTrams {
// Calculate current Station name depending on current time
realStation := tram.From
if now.Sub(depTime) > arrTime.Sub(now) {
// log.Printf("> %v, %v\n", now.Sub(tram.Departure), tram.Arrival.Sub(now))
realStation = tram.To
}
// log.Printf("From='%s' To='%s' Real='%s'\n", tram.From.Name, tram.To.Name, realStation.Name)
activeLights[stationLookupTable[strings.ToLower(realStation.Name)]] = true
}
fmt.Println(slices.Collect(maps.Keys(activeLights)))
*/
}

98
cmd/web/main.go Normal file
View File

@@ -0,0 +1,98 @@
package main
import (
"encoding/base64"
"encoding/json"
"io"
"log"
"maps"
"net/http"
trmaps "shellixa/puvetra/internal/maps"
"shellixa/puvetra/internal/types"
"slices"
"strings"
"time"
)
func main() {
mux := http.NewServeMux()
server := &http.Server{
Handler: mux,
Addr: ":8080",
}
mux.HandleFunc("POST /line", GetLineTrams)
err := server.ListenAndServe()
if err != nil {
log.Println(err)
}
}
func GetLineTrams(w http.ResponseWriter, r *http.Request) {
// log.Printf("%+v\n", r)
b64Body := base64.NewDecoder(base64.StdEncoding, r.Body)
defer r.Body.Close()
body, _ := io.ReadAll(b64Body)
stationSeq := strings.Split(string(body), ";")
tramLine := r.URL.Query().Get("number")
trams := trmaps.GetTrips()
var allTrams []types.MapTrip
for _, t := range trams {
if t.Mode == "TRAM" && t.Trips[0].RouteShortName == tramLine {
allTrams = append(allTrams, t)
}
}
var traminfo = make(map[string]types.MapTrip)
now := time.Now()
for _, tr := range allTrams {
// Überschreibe [tripId] nur, wenn (OR)
// - [tripId] == nil
// - departure < now && arrival > [tripId].departure
storedTram := traminfo[tr.Trips[0].TripId]
if tr.Departure.Sub(now).Abs() < storedTram.Departure.Sub(now).Abs() {
// Current is past now, Stored is future now
traminfo[tr.Trips[0].TripId] = tr
}
}
stationLookupTable := make(map[string]int)
for i, v := range stationSeq {
stationLookupTable[strings.ToLower(v)] = i
}
activeLights := make(map[int]bool)
for _, tram := range slices.Collect(maps.Values(traminfo)) {
// Calculate current Station name depending on current time
realStation := tram.From
if tram.Departure.Sub(now).Abs() > tram.Arrival.Sub(now).Abs() {
// log.Printf("> %v, %v\n", now.Sub(depTime), arrTime.Sub(now))
realStation = tram.To
}
// log.Printf("From='%s' To='%s' Real='%s'\n", tram.From.Name, tram.To.Name, realStation.Name)
activeLights[stationLookupTable[strings.ToLower(realStation.Name)]] = true
}
lightIds := slices.Collect(maps.Keys(activeLights))
// log.Println(lightIds)
enc := json.NewEncoder(w)
err := enc.Encode(lightIds)
if err != nil {
log.Println(err)
}
}

View File

@@ -2,6 +2,7 @@ package maps
import ( import (
"encoding/json" "encoding/json"
"io"
"log" "log"
"net/http" "net/http"
"shellixa/puvetra/internal/types" "shellixa/puvetra/internal/types"
@@ -16,9 +17,12 @@ func GetTrips() []types.MapTrip {
} }
q := req.URL.Query() q := req.URL.Query()
now := time.Now().Format(time.RFC3339) now := time.Now()
q.Add("startTime", now) delta := time.Minute * 5
q.Add("endTime", now) lowerBound := now.Add(-delta).Format(time.RFC3339)
upperBound := now.Add(delta).Format(time.RFC3339)
q.Add("startTime", lowerBound)
q.Add("endTime", upperBound)
q.Add("min", "51.1626,13.5609") q.Add("min", "51.1626,13.5609")
q.Add("max", "50.9973,13.8596") q.Add("max", "50.9973,13.8596")
q.Add("zoom", "14") q.Add("zoom", "14")
@@ -34,9 +38,11 @@ func GetTrips() []types.MapTrip {
defer response.Body.Close() defer response.Body.Close()
var trips []types.MapTrip var trips []types.MapTrip
if err := json.NewDecoder(response.Body).Decode(&trips); err != nil { buffer, _ := io.ReadAll(response.Body)
if err = json.Unmarshal(buffer, &trips); err != nil {
log.Fatal(err) log.Fatal(err)
} }
log.Printf("component='Transitous API' message='Response was %d bytes'", len(buffer))
return trips return trips

View File

@@ -0,0 +1,34 @@
package timetable
import (
"encoding/json"
"fmt"
"log"
"net/http"
"shellixa/puvetra/internal/types"
)
func GetTrip(tripId string) types.Trip {
client := http.Client{}
url := fmt.Sprintf("https://api.transitous.org/api/v6/trip?tripId=%s", tripId)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
log.Fatal(err)
}
response, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer response.Body.Close()
var trip types.Trip
if err := json.NewDecoder(response.Body).Decode(&trip); err != nil {
log.Fatal(err)
}
return trip
}

View File

@@ -1,5 +1,7 @@
package types package types
import "time"
type TripMeta struct { type TripMeta struct {
TripId string TripId string
@@ -12,20 +14,20 @@ type MapTrip struct {
RouteColor string RouteColor string
Mode string Mode string
Distance float32 Distance float32
Departure string Departure time.Time
Arrival string Arrival time.Time
From Place From Place
To Place To Place
ScheduledDeparture string ScheduledDeparture time.Time
ScheduledArrival string ScheduledArrival time.Time
RealTime bool RealTime bool
Polyline string Polyline string
} }
type Trip struct { type Trip struct {
Duration int64 Duration int64
StartTime string StartTime time.Time
EndTine string EndTine time.Time
Transfers int Transfers int
Id string Id string
Legs []TripLeg Legs []TripLeg
@@ -36,10 +38,10 @@ type TripLeg struct {
From Place From Place
To Place To Place
Duration int64 Duration int64
StartTime string StartTime time.Time
EndTime string EndTime time.Time
ScheduledStartTime string ScheduledStartTime time.Time
ScheduledEndTime string ScheduledEndTime time.Time
RealTime bool RealTime bool
Scheduled bool Scheduled bool
Distance float32 Distance float32
@@ -53,8 +55,8 @@ type Place struct {
StopId string StopId string
Lat float32 Lat float32
Lon float32 Lon float32
Arrival string Arrival time.Time
Departure string Departure time.Time
Track string Track string
StopCode string StopCode string
Description string Description string