gobot.io/x/gobot/v2@v2.1.0/platforms/raspi/raspi_adaptor.go (about) 1 package raspi 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 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 ( 18 infoFile = "/proc/cpuinfo" 19 20 defaultSpiBusNumber = 0 21 defaultSpiChipNumber = 0 22 defaultSpiMode = 0 23 defaultSpiBitsNumber = 8 24 defaultSpiMaxSpeed = 500000 25 ) 26 27 // Adaptor is the Gobot Adaptor for the Raspberry Pi 28 type Adaptor struct { 29 name string 30 mutex sync.Mutex 31 sys *system.Accesser 32 revision string 33 pwmPins map[string]gobot.PWMPinner 34 *adaptors.DigitalPinsAdaptor 35 *adaptors.I2cBusAdaptor 36 *adaptors.SpiBusAdaptor 37 spiDefaultMaxSpeed int64 38 PiBlasterPeriod uint32 39 } 40 41 // NewAdaptor creates a Raspi Adaptor 42 // 43 // Optional parameters: 44 // 45 // adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) 46 // adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.# 47 // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior 48 // adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor 49 // adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior 50 // adaptors.WithGpioDebounce(pin, period): sets the input debouncer 51 // adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection 52 func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor { 53 sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) 54 c := &Adaptor{ 55 name: gobot.DefaultName("RaspberryPi"), 56 sys: sys, 57 PiBlasterPeriod: 10000000, 58 } 59 c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.getPinTranslatorFunction(), opts...) 60 c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, 1) 61 c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, 62 defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) 63 return c 64 } 65 66 // Name returns the Adaptor's name 67 func (c *Adaptor) Name() string { 68 c.mutex.Lock() 69 defer c.mutex.Unlock() 70 71 return c.name 72 } 73 74 // SetName sets the Adaptor's name 75 func (c *Adaptor) SetName(n string) { 76 c.mutex.Lock() 77 defer c.mutex.Unlock() 78 79 c.name = n 80 } 81 82 // Connect create new connection to board and pins. 83 func (c *Adaptor) Connect() error { 84 c.mutex.Lock() 85 defer c.mutex.Unlock() 86 87 if err := c.SpiBusAdaptor.Connect(); err != nil { 88 return err 89 } 90 91 if err := c.I2cBusAdaptor.Connect(); err != nil { 92 return err 93 } 94 95 c.pwmPins = make(map[string]gobot.PWMPinner) 96 return c.DigitalPinsAdaptor.Connect() 97 } 98 99 // Finalize closes connection to board and pins 100 func (c *Adaptor) Finalize() error { 101 c.mutex.Lock() 102 defer c.mutex.Unlock() 103 104 err := c.DigitalPinsAdaptor.Finalize() 105 106 for _, pin := range c.pwmPins { 107 if pin != nil { 108 if perr := pin.Unexport(); err != nil { 109 err = multierror.Append(err, perr) 110 } 111 } 112 } 113 c.pwmPins = nil 114 115 if e := c.I2cBusAdaptor.Finalize(); e != nil { 116 err = multierror.Append(err, e) 117 } 118 119 if e := c.SpiBusAdaptor.Finalize(); e != nil { 120 err = multierror.Append(err, e) 121 } 122 return err 123 } 124 125 // DefaultI2cBus returns the default i2c bus for this platform. 126 // This overrides the base function due to the revision dependency. 127 func (c *Adaptor) DefaultI2cBus() int { 128 rev := c.readRevision() 129 if rev == "2" || rev == "3" { 130 return 1 131 } 132 return 0 133 } 134 135 // PWMPin returns a raspi.PWMPin which provides the gobot.PWMPinner interface 136 func (c *Adaptor) PWMPin(id string) (gobot.PWMPinner, error) { 137 c.mutex.Lock() 138 defer c.mutex.Unlock() 139 140 return c.pwmPin(id) 141 } 142 143 // PwmWrite writes a PWM signal to the specified pin 144 func (c *Adaptor) PwmWrite(pin string, val byte) (err error) { 145 c.mutex.Lock() 146 defer c.mutex.Unlock() 147 148 sysPin, err := c.pwmPin(pin) 149 if err != nil { 150 return err 151 } 152 153 duty := uint32(gobot.FromScale(float64(val), 0, 255) * float64(c.PiBlasterPeriod)) 154 return sysPin.SetDutyCycle(duty) 155 } 156 157 // ServoWrite writes a servo signal to the specified pin 158 func (c *Adaptor) ServoWrite(pin string, angle byte) (err error) { 159 c.mutex.Lock() 160 defer c.mutex.Unlock() 161 162 sysPin, err := c.pwmPin(pin) 163 if err != nil { 164 return err 165 } 166 167 duty := uint32(gobot.FromScale(float64(angle), 0, 180) * float64(c.PiBlasterPeriod)) 168 return sysPin.SetDutyCycle(duty) 169 } 170 171 func (c *Adaptor) validateSpiBusNumber(busNr int) error { 172 // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. 173 // x is the chip number <255 174 if (busNr < 0) || (busNr > 1) { 175 return fmt.Errorf("Bus number %d out of range", busNr) 176 } 177 return nil 178 } 179 180 func (c *Adaptor) validateI2cBusNumber(busNr int) error { 181 // Valid bus number is [0..1] which corresponds to /dev/i2c-0 through /dev/i2c-1. 182 if (busNr < 0) || (busNr > 1) { 183 return fmt.Errorf("Bus number %d out of range", busNr) 184 } 185 return nil 186 } 187 188 func (c *Adaptor) getPinTranslatorFunction() func(string) (string, int, error) { 189 return func(pin string) (chip string, line int, err error) { 190 if val, ok := pins[pin][c.readRevision()]; ok { 191 line = val 192 } else if val, ok := pins[pin]["*"]; ok { 193 line = val 194 } else { 195 err = errors.New("Not a valid pin") 196 return 197 } 198 // TODO: Pi1 model B has only this single "gpiochip0", a change of the translator is needed, 199 // to support different chips with different revisions 200 return "gpiochip0", line, nil 201 } 202 } 203 204 func (c *Adaptor) readRevision() string { 205 if c.revision == "" { 206 c.revision = "0" 207 content, err := c.sys.ReadFile(infoFile) 208 if err != nil { 209 return c.revision 210 } 211 for _, v := range strings.Split(string(content), "\n") { 212 if strings.Contains(v, "Revision") { 213 s := strings.Split(string(v), " ") 214 version, _ := strconv.ParseInt("0x"+s[len(s)-1], 0, 64) 215 if version <= 3 { 216 c.revision = "1" 217 } else if version <= 15 { 218 c.revision = "2" 219 } else { 220 c.revision = "3" 221 } 222 } 223 } 224 } 225 226 return c.revision 227 } 228 229 func (c *Adaptor) pwmPin(id string) (gobot.PWMPinner, error) { 230 pin := c.pwmPins[id] 231 232 if pin == nil { 233 tf := c.getPinTranslatorFunction() 234 _, i, err := tf(id) 235 if err != nil { 236 return nil, err 237 } 238 pin = NewPWMPin(c.sys, "/dev/pi-blaster", strconv.Itoa(i)) 239 pin.SetPeriod(c.PiBlasterPeriod) 240 c.pwmPins[id] = pin 241 } 242 243 return pin, nil 244 }