diff --git a/go.mod b/go.mod index 81bf773..b6d5464 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,10 @@ go 1.18 require ( github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect + github.com/mazznoer/colorgrad v0.9.0 // indirect + github.com/mazznoer/csscolorparser v0.1.2 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.1 // indirect diff --git a/go.sum b/go.sum index 5bb7372..de7276c 100644 --- a/go.sum +++ b/go.sum @@ -126,8 +126,14 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mazznoer/colorgrad v0.9.0 h1:C3Y0l0oIJhXLcbmIRxgmBSUAus4np1t/gUFt7fHukDA= +github.com/mazznoer/colorgrad v0.9.0/go.mod h1:WX2R9wt9B47+txJZVVpM9LY+LAGIdi4lTI5wIyreDH4= +github.com/mazznoer/csscolorparser v0.1.2 h1:/UBHuQg792ePmGFzTQAC9u+XbFr7/HzP/Gj70Phyz2A= +github.com/mazznoer/csscolorparser v0.1.2/go.mod h1:Aj22+L/rYN/Y6bj3bYqO3N6g1dtdHtGfQ32xZ5PJQic= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= diff --git a/internal/config/config.go b/internal/config/config.go index 9662e35..cd8b792 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,7 +6,7 @@ import ( ) type Conf struct { - ISOUser string + ISOUser string ISOPassword string } @@ -15,13 +15,12 @@ func Config() *Conf { viper.SetConfigType("yaml") viper.AddConfigPath(".") err := viper.ReadInConfig() // Find and read the config file - if err != nil { // Handle errors reading the config file + if err != nil { // Handle errors reading the config file panic(fmt.Errorf("fatal error config file: %w", err)) } return &Conf{ - ISOUser: viper.Get("iso.user").(string), + ISOUser: viper.Get("iso.user").(string), ISOPassword: viper.Get("iso.password").(string), } } - diff --git a/pkg/energy/energy.go b/pkg/energy/energy.go index 26e581f..1f0c6d9 100644 --- a/pkg/energy/energy.go +++ b/pkg/energy/energy.go @@ -3,10 +3,14 @@ package energy import ( "context" "fmt" + "log" "math" + "strings" "time" "github.com/amigan/energyd/pkg/isoclient" + "github.com/lucasb-eyer/go-colorful" + "github.com/mazznoer/colorgrad" ) type Energy struct { @@ -14,34 +18,67 @@ type Energy struct { } type LoadProfile struct { - Current int - Peak int - Lowest int - PeakHr int + Time time.Time + Current int + Peak int + Lowest int + PeakHr int LowestHr int - PctPeak int - HoursAway int + PctPeak int + HoursAway int + HighFuel string + MarginalFuels MarginalFuels } -func (e *Energy) Compute() { +type MarginalFuels []string + +func (mf MarginalFuels) String() string { + return strings.Join(mf, ", ") +} + +func (e *Energy) Compute() error { ic := e.iso load, err := ic.GetCurrentLoad() if err != nil { - panic(err) + return err } gphd, err := ic.GetHourlyLoadForecast() if err != nil { - panic(err) + return err + } + + fm, err := ic.GetGenFuelMixes() + if err != nil { + return err } lp := LoadProfile{ - Current: int(load.LoadMW), + Time: load.BeginDate, + Current: int(load.LoadMW), LowestHr: -1, } + if len(fm) > 0 { + highGen := int(fm[0].GenMW) + highFuel := fm[0] + + for _, mix := range fm { + if int(mix.GenMW) > highGen { + highGen = int(mix.GenMW) + highFuel = mix + } + + if mix.Marginal { + lp.MarginalFuels = append(lp.MarginalFuels, mix.FuelCategory) + } + } + + lp.HighFuel = highFuel.FuelCategory + } + for h, hf := range gphd.HourlyLoadForecast { if int(hf.LoadMW) > lp.Peak { lp.Peak = int(hf.LoadMW) @@ -54,21 +91,85 @@ func (e *Energy) Compute() { } } - - hour := time.Now().Hour() - lp.PctPeak = int(float32(load.LoadMW / float32(lp.Peak)) * 100) + lp.PctPeak = int(float32(load.LoadMW/float32(lp.Peak)) * 100) lp.HoursAway = int(math.Abs(float64(hour) - float64(lp.PeakHr))) - fmt.Printf("lp %#v\n", lp) + fmt.Println(lp.String()) + + return nil +} + +type Colors struct { + Mix [2]colorful.Color + Peak [2]colorful.Color +} + +func vaporwavePalette() []string { + return []string{ + "#01cdfe", // blue + "#05ffa1", // green + "#fffb96", // gold + "#ff71ce", // pinkred + "#b967ff", // purple + } +} + +func outrunPalette() []string { + return []string{ + "#2de2e6", // blue + "#00ff1b", // green + "#ff6c11", // gold + "#ff3864", // pinkred + "#7930ff", // purple + } +} + +func (lp *LoadProfile) PeakScaleColors() []string { + var scale []string + palette := outrunPalette() + + nowHr := time.Now().Hour() // XXX: get this from lp + + //if lp.HoursAway > + + _, _ = palette, nowHr + + return scale +} + +func (lp *LoadProfile) String() string { + return fmt.Sprintf("%s\tC: %d\tP: %d\tL: %d\tPeakHr: %d\tLowestHr: %d\t%%Pk: %d\tHrAway: %d\tPkFuel: %s\tMarginal: %s", + lp.Time.String(), lp.Current, lp.Peak, lp.Lowest, lp.PeakHr, lp.LowestHr, lp.PctPeak, lp.HoursAway, lp.HighFuel, lp.MarginalFuels) +} + +func (lp *LoadProfile) Colors() Colors { + c := Colors{} + + peak, err := colorgrad.NewGradient().Domain(float64(lp.Lowest), float64(lp.Peak)).HtmlColors(lp.PeakScaleColors()...).Build() + + if err != nil { + panic(err) + } + + c.Peak[0] = peak.At(float64(time.Now().Hour())) + + return c } func (e *Energy) Go(ctx context.Context) { - tick := time.NewTicker(time.Minute*15) - e.Compute() + tick := time.NewTicker(time.Minute * 15) + err := e.Compute() + if err != nil { + log.Println(err) + } + for { select { case <-tick.C: - e.Compute() + err := e.Compute() + if err != nil { + log.Println(err) + } case <-ctx.Done(): return } diff --git a/pkg/isoclient/isoclient.go b/pkg/isoclient/isoclient.go index 41842c3..1e33a4e 100644 --- a/pkg/isoclient/isoclient.go +++ b/pkg/isoclient/isoclient.go @@ -7,6 +7,8 @@ import ( "io/ioutil" "log" "net/http" + "os" + "strings" "time" "github.com/amigan/energyd/internal/config" @@ -15,6 +17,7 @@ import ( type Client interface { GetCurrentLoad() (*FiveMinSystemLoad, error) GetHourlyLoadForecast() (*HourlyLoadForecasts, error) + GetGenFuelMixes() (GenFuelMixes, error) } type client struct { @@ -27,6 +30,7 @@ func New(cfg *config.Conf) Client { c := &client{ user: cfg.ISOUser, passwd: cfg.ISOPassword, + Debug: os.Getenv("DEBUG") == "true", } return c @@ -122,3 +126,49 @@ func (c *client) GetHourlyLoadForecast() (*HourlyLoadForecasts, error) { return &hlfc.HourlyLoadForecasts, nil } + +type GenFuelMix struct { + BeginDate time.Time `json:"BeginDate"` + GenMW float32 `json:"GenMW"` + FuelCategoryRollup string `json:"FuelCategoryRollup"` + FuelCategory string `json:"FuelCategory"` + Marginal YNFlag `json:"MarginalFlag"` +} + +type YNFlag bool + +func (ynf *YNFlag) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + switch strings.ToLower(s) { + case "y": + *ynf = true + case "n": + *ynf = false + } + + return nil +} + +type GenFuelMixes []GenFuelMix + +const GETGENFUELMIXES = "/genfuelmix/current" + +func (c *client) GetGenFuelMixes() (GenFuelMixes, error) { + var fm struct { + GenFuelMixes struct { + GenFuelMix GenFuelMixes `json:"GenFuelMix"` + } `json:"GenFuelMixes"` + } + err := c.makeRequest(GETGENFUELMIXES, &fm) + if err != nil { + return nil, err + } + + return fm.GenFuelMixes.GenFuelMix, nil + +}