Gpio plugin: Raspberry Pi 5 compatibility (#27815)
This commit is contained in:
parent
a0dccea9a3
commit
6e12bd9d59
4 changed files with 71 additions and 60 deletions
|
|
@ -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
2
go.mod
|
|
@ -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
6
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue