gobot.io/x/gobot/v2@v2.1.0/platforms/intel-iot/edison/edison_adaptor.go (about) 1 package edison 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "sync" 8 9 multierror "github.com/hashicorp/go-multierror" 10 "gobot.io/x/gobot/v2" 11 "gobot.io/x/gobot/v2/platforms/adaptors" 12 "gobot.io/x/gobot/v2/system" 13 ) 14 15 const ( 16 defaultI2cBusNumber = 6 17 defaultI2cBusNumberOther = 1 18 ) 19 20 type mux struct { 21 pin int 22 value int 23 } 24 25 type sysfsPin struct { 26 pin int 27 resistor int 28 levelShifter int 29 pwmPin int 30 mux []mux 31 } 32 33 // Adaptor represents a Gobot Adaptor for an Intel Edison 34 type Adaptor struct { 35 name string 36 board string 37 sys *system.Accesser 38 mutex sync.Mutex 39 pinmap map[string]sysfsPin 40 tristate gobot.DigitalPinner 41 digitalPins map[int]gobot.DigitalPinner 42 *adaptors.PWMPinsAdaptor 43 *adaptors.I2cBusAdaptor 44 arduinoI2cInitialized bool 45 } 46 47 // NewAdaptor returns a new Edison Adaptor of the given type. 48 // Supported types are: "arduino", "miniboard", "sparkfun", an empty string defaults to "arduino" 49 func NewAdaptor(boardType ...string) *Adaptor { 50 sys := system.NewAccesser() 51 c := &Adaptor{ 52 name: gobot.DefaultName("Edison"), 53 board: "arduino", 54 sys: sys, 55 pinmap: arduinoPinMap, 56 } 57 if len(boardType) > 0 && boardType[0] != "" { 58 c.board = boardType[0] 59 } 60 c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translateAndMuxPWMPin, 61 adaptors.WithPWMPinInitializer(pwmPinInitializer)) 62 defI2cBusNr := defaultI2cBusNumber 63 if c.board != "arduino" { 64 defI2cBusNr = defaultI2cBusNumberOther 65 } 66 c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateAndSetupI2cBusNumber, defI2cBusNr) 67 return c 68 } 69 70 // Name returns the Adaptors name 71 func (c *Adaptor) Name() string { return c.name } 72 73 // SetName sets the Adaptors name 74 func (c *Adaptor) SetName(n string) { c.name = n } 75 76 // Connect initializes the Edison for use with the Arduino breakout board 77 func (c *Adaptor) Connect() error { 78 c.digitalPins = make(map[int]gobot.DigitalPinner) 79 80 if err := c.I2cBusAdaptor.Connect(); err != nil { 81 return err 82 } 83 84 if err := c.PWMPinsAdaptor.Connect(); err != nil { 85 return err 86 } 87 88 switch c.board { 89 case "sparkfun": 90 c.pinmap = sparkfunPinMap 91 case "arduino": 92 c.board = "arduino" 93 c.pinmap = arduinoPinMap 94 if err := c.arduinoSetup(); err != nil { 95 return err 96 } 97 case "miniboard": 98 c.pinmap = miniboardPinMap 99 default: 100 return fmt.Errorf("Unknown board type: %s", c.board) 101 } 102 103 return nil 104 } 105 106 // Finalize releases all i2c devices and exported analog, digital, pwm pins. 107 func (c *Adaptor) Finalize() (err error) { 108 if c.tristate != nil { 109 if errs := c.tristate.Unexport(); errs != nil { 110 err = multierror.Append(err, errs) 111 } 112 } 113 c.tristate = nil 114 115 for _, pin := range c.digitalPins { 116 if pin != nil { 117 if errs := pin.Unexport(); errs != nil { 118 err = multierror.Append(err, errs) 119 } 120 } 121 } 122 c.digitalPins = nil 123 124 if e := c.PWMPinsAdaptor.Finalize(); e != nil { 125 err = multierror.Append(err, e) 126 } 127 128 if e := c.I2cBusAdaptor.Finalize(); e != nil { 129 err = multierror.Append(err, e) 130 } 131 c.arduinoI2cInitialized = false 132 return 133 } 134 135 // DigitalRead reads digital value from pin 136 func (c *Adaptor) DigitalRead(pin string) (i int, err error) { 137 c.mutex.Lock() 138 defer c.mutex.Unlock() 139 140 sysPin, err := c.digitalPin(pin, system.WithPinDirectionInput()) 141 if err != nil { 142 return 143 } 144 return sysPin.Read() 145 } 146 147 // DigitalWrite writes a value to the pin. Acceptable values are 1 or 0. 148 func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) { 149 c.mutex.Lock() 150 defer c.mutex.Unlock() 151 152 return c.digitalWrite(pin, val) 153 } 154 155 // DigitalPin returns a digital pin. If the pin is initially acquired, it is an input. 156 // Pin direction and other options can be changed afterwards by pin.ApplyOptions() at any time. 157 func (c *Adaptor) DigitalPin(id string) (gobot.DigitalPinner, error) { 158 c.mutex.Lock() 159 defer c.mutex.Unlock() 160 161 return c.digitalPin(id) 162 } 163 164 // AnalogRead returns value from analog reading of specified pin 165 func (c *Adaptor) AnalogRead(pin string) (val int, err error) { 166 buf, err := c.readFile("/sys/bus/iio/devices/iio:device1/in_voltage" + pin + "_raw") 167 if err != nil { 168 return 169 } 170 171 val, err = strconv.Atoi(string(buf[0 : len(buf)-1])) 172 173 return val / 4, err 174 } 175 176 func (c *Adaptor) validateAndSetupI2cBusNumber(busNr int) error { 177 // Valid bus number is 6 for "arduino", otherwise 1. 178 if busNr == 6 && c.board == "arduino" { 179 if !c.arduinoI2cInitialized { 180 if err := c.arduinoI2CSetup(); err != nil { 181 return err 182 } 183 c.arduinoI2cInitialized = true 184 return nil 185 } 186 return nil 187 } 188 189 if busNr == 1 && c.board != "arduino" { 190 return nil 191 } 192 193 return fmt.Errorf("Unsupported I2C bus '%d'", busNr) 194 } 195 196 // arduinoSetup does needed setup for the Arduino compatible breakout board 197 func (c *Adaptor) arduinoSetup() error { 198 // TODO: also check to see if device labels for 199 // /sys/class/gpio/gpiochip{200,216,232,248}/label == "pcal9555a" 200 201 tpin, err := c.newExportedDigitalPin(214, system.WithPinDirectionOutput(system.LOW)) 202 if err != nil { 203 return err 204 } 205 c.tristate = tpin 206 207 for _, i := range []int{263, 262} { 208 if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionOutput(system.HIGH)); err != nil { 209 return err 210 } 211 } 212 213 for _, i := range []int{240, 241, 242, 243} { 214 if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionOutput(system.LOW)); err != nil { 215 return err 216 } 217 } 218 219 for _, i := range []string{"111", "115", "114", "109"} { 220 if err := c.changePinMode(i, "1"); err != nil { 221 return err 222 } 223 } 224 225 for _, i := range []string{"131", "129", "40"} { 226 if err := c.changePinMode(i, "0"); err != nil { 227 return err 228 } 229 } 230 231 return c.tristate.Write(system.HIGH) 232 } 233 234 func (c *Adaptor) arduinoI2CSetup() error { 235 if c.tristate == nil { 236 return fmt.Errorf("not connected") 237 } 238 239 if err := c.tristate.Write(system.LOW); err != nil { 240 return err 241 } 242 243 for _, i := range []int{14, 165, 212, 213} { 244 if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionInput()); err != nil { 245 return err 246 } 247 } 248 249 for _, i := range []int{236, 237, 204, 205} { 250 if err := c.newUnexportedDigitalPin(i, system.WithPinDirectionOutput(system.LOW)); err != nil { 251 return err 252 } 253 } 254 255 for _, i := range []string{"28", "27"} { 256 if err := c.changePinMode(i, "1"); err != nil { 257 return err 258 } 259 } 260 261 return c.tristate.Write(system.HIGH) 262 } 263 264 func (c *Adaptor) readFile(path string) ([]byte, error) { 265 file, err := c.sys.OpenFile(path, os.O_RDONLY, 0644) 266 defer file.Close() //nolint:staticcheck // for historical reasons 267 if err != nil { 268 return make([]byte, 0), err 269 } 270 271 buf := make([]byte, 200) 272 var i int 273 i, err = file.Read(buf) 274 if i == 0 { 275 return buf, err 276 } 277 return buf[:i], err 278 } 279 280 func (c *Adaptor) digitalPin(id string, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) { 281 i := c.pinmap[id] 282 283 err := c.ensureDigitalPin(i.pin, o...) 284 if err != nil { 285 return nil, err 286 } 287 pin := c.digitalPins[i.pin] 288 vpin, ok := pin.(gobot.DigitalPinValuer) 289 if !ok { 290 return nil, fmt.Errorf("can not determine the direction behavior") 291 } 292 dir := vpin.DirectionBehavior() 293 if i.resistor > 0 { 294 rop := system.WithPinDirectionOutput(system.LOW) 295 if dir == system.OUT { 296 rop = system.WithPinDirectionInput() 297 } 298 if err := c.ensureDigitalPin(i.resistor, rop); err != nil { 299 return nil, err 300 } 301 } 302 303 if i.levelShifter > 0 { 304 lop := system.WithPinDirectionOutput(system.LOW) 305 if dir == system.OUT { 306 lop = system.WithPinDirectionOutput(system.HIGH) 307 } 308 if err := c.ensureDigitalPin(i.levelShifter, lop); err != nil { 309 return nil, err 310 } 311 } 312 313 if len(i.mux) > 0 { 314 for _, mux := range i.mux { 315 if err := c.ensureDigitalPin(mux.pin, system.WithPinDirectionOutput(mux.value)); err != nil { 316 return nil, err 317 } 318 } 319 } 320 321 return pin, nil 322 } 323 324 func (c *Adaptor) ensureDigitalPin(idx int, o ...func(gobot.DigitalPinOptioner) bool) error { 325 pin := c.digitalPins[idx] 326 var err error 327 if pin == nil { 328 pin, err = c.newExportedDigitalPin(idx, o...) 329 if err != nil { 330 return err 331 } 332 c.digitalPins[idx] = pin 333 } else { 334 if err := pin.ApplyOptions(o...); err != nil { 335 return err 336 } 337 } 338 return nil 339 } 340 341 func pwmPinInitializer(pin gobot.PWMPinner) error { 342 if err := pin.Export(); err != nil { 343 return err 344 } 345 return pin.SetEnabled(true) 346 } 347 348 func (c *Adaptor) translateAndMuxPWMPin(id string) (string, int, error) { 349 sysPin, ok := c.pinmap[id] 350 if !ok { 351 return "", -1, fmt.Errorf("'%s' is not a valid id for a pin", id) 352 } 353 if sysPin.pwmPin == -1 { 354 return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) 355 } 356 if err := c.digitalWrite(id, 1); err != nil { 357 return "", -1, err 358 } 359 if err := c.changePinMode(strconv.Itoa(int(sysPin.pin)), "1"); err != nil { 360 return "", -1, err 361 } 362 return "/sys/class/pwm/pwmchip0", sysPin.pwmPin, nil 363 } 364 365 func (c *Adaptor) newUnexportedDigitalPin(i int, o ...func(gobot.DigitalPinOptioner) bool) error { 366 io := c.sys.NewDigitalPin("", i, o...) 367 if err := io.Export(); err != nil { 368 return err 369 } 370 return io.Unexport() 371 } 372 373 func (c *Adaptor) newExportedDigitalPin(pin int, o ...func(gobot.DigitalPinOptioner) bool) (gobot.DigitalPinner, error) { 374 sysPin := c.sys.NewDigitalPin("", pin, o...) 375 err := sysPin.Export() 376 return sysPin, err 377 } 378 379 // changePinMode writes pin mode to current_pinmux file 380 func (c *Adaptor) changePinMode(pin, mode string) error { 381 file, err := c.sys.OpenFile("/sys/kernel/debug/gpio_debug/gpio"+pin+"/current_pinmux", os.O_WRONLY, 0644) 382 defer file.Close() //nolint:staticcheck // for historical reasons 383 if err != nil { 384 return err 385 } 386 _, err = file.Write([]byte("mode" + mode)) 387 return err 388 } 389 390 func (c *Adaptor) digitalWrite(pin string, val byte) (err error) { 391 sysPin, err := c.digitalPin(pin, system.WithPinDirectionOutput(int(val))) 392 if err != nil { 393 return 394 } 395 return sysPin.Write(int(val)) 396 }