gobot.io/x/gobot/v2@v2.1.0/platforms/adaptors/digitalpinsadaptor.go (about) 1 package adaptors 2 3 import ( 4 "fmt" 5 "sync" 6 "time" 7 8 "github.com/hashicorp/go-multierror" 9 "gobot.io/x/gobot/v2" 10 "gobot.io/x/gobot/v2/system" 11 ) 12 13 type digitalPinTranslator func(pin string) (chip string, line int, err error) 14 type digitalPinInitializer func(gobot.DigitalPinner) error 15 16 type digitalPinsOptioner interface { 17 setDigitalPinInitializer(digitalPinInitializer) 18 setDigitalPinsForSystemGpiod() 19 setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin string) 20 prepareDigitalPinsActiveLow(pin string, otherPins ...string) 21 prepareDigitalPinsPullDown(pin string, otherPins ...string) 22 prepareDigitalPinsPullUp(pin string, otherPins ...string) 23 prepareDigitalPinsOpenDrain(pin string, otherPins ...string) 24 prepareDigitalPinsOpenSource(pin string, otherPins ...string) 25 prepareDigitalPinDebounce(pin string, period time.Duration) 26 prepareDigitalPinEventOnFallingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, 27 detectedEdge string, seqno uint32, lseqno uint32)) 28 prepareDigitalPinEventOnRisingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, 29 detectedEdge string, seqno uint32, lseqno uint32)) 30 prepareDigitalPinEventOnBothEdges(pin string, handler func(lineOffset int, timestamp time.Duration, 31 detectedEdge string, seqno uint32, lseqno uint32)) 32 } 33 34 // DigitalPinsAdaptor is a adaptor for digital pins, normally used for composition in platforms. 35 type DigitalPinsAdaptor struct { 36 sys *system.Accesser 37 translate digitalPinTranslator 38 initialize digitalPinInitializer 39 pins map[string]gobot.DigitalPinner 40 pinOptions map[string][]func(gobot.DigitalPinOptioner) bool 41 mutex sync.Mutex 42 } 43 44 // NewDigitalPinsAdaptor provides the access to digital pins of the board. It supports sysfs and gpiod system drivers. 45 // This is decided by the given accesser. The translator is used to adapt the pin header naming, which is given by user, 46 // to the internal file name or chip/line nomenclature. This varies by each platform. If for some reasons the default 47 // initializer is not suitable, it can be given by the option "WithDigitalPinInitializer()". This is especially needed, 48 // if some values needs to be adjusted after the pin was created but before the pin is exported. 49 func NewDigitalPinsAdaptor(sys *system.Accesser, t digitalPinTranslator, options ...func(Optioner)) *DigitalPinsAdaptor { 50 a := &DigitalPinsAdaptor{ 51 sys: sys, 52 translate: t, 53 initialize: func(pin gobot.DigitalPinner) error { return pin.Export() }, 54 } 55 for _, option := range options { 56 option(a) 57 } 58 return a 59 } 60 61 // WithDigitalPinInitializer can be used to substitute the default initializer. 62 func WithDigitalPinInitializer(pc digitalPinInitializer) func(Optioner) { 63 return func(o Optioner) { 64 a, ok := o.(digitalPinsOptioner) 65 if ok { 66 a.setDigitalPinInitializer(pc) 67 } 68 } 69 } 70 71 // WithGpiodAccess can be used to change the default sysfs implementation to the character device Kernel ABI. 72 // The access is provided by the gpiod package. 73 func WithGpiodAccess() func(Optioner) { 74 return func(o Optioner) { 75 a, ok := o.(digitalPinsOptioner) 76 if ok { 77 a.setDigitalPinsForSystemGpiod() 78 } 79 } 80 } 81 82 // WithSpiGpioAccess can be used to switch the default SPI implementation to GPIO usage. 83 func WithSpiGpioAccess(sclkPin, nssPin, mosiPin, misoPin string) func(Optioner) { 84 return func(o Optioner) { 85 a, ok := o.(digitalPinsOptioner) 86 if ok { 87 a.setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin) 88 } 89 } 90 } 91 92 // WithGpiosActiveLow prepares the given pins for inverse reaction on next initialize. 93 // This is working for inputs and outputs. 94 func WithGpiosActiveLow(pin string, otherPins ...string) func(Optioner) { 95 return func(o Optioner) { 96 a, ok := o.(digitalPinsOptioner) 97 if ok { 98 a.prepareDigitalPinsActiveLow(pin, otherPins...) 99 } 100 } 101 } 102 103 // WithGpiosPullDown prepares the given pins to be pulled down (high impedance to GND) on next initialize. 104 // This is working for inputs and outputs since Kernel 5.5, but will be ignored with sysfs ABI. 105 func WithGpiosPullDown(pin string, otherPins ...string) func(Optioner) { 106 return func(o Optioner) { 107 a, ok := o.(digitalPinsOptioner) 108 if ok { 109 a.prepareDigitalPinsPullDown(pin, otherPins...) 110 } 111 } 112 } 113 114 // WithGpiosPullUp prepares the given pins to be pulled up (high impedance to VDD) on next initialize. 115 // This is working for inputs and outputs since Kernel 5.5, but will be ignored with sysfs ABI. 116 func WithGpiosPullUp(pin string, otherPins ...string) func(Optioner) { 117 return func(o Optioner) { 118 a, ok := o.(digitalPinsOptioner) 119 if ok { 120 a.prepareDigitalPinsPullUp(pin, otherPins...) 121 } 122 } 123 } 124 125 // WithGpiosOpenDrain prepares the given output pins to be driven with open drain/collector on next initialize. 126 // This will be ignored for inputs or with sysfs ABI. 127 func WithGpiosOpenDrain(pin string, otherPins ...string) func(Optioner) { 128 return func(o Optioner) { 129 a, ok := o.(digitalPinsOptioner) 130 if ok { 131 a.prepareDigitalPinsOpenDrain(pin, otherPins...) 132 } 133 } 134 } 135 136 // WithGpiosOpenSource prepares the given output pins to be driven with open source/emitter on next initialize. 137 // This will be ignored for inputs or with sysfs ABI. 138 func WithGpiosOpenSource(pin string, otherPins ...string) func(Optioner) { 139 return func(o Optioner) { 140 a, ok := o.(digitalPinsOptioner) 141 if ok { 142 a.prepareDigitalPinsOpenSource(pin, otherPins...) 143 } 144 } 145 } 146 147 // WithGpioDebounce prepares the given input pin to be debounced on next initialize. 148 // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. 149 func WithGpioDebounce(pin string, period time.Duration) func(Optioner) { 150 return func(o Optioner) { 151 a, ok := o.(digitalPinsOptioner) 152 if ok { 153 a.prepareDigitalPinDebounce(pin, period) 154 } 155 } 156 } 157 158 // WithGpioEventOnFallingEdge prepares the given input pin to be generate an event on falling edge. 159 // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. 160 func WithGpioEventOnFallingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string, 161 seqno uint32, lseqno uint32)) func(Optioner) { 162 return func(o Optioner) { 163 a, ok := o.(digitalPinsOptioner) 164 if ok { 165 a.prepareDigitalPinEventOnFallingEdge(pin, handler) 166 } 167 } 168 } 169 170 // WithGpioEventOnRisingEdge prepares the given input pin to be generate an event on rising edge. 171 // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. 172 func WithGpioEventOnRisingEdge(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string, 173 seqno uint32, lseqno uint32)) func(Optioner) { 174 return func(o Optioner) { 175 a, ok := o.(digitalPinsOptioner) 176 if ok { 177 a.prepareDigitalPinEventOnRisingEdge(pin, handler) 178 } 179 } 180 } 181 182 // WithGpioEventOnBothEdges prepares the given input pin to be generate an event on rising and falling edges. 183 // This is working for inputs since Kernel 5.10, but will be ignored for outputs or with sysfs ABI. 184 func WithGpioEventOnBothEdges(pin string, handler func(lineOffset int, timestamp time.Duration, detectedEdge string, 185 seqno uint32, lseqno uint32)) func(Optioner) { 186 return func(o Optioner) { 187 a, ok := o.(digitalPinsOptioner) 188 if ok { 189 a.prepareDigitalPinEventOnBothEdges(pin, handler) 190 } 191 } 192 } 193 194 // Connect prepare new connection to digital pins. 195 func (a *DigitalPinsAdaptor) Connect() error { 196 a.mutex.Lock() 197 defer a.mutex.Unlock() 198 199 a.pins = make(map[string]gobot.DigitalPinner) 200 return nil 201 } 202 203 // Finalize closes connection to digital pins 204 func (a *DigitalPinsAdaptor) Finalize() (err error) { 205 a.mutex.Lock() 206 defer a.mutex.Unlock() 207 208 for _, pin := range a.pins { 209 if pin != nil { 210 if e := pin.Unexport(); e != nil { 211 err = multierror.Append(err, e) 212 } 213 } 214 } 215 a.pins = nil 216 a.pinOptions = nil 217 return 218 } 219 220 // DigitalPin returns a digital pin. If the pin is initially acquired, it is an input. 221 // Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time. 222 func (a *DigitalPinsAdaptor) DigitalPin(id string) (gobot.DigitalPinner, error) { 223 a.mutex.Lock() 224 defer a.mutex.Unlock() 225 226 return a.digitalPin(id) 227 } 228 229 // DigitalRead reads digital value from pin 230 func (a *DigitalPinsAdaptor) DigitalRead(id string) (int, error) { 231 a.mutex.Lock() 232 defer a.mutex.Unlock() 233 234 pin, err := a.digitalPin(id, system.WithPinDirectionInput()) 235 if err != nil { 236 return 0, err 237 } 238 return pin.Read() 239 } 240 241 // DigitalWrite writes digital value to specified pin 242 func (a *DigitalPinsAdaptor) DigitalWrite(id string, val byte) error { 243 a.mutex.Lock() 244 defer a.mutex.Unlock() 245 246 pin, err := a.digitalPin(id, system.WithPinDirectionOutput(int(val))) 247 if err != nil { 248 return err 249 } 250 return pin.Write(int(val)) 251 } 252 253 func (a *DigitalPinsAdaptor) setDigitalPinInitializer(pinInit digitalPinInitializer) { 254 a.initialize = pinInit 255 } 256 257 func (a *DigitalPinsAdaptor) setDigitalPinsForSystemGpiod() { 258 system.WithDigitalPinGpiodAccess()(a.sys) 259 } 260 261 func (a *DigitalPinsAdaptor) setDigitalPinsForSystemSpi(sclkPin, nssPin, mosiPin, misoPin string) { 262 system.WithSpiGpioAccess(a, sclkPin, nssPin, mosiPin, misoPin)(a.sys) 263 } 264 265 func (a *DigitalPinsAdaptor) prepareDigitalPinsActiveLow(id string, otherIDs ...string) { 266 ids := []string{id} 267 ids = append(ids, otherIDs...) 268 269 if a.pinOptions == nil { 270 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 271 } 272 273 for _, i := range ids { 274 a.pinOptions[i] = append(a.pinOptions[i], system.WithPinActiveLow()) 275 } 276 } 277 278 func (a *DigitalPinsAdaptor) prepareDigitalPinsPullDown(id string, otherIDs ...string) { 279 ids := []string{id} 280 ids = append(ids, otherIDs...) 281 282 if a.pinOptions == nil { 283 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 284 } 285 286 for _, i := range ids { 287 a.pinOptions[i] = append(a.pinOptions[i], system.WithPinPullDown()) 288 } 289 } 290 291 func (a *DigitalPinsAdaptor) prepareDigitalPinsPullUp(id string, otherIDs ...string) { 292 ids := []string{id} 293 ids = append(ids, otherIDs...) 294 295 if a.pinOptions == nil { 296 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 297 } 298 299 for _, i := range ids { 300 a.pinOptions[i] = append(a.pinOptions[i], system.WithPinPullUp()) 301 } 302 } 303 304 func (a *DigitalPinsAdaptor) prepareDigitalPinsOpenDrain(id string, otherIDs ...string) { 305 ids := []string{id} 306 ids = append(ids, otherIDs...) 307 308 if a.pinOptions == nil { 309 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 310 } 311 312 for _, i := range ids { 313 a.pinOptions[i] = append(a.pinOptions[i], system.WithPinOpenDrain()) 314 } 315 } 316 317 func (a *DigitalPinsAdaptor) prepareDigitalPinsOpenSource(id string, otherIDs ...string) { 318 ids := []string{id} 319 ids = append(ids, otherIDs...) 320 321 if a.pinOptions == nil { 322 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 323 } 324 325 for _, i := range ids { 326 a.pinOptions[i] = append(a.pinOptions[i], system.WithPinOpenSource()) 327 } 328 } 329 330 func (a *DigitalPinsAdaptor) prepareDigitalPinDebounce(id string, period time.Duration) { 331 if a.pinOptions == nil { 332 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 333 } 334 335 a.pinOptions[id] = append(a.pinOptions[id], system.WithPinDebounce(period)) 336 } 337 338 func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnFallingEdge(id string, handler func(int, time.Duration, string, 339 uint32, uint32)) { 340 if a.pinOptions == nil { 341 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 342 } 343 344 a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnFallingEdge(handler)) 345 } 346 347 func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnRisingEdge(id string, handler func(int, time.Duration, string, 348 uint32, uint32)) { 349 if a.pinOptions == nil { 350 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 351 } 352 353 a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnRisingEdge(handler)) 354 } 355 356 func (a *DigitalPinsAdaptor) prepareDigitalPinEventOnBothEdges(id string, handler func(int, time.Duration, string, 357 uint32, uint32)) { 358 if a.pinOptions == nil { 359 a.pinOptions = make(map[string][]func(gobot.DigitalPinOptioner) bool) 360 } 361 362 a.pinOptions[id] = append(a.pinOptions[id], system.WithPinEventOnBothEdges(handler)) 363 } 364 365 func (a *DigitalPinsAdaptor) digitalPin(id string, opts ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) { 366 if a.pins == nil { 367 return nil, fmt.Errorf("not connected for pin %s", id) 368 } 369 370 o := append(a.pinOptions[id], opts...) 371 pin := a.pins[id] 372 373 if pin == nil { 374 chip, line, err := a.translate(id) 375 if err != nil { 376 return nil, err 377 } 378 pin = a.sys.NewDigitalPin(chip, line, o...) 379 if err = a.initialize(pin); err != nil { 380 return nil, err 381 } 382 a.pins[id] = pin 383 } else { 384 if err := pin.ApplyOptions(o...); err != nil { 385 return nil, err 386 } 387 } 388 389 return pin, nil 390 }