From 57506b6f16cbbe811beb4b2687025dd96001ccdb Mon Sep 17 00:00:00 2001 From: Thomas Kohler Date: Mon, 9 Dec 2024 16:49:10 +0100 Subject: [PATCH] tinkerboard2: introduce adapter for Asus Tinker Board 2 --- .../tinkerboard2_direct_pin_bin_counter.go | 89 +++++++ examples/tinkerboard2_yl40.go | 80 ++++++ platforms/adaptors/analogpinsadaptor_test.go | 6 +- platforms/adaptors/analogpintranslator.go | 45 ++++ .../adaptors/analogpintranslator_test.go | 77 ++++++ platforms/adaptors/busnumbervalidator.go | 22 ++ platforms/adaptors/busnumbervalidator_test.go | 90 +++++++ platforms/adaptors/digitalpintranslator.go | 43 +++ .../adaptors/digitalpintranslator_test.go | 94 +++++++ platforms/adaptors/pwmpintranslator.go | 57 ++++ platforms/adaptors/pwmpintranslator_test.go | 107 ++++++++ platforms/beaglebone/beaglebone_adaptor.go | 105 ++------ .../beaglebone/beaglebone_adaptor_test.go | 93 +------ platforms/beaglebone/black_pins.go | 34 +-- platforms/beaglebone/pocketbeagle_adaptor.go | 9 +- .../beaglebone/pocketbeagle_adaptor_test.go | 31 +++ platforms/beaglebone/pocketbeagle_pins.go | 26 +- platforms/chip/chip_adaptor.go | 13 +- platforms/chip/chip_adaptor_test.go | 35 --- platforms/dragonboard/dragonboard_adaptor.go | 14 +- .../dragonboard/dragonboard_adaptor_test.go | 33 --- .../intel-iot/edison/edison_adaptor_test.go | 2 +- platforms/intel-iot/joule/joule_adaptor.go | 13 +- .../intel-iot/joule/joule_adaptor_test.go | 36 --- platforms/jetson/jetson_adaptor.go | 29 +- platforms/jetson/jetson_adaptor_test.go | 65 ----- platforms/nanopi/nanopi_adaptor.go | 137 ++-------- platforms/nanopi/nanopi_adaptor_test.go | 191 +------------- platforms/nanopi/nanopineo_pin_map.go | 40 +-- platforms/raspi/raspi_adaptor.go | 57 +--- platforms/raspi/raspi_adaptor_test.go | 109 +------- platforms/raspi/raspi_pin_map.go | 6 +- platforms/rockpi/rockpi_adaptor.go | 100 ++++--- platforms/rockpi/rockpi_adaptor_test.go | 67 ----- platforms/tinkerboard/README.md | 2 +- platforms/tinkerboard/adaptor.go | 127 ++------- platforms/tinkerboard/adaptor_test.go | 249 +----------------- platforms/tinkerboard/pin_map.go | 72 ++--- platforms/tinkerboard/tinkerboard2/README.md | 90 +++++++ platforms/tinkerboard/tinkerboard2/adaptor.go | 69 +++++ .../tinkerboard/tinkerboard2/adaptor_test.go | 24 ++ platforms/tinkerboard/tinkerboard2/pin_map.go | 57 ++++ platforms/upboard/up2/adaptor.go | 29 +- platforms/upboard/up2/adaptor_test.go | 68 ----- 44 files changed, 1235 insertions(+), 1507 deletions(-) create mode 100644 examples/tinkerboard2_direct_pin_bin_counter.go create mode 100644 examples/tinkerboard2_yl40.go create mode 100644 platforms/adaptors/analogpintranslator.go create mode 100644 platforms/adaptors/analogpintranslator_test.go create mode 100644 platforms/adaptors/busnumbervalidator.go create mode 100644 platforms/adaptors/busnumbervalidator_test.go create mode 100644 platforms/adaptors/digitalpintranslator.go create mode 100644 platforms/adaptors/digitalpintranslator_test.go create mode 100644 platforms/adaptors/pwmpintranslator.go create mode 100644 platforms/adaptors/pwmpintranslator_test.go create mode 100644 platforms/beaglebone/pocketbeagle_adaptor_test.go create mode 100644 platforms/tinkerboard/tinkerboard2/README.md create mode 100644 platforms/tinkerboard/tinkerboard2/adaptor.go create mode 100644 platforms/tinkerboard/tinkerboard2/adaptor_test.go create mode 100644 platforms/tinkerboard/tinkerboard2/pin_map.go diff --git a/examples/tinkerboard2_direct_pin_bin_counter.go b/examples/tinkerboard2_direct_pin_bin_counter.go new file mode 100644 index 000000000..044f7ddbd --- /dev/null +++ b/examples/tinkerboard2_direct_pin_bin_counter.go @@ -0,0 +1,89 @@ +//go:build example +// +build example + +// +// Do not build by default. + +package main + +import ( + "fmt" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/gpio" + "gobot.io/x/gobot/v2/platforms/adaptors" + "gobot.io/x/gobot/v2/platforms/tinkerboard/tinkerboard2" +) + +// Wiring +// PWR Tinkerboard-2: 1 (+3.3V, VCC), 2(+5V), 6, 9, 14, 20 (GND) +// GPIO Tinkerboard-2: header pins 3, 5, 7, 11 used as inverted output +// LED's: the output pins are wired to the cathode of a LED, the anode is wired with a resistor (70-130Ohm for 20mA) +// to +3.3V (use >150Ohm if connected to +5V) +// Expected behavior: the 4 LED's on normal output counts up binary +func main() { + const ( + outPinBit0Num = "3" + outPinBit1Num = "5" + outPinBit2Num = "7" + outPinBit3Num = "11" + ) + + board := tinkerboard2.NewAdaptor(adaptors.WithGpiosActiveLow(outPinBit0Num, outPinBit1Num, outPinBit2Num, + outPinBit3Num)) + outPinB0 := gpio.NewDirectPinDriver(board, outPinBit0Num) + outPinB1 := gpio.NewDirectPinDriver(board, outPinBit1Num) + outPinB2 := gpio.NewDirectPinDriver(board, outPinBit2Num) + outPinB3 := gpio.NewDirectPinDriver(board, outPinBit3Num) + + work := func() { + value := byte(0) + + gobot.Every(500*time.Millisecond, func() { + b0 := value & 0x01 + b1 := (value & 0x02) / 0x02 + b2 := (value & 0x04) / 0x04 + b3 := (value & 0x08) / 0x08 + + if err := outPinB0.DigitalWrite(b0); err != nil { + fmt.Println(err) + } else { + fmt.Printf("pin %s is now %d\n", outPinBit0Num, b0) + } + + if err := outPinB1.DigitalWrite(b1); err != nil { + fmt.Println(err) + } else { + fmt.Printf("pin %s is now %d\n", outPinBit1Num, b1) + } + + if err := outPinB2.DigitalWrite(b2); err != nil { + fmt.Println(err) + } else { + fmt.Printf("pin %s is now %d\n", outPinBit2Num, b2) + } + + if err := outPinB3.DigitalWrite(b3); err != nil { + fmt.Println(err) + } else { + fmt.Printf("pin %s is now %d\n", outPinBit3Num, b3) + } + + value++ + if value > 15 { + value = 0 + } + }) + } + + robot := gobot.NewRobot("pinBot", + []gobot.Connection{board}, + []gobot.Device{outPinB0, outPinB1, outPinB2, outPinB3}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/examples/tinkerboard2_yl40.go b/examples/tinkerboard2_yl40.go new file mode 100644 index 000000000..79e59fcf4 --- /dev/null +++ b/examples/tinkerboard2_yl40.go @@ -0,0 +1,80 @@ +//go:build example +// +build example + +// +// Do not build by default. + +package main + +import ( + "fmt" + "log" + "time" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/drivers/i2c" + "gobot.io/x/gobot/v2/platforms/tinkerboard/tinkerboard2" +) + +func main() { + // Wiring + // PWR Tinkerboard 2: 1 (+3.3V, VCC), 6, 9, 14, 20 (GND) + // I2C1 Tinkerboard 2: 27 (SDA), 28 (SCL) + // YL-40 module: wire AOUT --> AIN2 for this example, set all jumpers for temp, LDR and variable resistor + // + // Note: temperature measurement is often buggy, because sensor is not properly grounded + // fix it by soldering a small bridge to the adjacent ground pin of brightness sensor + board := tinkerboard2.NewAdaptor() + yl := i2c.NewYL40Driver(board, i2c.WithBus(7)) + + work := func() { + // the LED light is visible above ~1.7V + writeVal, _ := yl.AOUT() + + gobot.Every(1000*time.Millisecond, func() { + if err := yl.Write(writeVal); err != nil { + fmt.Println(err) + } else { + log.Printf(" %.1f V written", writeVal) + writeVal = writeVal + 0.1 + if writeVal > 3.3 { + writeVal = 0 + } + } + + if brightness, err := yl.ReadBrightness(); err != nil { + fmt.Println(err) + } else { + log.Printf("Brightness: %.0f [0..1000]", brightness) + } + + if temperature, err := yl.ReadTemperature(); err != nil { + fmt.Println(err) + } else { + log.Printf("Temperature: %.1f °C", temperature) + } + + if ain2, err := yl.ReadAIN2(); err != nil { + fmt.Println(err) + } else { + log.Printf("Read back AOUT: %.1f [0..3.3]", ain2) + } + + if potiState, err := yl.ReadPotentiometer(); err != nil { + fmt.Println(err) + } else { + log.Printf("Resistor: %.0f %% [-100..+100]", potiState) + } + }) + } + + robot := gobot.NewRobot("yl40Bot", + []gobot.Connection{board}, + []gobot.Device{yl}, + work, + ) + + if err := robot.Start(); err != nil { + panic(err) + } +} diff --git a/platforms/adaptors/analogpinsadaptor_test.go b/platforms/adaptors/analogpinsadaptor_test.go index 3a5289ed4..0f78e4fa6 100644 --- a/platforms/adaptors/analogpinsadaptor_test.go +++ b/platforms/adaptors/analogpinsadaptor_test.go @@ -52,7 +52,7 @@ func testAnalogPinTranslator(id string) (string, bool, bool, uint16, error) { return analogReadWriteStringPath, true, true, 13, nil } - return "", false, false, 0, fmt.Errorf("'%s' is not a valid id of a analog pin", id) + return "", false, false, 0, fmt.Errorf("'%s' is not a valid id of an analog pin", id) } func TestAnalogPinsConnect(t *testing.T) { @@ -158,7 +158,7 @@ func TestAnalogWrite(t *testing.T) { wantValW: "0", wantValRW: "30000", wantValRWS: "inverted", - wantErr: "'notexist' is not a valid id of a analog pin", + wantErr: "'notexist' is not a valid id of an analog pin", }, "error_write_not_allowed": { pin: "read", @@ -218,7 +218,7 @@ func TestAnalogRead(t *testing.T) { }, "error_notexist": { pin: "notexist", - wantErr: "'notexist' is not a valid id of a analog pin", + wantErr: "'notexist' is not a valid id of an analog pin", }, "error_invalid_syntax": { pin: "read/write_string", diff --git a/platforms/adaptors/analogpintranslator.go b/platforms/adaptors/analogpintranslator.go new file mode 100644 index 000000000..6cbf19bbe --- /dev/null +++ b/platforms/adaptors/analogpintranslator.go @@ -0,0 +1,45 @@ +package adaptors + +import ( + "fmt" + + "gobot.io/x/gobot/v2/system" +) + +type AnalogPinDefinition struct { + Path string + R bool // readable + W bool // writable + BufLen uint16 +} + +type AnalogPinDefinitions map[string]AnalogPinDefinition + +type AnalogPinTranslator struct { + sys *system.Accesser + pinDefinitions AnalogPinDefinitions +} + +// NewAnalogPinTranslator creates a new instance of a translator for analog pins, suitable for the most cases. +func NewAnalogPinTranslator(sys *system.Accesser, pinDefinitions AnalogPinDefinitions) *AnalogPinTranslator { + return &AnalogPinTranslator{sys: sys, pinDefinitions: pinDefinitions} +} + +// Translate returns the sysfs path for the given id. +func (pt *AnalogPinTranslator) Translate(id string) (string, bool, bool, uint16, error) { + pinInfo, ok := pt.pinDefinitions[id] + if !ok { + return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for an analog pin", id) + } + + path := pinInfo.Path + info, err := pt.sys.Stat(path) + if err != nil { + return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path) + } + if info.IsDir() { + return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path) + } + + return path, pinInfo.R, pinInfo.W, pinInfo.BufLen, nil +} diff --git a/platforms/adaptors/analogpintranslator_test.go b/platforms/adaptors/analogpintranslator_test.go new file mode 100644 index 000000000..3a5c43991 --- /dev/null +++ b/platforms/adaptors/analogpintranslator_test.go @@ -0,0 +1,77 @@ +package adaptors + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gobot.io/x/gobot/v2/system" +) + +func TestNewAnalogPinTranslator(t *testing.T) { + // arrange + sys := &system.Accesser{} + pinDef := AnalogPinDefinitions{} + // act + pt := NewAnalogPinTranslator(sys, pinDef) + // assert + assert.IsType(t, &AnalogPinTranslator{}, pt) + assert.Equal(t, sys, pt.sys) + assert.Equal(t, pinDef, pt.pinDefinitions) +} + +func TestAnalogPinTranslatorTranslate(t *testing.T) { + pinDefinitions := AnalogPinDefinitions{ + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, + "thermal_zone1": {Path: "/sys/class/thermal/thermal_zone1/temp", R: true, W: false, BufLen: 7}, + } + mockedPaths := []string{ + "/sys/class/thermal/thermal_zone0/temp", + "/sys/class/thermal/thermal_zone1/temp", + } + tests := map[string]struct { + id string + wantPath string + wantReadable bool + wantBufLen uint16 + wantErr string + }{ + "translate_thermal_zone0": { + id: "thermal_zone0", + wantPath: "/sys/class/thermal/thermal_zone0/temp", + wantReadable: true, + wantBufLen: 7, + }, + "translate_thermal_zone1": { + id: "thermal_zone1", + wantPath: "/sys/class/thermal/thermal_zone1/temp", + wantReadable: true, + wantBufLen: 7, + }, + "unknown_id": { + id: "99", + wantErr: "'99' is not a valid id for an analog pin", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + sys := system.NewAccesser() + _ = sys.UseMockFilesystem(mockedPaths) + pt := NewAnalogPinTranslator(sys, pinDefinitions) + // act + path, r, w, buf, err := pt.Translate(tc.id) + // assert + if tc.wantErr != "" { + require.EqualError(t, err, tc.wantErr) + } else { + require.NoError(t, err) + } + assert.Equal(t, tc.wantPath, path) + assert.Equal(t, tc.wantReadable, r) + assert.False(t, w) + assert.Equal(t, tc.wantBufLen, buf) + }) + } +} diff --git a/platforms/adaptors/busnumbervalidator.go b/platforms/adaptors/busnumbervalidator.go new file mode 100644 index 000000000..c8108f8ee --- /dev/null +++ b/platforms/adaptors/busnumbervalidator.go @@ -0,0 +1,22 @@ +package adaptors + +import "fmt" + +type BusNumberValidator struct { + validNumbers []int +} + +// NewBusNumberValidator creates a new instance for a bus number validator, used for I2C and SPI. +func NewBusNumberValidator(validNumbers []int) *BusNumberValidator { + return &BusNumberValidator{validNumbers: validNumbers} +} + +func (bnv *BusNumberValidator) Validate(busNr int) error { + for _, validNumber := range bnv.validNumbers { + if validNumber == busNr { + return nil + } + } + + return fmt.Errorf("Bus number %d out of range %v", busNr, bnv.validNumbers) +} diff --git a/platforms/adaptors/busnumbervalidator_test.go b/platforms/adaptors/busnumbervalidator_test.go new file mode 100644 index 000000000..1967b3a5f --- /dev/null +++ b/platforms/adaptors/busnumbervalidator_test.go @@ -0,0 +1,90 @@ +package adaptors + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewBusNumberValidator(t *testing.T) { + // arrange + validNums := []int{5, 8} + // act + bnv := NewBusNumberValidator(validNums) + // assert + assert.IsType(t, &BusNumberValidator{}, bnv) + assert.Equal(t, validNums, bnv.validNumbers) +} + +func TestBusNumberValidatorValidate(t *testing.T) { + tests := map[string]struct { + validNumbers []int + busNr int + wantErr error + }{ + "number_negative_error": { + validNumbers: []int{0, 1, 2, 3, 4}, + busNr: -1, + wantErr: fmt.Errorf("Bus number -1 out of range [0 1 2 3 4]"), + }, + "number_0_ok": { + validNumbers: []int{0, 1, 2, 3, 4}, + busNr: 0, + }, + "number_1_ok": { + validNumbers: []int{0, 1, 2, 3, 4}, + busNr: 1, + }, + "number_2_ok": { + validNumbers: []int{0, 1, 2, 3, 4}, + busNr: 2, + }, + "number_3_ok": { + validNumbers: []int{0, 1, 2, 3, 4}, + busNr: 3, + }, + "number_4_ok": { + validNumbers: []int{0, 1, 2, 3, 4}, + busNr: 4, + }, + "number_5_error": { + validNumbers: []int{0, 1, 2, 3, 4}, + busNr: 5, + wantErr: fmt.Errorf("Bus number 5 out of range [0 1 2 3 4]"), + }, + "number_negative_error_0_2": { + validNumbers: []int{0, 2}, + busNr: -1, + wantErr: fmt.Errorf("Bus number -1 out of range [0 2]"), + }, + "number_0_ok_0_2": { + validNumbers: []int{0, 2}, + busNr: 0, + }, + "number_1_error_0_2": { + validNumbers: []int{0, 2}, + busNr: 1, + wantErr: fmt.Errorf("Bus number 1 out of range [0 2]"), + }, + "number_2_ok_0_2": { + validNumbers: []int{0, 2}, + busNr: 2, + }, + "number_3_error_0_2": { + validNumbers: []int{0, 2}, + busNr: 3, + wantErr: fmt.Errorf("Bus number 3 out of range [0 2]"), + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + bnv := NewBusNumberValidator(tc.validNumbers) + // act + err := bnv.Validate(tc.busNr) + // assert + assert.Equal(t, tc.wantErr, err) + }) + } +} diff --git a/platforms/adaptors/digitalpintranslator.go b/platforms/adaptors/digitalpintranslator.go new file mode 100644 index 000000000..8e99ae663 --- /dev/null +++ b/platforms/adaptors/digitalpintranslator.go @@ -0,0 +1,43 @@ +package adaptors + +import ( + "fmt" + + "gobot.io/x/gobot/v2/system" +) + +type CdevPin struct { + Chip uint8 + Line uint8 +} + +type DigitalPinDefinition struct { + Sysfs int + Cdev CdevPin +} + +type DigitalPinDefinitions map[string]DigitalPinDefinition + +type DigitalPinTranslator struct { + sys *system.Accesser + pinDefinitions DigitalPinDefinitions +} + +// NewDigitalPinTranslator creates a new instance of a translator for digital GPIO pins, suitable for the most cases. +func NewDigitalPinTranslator(sys *system.Accesser, pinDefinitions DigitalPinDefinitions) *DigitalPinTranslator { + return &DigitalPinTranslator{sys: sys, pinDefinitions: pinDefinitions} +} + +// Translate returns the chip and the line or for legacy sysfs usage the pin number from the given id. +func (pt *DigitalPinTranslator) Translate(id string) (string, int, error) { + pindef, ok := pt.pinDefinitions[id] + if !ok { + return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) + } + if pt.sys.IsSysfsDigitalPinAccess() { + return "", pindef.Sysfs, nil + } + chip := fmt.Sprintf("gpiochip%d", pindef.Cdev.Chip) + line := int(pindef.Cdev.Line) + return chip, line, nil +} diff --git a/platforms/adaptors/digitalpintranslator_test.go b/platforms/adaptors/digitalpintranslator_test.go new file mode 100644 index 000000000..cce547790 --- /dev/null +++ b/platforms/adaptors/digitalpintranslator_test.go @@ -0,0 +1,94 @@ +package adaptors + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "gobot.io/x/gobot/v2/system" +) + +func TestNewDigitalPinTranslator(t *testing.T) { + // arrange + sys := &system.Accesser{} + pinDef := DigitalPinDefinitions{} + // act + pt := NewDigitalPinTranslator(sys, pinDef) + // assert + assert.IsType(t, &DigitalPinTranslator{}, pt) + assert.Equal(t, sys, pt.sys) + assert.Equal(t, pinDef, pt.pinDefinitions) +} + +func TestDigitalPinTranslatorTranslate(t *testing.T) { + pinDefinitions := DigitalPinDefinitions{ + "7": {Sysfs: 17, Cdev: CdevPin{Chip: 0, Line: 17}}, + "22": {Sysfs: 171, Cdev: CdevPin{Chip: 5, Line: 19}}, + "5": {Sysfs: 253, Cdev: CdevPin{Chip: 8, Line: 5}}, + } + tests := map[string]struct { + access string + pin string + wantChip string + wantLine int + wantErr error + }{ + "cdev_ok_7": { + access: "cdev", + pin: "7", + wantChip: "gpiochip0", + wantLine: 17, + }, + "cdev_ok_22": { + access: "cdev", + pin: "22", + wantChip: "gpiochip5", + wantLine: 19, + }, + "cdev_ok_5": { + access: "cdev", + pin: "5", + wantChip: "gpiochip8", + wantLine: 5, + }, + "sysfs_ok_7": { + access: "sysfs", + pin: "7", + wantChip: "", + wantLine: 17, + }, + "sysfs_ok_22": { + access: "sysfs", + pin: "22", + wantChip: "", + wantLine: 171, + }, + "sysfs_ok_5": { + access: "sysfs", + pin: "5", + wantChip: "", + wantLine: 253, + }, + "unknown_pin": { + pin: "99", + wantChip: "", + wantLine: -1, + wantErr: fmt.Errorf("'99' is not a valid id for a digital pin"), + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) + sys.UseDigitalPinAccessWithMockFs(tc.access, []string{}) + pt := NewDigitalPinTranslator(sys, pinDefinitions) + // act + chip, line, err := pt.Translate(tc.pin) + // assert + assert.Equal(t, tc.wantErr, err) + assert.Equal(t, tc.wantChip, chip) + assert.Equal(t, tc.wantLine, line) + }) + } +} diff --git a/platforms/adaptors/pwmpintranslator.go b/platforms/adaptors/pwmpintranslator.go new file mode 100644 index 000000000..620a40c0b --- /dev/null +++ b/platforms/adaptors/pwmpintranslator.go @@ -0,0 +1,57 @@ +package adaptors + +import ( + "fmt" + + "gobot.io/x/gobot/v2/system" +) + +type PWMPinDefinition struct { + Dir string + DirRegexp string + Channel int +} + +type PWMPinDefinitions map[string]PWMPinDefinition + +type PWMPinTranslator struct { + sys *system.Accesser + pinDefinitions PWMPinDefinitions +} + +// NewPWMPinTranslator creates a new instance of a PWM pin translator, suitable for the most cases. +func NewPWMPinTranslator(sys *system.Accesser, pinDefinitions PWMPinDefinitions) *PWMPinTranslator { + return &PWMPinTranslator{sys: sys, pinDefinitions: pinDefinitions} +} + +// Translate returns the sysfs path and channel for the given id. +func (pt *PWMPinTranslator) Translate(id string) (string, int, error) { + pinInfo, ok := pt.pinDefinitions[id] + if !ok { + return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) + } + path, err := pinInfo.FindPWMDir(pt.sys) + if err != nil { + return "", -1, err + } + return path, pinInfo.Channel, nil +} + +func (p PWMPinDefinition) FindPWMDir(sys *system.Accesser) (string, error) { + items, _ := sys.Find(p.Dir, p.DirRegexp) + if len(items) == 0 { + return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'. See README.md for activation", + p.DirRegexp, p.Dir) + } + + dir := items[0] + info, err := sys.Stat(dir) + if err != nil { + return "", fmt.Errorf("Error (%v) on access '%s'", err, dir) + } + if !info.IsDir() { + return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir) + } + + return dir, nil +} diff --git a/platforms/adaptors/pwmpintranslator_test.go b/platforms/adaptors/pwmpintranslator_test.go new file mode 100644 index 000000000..15bfed01d --- /dev/null +++ b/platforms/adaptors/pwmpintranslator_test.go @@ -0,0 +1,107 @@ +package adaptors + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + + "gobot.io/x/gobot/v2/system" +) + +func TestNewPWMPinTranslator(t *testing.T) { + // arrange + sys := &system.Accesser{} + pinDef := PWMPinDefinitions{} + // act + pt := NewPWMPinTranslator(sys, pinDef) + // assert + assert.IsType(t, &PWMPinTranslator{}, pt) + assert.Equal(t, sys, pt.sys) + assert.Equal(t, pinDef, pt.pinDefinitions) +} + +func TestPWMPinTranslatorTranslate(t *testing.T) { + pinDefinitions := PWMPinDefinitions{ + "33": {Dir: "/sys/devices/platform/ff680020.pwm/pwm/", DirRegexp: "pwmchip[0|1|2]$", Channel: 0}, + "32": {Dir: "/sys/devices/platform/ff680030.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3]$", Channel: 0}, + } + basePaths := []string{ + "/sys/devices/platform/ff680020.pwm/pwm/", + "/sys/devices/platform/ff680030.pwm/pwm/", + } + tests := map[string]struct { + pin string + chip string + wantDir string + wantChannel int + wantErr error + }{ + "32_chip0": { + pin: "32", + chip: "pwmchip0", + wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip0", + wantChannel: 0, + }, + "32_chip1": { + pin: "32", + chip: "pwmchip1", + wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip1", + wantChannel: 0, + }, + "32_chip2": { + pin: "32", + chip: "pwmchip2", + wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip2", + wantChannel: 0, + }, + "32_chip3": { + pin: "32", + chip: "pwmchip3", + wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip3", + wantChannel: 0, + }, + "33_chip0": { + pin: "33", + chip: "pwmchip0", + wantDir: "/sys/devices/platform/ff680020.pwm/pwm/pwmchip0", + wantChannel: 0, + }, + "33_chip1": { + pin: "33", + chip: "pwmchip1", + wantDir: "/sys/devices/platform/ff680020.pwm/pwm/pwmchip1", + wantChannel: 0, + }, + "33_chip2": { + pin: "33", + chip: "pwmchip2", + wantDir: "/sys/devices/platform/ff680020.pwm/pwm/pwmchip2", + wantChannel: 0, + }, + "invalid_pin": { + pin: "7", + wantDir: "", + wantChannel: -1, + wantErr: fmt.Errorf("'7' is not a valid id for a PWM pin"), + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // arrange + mockedPaths := []string{} + for _, base := range basePaths { + mockedPaths = append(mockedPaths, base+tc.chip+"/") + } + sys := system.NewAccesser() + _ = sys.UseMockFilesystem(mockedPaths) + pt := NewPWMPinTranslator(sys, pinDefinitions) + // act + dir, channel, err := pt.Translate(tc.pin) + // assert + assert.Equal(t, tc.wantErr, err) + assert.Equal(t, tc.wantDir, dir) + assert.Equal(t, tc.wantChannel, channel) + }) + } +} diff --git a/platforms/beaglebone/beaglebone_adaptor.go b/platforms/beaglebone/beaglebone_adaptor.go index c95fb170e..a8594e4e2 100644 --- a/platforms/beaglebone/beaglebone_adaptor.go +++ b/platforms/beaglebone/beaglebone_adaptor.go @@ -14,19 +14,6 @@ import ( "gobot.io/x/gobot/v2/system" ) -type pwmPinDefinition struct { - channel int - dir string - dirRegexp string -} - -type analogPinDefinition struct { - path string - r bool // readable - w bool // writable - bufLen uint16 -} - const ( pwmPeriodDefault = 500000 // 0.5 ms = 2 kHz @@ -49,10 +36,9 @@ type Adaptor struct { *adaptors.PWMPinsAdaptor *adaptors.I2cBusAdaptor *adaptors.SpiBusAdaptor - usrLed string - pinMap map[string]int - pwmPinMap map[string]pwmPinDefinition - analogPinMap map[string]analogPinDefinition + usrLed string + pinMap map[string]int + pwmPinTranslate func(string) (string, int, error) } // NewAdaptor returns a new Beaglebone Black/Green Adaptor @@ -65,14 +51,14 @@ type Adaptor struct { // Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] func NewAdaptor(opts ...interface{}) *Adaptor { sys := system.NewAccesser() + pwmPinTranslator := adaptors.NewPWMPinTranslator(sys, bbbPwmPinMap) a := &Adaptor{ - name: gobot.DefaultName("BeagleboneBlack"), - sys: sys, - mutex: &sync.Mutex{}, - pinMap: bbbPinMap, - pwmPinMap: bbbPwmPinMap, - analogPinMap: bbbAnalogPinMap, - usrLed: "/sys/class/leds/beaglebone:green:", + name: gobot.DefaultName("BeagleboneBlack"), + sys: sys, + mutex: &sync.Mutex{}, + pinMap: bbbPinMap, + pwmPinTranslate: pwmPinTranslator.Translate, + usrLed: "/sys/class/leds/beaglebone:green:", } var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) @@ -88,12 +74,19 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } } - a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, a.translateAnalogPin) + analogPinTranslator := adaptors.NewAnalogPinTranslator(sys, bbbAnalogPinMap) + // Valid bus number is either 0 or 2 which corresponds to /dev/i2c-0 or /dev/i2c-2. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 2}) + // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. + // x is the chip number <255 + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) + + a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, analogPinTranslator.Translate) a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateAndMuxDigitalPin, digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translateAndMuxPWMPin, pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber) - a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, a.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, - defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) return a } @@ -170,33 +163,6 @@ func (a *Adaptor) DigitalWrite(id string, val byte) error { return a.DigitalPinsAdaptor.DigitalWrite(id, val) } -func (a *Adaptor) validateSpiBusNumber(busNr int) error { - // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. - // x is the chip number <255 - if (busNr < 0) || (busNr > 1) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is either 0 or 2 which corresponds to /dev/i2c-0 or /dev/i2c-2. - if (busNr != 0) && (busNr != 2) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -// translateAnalogPin converts analog pin name to pin position -func (a *Adaptor) translateAnalogPin(pin string) (string, bool, bool, uint16, error) { - pinInfo, ok := a.analogPinMap[pin] - if !ok { - return "", false, false, 0, fmt.Errorf("Not a valid analog pin") - } - - return pinInfo.path, pinInfo.r, pinInfo.w, pinInfo.bufLen, nil -} - // translatePin converts digital pin name to pin position func (a *Adaptor) translateAndMuxDigitalPin(id string) (string, int, error) { line, ok := a.pinMap[id] @@ -211,39 +177,16 @@ func (a *Adaptor) translateAndMuxDigitalPin(id string) (string, int, error) { } func (a *Adaptor) translateAndMuxPWMPin(id string) (string, int, error) { - pinInfo, ok := a.pwmPinMap[id] - if !ok { - return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) - } - - path, err := pinInfo.findPWMDir(a.sys) + path, channel, err := a.pwmPinTranslate(id) if err != nil { - return "", -1, err + return path, channel, err } if err := a.muxPin(id, "pwm"); err != nil { return "", -1, err } - return path, pinInfo.channel, nil -} - -func (p pwmPinDefinition) findPWMDir(sys *system.Accesser) (string, error) { - items, _ := sys.Find(p.dir, p.dirRegexp) - if len(items) == 0 { - return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'", p.dirRegexp, p.dir) - } - - dir := items[0] - info, err := sys.Stat(dir) - if err != nil { - return "", fmt.Errorf("Error (%v) on access '%s'", err, dir) - } - if !info.IsDir() { - return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir) - } - - return dir, nil + return path, channel, nil } func (a *Adaptor) muxPin(pin, cmd string) error { diff --git a/platforms/beaglebone/beaglebone_adaptor_test.go b/platforms/beaglebone/beaglebone_adaptor_test.go index e94de6971..1929f8d99 100644 --- a/platforms/beaglebone/beaglebone_adaptor_test.go +++ b/platforms/beaglebone/beaglebone_adaptor_test.go @@ -90,34 +90,13 @@ func TestNewAdaptor(t *testing.T) { assert.NotNil(t, a.I2cBusAdaptor) assert.NotNil(t, a.SpiBusAdaptor) assert.Equal(t, bbbPinMap, a.pinMap) - assert.Equal(t, bbbPwmPinMap, a.pwmPinMap) - assert.Equal(t, bbbAnalogPinMap, a.analogPinMap) + assert.NotNil(t, a.pwmPinTranslate) assert.Equal(t, "/sys/class/leds/beaglebone:green:", a.usrLed) // act & assert a.SetName("NewName") assert.Equal(t, "NewName", a.Name()) } -func TestNewPocketBeagleAdaptor(t *testing.T) { - // arrange & act - a := NewPocketBeagleAdaptor() - // assert - assert.IsType(t, &PocketBeagleAdaptor{}, a) - assert.True(t, strings.HasPrefix(a.Name(), "PocketBeagle")) - assert.NotNil(t, a.sys) - assert.Equal(t, pocketBeaglePinMap, a.pinMap) - assert.Equal(t, pocketBeaglePwmPinMap, a.pwmPinMap) - assert.Equal(t, pocketBeagleAnalogPinMap, a.analogPinMap) - assert.Equal(t, "/sys/class/leds/beaglebone:green:", a.usrLed) -} - -func TestNewPocketBeagleAdaptorWithOption(t *testing.T) { - // arrange & act - a := NewPocketBeagleAdaptor(adaptors.WithGpiodAccess()) - // assert - require.NoError(t, a.Connect()) -} - func TestPWMWrite(t *testing.T) { // arrange a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths) @@ -173,7 +152,7 @@ func TestAnalog(t *testing.T) { require.NoError(t, err) _, err = a.AnalogRead("P9_99") - require.ErrorContains(t, err, "Not a valid analog pin") + require.ErrorContains(t, err, "not a valid id for an analog pin") fs.WithReadError = true _, err = a.AnalogRead("P9_40") @@ -307,74 +286,6 @@ func TestI2cFinalizeWithErrors(t *testing.T) { require.ErrorContains(t, err, "close error") } -func Test_validateSpiBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_error": { - busNr: 2, - wantErr: fmt.Errorf("Bus number 2 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateSpiBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_error": { - busNr: 1, - wantErr: fmt.Errorf("Bus number 1 out of range"), - }, - "number_2_ok": { - busNr: 2, - }, - "number_3_error": { - busNr: 3, - wantErr: fmt.Errorf("Bus number 3 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - func Test_translateAndMuxPWMPin(t *testing.T) { // arrange mockPaths := []string{ diff --git a/platforms/beaglebone/black_pins.go b/platforms/beaglebone/black_pins.go index c8f03ae98..ed6bec431 100644 --- a/platforms/beaglebone/black_pins.go +++ b/platforms/beaglebone/black_pins.go @@ -1,5 +1,7 @@ package beaglebone +import "gobot.io/x/gobot/v2/platforms/adaptors" + var bbbPinMap = map[string]int{ // P8_01 - P8_2 GND // P8_03 - P8_6 EMCC @@ -49,36 +51,36 @@ var bbbPinMap = map[string]int{ "P9_31": 110, } -var bbbPwmPinMap = map[string]pwmPinDefinition{ +var bbbPwmPinMap = adaptors.PWMPinDefinitions{ "P8_13": { - dir: "/sys/devices/platform/ocp/48304000.epwmss/48304200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 1, + Dir: "/sys/devices/platform/ocp/48304000.epwmss/48304200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 1, }, "P8_19": { - dir: "/sys/devices/platform/ocp/48304000.epwmss/48304200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 0, + Dir: "/sys/devices/platform/ocp/48304000.epwmss/48304200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 0, }, "P9_14": { - dir: "/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 0, + Dir: "/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 0, }, "P9_16": { - dir: "/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 1, + Dir: "/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 1, }, "P9_21": { - dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 1, + Dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 1, }, "P9_22": { - dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 0, + Dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 0, }, "P9_42": { - dir: "/sys/devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 0, + Dir: "/sys/devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 0, }, } -var bbbAnalogPinMap = map[string]analogPinDefinition{ - "P9_39": {path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", r: true, w: false, bufLen: 1024}, - "P9_40": {path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", r: true, w: false, bufLen: 1024}, - "P9_37": {path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", r: true, w: false, bufLen: 1024}, - "P9_38": {path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", r: true, w: false, bufLen: 1024}, - "P9_33": {path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", r: true, w: false, bufLen: 1024}, - "P9_36": {path: "/sys/bus/iio/devices/iio:device0/in_voltage5_raw", r: true, w: false, bufLen: 1024}, - "P9_35": {path: "/sys/bus/iio/devices/iio:device0/in_voltage6_raw", r: true, w: false, bufLen: 1024}, +var bbbAnalogPinMap = adaptors.AnalogPinDefinitions{ + "P9_39": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", R: true, W: false, BufLen: 1024}, + "P9_40": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", R: true, W: false, BufLen: 1024}, + "P9_37": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", R: true, W: false, BufLen: 1024}, + "P9_38": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", R: true, W: false, BufLen: 1024}, + "P9_33": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", R: true, W: false, BufLen: 1024}, + "P9_36": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage5_raw", R: true, W: false, BufLen: 1024}, + "P9_35": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage6_raw", R: true, W: false, BufLen: 1024}, } diff --git a/platforms/beaglebone/pocketbeagle_adaptor.go b/platforms/beaglebone/pocketbeagle_adaptor.go index 907b37e40..6ce76e265 100644 --- a/platforms/beaglebone/pocketbeagle_adaptor.go +++ b/platforms/beaglebone/pocketbeagle_adaptor.go @@ -2,6 +2,7 @@ package beaglebone import ( "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/platforms/adaptors" ) // PocketBeagleAdaptor is the Gobot Adaptor for the PocketBeagle @@ -22,9 +23,13 @@ type PocketBeagleAdaptor struct { func NewPocketBeagleAdaptor(opts ...interface{}) *PocketBeagleAdaptor { a := NewAdaptor(opts...) a.SetName(gobot.DefaultName("PocketBeagle")) + + analogPinTranslator := adaptors.NewAnalogPinTranslator(a.sys, pocketBeagleAnalogPinMap) + pwmPinTranslator := adaptors.NewPWMPinTranslator(a.sys, pocketBeaglePwmPinMap) + + a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(a.sys, analogPinTranslator.Translate) a.pinMap = pocketBeaglePinMap - a.pwmPinMap = pocketBeaglePwmPinMap - a.analogPinMap = pocketBeagleAnalogPinMap + a.pwmPinTranslate = pwmPinTranslator.Translate return &PocketBeagleAdaptor{ Adaptor: a, diff --git a/platforms/beaglebone/pocketbeagle_adaptor_test.go b/platforms/beaglebone/pocketbeagle_adaptor_test.go new file mode 100644 index 000000000..edcc1af62 --- /dev/null +++ b/platforms/beaglebone/pocketbeagle_adaptor_test.go @@ -0,0 +1,31 @@ +package beaglebone + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "gobot.io/x/gobot/v2/platforms/adaptors" +) + +func TestNewPocketBeagleAdaptor(t *testing.T) { + // arrange & act + a := NewPocketBeagleAdaptor() + // assert + assert.IsType(t, &PocketBeagleAdaptor{}, a) + assert.True(t, strings.HasPrefix(a.Name(), "PocketBeagle")) + assert.NotNil(t, a.sys) + assert.NotNil(t, a.AnalogPinsAdaptor) + assert.NotNil(t, a.pwmPinTranslate) + assert.Equal(t, pocketBeaglePinMap, a.pinMap) + assert.Equal(t, "/sys/class/leds/beaglebone:green:", a.usrLed) +} + +func TestNewPocketBeagleAdaptorWithOption(t *testing.T) { + // arrange & act + a := NewPocketBeagleAdaptor(adaptors.WithGpiodAccess()) + // assert + require.NoError(t, a.Connect()) +} diff --git a/platforms/beaglebone/pocketbeagle_pins.go b/platforms/beaglebone/pocketbeagle_pins.go index c94dd42ad..ae9239311 100644 --- a/platforms/beaglebone/pocketbeagle_pins.go +++ b/platforms/beaglebone/pocketbeagle_pins.go @@ -1,5 +1,7 @@ package beaglebone +import "gobot.io/x/gobot/v2/platforms/adaptors" + var pocketBeaglePinMap = map[string]int{ // P1_01 - VIN "P1_02": 87, @@ -76,19 +78,19 @@ var pocketBeaglePinMap = map[string]int{ // P2_36 - AIO7 } -var pocketBeaglePwmPinMap = map[string]pwmPinDefinition{ - "P1_33": {dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 1}, - "P1_36": {dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 0}, +var pocketBeaglePwmPinMap = adaptors.PWMPinDefinitions{ + "P1_33": {Dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 1}, + "P1_36": {Dir: "/sys/devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 0}, - "P2_1": {dir: "/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 0}, - "P2_3": {dir: "/sys/devices/platform/ocp/48304000.epwmss/48304200.pwm/pwm/", dirRegexp: "pwmchip[0-9]+$", channel: 1}, + "P2_1": {Dir: "/sys/devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 0}, + "P2_3": {Dir: "/sys/devices/platform/ocp/48304000.epwmss/48304200.pwm/pwm/", DirRegexp: "pwmchip[0-9]+$", Channel: 1}, } -var pocketBeagleAnalogPinMap = map[string]analogPinDefinition{ - "P1_19": {path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", r: true, w: false, bufLen: 1024}, - "P1_21": {path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", r: true, w: false, bufLen: 1024}, - "P1_23": {path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", r: true, w: false, bufLen: 1024}, - "P1_25": {path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", r: true, w: false, bufLen: 1024}, - "P1_27": {path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", r: true, w: false, bufLen: 1024}, - "P2_36": {path: "/sys/bus/iio/devices/iio:device0/in_voltage7_raw", r: true, w: false, bufLen: 1024}, +var pocketBeagleAnalogPinMap = adaptors.AnalogPinDefinitions{ + "P1_19": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage0_raw", R: true, W: false, BufLen: 1024}, + "P1_21": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", R: true, W: false, BufLen: 1024}, + "P1_23": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage2_raw", R: true, W: false, BufLen: 1024}, + "P1_25": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage3_raw", R: true, W: false, BufLen: 1024}, + "P1_27": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage4_raw", R: true, W: false, BufLen: 1024}, + "P2_36": {Path: "/sys/bus/iio/devices/iio:device0/in_voltage7_raw", R: true, W: false, BufLen: 1024}, } diff --git a/platforms/chip/chip_adaptor.go b/platforms/chip/chip_adaptor.go index cdd3092ee..b412d8282 100644 --- a/platforms/chip/chip_adaptor.go +++ b/platforms/chip/chip_adaptor.go @@ -71,9 +71,12 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } } + // Valid bus numbers are [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1, 2}) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) return a } @@ -154,14 +157,6 @@ func getXIOBase() (int, error) { return baseAddr, nil } -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. - if (busNr < 0) || (busNr > 2) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { if val, ok := a.pinmap[id]; ok { return "", val.pin, nil diff --git a/platforms/chip/chip_adaptor_test.go b/platforms/chip/chip_adaptor_test.go index 61aad0c58..ae61f1166 100644 --- a/platforms/chip/chip_adaptor_test.go +++ b/platforms/chip/chip_adaptor_test.go @@ -190,41 +190,6 @@ func TestI2cFinalizeWithErrors(t *testing.T) { require.ErrorContains(t, err, "close error") } -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_ok": { - busNr: 2, - }, - "number_3_error": { - busNr: 3, - wantErr: fmt.Errorf("Bus number 3 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - func Test_translatePWMPin(t *testing.T) { tests := map[string]struct { usePro bool diff --git a/platforms/dragonboard/dragonboard_adaptor.go b/platforms/dragonboard/dragonboard_adaptor.go index ea1e2d35f..fe33ce93c 100644 --- a/platforms/dragonboard/dragonboard_adaptor.go +++ b/platforms/dragonboard/dragonboard_adaptor.go @@ -56,8 +56,12 @@ func NewAdaptor(opts ...func(adaptors.DigitalPinsOptioner)) *Adaptor { name: gobot.DefaultName("DragonBoard"), sys: sys, } + + // Valid bus numbers are [0,1] which corresponds to /dev/i2c-0 through /dev/i2c-1. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) + c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...) - c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber) + c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) c.pinMap = fixedPins for i := 0; i < 122; i++ { pin := fmt.Sprintf("GPIO_%d", i) @@ -98,14 +102,6 @@ func (c *Adaptor) Finalize() error { return err } -func (c *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [0..1] which corresponds to /dev/i2c-0 through /dev/i2c-1. - if (busNr < 0) || (busNr > 1) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - func (c *Adaptor) translateDigitalPin(id string) (string, int, error) { if line, ok := c.pinMap[id]; ok { return "", line, nil diff --git a/platforms/dragonboard/dragonboard_adaptor_test.go b/platforms/dragonboard/dragonboard_adaptor_test.go index cd8cb6adf..a70cf1961 100644 --- a/platforms/dragonboard/dragonboard_adaptor_test.go +++ b/platforms/dragonboard/dragonboard_adaptor_test.go @@ -1,7 +1,6 @@ package dragonboard import ( - "fmt" "strings" "testing" @@ -102,35 +101,3 @@ func TestI2cFinalizeWithErrors(t *testing.T) { // assert require.ErrorContains(t, err, "close error") } - -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_error": { - busNr: 2, - wantErr: fmt.Errorf("Bus number 2 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/platforms/intel-iot/edison/edison_adaptor_test.go b/platforms/intel-iot/edison/edison_adaptor_test.go index 05bb4bad9..7e132ea2f 100644 --- a/platforms/intel-iot/edison/edison_adaptor_test.go +++ b/platforms/intel-iot/edison/edison_adaptor_test.go @@ -574,7 +574,7 @@ func TestI2cFinalizeWithErrors(t *testing.T) { require.ErrorContains(t, err, "close error") } -func Test_validateI2cBusNumber(t *testing.T) { +func Test_validateAndSetupI2cBusNumber(t *testing.T) { tests := map[string]struct { board string busNr int diff --git a/platforms/intel-iot/joule/joule_adaptor.go b/platforms/intel-iot/joule/joule_adaptor.go index b20c01eec..c6086071b 100644 --- a/platforms/intel-iot/joule/joule_adaptor.go +++ b/platforms/intel-iot/joule/joule_adaptor.go @@ -56,9 +56,12 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } } + // Valid bus numbers are [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1, 2}) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) return a } @@ -101,14 +104,6 @@ func (a *Adaptor) Finalize() error { return err } -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. - if (busNr < 0) || (busNr > 2) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { if val, ok := sysfsPinMap[id]; ok { return "", val.pin, nil diff --git a/platforms/intel-iot/joule/joule_adaptor_test.go b/platforms/intel-iot/joule/joule_adaptor_test.go index fa159a4ab..2d6a73b72 100644 --- a/platforms/intel-iot/joule/joule_adaptor_test.go +++ b/platforms/intel-iot/joule/joule_adaptor_test.go @@ -1,7 +1,6 @@ package joule import ( - "fmt" "strings" "testing" @@ -189,38 +188,3 @@ func TestI2cFinalizeWithErrors(t *testing.T) { // assert require.ErrorContains(t, err, "close error") } - -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_ok": { - busNr: 2, - }, - "number_3_error": { - busNr: 3, - wantErr: fmt.Errorf("Bus number 3 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/platforms/jetson/jetson_adaptor.go b/platforms/jetson/jetson_adaptor.go index 748babb15..78b508f4b 100644 --- a/platforms/jetson/jetson_adaptor.go +++ b/platforms/jetson/jetson_adaptor.go @@ -69,11 +69,17 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } } + // Valid bus numbers are [0,1] which corresponds to /dev/i2c-0 through /dev/i2c-1. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) + // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. + // x is the chip number <255 + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber) - a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, a.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, - defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) return a } @@ -134,23 +140,6 @@ func (a *Adaptor) Finalize() error { return err } -func (a *Adaptor) validateSpiBusNumber(busNr int) error { - // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. - // x is the chip number <255 - if (busNr < 0) || (busNr > 1) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [0..1] which corresponds to /dev/i2c-0 through /dev/i2c-1. - if (busNr < 0) || (busNr > 1) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { if line, ok := gpioPins[id]; ok { return "", line, nil diff --git a/platforms/jetson/jetson_adaptor_test.go b/platforms/jetson/jetson_adaptor_test.go index 7f3599b06..9b2fd47ba 100644 --- a/platforms/jetson/jetson_adaptor_test.go +++ b/platforms/jetson/jetson_adaptor_test.go @@ -1,7 +1,6 @@ package jetson import ( - "fmt" "runtime" "strconv" "strings" @@ -172,70 +171,6 @@ func TestI2cFinalizeWithErrors(t *testing.T) { require.ErrorContains(t, err, "close error") } -func Test_validateSpiBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_error": { - busNr: 2, - wantErr: fmt.Errorf("Bus number 2 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateSpiBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_not_ok": { - busNr: 2, - wantErr: fmt.Errorf("Bus number 2 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - func Test_translatePWMPin(t *testing.T) { tests := map[string]struct { pin string diff --git a/platforms/nanopi/nanopi_adaptor.go b/platforms/nanopi/nanopi_adaptor.go index 42d7cbd3c..e63e0fa94 100644 --- a/platforms/nanopi/nanopi_adaptor.go +++ b/platforms/nanopi/nanopi_adaptor.go @@ -21,36 +21,11 @@ const ( defaultSpiMaxSpeed = 500000 ) -type cdevPin struct { - chip uint8 - line uint8 -} - -type gpioPinDefinition struct { - sysfs int - cdev cdevPin -} - -type analogPinDefinition struct { - path string - r bool // readable - w bool // writable - bufLen uint16 -} - -type pwmPinDefinition struct { - channel int - dir string - dirRegexp string -} - // Adaptor represents a Gobot Adaptor for the FriendlyARM NanoPi Boards type Adaptor struct { - name string - sys *system.Accesser - gpioPinMap map[string]gpioPinDefinition - pwmPinMap map[string]pwmPinDefinition - mutex sync.Mutex + name string + sys *system.Accesser + mutex sync.Mutex *adaptors.AnalogPinsAdaptor *adaptors.DigitalPinsAdaptor *adaptors.PWMPinsAdaptor @@ -74,10 +49,8 @@ type Adaptor struct { func NewNeoAdaptor(opts ...interface{}) *Adaptor { sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) a := &Adaptor{ - name: gobot.DefaultName("NanoPi NEO Board"), - sys: sys, - gpioPinMap: neoGpioPins, - pwmPinMap: neoPwmPins, + name: gobot.DefaultName("NanoPi NEO Board"), + sys: sys, } var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) @@ -93,12 +66,21 @@ func NewNeoAdaptor(opts ...interface{}) *Adaptor { } } - a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, a.translateAnalogPin) - a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) - a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber) - a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, a.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, - defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + analogPinTranslator := adaptors.NewAnalogPinTranslator(sys, analogPinDefinitions) + digitalPinTranslator := adaptors.NewDigitalPinTranslator(sys, neoDigitalPinDefinitions) + pwmPinTranslator := adaptors.NewPWMPinTranslator(sys, neoPWMPinDefinitions) + // Valid bus numbers are [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1, 2}) + // Valid bus numbers are [0] which corresponds to /dev/spidev0.x + // x is the chip number <255 + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{0}) + + a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, analogPinTranslator.Translate) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, digitalPinTranslator.Translate, digitalPinsOpts...) + a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, pwmPinTranslator.Translate, pwmPinsOpts...) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) return a } @@ -155,82 +137,3 @@ func (a *Adaptor) Finalize() error { } return err } - -func (a *Adaptor) validateSpiBusNumber(busNr int) error { - // Valid bus numbers are [0] which corresponds to /dev/spidev0.x - // x is the chip number <255 - if busNr != 0 { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [0..2] which corresponds to /dev/i2c-0 through /dev/i2c-2. - if (busNr < 0) || (busNr > 2) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) translateAnalogPin(id string) (string, bool, bool, uint16, error) { - pinInfo, ok := analogPinDefinitions[id] - if !ok { - return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for a analog pin", id) - } - - path := pinInfo.path - info, err := a.sys.Stat(path) - if err != nil { - return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path) - } - if info.IsDir() { - return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path) - } - - return path, pinInfo.r, pinInfo.w, pinInfo.bufLen, nil -} - -func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { - pindef, ok := a.gpioPinMap[id] - if !ok { - return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) - } - if a.sys.IsSysfsDigitalPinAccess() { - return "", pindef.sysfs, nil - } - chip := fmt.Sprintf("gpiochip%d", pindef.cdev.chip) - line := int(pindef.cdev.line) - return chip, line, nil -} - -func (a *Adaptor) translatePWMPin(id string) (string, int, error) { - pinInfo, ok := a.pwmPinMap[id] - if !ok { - return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) - } - path, err := pinInfo.findPWMDir(a.sys) - if err != nil { - return "", -1, err - } - return path, pinInfo.channel, nil -} - -func (p pwmPinDefinition) findPWMDir(sys *system.Accesser) (string, error) { - items, _ := sys.Find(p.dir, p.dirRegexp) - if len(items) == 0 { - return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'. See README.md for activation", - p.dirRegexp, p.dir) - } - - dir := items[0] - info, err := sys.Stat(dir) - if err != nil { - return "", fmt.Errorf("Error (%v) on access '%s'", err, dir) - } - if !info.IsDir() { - return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir) - } - - return dir, nil -} diff --git a/platforms/nanopi/nanopi_adaptor_test.go b/platforms/nanopi/nanopi_adaptor_test.go index 79f46018f..7b72f9e4a 100644 --- a/platforms/nanopi/nanopi_adaptor_test.go +++ b/platforms/nanopi/nanopi_adaptor_test.go @@ -117,7 +117,7 @@ func TestAnalog(t *testing.T) { assert.Equal(t, 567, got) _, err = a.AnalogRead("thermal_zone10") - require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for a analog pin") + require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for an analog pin") fs.WithReadError = true _, err = a.AnalogRead("thermal_zone0") @@ -282,192 +282,3 @@ func TestI2cFinalizeWithErrors(t *testing.T) { // assert require.ErrorContains(t, err, "close error") } - -func Test_validateSpiBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_error": { - busNr: 1, - wantErr: fmt.Errorf("Bus number 1 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewNeoAdaptor() - // act - err := a.validateSpiBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_ok": { - busNr: 2, - }, - "number_3_error": { - busNr: 3, - wantErr: fmt.Errorf("Bus number 3 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewNeoAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_translateDigitalPin(t *testing.T) { - tests := map[string]struct { - access string - pin string - wantChip string - wantLine int - wantErr error - }{ - "cdev_ok": { - access: "cdev", - pin: "7", - wantChip: "gpiochip0", - wantLine: 203, - }, - "sysfs_ok": { - access: "sysfs", - pin: "7", - wantChip: "", - wantLine: 203, - }, - "unknown_pin": { - pin: "99", - wantChip: "", - wantLine: -1, - wantErr: fmt.Errorf("'99' is not a valid id for a digital pin"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewNeoAdaptor() - a.sys.UseDigitalPinAccessWithMockFs(tc.access, []string{}) - // act - chip, line, err := a.translateDigitalPin(tc.pin) - // assert - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantChip, chip) - assert.Equal(t, tc.wantLine, line) - }) - } -} - -func Test_translateAnalogPin(t *testing.T) { - mockedPaths := []string{ - "/sys/class/thermal/thermal_zone0/temp", - "/sys/class/thermal/thermal_zone1/temp", - } - tests := map[string]struct { - id string - wantPath string - wantReadable bool - wantBufLen uint16 - wantErr string - }{ - "translate_thermal_zone0": { - id: "thermal_zone0", - wantPath: "/sys/class/thermal/thermal_zone0/temp", - wantReadable: true, - wantBufLen: 7, - }, - "unknown_id": { - id: "thermal_zone1", - wantErr: "'thermal_zone1' is not a valid id for a analog pin", - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths) - // act - path, r, w, buf, err := a.translateAnalogPin(tc.id) - // assert - if tc.wantErr != "" { - require.EqualError(t, err, tc.wantErr) - } else { - require.NoError(t, err) - } - assert.Equal(t, tc.wantPath, path) - assert.Equal(t, tc.wantReadable, r) - assert.False(t, w) - assert.Equal(t, tc.wantBufLen, buf) - }) - } -} - -func Test_translatePWMPin(t *testing.T) { - basePaths := []string{"/sys/devices/platform/soc/1c21400.pwm/pwm/"} - tests := map[string]struct { - pin string - chip string - wantDir string - wantChannel int - wantErr error - }{ - "33_chip0": { - pin: "PWM", - chip: "pwmchip0", - wantDir: "/sys/devices/platform/soc/1c21400.pwm/pwm/pwmchip0", - wantChannel: 0, - }, - "invalid_pin": { - pin: "7", - wantDir: "", - wantChannel: -1, - wantErr: fmt.Errorf("'7' is not a valid id for a PWM pin"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - mockedPaths := []string{} - for _, base := range basePaths { - mockedPaths = append(mockedPaths, base+tc.chip+"/") - } - a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths) - // act - dir, channel, err := a.translatePWMPin(tc.pin) - // assert - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantDir, dir) - assert.Equal(t, tc.wantChannel, channel) - }) - } -} diff --git a/platforms/nanopi/nanopineo_pin_map.go b/platforms/nanopi/nanopineo_pin_map.go index 349923666..cf9398118 100644 --- a/platforms/nanopi/nanopineo_pin_map.go +++ b/platforms/nanopi/nanopineo_pin_map.go @@ -1,30 +1,32 @@ package nanopi +import "gobot.io/x/gobot/v2/platforms/adaptors" + // pin definition for NanoPi NEO // pins: A=0+Nr, C=64+Nr, G=192+Nr -var neoGpioPins = map[string]gpioPinDefinition{ - "11": {sysfs: 0, cdev: cdevPin{chip: 0, line: 0}}, // UART2_TX/GPIOA0 - "22": {sysfs: 1, cdev: cdevPin{chip: 0, line: 1}}, // UART2_RX/GPIOA1 - "13": {sysfs: 2, cdev: cdevPin{chip: 0, line: 2}}, // UART2_RTS/GPIOA2 - "15": {sysfs: 3, cdev: cdevPin{chip: 0, line: 3}}, // UART2_CTS/GPIOA3 - "12": {sysfs: 6, cdev: cdevPin{chip: 0, line: 6}}, // GPIOA6 - "19": {sysfs: 64, cdev: cdevPin{chip: 0, line: 64}}, // SPI0_SDO/GPIOC0 - "21": {sysfs: 65, cdev: cdevPin{chip: 0, line: 65}}, // SPI0_SDI/GPIOC1 - "23": {sysfs: 66, cdev: cdevPin{chip: 0, line: 66}}, // SPI0_CLK/GPIOC2 - "24": {sysfs: 67, cdev: cdevPin{chip: 0, line: 67}}, // SPI0_CS/GPIOC3 - "8": {sysfs: 198, cdev: cdevPin{chip: 0, line: 198}}, // UART1_TX/GPIOG6 - "10": {sysfs: 199, cdev: cdevPin{chip: 0, line: 199}}, // UART1_RX/GPIOG7 - "16": {sysfs: 200, cdev: cdevPin{chip: 0, line: 200}}, // UART1_RTS/GPIOG8 - "18": {sysfs: 201, cdev: cdevPin{chip: 0, line: 201}}, // UART1_CTS/GPIOG9 - "7": {sysfs: 203, cdev: cdevPin{chip: 0, line: 203}}, // GPIOG11 +var neoDigitalPinDefinitions = adaptors.DigitalPinDefinitions{ + "11": {Sysfs: 0, Cdev: adaptors.CdevPin{Chip: 0, Line: 0}}, // UART2_TX/GPIOA0 + "22": {Sysfs: 1, Cdev: adaptors.CdevPin{Chip: 0, Line: 1}}, // UART2_RX/GPIOA1 + "13": {Sysfs: 2, Cdev: adaptors.CdevPin{Chip: 0, Line: 2}}, // UART2_RTS/GPIOA2 + "15": {Sysfs: 3, Cdev: adaptors.CdevPin{Chip: 0, Line: 3}}, // UART2_CTS/GPIOA3 + "12": {Sysfs: 6, Cdev: adaptors.CdevPin{Chip: 0, Line: 6}}, // GPIOA6 + "19": {Sysfs: 64, Cdev: adaptors.CdevPin{Chip: 0, Line: 64}}, // SPI0_SDO/GPIOC0 + "21": {Sysfs: 65, Cdev: adaptors.CdevPin{Chip: 0, Line: 65}}, // SPI0_SDI/GPIOC1 + "23": {Sysfs: 66, Cdev: adaptors.CdevPin{Chip: 0, Line: 66}}, // SPI0_CLK/GPIOC2 + "24": {Sysfs: 67, Cdev: adaptors.CdevPin{Chip: 0, Line: 67}}, // SPI0_CS/GPIOC3 + "8": {Sysfs: 198, Cdev: adaptors.CdevPin{Chip: 0, Line: 198}}, // UART1_TX/GPIOG6 + "10": {Sysfs: 199, Cdev: adaptors.CdevPin{Chip: 0, Line: 199}}, // UART1_RX/GPIOG7 + "16": {Sysfs: 200, Cdev: adaptors.CdevPin{Chip: 0, Line: 200}}, // UART1_RTS/GPIOG8 + "18": {Sysfs: 201, Cdev: adaptors.CdevPin{Chip: 0, Line: 201}}, // UART1_CTS/GPIOG9 + "7": {Sysfs: 203, Cdev: adaptors.CdevPin{Chip: 0, Line: 203}}, // GPIOG11 } -var neoPwmPins = map[string]pwmPinDefinition{ +var neoPWMPinDefinitions = adaptors.PWMPinDefinitions{ // UART_RXD0, GPIOA5, PWM - "PWM": {dir: "/sys/devices/platform/soc/1c21400.pwm/pwm/", dirRegexp: "pwmchip[0]$", channel: 0}, + "PWM": {Dir: "/sys/devices/platform/soc/1c21400.pwm/pwm/", DirRegexp: "pwmchip[0]$", Channel: 0}, } -var analogPinDefinitions = map[string]analogPinDefinition{ +var analogPinDefinitions = adaptors.AnalogPinDefinitions{ // +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius - "thermal_zone0": {path: "/sys/class/thermal/thermal_zone0/temp", r: true, w: false, bufLen: 7}, + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, } diff --git a/platforms/raspi/raspi_adaptor.go b/platforms/raspi/raspi_adaptor.go index b8ffa95da..a41448f02 100644 --- a/platforms/raspi/raspi_adaptor.go +++ b/platforms/raspi/raspi_adaptor.go @@ -23,13 +23,6 @@ const ( defaultSpiMaxSpeed = 500000 ) -type analogPinDefinition struct { - path string - r bool // readable - w bool // writable - bufLen uint16 -} - // Adaptor is the Gobot Adaptor for the Raspberry Pi type Adaptor struct { name string @@ -74,12 +67,19 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } } - a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, a.translateAnalogPin) + analogPinTranslator := adaptors.NewAnalogPinTranslator(sys, analogPinDefinitions) + // Valid bus numbers are [0,1] which corresponds to /dev/i2c-0 through /dev/i2c-1. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) + // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. + // x is the chip number <255 + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) + + a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, analogPinTranslator.Translate) a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.getPinTranslatorFunction(), digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.getPinTranslatorFunction(), pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, 1) - a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, a.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, - defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, 1) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) return a } @@ -158,41 +158,6 @@ func (a *Adaptor) DefaultI2cBus() int { return 0 } -func (a *Adaptor) validateSpiBusNumber(busNr int) error { - // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. - // x is the chip number <255 - if (busNr < 0) || (busNr > 1) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [0..1] which corresponds to /dev/i2c-0 through /dev/i2c-1. - if (busNr < 0) || (busNr > 1) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) translateAnalogPin(id string) (string, bool, bool, uint16, error) { - pinInfo, ok := analogPinDefinitions[id] - if !ok { - return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for a analog pin", id) - } - - path := pinInfo.path - info, err := a.sys.Stat(path) - if err != nil { - return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path) - } - if info.IsDir() { - return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path) - } - - return path, pinInfo.r, pinInfo.w, pinInfo.bufLen, nil -} - // getPinTranslatorFunction returns a function to be able to translate GPIO and PWM pins. // This means for pi-blaster usage, each pin can be used and therefore the pin is given as number, like a GPIO pin. // For sysfs-PWM usage, the pin will be given as "pwm0" or "pwm1", because the real pin number depends on the user diff --git a/platforms/raspi/raspi_adaptor_test.go b/platforms/raspi/raspi_adaptor_test.go index a463963a6..cd63ab4c9 100644 --- a/platforms/raspi/raspi_adaptor_test.go +++ b/platforms/raspi/raspi_adaptor_test.go @@ -155,7 +155,7 @@ func TestAnalog(t *testing.T) { assert.Equal(t, 567, got) _, err = a.AnalogRead("thermal_zone10") - require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for a analog pin") + require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for an analog pin") fs.WithReadError = true _, err = a.AnalogRead("thermal_zone0") @@ -345,113 +345,6 @@ func TestI2cFinalizeWithErrors(t *testing.T) { require.ErrorContains(t, err, "close error") } -func Test_validateSpiBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_error": { - busNr: 2, - wantErr: fmt.Errorf("Bus number 2 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateSpiBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_not_ok": { - busNr: 2, - wantErr: fmt.Errorf("Bus number 2 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_translateAnalogPin(t *testing.T) { - mockedPaths := []string{ - "/sys/class/thermal/thermal_zone0/temp", - "/sys/class/thermal/thermal_zone1/temp", - } - tests := map[string]struct { - id string - wantPath string - wantReadable bool - wantBufLen uint16 - wantErr string - }{ - "translate_thermal_zone0": { - id: "thermal_zone0", - wantPath: "/sys/class/thermal/thermal_zone0/temp", - wantReadable: true, - wantBufLen: 7, - }, - "unknown_id": { - id: "thermal_zone1", - wantErr: "'thermal_zone1' is not a valid id for a analog pin", - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths) - // act - path, r, w, buf, err := a.translateAnalogPin(tc.id) - // assert - if tc.wantErr != "" { - require.EqualError(t, err, tc.wantErr) - } else { - require.NoError(t, err) - } - assert.Equal(t, tc.wantPath, path) - assert.Equal(t, tc.wantReadable, r) - assert.False(t, w) - assert.Equal(t, tc.wantBufLen, buf) - }) - } -} - func Test_getPinTranslatorFunction(t *testing.T) { tests := map[string]struct { id string diff --git a/platforms/raspi/raspi_pin_map.go b/platforms/raspi/raspi_pin_map.go index 6a7bdf7ea..53d5147d2 100644 --- a/platforms/raspi/raspi_pin_map.go +++ b/platforms/raspi/raspi_pin_map.go @@ -1,5 +1,7 @@ package raspi +import "gobot.io/x/gobot/v2/platforms/adaptors" + var pins = map[string]map[string]int{ "3": { "1": 0, @@ -93,7 +95,7 @@ var pins = map[string]map[string]int{ }, } -var analogPinDefinitions = map[string]analogPinDefinition{ +var analogPinDefinitions = adaptors.AnalogPinDefinitions{ // +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius - "thermal_zone0": {path: "/sys/class/thermal/thermal_zone0/temp", r: true, w: false, bufLen: 7}, + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, } diff --git a/platforms/rockpi/rockpi_adaptor.go b/platforms/rockpi/rockpi_adaptor.go index 2778fe091..4070a4fdf 100644 --- a/platforms/rockpi/rockpi_adaptor.go +++ b/platforms/rockpi/rockpi_adaptor.go @@ -2,7 +2,6 @@ package rockpi import ( "errors" - "fmt" "sync" multierror "github.com/hashicorp/go-multierror" @@ -46,88 +45,79 @@ type Adaptor struct { // adaptors.WithGpiosActiveLow(pin's): invert the pin behavior func NewAdaptor(opts ...func(adaptors.DigitalPinsOptioner)) *Adaptor { sys := system.NewAccesser() - c := &Adaptor{ + a := &Adaptor{ name: gobot.DefaultName("RockPi"), sys: sys, } - c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.getPinTranslatorFunction(), opts...) - c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, defaultI2cBusNumber) - c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, - defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) - return c + + // The RockPi4 has 3 I2C buses: 2, 6, 7. See https://wiki.radxa.com/Rock4/hardware/gpio + // This could change in the future with other revisions! + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{2, 6, 7}) + // The RockPi4 has 2 SPI buses: 1, 2. See https://wiki.radxa.com/Rock4/hardware/gpio + // This could change in the future with other revisions! + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{1, 2}) + + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.getPinTranslatorFunction(), opts...) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + + return a } // Name returns the adaptors name -func (c *Adaptor) Name() string { - c.mutex.Lock() - defer c.mutex.Unlock() +func (a *Adaptor) Name() string { + a.mutex.Lock() + defer a.mutex.Unlock() - return c.name + return a.name } // SetName sets the adaptors name -func (c *Adaptor) SetName(n string) { - c.mutex.Lock() - defer c.mutex.Unlock() +func (a *Adaptor) SetName(n string) { + a.mutex.Lock() + defer a.mutex.Unlock() - c.name = n + a.name = n } // Connect create new connection to board and pins. -func (c *Adaptor) Connect() error { - c.mutex.Lock() - defer c.mutex.Unlock() +func (a *Adaptor) Connect() error { + a.mutex.Lock() + defer a.mutex.Unlock() - if err := c.SpiBusAdaptor.Connect(); err != nil { + if err := a.SpiBusAdaptor.Connect(); err != nil { return err } - if err := c.I2cBusAdaptor.Connect(); err != nil { + if err := a.I2cBusAdaptor.Connect(); err != nil { return err } - return c.DigitalPinsAdaptor.Connect() + return a.DigitalPinsAdaptor.Connect() } // Finalize closes connection to board and pins -func (c *Adaptor) Finalize() error { - c.mutex.Lock() - defer c.mutex.Unlock() +func (a *Adaptor) Finalize() error { + a.mutex.Lock() + defer a.mutex.Unlock() - err := c.DigitalPinsAdaptor.Finalize() + err := a.DigitalPinsAdaptor.Finalize() - if e := c.I2cBusAdaptor.Finalize(); e != nil { + if e := a.I2cBusAdaptor.Finalize(); e != nil { err = multierror.Append(err, e) } - if e := c.SpiBusAdaptor.Finalize(); e != nil { + if e := a.SpiBusAdaptor.Finalize(); e != nil { err = multierror.Append(err, e) } return err } -// The RockPi4 has 2 SPI buses: 1, 2. See https://wiki.radxa.com/Rock4/hardware/gpio -// This could change in the future with other revisions! -func (c *Adaptor) validateSpiBusNumber(busNr int) error { - if busNr != 1 && busNr != 2 { - return fmt.Errorf("SPI Bus number %d invalid: only 1, 2 supported by current Rockchip.", busNr) - } - return nil -} - -// The RockPi4 has 3 I2C buses: 2, 6, 7. See https://wiki.radxa.com/Rock4/hardware/gpio -// This could change in the future with other revisions! -func (c *Adaptor) validateI2cBusNumber(busNr int) error { - if busNr != 2 && busNr != 6 && busNr != 7 { - return fmt.Errorf("I2C Bus number %d invalid: only 2, 6, 7 supported by current Rockchip.", busNr) - } - return nil -} - -func (c *Adaptor) getPinTranslatorFunction() func(string) (string, int, error) { +func (a *Adaptor) getPinTranslatorFunction() func(string) (string, int, error) { return func(pin string) (string, int, error) { var line int - if val, ok := pins[pin][c.readRevision()]; ok { + if val, ok := pins[pin][a.readRevision()]; ok { line = val } else if val, ok := pins[pin]["*"]; ok { line = val @@ -138,22 +128,22 @@ func (c *Adaptor) getPinTranslatorFunction() func(string) (string, int, error) { } } -func (c *Adaptor) readRevision() string { - if c.revision == "" { - content, err := c.sys.ReadFile(procDeviceTreeModel) +func (a *Adaptor) readRevision() string { + if a.revision == "" { + content, err := a.sys.ReadFile(procDeviceTreeModel) if err != nil { - return c.revision + return a.revision } model := string(content) switch model { case "Radxa ROCK 4": - c.revision = "4" + a.revision = "4" case "Radxa ROCK 4C+": - c.revision = "4C+" + a.revision = "4C+" default: - c.revision = "4" + a.revision = "4" } } - return c.revision + return a.revision } diff --git a/platforms/rockpi/rockpi_adaptor_test.go b/platforms/rockpi/rockpi_adaptor_test.go index 617cee981..9534094da 100644 --- a/platforms/rockpi/rockpi_adaptor_test.go +++ b/platforms/rockpi/rockpi_adaptor_test.go @@ -70,70 +70,3 @@ func Test_getPinTranslatorFunction(t *testing.T) { }) } } - -func Test_validateSpiBusNumber(t *testing.T) { - cases := map[string]struct { - busNr int - expectedErr error - }{ - "number_1_ok": { - busNr: 2, - }, - "number_2_ok": { - busNr: 2, - }, - "number_0_not_ok": { - busNr: 0, - expectedErr: fmt.Errorf("SPI Bus number 0 invalid: only 1, 2 supported by current Rockchip."), - }, - "number_6_not_ok": { - busNr: 6, - expectedErr: fmt.Errorf("SPI Bus number 6 invalid: only 1, 2 supported by current Rockchip."), - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateSpiBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.expectedErr, err) - }) - } -} - -func Test_validateI2cBusNumber(t *testing.T) { - cases := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("I2C Bus number -1 invalid: only 2, 6, 7 supported by current Rockchip."), - }, - "number_2_ok": { - busNr: 2, - }, - "number_6_ok": { - busNr: 6, - }, - "number_7_ok": { - busNr: 7, - }, - "number_1_not_ok": { - busNr: 1, - wantErr: fmt.Errorf("I2C Bus number 1 invalid: only 2, 6, 7 supported by current Rockchip."), - }, - } - for name, tc := range cases { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} diff --git a/platforms/tinkerboard/README.md b/platforms/tinkerboard/README.md index 02c3dd6eb..64d337260 100644 --- a/platforms/tinkerboard/README.md +++ b/platforms/tinkerboard/README.md @@ -233,7 +233,7 @@ lrwxrwxrwx 1 root root 0 Apr 24 14:17 subsystem -> ../../../../../class/pwm #### Creating pwm0 -`echo 0 > /sys/class/pwm/pwmchip2/enable` +`echo 0 > /sys/class/pwm/pwmchip2/export` investigate result: diff --git a/platforms/tinkerboard/adaptor.go b/platforms/tinkerboard/adaptor.go index e7a373c11..5fde72c42 100644 --- a/platforms/tinkerboard/adaptor.go +++ b/platforms/tinkerboard/adaptor.go @@ -21,29 +21,6 @@ const ( defaultSpiMaxSpeed = 500000 ) -type cdevPin struct { - chip uint8 - line uint8 -} - -type gpioPinDefinition struct { - sysfs int - cdev cdevPin -} - -type analogPinDefinition struct { - path string - r bool // readable - w bool // writable - bufLen uint16 -} - -type pwmPinDefinition struct { - dir string - dirRegexp string - channel int -} - // Adaptor represents a Gobot Adaptor for the ASUS Tinker Board type Adaptor struct { name string @@ -91,13 +68,24 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } } - a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, a.translateAnalogPin) - a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) - a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber) - a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, a.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, - defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + analogPinTranslator := adaptors.NewAnalogPinTranslator(sys, analogPinDefinitions) + digitalPinTranslator := adaptors.NewDigitalPinTranslator(sys, gpioPinDefinitions) + pwmPinTranslator := adaptors.NewPWMPinTranslator(sys, pwmPinDefinitions) + // Valid bus numbers are [0..4] which corresponds to /dev/i2c-0 through /dev/i2c-4. + // We don't support "/dev/i2c-6 DesignWare HDMI". + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1, 2, 3, 4}) + // Valid bus numbers are [0,2] which corresponds to /dev/spidev0.x, /dev/spidev2.x + // x is the chip number <255 + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 2}) + + a.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, analogPinTranslator.Translate) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, digitalPinTranslator.Translate, digitalPinsOpts...) + a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, pwmPinTranslator.Translate, pwmPinsOpts...) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) a.OneWireBusAdaptor = adaptors.NewOneWireBusAdaptor(sys) + return a } @@ -160,85 +148,6 @@ func (a *Adaptor) Finalize() error { if e := a.OneWireBusAdaptor.Finalize(); e != nil { err = multierror.Append(err, e) } - return err -} - -func (a *Adaptor) validateSpiBusNumber(busNr int) error { - // Valid bus numbers are [0,2] which corresponds to /dev/spidev0.x, /dev/spidev2.x - // x is the chip number <255 - if (busNr != 0) && (busNr != 2) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [0..4] which corresponds to /dev/i2c-0 through /dev/i2c-4. - // We don't support "/dev/i2c-6 DesignWare HDMI". - if (busNr < 0) || (busNr > 4) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) translateAnalogPin(id string) (string, bool, bool, uint16, error) { - pinInfo, ok := analogPinDefinitions[id] - if !ok { - return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for a analog pin", id) - } - - path := pinInfo.path - info, err := a.sys.Stat(path) - if err != nil { - return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path) - } - if info.IsDir() { - return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path) - } - - return path, pinInfo.r, pinInfo.w, pinInfo.bufLen, nil -} - -func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { - pindef, ok := gpioPinDefinitions[id] - if !ok { - return "", -1, fmt.Errorf("'%s' is not a valid id for a digital pin", id) - } - if a.sys.IsSysfsDigitalPinAccess() { - return "", pindef.sysfs, nil - } - chip := fmt.Sprintf("gpiochip%d", pindef.cdev.chip) - line := int(pindef.cdev.line) - return chip, line, nil -} - -func (a *Adaptor) translatePWMPin(id string) (string, int, error) { - pinInfo, ok := pwmPinDefinitions[id] - if !ok { - return "", -1, fmt.Errorf("'%s' is not a valid id for a PWM pin", id) - } - path, err := pinInfo.findPWMDir(a.sys) - if err != nil { - return "", -1, err - } - return path, pinInfo.channel, nil -} -func (p pwmPinDefinition) findPWMDir(sys *system.Accesser) (string, error) { - items, _ := sys.Find(p.dir, p.dirRegexp) - if len(items) == 0 { - return "", fmt.Errorf("No path found for PWM directory pattern, '%s' in path '%s'. See README.md for activation", - p.dirRegexp, p.dir) - } - - dir := items[0] - info, err := sys.Stat(dir) - if err != nil { - return "", fmt.Errorf("Error (%v) on access '%s'", err, dir) - } - if !info.IsDir() { - return "", fmt.Errorf("The item '%s' is not a directory, which is not expected", dir) - } - - return dir, nil + return err } diff --git a/platforms/tinkerboard/adaptor_test.go b/platforms/tinkerboard/adaptor_test.go index 77309b971..3b555b604 100644 --- a/platforms/tinkerboard/adaptor_test.go +++ b/platforms/tinkerboard/adaptor_test.go @@ -136,7 +136,7 @@ func TestAnalogRead(t *testing.T) { assert.Equal(t, 567, got) _, err = a.AnalogRead("thermal_zone10") - require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for a analog pin") + require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for an analog pin") fs.WithReadError = true _, err = a.AnalogRead("thermal_zone0") @@ -287,250 +287,3 @@ func TestI2cFinalizeWithErrors(t *testing.T) { // assert require.ErrorContains(t, err, "close error") } - -func Test_validateSpiBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_error": { - busNr: 1, - wantErr: fmt.Errorf("Bus number 1 out of range"), - }, - "number_2_ok": { - busNr: 2, - }, - "number_3_error": { - busNr: 3, - wantErr: fmt.Errorf("Bus number 3 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateSpiBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_ok": { - busNr: 2, - }, - "number_3_ok": { - busNr: 3, - }, - "number_4_ok": { - busNr: 4, - }, - "number_5_error": { - busNr: 5, - wantErr: fmt.Errorf("Bus number 5 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - -func Test_translateDigitalPin(t *testing.T) { - tests := map[string]struct { - access string - pin string - wantChip string - wantLine int - wantErr error - }{ - "cdev_ok": { - access: "cdev", - pin: "7", - wantChip: "gpiochip0", - wantLine: 17, - }, - "sysfs_ok": { - access: "sysfs", - pin: "7", - wantChip: "", - wantLine: 17, - }, - "unknown_pin": { - pin: "99", - wantChip: "", - wantLine: -1, - wantErr: fmt.Errorf("'99' is not a valid id for a digital pin"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - a.sys.UseDigitalPinAccessWithMockFs(tc.access, []string{}) - // act - chip, line, err := a.translateDigitalPin(tc.pin) - // assert - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantChip, chip) - assert.Equal(t, tc.wantLine, line) - }) - } -} - -func Test_translateAnalogPin(t *testing.T) { - mockedPaths := []string{ - "/sys/class/thermal/thermal_zone0/temp", - "/sys/class/thermal/thermal_zone1/temp", - } - tests := map[string]struct { - id string - wantPath string - wantReadable bool - wantBufLen uint16 - wantErr string - }{ - "translate_thermal_zone0": { - id: "thermal_zone0", - wantPath: "/sys/class/thermal/thermal_zone0/temp", - wantReadable: true, - wantBufLen: 7, - }, - "translate_thermal_zone1": { - id: "thermal_zone1", - wantPath: "/sys/class/thermal/thermal_zone1/temp", - wantReadable: true, - wantBufLen: 7, - }, - "unknown_id": { - id: "99", - wantErr: "'99' is not a valid id for a analog pin", - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths) - // act - path, r, w, buf, err := a.translateAnalogPin(tc.id) - // assert - if tc.wantErr != "" { - require.EqualError(t, err, tc.wantErr) - } else { - require.NoError(t, err) - } - assert.Equal(t, tc.wantPath, path) - assert.Equal(t, tc.wantReadable, r) - assert.False(t, w) - assert.Equal(t, tc.wantBufLen, buf) - }) - } -} - -func Test_translatePWMPin(t *testing.T) { - basePaths := []string{ - "/sys/devices/platform/ff680020.pwm/pwm/", - "/sys/devices/platform/ff680030.pwm/pwm/", - } - tests := map[string]struct { - pin string - chip string - wantDir string - wantChannel int - wantErr error - }{ - "32_chip0": { - pin: "32", - chip: "pwmchip0", - wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip0", - wantChannel: 0, - }, - "32_chip1": { - pin: "32", - chip: "pwmchip1", - wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip1", - wantChannel: 0, - }, - "32_chip2": { - pin: "32", - chip: "pwmchip2", - wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip2", - wantChannel: 0, - }, - "32_chip3": { - pin: "32", - chip: "pwmchip3", - wantDir: "/sys/devices/platform/ff680030.pwm/pwm/pwmchip3", - wantChannel: 0, - }, - "33_chip0": { - pin: "33", - chip: "pwmchip0", - wantDir: "/sys/devices/platform/ff680020.pwm/pwm/pwmchip0", - wantChannel: 0, - }, - "33_chip1": { - pin: "33", - chip: "pwmchip1", - wantDir: "/sys/devices/platform/ff680020.pwm/pwm/pwmchip1", - wantChannel: 0, - }, - "33_chip2": { - pin: "33", - chip: "pwmchip2", - wantDir: "/sys/devices/platform/ff680020.pwm/pwm/pwmchip2", - wantChannel: 0, - }, - "invalid_pin": { - pin: "7", - wantDir: "", - wantChannel: -1, - wantErr: fmt.Errorf("'7' is not a valid id for a PWM pin"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - mockedPaths := []string{} - for _, base := range basePaths { - mockedPaths = append(mockedPaths, base+tc.chip+"/") - } - a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths) - // act - dir, channel, err := a.translatePWMPin(tc.pin) - // assert - assert.Equal(t, tc.wantErr, err) - assert.Equal(t, tc.wantDir, dir) - assert.Equal(t, tc.wantChannel, channel) - }) - } -} diff --git a/platforms/tinkerboard/pin_map.go b/platforms/tinkerboard/pin_map.go index 40feeaca1..5f3c6dcb1 100644 --- a/platforms/tinkerboard/pin_map.go +++ b/platforms/tinkerboard/pin_map.go @@ -1,48 +1,50 @@ package tinkerboard +import "gobot.io/x/gobot/v2/platforms/adaptors" + // notes for character device // pins: A=0+Nr, B=8+Nr, C=16+Nr // tested: armbian Linux, OK: work as input and output, IN: work only as input -var gpioPinDefinitions = map[string]gpioPinDefinition{ - "7": {sysfs: 17, cdev: cdevPin{chip: 0, line: 17}}, // GPIO0_C1_CLKOUT - OK - "10": {sysfs: 160, cdev: cdevPin{chip: 5, line: 8}}, // GPIO5_B0_UART1RX - IN, initial 1 - "8": {sysfs: 161, cdev: cdevPin{chip: 5, line: 9}}, // GPIO5_B1_UART1TX - NO, initial 1 - "16": {sysfs: 162, cdev: cdevPin{chip: 5, line: 10}}, // GPIO5_B2_UART1CTSN - NO, initial 0 - "18": {sysfs: 163, cdev: cdevPin{chip: 5, line: 11}}, // GPIO5_B3_UART1RTSN - NO, initial 0 - "11": {sysfs: 164, cdev: cdevPin{chip: 5, line: 12}}, // GPIO5_B4_SPI0CLK_UART4CTSN - NO, initial 0 - "29": {sysfs: 165, cdev: cdevPin{chip: 5, line: 13}}, // GPIO5_B5_SPI0CSN_UART4RTSN - NO, initial 0 - "13": {sysfs: 166, cdev: cdevPin{chip: 5, line: 14}}, // GPIO5_B6_SPI0_TXD_UART4TX - NO, initial 1 - "15": {sysfs: 167, cdev: cdevPin{chip: 5, line: 15}}, // GPIO5_B7_SPI0_RXD_UART4RX - IN, initial 1 - "31": {sysfs: 168, cdev: cdevPin{chip: 5, line: 16}}, // GPIO5_C0_SPI0CSN1 - OK if SPI0 off - "22": {sysfs: 171, cdev: cdevPin{chip: 5, line: 19}}, // GPIO5_C3 - OK - "12": {sysfs: 184, cdev: cdevPin{chip: 6, line: 0}}, // GPIO6_A0_PCM/I2S_CLK - NO, initial 1 - "35": {sysfs: 185, cdev: cdevPin{chip: 6, line: 1}}, // GPIO6_A1_PCM/I2S_FS - NO, initial 0 - "38": {sysfs: 187, cdev: cdevPin{chip: 6, line: 3}}, // GPIO6_A3_PCM/I2S_SDI - IN, initial 1 - "40": {sysfs: 188, cdev: cdevPin{chip: 6, line: 4}}, // GPIO6_A4_PCM/I2S_SDO - NO, initial 0 - "36": {sysfs: 223, cdev: cdevPin{chip: 7, line: 7}}, // GPIO7_A7_UART3RX - IN, initial 1 - "37": {sysfs: 224, cdev: cdevPin{chip: 7, line: 8}}, // GPIO7_B0_UART3TX - NO, initial 1 - "27": {sysfs: 233, cdev: cdevPin{chip: 7, line: 17}}, // GPIO7_C1_I2C4_SDA - OK if I2C4 off - "28": {sysfs: 234, cdev: cdevPin{chip: 7, line: 18}}, // GPIO7_C2_I2C_SCL - OK if I2C4 off - "33": {sysfs: 238, cdev: cdevPin{chip: 7, line: 22}}, // GPIO7_C6_UART2RX_PWM2 - IN, initial 1 - "32": {sysfs: 239, cdev: cdevPin{chip: 7, line: 23}}, // GPIO7_C7_UART2TX_PWM3 - NO, initial 1 - "26": {sysfs: 251, cdev: cdevPin{chip: 8, line: 3}}, // GPIO8_A3_SPI2CSN1 - OK if SPI2 off - "3": {sysfs: 252, cdev: cdevPin{chip: 8, line: 4}}, // GPIO8_A4_I2C1_SDA - OK if I2C1 off - "5": {sysfs: 253, cdev: cdevPin{chip: 8, line: 5}}, // GPIO8_A5_I2C1_SCL - OK if I2C1 off - "23": {sysfs: 254, cdev: cdevPin{chip: 8, line: 6}}, // GPIO8_A6_SPI2CLK - OK if SPI2 off - "24": {sysfs: 255, cdev: cdevPin{chip: 8, line: 7}}, // GPIO8_A7_SPI2CSN0 - OK if SPI2 off - "21": {sysfs: 256, cdev: cdevPin{chip: 8, line: 8}}, // GPIO8_B0_SPI2RXD - OK if SPI2 off - "19": {sysfs: 257, cdev: cdevPin{chip: 8, line: 9}}, // GPIO8_B1_SPI2TXD - OK if SPI2 off +var gpioPinDefinitions = adaptors.DigitalPinDefinitions{ + "7": {Sysfs: 17, Cdev: adaptors.CdevPin{Chip: 0, Line: 17}}, // GPIO0_C1_CLKOUT - OK + "10": {Sysfs: 160, Cdev: adaptors.CdevPin{Chip: 5, Line: 8}}, // GPIO5_B0_UART1RX - IN, initial 1 + "8": {Sysfs: 161, Cdev: adaptors.CdevPin{Chip: 5, Line: 9}}, // GPIO5_B1_UART1TX - NO, initial 1 + "16": {Sysfs: 162, Cdev: adaptors.CdevPin{Chip: 5, Line: 10}}, // GPIO5_B2_UART1CTSN - NO, initial 0 + "18": {Sysfs: 163, Cdev: adaptors.CdevPin{Chip: 5, Line: 11}}, // GPIO5_B3_UART1RTSN - NO, initial 0 + "11": {Sysfs: 164, Cdev: adaptors.CdevPin{Chip: 5, Line: 12}}, // GPIO5_B4_SPI0CLK_UART4CTSN - NO, initial 0 + "29": {Sysfs: 165, Cdev: adaptors.CdevPin{Chip: 5, Line: 13}}, // GPIO5_B5_SPI0CSN_UART4RTSN - NO, initial 0 + "13": {Sysfs: 166, Cdev: adaptors.CdevPin{Chip: 5, Line: 14}}, // GPIO5_B6_SPI0_TXD_UART4TX - NO, initial 1 + "15": {Sysfs: 167, Cdev: adaptors.CdevPin{Chip: 5, Line: 15}}, // GPIO5_B7_SPI0_RXD_UART4RX - IN, initial 1 + "31": {Sysfs: 168, Cdev: adaptors.CdevPin{Chip: 5, Line: 16}}, // GPIO5_C0_SPI0CSN1 - OK if SPI0 off + "22": {Sysfs: 171, Cdev: adaptors.CdevPin{Chip: 5, Line: 19}}, // GPIO5_C3 - OK + "12": {Sysfs: 184, Cdev: adaptors.CdevPin{Chip: 6, Line: 0}}, // GPIO6_A0_PCM/I2S_CLK - NO, initial 1 + "35": {Sysfs: 185, Cdev: adaptors.CdevPin{Chip: 6, Line: 1}}, // GPIO6_A1_PCM/I2S_FS - NO, initial 0 + "38": {Sysfs: 187, Cdev: adaptors.CdevPin{Chip: 6, Line: 3}}, // GPIO6_A3_PCM/I2S_SDI - IN, initial 1 + "40": {Sysfs: 188, Cdev: adaptors.CdevPin{Chip: 6, Line: 4}}, // GPIO6_A4_PCM/I2S_SDO - NO, initial 0 + "36": {Sysfs: 223, Cdev: adaptors.CdevPin{Chip: 7, Line: 7}}, // GPIO7_A7_UART3RX - IN, initial 1 + "37": {Sysfs: 224, Cdev: adaptors.CdevPin{Chip: 7, Line: 8}}, // GPIO7_B0_UART3TX - NO, initial 1 + "27": {Sysfs: 233, Cdev: adaptors.CdevPin{Chip: 7, Line: 17}}, // GPIO7_C1_I2C4_SDA - OK if I2C4 off + "28": {Sysfs: 234, Cdev: adaptors.CdevPin{Chip: 7, Line: 18}}, // GPIO7_C2_I2C_SCL - OK if I2C4 off + "33": {Sysfs: 238, Cdev: adaptors.CdevPin{Chip: 7, Line: 22}}, // GPIO7_C6_UART2RX_PWM2 - IN, initial 1 + "32": {Sysfs: 239, Cdev: adaptors.CdevPin{Chip: 7, Line: 23}}, // GPIO7_C7_UART2TX_PWM3 - NO, initial 1 + "26": {Sysfs: 251, Cdev: adaptors.CdevPin{Chip: 8, Line: 3}}, // GPIO8_A3_SPI2CSN1 - OK if SPI2 off + "3": {Sysfs: 252, Cdev: adaptors.CdevPin{Chip: 8, Line: 4}}, // GPIO8_A4_I2C1_SDA - OK if I2C1 off + "5": {Sysfs: 253, Cdev: adaptors.CdevPin{Chip: 8, Line: 5}}, // GPIO8_A5_I2C1_SCL - OK if I2C1 off + "23": {Sysfs: 254, Cdev: adaptors.CdevPin{Chip: 8, Line: 6}}, // GPIO8_A6_SPI2CLK - OK if SPI2 off + "24": {Sysfs: 255, Cdev: adaptors.CdevPin{Chip: 8, Line: 7}}, // GPIO8_A7_SPI2CSN0 - OK if SPI2 off + "21": {Sysfs: 256, Cdev: adaptors.CdevPin{Chip: 8, Line: 8}}, // GPIO8_B0_SPI2RXD - OK if SPI2 off + "19": {Sysfs: 257, Cdev: adaptors.CdevPin{Chip: 8, Line: 9}}, // GPIO8_B1_SPI2TXD - OK if SPI2 off } -var pwmPinDefinitions = map[string]pwmPinDefinition{ +var pwmPinDefinitions = adaptors.PWMPinDefinitions{ // GPIO7_C6_UART2RX_PWM2 - "33": {dir: "/sys/devices/platform/ff680020.pwm/pwm/", dirRegexp: "pwmchip[0|1|2]$", channel: 0}, + "33": {Dir: "/sys/devices/platform/ff680020.pwm/pwm/", DirRegexp: "pwmchip[0|1|2]$", Channel: 0}, // GPIO7_C7_UART2TX_PWM3 - "32": {dir: "/sys/devices/platform/ff680030.pwm/pwm/", dirRegexp: "pwmchip[0|1|2|3]$", channel: 0}, + "32": {Dir: "/sys/devices/platform/ff680030.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3]$", Channel: 0}, } -var analogPinDefinitions = map[string]analogPinDefinition{ +var analogPinDefinitions = adaptors.AnalogPinDefinitions{ // +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius - "thermal_zone0": {path: "/sys/class/thermal/thermal_zone0/temp", r: true, w: false, bufLen: 7}, - "thermal_zone1": {path: "/sys/class/thermal/thermal_zone1/temp", r: true, w: false, bufLen: 7}, + "thermal_zone0": {Path: "/sys/class/thermal/thermal_zone0/temp", R: true, W: false, BufLen: 7}, + "thermal_zone1": {Path: "/sys/class/thermal/thermal_zone1/temp", R: true, W: false, BufLen: 7}, } diff --git a/platforms/tinkerboard/tinkerboard2/README.md b/platforms/tinkerboard/tinkerboard2/README.md new file mode 100644 index 000000000..5a0fafe58 --- /dev/null +++ b/platforms/tinkerboard/tinkerboard2/README.md @@ -0,0 +1,90 @@ +# Tinker Board 2 + +The ASUS Tinker Board 2 is a single board SoC computer based on the Rockchip RK3399 processor (arm64). It has built-in +GPIO, I2C, PWM, SPI, 1-Wire, MIPI CSI and MIPI DSI interfaces. + +For more info about the Tinker Board, go to [https://tinker-board.asus.com/series/tinker-board-2.html/](https://tinker-board.asus.com/series/tinker-board-2.html). + +## How to Install + +Please refer to the main [README.md](/~https://github.com/hybridgroup/gobot/blob/release/README.md) + +Tested OS: + +* [armbian](https://www.armbian.com/tinkerboard-2/) with Debian + +### System access and configuration basics + +Use `sudo armbian-config` or see description for [Tinker Board](../README.md). + +### Enabling hardware drivers + +See description for [Tinker Board](../README.md). + +### Enabling GPIO pins + +See description for [Tinker Board](../README.md). + +### Enabling I2C + +See description for [Tinker Board](../README.md). + +## How to Use + +The pin numbering used by your Gobot program should match the way your board is labeled right on the board itself. + +```go +r := tinkerboard2.NewAdaptor() +led := gpio.NewLedDriver(r, "7") +``` + +## How to Connect + +### Compiling + +Compile your Gobot program on your workstation like this: + +```sh +GOARCH=arm64 GOOS=linux go build examples/tinkerboard_blink.go +``` + +Once you have compiled your code, you can upload your program and execute it on the Tinker Board 2 from your workstation +using the `scp` and `ssh` commands like this: + +```sh +scp tinkerboard_blink @192.168.1.xxx:~ +ssh -t @192.168.1.xxx "./tinkerboard_blink" +``` + +## Troubleshooting + +### I2C + +The version "Armbian_community 25.2.0-trunk.124 bookworm" contains by default the overlay for i2c7 (header pins 27, 28) +and i2c8 (connection on DSI_1, DSI_2). An overlay for i2c6 (header pins 3, 5) needs to be created manually based on one +of the other overlays. + +### PWM + +#### Investigate state + +```sh +# ls -la /sys/class/pwm/ +ls -la /sys/class/pwm/ +total 0 +drwxr-xr-x 2 root root 0 Jan 18 2013 . +drwxr-xr-x 77 root root 0 Jan 18 2013 .. +lrwxrwxrwx 1 root root 0 Jan 18 2013 pwmchip0 -> ../../devices/platform/ff420020.pwm/pwm/pwmchip0 +``` + +looking for one of the following items in the path: +ff420000 => pwm0, pin32 +ff420010 => pwm1, pin33 +ff420020 => pwm2, already activated, but internally, not usable +ff420030 => pwm3, pin26 + +#### Activate + +The version "Armbian_community 25.2.0-trunk.124 bookworm" contains no overlay for pwm0, pwm1 or pwm3. This needs to be +created based on another overlay, e.g. `rockchip-rk3568-hk-pwm1.dtbo`. After activation of your preferred pwmchip, +proceed like described for [Tinker Board](../README). diff --git a/platforms/tinkerboard/tinkerboard2/adaptor.go b/platforms/tinkerboard/tinkerboard2/adaptor.go new file mode 100644 index 000000000..4a09f784b --- /dev/null +++ b/platforms/tinkerboard/tinkerboard2/adaptor.go @@ -0,0 +1,69 @@ +package tinkerboard2 + +import ( + "fmt" + + "gobot.io/x/gobot/v2" + "gobot.io/x/gobot/v2/platforms/adaptors" + "gobot.io/x/gobot/v2/platforms/tinkerboard" + "gobot.io/x/gobot/v2/system" +) + +const ( + defaultI2cBusNumber = 7 // i2c-7 (header pins 27, 28) + + defaultSpiBusNumber = 1 // spidev1.x (header pins 19, 21, 23, 24) + defaultSpiChipNumber = 0 + defaultSpiMode = 0 + defaultSpiBitsNumber = 8 + defaultSpiMaxSpeed = 500000 +) + +type Tinkerboard2Adaptor struct { + *tinkerboard.Adaptor +} + +// NewAdaptor creates a Tinkerboard-2 Adaptor +// +// Optional parameters: +// +// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default) +// adaptors.WithSpiGpioAccess(sclk, ncs, sdo, sdi): use GPIO's instead of /dev/spidev#.# +// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior +// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor +// +// Optional parameters for PWM, see [adaptors.NewPWMPinsAdaptor] +func NewAdaptor(opts ...interface{}) *Tinkerboard2Adaptor { + sys := system.NewAccesser(system.WithDigitalPinGpiodAccess()) + a := tinkerboard.NewAdaptor() + a.SetName(gobot.DefaultName("Tinker Board 2")) + + var digitalPinsOpts []func(adaptors.DigitalPinsOptioner) + var pwmPinsOpts []adaptors.PwmPinsOptionApplier + for _, opt := range opts { + switch o := opt.(type) { + case func(adaptors.DigitalPinsOptioner): + digitalPinsOpts = append(digitalPinsOpts, o) + case adaptors.PwmPinsOptionApplier: + pwmPinsOpts = append(pwmPinsOpts, o) + default: + panic(fmt.Sprintf("'%s' can not be applied on adaptor '%s'", opt, a.Name())) + } + } + + // note: only adaptors different from tinkerboard needs to be re-assigned + digitalPinTranslator := adaptors.NewDigitalPinTranslator(sys, gpioPinDefinitions) + pwmPinTranslator := adaptors.NewPWMPinTranslator(sys, pwmPinDefinitions) + // Valid bus numbers are [6..8] which corresponds to /dev/i2c-6 through /dev/i2c-8. + // We don't support "/dev/i2c-0, /dev/i2c-3, /dev/i2c-4". + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{6, 7, 8}) + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{1, 5}) + + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, digitalPinTranslator.Translate, digitalPinsOpts...) + a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, pwmPinTranslator.Translate, pwmPinsOpts...) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + + return &Tinkerboard2Adaptor{Adaptor: a} +} diff --git a/platforms/tinkerboard/tinkerboard2/adaptor_test.go b/platforms/tinkerboard/tinkerboard2/adaptor_test.go new file mode 100644 index 000000000..10e02ee39 --- /dev/null +++ b/platforms/tinkerboard/tinkerboard2/adaptor_test.go @@ -0,0 +1,24 @@ +package tinkerboard2 + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewAdaptor(t *testing.T) { + // arrange & act + a := NewAdaptor() + // assert + assert.IsType(t, &Tinkerboard2Adaptor{}, a) + assert.True(t, strings.HasPrefix(a.Name(), "Tinker Board 2")) + assert.NotNil(t, a.AnalogPinsAdaptor) + assert.NotNil(t, a.DigitalPinsAdaptor) + assert.NotNil(t, a.PWMPinsAdaptor) + assert.NotNil(t, a.I2cBusAdaptor) + assert.NotNil(t, a.SpiBusAdaptor) + // act & assert + a.SetName("NewName") + assert.Equal(t, "NewName", a.Name()) +} diff --git a/platforms/tinkerboard/tinkerboard2/pin_map.go b/platforms/tinkerboard/tinkerboard2/pin_map.go new file mode 100644 index 000000000..76a669673 --- /dev/null +++ b/platforms/tinkerboard/tinkerboard2/pin_map.go @@ -0,0 +1,57 @@ +package tinkerboard2 + +import "gobot.io/x/gobot/v2/platforms/adaptors" + +// notes for character device +// pins: A=0+Nr, B=8+Nr, C=16+Nr, D=24+Nr +// tested: armbian Linux, OK: work as input and output, IN: work only as input +// MIPI ports itself not wired yet +var gpioPinDefinitions = adaptors.DigitalPinDefinitions{ + "26": {Sysfs: 6, Cdev: adaptors.CdevPin{Chip: 0, Line: 6}}, // GPIO0_A6_PWM3A_IR - OK, PWM OK + "7": {Sysfs: 8, Cdev: adaptors.CdevPin{Chip: 0, Line: 8}}, // GPIO0_B0_TEST_CLKOUT2 - OK + "21": {Sysfs: 39, Cdev: adaptors.CdevPin{Chip: 1, Line: 7}}, // GPIO1_A7_SPI1_RXD_UART4_RXD - OK + "19": {Sysfs: 40, Cdev: adaptors.CdevPin{Chip: 1, Line: 8}}, // GPIO1_B0_SPI1_TXD_UART4_TXD - OK + "23": {Sysfs: 41, Cdev: adaptors.CdevPin{Chip: 1, Line: 9}}, // GPIO1_B1_SPI1_CLK - OK + "24": {Sysfs: 42, Cdev: adaptors.CdevPin{Chip: 1, Line: 10}}, // GPIO1_B2_SPI1_CSN - OK + "DSI_1": {Sysfs: 48, Cdev: adaptors.CdevPin{Chip: 1, Line: 20}}, // GPIO1_C4_I2C8_SDA - ? + "DSI_2": {Sysfs: 48, Cdev: adaptors.CdevPin{Chip: 1, Line: 21}}, // GPIO1_C5_I2C8_SCL - ? + "27": {Sysfs: 71, Cdev: adaptors.CdevPin{Chip: 2, Line: 7}}, // GPIO2_A7_I2C7_SDA - OK + "28": {Sysfs: 72, Cdev: adaptors.CdevPin{Chip: 2, Line: 8}}, // GPIO2_B0_I2C7_SCL - OK + "3": {Sysfs: 73, Cdev: adaptors.CdevPin{Chip: 2, Line: 9}}, // GPIO2_B1_I2C6_SDA - OK + "5": {Sysfs: 74, Cdev: adaptors.CdevPin{Chip: 2, Line: 10}}, // GPIO2_B2_I2C6_SCL - OK + "CSI_3": {Sysfs: 75, Cdev: adaptors.CdevPin{Chip: 2, Line: 11}}, // GPIO2_B3_CSI_CLKOUT - ? + "10": {Sysfs: 80, Cdev: adaptors.CdevPin{Chip: 2, Line: 16}}, // GPIO2_C0_UART0_RXD - OK + "8": {Sysfs: 81, Cdev: adaptors.CdevPin{Chip: 2, Line: 17}}, // GPIO2_C1_UART0_TXD - OK + "36": {Sysfs: 82, Cdev: adaptors.CdevPin{Chip: 2, Line: 18}}, // GPIO2_C2_UART0_CTSN - OK + "11": {Sysfs: 83, Cdev: adaptors.CdevPin{Chip: 2, Line: 19}}, // GPIO2_C3_UART0_RTSN - OK + "15": {Sysfs: 84, Cdev: adaptors.CdevPin{Chip: 2, Line: 20}}, // GPIO2_C4_SPI5_RX - OK + "13": {Sysfs: 85, Cdev: adaptors.CdevPin{Chip: 2, Line: 21}}, // GPIO2_C5_SPI5_TX - OK + "16": {Sysfs: 86, Cdev: adaptors.CdevPin{Chip: 2, Line: 22}}, // GPIO2_C6_SPI5_CLK - OK + "18": {Sysfs: 87, Cdev: adaptors.CdevPin{Chip: 2, Line: 23}}, // GPIO2_C7_SPI5_CSN - OK + "12": {Sysfs: 120, Cdev: adaptors.CdevPin{Chip: 3, Line: 24}}, // GPIO3_D0_I2S0_SCLK - OK + "35": {Sysfs: 121, Cdev: adaptors.CdevPin{Chip: 3, Line: 25}}, // GPIO3_D1_I2S0_FS - OK (smooth digital behavior) + "38": {Sysfs: 123, Cdev: adaptors.CdevPin{Chip: 3, Line: 27}}, // GPIO3_D3_I2S0_SDI0 - OK + "22": {Sysfs: 124, Cdev: adaptors.CdevPin{Chip: 3, Line: 28}}, // GPIO3_D4_I2S0_SDO3 - OK + "31": {Sysfs: 125, Cdev: adaptors.CdevPin{Chip: 3, Line: 29}}, // GPIO3_D5_I2S0_SDO2 - OK + "29": {Sysfs: 126, Cdev: adaptors.CdevPin{Chip: 3, Line: 30}}, // GPIO3_D6_I2S0_SDO1 - OK + "40": {Sysfs: 127, Cdev: adaptors.CdevPin{Chip: 3, Line: 31}}, // GPIO3_D7_I2S0_SDO0 - OK + "CSI_1": {Sysfs: 128, Cdev: adaptors.CdevPin{Chip: 4, Line: 1}}, // GPIO4_A1_I2C1_SDA -? + "CSI_2": {Sysfs: 129, Cdev: adaptors.CdevPin{Chip: 4, Line: 2}}, // GPIO4_A2_I2C1_SCL -? + "CSI_4": {Sysfs: 130, Cdev: adaptors.CdevPin{Chip: 4, Line: 3}}, // GPIO4_A3_CSI_GPIO -? + "32": {Sysfs: 146, Cdev: adaptors.CdevPin{Chip: 4, Line: 18}}, // GPIO4_C2_PWM0 - OK, PWM OK + "J6_1": {Sysfs: 147, Cdev: adaptors.CdevPin{Chip: 4, Line: 19}}, // GPIO4_C3_UART2_RX -? + "J6_2": {Sysfs: 148, Cdev: adaptors.CdevPin{Chip: 4, Line: 20}}, // GPIO4_C4_UART2_TX -? + "37": {Sysfs: 149, Cdev: adaptors.CdevPin{Chip: 4, Line: 21}}, // GPIO4_C5_SPDIF_TX - OK + "33": {Sysfs: 150, Cdev: adaptors.CdevPin{Chip: 4, Line: 22}}, // GPIO4_C6_PWM1 - OK, PWM OK +} + +var pwmPinDefinitions = adaptors.PWMPinDefinitions{ + // needs to be enabled by device tree (pwm0) + "32": {Dir: "/sys/devices/platform/ff420000.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3]$", Channel: 0}, + // needs to be enabled by device tree (pwm1) + "33": {Dir: "/sys/devices/platform/ff420010.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3]$", Channel: 0}, + // needs to be enabled by device tree (pwm3) + "26": {Dir: "/sys/devices/platform/ff420030.pwm/pwm/", DirRegexp: "pwmchip[0|1|2|3]$", Channel: 0}, +} + +// analog pins are the same as for tinkerboard diff --git a/platforms/upboard/up2/adaptor.go b/platforms/upboard/up2/adaptor.go index 4641303c2..bd1dfe824 100644 --- a/platforms/upboard/up2/adaptor.go +++ b/platforms/upboard/up2/adaptor.go @@ -80,11 +80,17 @@ func NewAdaptor(opts ...interface{}) *Adaptor { } } + // Valid bus numbers are [5,6] which corresponds to /dev/i2c-5 through /dev/i2c-6. + i2cBusNumberValidator := adaptors.NewBusNumberValidator([]int{5, 6}) + // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. + // x is the chip number <255 + spiBusNumberValidator := adaptors.NewBusNumberValidator([]int{0, 1}) + a.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, a.translateDigitalPin, digitalPinsOpts...) a.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, a.translatePWMPin, pwmPinsOpts...) - a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber) - a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, a.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber, - defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) + a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, i2cBusNumberValidator.Validate, defaultI2cBusNumber) + a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, spiBusNumberValidator.Validate, defaultSpiBusNumber, + defaultSpiChipNumber, defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed) return a } @@ -154,23 +160,6 @@ func (a *Adaptor) DigitalWrite(id string, val byte) error { return a.DigitalPinsAdaptor.DigitalWrite(id, val) } -func (a *Adaptor) validateSpiBusNumber(busNr int) error { - // Valid bus numbers are [0,1] which corresponds to /dev/spidev0.x through /dev/spidev1.x. - // x is the chip number <255 - if (busNr < 0) || (busNr > 1) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - -func (a *Adaptor) validateI2cBusNumber(busNr int) error { - // Valid bus number is [5..6] which corresponds to /dev/i2c-5 through /dev/i2c-6. - if (busNr < 5) || (busNr > 6) { - return fmt.Errorf("Bus number %d out of range", busNr) - } - return nil -} - func (a *Adaptor) translateDigitalPin(id string) (string, int, error) { if val, ok := a.pinmap[id]; ok { return "", val.pin, nil diff --git a/platforms/upboard/up2/adaptor_test.go b/platforms/upboard/up2/adaptor_test.go index 6993bb5b1..7f34998ca 100644 --- a/platforms/upboard/up2/adaptor_test.go +++ b/platforms/upboard/up2/adaptor_test.go @@ -170,38 +170,6 @@ func TestSpiDefaultValues(t *testing.T) { assert.Equal(t, int64(500000), a.SpiDefaultMaxSpeed()) } -func Test_validateSpiBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_0_ok": { - busNr: 0, - }, - "number_1_ok": { - busNr: 1, - }, - "number_2_error": { - busNr: 2, - wantErr: fmt.Errorf("Bus number 2 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateSpiBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - func TestI2cDefaultBus(t *testing.T) { a := NewAdaptor() assert.Equal(t, 5, a.DefaultI2cBus()) @@ -224,42 +192,6 @@ func TestI2cFinalizeWithErrors(t *testing.T) { require.ErrorContains(t, err, "close error") } -func Test_validateI2cBusNumber(t *testing.T) { - tests := map[string]struct { - busNr int - wantErr error - }{ - "number_negative_error": { - busNr: -1, - wantErr: fmt.Errorf("Bus number -1 out of range"), - }, - "number_4_error": { - busNr: 4, - wantErr: fmt.Errorf("Bus number 4 out of range"), - }, - "number_5_ok": { - busNr: 5, - }, - "number_6_ok": { - busNr: 6, - }, - "number_7_error": { - busNr: 7, - wantErr: fmt.Errorf("Bus number 7 out of range"), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // arrange - a := NewAdaptor() - // act - err := a.validateI2cBusNumber(tc.busNr) - // assert - assert.Equal(t, tc.wantErr, err) - }) - } -} - func Test_translatePWMPin(t *testing.T) { tests := map[string]struct { wantDir string