1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-05-01 13:48:57 +08:00
hybridgroup.gobot/system/digitalpin_gpiod.go

225 lines
6.3 KiB
Go

package system
import (
"fmt"
"strconv"
"strings"
"github.com/warthog618/gpiod"
"gobot.io/x/gobot"
)
const systemGpiodDebug = true
type cdevLine interface {
SetValue(value int) error
Value() (int, error)
Close() error
}
type digitalPinGpiod struct {
chipName string
pin int
*digitalPinConfig
line cdevLine
}
var used = map[bool]string{true: "used", false: "unused"}
var activeLow = map[bool]string{true: "low", false: "high"}
var debounced = map[bool]string{true: "debounced", false: "not debounced"}
var direction = map[gpiod.LineDirection]string{gpiod.LineDirectionUnknown: "unknown direction",
gpiod.LineDirectionInput: "input", gpiod.LineDirectionOutput: "output"}
var drive = map[gpiod.LineDrive]string{gpiod.LineDrivePushPull: "push-pull", gpiod.LineDriveOpenDrain: "open-drain",
gpiod.LineDriveOpenSource: "open-source"}
var bias = map[gpiod.LineBias]string{gpiod.LineBiasUnknown: "unknown", gpiod.LineBiasDisabled: "disabled",
gpiod.LineBiasPullUp: "pull-up", gpiod.LineBiasPullDown: "pull-down"}
var edgeDetect = map[gpiod.LineEdge]string{gpiod.LineEdgeNone: "no", gpiod.LineEdgeRising: "rising",
gpiod.LineEdgeFalling: "falling", gpiod.LineEdgeBoth: "both"}
var eventClock = map[gpiod.LineEventClock]string{gpiod.LineEventClockMonotonic: "monotonic",
gpiod.LineEventClockRealtime: "realtime"}
// newDigitalPinGpiod returns a digital pin given the pin number, with the label "gobotio" followed by the pin number.
// The pin label can be modified optionally. The pin is handled by the character device Kernel ABI.
func newDigitalPinGpiod(chipName string, pin int, options ...func(gobot.DigitalPinOptioner) bool) *digitalPinGpiod {
if chipName == "" {
chipName = "gpiochip0"
}
cfg := newDigitalPinConfig("gobotio"+strconv.Itoa(int(pin)), options...)
d := &digitalPinGpiod{
chipName: chipName,
pin: pin,
digitalPinConfig: cfg,
}
return d
}
// ApplyOptions apply all given options to the pin immediately. Implements interface gobot.DigitalPinOptionApplier.
func (d *digitalPinGpiod) ApplyOptions(options ...func(gobot.DigitalPinOptioner) bool) error {
anyChange := false
for _, option := range options {
anyChange = anyChange || option(d)
}
if anyChange {
return d.reconfigure(false)
}
return nil
}
// DirectionBehavior gets the direction behavior when the pin is used the next time. This means its possibly not in
// this direction type at the moment. Implements the interface gobot.DigitalPinValuer, but should be rarely used.
func (d *digitalPinGpiod) DirectionBehavior() string {
return d.direction
}
// Export sets the pin as used by this driver. Implements the interface gobot.DigitalPinner.
func (d *digitalPinGpiod) Export() error {
err := d.reconfigure(false)
if err != nil {
return fmt.Errorf("gpiod.Export(): %v", err)
}
return nil
}
// Unexport releases the pin as input. Implements the interface gobot.DigitalPinner.
func (d *digitalPinGpiod) Unexport() error {
var errs []string
if d.line != nil {
if err := d.reconfigure(true); err != nil {
errs = append(errs, err.Error())
}
if err := d.line.Close(); err != nil {
err = fmt.Errorf("gpiod.Unexport()-line.Close(): %v", err)
errs = append(errs, err.Error())
}
}
if len(errs) == 0 {
return nil
}
return fmt.Errorf(strings.Join(errs, ","))
}
// Write writes the given value to the character device. Implements the interface gobot.DigitalPinner.
func (d *digitalPinGpiod) Write(val int) error {
if val < 0 {
val = 0
}
if val > 1 {
val = 1
}
err := d.line.SetValue(val)
if err != nil {
return fmt.Errorf("gpiod.Write(): %v", err)
}
return nil
}
// Read reads the given value from character device. Implements the interface gobot.DigitalPinner.
func (d *digitalPinGpiod) Read() (int, error) {
val, err := d.line.Value()
if err != nil {
return 0, fmt.Errorf("gpiod.Read(): %v", err)
}
return val, err
}
// ListLines is used for development purposes.
func (d *digitalPinGpiod) ListLines() error {
c, err := gpiod.NewChip(d.chipName, gpiod.WithConsumer(d.label))
if err != nil {
return err
}
for i := 0; i < c.Lines(); i++ {
li, err := c.LineInfo(i)
if err != nil {
return err
}
fmt.Println(fmtLine(li))
}
return nil
}
// List is used for development purposes.
func (d *digitalPinGpiod) List() error {
c, err := gpiod.NewChip(d.chipName)
if err != nil {
return err
}
defer c.Close()
l, err := c.RequestLine(d.pin)
if err != nil && l != nil {
l.Close()
l = nil
}
li, err := l.Info()
if err != nil {
return err
}
fmt.Println(fmtLine(li))
return nil
}
func (d *digitalPinGpiod) reconfigure(forceInput bool) error {
// cleanup old line
if d.line != nil {
d.line.Close()
}
d.line = nil
// acquire chip, temporary
// the given label is applied to all lines, which are requested on the chip
gpiodChip, err := gpiod.NewChip(d.chipName, gpiod.WithConsumer(d.label))
id := fmt.Sprintf("%s-%d", d.chipName, d.pin)
if err != nil {
return fmt.Errorf("gpiod.reconfigure(%s)-lib.NewChip(%s): %v", id, d.chipName, err)
}
defer gpiodChip.Close()
// acquire line
gpiodLine, err := gpiodChip.RequestLine(d.pin)
if err != nil {
if gpiodLine != nil {
gpiodLine.Close()
}
d.line = nil
return fmt.Errorf("gpiod.reconfigure(%s)-c.RequestLine(%d): %v", id, d.pin, err)
}
d.line = gpiodLine
// configure line
if d.direction == IN || forceInput {
if err := gpiodLine.Reconfigure(gpiod.AsInput); err != nil {
return fmt.Errorf("gpiod.reconfigure(%s)-l.Reconfigure(gpiod.AsInput): %v", id, err)
}
return nil
}
if err := gpiodLine.Reconfigure(gpiod.AsOutput(d.outInitialState)); err != nil {
return fmt.Errorf("gpiod.reconfigure(%s)-l.Reconfigure(gpiod.AsOutput(%d)): %v", id, d.outInitialState, err)
}
return nil
}
func fmtLine(li gpiod.LineInfo) string {
var consumer string
if li.Consumer != "" {
consumer = fmt.Sprintf(" by '%s'", li.Consumer)
}
return fmt.Sprintf("++ Info line %d '%s', %s%s ++\n Config: %s\n",
li.Offset, li.Name, used[li.Used], consumer, fmtLineConfig(li.Config))
}
func fmtLineConfig(cfg gpiod.LineConfig) string {
t := "active-%s, %s, %s, %s bias, %s edge detect, %s, debounce-period: %v, %s event clock"
return fmt.Sprintf(t, activeLow[cfg.ActiveLow], direction[cfg.Direction], drive[cfg.Drive], bias[cfg.Bias],
edgeDetect[cfg.EdgeDetection], debounced[cfg.Debounced], cfg.DebouncePeriod, eventClock[cfg.EventClock])
}