gobot.io/x/gobot/v2@v2.1.0/platforms/nanopi/nanopi_adaptor.go (about) 1 package nanopi 2 3 import ( 4 "fmt" 5 "sync" 6 7 multierror "github.com/hashicorp/go-multierror" 8 "gobot.io/x/gobot/v2" 9 "gobot.io/x/gobot/v2/platforms/adaptors" 10 "gobot.io/x/gobot/v2/system" 11 ) 12 13 const ( 14 debug = false 15 16 pwmInvertedIdentifier = "inversed" 17 18 defaultI2cBusNumber = 0 19 20 defaultSpiBusNumber = 0 21 defaultSpiChipNumber = 0 22 defaultSpiMode = 0 23 defaultSpiBitsNumber = 8 24 defaultSpiMaxSpeed = 500000 25 ) 26 27 type cdevPin struct { 28 chip uint8 29 line uint8 30 } 31 32 type gpioPinDefinition struct { 33 sysfs int 34 cdev cdevPin 35 } 36 37 type pwmPinDefinition struct { 38 channel int 39 dir string 40 dirRegexp string 41 } 42 43 // Adaptor represents a Gobot Adaptor for the FriendlyARM NanoPi Boards 44 type Adaptor struct { 45 name string 46 sys *system.Accesser 47 gpioPinMap map[string]gpioPinDefinition 48 pwmPinMap map[string]pwmPinDefinition 49 mutex sync.Mutex 50 *adaptors.DigitalPinsAdaptor 51 *adaptors.PWMPinsAdaptor 52 *adaptors.I2cBusAdaptor 53 *adaptors.SpiBusAdaptor 54 } 55 56 // NewNeoAdaptor creates a board adaptor for NanoPi NEO 57 // 58 // Optional parameters: 59 // 60 // adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) 61 // adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.# 62 // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior 63 // adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor 64 // adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior 65 // adaptors.WithGpioDebounce(pin, period): sets the input debouncer 66 // adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection 67 func NewNeoAdaptor(opts ...func(adaptors.Optioner)) *Adaptor { 68 sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) 69 c := &Adaptor{ 70 name: gobot.DefaultName("NanoPi NEO Board"), 71 sys: sys, 72 gpioPinMap: neoGpioPins, 73 pwmPinMap: neoPwmPins, 74 } 75 c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...) 76 c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin, 77 adaptors.WithPolarityInvertedIdentifier(pwmInvertedIdentifier)) 78 c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber) 79 c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, 80 defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) 81 return c 82 } 83 84 // Name returns the name of the Adaptor 85 func (c *Adaptor) Name() string { return c.name } 86 87 // SetName sets the name of the Adaptor 88 func (c *Adaptor) SetName(n string) { c.name = n } 89 90 // Connect create new connection to board and pins. 91 func (c *Adaptor) Connect() error { 92 c.mutex.Lock() 93 defer c.mutex.Unlock() 94 95 if err := c.SpiBusAdaptor.Connect(); err != nil { 96 return err 97 } 98 99 if err := c.I2cBusAdaptor.Connect(); err != nil { 100 return err 101 } 102 103 if err := c.PWMPinsAdaptor.Connect(); err != nil { 104 return err 105 } 106 return c.DigitalPinsAdaptor.Connect() 107 } 108 109 // Finalize closes connection to board, pins and bus 110 func (c *Adaptor) Finalize() error { 111 c.mutex.Lock() 112 defer c.mutex.Unlock() 113 114 err := c.DigitalPinsAdaptor.Finalize() 115 116 if e := c.PWMPinsAdaptor.Finalize(); e != nil { 117 err = multierror.Append(err, e) 118 } 119 120 if e := c.I2cBusAdaptor.Finalize(); e != nil { 121 err = multierror.Append(err, e) 122 } 123 124 if e := c.SpiBusAdaptor.Finalize(); e != nil { 125 err = multierror.Append(err, e) 126 } 127 return err 128 } 129 130 func (c *Adaptor) validateSpiBusNumber(busNr int) error { 131 // Valid bus numbers are [0] which corresponds to /dev/spidev0.x 132 // x is the chip number <255 133 if busNr != 0 { 134 return fmt.Errorf("Bus number %d out of range", busNr) 135 } 136 return nil 137 } 138 139 func (c *Adaptor) validateI2cBusNumber(busNr int) error { 140 // Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. 141 if (busNr < 0) || (busNr > 2) { 142 return fmt.Errorf("Bus number %d out of range", busNr) 143 } 144 return nil 145 } 146 147 func (c *Adaptor) translateDigitalPin(id string) (string, int, error) { 148 pindef, ok := c.gpioPinMap[id] 149 if !ok { 150 return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) 151 } 152 if c.sys.IsSysfsDigitalPinAccess() { 153 return "", pindef.sysfs, nil 154 } 155 chip := fmt.Sprintf("gpiochip%d", pindef.cdev.chip) 156 line := int(pindef.cdev.line) 157 return chip, line, nil 158 } 159 160 func (c *Adaptor) translatePWMPin(id string) (string, int, error) { 161 pinInfo, ok := c.pwmPinMap[id] 162 if !ok { 163 return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) 164 } 165 path, err := pinInfo.findPWMDir(c.sys) 166 if err != nil { 167 return "", -1, err 168 } 169 return path, pinInfo.channel, nil 170 } 171 172 func (p pwmPinDefinition) findPWMDir(sys *system.Accesser) (dir string, err error) { 173 items, _ := sys.Find(p.dir, p.dirRegexp) 174 if len(items) == 0 { 175 return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'. See README.md for activation", 176 p.dirRegexp, p.dir) 177 } 178 179 dir = items[0] 180 info, err := sys.Stat(dir) 181 if err != nil { 182 return "", fmt.Errorf("Error (%v) on access '%s'", err, dir) 183 } 184 if !info.IsDir() { 185 return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir) 186 } 187 188 return 189 }