Skip to content

Commit

Permalink
✨ Support querying multiple stations
Browse files Browse the repository at this point in the history
You can now pass -id multiple times to query multiple stations. This
will expose a weatherStation for each station ID that we get a response
for from the API.
  • Loading branch information
daenney committed Jul 22, 2022
1 parent 020e19d commit d780848
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 74 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ Usage of trafikvader:
Parameters:
-id string
Weatherstation ID to retrieve data for (default "REQUIRED")
-id value
station ID to query for, needs to be passed at least 1 time
[..]
-token string
Trafikinfo API token (default "REQUIRED")
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module hemtjan.st/trafikvader
go 1.18

require (
code.dny.dev/trafikinfo v0.3.0
code.dny.dev/trafikinfo v0.5.0
lib.hemtjan.st v0.7.1
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
code.dny.dev/trafikinfo v0.3.0 h1:1IOuLCnA1qkbx4uyxutW6lmZDOPRwOiH70y+kg4cg8M=
code.dny.dev/trafikinfo v0.3.0/go.mod h1:vSLjxprxL9sILsFuPFpJicw8LenBsOS1tsMa4oVCIy4=
code.dny.dev/trafikinfo v0.5.0 h1:d9c885Yy5TRy+yOSFPAXlKCwTviSvbxt7y6mnd5SbdY=
code.dny.dev/trafikinfo v0.5.0/go.mod h1:vSLjxprxL9sILsFuPFpJicw8LenBsOS1tsMa4oVCIy4=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
Expand Down
170 changes: 101 additions & 69 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,36 @@ import (
"os"
"os/signal"
"strconv"
"strings"
"time"

"code.dny.dev/trafikinfo"
"lib.hemtjan.st/client"
"lib.hemtjan.st/transport/mqtt"
)

var (
stationID = flag.String("id", "REQUIRED", "Weatherstation ID to retrieve data for")
apiToken = flag.String("token", "REQUIRED", "Trafikinfo API token")

version = "unknown"
commit = "unknown"
date = "unknown"
)

type stationIDFlag []string

func (s *stationIDFlag) String() string {
return strings.Join(*s, ", ")
}

func (s *stationIDFlag) Set(value string) error {
*s = append(*s, value)
return nil
}

func main() {
var stationIDs stationIDFlag
flag.Var(&stationIDs, "id", "station ID to query for, needs to be passed at least 1 time")
apiToken := flag.String("token", "REQUIRED", "Trafikinfo API token")

flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Parameters:\n\n")
Expand All @@ -43,8 +57,13 @@ func main() {
if *apiToken == "REQUIRED" {
log.Fatalln("A token is required to be able to query the Trafikinfo API")
}
if *stationID == "REQUIRED" {
log.Fatalln("A station ID is required to be able to query the Trafikinfo API")
if len(stationIDs) == 0 {
log.Fatalln("At least one station ID is required to be able to query the Trafikinfo API")
}

stationFilters := make([]trafikinfo.Filter, 0, len(stationIDs))
for _, station := range stationIDs {
stationFilters = append(stationFilters, trafikinfo.Equal("Id", station))
}

req, err := trafikinfo.NewRequest().
Expand All @@ -54,9 +73,9 @@ func main() {
trafikinfo.WeatherStation,
1.0,
).Filter(
trafikinfo.Equal("Id", *stationID),
trafikinfo.Or(stationFilters...),
).Include(
"Active", "Id", "Name", "Measurement",
"Active", "Id", "Name", "Measurement", "RoadNumberNumeric",
),
).Build()
if err != nil {
Expand Down Expand Up @@ -91,29 +110,25 @@ func main() {
}
}()

station := newWeatherStation(data.name, *stationID, data.roadNum, m)

err = station.Feature("currentTemperature").Update(
strconv.FormatFloat(data.tempC, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: %s\n", err)
stations := map[string]client.Device{}
for _, item := range data {
station := newWeatherStation(
item.name, item.id, item.roadNum, m,
)
stations[item.id] = station
}

err = station.Feature("currentRelativeHumidity").Update(
strconv.FormatFloat(data.rhPct, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: %s\n", err)
}

err = station.Feature("precipitation").Update(
strconv.FormatFloat(data.precip, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: %s\n", err)
if len(stations) != len(stationIDs) {
notfound := []string{}
for _, id := range stationIDs {
if _, ok := stations[id]; !ok {
notfound = append(notfound, id)
}
}
log.Printf("Station IDs %s could not be found\n", strings.Join(notfound, ", "))
}

update(data, stations)
log.Println("MQTT: published initial sensor data")

loop:
Expand All @@ -129,53 +144,67 @@ loop:
log.Printf("failed to fetch data from API: %s\n", err)
continue
}
err = station.Feature("currentTemperature").Update(
strconv.FormatFloat(data.tempC, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: failed to publish temperature update: %s\n", err)
}
err = station.Feature("currentRelativeHumidity").Update(
strconv.FormatFloat(data.rhPct, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: failed to publish relative humidity update: %s\n", err)
}
err = station.Feature("precipitation").Update(
strconv.FormatFloat(data.precip, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: failed to publish precipitation update: %s\n", err)
}
update(data, stations)
}
}
os.Exit(0)
}

func update(data []data, stations map[string]client.Device) {
for _, item := range data {
station, ok := stations[item.id]
if !ok {
continue
}

err := station.Feature("currentTemperature").Update(
strconv.FormatFloat(item.tempC, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: failed to publish temperature: %s\n", err)
}

err = station.Feature("currentRelativeHumidity").Update(
strconv.FormatFloat(item.rhPct, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: failed to publish relative humidity: %s\n", err)
}

err = station.Feature("precipitation").Update(
strconv.FormatFloat(item.precip, 'f', 1, 32),
)
if err != nil {
log.Printf("MQTT: failed to publish precipitation: %s\n", err)
}
}
}

type data struct {
id string
name string
tempC float64
rhPct float64
precip float64
roadNum int
}

func retrieve(ctx context.Context, client *http.Client, body []byte) (data, error) {
func retrieve(ctx context.Context, client *http.Client, body []byte) ([]data, error) {
httpReq, err := http.NewRequest(http.MethodPost, trafikinfo.Endpoint, bytes.NewBuffer(body))
if err != nil {
return data{}, err
return nil, err
}

httpReq.Header.Set("content-type", "text/xml")

resp, err := client.Do(httpReq)
if err != nil {
return data{}, err
return nil, err
}
defer resp.Body.Close()

if resp.StatusCode == http.StatusUnauthorized {
return data{}, fmt.Errorf("invalid credentials")
return nil, fmt.Errorf("invalid credentials")
}

if resp.StatusCode == http.StatusBadRequest {
Expand All @@ -192,14 +221,14 @@ func retrieve(ctx context.Context, client *http.Client, body []byte) (data, erro
d := json.NewDecoder(resp.Body)
err := d.Decode(&e)
if err != nil {
return data{}, fmt.Errorf("failed to decode API error response: %w", err)
return nil, fmt.Errorf("failed to decode API error response: %w", err)
}
return data{}, fmt.Errorf("invalid request: %s", e.Response.Result[0].Error.Message)
return nil, fmt.Errorf("invalid request: %s", e.Response.Result[0].Error.Message)
}

if resp.StatusCode != 200 {
io.Copy(io.Discard, resp.Body)
return data{}, fmt.Errorf("got status code: %d %s", resp.StatusCode, http.StatusText(resp.StatusCode))
return nil, fmt.Errorf("got status code: %d %s", resp.StatusCode, http.StatusText(resp.StatusCode))
}

type weatherstationResp struct {
Expand All @@ -214,28 +243,31 @@ func retrieve(ctx context.Context, client *http.Client, body []byte) (data, erro
var wr weatherstationResp
err = d.Decode(&wr)
if err != nil {
return data{}, fmt.Errorf("failed to decode response: %w", err)
}

if len(wr.Response.Result[0].WeatherStation) == 0 {
return data{}, fmt.Errorf("station with ID: %s does not exist", *stationID)
return nil, fmt.Errorf("failed to decode response: %w", err)
}

station := wr.Response.Result[0].WeatherStation[0]
if station.Active != nil && !*station.Active {
return data{}, fmt.Errorf("station with ID: %s is not active", *stationID)
if numRes := len(wr.Response.Result); numRes != 1 {
return nil, fmt.Errorf("expected 1 query result, got %d", numRes)
}

precip := 0.0
if data := station.Measurement.Precipitation.Amount; data != nil {
precip = *data
res := []data{}
for _, station := range wr.Response.Result[0].WeatherStation {
if station.Active != nil && !*station.Active {
continue
}
precip := 0.0
if data := station.Measurement.Precipitation.Amount; data != nil {
precip = *data
}
res = append(res, data{
id: *station.ID,
name: *station.Name,
tempC: *station.Measurement.Air.Temperature,
rhPct: *station.Measurement.Air.RelativeHumidity,
precip: precip,
roadNum: *station.RoadNumber,
})
}

return data{
name: *station.Name,
tempC: *station.Measurement.Air.Temperature,
rhPct: *station.Measurement.Air.RelativeHumidity,
precip: precip,
roadNum: *station.RoadNumber,
}, nil
return res, nil
}

0 comments on commit d780848

Please sign in to comment.