257 lines
9.3 KiB
Go
257 lines
9.3 KiB
Go
package inverter
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"strings"
|
|
|
|
"git.netflux.io/rob/goodwe-go/command"
|
|
)
|
|
|
|
type ET struct {
|
|
SerialNumber string
|
|
ModelName string
|
|
}
|
|
|
|
// Unexported struct used for parsing binary data only.
|
|
type etDeviceInfo struct {
|
|
ModbusVersion uint16
|
|
RatedPower uint16
|
|
ACOutputType uint16
|
|
SerialNumber [16]byte
|
|
ModelName [10]byte
|
|
DSP1SWVersion uint16
|
|
DSP2SWVersion uint16
|
|
DSPSVNVersion uint16
|
|
ArmSWVersion uint16
|
|
ArmSVNVersion uint16
|
|
SoftwareVersion [12]byte
|
|
ArmVersion [12]byte
|
|
}
|
|
|
|
func (info *etDeviceInfo) toDeviceInfo() *DeviceInfo {
|
|
serialNumber := string(info.SerialNumber[:])
|
|
return &DeviceInfo{
|
|
ModbusVersion: int(info.ModbusVersion),
|
|
RatedPower: int(info.RatedPower),
|
|
ACOutputType: int(info.ACOutputType),
|
|
SerialNumber: serialNumber,
|
|
ModelName: strings.TrimSpace(string(info.ModelName[:])),
|
|
DSP1SWVersion: int(info.DSP1SWVersion),
|
|
DSP2SWVersion: int(info.DSP2SWVersion),
|
|
DSPSVNVersion: int(info.DSPSVNVersion),
|
|
ArmSWVersion: int(info.ArmSWVersion),
|
|
ArmSVNVersion: int(info.ArmSVNVersion),
|
|
SoftwareVersion: string(info.SoftwareVersion[:]),
|
|
ArmVersion: string(info.ArmVersion[:]),
|
|
SinglePhase: strings.Contains(serialNumber, "EHU"),
|
|
}
|
|
}
|
|
|
|
// Unexported struct used for parsing binary data only.
|
|
type etRuntimeData struct {
|
|
_ [6]byte
|
|
PV1Voltage int16
|
|
PV1Current int16
|
|
PV1Power int32
|
|
PV2Voltage int16
|
|
PV2Current int16
|
|
PV2Power int32
|
|
_ [18]byte
|
|
PV2Mode byte
|
|
PV1Mode byte
|
|
OnGridL1Voltage int16
|
|
OnGridL1Current int16
|
|
OnGridL1Frequency int16
|
|
OnGridL1Power int32
|
|
OnGridL2Voltage int16
|
|
OnGridL2Current int16
|
|
OnGridL2Frequency int16
|
|
OnGridL2Power int32
|
|
OnGridL3Voltage int16
|
|
OnGridL3Current int16
|
|
OnGridL3Frequency int16
|
|
OnGridL3Power int32
|
|
GridMode int16
|
|
TotalInverterPower int32
|
|
ActivePower int32
|
|
ReactivePower int32
|
|
ApparentPower int32
|
|
BackupL1Voltage int16
|
|
BackupL1Current int16
|
|
BackupL1Frequency int16
|
|
LoadModeL1 int16
|
|
BackupL1Power int32
|
|
BackupL2Voltage int16
|
|
BackupL2Current int16
|
|
BackupL2Frequency int16
|
|
LoadModeL2 int16
|
|
BackupL2Power int32
|
|
BackupL3Voltage int16
|
|
BackupL3Current int16
|
|
BackupL3Frequency int16
|
|
LoadModeL3 int16
|
|
BackupL3Power int32
|
|
LoadL1 int32
|
|
LoadL2 int32
|
|
LoadL3 int32
|
|
BackupLoad int32
|
|
Load int32
|
|
UPSLoad int16
|
|
TemperatureAir int16
|
|
TemperatureModule int16
|
|
Temperature int16
|
|
FunctionBit int16
|
|
BusVoltage int16
|
|
NBusVoltage int16
|
|
BatteryVoltage int16
|
|
BatteryCurrent int16
|
|
_ [2]byte
|
|
BatteryMode int32
|
|
WarningCode int16
|
|
SafetyCountryCode int16
|
|
WorkMode int32
|
|
OperationCode int16
|
|
ErrorCodes int16
|
|
PVGenerationTotal int32
|
|
PVGenerationToday int32
|
|
EnergyExportTotal int32
|
|
EnergyExportTotalHours int32
|
|
EnergyExportToday int16
|
|
EnergyImportTotal int32
|
|
EnergyImportToday int16
|
|
EnergyLoadTotal int32
|
|
EnergyLoadDay int16
|
|
BatteryChargeTotal int32
|
|
BatteryChargeToday int16
|
|
BatteryDischargeTotal int32
|
|
BatteryDischargeToday int16
|
|
_ [16]byte
|
|
DiagStatusCode int32
|
|
}
|
|
|
|
func (data *etRuntimeData) toRuntimeData(singlePhase bool) *ETRuntimeData {
|
|
filterSinglePhase := func(i int) int {
|
|
if singlePhase {
|
|
return 0
|
|
}
|
|
return i
|
|
}
|
|
|
|
return &ETRuntimeData{
|
|
PV1Voltage: int(data.PV1Voltage),
|
|
PV1Current: int(data.PV1Current),
|
|
PV1Power: int(data.PV1Power),
|
|
PV2Voltage: int(data.PV2Voltage),
|
|
PV2Current: int(data.PV2Current),
|
|
PV2Power: int(data.PV2Power),
|
|
PVPower: int(data.PV1Power) + int(data.PV2Power),
|
|
PV2Mode: data.PV2Mode,
|
|
PV1Mode: data.PV1Mode,
|
|
OnGridL1Voltage: int(data.OnGridL1Voltage),
|
|
OnGridL1Current: int(data.OnGridL1Current),
|
|
OnGridL1Frequency: int(data.OnGridL1Frequency),
|
|
OnGridL1Power: int(data.OnGridL1Power),
|
|
OnGridL2Voltage: filterSinglePhase(int(data.OnGridL2Voltage)),
|
|
OnGridL2Current: filterSinglePhase(int(data.OnGridL2Current)),
|
|
OnGridL2Frequency: filterSinglePhase(int(data.OnGridL2Frequency)),
|
|
OnGridL2Power: filterSinglePhase(int(data.OnGridL2Power)),
|
|
OnGridL3Voltage: filterSinglePhase(int(data.OnGridL3Voltage)),
|
|
OnGridL3Current: filterSinglePhase(int(data.OnGridL3Current)),
|
|
OnGridL3Frequency: filterSinglePhase(int(data.OnGridL3Frequency)),
|
|
OnGridL3Power: filterSinglePhase(int(data.OnGridL3Power)),
|
|
GridMode: int(data.GridMode),
|
|
TotalInverterPower: int(data.TotalInverterPower),
|
|
ActivePower: int(data.ActivePower),
|
|
ReactivePower: int(data.ReactivePower),
|
|
ApparentPower: int(data.ApparentPower),
|
|
BackupL1Voltage: int(data.BackupL1Voltage),
|
|
BackupL1Current: int(data.BackupL1Current),
|
|
BackupL1Frequency: int(data.BackupL1Frequency),
|
|
LoadModeL1: int(data.LoadModeL1),
|
|
BackupL1Power: int(data.BackupL1Power),
|
|
BackupL2Voltage: filterSinglePhase(int(data.BackupL2Voltage)),
|
|
BackupL2Current: filterSinglePhase(int(data.BackupL2Current)),
|
|
BackupL2Frequency: filterSinglePhase(int(data.BackupL2Frequency)),
|
|
LoadModeL2: filterSinglePhase(int(data.LoadModeL2)),
|
|
BackupL2Power: filterSinglePhase(int(data.BackupL2Power)),
|
|
BackupL3Voltage: filterSinglePhase(int(data.BackupL3Voltage)),
|
|
BackupL3Current: filterSinglePhase(int(data.BackupL3Current)),
|
|
BackupL3Frequency: filterSinglePhase(int(data.BackupL3Frequency)),
|
|
LoadModeL3: filterSinglePhase(int(data.LoadModeL3)),
|
|
BackupL3Power: filterSinglePhase(int(data.BackupL3Power)),
|
|
LoadL1: int(data.LoadL1),
|
|
LoadL2: filterSinglePhase(int(data.LoadL2)),
|
|
LoadL3: filterSinglePhase(int(data.LoadL3)),
|
|
BackupLoad: int(data.BackupLoad),
|
|
Load: int(data.Load),
|
|
UPSLoad: int(data.UPSLoad),
|
|
TemperatureAir: int(data.TemperatureAir),
|
|
TemperatureModule: int(data.TemperatureModule),
|
|
Temperature: int(data.Temperature),
|
|
FunctionBit: int(data.FunctionBit),
|
|
BusVoltage: int(data.BusVoltage),
|
|
NBusVoltage: int(data.NBusVoltage),
|
|
BatteryVoltage: int(data.BatteryVoltage),
|
|
BatteryCurrent: int(data.BatteryCurrent),
|
|
BatteryMode: int(data.BatteryMode),
|
|
WarningCode: int(data.WarningCode),
|
|
SafetyCountryCode: int(data.SafetyCountryCode),
|
|
WorkMode: int(data.WorkMode),
|
|
OperationCode: int(data.OperationCode),
|
|
ErrorCodes: int(data.ErrorCodes),
|
|
PVGenerationTotal: int(data.PVGenerationTotal),
|
|
PVGenerationToday: int(data.PVGenerationToday),
|
|
EnergyExportTotal: int(data.EnergyExportTotal),
|
|
EnergyExportTotalHours: int(data.EnergyExportTotalHours),
|
|
EnergyExportToday: int(data.EnergyExportToday),
|
|
EnergyImportTotal: int(data.EnergyImportTotal),
|
|
EnergyImportToday: int(data.EnergyImportToday),
|
|
EnergyLoadTotal: int(data.EnergyLoadTotal),
|
|
EnergyLoadDay: int(data.EnergyLoadDay),
|
|
BatteryChargeTotal: int(data.BatteryChargeTotal),
|
|
BatteryChargeToday: int(data.BatteryChargeToday),
|
|
BatteryDischargeTotal: int(data.BatteryDischargeTotal),
|
|
BatteryDischargeToday: int(data.BatteryDischargeToday),
|
|
DiagStatusCode: int(data.DiagStatusCode),
|
|
HouseConsumption: int32(float64(data.PV1Power) + float64(data.PV2Power) + math.Round(float64(data.BatteryVoltage)*float64(data.BatteryCurrent)) - float64(data.ActivePower)),
|
|
}
|
|
}
|
|
|
|
func (inv ET) DeviceInfo(ctx context.Context, conn io.ReadWriter) (*DeviceInfo, error) {
|
|
resp, err := command.Send(command.NewModbus(command.ModbusCommandTypeRead, 0x88b8, 0x0021), conn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error sending command: %s", err)
|
|
}
|
|
|
|
var deviceInfo etDeviceInfo
|
|
if err := binary.Read(bytes.NewReader(resp), binary.BigEndian, &deviceInfo); err != nil {
|
|
return nil, fmt.Errorf("error parsing response: %s", err)
|
|
}
|
|
|
|
return deviceInfo.toDeviceInfo(), nil
|
|
}
|
|
|
|
func (inv ET) RuntimeData(ctx context.Context, conn io.ReadWriter) (*ETRuntimeData, error) {
|
|
deviceInfo, err := inv.DeviceInfo(ctx, conn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error fetching device info: %s", err)
|
|
}
|
|
|
|
resp, err := command.Send(command.NewModbus(command.ModbusCommandTypeRead, 0x891c, 0x007d), conn)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error sending command: %s", err)
|
|
}
|
|
|
|
var runtimeData etRuntimeData
|
|
if err := binary.Read(bytes.NewReader(resp), binary.BigEndian, &runtimeData); err != nil {
|
|
return nil, fmt.Errorf("error parsing response: %s", err)
|
|
}
|
|
|
|
return runtimeData.toRuntimeData(deviceInfo.SinglePhase), nil
|
|
}
|