This repository has been archived by the owner on Oct 7, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
modbus.go
152 lines (124 loc) · 4.59 KB
/
modbus.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package main
import (
"fmt"
solaredge "github.com/ingmarstein/mielesolar/modbus"
"github.com/simonvetter/modbus"
"log"
"math"
"time"
)
type modbusProvider struct {
c *modbus.ModbusClient
hasBattery bool
inverterUnitID int
}
func newModbusProvider(address string, unitID int) (*modbusProvider, error) {
p := modbusProvider{
inverterUnitID: unitID,
}
var err error
p.c, err = modbus.NewClient(&modbus.ClientConfiguration{
URL: "tcp://" + address,
Timeout: 10 * time.Second,
})
if err != nil {
return nil, fmt.Errorf("error creating client: %v", err)
}
return &p, err
}
func (mp *modbusProvider) Open() error {
if err := mp.c.Open(); err != nil {
return err
}
if err := mp.c.SetUnitId(uint8(mp.inverterUnitID)); err != nil {
return fmt.Errorf("error setting unit ID: %v", err)
}
return nil
}
func (mp *modbusProvider) Close() error {
if err := mp.c.Close(); err != nil {
return fmt.Errorf("error closing modbus client: %v", err)
}
return nil
}
func (mp *modbusProvider) Init() {
// Collect and log common inverter data
inverter, err := solaredge.ReadInverter(mp.c)
if err != nil {
log.Fatalf("%v", err)
}
log.Printf("Inverter Manufacturer: %s", inverter.Manufacturer())
log.Printf("Inverter Model: %s", inverter.Model())
log.Printf("Inverter Version: %s", inverter.Version())
log.Printf("Inverter Serial: %s", inverter.SerialNumber())
log.Printf("Inverter device ID: %d", inverter.C_DeviceAddress)
meter, err := solaredge.ReadMeter(mp.c, 0)
if err != nil {
log.Fatalf("error reading meter registers: %s", err.Error())
}
if meter.C_DeviceAddress != 0x8000 {
log.Printf("Meter Manufacturer: %s", meter.Manufacturer())
log.Printf("Meter Model: %s", meter.Model())
log.Printf("Meter Option: %s", meter.Option())
log.Printf("Meter Version: %s", meter.Version())
log.Printf("Meter Serial: %s", meter.SerialNumber())
log.Printf("Meter device ID: %d", meter.C_DeviceAddress)
}
battery, err := solaredge.ReadBatteryInfo(mp.c, 0)
if err != nil {
log.Fatalf("error reading battery registers: %s", err.Error())
}
mp.hasBattery = battery.C_DeviceAddress != 0xFF
if mp.hasBattery {
log.Printf("Battery Manufacturer: %s", battery.Manufacturer())
log.Printf("Battery Model: %s", battery.Model())
log.Printf("Battery Version: %s", battery.Version())
log.Printf("Battery Serial: %s", battery.SerialNumber())
log.Printf("Battery device ID: %d", battery.C_DeviceAddress)
log.Printf("Battery rated energy: %.0f W", battery.RatedEnergy)
log.Printf("Battery maximum charge continuous power: %.0f W", battery.MaximumChargeContinuousPower)
log.Printf("Battery maximum discharge continuous power: %.0f W", battery.MaximumDischargeContinuousPower)
log.Printf("Battery maximum charge peak power: %.0f W", battery.MaximumChargePeakPower)
log.Printf("Battery maximum discharge peak power: %.0f W", battery.MaximumDischargePeakPower)
}
}
func (mp *modbusProvider) CurrentPowerExport() (float64, error) {
inverter, err := solaredge.ReadInverter(mp.c)
if err != nil {
log.Printf("error reading inverter registers: %s", err.Error())
return 0, err
}
if inverter.Status != solaredge.I_STATUS_MPPT && inverter.Status != solaredge.I_STATUS_THROTTLED {
log.Printf("current inverter status: %d\n", inverter.Status)
//return 0, nil
}
// inverter DC power = solar production
inverterDCPower := float64(inverter.DC_Power) * math.Pow(10.0, float64(inverter.DC_Power_SF))
log.Printf("Inverter DC Power: %f", inverterDCPower)
// inverter AC power = production after conversion to AC
inverterACPower := float64(inverter.AC_Power) * math.Pow(10.0, float64(inverter.AC_Power_SF))
log.Printf("Inverter AC Power: %f", inverterACPower)
meter, err := solaredge.ReadMeter(mp.c, 0)
if err != nil {
log.Printf("error reading meter data: %s", err.Error())
return 0, err
}
// meter AC power = balance of production and consumption
// positive values indicate a surplus -> export to grid
// negative values indicate a deficit -> import from grid
meterACPower := float64(meter.M_AC_Power) * math.Pow(10.0, float64(meter.M_AC_Power_SF))
log.Printf("Meter AC Power: %f", meterACPower)
powerExport := meterACPower
// If the system has a battery installed, consider the amount of energy flowing into it
// as surplus. That is, prioritize Miele appliances higher than the battery.
if mp.hasBattery {
battery, err := solaredge.ReadBattery(mp.c, 0)
if err != nil {
log.Printf("error reading battery data: %v", err)
return 0, err
}
log.Printf("Battery Power: %f", battery.InstantaneousPower)
powerExport += float64(battery.InstantaneousPower)
}
return powerExport, nil
}