mirror of
https://github.com/tess1o/go-ecoflow-exporter.git
synced 2025-10-23 03:51:55 +00:00
First commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.idea
|
32
Dockerfile
Normal file
32
Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
# Stage 1: Build stage
|
||||
FROM golang:1.22-alpine AS build
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy and download dependencies
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# Copy the source code
|
||||
COPY main.go ./
|
||||
|
||||
# Build the Go application
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o ecoflow-exporter .
|
||||
|
||||
# Stage 2: Final stage
|
||||
FROM alpine:edge
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy the binary from the build stage
|
||||
COPY --from=build /app/ecoflow-exporter .
|
||||
|
||||
# Set the timezone and install CA certificates
|
||||
RUN apk --no-cache add ca-certificates tzdata
|
||||
|
||||
EXPOSE 2112
|
||||
|
||||
# Set the entrypoint command
|
||||
ENTRYPOINT ["/app/ecoflow-exporter"]
|
42
README.md
42
README.md
@@ -1 +1,41 @@
|
||||
# go-ecoflow-exporter
|
||||
# ⚡ EcoFlow to Prometheus exporter in Go via Rest API
|
||||
|
||||
# Caution
|
||||
|
||||
It's a beta version. It works fine, I need to provide more documentation
|
||||
|
||||
## About the project
|
||||
|
||||
This is an Ecoflow metrics exporter to Prometheus implemented in Go.\
|
||||
It uses library https://github.com/tess1o/go-ecoflow to fetch the metrics from your Ecoflow devices via Ecoflow Rest
|
||||
API. More details about the API are on their website: https://developer-eu.ecoflow.com/
|
||||
|
||||
Other known to me projects use MQTT protocol to scrap the metrics, this implementation uses Rest API.
|
||||
|
||||
## Compare to other exporters
|
||||
|
||||
This implementation is inspired by https://github.com/berezhinskiy/ecoflow_exporter, and it's fully
|
||||
compatible with their grafana dashboard. This exporter by default uses the same prefix for the metrics (`ecoflow`)
|
||||
Both exporters support all parameters returned by the API. MQTT and Rest API actually return the same set of parameters.
|
||||
This implementation was tested on Delta 2 and River 2.
|
||||
|
||||
Some difference between this project and https://github.com/berezhinskiy/ecoflow_exporter:
|
||||
|
||||
1. This project requires `ACCESS_KEY` and `SECRET_KEY` that can be obtained on https://developer-eu.ecoflow.com/. For
|
||||
me, it took less than 1 day until Ecoflow approved access to the API. The other project needs only ecoflow
|
||||
credentials (login and password)
|
||||
2. This project doesn't hardcode devices `Serial Numbers` and this exporter can export metrics from all linked devices.
|
||||
Internally it uses Ecoflow API to fetch the list of linked devices and then for each device it exports the
|
||||
metrics. https://github.com/berezhinskiy/ecoflow_exporter can export metrics for a single device only, and you have
|
||||
to hardcode it's Serial Number in the env variables. If you have 5 devices, then you need to run 5 instances of the
|
||||
exporter
|
||||
3. The image size (not compressed!) of this exporter is only 21MB, `ghcr.io/berezhinskiy/ecoflow_exporter` is 142 MB
|
||||
4. This implementation is extremely lightweight and barely consumes any RAM & CPU (it needs less than 10MB of RAM to
|
||||
scrap metrics from 2 devices)
|
||||
|
||||
# TODO
|
||||
|
||||
1. Instructions how to get AccessToken and SecretToken
|
||||
2. Instructions how to run exporter, grafana and prometheus
|
||||
3. Instructions how to import grafana dashboard
|
||||
4. Github workflow to automatically push new
|
7
docker-compose/.env
Normal file
7
docker-compose/.env
Normal file
@@ -0,0 +1,7 @@
|
||||
ECOFLOW_ACCESS_KEY=your_access_key_from_ecoflow
|
||||
ECOFLOW_SECRET_KEY=your_secret_key_from_ecoflow
|
||||
METRIC_PREFIX=ecoflow
|
||||
PROMETHEUS_INTERVAL=10
|
||||
DEBUG_ENABLED=false
|
||||
GRAFANA_USERNAME=grafana
|
||||
GRAFANA_PASSWORD=grafana
|
43
docker-compose/compose-local-build.yaml
Normal file
43
docker-compose/compose-local-build.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
container_name: prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
ports:
|
||||
- 9090:9090
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./prometheus:/etc/prometheus
|
||||
- prometheus_data:/prometheus
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana
|
||||
container_name: grafana
|
||||
ports:
|
||||
- 3000:3000
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_USER: "${GRAFANA_USERNAME}"
|
||||
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD}"
|
||||
volumes:
|
||||
- ./grafana:/etc/grafana/provisioning/datasources
|
||||
- grafana_data:/var/lib/grafana
|
||||
|
||||
go_ecoflow_exporter:
|
||||
build:
|
||||
context: ./..
|
||||
dockerfile: ./Dockerfile
|
||||
container_name: go_ecoflow_exporter
|
||||
ports:
|
||||
- 2112:2112
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ACCESS_KEY: ${ACCESS_KEY}
|
||||
SECRET_KEY: ${SECRET_KEY}
|
||||
METRIC_PREFIX: ${METRIC_PREFIX}
|
||||
PROMETHEUS_INTERVAL: ${PROMETHEUS_INTERVAL}
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
grafana_data:
|
41
docker-compose/compose.yaml
Normal file
41
docker-compose/compose.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
services:
|
||||
prometheus:
|
||||
image: prom/prometheus
|
||||
container_name: prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
ports:
|
||||
- 9090:9090
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./prometheus:/etc/prometheus
|
||||
- prometheus_data:/prometheus
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana
|
||||
container_name: grafana
|
||||
ports:
|
||||
- 3000:3000
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_USER: "${GRAFANA_USERNAME}"
|
||||
GF_SECURITY_ADMIN_PASSWORD: "${GRAFANA_PASSWORD}"
|
||||
volumes:
|
||||
- ./grafana:/etc/grafana/provisioning/datasources
|
||||
- grafana_data:/var/lib/grafana
|
||||
|
||||
go_ecoflow_exporter:
|
||||
image: tess1o/go-ecoflow-exporter:latest
|
||||
container_name: go_ecoflow_exporter
|
||||
ports:
|
||||
- 2112:2112
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
ECOFLOW_ACCESS_KEY: ${ECOFLOW_ACCESS_KEY}
|
||||
ECOFLOW_SECRET_KEY: ${ECOFLOW_SECRET_KEY}
|
||||
METRIC_PREFIX: ${METRIC_PREFIX}
|
||||
PROMETHEUS_INTERVAL: ${PROMETHEUS_INTERVAL}
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
grafana_data:
|
9
docker-compose/grafana/datasource.yml
Normal file
9
docker-compose/grafana/datasource.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
access: proxy
|
||||
editable: true
|
18
docker-compose/prometheus/prometheus.yml
Executable file
18
docker-compose/prometheus/prometheus.yml
Executable file
@@ -0,0 +1,18 @@
|
||||
global:
|
||||
scrape_interval: 10s # Set the scrape interval to every 10 seconds. Default is every 1 minute.
|
||||
scrape_timeout: 10s
|
||||
evaluation_interval: 10s # Evaluate rules every 10 seconds. The default is every 1 minute.
|
||||
|
||||
scrape_configs:
|
||||
- job_name: prometheus
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:9090
|
||||
|
||||
- job_name: ecoflow-exporter
|
||||
static_configs:
|
||||
- targets:
|
||||
- go_ecoflow_exporter:2112
|
||||
|
||||
|
||||
|
75
exporter.go
Normal file
75
exporter.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/tess1o/go-ecoflow"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultMetricPrefix = "ecoflow"
|
||||
defaultInterval = 30
|
||||
)
|
||||
|
||||
func main() {
|
||||
setLoggerLevel()
|
||||
|
||||
accessKey := os.Getenv("ECOFLOW_ACCESS_KEY")
|
||||
secretKey := os.Getenv("ECOFLOW_SECRET_KEY")
|
||||
|
||||
if accessKey == "" || secretKey == "" {
|
||||
slog.Error("AccessKey and SecretKey are mandatory")
|
||||
return
|
||||
}
|
||||
|
||||
client := ecoflow.NewEcoflowClient(accessKey, secretKey)
|
||||
|
||||
metricPrefix := getStringOrDefault("METRIC_PREFIX", defaultMetricPrefix)
|
||||
interval := getIntOrDefault("PROMETHEUS_INTERVAL", defaultInterval)
|
||||
|
||||
// configure prometheus scrap interval and metric prefix
|
||||
config := ecoflow.PrometheusConfig{
|
||||
Interval: time.Second * time.Duration(interval),
|
||||
Prefix: metricPrefix,
|
||||
}
|
||||
|
||||
client.RecordPrometheusMetrics(&config)
|
||||
|
||||
// start server with metrics
|
||||
slog.Info("Starting server on port 2112. Metrics are available at http://localhost:2112/metrics")
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
http.ListenAndServe(":2112", nil)
|
||||
}
|
||||
|
||||
func setLoggerLevel() {
|
||||
debugEnabled := os.Getenv("DEBUG_ENABLED")
|
||||
if debugEnabled == "true" || debugEnabled == "1" {
|
||||
slog.SetLogLoggerLevel(slog.LevelDebug)
|
||||
} else {
|
||||
slog.SetLogLoggerLevel(slog.LevelInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func getStringOrDefault(key, def string) string {
|
||||
val, exists := os.LookupEnv(key)
|
||||
if exists {
|
||||
return val
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func getIntOrDefault(key string, def int) int {
|
||||
val, exists := os.LookupEnv(key)
|
||||
if exists {
|
||||
intVal, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return intVal
|
||||
}
|
||||
return def
|
||||
}
|
18
go.mod
Normal file
18
go.mod
Normal file
@@ -0,0 +1,18 @@
|
||||
module go-ecoflow-usage
|
||||
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/prometheus/client_golang v1.19.1
|
||||
github.com/tess1o/go-ecoflow v0.0.0-20240613201311-d00152d1ead3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.1 // indirect
|
||||
github.com/prometheus/common v0.54.0 // indirect
|
||||
github.com/prometheus/procfs v0.15.1 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
)
|
9
go.sum
Normal file
9
go.sum
Normal file
@@ -0,0 +1,9 @@
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/tess1o/go-ecoflow v0.0.0-20240613201311-d00152d1ead3/go.mod h1:HtMB5DptewhOD+SbAwLtfGlWPq504RUjDhRx+NqxBxs=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
Reference in New Issue
Block a user