handler: Wire in Postgres store

This commit is contained in:
Rob Watson 2022-07-13 19:01:09 +02:00
parent 72b0a690a6
commit baa8ba7479
7 changed files with 313 additions and 79 deletions

View File

@ -31,7 +31,7 @@ func main() {
} }
store := store.NewSQL(db) store := store.NewSQL(db)
handler := handler.NewHandler(store) handler := handler.New(store)
srv := http.Server{ srv := http.Server{
ReadTimeout: time.Second * 3, ReadTimeout: time.Second * 3,
WriteTimeout: time.Second * 3, WriteTimeout: time.Second * 3,

View File

@ -1,11 +1,16 @@
package handler package handler
import ( import (
"encoding/json"
"io"
"log"
"net/http" "net/http"
"git.netflux.io/rob/solar-toolkit/inverter" "git.netflux.io/rob/solar-toolkit/inverter"
) )
const timestampMinimumYear = 2022
type Store interface { type Store interface {
InsertETRuntimeData(*inverter.ETRuntimeData) error InsertETRuntimeData(*inverter.ETRuntimeData) error
} }
@ -14,8 +19,47 @@ type Handler struct {
store Store store Store
} }
func NewHandler(store Store) *Handler { return &Handler{store: store} } func New(store Store) *Handler { return &Handler{store: store} }
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if r.URL.Path != "/gateway/et_runtime_data" {
http.Error(w, "endpoint not found", http.StatusNotFound)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
log.Printf("could not read body: %v", err)
http.Error(w, "unexpected error", http.StatusInternalServerError)
return
}
var runtimeData inverter.ETRuntimeData
err = json.Unmarshal(body, &runtimeData)
if err != nil {
log.Printf("could not unmarshal body: %v", err)
http.Error(w, "unexpected error", http.StatusInternalServerError)
return
}
if runtimeData.Timestamp.Year() < timestampMinimumYear {
log.Printf("invalid timestamp: %v", runtimeData.Timestamp)
http.Error(w, "invalid data", http.StatusBadRequest)
return
}
if err = h.store.InsertETRuntimeData(&runtimeData); err != nil {
log.Printf("error storing data: %v", err)
http.Error(w, "unexpected error", http.StatusInternalServerError)
return
}
w.Header().Set("content-type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK\n"))
} }

View File

@ -0,0 +1,103 @@
package handler_test
import (
"errors"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.netflux.io/rob/solar-toolkit/gateway/handler"
"git.netflux.io/rob/solar-toolkit/inverter"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type store struct {
err error
}
func (s *store) InsertETRuntimeData(*inverter.ETRuntimeData) error {
return s.err
}
func TestHandler(t *testing.T) {
testCases := []struct {
name string
httpMethod string
path string
body string
storeErr error
wantStatusCode int
wantBody string
}{
{
name: "method not allowed",
httpMethod: http.MethodGet,
path: "/gateway/et_runtime_data",
wantStatusCode: http.StatusMethodNotAllowed,
wantBody: "method not allowed\n",
},
{
name: "method not allowed",
httpMethod: http.MethodPost,
path: "/gateway/foo",
wantStatusCode: http.StatusNotFound,
wantBody: "endpoint not found\n",
},
{
name: "invalid payload",
httpMethod: http.MethodPost,
path: "/gateway/et_runtime_data",
body: `{`,
wantStatusCode: http.StatusInternalServerError,
wantBody: "unexpected error\n",
},
{
name: "invalid timestamp",
httpMethod: http.MethodPost,
path: "/gateway/et_runtime_data",
body: `{"timestamp": "1970-01-01T00:00:00Z"}`,
wantStatusCode: http.StatusBadRequest,
wantBody: "invalid data\n",
},
{
name: "store error",
httpMethod: http.MethodPost,
path: "/gateway/et_runtime_data",
body: `{"timestamp": "2022-01-01T00:00:00Z"}`,
storeErr: errors.New("boom"),
wantStatusCode: http.StatusInternalServerError,
wantBody: "unexpected error\n",
},
{
name: "OK",
httpMethod: http.MethodPost,
path: "/gateway/et_runtime_data",
body: `{"timestamp": "2022-01-01T00:00:00Z"}`,
wantStatusCode: http.StatusOK,
wantBody: "OK\n",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
mockStore := store{err: tc.storeErr}
handler := handler.New(&mockStore)
req := httptest.NewRequest(tc.httpMethod, tc.path, strings.NewReader(tc.body))
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
resp := rec.Result()
defer resp.Body.Close()
assert.Equal(t, tc.wantStatusCode, resp.StatusCode)
if tc.wantBody != "" {
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
assert.Equal(t, tc.wantBody, string(body))
}
})
}
}

View File

@ -0,0 +1 @@
DROP TABLE et_runtime_data;

View File

@ -0,0 +1,78 @@
CREATE TABLE et_runtime_data (
timestamp TIMESTAMP WITH TIME ZONE NOT NULL,
pv1_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
pv1_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
pv1_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
pv2_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
pv2_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
pv2_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
pv_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
pv2_mode INT NOT NULL DEFAULT 0,
pv1_mode INT NOT NULL DEFAULT 0,
on_grid_l1_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l1_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l1_frequency DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l1_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l2_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l2_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l2_frequency DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l2_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l3_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l3_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l3_frequency DOUBLE PRECISION NOT NULL DEFAULT 0.0,
on_grid_l3_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
grid_mode INT NOT NULL DEFAULT 0,
total_inverter_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
active_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
reactive_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
apparent_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l1_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l1_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l1_frequency DOUBLE PRECISION NOT NULL DEFAULT 0.0,
load_mode_l1 INT NOT NULL DEFAULT 0,
backup_l1_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l2_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l2_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l2_frequency DOUBLE PRECISION NOT NULL DEFAULT 0.0,
load_mode_l2 INT NOT NULL DEFAULT 0,
backup_l2_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l3_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l3_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_l3_frequency DOUBLE PRECISION NOT NULL DEFAULT 0.0,
load_mode_l3 INT NOT NULL DEFAULT 0,
backup_l3_power DOUBLE PRECISION NOT NULL DEFAULT 0.0,
load_l1 DOUBLE PRECISION NOT NULL DEFAULT 0.0,
load_l2 DOUBLE PRECISION NOT NULL DEFAULT 0.0,
load_l3 DOUBLE PRECISION NOT NULL DEFAULT 0.0,
backup_load DOUBLE PRECISION NOT NULL DEFAULT 0.0,
load DOUBLE PRECISION NOT NULL DEFAULT 0.0,
ups_load DOUBLE PRECISION NOT NULL DEFAULT 0.0,
temperature_air DOUBLE PRECISION NOT NULL DEFAULT 0.0,
temperature_module DOUBLE PRECISION NOT NULL DEFAULT 0.0,
temperature DOUBLE PRECISION NOT NULL DEFAULT 0.0,
bus_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
nbus_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
battery_voltage DOUBLE PRECISION NOT NULL DEFAULT 0.0,
battery_current DOUBLE PRECISION NOT NULL DEFAULT 0.0,
battery_mode INT NOT NULL DEFAULT 0,
warning_code INT NOT NULL DEFAULT 0,
safety_country_code INT NOT NULL DEFAULT 0,
work_mode INT NOT NULL DEFAULT 0,
operation_code INT NOT NULL DEFAULT 0,
energy_generation_total DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_generation_today DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_export_total DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_export_total_hours DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_export_today DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_import_total DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_import_today DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_load_total DOUBLE PRECISION NOT NULL DEFAULT 0.0,
energy_load_day DOUBLE PRECISION NOT NULL DEFAULT 0.0,
battery_charge_total DOUBLE PRECISION NOT NULL DEFAULT 0.0,
battery_charge_today DOUBLE PRECISION NOT NULL DEFAULT 0.0,
battery_discharge_total DOUBLE PRECISION NOT NULL DEFAULT 0.0,
battery_discharge_today DOUBLE PRECISION NOT NULL DEFAULT 0.0,
house_consumption DOUBLE PRECISION NOT NULL DEFAULT 0.0
);
CREATE UNIQUE INDEX index_et_runtime_data_on_timestamp ON et_runtime_data (timestamp);

View File

@ -1,6 +1,8 @@
package store package store
import ( import (
"fmt"
"git.netflux.io/rob/solar-toolkit/inverter" "git.netflux.io/rob/solar-toolkit/inverter"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
) )
@ -13,6 +15,12 @@ func NewSQL(db *sqlx.DB) *PostgresStore {
return &PostgresStore{db: db} return &PostgresStore{db: db}
} }
const insertSql = `INSERT INTO et_runtime_data (timestamp, pv1_voltage, pv1_current, pv1_power, pv2_voltage, pv2_current, pv2_power, pv_power, pv2_mode, pv1_mode, on_grid_l1_voltage, on_grid_l1_current, on_grid_l1_frequency, on_grid_l1_power, on_grid_l2_voltage, on_grid_l2_current, on_grid_l2_frequency, on_grid_l2_power, on_grid_l3_voltage, on_grid_l3_current, on_grid_l3_frequency, on_grid_l3_power, grid_mode, total_inverter_power, active_power, reactive_power, apparent_power, backup_l1_voltage, backup_l1_current, backup_l1_frequency, load_mode_l1, backup_l1_power, backup_l2_voltage, backup_l2_current, backup_l2_frequency, load_mode_l2, backup_l2_power, backup_l3_voltage, backup_l3_current, backup_l3_frequency, load_mode_l3, backup_l3_power, load_l1, load_l2, load_l3, backup_load, load, ups_load, temperature_air, temperature_module, temperature, bus_voltage, nbus_voltage, battery_voltage, battery_current, battery_mode, warning_code, safety_country_code, work_mode, operation_code, energy_generation_total, energy_generation_today, energy_export_total, energy_export_total_hours, energy_export_today, energy_import_total, energy_import_today, energy_load_total, energy_load_day, battery_charge_total, battery_charge_today, battery_discharge_total, battery_discharge_today, house_consumption) VALUES (:timestamp, :pv1_voltage, :pv1_current, :pv1_power, :pv2_voltage, :pv2_current, :pv2_power, :pv_power, :pv2_mode, :pv1_mode, :on_grid_l1_voltage, :on_grid_l1_current, :on_grid_l1_frequency, :on_grid_l1_power, :on_grid_l2_voltage, :on_grid_l2_current, :on_grid_l2_frequency, :on_grid_l2_power, :on_grid_l3_voltage, :on_grid_l3_current, :on_grid_l3_frequency, :on_grid_l3_power, :grid_mode, :total_inverter_power, :active_power, :reactive_power, :apparent_power, :backup_l1_voltage, :backup_l1_current, :backup_l1_frequency, :load_mode_l1, :backup_l1_power, :backup_l2_voltage, :backup_l2_current, :backup_l2_frequency, :load_mode_l2, :backup_l2_power, :backup_l3_voltage, :backup_l3_current, :backup_l3_frequency, :load_mode_l3, :backup_l3_power, :load_l1, :load_l2, :load_l3, :backup_load, :load, :ups_load, :temperature_air, :temperature_module, :temperature, :bus_voltage, :nbus_voltage, :battery_voltage, :battery_current, :battery_mode, :warning_code, :safety_country_code, :work_mode, :operation_code, :energy_generation_total, :energy_generation_today, :energy_export_total, :energy_export_total_hours, :energy_export_today, :energy_import_total, :energy_import_today, :energy_load_total, :energy_load_day, :battery_charge_total, :battery_charge_today, :battery_discharge_total, :battery_discharge_today, :house_consumption);`
func (s *PostgresStore) InsertETRuntimeData(runtimeData *inverter.ETRuntimeData) error { func (s *PostgresStore) InsertETRuntimeData(runtimeData *inverter.ETRuntimeData) error {
if _, err := s.db.NamedExec(insertSql, runtimeData); err != nil {
return fmt.Errorf("error inserting data: %s", err)
}
return nil return nil
} }

View File

@ -58,81 +58,81 @@ type DeviceInfo struct {
} }
type ETRuntimeData struct { type ETRuntimeData struct {
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp" db:"timestamp"`
PV1Voltage Voltage `json:"pv1_voltage"` PV1Voltage Voltage `json:"pv1_voltage" db:"pv1_voltage"`
PV1Current Current `json:"pv1_current"` PV1Current Current `json:"pv1_current" db:"pv1_current"`
PV1Power Power `json:"pv1_power"` PV1Power Power `json:"pv1_power" db:"pv1_power"`
PV2Voltage Voltage `json:"pv2_voltage"` PV2Voltage Voltage `json:"pv2_voltage" db:"pv2_voltage"`
PV2Current Current `json:"pv2_current"` PV2Current Current `json:"pv2_current" db:"pv2_current"`
PV2Power Power `json:"pv2_power"` PV2Power Power `json:"pv2_power" db:"pv2_power"`
PVPower Power `json:"pv_power"` PVPower Power `json:"pv_power" db:"pv_power"`
PV2Mode byte `json:"pv2_mode"` PV2Mode byte `json:"pv2_mode" db:"pv2_mode"`
PV1Mode byte `json:"pv1_mode"` PV1Mode byte `json:"pv1_mode" db:"pv1_mode"`
OnGridL1Voltage Voltage `json:"on_grid_l1_voltage"` OnGridL1Voltage Voltage `json:"on_grid_l1_voltage" db:"on_grid_l1_voltage"`
OnGridL1Current Current `json:"on_grid_l1_current"` OnGridL1Current Current `json:"on_grid_l1_current" db:"on_grid_l1_current"`
OnGridL1Frequency Frequency `json:"on_grid_l1_frequency"` OnGridL1Frequency Frequency `json:"on_grid_l1_frequency" db:"on_grid_l1_frequency"`
OnGridL1Power Power `json:"on_grid_l1_power"` OnGridL1Power Power `json:"on_grid_l1_power" db:"on_grid_l1_power"`
OnGridL2Voltage Voltage `json:"on_grid_l2_voltage"` OnGridL2Voltage Voltage `json:"on_grid_l2_voltage" db:"on_grid_l2_voltage"`
OnGridL2Current Current `json:"on_grid_l2_current"` OnGridL2Current Current `json:"on_grid_l2_current" db:"on_grid_l2_current"`
OnGridL2Frequency Frequency `json:"on_grid_l2_frequency"` OnGridL2Frequency Frequency `json:"on_grid_l2_frequency" db:"on_grid_l2_frequency"`
OnGridL2Power Power `json:"on_grid_l2_power"` OnGridL2Power Power `json:"on_grid_l2_power" db:"on_grid_l2_power"`
OnGridL3Voltage Voltage `json:"on_grid_l3_voltage"` OnGridL3Voltage Voltage `json:"on_grid_l3_voltage" db:"on_grid_l3_voltage"`
OnGridL3Current Current `json:"on_grid_l3_current"` OnGridL3Current Current `json:"on_grid_l3_current" db:"on_grid_l3_current"`
OnGridL3Frequency Frequency `json:"on_grid_l3_frequency"` OnGridL3Frequency Frequency `json:"on_grid_l3_frequency" db:"on_grid_l3_frequency"`
OnGridL3Power Power `json:"on_grid_l3_power"` OnGridL3Power Power `json:"on_grid_l3_power" db:"on_grid_l3_power"`
GridMode int `json:"grid_mode"` GridMode int `json:"grid_mode" db:"grid_mode"`
TotalInverterPower Power `json:"total_inverter_power"` TotalInverterPower Power `json:"total_inverter_power" db:"total_inverter_power"`
ActivePower Power `json:"active_power"` ActivePower Power `json:"active_power" db:"active_power"`
ReactivePower int `json:"reactive_power"` ReactivePower int `json:"reactive_power" db:"reactive_power"`
ApparentPower int `json:"apparent_power"` ApparentPower int `json:"apparent_power" db:"apparent_power"`
BackupL1Voltage Voltage `json:"backup_l1_voltage"` BackupL1Voltage Voltage `json:"backup_l1_voltage" db:"backup_l1_voltage"`
BackupL1Current Current `json:"backup_l1_current"` BackupL1Current Current `json:"backup_l1_current" db:"backup_l1_current"`
BackupL1Frequency Frequency `json:"backup_l1_frequency"` BackupL1Frequency Frequency `json:"backup_l1_frequency" db:"backup_l1_frequency"`
LoadModeL1 int `json:"load_mode_l1"` LoadModeL1 int `json:"load_mode_l1" db:"load_mode_l1"`
BackupL1Power Power `json:"backup_l1_power"` BackupL1Power Power `json:"backup_l1_power" db:"backup_l1_power"`
BackupL2Voltage Voltage `json:"backup_l2_voltage"` BackupL2Voltage Voltage `json:"backup_l2_voltage" db:"backup_l2_voltage"`
BackupL2Current Current `json:"backup_l2_current"` BackupL2Current Current `json:"backup_l2_current" db:"backup_l2_current"`
BackupL2Frequency Frequency `json:"backup_l2_frequency"` BackupL2Frequency Frequency `json:"backup_l2_frequency" db:"backup_l2_frequency"`
LoadModeL2 int `json:"load_mode_l2"` LoadModeL2 int `json:"load_mode_l2" db:"load_mode_l2"`
BackupL2Power Power `json:"backup_l2_power"` BackupL2Power Power `json:"backup_l2_power" db:"backup_l2_power"`
BackupL3Voltage Voltage `json:"backup_l3_voltage"` BackupL3Voltage Voltage `json:"backup_l3_voltage" db:"backup_l3_voltage"`
BackupL3Current Current `json:"backup_l3_current"` BackupL3Current Current `json:"backup_l3_current" db:"backup_l3_current"`
BackupL3Frequency Frequency `json:"backup_l3_frequency"` BackupL3Frequency Frequency `json:"backup_l3_frequency" db:"backup_l3_frequency"`
LoadModeL3 int `json:"load_mode_l3"` LoadModeL3 int `json:"load_mode_l3" db:"load_mode_l3"`
BackupL3Power Power `json:"backup_l3_power"` BackupL3Power Power `json:"backup_l3_power" db:"backup_l3_power"`
LoadL1 Power `json:"load_l1"` LoadL1 Power `json:"load_l1" db:"load_l1"`
LoadL2 Power `json:"load_l2"` LoadL2 Power `json:"load_l2" db:"load_l2"`
LoadL3 Power `json:"load_l3"` LoadL3 Power `json:"load_l3" db:"load_l3"`
BackupLoad Power `json:"backup_load"` BackupLoad Power `json:"backup_load" db:"backup_load"`
Load Power `json:"load"` Load Power `json:"load" db:"load"`
UPSLoad int `json:"ups_load"` UPSLoad int `json:"ups_load" db:"ups_load"`
TemperatureAir Temp `json:"temperature_air"` TemperatureAir Temp `json:"temperature_air" db:"temperature_air"`
TemperatureModule Temp `json:"temperature_module"` TemperatureModule Temp `json:"temperature_module" db:"temperature_module"`
Temperature Temp `json:"temperature"` Temperature Temp `json:"temperature" db:"temperature"`
FunctionBit int `json:"-"` FunctionBit int `json:"-" db:"-"`
BusVoltage Voltage `json:"bus_voltage"` BusVoltage Voltage `json:"bus_voltage" db:"bus_voltage"`
NBusVoltage Voltage `json:"nbus_voltage"` NBusVoltage Voltage `json:"nbus_voltage" db:"nbus_voltage"`
BatteryVoltage Voltage `json:"battery_voltage"` BatteryVoltage Voltage `json:"battery_voltage" db:"battery_voltage"`
BatteryCurrent Current `json:"battery_current"` BatteryCurrent Current `json:"battery_current" db:"battery_current"`
BatteryMode int `json:"battery_mode"` BatteryMode int `json:"battery_mode" db:"battery_mode"`
WarningCode int `json:"warning_code"` WarningCode int `json:"warning_code" db:"warning_code"`
SafetyCountryCode int `json:"safety_country_code"` SafetyCountryCode int `json:"safety_country_code" db:"safety_country_code"`
WorkMode int `json:"work_mode"` WorkMode int `json:"work_mode" db:"work_mode"`
OperationCode int `json:"operation_code"` OperationCode int `json:"operation_code" db:"operation_code"`
ErrorCodes int `json:"-"` ErrorCodes int `json:"-" db:"-"`
EnergyGenerationTotal Energy `json:"energy_generation_total"` EnergyGenerationTotal Energy `json:"energy_generation_total" db:"energy_generation_total"`
EnergyGenerationToday Energy `json:"energy_generation_today"` EnergyGenerationToday Energy `json:"energy_generation_today" db:"energy_generation_today"`
EnergyExportTotal Energy `json:"energy_export_total"` EnergyExportTotal Energy `json:"energy_export_total" db:"energy_export_total"`
EnergyExportTotalHours int `json:"energy_export_total_hours"` EnergyExportTotalHours int `json:"energy_export_total_hours" db:"energy_export_total_hours"`
EnergyExportToday Energy `json:"energy_export_today"` EnergyExportToday Energy `json:"energy_export_today" db:"energy_export_today"`
EnergyImportTotal Energy `json:"energy_import_total"` EnergyImportTotal Energy `json:"energy_import_total" db:"energy_import_total"`
EnergyImportToday Energy `json:"energy_import_today"` EnergyImportToday Energy `json:"energy_import_today" db:"energy_import_today"`
EnergyLoadTotal Energy `json:"energy_load_total"` EnergyLoadTotal Energy `json:"energy_load_total" db:"energy_load_total"`
EnergyLoadDay Energy `json:"energy_load_day"` EnergyLoadDay Energy `json:"energy_load_day" db:"energy_load_day"`
BatteryChargeTotal int `json:"battery_charge_total"` BatteryChargeTotal int `json:"battery_charge_total" db:"battery_charge_total"`
BatteryChargeToday int `json:"battery_charge_today"` BatteryChargeToday int `json:"battery_charge_today" db:"battery_charge_today"`
BatteryDischargeTotal int `json:"battery_discharge_total"` BatteryDischargeTotal int `json:"battery_discharge_total" db:"battery_discharge_total"`
BatteryDischargeToday int `json:"battery_discharge_today"` BatteryDischargeToday int `json:"battery_discharge_today" db:"battery_discharge_today"`
DiagStatusCode int `json:"-"` DiagStatusCode int `json:"-" db:"-"`
HouseConsumption Power `json:"house_consumption"` HouseConsumption Power `json:"house_consumption" db:"house_consumption"`
} }