lightshowpi/light/rpi/manager.go

122 lines
2.6 KiB
Go

package rpi
import (
"errors"
"fmt"
"sync"
"github.com/warthog618/gpiod"
)
const (
// DefaultChipName to try to use if one is not provided
DefaultChipName = "gpiochip0"
)
var (
// ErrLineAlreadyRequested denotes that the requested line is not available because it has already been requested and not Closed
ErrLineAlreadyRequested = errors.New("line has already been requested")
// ErrLineNotRequested denotes that the given line cannot be acted on because it was not previously requested
ErrLineNotRequested = errors.New("line was not previously requested")
)
// ManagerConfig configuration for a Manager
type ManagerConfig struct {
ChipName string
}
// GetChipName from the config
func (mc *ManagerConfig) GetChipName() string {
if mc.ChipName == "" {
return DefaultChipName
}
return mc.ChipName
}
// Manager for overarching raspberry pi GPIO management
type Manager struct {
config *ManagerConfig
chip *gpiod.Chip
lines map[int]*gpiod.Line
linesLock sync.Mutex
}
// NewManager creates a new Manager instance for the given config
func NewManager(config ManagerConfig) (*Manager, error) {
chip, err := gpiod.NewChip(config.GetChipName())
if err != nil {
return nil, fmt.Errorf("failed to open GPIO chip %s: %w", config.GetChipName(), err)
}
return &Manager{
config: &config,
chip: chip,
lines: make(map[int]*gpiod.Line),
}, nil
}
// GetBasicLight for the given pin if available
func (m *Manager) GetBasicLight(pin int) (*BasicLight, error) {
line, err := m.requestLine(pin)
if err != nil {
return nil, err
}
return &BasicLight{
pin: pin,
line: line,
manager: m,
}, nil
}
func (m *Manager) requestLine(pin int) (*gpiod.Line, error) {
m.linesLock.Lock()
defer m.linesLock.Unlock()
_, exists := m.lines[pin]
if exists {
return nil, fmt.Errorf("%w pin %d", ErrLineAlreadyRequested, pin)
}
line, err := m.chip.RequestLine(pin, gpiod.AsOutput(0))
if err != nil {
return nil, fmt.Errorf("failed to request line for pin %d: %w", pin, err)
}
m.lines[pin] = line
return line, nil
}
func (m *Manager) closeLine(pin int) error {
m.linesLock.Lock()
defer m.linesLock.Unlock()
return m.closeLineHelper(pin)
}
func (m *Manager) closeLineHelper(pin int) error {
line := m.lines[pin]
if line == nil {
return fmt.Errorf("%w pin %d", ErrLineNotRequested, pin)
}
delete(m.lines, pin)
return line.Close()
}
// Close this Manager and all of its associated lines
func (m *Manager) Close() error {
m.linesLock.Lock()
defer m.linesLock.Unlock()
for pin := range m.lines {
m.closeLineHelper(pin)
}
return m.chip.Close()
}