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) validateResponse(p []byte) error {
|
||||
func (cmd AA55Command) validateResponse(p []byte) ([]byte, error) {
|
||||
if len(p) < 8 {
|
||||
return fmt.Errorf("response truncated")
|
||||
return nil, fmt.Errorf("response truncated")
|
||||
}
|
||||
|
||||
expectedLen := int(p[aa55ResponseLengthIndex] + aa55ResponseLengthOffset)
|
||||
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])
|
||||
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
|
||||
|
@ -62,8 +62,9 @@ func (cmd AA55Command) validateResponse(p []byte) error {
|
|||
}
|
||||
expSum := binary.BigEndian.Uint16(p[len(p)-2:])
|
||||
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"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
)
|
||||
|
||||
type command interface {
|
||||
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) {
|
||||
_, err := fmt.Fprint(conn, cmd.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error writing to socket: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("sent data to socket: %X", cmd)
|
||||
|
||||
p := make([]byte, 4_096)
|
||||
r := bufio.NewReader(conn)
|
||||
n, err := r.Read(p)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading from socket: %s", err)
|
||||
}
|
||||
p = p[:n]
|
||||
|
||||
if err := cmd.validateResponse(p); err != nil {
|
||||
return nil, fmt.Errorf("error validating response: %s", err)
|
||||
}
|
||||
|
||||
return p, nil
|
||||
return cmd.validateResponse(p[:n])
|
||||
}
|
||||
|
|
|
@ -58,6 +58,6 @@ func modbusChecksum(b []byte) uint16 {
|
|||
|
||||
func (cmd ModbusCommand) String() string { return string(cmd.payload) }
|
||||
|
||||
func (cmd ModbusCommand) validateResponse(p []byte) error {
|
||||
return nil
|
||||
func (cmd ModbusCommand) validateResponse(p []byte) ([]byte, error) {
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.netflux.io/rob/goodwe-go/command"
|
||||
"git.netflux.io/rob/goodwe-go/inverter"
|
||||
)
|
||||
|
||||
const commandTimeout = time.Second * 5
|
||||
|
||||
func main() {
|
||||
var ipAddr string
|
||||
flag.StringVar(&ipAddr, "ipaddr", "", "IP address/port")
|
||||
|
@ -21,7 +24,13 @@ func main() {
|
|||
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)
|
||||
if err != nil {
|
||||
|
@ -29,26 +38,32 @@ func main() {
|
|||
}
|
||||
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 {
|
||||
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 {
|
||||
log.Fatalf("error sending command: %s", err)
|
||||
log.Fatalf("error encoding JSON: %s", err)
|
||||
}
|
||||
|
||||
modelName := strings.TrimSpace(string(resp[12:22]))
|
||||
serialNum := string(resp[38:54])
|
||||
|
||||
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)
|
||||
if _, err = os.Stdout.Write(json); err != nil {
|
||||
log.Fatalf("error writing to stdout: %s", err)
|
||||
}
|
||||
|
||||
log.Printf("rcvd modbus resp = %X", resp)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue