Implement query meter data
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
8c80417a53
commit
def17ff49c
|
@ -14,8 +14,11 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var inverterAddr string
|
var inverterAddr string
|
||||||
|
var meterData bool
|
||||||
|
var err error
|
||||||
|
|
||||||
flag.StringVar(&inverterAddr, "inverter-addr", "", "IP+port of solar inverter")
|
flag.StringVar(&inverterAddr, "inverter-addr", "", "IP+port of solar inverter")
|
||||||
|
flag.BoolVar(&meterData, "meter-data", false, "print meter data, not sensors")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if inverterAddr == "" {
|
if inverterAddr == "" {
|
||||||
|
@ -30,16 +33,29 @@ func main() {
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
var inv inverter.ET
|
var inv inverter.ET
|
||||||
|
var result []byte
|
||||||
|
|
||||||
|
if meterData {
|
||||||
|
meterData, err := inv.MeterData(context.Background(), conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error fetching meter data: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err = json.Marshal(meterData)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error encoding meter data: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
runtimeData, err := inv.RuntimeData(context.Background(), conn)
|
runtimeData, err := inv.RuntimeData(context.Background(), conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error fetching runtime data: %s", err)
|
log.Fatalf("error fetching runtime data: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
json, err := json.Marshal(runtimeData)
|
result, err = json.Marshal(runtimeData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error encoding runtime data: %s", err)
|
log.Fatalf("error encoding runtime data: %s", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Fprint(os.Stdout, string(json))
|
fmt.Fprint(os.Stdout, string(result))
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,77 @@ func (info *etDeviceInfo) toDeviceInfo() *DeviceInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unexported struct used for parsing binary data only.
|
||||||
|
type etMeterData struct {
|
||||||
|
ComMode int16
|
||||||
|
RSSI int16
|
||||||
|
ManufactureCode int16
|
||||||
|
MeterTestStatus int16
|
||||||
|
MeterCommStatus int16
|
||||||
|
ActivePowerL1 int16
|
||||||
|
ActivePowerL2 int16
|
||||||
|
ActivePowerL3 int16
|
||||||
|
ActivePowerTotal int16
|
||||||
|
ReactivePowerTotal int16
|
||||||
|
MeterPowerFactor1 int16
|
||||||
|
MeterPowerFactor2 int16
|
||||||
|
MeterPowerFactor3 int16
|
||||||
|
MeterPowerFactor int16
|
||||||
|
MeterFrequency int16
|
||||||
|
EnergyExportTotal float32
|
||||||
|
EnergyImportTotal float32
|
||||||
|
MeterActivePower1 int32
|
||||||
|
MeterActivePower2 int32
|
||||||
|
MeterActivePower3 int32
|
||||||
|
MeterActivePowerTotal int32
|
||||||
|
MeterReactivePower1 int32
|
||||||
|
MeterReactivePower2 int32
|
||||||
|
MeterReactivePower3 int32
|
||||||
|
MeterReactivePowerTotal int32
|
||||||
|
MeterApparentPower1 int32
|
||||||
|
MeterApparentPower2 int32
|
||||||
|
MeterApparentPower3 int32
|
||||||
|
MeterApparentPowerTotal int32
|
||||||
|
MeterType int16
|
||||||
|
MeterSoftwareVersion int16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (data *etMeterData) toMeterData(singlePhase bool) *ETMeterData {
|
||||||
|
return &ETMeterData{
|
||||||
|
ComMode: int(data.ComMode),
|
||||||
|
RSSI: int(data.RSSI),
|
||||||
|
ManufactureCode: int(data.ManufactureCode),
|
||||||
|
MeterTestStatus: int(data.MeterTestStatus),
|
||||||
|
MeterCommStatus: int(data.MeterCommStatus),
|
||||||
|
ActivePowerL1: newPower(data.ActivePowerL1),
|
||||||
|
ActivePowerL2: newPower(data.ActivePowerL2),
|
||||||
|
ActivePowerL3: newPower(data.ActivePowerL3),
|
||||||
|
ActivePowerTotal: newPower(data.ActivePowerTotal),
|
||||||
|
ReactivePowerTotal: int(data.ReactivePowerTotal),
|
||||||
|
MeterPowerFactor1: float64(data.MeterPowerFactor1) / 1000.0,
|
||||||
|
MeterPowerFactor2: float64(filterSinglePhase(data.MeterPowerFactor2, singlePhase)) / 1000.0,
|
||||||
|
MeterPowerFactor3: float64(filterSinglePhase(data.MeterPowerFactor3, singlePhase)) / 1000.0,
|
||||||
|
MeterPowerFactor: float64(data.MeterPowerFactor) / 1000.0,
|
||||||
|
MeterFrequency: newFrequency(data.MeterFrequency),
|
||||||
|
EnergyExportTotal: newPower(data.EnergyExportTotal),
|
||||||
|
EnergyImportTotal: newPower(data.EnergyImportTotal),
|
||||||
|
MeterActivePower1: newPower(data.MeterActivePower1),
|
||||||
|
MeterActivePower2: newPower(data.MeterActivePower2),
|
||||||
|
MeterActivePower3: newPower(data.MeterActivePower3),
|
||||||
|
MeterActivePowerTotal: newPower(data.MeterActivePowerTotal),
|
||||||
|
MeterReactivePower1: int(data.MeterReactivePower1),
|
||||||
|
MeterReactivePower2: int(data.MeterReactivePower2),
|
||||||
|
MeterReactivePower3: int(data.MeterReactivePower3),
|
||||||
|
MeterReactivePowerTotal: int(data.MeterReactivePowerTotal),
|
||||||
|
MeterApparentPower1: int(data.MeterApparentPower1),
|
||||||
|
MeterApparentPower2: int(data.MeterApparentPower2),
|
||||||
|
MeterApparentPower3: int(data.MeterApparentPower3),
|
||||||
|
MeterApparentPowerTotal: int(data.MeterApparentPowerTotal),
|
||||||
|
MeterType: int(data.MeterType),
|
||||||
|
MeterSoftwareVersion: int(data.MeterSoftwareVersion),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Unexported struct used for parsing binary data only.
|
// Unexported struct used for parsing binary data only.
|
||||||
//
|
//
|
||||||
// Raw types are based partly on the the PyPI library, and partly on the
|
// Raw types are based partly on the the PyPI library, and partly on the
|
||||||
|
@ -264,6 +335,15 @@ func (inv ET) DecodeRuntimeData(p []byte) (*ETRuntimeData, error) {
|
||||||
return runtimeData.toRuntimeData(inv.isSinglePhase()), nil
|
return runtimeData.toRuntimeData(inv.isSinglePhase()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (inv ET) DecodeMeterData(p []byte) (*ETMeterData, error) {
|
||||||
|
var meterData etMeterData
|
||||||
|
if err := binary.Read(bytes.NewReader(p), binary.BigEndian, &meterData); err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return meterData.toMeterData(inv.isSinglePhase()), nil
|
||||||
|
}
|
||||||
|
|
||||||
// DEPRECATED
|
// DEPRECATED
|
||||||
func (inv ET) DeviceInfo(ctx context.Context, conn command.Conn) (*DeviceInfo, error) {
|
func (inv ET) DeviceInfo(ctx context.Context, conn command.Conn) (*DeviceInfo, error) {
|
||||||
resp, err := command.Send(command.NewModbus(command.ModbusCommandTypeRead, 0x88b8, 0x0021), conn)
|
resp, err := command.Send(command.NewModbus(command.ModbusCommandTypeRead, 0x88b8, 0x0021), conn)
|
||||||
|
@ -298,3 +378,19 @@ func (inv ET) RuntimeData(ctx context.Context, conn command.Conn) (*ETRuntimeDat
|
||||||
|
|
||||||
return runtimeData.toRuntimeData(deviceInfo.SinglePhase), nil
|
return runtimeData.toRuntimeData(deviceInfo.SinglePhase), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
|
func (inv ET) MeterData(ctx context.Context, conn command.Conn) (*ETMeterData, error) {
|
||||||
|
resp, err := command.Send(command.NewModbus(command.ModbusCommandTypeRead, 0x8ca0, 0x2d), conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error sending command: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var meterData etMeterData
|
||||||
|
if err := binary.Read(bytes.NewReader(resp), binary.BigEndian, &meterData); err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: wire in single phase:
|
||||||
|
return meterData.toMeterData(true), nil
|
||||||
|
}
|
||||||
|
|
|
@ -139,3 +139,56 @@ func TestDecodeDeviceInfo(t *testing.T) {
|
||||||
assert.Equal(t, inverter.Power(0), runtimeData.LoadL3)
|
assert.Equal(t, inverter.Power(0), runtimeData.LoadL3)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDecodeMeterData(t *testing.T) {
|
||||||
|
inBytes := []byte{0, 1, 0, 45, 0, 10, 0, 0, 0, 1, 4, 114, 0, 0, 0, 0, 4, 114, 0, 226, 3, 201, 3, 231, 3, 231, 3, 200, 19, 132, 73, 48, 193, 246, 71, 195, 119, 16, 0, 0, 4, 114, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 114, 0, 0, 0, 226, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 226, 0, 0, 4, 151, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 151, 0, 255, 9, 44}
|
||||||
|
|
||||||
|
t.Run("with single-phase inverter", func(t *testing.T) {
|
||||||
|
inv := inverter.ET{SerialNumber: "foo"}
|
||||||
|
meterData, err := inv.DecodeMeterData(inBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 1, meterData.ComMode)
|
||||||
|
assert.Equal(t, 45, meterData.RSSI)
|
||||||
|
assert.Equal(t, 10, meterData.ManufactureCode)
|
||||||
|
assert.Equal(t, 0, meterData.MeterTestStatus)
|
||||||
|
assert.Equal(t, 1, meterData.MeterCommStatus)
|
||||||
|
assert.Equal(t, inverter.Power(1138), meterData.ActivePowerL1)
|
||||||
|
assert.Equal(t, inverter.Power(0), meterData.ActivePowerL2)
|
||||||
|
assert.Equal(t, inverter.Power(0), meterData.ActivePowerL3)
|
||||||
|
assert.Equal(t, inverter.Power(1138), meterData.ActivePowerTotal)
|
||||||
|
assert.Equal(t, 226, meterData.ReactivePowerTotal)
|
||||||
|
assert.Equal(t, 0.969, meterData.MeterPowerFactor1)
|
||||||
|
assert.Equal(t, 0.999, meterData.MeterPowerFactor2)
|
||||||
|
assert.Equal(t, 0.999, meterData.MeterPowerFactor3)
|
||||||
|
assert.Equal(t, 0.968, meterData.MeterPowerFactor)
|
||||||
|
assert.Equal(t, inverter.Frequency(49.96), meterData.MeterFrequency)
|
||||||
|
assert.Equal(t, inverter.Power(723999.375000), meterData.EnergyExportTotal)
|
||||||
|
assert.Equal(t, inverter.Power(100078.125000), meterData.EnergyImportTotal)
|
||||||
|
assert.Equal(t, inverter.Power(1138), meterData.MeterActivePower1)
|
||||||
|
assert.Equal(t, inverter.Power(0), meterData.MeterActivePower2)
|
||||||
|
assert.Equal(t, inverter.Power(0), meterData.MeterActivePower3)
|
||||||
|
assert.Equal(t, inverter.Power(1138), meterData.MeterActivePowerTotal)
|
||||||
|
assert.Equal(t, 226, meterData.MeterReactivePower1)
|
||||||
|
assert.Equal(t, 0, meterData.MeterReactivePower2)
|
||||||
|
assert.Equal(t, 0, meterData.MeterReactivePower3)
|
||||||
|
assert.Equal(t, 226, meterData.MeterReactivePowerTotal)
|
||||||
|
assert.Equal(t, 1175, meterData.MeterApparentPower1)
|
||||||
|
assert.Equal(t, 0, meterData.MeterApparentPower2)
|
||||||
|
assert.Equal(t, 0, meterData.MeterApparentPower3)
|
||||||
|
assert.Equal(t, 1175, meterData.MeterApparentPowerTotal)
|
||||||
|
assert.Equal(t, 255, meterData.MeterType)
|
||||||
|
assert.Equal(t, 2348, meterData.MeterSoftwareVersion)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with multi-phase inverter", func(t *testing.T) {
|
||||||
|
inv := inverter.ET{SerialNumber: "EHUfoo"}
|
||||||
|
meterData, err := inv.DecodeMeterData(inBytes)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 0.969, meterData.MeterPowerFactor1)
|
||||||
|
assert.Equal(t, 0.0, meterData.MeterPowerFactor2)
|
||||||
|
assert.Equal(t, 0.0, meterData.MeterPowerFactor3)
|
||||||
|
assert.Equal(t, 0.968, meterData.MeterPowerFactor)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -136,3 +136,37 @@ type ETRuntimeData struct {
|
||||||
DiagStatusCode int `json:"-" db:"-"`
|
DiagStatusCode int `json:"-" db:"-"`
|
||||||
HouseConsumption Power `json:"house_consumption" db:"house_consumption"`
|
HouseConsumption Power `json:"house_consumption" db:"house_consumption"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ETMeterData struct {
|
||||||
|
ComMode int `json:"com_mode"`
|
||||||
|
RSSI int `json:"rssi"`
|
||||||
|
ManufactureCode int `json:"manufacture_code"`
|
||||||
|
MeterTestStatus int `json:"meter_test_status"`
|
||||||
|
MeterCommStatus int `json:"meter_comm_status"`
|
||||||
|
ActivePowerL1 Power `json:"active_power_l1"`
|
||||||
|
ActivePowerL2 Power `json:"active_power_l2"`
|
||||||
|
ActivePowerL3 Power `json:"active_power_l3"`
|
||||||
|
ActivePowerTotal Power `json:"active_power_total"`
|
||||||
|
ReactivePowerTotal int `json:"reactive_power_total"`
|
||||||
|
MeterPowerFactor1 float64 `json:"meter_power_factor1"`
|
||||||
|
MeterPowerFactor2 float64 `json:"meter_power_factor2"`
|
||||||
|
MeterPowerFactor3 float64 `json:"meter_power_factor3"`
|
||||||
|
MeterPowerFactor float64 `json:"meter_power_factor"`
|
||||||
|
MeterFrequency Frequency `json:"meter_frequency"`
|
||||||
|
EnergyExportTotal Power `json:"energy_export_total"`
|
||||||
|
EnergyImportTotal Power `json:"energy_import_total"`
|
||||||
|
MeterActivePower1 Power `json:"meter_active_power1"`
|
||||||
|
MeterActivePower2 Power `json:"meter_active_power2"`
|
||||||
|
MeterActivePower3 Power `json:"meter_active_power3"`
|
||||||
|
MeterActivePowerTotal Power `json:"meter_active_power_total"`
|
||||||
|
MeterReactivePower1 int `json:"meter_reactive_power1"`
|
||||||
|
MeterReactivePower2 int `json:"meter_reactive_power2"`
|
||||||
|
MeterReactivePower3 int `json:"meter_reactive_power3"`
|
||||||
|
MeterReactivePowerTotal int `json:"meter_reactive_power_total"`
|
||||||
|
MeterApparentPower1 int `json:"meter_apparent_power1"`
|
||||||
|
MeterApparentPower2 int `json:"meter_apparent_power2"`
|
||||||
|
MeterApparentPower3 int `json:"meter_apparent_power3"`
|
||||||
|
MeterApparentPowerTotal int `json:"meter_apparent_power_total"`
|
||||||
|
MeterType int `json:"meter_type"`
|
||||||
|
MeterSoftwareVersion int `json:"meter_software_version"`
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue