gobot.io/x/gobot/v2@v2.1.0/platforms/chip/chip_adaptor.go (about) 1 package chip 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "path/filepath" 7 "strconv" 8 "strings" 9 "sync" 10 11 multierror "github.com/hashicorp/go-multierror" 12 "gobot.io/x/gobot/v2" 13 "gobot.io/x/gobot/v2/platforms/adaptors" 14 "gobot.io/x/gobot/v2/system" 15 ) 16 17 const defaultI2cBusNumber = 1 18 19 // Valids pins are the XIO-P0 through XIO-P7 pins from the 20 // extender (pins 13-20 on header 14), as well as the SoC pins 21 // aka all the other pins. 22 type sysfsPin struct { 23 pin int 24 pwmPin int 25 } 26 27 // Adaptor represents a Gobot Adaptor for a C.H.I.P. 28 type Adaptor struct { 29 name string 30 sys *system.Accesser 31 mutex sync.Mutex 32 pinmap map[string]sysfsPin 33 *adaptors.DigitalPinsAdaptor 34 *adaptors.PWMPinsAdaptor 35 *adaptors.I2cBusAdaptor 36 } 37 38 // NewAdaptor creates a C.H.I.P. Adaptor 39 // 40 // Optional parameters: 41 // 42 // adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs 43 // adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.# 44 func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor { 45 sys := system.NewAccesser() 46 c := &Adaptor{ 47 name: gobot.DefaultName("CHIP"), 48 sys: sys, 49 } 50 51 c.pinmap = chipPins 52 baseAddr, _ := getXIOBase() 53 for i := 0; i < 8; i++ { 54 pin := fmt.Sprintf("XIO-P%d", i) 55 c.pinmap[pin] = sysfsPin{pin: baseAddr + i, pwmPin: -1} 56 } 57 58 c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...) 59 c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin) 60 c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber) 61 return c 62 } 63 64 // NewProAdaptor creates a C.H.I.P. Pro Adaptor 65 func NewProAdaptor() *Adaptor { 66 c := NewAdaptor() 67 c.name = gobot.DefaultName("CHIP Pro") 68 c.pinmap = chipProPins 69 return c 70 } 71 72 // Name returns the name of the Adaptor 73 func (c *Adaptor) Name() string { return c.name } 74 75 // SetName sets the name of the Adaptor 76 func (c *Adaptor) SetName(n string) { c.name = n } 77 78 // Connect create new connection to board and pins. 79 func (c *Adaptor) Connect() error { 80 c.mutex.Lock() 81 defer c.mutex.Unlock() 82 83 if err := c.I2cBusAdaptor.Connect(); err != nil { 84 return err 85 } 86 87 if err := c.PWMPinsAdaptor.Connect(); err != nil { 88 return err 89 } 90 return c.DigitalPinsAdaptor.Connect() 91 } 92 93 // Finalize closes connection to board and pins 94 func (c *Adaptor) Finalize() error { 95 c.mutex.Lock() 96 defer c.mutex.Unlock() 97 98 err := c.DigitalPinsAdaptor.Finalize() 99 100 if e := c.PWMPinsAdaptor.Finalize(); e != nil { 101 err = multierror.Append(err, e) 102 } 103 104 if e := c.I2cBusAdaptor.Finalize(); e != nil { 105 err = multierror.Append(err, e) 106 } 107 108 return err 109 } 110 111 func getXIOBase() (baseAddr int, err error) { 112 // Default to original base from 4.3 kernel 113 baseAddr = 408 114 const expanderID = "pcf8574a" 115 116 labels, err := filepath.Glob("/sys/class/gpio/*/label") 117 if err != nil { 118 return 119 } 120 121 for _, labelPath := range labels { 122 label, err := ioutil.ReadFile(labelPath) 123 if err != nil { 124 return baseAddr, err 125 } 126 if strings.HasPrefix(string(label), expanderID) { 127 expanderPath, _ := filepath.Split(labelPath) 128 basePath := filepath.Join(expanderPath, "base") 129 base, err := ioutil.ReadFile(basePath) 130 if err != nil { 131 return baseAddr, err 132 } 133 baseAddr, _ = strconv.Atoi(strings.TrimSpace(string(base))) 134 break 135 } 136 } 137 138 return baseAddr, nil 139 } 140 141 func (c *Adaptor) validateI2cBusNumber(busNr int) error { 142 // Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. 143 if (busNr < 0) || (busNr > 2) { 144 return fmt.Errorf("Bus number %d out of range", busNr) 145 } 146 return nil 147 } 148 149 func (c *Adaptor) translateDigitalPin(id string) (string, int, error) { 150 if val, ok := c.pinmap[id]; ok { 151 return "", val.pin, nil 152 } 153 return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) 154 } 155 156 func (c *Adaptor) translatePWMPin(id string) (string, int, error) { 157 sysPin, ok := c.pinmap[id] 158 if !ok { 159 return "", -1, fmt.Errorf("'%s' is not a valid id for a pin", id) 160 } 161 if sysPin.pwmPin == -1 { 162 return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) 163 } 164 return "/sys/class/pwm/pwmchip0", sysPin.pwmPin, nil 165 }