Skip to content

Commit

Permalink
Add entsoe power price source
Browse files Browse the repository at this point in the history
  • Loading branch information
GJSBRT committed Mar 15, 2024
1 parent 58d04af commit 12a7237
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 19 deletions.
18 changes: 9 additions & 9 deletions power_prices/power_prices.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import (
)

type PowerPriceSourceConfigs struct {
AllInPowerConfig AllInPowerConfig `json:"all-in-power"`
Entsoe EntsoeConfig `mapstructure:"entsoe"`
}

type Config struct {
Sources PowerPriceSourceConfigs `json:"sources"`
Sources PowerPriceSourceConfigs `mapstructure:"sources"`
}

type PowerPrices struct {
Expand Down Expand Up @@ -45,7 +45,8 @@ func New(
logger *logrus.Logger,
victoriaMetrics *victoria_metrics.VictoriaMetrics,
) *PowerPrices {
sources = append(sources, newAllInPower(config.Sources.AllInPowerConfig))
sources = append(sources, newAllInPower())
sources = append(sources, newEntsoe(config.Sources.Entsoe))

return &PowerPrices{
Config: config,
Expand All @@ -57,7 +58,7 @@ func New(
}

func (pp *PowerPrices) addPricesOfSource(source PowerPriceSource) error {
prices, err := source.GetPricesKwH(time.Now().Add(24 * time.Hour))
prices, err := source.GetPricesKwH(time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC))
if err != nil {
return err
}
Expand Down Expand Up @@ -98,17 +99,16 @@ func (pp *PowerPrices) updateMetrics() error {
func (pp *PowerPrices) Start() {
pp.logger.Info("Starting power price collector")

err := pp.updateMetrics()
if err != nil {
pp.errChannel <- err
}
now := time.Now()

nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 1, 0, 0, 0, time.Local)
durationUntilMidnight := nextMidnight.Sub(now)

time.Sleep(durationUntilMidnight)

err := pp.updateMetrics()
if err != nil {
pp.errChannel <- err
}

go pp.Start()
}
20 changes: 20 additions & 0 deletions power_prices/power_prices_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package power_prices

// import (
// "time"
// "testing"
// )

// func Test_Entsoe_GetPricesKwH(t *testing.T) {
// entsoeClient := newEntsoe(EntsoeConfig{
// Domain: "",
// SecurityToken: "",
// })

// prices, err := entsoeClient.GetPricesKwH(time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC))
// if err != nil {
// t.Fatalf("Error getting prices: %s", err)
// }

// t.Log(prices)
// }
16 changes: 6 additions & 10 deletions power_prices/source_allinpower.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
package power_prices

import (
"fmt"
"time"
"net/http"
"io/ioutil"
"encoding/json"
)

type AllInPowerConfig struct {}

type AllInPower struct {
name string
Config AllInPowerConfig
Resolution uint
Client *http.Client
}

func newAllInPower(config AllInPowerConfig) *AllInPower {
func newAllInPower() *AllInPower {
return &AllInPower{
name: "all-in-power",
Config: config,
Resolution: 60,
Client: &http.Client{},
}
}
Expand All @@ -29,7 +24,7 @@ func (a *AllInPower) GetName() string {
return a.name
}

type spotMarketPriceResponse struct {
type allInPowerSpotMarketPriceResponse struct {
Id int `json:"id"`
Timestamps []string `json:"timestamps"`
Created string `json:"created"`
Expand All @@ -41,7 +36,8 @@ type spotMarketPriceResponse struct {
}

func (a *AllInPower) GetPricesKwH(timestamp time.Time) (map[time.Time]float64, error) {
request, err := http.NewRequest("GET", "https://api.allinpower.nl/troodon/api/p/spot_market/prices/?date="+timestamp.Format(time.DateOnly)+"&product_type=ELK", nil)
requestUrl := fmt.Sprintf("https://api.allinpower.nl/troodon/api/p/spot_market/prices/?date=%s&product_type=ELK", timestamp.Add(24 * time.Hour).Format(time.DateOnly))
request, err := http.NewRequest("GET", requestUrl, nil)
if err != nil {
return nil, err
}
Expand All @@ -62,7 +58,7 @@ func (a *AllInPower) GetPricesKwH(timestamp time.Time) (map[time.Time]float64, e
return nil, err
}

var responseBody spotMarketPriceResponse
var responseBody allInPowerSpotMarketPriceResponse
err = json.Unmarshal(body, &responseBody)
if err != nil {
return nil, err
Expand Down
139 changes: 139 additions & 0 deletions power_prices/source_entsoe.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package power_prices

import (
"fmt"
"time"
"strconv"
"net/http"
"io/ioutil"
"encoding/xml"
)

const entsoeTimestampFormat = "200601021504"

type EntsoeConfig struct {
SecurityToken string `mapstructure:"security-token"`
Domain string `mapstructure:"domain"`
}

type Entsoe struct {
name string
Client *http.Client
Config EntsoeConfig
}

func newEntsoe(config EntsoeConfig) *Entsoe {
return &Entsoe{
name: "entsoe",
Client: &http.Client{},
Config: config,
}
}

func (e *Entsoe) GetName() string {
return e.name
}

type entsoeDayAheadResponse struct {
MRID string `xml:"mRID"`
RevisionNumber string `xml:"revisionNumber"`
Type string `xml:"type"`
SenderMarketParticipantMRID struct {
Text string `xml:",chardata"`
CodingScheme string `xml:"codingScheme,attr"`
} `xml:"sender_MarketParticipant.mRID"`
SenderMarketParticipantMarketRoleType string `xml:"sender_MarketParticipant.marketRole.type"`
ReceiverMarketParticipantMRID struct {
Text string `xml:",chardata"`
CodingScheme string `xml:"codingScheme,attr"`
} `xml:"receiver_MarketParticipant.mRID"`
ReceiverMarketParticipantMarketRoleType string `xml:"receiver_MarketParticipant.marketRole.type"`
CreatedDateTime string `xml:"createdDateTime"`
PeriodTimeInterval struct {
Text string `xml:",chardata"`
Start string `xml:"start"`
End string `xml:"end"`
} `xml:"period.timeInterval"`
TimeSeries []struct {
Text string `xml:",chardata"`
MRID string `xml:"mRID"`
BusinessType string `xml:"businessType"`
InDomainMRID struct {
Text string `xml:",chardata"`
CodingScheme string `xml:"codingScheme,attr"`
} `xml:"in_Domain.mRID"`
OutDomainMRID struct {
Text string `xml:",chardata"`
CodingScheme string `xml:"codingScheme,attr"`
} `xml:"out_Domain.mRID"`
CurrencyUnitName string `xml:"currency_Unit.name"`
PriceMeasureUnitName string `xml:"price_Measure_Unit.name"`
CurveType string `xml:"curveType"`
Period struct {
Text string `xml:",chardata"`
TimeInterval struct {
Text string `xml:",chardata"`
Start string `xml:"start"`
End string `xml:"end"`
} `xml:"timeInterval"`
Resolution string `xml:"resolution"`
Point []struct {
Text string `xml:",chardata"`
Position string `xml:"position"`
PriceAmount string `xml:"price.amount"`
} `xml:"Point"`
} `xml:"Period"`
} `xml:"TimeSeries"`
}

func (e *Entsoe) GetPricesKwH(timestamp time.Time) (map[time.Time]float64, error) {
startTimestamp := timestamp.Format(entsoeTimestampFormat)
endTimestamp := timestamp.Add(24 * time.Hour).Format(entsoeTimestampFormat)

requestUrl := fmt.Sprintf("https://web-api.tp.entsoe.eu/api?securityToken=%s&documentType=A44&in_Domain=%s&out_Domain=%s&periodStart=%s&periodEnd=%s", e.Config.SecurityToken, e.Config.Domain, e.Config.Domain, startTimestamp, endTimestamp)
request, err := http.NewRequest("GET", requestUrl, nil)
if err != nil {
return nil, err
}
request.Header.Del("User-Agent")

response, err := e.Client.Do(request)
if err != nil {
return nil, err
}
defer response.Body.Close()

if response.StatusCode != http.StatusOK {
return nil, ErrFailedToRetrieveData
}

body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}

var responseBody entsoeDayAheadResponse
err = xml.Unmarshal(body, &responseBody)
if err != nil {
return nil, err
}

prices := make(map[time.Time]float64)
startingTimestamp, err := time.Parse("2006-01-02T15:04Z", responseBody.TimeSeries[1].Period.TimeInterval.Start)
if err != nil {
return nil, err
}

for i, entry := range responseBody.TimeSeries[1].Period.Point {
timestamp := startingTimestamp.Add(time.Duration(i) * time.Hour)

price, err := strconv.ParseFloat(entry.PriceAmount, 2)
if err != nil {
return nil, err
}

prices[timestamp] = price / 1000
}

return prices, nil
}

0 comments on commit 12a7237

Please sign in to comment.