evcc-io/core/metrics/db.go

77 lines
1.6 KiB
Go

package metrics
import (
"errors"
"time"
"github.com/evcc-io/evcc/server/db"
)
type meter struct {
Meter int `json:"meter" gorm:"column:meter;uniqueIndex:meter_ts"`
Timestamp time.Time `json:"ts" gorm:"column:ts;uniqueIndex:meter_ts"`
Value float64 `json:"val" gorm:"column:val"`
}
var ErrIncomplete = errors.New("meter profile incomplete")
func Init() error {
return db.Instance.AutoMigrate(new(meter))
}
// Persist stores 15min consumption in Wh
func Persist(ts time.Time, value float64) error {
return db.Instance.Create(meter{
Meter: 1,
Timestamp: ts.Truncate(15 * time.Minute),
Value: value,
}).Error
}
// Profile returns a 15min average meter profile in Wh.
// Profile is sorted by timestamp starting at 00:00. It is guaranteed to contain 96 15min values.
func Profile(from time.Time) (*[96]float64, error) {
db, err := db.Instance.DB()
if err != nil {
return nil, err
}
rows, err := db.Query(`SELECT min(ts) AS ts, avg(val) AS val
FROM meters
WHERE meter = ? AND ts >= ?
GROUP BY strftime("%H:%M", ts)
ORDER BY strftime("%H:%M", ts) ASC`, 1, from,
)
if err != nil {
return nil, err
}
defer rows.Close()
res := make([]float64, 0, 96)
for rows.Next() {
var ts SqlTime
var val float64
if err := rows.Scan(&ts, &val); err != nil {
return nil, err
}
res = append(res, val)
}
if err := rows.Err(); err != nil {
return nil, err
}
if len(res) != 96 {
return nil, ErrIncomplete
}
return (*[96]float64)(res), nil
}
func SlotNum(ts time.Time) int {
ts = ts.Local()
return ts.Hour()*4 + ts.Minute()/15
}