gobot.io/x/gobot@v1.16.0/platforms/chip/chip_adaptor.go (about) 1 package chip 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "path/filepath" 8 "strconv" 9 "strings" 10 "sync" 11 12 multierror "github.com/hashicorp/go-multierror" 13 "gobot.io/x/gobot" 14 "gobot.io/x/gobot/drivers/i2c" 15 "gobot.io/x/gobot/sysfs" 16 ) 17 18 type sysfsPin struct { 19 pin int 20 pwmPin int 21 } 22 23 // Adaptor represents a Gobot Adaptor for a C.H.I.P. 24 type Adaptor struct { 25 name string 26 board string 27 pinmap map[string]sysfsPin 28 digitalPins map[int]*sysfs.DigitalPin 29 pwmPins map[int]*sysfs.PWMPin 30 i2cBuses [3]i2c.I2cDevice 31 mutex *sync.Mutex 32 } 33 34 // NewAdaptor creates a C.H.I.P. Adaptor 35 func NewAdaptor() *Adaptor { 36 c := &Adaptor{ 37 name: gobot.DefaultName("CHIP"), 38 board: "chip", 39 mutex: &sync.Mutex{}, 40 } 41 42 c.setPins() 43 return c 44 } 45 46 // NewAdaptor creates a C.H.I.P. Pro Adaptor 47 func NewProAdaptor() *Adaptor { 48 c := &Adaptor{ 49 name: gobot.DefaultName("CHIP Pro"), 50 board: "pro", 51 mutex: &sync.Mutex{}, 52 } 53 54 c.setPins() 55 return c 56 } 57 58 // Name returns the name of the Adaptor 59 func (c *Adaptor) Name() string { return c.name } 60 61 // SetName sets the name of the Adaptor 62 func (c *Adaptor) SetName(n string) { c.name = n } 63 64 // Connect initializes the board 65 func (c *Adaptor) Connect() (err error) { 66 return nil 67 } 68 69 // Finalize closes connection to board and pins 70 func (c *Adaptor) Finalize() (err error) { 71 c.mutex.Lock() 72 defer c.mutex.Unlock() 73 74 for _, pin := range c.digitalPins { 75 if pin != nil { 76 if e := pin.Unexport(); e != nil { 77 err = multierror.Append(err, e) 78 } 79 } 80 } 81 for _, pin := range c.pwmPins { 82 if pin != nil { 83 if errs := pin.Enable(false); errs != nil { 84 err = multierror.Append(err, errs) 85 } 86 if errs := pin.Unexport(); errs != nil { 87 err = multierror.Append(err, errs) 88 } 89 } 90 } 91 for _, bus := range c.i2cBuses { 92 if bus != nil { 93 if e := bus.Close(); e != nil { 94 err = multierror.Append(err, e) 95 } 96 } 97 } 98 return 99 } 100 101 // DigitalRead reads digital value from the specified pin. 102 // Valids pins are the XIO-P0 through XIO-P7 pins from the 103 // extender (pins 13-20 on header 14), as well as the SoC pins 104 // aka all the other pins. 105 func (c *Adaptor) DigitalRead(pin string) (val int, err error) { 106 sysfsPin, err := c.DigitalPin(pin, sysfs.IN) 107 if err != nil { 108 return 109 } 110 return sysfsPin.Read() 111 } 112 113 // DigitalWrite writes digital value to the specified pin. 114 // Valids pins are the XIO-P0 through XIO-P7 pins from the 115 // extender (pins 13-20 on header 14), as well as the SoC pins 116 // aka all the other pins. 117 func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) { 118 sysfsPin, err := c.DigitalPin(pin, sysfs.OUT) 119 if err != nil { 120 return err 121 } 122 return sysfsPin.Write(int(val)) 123 } 124 125 // GetConnection returns a connection to a device on a specified bus. 126 // Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. 127 func (c *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection, err error) { 128 c.mutex.Lock() 129 defer c.mutex.Unlock() 130 131 if (bus < 0) || (bus > 2) { 132 return nil, fmt.Errorf("Bus number %d out of range", bus) 133 } 134 if c.i2cBuses[bus] == nil { 135 c.i2cBuses[bus], err = sysfs.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", bus)) 136 } 137 return i2c.NewConnection(c.i2cBuses[bus], address), err 138 } 139 140 // GetDefaultBus returns the default i2c bus for this platform 141 func (c *Adaptor) GetDefaultBus() int { 142 return 1 143 } 144 145 // digitalPin returns matched digitalPin for specified values 146 func (c *Adaptor) DigitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPinner, err error) { 147 c.mutex.Lock() 148 defer c.mutex.Unlock() 149 150 i, err := c.translatePin(pin) 151 152 if err != nil { 153 return 154 } 155 156 if c.digitalPins[i] == nil { 157 c.digitalPins[i] = sysfs.NewDigitalPin(i) 158 if err = c.digitalPins[i].Export(); err != nil { 159 return 160 } 161 } 162 163 if err = c.digitalPins[i].Direction(dir); err != nil { 164 return 165 } 166 167 return c.digitalPins[i], nil 168 } 169 170 // pwmPin returns matched pwmPin for specified pin number 171 func (c *Adaptor) PWMPin(pin string) (sysfsPin sysfs.PWMPinner, err error) { 172 c.mutex.Lock() 173 defer c.mutex.Unlock() 174 175 sysPin := c.pinmap[pin] 176 if sysPin.pwmPin != -1 { 177 if c.pwmPins[sysPin.pwmPin] == nil { 178 newPin := sysfs.NewPWMPin(sysPin.pwmPin) 179 if err = newPin.Export(); err != nil { 180 return 181 } 182 // Make sure pwm is disabled when setting polarity 183 if err = newPin.Enable(false); err != nil { 184 return 185 } 186 if err = newPin.InvertPolarity(false); err != nil { 187 return 188 } 189 if err = newPin.Enable(true); err != nil { 190 return 191 } 192 if err = newPin.SetPeriod(10000000); err != nil { 193 return 194 } 195 c.pwmPins[sysPin.pwmPin] = newPin 196 } 197 198 sysfsPin = c.pwmPins[sysPin.pwmPin] 199 return 200 } 201 err = errors.New("Not a PWM pin") 202 return 203 } 204 205 // PwmWrite writes a PWM signal to the specified pin 206 func (c *Adaptor) PwmWrite(pin string, val byte) (err error) { 207 pwmPin, err := c.PWMPin(pin) 208 if err != nil { 209 return 210 } 211 period, err := pwmPin.Period() 212 if err != nil { 213 return err 214 } 215 duty := gobot.FromScale(float64(val), 0, 255.0) 216 return pwmPin.SetDutyCycle(uint32(float64(period) * duty)) 217 } 218 219 // ServoWrite writes a servo signal to the specified pin 220 func (c *Adaptor) ServoWrite(pin string, angle byte) (err error) { 221 pwmPin, err := c.PWMPin(pin) 222 if err != nil { 223 return 224 } 225 226 // 0.5 ms => -90 227 // 1.5 ms => 0 228 // 2.0 ms => 90 229 // 230 // Duty cycle is in nanos 231 const minDuty = 0.0005 * 1e9 232 const maxDuty = 0.0020 * 1e9 233 duty := uint32(gobot.ToScale(gobot.FromScale(float64(angle), 0, 180), minDuty, maxDuty)) 234 return pwmPin.SetDutyCycle(duty) 235 } 236 237 // SetBoard sets the name of the type of board 238 func (c *Adaptor) SetBoard(n string) (err error) { 239 if n == "chip" || n == "pro" { 240 c.board = n 241 c.setPins() 242 return 243 } 244 return errors.New("Invalid board type") 245 } 246 247 func (c *Adaptor) setPins() { 248 c.digitalPins = make(map[int]*sysfs.DigitalPin) 249 c.pwmPins = make(map[int]*sysfs.PWMPin) 250 251 if c.board == "pro" { 252 c.pinmap = chipProPins 253 return 254 } 255 // otherwise, original CHIP 256 c.pinmap = chipPins 257 baseAddr, _ := getXIOBase() 258 for i := 0; i < 8; i++ { 259 pin := fmt.Sprintf("XIO-P%d", i) 260 c.pinmap[pin] = sysfsPin{pin: baseAddr + i, pwmPin: -1} 261 } 262 } 263 264 func getXIOBase() (baseAddr int, err error) { 265 // Default to original base from 4.3 kernel 266 baseAddr = 408 267 const expanderID = "pcf8574a" 268 269 labels, err := filepath.Glob("/sys/class/gpio/*/label") 270 if err != nil { 271 return 272 } 273 274 for _, labelPath := range labels { 275 label, err := ioutil.ReadFile(labelPath) 276 if err != nil { 277 return baseAddr, err 278 } 279 if strings.HasPrefix(string(label), expanderID) { 280 expanderPath, _ := filepath.Split(labelPath) 281 basePath := filepath.Join(expanderPath, "base") 282 base, err := ioutil.ReadFile(basePath) 283 if err != nil { 284 return baseAddr, err 285 } 286 baseAddr, _ = strconv.Atoi(strings.TrimSpace(string(base))) 287 break 288 } 289 } 290 291 return baseAddr, nil 292 } 293 294 func (c *Adaptor) translatePin(pin string) (i int, err error) { 295 if val, ok := c.pinmap[pin]; ok { 296 i = val.pin 297 } else { 298 err = errors.New("Not a valid pin") 299 } 300 return 301 }