Skip to content

Commit f6c64df

Browse files
authored
Merge pull request #18 from Maschga/main
add write support
2 parents 1c5b750 + 1afb10c commit f6c64df

File tree

4 files changed

+218
-26
lines changed

4 files changed

+218
-26
lines changed

README.md

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# rct
22

3-
A library for communication with solar power inverters of the RCT power brand.
3+
A library for communication with solar power inverters of the RCT power brand.
44
Tested with the RCT PS 6.0 solar power inverter, battery and grid power sensor.
55

66
RCT power is a registered trademark of RCT Power GmbH. This library is not provided by, endorsed by, supported by or affiliated with the company in any way.
@@ -18,26 +18,33 @@ Use like this:
1818
package main
1919

2020
import (
21-
"rct"
22-
"time"
23-
"fmt"
21+
"fmt"
22+
"rct"
23+
"time"
2424
)
2525

2626
func main() {
27-
conn, err:=rct.NewConnection("my-RCT-hostname-or-IP-address", time.Second*2)
28-
if err!=nil {
29-
fmt.Println(err)
30-
return
31-
}
32-
defer conn.Close()
33-
34-
a, err:=rct.QueryFloat32(rct.SolarGenAPowerW)
35-
if err!=nil {
36-
fmt.Println(err)
37-
return
38-
}
39-
40-
fmt.Printf("%s is %.0fV\n", string(rct.SolarGenAPowerW), a)
27+
conn, err := rct.NewConnection("my-RCT-hostname-or-IP-address", time.Second*2)
28+
if err != nil {
29+
fmt.Println(err)
30+
return
31+
}
32+
defer conn.Close()
33+
34+
// read
35+
a, err := conn.QueryFloat32(rct.SolarGenAPowerW)
36+
if err != nil {
37+
fmt.Println(err)
38+
return
39+
}
40+
41+
fmt.Printf("%s is %.0fV\n", string(rct.SolarGenAPowerW), a)
42+
43+
// write
44+
if err := conn.SetSocMin(0.07); err != nil {
45+
fmt.Println(err)
46+
return
47+
}
4148
}
4249
```
4350

@@ -48,4 +55,4 @@ func main() {
4855
* `build.go` defines a datagram builder for assembling datagrams to send
4956
* `parse.go` defines a datagram parser which parses incoming bytes into datagrams
5057
* `connection.go` ties builders and parsers into a bidirectional connection with the device, and defines convenience methods to synchronously query identifiers
51-
58+
* `write.go` defines methods to validate and write data

connection.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,11 @@ func (c *Connection) QueryUint8(id Identifier) (val uint8, err error) {
175175
}
176176
return dg.Uint8()
177177
}
178+
179+
// Writes the given identifier with the given value on the RCT device
180+
func (c *Connection) Write(id Identifier, data []byte) error {
181+
b := NewDatagramBuilder()
182+
b.Build(&Datagram{Write, id, data})
183+
_, err := c.Send(b)
184+
return err
185+
}

datagram.go

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ func (c Command) String() string {
4646
return rctCommandToString[0]
4747
}
4848

49+
// SOC target selection
50+
const (
51+
SOCTargetSOC uint8 = 0x00
52+
SOCTargetConstant uint8 = 0x01
53+
SOCTargetExternal uint8 = 0x02
54+
SOCTargetMiddleVoltage uint8 = 0x03
55+
SOCTargetInternal uint8 = 0x04 // default
56+
SOCTargetSchedule uint8 = 0x05
57+
)
58+
4959
// Identifier type for variables on the RCT device
5060
type Identifier uint32
5161

@@ -80,15 +90,27 @@ const (
8090
TotalEnergyGridFeedInWh Identifier = 0x44D4C533 // float32
8191
TotalEnergyGridLoadWh Identifier = 0x62FBE7DC // float32
8292

93+
// write
94+
//
95+
PowerMngSocStrategy Identifier = 0xF168B748 // ENUM: SOC target selection
96+
PowerMngSocTargetSet Identifier = 0xD1DFC969 // float32
97+
PowerMngBatteryPowerExternW Identifier = 0xBD008E29 // float32
98+
BatterySoCTargetMin Identifier = 0xCE266F0F // float32 0 ... 1
99+
BatterySoCTargetMinIsland Identifier = 0x8EBF9574 // float32 0 ... 1
100+
PowerMngSocMax Identifier = 0x97997C93 // float32
101+
PowerMngSocChargePowerW Identifier = 0x1D2994EA // float32
102+
PowerMngSocCharge Identifier = 0xBD3A23C3 // float32
103+
PowerMngGridPowerLimitW Identifier = 0x54829753 // float32
104+
PowerMngUseGridPowerEnable Identifier = 0x36A9E9A6 // bool
105+
83106
// other
84107
//
85-
InverterState Identifier = 0x5F33284E // uint8
86-
BatteryCapacityAh Identifier = 0xB57B59BD // float32
87-
BatteryTemperatureC Identifier = 0x902AFAFB // float32
88-
BatterySoCTarget Identifier = 0x8B9FF008 // float32 0 ... 1
89-
BatterySoCTargetHigh Identifier = 0xB84A38AB // float32 0 ... 1
90-
BatterySoCTargetMin Identifier = 0xCE266F0F // float32 0 ... 1
91-
BatterySoCTargetMinIsland Identifier = 0x8EBF9574 // float32 0 ... 1
108+
InverterState Identifier = 0x5F33284E // uint8
109+
BatteryCapacityAh Identifier = 0xB57B59BD // float32
110+
BatteryTemperatureC Identifier = 0x902AFAFB // float32
111+
BatterySoCTarget Identifier = 0x8B9FF008 // float32 0 ... 1
112+
BatterySoCTargetHigh Identifier = 0xB84A38AB // float32 0 ... 1
113+
BatteryBatStatus Identifier = 0x70A2AF4F // int32
92114
)
93115

94116
// Table to convert identifier values to human-readable strings

write.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package rct
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"math"
7+
)
8+
9+
/*
10+
* See https://github.com/do-gooder/rctpower_writesupport?tab=readme-ov-file#usage
11+
*/
12+
13+
// SetSocStrategy sets the SOC strategy (power_mng.soc_strategy) with the given ENUM value
14+
func (c *Connection) SetSocStrategy(strategy uint8) error {
15+
if strategy > SOCTargetSchedule {
16+
return fmt.Errorf("invalid SOC strategy value: %d", strategy)
17+
}
18+
19+
if err := c.Write(PowerMngSocStrategy, []byte{strategy}); err != nil {
20+
return fmt.Errorf("failed to set SOC strategy: %w", err)
21+
}
22+
23+
return nil
24+
}
25+
26+
// SetSocTarget sets the SOC target (power_mng.soc_target_set) with the given value
27+
func (c *Connection) SetSocTarget(target float32) error {
28+
if target < 0.00 || target > 1.00 {
29+
return fmt.Errorf("invalid SOC target value: %.2f, valid range is 0.00 to 1.00", target)
30+
}
31+
32+
data := make([]byte, 4)
33+
binary.BigEndian.PutUint32(data, math.Float32bits(target))
34+
35+
if err := c.Write(PowerMngSocTargetSet, data); err != nil {
36+
return fmt.Errorf("failed to set SOC target: %w", err)
37+
}
38+
39+
return nil
40+
}
41+
42+
// SetBatteryPowerExtern sets the external battery power (power_mng.battery_power_extern) with the given float32 value in W
43+
func (c *Connection) SetBatteryPowerExtern(power float32) error {
44+
data := make([]byte, 4)
45+
binary.BigEndian.PutUint32(data, math.Float32bits(power))
46+
47+
if err := c.Write(PowerMngBatteryPowerExternW, data); err != nil {
48+
return fmt.Errorf("failed to set battery power extern: %w", err)
49+
}
50+
51+
return nil
52+
}
53+
54+
// SetSocMin sets the minimum SOC target (power_mng.soc_min) with the given value
55+
func (c *Connection) SetSocMin(min float32) error {
56+
if min < 0.00 || min > 1.00 {
57+
return fmt.Errorf("invalid SOC min value: %.2f, valid range is 0.00 to 1.00", min)
58+
}
59+
60+
data := make([]byte, 4)
61+
binary.BigEndian.PutUint32(data, math.Float32bits(min))
62+
63+
if err := c.Write(BatterySoCTargetMin, data); err != nil {
64+
return fmt.Errorf("failed to set SOC min: %w", err)
65+
}
66+
67+
return nil
68+
}
69+
70+
// SetSocMinIsland sets the minimum SOC target (power_mng.soc_min_island) with the given value
71+
func (c *Connection) SetSocMinIsland(min float32) error {
72+
if min < 0.00 || min > 1.00 {
73+
return fmt.Errorf("invalid SOC min value: %.2f, valid range is 0.00 to 1.00", min)
74+
}
75+
76+
data := make([]byte, 4)
77+
binary.BigEndian.PutUint32(data, math.Float32bits(min))
78+
79+
if err := c.Write(BatterySoCTargetMinIsland, data); err != nil {
80+
return fmt.Errorf("failed to set SOC min island: %w", err)
81+
}
82+
83+
return nil
84+
}
85+
86+
// SetSocMax sets the maximum SOC target (power_mng.soc_max) with the given value
87+
func (c *Connection) SetSocMax(max float32) error {
88+
if max < 0.00 || max > 1.00 {
89+
return fmt.Errorf("invalid SOC max value: %.2f, valid range is 0.00 to 1.00", max)
90+
}
91+
92+
data := make([]byte, 4)
93+
binary.BigEndian.PutUint32(data, math.Float32bits(max))
94+
95+
if err := c.Write(PowerMngSocMax, data); err != nil {
96+
return fmt.Errorf("failed to set SOC max: %w", err)
97+
}
98+
99+
return nil
100+
}
101+
102+
// SetSocChargePower sets the charging power to reach SOC target (power_mng.soc_charge_power)
103+
func (c *Connection) SetSocChargePower(power uint16) error {
104+
// Valid range is not defined, assume it’s a valid unsigned integer
105+
data := make([]byte, 2)
106+
binary.BigEndian.PutUint16(data, power)
107+
108+
if err := c.Write(PowerMngSocChargePowerW, data); err != nil {
109+
return fmt.Errorf("failed to set SOC charge power: %w", err)
110+
}
111+
112+
return nil
113+
}
114+
115+
// SetSocCharge sets the trigger for charging to SOC_min (power_mng.soc_charge)
116+
func (c *Connection) SetSocCharge(charge float32) error {
117+
if charge < 0.00 || charge > 1.00 {
118+
return fmt.Errorf("invalid SOC charge value: %.2f, valid range is 0.00 to 1.00", charge)
119+
}
120+
121+
data := make([]byte, 4)
122+
binary.BigEndian.PutUint32(data, math.Float32bits(charge))
123+
124+
if err := c.Write(PowerMngSocCharge, data); err != nil {
125+
return fmt.Errorf("failed to set SOC charge: %w", err)
126+
}
127+
128+
return nil
129+
}
130+
131+
// SetGridPowerLimit sets the maximum battery-to-grid power (p_rec_lim[1])
132+
func (c *Connection) SetGridPowerLimit(power uint16) error {
133+
data := make([]byte, 2)
134+
binary.BigEndian.PutUint16(data, power)
135+
136+
if err := c.Write(PowerMngGridPowerLimitW, data); err != nil {
137+
return fmt.Errorf("failed to set grid power limit: %w", err)
138+
}
139+
140+
return nil
141+
}
142+
143+
// SetUseGridPower sets the enable/disable flag for grid power usage (power_mng.use_grid_power_enable)
144+
func (c *Connection) SetUseGridPower(enable bool) error {
145+
var data byte
146+
if enable {
147+
data = 1
148+
}
149+
150+
if err := c.Write(PowerMngUseGridPowerEnable, []byte{data}); err != nil {
151+
return fmt.Errorf("failed to set grid power usage: %w", err)
152+
}
153+
154+
return nil
155+
}

0 commit comments

Comments
 (0)