Skip to content

Commit

Permalink
Merge pull request #1 from GJSBRT/feature/power-prices
Browse files Browse the repository at this point in the history
Added power price sources and All In Power
  • Loading branch information
GJSBRT authored Mar 10, 2024
2 parents 1f6f5cd + 0cf84ca commit d7c90f9
Show file tree
Hide file tree
Showing 13 changed files with 416 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
huawei-modbus*
vonkje*
config.yaml
temp/*
11 changes: 11 additions & 0 deletions config.yaml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ http:
port: 8080

modbus:
run: true # Read metrics from inverters every interval
read-metrics-interval: 15 # Seconds

connections:
Expand All @@ -25,3 +26,13 @@ modbus:
unit-id: 2
power-meter: true
luna2000: true

power-prices:
sources:
all-in-power:

victoria-metrics:
url: http://127.0.0.1:8428
# Basic HTTP authentication
username:
password:
5 changes: 5 additions & 0 deletions docs/modbus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Modbus

## Links
- **Modbus definitions** https://www.photovoltaikforum.com/core/attachment/251184-solar-inverter-modbus-interface-definitions-pdf/
- **Helpful modbus definitions guide** https://community.openhab.org/t/reading-data-from-huawei-inverter-sun-2000-3ktl-10ktl-via-modbus-tcp-and-rtu/87670
6 changes: 6 additions & 0 deletions docs/power-prices.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Power prices
Power prices can be obtained from multiple sources. We want to create an enviroment which can accept multiple power price sources.

## Support sources
- [All In Power](https://allinpower.nl)

17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,33 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/goburrow/serial v0.1.0 h1:v2T1SQa/dlUqQiYIT8+Cu7YolfqAi3K96UmhwYyuSrA=
github.com/goburrow/serial v0.1.0/go.mod h1:sAiqG0nRVswsm1C97xsttiYCzSLBmUZ/VSlVLZJ8haA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
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/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
Expand All @@ -27,6 +39,8 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
Expand All @@ -52,6 +66,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
Expand All @@ -69,6 +84,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
2 changes: 1 addition & 1 deletion grafana/sun2000.json
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@
"fullMetaSearch": false,
"includeNullMetadata": true,
"instant": false,
"legendFormat": "Efficiency",
"legendFormat": "{{inverter}} Efficiency",
"range": true,
"refId": "A",
"useBackend": false
Expand Down
16 changes: 12 additions & 4 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import (

"gijs.eu/vonkje/http"
"gijs.eu/vonkje/modbus"
"gijs.eu/vonkje/power_prices"
"gijs.eu/vonkje/packages/victoria_metrics"

"github.com/spf13/viper"
"github.com/sirupsen/logrus"
)

type Config struct {
LogLevel string `mapstructure:"log-level"`
HTTP http.Config `mapstructure:"http"`
Modbus modbus.Config `mapstructure:"modbus"`
LogLevel string `mapstructure:"log-level"`
HTTP http.Config `mapstructure:"http"`
Modbus modbus.Config `mapstructure:"modbus"`
VictoriaMetrics victoria_metrics.Config `mapstructure:"victoria-metrics"`
PowerPrices power_prices.Config `mapstructure:"power-prices"`
}

var (
Expand Down Expand Up @@ -85,12 +89,16 @@ func main() {
if err != nil {
logger.WithError(err).Panic("Failed to create modbus client")
}

go modbusClient.Start()

httpServer := http.New(config.HTTP, errChannel, stopCtx, logger)
go httpServer.Start()

victoriaMetricsClient := victoria_metrics.New(config.VictoriaMetrics)

powerPricesClient := power_prices.New(config.PowerPrices, errChannel, stopCtx, logger, victoriaMetricsClient)
go powerPricesClient.Start()

<-stopCtx.Done()

modbusClient.Close()
Expand Down
6 changes: 6 additions & 0 deletions modbus/modbus.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Connection struct {
}

type Config struct {
Run bool `mapstructure:"run"`
ReadMetricsInterval uint `mapstructure:"read-metrics-interval"`
Connections []ConnectionConfig `mapstructure:"connections"`
}
Expand Down Expand Up @@ -99,6 +100,11 @@ func (m *Modbus) Close() {
}

func (m *Modbus) Start() {
if !m.config.Run {
m.logger.Warn("Modbus metrics collector is disabled")
return
}

m.logger.Info("Starting modbus metrics collector")

m.updateMetrics()
Expand Down
24 changes: 24 additions & 0 deletions packages/victoria_metrics/entities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package victoria_metrics

type Config struct {
URL string
Username string
Password string
}

type VictoriaMetricsRequest struct {
Metric map[string]string `json:"metric"`
Values []float64 `json:"values"`
Timestamps []int64 `json:"timestamps"`
}

type VictoriaMetricsQueryResponse struct {
Status string `json:"status"`
Data struct {
ResultType string `json:"resultType"`
Result []struct {
Metric map[string]string `json:"metric"`
Values [][]interface{} `json:"values"`
} `json:"result"`
} `json:"data"`
}
128 changes: 128 additions & 0 deletions packages/victoria_metrics/victoria_metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package victoria_metrics

import (
"fmt"
"time"
"bytes"
"errors"
"net/url"
"strconv"
"net/http"
"io/ioutil"
"encoding/json"
"encoding/base64"
)

type VictoriaMetrics struct {
Config Config
Client *http.Client
}

// New creates a new GoVictoria instance
func New(config Config) *VictoriaMetrics {
return &VictoriaMetrics{
Config: config,
Client: &http.Client{},
}
}

// SendMetrics sends the metrics to VictoriaMetrics
func (g *VictoriaMetrics) SendMetrics(requests []VictoriaMetricsRequest) error {
if len(requests) == 0 {
return errors.New("No requests to send")
}

// Loop through the request and build the body
body := ""
for _, requestBody := range requests {
jsonRequest, err := json.Marshal(requestBody)
if err != nil {
return err
}

body += string(jsonRequest)
}

// Create the request to Victoria Metrics
request, err := http.NewRequest("POST", g.Config.URL+"/api/v1/import", bytes.NewBuffer([]byte(body)))
request.Header.Add("Authorization", "Basic "+BasicAuth(g.Config.Username, g.Config.Password))
request.Header.Add("User-Agent", "Vonkje (github.com/GJSBRT/vonkje)")

// Send the request to Victoria Metrics
response, err := g.Client.Do(request)
if err != nil {
return err
}

// Close the response body
defer response.Body.Close()

// Check if the status code is not 204
if response.StatusCode != http.StatusNoContent {
return errors.New(fmt.Sprintf("Victoria Metrics returned a non-200 status code: %d", response.StatusCode))
}

return nil
}

// QueryTimeRange queries Victoria Metrics for metrics in a time range
func (g *VictoriaMetrics) QueryTimeRange(promql string, startTime time.Time, endTime time.Time, step string) (VictoriaMetricsQueryResponse, error) {
// Check if the start time is before the end time
if startTime.After(endTime) {
return VictoriaMetricsQueryResponse{}, errors.New("Start time must be before end time")
}

// Add the query parameters to the request
params := url.Values{}
params.Add("query", promql)
params.Add("start", strconv.FormatInt(startTime.Unix(), 10))
params.Add("end", strconv.FormatInt(endTime.Unix(), 10))
params.Add("step", step)

url := g.Config.URL + "/api/v1/query_range?" + params.Encode()

// Create the request to Victoria Metrics
request, err := http.NewRequest("GET", url, nil)
if err != nil {
return VictoriaMetricsQueryResponse{}, err
}

// Add the query parameters to the request
request.Header.Add("Authorization", "Basic "+BasicAuth(g.Config.Username, g.Config.Password))
request.Header.Add("User-Agent", "Vonkje (github.com/GJSBRT/vonkje)")

// Send the request to Victoria Metrics
response, err := g.Client.Do(request)
if err != nil {
return VictoriaMetricsQueryResponse{}, err
}

// Close the response body
defer response.Body.Close()

// Check if the status code is not 200
if response.StatusCode != http.StatusOK {
return VictoriaMetricsQueryResponse{}, errors.New(fmt.Sprintf("Victoria Metrics returned a non-200 status code: %d", response.StatusCode))
}

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

// Unmarshal the response
var metrics VictoriaMetricsQueryResponse
err = json.Unmarshal([]byte(body), &metrics)
if err != nil {
return VictoriaMetricsQueryResponse{}, err
}

return metrics, nil
}

// BasicAuth returns the base64 encoded string for basic auth
func BasicAuth(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
Loading

0 comments on commit d7c90f9

Please sign in to comment.