Gpio plugin: Raspberry Pi 5 compatibility (#27815)

This commit is contained in:
Copilot 2026-03-08 12:32:33 +01:00 • committed by GitHub
parent a0dccea9a3
commit 6e12bd9d59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 71 additions and 60 deletions

View file

@ -9,12 +9,18 @@ import (
"github.com/evcc-io/evcc/charger/openwb/native"
"github.com/evcc-io/evcc/util"
"github.com/evcc-io/evcc/util/modbus"
"github.com/fatih/structs"
"github.com/stianeikeland/go-rpio/v4"
"github.com/warthog618/go-gpiocdev"
)
const minCpWaitTime time.Duration = 5 * time.Second
// openWbGpioLines holds GPIO lines for a single charge point
type openWbGpioLines struct {
cp *gpiocdev.Line
ph1 *gpiocdev.Line
ph3 *gpiocdev.Line
}
// OpenWbNative charger implementation
type OpenWbNative struct {
api.Charger
@ -23,6 +29,7 @@ type OpenWbNative struct {
cpWait time.Duration
connector int
chargeState api.ChargeStatus
gpio openWbGpioLines
}
// gpioAction defines a single GPIO pin operation with timing
@ -44,8 +51,10 @@ func NewOpenWbNativeFromConfig(ctx context.Context, other map[string]any) (api.C
RfId string
CpWait time.Duration
Connector int
Chip string
modbus.Settings `mapstructure:",squash"`
}{
Chip: "gpiochip0",
Settings: modbus.Settings{
Baudrate: 9600,
Comset: "8N1",
@ -64,11 +73,11 @@ func NewOpenWbNativeFromConfig(ctx context.Context, other map[string]any) (api.C
return nil, fmt.Errorf("invalid cpwait value: %v, needs to be greater than %s", cc.CpWait, minCpWaitTime)
}
return NewOpenWbNative(ctx, cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.Protocol(), cc.ID, cc.Phases1p3p, cc.RfId, cc.CpWait, cc.Connector)
return NewOpenWbNative(ctx, cc.URI, cc.Device, cc.Comset, cc.Baudrate, cc.Protocol(), cc.ID, cc.Phases1p3p, cc.RfId, cc.CpWait, cc.Connector, cc.Chip)
}
// NewOpenWbNative creates OpenWbNative charger
func NewOpenWbNative(ctx context.Context, uri, device, comset string, baudrate int, proto modbus.Protocol, slaveID uint8, hasPhases1p3p bool, rfIdVidPid string, cpWait time.Duration, connector int) (api.Charger, error) {
func NewOpenWbNative(ctx context.Context, uri, device, comset string, baudrate int, proto modbus.Protocol, slaveID uint8, hasPhases1p3p bool, rfIdVidPid string, cpWait time.Duration, connector int, chip string) (api.Charger, error) {
log := util.NewLogger("openwb-native")
log.DEBUG.Printf("Creating OpenWB native with 3 phases %t, rfid %s, cpwait %s, connector %d", hasPhases1p3p, rfIdVidPid, cpWait.String(), connector)
@ -109,14 +118,21 @@ func NewOpenWbNative(ctx context.Context, uri, device, comset string, baudrate i
identify = wb.identify
}
// initialize GPIO and set pins to output
if err := rpio.Open(); err != nil {
return nil, fmt.Errorf("failed to open GPIO: %w", err)
}
defer rpio.Close()
for _, pin := range structs.Fields(native.ChargePoints[connector-1]) {
rpio.Pin(pin.Value().(int)).Output()
// initialize GPIO lines and set pins to output
pins := native.ChargePoints[connector-1]
for _, gpioConfig := range []struct {
dst **gpiocdev.Line
pin int
}{
{&wb.gpio.cp, pins.PIN_CP},
{&wb.gpio.ph1, pins.PIN_1P},
{&wb.gpio.ph3, pins.PIN_3P},
} {
line, err := gpiocdev.RequestLine(chip, gpioConfig.pin, gpiocdev.AsOutput(0))
if err != nil {
return nil, fmt.Errorf("failed to open GPIO pin %d: %w", gpioConfig.pin, err)
}
*gpioConfig.dst = line
}
return decorateOpenWbNative(wb, maxCurrentMillis, phases1p3p, identify), nil
@ -147,21 +163,14 @@ var _ api.Resurrector = (*OpenWbNative)(nil)
// WakeUp implements the api.Resurrector interface
func (wb *OpenWbNative) WakeUp() error {
cpPin := rpio.Pin(native.ChargePoints[wb.connector-1].PIN_CP)
return wb.runGpioSequence([]gpioAction{
{pin: cpPin.High, delay: wb.cpWait},
{pin: cpPin.Low, delay: 0},
{pin: func() { wb.gpio.cp.SetValue(1) }, delay: wb.cpWait},
{pin: func() { wb.gpio.cp.SetValue(0) }, delay: 0},
})
}
// runGpioSequence executes a sequence of GPIO operations
func (wb *OpenWbNative) runGpioSequence(seq []gpioAction) error {
if err := rpio.Open(); err != nil {
return fmt.Errorf("failed to open GPIO: %w", err)
}
defer rpio.Close()
if err := wb.Enable(false); err != nil {
return err
}
@ -178,17 +187,16 @@ func (wb *OpenWbNative) runGpioSequence(seq []gpioAction) error {
// gpioSwitchPhases toggles the GPIOs to switch between 1-phase and 3-phase charging
func (wb *OpenWbNative) gpioSwitchPhases(phases int) error {
cpPin := rpio.Pin(native.ChargePoints[wb.connector-1].PIN_CP)
phPin := rpio.Pin(native.ChargePoints[wb.connector-1].PIN_3P)
phLine := wb.gpio.ph3
if phases == 1 {
phPin = rpio.Pin(native.ChargePoints[wb.connector-1].PIN_1P)
phLine = wb.gpio.ph1
}
return wb.runGpioSequence([]gpioAction{
{pin: cpPin.High, delay: time.Second}, // enable phases switch relay (NO), disconnect CP
{pin: phPin.High, delay: wb.cpWait / 2}, // move latching relay to desired position
{pin: phPin.Low, delay: wb.cpWait / 2}, // lock latching relay
{pin: cpPin.Low, delay: time.Second}, // disable phase switching, reconnect CP
{pin: func() { wb.gpio.cp.SetValue(1) }, delay: time.Second}, // enable phases switch relay (NO), disconnect CP
{pin: func() { phLine.SetValue(1) }, delay: wb.cpWait / 2}, // move latching relay to desired position
{pin: func() { phLine.SetValue(0) }, delay: wb.cpWait / 2}, // lock latching relay
{pin: func() { wb.gpio.cp.SetValue(0) }, delay: time.Second}, // disable phase switching, reconnect CP
})
}

2
go.mod
View file

@ -94,12 +94,12 @@ require (
github.com/spf13/jwalterweatherman v1.1.0
github.com/spf13/pflag v1.0.10
github.com/spf13/viper v1.21.0
github.com/stianeikeland/go-rpio/v4 v4.6.0
github.com/stretchr/testify v1.11.1
github.com/teslamotors/vehicle-command v0.4.1
github.com/tess1o/go-ecoflow v1.1.1-0.20251003083510-2ccc15a17e29
github.com/traefik/yaegi v0.16.1
github.com/volkszaehler/mbmd v0.0.0-20260131091050-86c2d25b6103
github.com/warthog618/go-gpiocdev v0.9.1
gitlab.com/bboehmke/sunny v0.16.0
go.bug.st/serial v1.6.4
go.uber.org/mock v0.6.0

6
go.sum
View file

@ -727,8 +727,6 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stianeikeland/go-rpio/v4 v4.6.0 h1:eAJgtw3jTtvn/CqwbC82ntcS+dtzUTgo5qlZKe677EY=
github.com/stianeikeland/go-rpio/v4 v4.6.0/go.mod h1:A3GvHxC1Om5zaId+HqB3HKqx4K/AqeckxB7qRjxMK7o=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@ -773,6 +771,10 @@ github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAh
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/volkszaehler/mbmd v0.0.0-20260131091050-86c2d25b6103 h1:I91QVys5UwYJ8G7VVQDeXN2/BPZqEeqCp7NY65BmGTU=
github.com/volkszaehler/mbmd v0.0.0-20260131091050-86c2d25b6103/go.mod h1:ZkD3hHsxHxdD2KbqQrMXQd1veeFfggok+breXT+TqWQ=
github.com/warthog618/go-gpiocdev v0.9.1 h1:pwHPaqjJfhCipIQl78V+O3l9OKHivdRDdmgXYbmhuCI=
github.com/warthog618/go-gpiocdev v0.9.1/go.mod h1:dN3e3t/S2aSNC+hgigGE/dBW8jE1ONk9bDSEYfoPyl8=
github.com/warthog618/go-gpiosim v0.1.1 h1:MRAEv+T+itmw+3GeIGpQJBfanUVyg0l3JCTwHtwdre4=
github.com/warthog618/go-gpiosim v0.1.1/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU=
github.com/woodsbury/decimal128 v1.4.0 h1:xJATj7lLu4f2oObouMt2tgGiElE5gO6mSWUjQsBgUlc=
github.com/woodsbury/decimal128 v1.4.0/go.mod h1:BP46FUrVjVhdTbKT+XuQh2xfQaGki9LMIRJSFuh6THU=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=

View file

@ -8,7 +8,7 @@ import (
"sync"
"github.com/evcc-io/evcc/util"
"github.com/stianeikeland/go-rpio/v4"
"github.com/warthog618/go-gpiocdev"
)
func init() {
@ -16,44 +16,44 @@ func init() {
}
type gpio struct {
mu sync.Mutex
typ GpioType
pin rpio.Pin
mu sync.Mutex
typ GpioType
line *gpiocdev.Line
}
// NewGpioPluginFromConfig creates a GPIO provider
func NewGpioPluginFromConfig(ctx context.Context, other map[string]any) (Plugin, error) {
var cc struct {
cc := struct {
Function GpioType
Pin int
Chip string
}{
Chip: "gpiochip0",
}
if err := util.DecodeOther(other, &cc); err != nil {
return nil, err
}
p := &gpio{
typ: cc.Function,
pin: rpio.Pin(cc.Pin),
}
// initialize GPIO and set pins to input
if err := rpio.Open(); err != nil {
return nil, fmt.Errorf("failed to open GPIO: %w", err)
}
defer rpio.Close()
var opts []gpiocdev.LineReqOption
switch cc.Function {
case GpioTypeRead:
p.pin.Input()
p.pin.PullUp()
opts = append(opts, gpiocdev.AsInput, gpiocdev.WithPullUp)
case GpioTypeWrite:
p.pin.Output()
opts = append(opts, gpiocdev.AsOutput(0))
default:
return nil, fmt.Errorf("invalid type: %s", cc.Function)
}
return p, nil
line, err := gpiocdev.RequestLine(cc.Chip, cc.Pin, opts...)
if err != nil {
return nil, fmt.Errorf("failed to open GPIO: %w", err)
}
return &gpio{
typ: cc.Function,
line: line,
}, nil
}
var _ BoolGetter = (*gpio)(nil)
@ -68,12 +68,12 @@ func (p *gpio) BoolGetter() (func() (bool, error), error) {
p.mu.Lock()
defer p.mu.Unlock()
if err := rpio.Open(); err != nil {
return false, fmt.Errorf("failed to open GPIO: %w", err)
val, err := p.line.Value()
if err != nil {
return false, fmt.Errorf("failed to read GPIO: %w", err)
}
defer rpio.Close()
return p.pin.Read() != rpio.Low, nil
return val != 0, nil
}, nil
}
@ -89,13 +89,14 @@ func (p *gpio) BoolSetter(_ string) (func(bool) error, error) {
p.mu.Lock()
defer p.mu.Unlock()
if err := rpio.Open(); err != nil {
return fmt.Errorf("failed to open GPIO: %w", err)
val := 0
if b {
val = 1
}
defer rpio.Close()
val := map[bool]rpio.State{false: rpio.Low, true: rpio.High}[b]
p.pin.Write(val)
if err := p.line.SetValue(val); err != nil {
return fmt.Errorf("failed to write GPIO: %w", err)
}
return nil
}, nil