mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-05-01 13:48:57 +08:00
225 lines
6.3 KiB
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])
|
|
}
|