Implement DeviceInfo and RuntimeData commands
This commit is contained in:
parent
fcd937c52a
commit
e467102cfe
|
@ -41,19 +41,19 @@ func aa55Checksum(payload []byte) []byte {
|
||||||
|
|
||||||
func (cmd AA55Command) String() string { return string(cmd.payload) }
|
func (cmd AA55Command) String() string { return string(cmd.payload) }
|
||||||
|
|
||||||
func (cmd AA55Command) validateResponse(p []byte) error {
|
func (cmd AA55Command) validateResponse(p []byte) ([]byte, error) {
|
||||||
if len(p) < 8 {
|
if len(p) < 8 {
|
||||||
return fmt.Errorf("response truncated")
|
return nil, fmt.Errorf("response truncated")
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedLen := int(p[aa55ResponseLengthIndex] + aa55ResponseLengthOffset)
|
expectedLen := int(p[aa55ResponseLengthIndex] + aa55ResponseLengthOffset)
|
||||||
if len(p) != expectedLen {
|
if len(p) != expectedLen {
|
||||||
return fmt.Errorf("unexpected response length %d (expected %d)", len(p), expectedLen)
|
return nil, fmt.Errorf("unexpected response length %d (expected %d)", len(p), expectedLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
responseType := hex.EncodeToString(p[4:6])
|
responseType := hex.EncodeToString(p[4:6])
|
||||||
if responseType != cmd.responseType {
|
if responseType != cmd.responseType {
|
||||||
return fmt.Errorf("unexpected response type `%s` (expected `%s`)", responseType, cmd.responseType)
|
return nil, fmt.Errorf("unexpected response type `%s` (expected `%s`)", responseType, cmd.responseType)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sum uint16
|
var sum uint16
|
||||||
|
@ -62,8 +62,9 @@ func (cmd AA55Command) validateResponse(p []byte) error {
|
||||||
}
|
}
|
||||||
expSum := binary.BigEndian.Uint16(p[len(p)-2:])
|
expSum := binary.BigEndian.Uint16(p[len(p)-2:])
|
||||||
if sum != expSum {
|
if sum != expSum {
|
||||||
return fmt.Errorf("invalid response checksum %d (expected %d)", sum, expSum)
|
return nil, fmt.Errorf("invalid response checksum %d (expected %d)", sum, expSum)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
// FIXME: use correct offsets
|
||||||
|
return p[5 : len(p)-2], nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,33 +4,29 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type command interface {
|
type command interface {
|
||||||
String() string
|
String() string
|
||||||
validateResponse([]byte) error
|
validateResponse([]byte) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send writes the command to the provided Writer, and reads and validates the
|
||||||
|
// response.
|
||||||
|
//
|
||||||
|
// TODO: accept a context.Context and enforce deadline/timeout.
|
||||||
func Send(cmd command, conn io.ReadWriter) ([]byte, error) {
|
func Send(cmd command, conn io.ReadWriter) ([]byte, error) {
|
||||||
_, err := fmt.Fprint(conn, cmd.String())
|
_, err := fmt.Fprint(conn, cmd.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error writing to socket: %s", err)
|
return nil, fmt.Errorf("error writing to socket: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("sent data to socket: %X", cmd)
|
|
||||||
|
|
||||||
p := make([]byte, 4_096)
|
p := make([]byte, 4_096)
|
||||||
r := bufio.NewReader(conn)
|
r := bufio.NewReader(conn)
|
||||||
n, err := r.Read(p)
|
n, err := r.Read(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error reading from socket: %s", err)
|
return nil, fmt.Errorf("error reading from socket: %s", err)
|
||||||
}
|
}
|
||||||
p = p[:n]
|
|
||||||
|
|
||||||
if err := cmd.validateResponse(p); err != nil {
|
return cmd.validateResponse(p[:n])
|
||||||
return nil, fmt.Errorf("error validating response: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,6 @@ func modbusChecksum(b []byte) uint16 {
|
||||||
|
|
||||||
func (cmd ModbusCommand) String() string { return string(cmd.payload) }
|
func (cmd ModbusCommand) String() string { return string(cmd.payload) }
|
||||||
|
|
||||||
func (cmd ModbusCommand) validateResponse(p []byte) error {
|
func (cmd ModbusCommand) validateResponse(p []byte) ([]byte, error) {
|
||||||
return nil
|
return p[5 : len(p)-2], nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package inverter
|
||||||
|
|
||||||
|
// DeviceInfo holds the static information about an inverter.
|
||||||
|
type DeviceInfo struct {
|
||||||
|
ModbusVersion int `json:"modbus_version"`
|
||||||
|
RatedPower int `json:"rated_power"`
|
||||||
|
ACOutputType int `json:"ac_output_type"`
|
||||||
|
SerialNumber string `json:"serial_number"`
|
||||||
|
ModelName string `json:"model_name"`
|
||||||
|
DSP1SWVersion int `json:"dsp1_sw_version"`
|
||||||
|
DSP2SWVersion int `json:"dsp2_sw_version"`
|
||||||
|
DSPSVNVersion int `json:"dsp_svn_version"`
|
||||||
|
ArmSWVersion int `json:"arm_sw_version"`
|
||||||
|
ArmSVNVersion int `json:"arm_svn_version"`
|
||||||
|
SoftwareVersion string `json:"software_version"`
|
||||||
|
ArmVersion string `json:"arm_version"`
|
||||||
|
SinglePhase bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ETRuntimeData struct {
|
||||||
|
PV1Voltage int `json:"pv1_voltage"`
|
||||||
|
PV1Current int `json:"pv1_current"`
|
||||||
|
PV1Power int `json:"pv1_power"`
|
||||||
|
PV2Voltage int `json:"pv2_voltage"`
|
||||||
|
PV2Current int `json:"pv2_current"`
|
||||||
|
PV2Power int `json:"pv2_power"`
|
||||||
|
PVPower int `json:"pv_power"`
|
||||||
|
PV2Mode byte `json:"pv2_mode"`
|
||||||
|
PV1Mode byte `json:"pv1_mode"`
|
||||||
|
OnGridL1Voltage int `json:"on_grid_l1_voltage"`
|
||||||
|
OnGridL1Current int `json:"on_grid_l1_current"`
|
||||||
|
OnGridL1Frequency int `json:"on_grid_l1_frequency"`
|
||||||
|
OnGridL1Power int `json:"on_grid_l1_power"`
|
||||||
|
OnGridL2Voltage int `json:"on_grid_l2_voltage"`
|
||||||
|
OnGridL2Current int `json:"on_grid_l2_current"`
|
||||||
|
OnGridL2Frequency int `json:"on_grid_l2_frequency"`
|
||||||
|
OnGridL2Power int `json:"on_grid_l2_power"`
|
||||||
|
OnGridL3Voltage int `json:"on_grid_l3_voltage"`
|
||||||
|
OnGridL3Current int `json:"on_grid_l3_current"`
|
||||||
|
OnGridL3Frequency int `json:"on_grid_l3_frequency"`
|
||||||
|
OnGridL3Power int `json:"on_grid_l3_power"`
|
||||||
|
GridMode int `json:"grid_mode"`
|
||||||
|
TotalInverterPower int `json:"total_inverter_power"`
|
||||||
|
ActivePower int `json:"active_power"`
|
||||||
|
ReactivePower int `json:"reactive_power"`
|
||||||
|
ApparentPower int `json:"apparent_power"`
|
||||||
|
BackupL1Voltage int `json:"backup_l1_voltage"`
|
||||||
|
BackupL1Current int `json:"backup_l1_current"`
|
||||||
|
BackupL1Frequency int `json:"backup_l1_frequency"`
|
||||||
|
LoadModeL1 int `json:"load_mode_l1"`
|
||||||
|
BackupL1Power int `json:"backup_l1_power"`
|
||||||
|
BackupL2Voltage int `json:"backup_l2_voltage"`
|
||||||
|
BackupL2Current int `json:"backup_l2_current"`
|
||||||
|
BackupL2Frequency int `json:"backup_l2_frequency"`
|
||||||
|
LoadModeL2 int `json:"load_mode_l2"`
|
||||||
|
BackupL2Power int `json:"backup_l2_power"`
|
||||||
|
BackupL3Voltage int `json:"backup_l3_voltage"`
|
||||||
|
BackupL3Current int `json:"backup_l3_current"`
|
||||||
|
BackupL3Frequency int `json:"backup_l3_frequency"`
|
||||||
|
LoadModeL3 int `json:"load_mode_l3"`
|
||||||
|
BackupL3Power int `json:"backup_l3_power"`
|
||||||
|
LoadL1 int `json:"load_l1"`
|
||||||
|
LoadL2 int `json:"load_l2"`
|
||||||
|
LoadL3 int `json:"load_l3"`
|
||||||
|
BackupLoad int `json:"backup_load"`
|
||||||
|
Load int `json:"load"`
|
||||||
|
UPSLoad int `json:"ups_load"`
|
||||||
|
TemperatureAir int `json:"temperature_air"`
|
||||||
|
TemperatureModule int `json:"temperature_module"`
|
||||||
|
Temperature int `json:"temperature"`
|
||||||
|
FunctionBit int `json:"-"`
|
||||||
|
BusVoltage int `json:"bus_voltage"`
|
||||||
|
NBusVoltage int `json:"nbus_voltage"`
|
||||||
|
BatteryVoltage int `json:"battery_voltage"`
|
||||||
|
BatteryCurrent int `json:"battery_current"`
|
||||||
|
BatteryMode int `json:"battery_mode"`
|
||||||
|
WarningCode int `json:"warning_code"`
|
||||||
|
SafetyCountryCode int `json:"safety_country_code"`
|
||||||
|
WorkMode int `json:"work_mode"`
|
||||||
|
OperationCode int `json:"operation_code"`
|
||||||
|
ErrorCodes int `json:"-"`
|
||||||
|
PVGenerationTotal int `json:"pv_generation_total"`
|
||||||
|
PVGenerationToday int `json:"pv_generation_today"`
|
||||||
|
EnergyExportTotal int `json:"energy_export_total"`
|
||||||
|
EnergyExportTotalHours int `json:"energy_export_total_hours"`
|
||||||
|
EnergyExportToday int `json:"energy_export_today"`
|
||||||
|
EnergyImportTotal int `json:"energy_import_total"`
|
||||||
|
EnergyImportToday int `json:"energy_import_today"`
|
||||||
|
EnergyLoadTotal int `json:"energy_load_total"`
|
||||||
|
EnergyLoadDay int `json:"energy_load_day"`
|
||||||
|
BatteryChargeTotal int `json:"battery_charge_total"`
|
||||||
|
BatteryChargeToday int `json:"battery_charge_today"`
|
||||||
|
BatteryDischargeTotal int `json:"battery_discharge_total"`
|
||||||
|
BatteryDischargeToday int `json:"battery_discharge_today"`
|
||||||
|
DiagStatusCode int `json:"-"`
|
||||||
|
HouseConsumption int32 `json:"house_consumption"`
|
||||||
|
}
|
53
main.go
53
main.go
|
@ -1,16 +1,19 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"time"
|
||||||
|
|
||||||
"git.netflux.io/rob/goodwe-go/command"
|
"git.netflux.io/rob/goodwe-go/inverter"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const commandTimeout = time.Second * 5
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var ipAddr string
|
var ipAddr string
|
||||||
flag.StringVar(&ipAddr, "ipaddr", "", "IP address/port")
|
flag.StringVar(&ipAddr, "ipaddr", "", "IP address/port")
|
||||||
|
@ -21,7 +24,13 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("ipAddr", ipAddr)
|
arg := flag.Arg(0)
|
||||||
|
if arg != "discover" && arg != "runtime" && arg != "info" {
|
||||||
|
log.Fatal("missing command: [discover|runtime|info]")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), commandTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
conn, err := net.Dial("udp", ipAddr)
|
conn, err := net.Dial("udp", ipAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -29,26 +38,32 @@ func main() {
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
|
||||||
infoCmd, err := command.NewAA55("010200", "0182")
|
var (
|
||||||
|
inverter inverter.ET
|
||||||
|
output any
|
||||||
|
)
|
||||||
|
|
||||||
|
switch arg {
|
||||||
|
case "discover":
|
||||||
|
log.Fatal("not yet implemented")
|
||||||
|
case "info":
|
||||||
|
output, err = inverter.DeviceInfo(ctx, conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error building command: %s", err)
|
log.Fatalf("error getting device info: %s", err)
|
||||||
|
}
|
||||||
|
case "runtime":
|
||||||
|
output, err = inverter.RuntimeData(ctx, conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("error getting runtime data: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := command.Send(infoCmd, conn)
|
json, err := json.Marshal(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("error sending command: %s", err)
|
log.Fatalf("error encoding JSON: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
modelName := strings.TrimSpace(string(resp[12:22]))
|
if _, err = os.Stdout.Write(json); err != nil {
|
||||||
serialNum := string(resp[38:54])
|
log.Fatalf("error writing to stdout: %s", err)
|
||||||
|
|
||||||
log.Printf("modelName = %q, serialNum = %q\n", modelName, serialNum)
|
|
||||||
|
|
||||||
dataCmd := command.NewModbus(command.ModbusCommandTypeRead, 0x891c, 0x007d)
|
|
||||||
resp, err = command.Send(dataCmd, conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("error sending command: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("rcvd modbus resp = %X", resp)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue