gobot.io/x/gobot/v2@v2.1.0/platforms/nanopi/nanopi_adaptor_test.go (about)

     1  package nanopi
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"gobot.io/x/gobot/v2"
     9  	"gobot.io/x/gobot/v2/drivers/gpio"
    10  	"gobot.io/x/gobot/v2/drivers/i2c"
    11  	"gobot.io/x/gobot/v2/gobottest"
    12  	"gobot.io/x/gobot/v2/system"
    13  )
    14  
    15  const (
    16  	gpio203Path = "/sys/class/gpio/gpio203/"
    17  	gpio199Path = "/sys/class/gpio/gpio199/"
    18  )
    19  
    20  const (
    21  	pwmDir           = "/sys/devices/platform/soc/1c21400.pwm/pwm/pwmchip0/"
    22  	pwmPwmDir        = pwmDir + "pwm0/"
    23  	pwmExportPath    = pwmDir + "export"
    24  	pwmUnexportPath  = pwmDir + "unexport"
    25  	pwmEnablePath    = pwmPwmDir + "enable"
    26  	pwmPeriodPath    = pwmPwmDir + "period"
    27  	pwmDutyCyclePath = pwmPwmDir + "duty_cycle"
    28  	pwmPolarityPath  = pwmPwmDir + "polarity"
    29  )
    30  
    31  var pwmMockPaths = []string{
    32  	pwmExportPath,
    33  	pwmUnexportPath,
    34  	pwmEnablePath,
    35  	pwmPeriodPath,
    36  	pwmDutyCyclePath,
    37  	pwmPolarityPath,
    38  }
    39  
    40  var gpioMockPaths = []string{
    41  	"/sys/class/gpio/export",
    42  	"/sys/class/gpio/unexport",
    43  	gpio203Path + "value",
    44  	gpio203Path + "direction",
    45  	gpio199Path + "value",
    46  	gpio199Path + "direction",
    47  }
    48  
    49  // make sure that this Adaptor fulfills all the required interfaces
    50  var _ gobot.Adaptor = (*Adaptor)(nil)
    51  var _ gobot.DigitalPinnerProvider = (*Adaptor)(nil)
    52  var _ gobot.PWMPinnerProvider = (*Adaptor)(nil)
    53  var _ gpio.DigitalReader = (*Adaptor)(nil)
    54  var _ gpio.DigitalWriter = (*Adaptor)(nil)
    55  var _ gpio.PwmWriter = (*Adaptor)(nil)
    56  var _ gpio.ServoWriter = (*Adaptor)(nil)
    57  var _ i2c.Connector = (*Adaptor)(nil)
    58  
    59  func preparePwmFs(fs *system.MockFilesystem) {
    60  	fs.Files[pwmEnablePath].Contents = "0"
    61  	fs.Files[pwmPeriodPath].Contents = "0"
    62  	fs.Files[pwmDutyCyclePath].Contents = "0"
    63  	fs.Files[pwmPolarityPath].Contents = pwmInvertedIdentifier
    64  }
    65  
    66  func initTestAdaptorWithMockedFilesystem(mockPaths []string) (*Adaptor, *system.MockFilesystem) {
    67  	a := NewNeoAdaptor()
    68  	fs := a.sys.UseMockFilesystem(mockPaths)
    69  	if err := a.Connect(); err != nil {
    70  		panic(err)
    71  	}
    72  	return a, fs
    73  }
    74  
    75  func TestName(t *testing.T) {
    76  	a := NewNeoAdaptor()
    77  	gobottest.Assert(t, strings.HasPrefix(a.Name(), "NanoPi NEO Board"), true)
    78  	a.SetName("NewName")
    79  	gobottest.Assert(t, a.Name(), "NewName")
    80  }
    81  
    82  func TestDigitalIO(t *testing.T) {
    83  	// only basic tests needed, further tests are done in "digitalpinsadaptor.go"
    84  	a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths)
    85  
    86  	a.DigitalWrite("7", 1)
    87  	gobottest.Assert(t, fs.Files[gpio203Path+"value"].Contents, "1")
    88  
    89  	fs.Files[gpio199Path+"value"].Contents = "1"
    90  	i, _ := a.DigitalRead("10")
    91  	gobottest.Assert(t, i, 1)
    92  
    93  	gobottest.Assert(t, a.DigitalWrite("99", 1), fmt.Errorf("'99' is not a valid id for a digital pin"))
    94  	gobottest.Assert(t, a.Finalize(), nil)
    95  }
    96  
    97  func TestInvalidPWMPin(t *testing.T) {
    98  	a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths)
    99  	preparePwmFs(fs)
   100  
   101  	err := a.PwmWrite("666", 42)
   102  	gobottest.Assert(t, err.Error(), "'666' is not a valid id for a PWM pin")
   103  
   104  	err = a.ServoWrite("666", 120)
   105  	gobottest.Assert(t, err.Error(), "'666' is not a valid id for a PWM pin")
   106  
   107  	err = a.PwmWrite("3", 42)
   108  	gobottest.Assert(t, err.Error(), "'3' is not a valid id for a PWM pin")
   109  
   110  	err = a.ServoWrite("3", 120)
   111  	gobottest.Assert(t, err.Error(), "'3' is not a valid id for a PWM pin")
   112  }
   113  
   114  func TestPwmWrite(t *testing.T) {
   115  	a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths)
   116  	preparePwmFs(fs)
   117  
   118  	err := a.PwmWrite("PWM", 100)
   119  	gobottest.Assert(t, err, nil)
   120  
   121  	gobottest.Assert(t, fs.Files[pwmExportPath].Contents, "0")
   122  	gobottest.Assert(t, fs.Files[pwmEnablePath].Contents, "1")
   123  	gobottest.Assert(t, fs.Files[pwmPeriodPath].Contents, fmt.Sprintf("%d", 10000000))
   124  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "3921568")
   125  	gobottest.Assert(t, fs.Files[pwmPolarityPath].Contents, "normal")
   126  
   127  	err = a.ServoWrite("PWM", 0)
   128  	gobottest.Assert(t, err, nil)
   129  
   130  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "500000")
   131  
   132  	err = a.ServoWrite("PWM", 180)
   133  	gobottest.Assert(t, err, nil)
   134  
   135  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "2000000")
   136  	gobottest.Assert(t, a.Finalize(), nil)
   137  }
   138  
   139  func TestSetPeriod(t *testing.T) {
   140  	// arrange
   141  	a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths)
   142  	preparePwmFs(fs)
   143  
   144  	newPeriod := uint32(2550000)
   145  	// act
   146  	err := a.SetPeriod("PWM", newPeriod)
   147  	// assert
   148  	gobottest.Assert(t, err, nil)
   149  	gobottest.Assert(t, fs.Files[pwmExportPath].Contents, "0")
   150  	gobottest.Assert(t, fs.Files[pwmEnablePath].Contents, "1")
   151  	gobottest.Assert(t, fs.Files[pwmPeriodPath].Contents, fmt.Sprintf("%d", newPeriod))
   152  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "0")
   153  	gobottest.Assert(t, fs.Files[pwmPolarityPath].Contents, "normal")
   154  
   155  	// arrange test for automatic adjustment of duty cycle to lower value
   156  	err = a.PwmWrite("PWM", 127) // 127 is a little bit smaller than 50% of period
   157  	gobottest.Assert(t, err, nil)
   158  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, fmt.Sprintf("%d", 1270000))
   159  	newPeriod = newPeriod / 10
   160  
   161  	// act
   162  	err = a.SetPeriod("PWM", newPeriod)
   163  
   164  	// assert
   165  	gobottest.Assert(t, err, nil)
   166  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, fmt.Sprintf("%d", 127000))
   167  
   168  	// arrange test for automatic adjustment of duty cycle to higher value
   169  	newPeriod = newPeriod * 20
   170  
   171  	// act
   172  	err = a.SetPeriod("PWM", newPeriod)
   173  
   174  	// assert
   175  	gobottest.Assert(t, err, nil)
   176  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, fmt.Sprintf("%d", 2540000))
   177  }
   178  
   179  func TestFinalizeErrorAfterGPIO(t *testing.T) {
   180  	a, fs := initTestAdaptorWithMockedFilesystem(gpioMockPaths)
   181  
   182  	gobottest.Assert(t, a.DigitalWrite("7", 1), nil)
   183  
   184  	fs.WithWriteError = true
   185  
   186  	err := a.Finalize()
   187  	gobottest.Assert(t, strings.Contains(err.Error(), "write error"), true)
   188  }
   189  
   190  func TestFinalizeErrorAfterPWM(t *testing.T) {
   191  	a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths)
   192  	preparePwmFs(fs)
   193  
   194  	gobottest.Assert(t, a.PwmWrite("PWM", 1), nil)
   195  
   196  	fs.WithWriteError = true
   197  
   198  	err := a.Finalize()
   199  	gobottest.Assert(t, strings.Contains(err.Error(), "write error"), true)
   200  }
   201  
   202  func TestSpiDefaultValues(t *testing.T) {
   203  	a := NewNeoAdaptor()
   204  
   205  	gobottest.Assert(t, a.SpiDefaultBusNumber(), 0)
   206  	gobottest.Assert(t, a.SpiDefaultChipNumber(), 0)
   207  	gobottest.Assert(t, a.SpiDefaultMode(), 0)
   208  	gobottest.Assert(t, a.SpiDefaultBitCount(), 8)
   209  	gobottest.Assert(t, a.SpiDefaultMaxSpeed(), int64(500000))
   210  }
   211  
   212  func TestI2cDefaultBus(t *testing.T) {
   213  	a := NewNeoAdaptor()
   214  	gobottest.Assert(t, a.DefaultI2cBus(), 0)
   215  }
   216  
   217  func TestI2cFinalizeWithErrors(t *testing.T) {
   218  	// arrange
   219  	a := NewNeoAdaptor()
   220  	a.sys.UseMockSyscall()
   221  	fs := a.sys.UseMockFilesystem([]string{"/dev/i2c-1"})
   222  	gobottest.Assert(t, a.Connect(), nil)
   223  	con, err := a.GetI2cConnection(0xff, 1)
   224  	gobottest.Assert(t, err, nil)
   225  	_, err = con.Write([]byte{0xbf})
   226  	gobottest.Assert(t, err, nil)
   227  	fs.WithCloseError = true
   228  	// act
   229  	err = a.Finalize()
   230  	// assert
   231  	gobottest.Assert(t, strings.Contains(err.Error(), "close error"), true)
   232  }
   233  
   234  func Test_validateSpiBusNumber(t *testing.T) {
   235  	var tests = map[string]struct {
   236  		busNr   int
   237  		wantErr error
   238  	}{
   239  		"number_negative_error": {
   240  			busNr:   -1,
   241  			wantErr: fmt.Errorf("Bus number -1 out of range"),
   242  		},
   243  		"number_0_ok": {
   244  			busNr: 0,
   245  		},
   246  		"number_1_error": {
   247  			busNr:   1,
   248  			wantErr: fmt.Errorf("Bus number 1 out of range"),
   249  		},
   250  	}
   251  	for name, tc := range tests {
   252  		t.Run(name, func(t *testing.T) {
   253  			// arrange
   254  			a := NewNeoAdaptor()
   255  			// act
   256  			err := a.validateSpiBusNumber(tc.busNr)
   257  			// assert
   258  			gobottest.Assert(t, err, tc.wantErr)
   259  		})
   260  	}
   261  }
   262  
   263  func Test_validateI2cBusNumber(t *testing.T) {
   264  	var tests = map[string]struct {
   265  		busNr   int
   266  		wantErr error
   267  	}{
   268  		"number_negative_error": {
   269  			busNr:   -1,
   270  			wantErr: fmt.Errorf("Bus number -1 out of range"),
   271  		},
   272  		"number_0_ok": {
   273  			busNr: 0,
   274  		},
   275  		"number_1_ok": {
   276  			busNr: 1,
   277  		},
   278  		"number_2_ok": {
   279  			busNr: 2,
   280  		},
   281  		"number_3_error": {
   282  			busNr:   3,
   283  			wantErr: fmt.Errorf("Bus number 3 out of range"),
   284  		},
   285  	}
   286  	for name, tc := range tests {
   287  		t.Run(name, func(t *testing.T) {
   288  			// arrange
   289  			a := NewNeoAdaptor()
   290  			// act
   291  			err := a.validateI2cBusNumber(tc.busNr)
   292  			// assert
   293  			gobottest.Assert(t, err, tc.wantErr)
   294  		})
   295  	}
   296  }
   297  
   298  func Test_translateDigitalPin(t *testing.T) {
   299  	var tests = map[string]struct {
   300  		access   string
   301  		pin      string
   302  		wantChip string
   303  		wantLine int
   304  		wantErr  error
   305  	}{
   306  		"cdev_ok": {
   307  			access:   "cdev",
   308  			pin:      "7",
   309  			wantChip: "gpiochip0",
   310  			wantLine: 203,
   311  		},
   312  		"sysfs_ok": {
   313  			access:   "sysfs",
   314  			pin:      "7",
   315  			wantChip: "",
   316  			wantLine: 203,
   317  		},
   318  		"unknown_pin": {
   319  			pin:      "99",
   320  			wantChip: "",
   321  			wantLine: -1,
   322  			wantErr:  fmt.Errorf("'99' is not a valid id for a digital pin"),
   323  		},
   324  	}
   325  	for name, tc := range tests {
   326  		t.Run(name, func(t *testing.T) {
   327  			// arrange
   328  			a := NewNeoAdaptor()
   329  			a.sys.UseDigitalPinAccessWithMockFs(tc.access, []string{})
   330  			// act
   331  			chip, line, err := a.translateDigitalPin(tc.pin)
   332  			// assert
   333  			gobottest.Assert(t, err, tc.wantErr)
   334  			gobottest.Assert(t, chip, tc.wantChip)
   335  			gobottest.Assert(t, line, tc.wantLine)
   336  		})
   337  	}
   338  }
   339  
   340  func Test_translatePWMPin(t *testing.T) {
   341  	basePaths := []string{"/sys/devices/platform/soc/1c21400.pwm/pwm/"}
   342  	var tests = map[string]struct {
   343  		pin         string
   344  		chip        string
   345  		wantDir     string
   346  		wantChannel int
   347  		wantErr     error
   348  	}{
   349  		"33_chip0": {
   350  			pin:         "PWM",
   351  			chip:        "pwmchip0",
   352  			wantDir:     "/sys/devices/platform/soc/1c21400.pwm/pwm/pwmchip0",
   353  			wantChannel: 0,
   354  		},
   355  		"invalid_pin": {
   356  			pin:         "7",
   357  			wantDir:     "",
   358  			wantChannel: -1,
   359  			wantErr:     fmt.Errorf("'7' is not a valid id for a PWM pin"),
   360  		},
   361  	}
   362  	for name, tc := range tests {
   363  		t.Run(name, func(t *testing.T) {
   364  			// arrange
   365  			mockedPaths := []string{}
   366  			for _, base := range basePaths {
   367  				mockedPaths = append(mockedPaths, base+tc.chip+"/")
   368  			}
   369  			a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths)
   370  			// act
   371  			dir, channel, err := a.translatePWMPin(tc.pin)
   372  			// assert
   373  			gobottest.Assert(t, err, tc.wantErr)
   374  			gobottest.Assert(t, dir, tc.wantDir)
   375  			gobottest.Assert(t, channel, tc.wantChannel)
   376  		})
   377  	}
   378  }