gobot.io/x/gobot/v2@v2.1.0/platforms/adaptors/pwmpinsadaptor_test.go (about)

     1  package adaptors
     2  
     3  import (
     4  	"fmt"
     5  	"runtime"
     6  	"strconv"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  
    11  	"gobot.io/x/gobot/v2"
    12  	"gobot.io/x/gobot/v2/drivers/gpio"
    13  	"gobot.io/x/gobot/v2/gobottest"
    14  	"gobot.io/x/gobot/v2/system"
    15  )
    16  
    17  const (
    18  	pwmDir           = "/sys/devices/platform/ff680020.pwm/pwm/pwmchip3/"
    19  	pwmPwm0Dir       = pwmDir + "pwm44/"
    20  	pwmExportPath    = pwmDir + "export"
    21  	pwmUnexportPath  = pwmDir + "unexport"
    22  	pwmEnablePath    = pwmPwm0Dir + "enable"
    23  	pwmPeriodPath    = pwmPwm0Dir + "period"
    24  	pwmDutyCyclePath = pwmPwm0Dir + "duty_cycle"
    25  	pwmPolarityPath  = pwmPwm0Dir + "polarity"
    26  )
    27  
    28  var pwmMockPaths = []string{
    29  	pwmExportPath,
    30  	pwmUnexportPath,
    31  	pwmEnablePath,
    32  	pwmPeriodPath,
    33  	pwmDutyCyclePath,
    34  	pwmPolarityPath,
    35  }
    36  
    37  // make sure that this PWMPinsAdaptor fulfills all the required interfaces
    38  var _ gobot.PWMPinnerProvider = (*PWMPinsAdaptor)(nil)
    39  var _ gpio.PwmWriter = (*PWMPinsAdaptor)(nil)
    40  var _ gpio.ServoWriter = (*PWMPinsAdaptor)(nil)
    41  
    42  func initTestPWMPinsAdaptorWithMockedFilesystem(mockPaths []string) (*PWMPinsAdaptor, *system.MockFilesystem) {
    43  	sys := system.NewAccesser()
    44  	fs := sys.UseMockFilesystem(mockPaths)
    45  	a := NewPWMPinsAdaptor(sys, testPWMPinTranslator)
    46  	fs.Files[pwmEnablePath].Contents = "0"
    47  	fs.Files[pwmPeriodPath].Contents = "0"
    48  	fs.Files[pwmDutyCyclePath].Contents = "0"
    49  	fs.Files[pwmPolarityPath].Contents = a.polarityInvertedIdentifier
    50  	if err := a.Connect(); err != nil {
    51  		panic(err)
    52  	}
    53  	return a, fs
    54  }
    55  
    56  func testPWMPinTranslator(id string) (string, int, error) {
    57  	channel, err := strconv.Atoi(id)
    58  	if err != nil {
    59  		return "", -1, fmt.Errorf("'%s' is not a valid id of a PWM pin", id)
    60  	}
    61  	channel = channel + 11 // just for tests
    62  	return pwmDir, channel, err
    63  }
    64  
    65  func TestNewPWMPinsAdaptor(t *testing.T) {
    66  	// arrange
    67  	translate := func(pin string) (chip string, line int, err error) { return }
    68  	// act
    69  	a := NewPWMPinsAdaptor(system.NewAccesser(), translate)
    70  	// assert
    71  	gobottest.Assert(t, a.periodDefault, uint32(pwmPeriodDefault))
    72  	gobottest.Assert(t, a.polarityNormalIdentifier, "normal")
    73  	gobottest.Assert(t, a.polarityInvertedIdentifier, "inverted")
    74  	gobottest.Assert(t, a.adjustDutyOnSetPeriod, true)
    75  }
    76  
    77  func TestWithPWMPinInitializer(t *testing.T) {
    78  	// This is a general test, that options are applied by using the WithPWMPinInitializer() option.
    79  	// All other configuration options can also be tested by With..(val)(a).
    80  	// arrange
    81  	wantErr := fmt.Errorf("new_initializer")
    82  	newInitializer := func(gobot.PWMPinner) error { return wantErr }
    83  	// act
    84  	a := NewPWMPinsAdaptor(system.NewAccesser(), func(pin string) (c string, l int, e error) { return },
    85  		WithPWMPinInitializer(newInitializer))
    86  	// assert
    87  	err := a.initialize(nil)
    88  	gobottest.Assert(t, err, wantErr)
    89  }
    90  
    91  func TestWithPWMPinDefaultPeriod(t *testing.T) {
    92  	// arrange
    93  	const newPeriod = uint32(10)
    94  	a := NewPWMPinsAdaptor(system.NewAccesser(), func(string) (c string, l int, e error) { return })
    95  	// act
    96  	WithPWMPinDefaultPeriod(newPeriod)(a)
    97  	// assert
    98  	gobottest.Assert(t, a.periodDefault, newPeriod)
    99  }
   100  
   101  func TestWithPolarityInvertedIdentifier(t *testing.T) {
   102  	// arrange
   103  	const newPolarityIdent = "pwm_invers"
   104  	a := NewPWMPinsAdaptor(system.NewAccesser(), func(pin string) (c string, l int, e error) { return })
   105  	// act
   106  	WithPolarityInvertedIdentifier(newPolarityIdent)(a)
   107  	// assert
   108  	gobottest.Assert(t, a.polarityInvertedIdentifier, newPolarityIdent)
   109  }
   110  
   111  func TestPWMPinsConnect(t *testing.T) {
   112  	translate := func(pin string) (chip string, line int, err error) { return }
   113  	a := NewPWMPinsAdaptor(system.NewAccesser(), translate)
   114  	gobottest.Assert(t, a.pins, (map[string]gobot.PWMPinner)(nil))
   115  
   116  	err := a.PwmWrite("33", 1)
   117  	gobottest.Assert(t, err.Error(), "not connected")
   118  
   119  	err = a.Connect()
   120  	gobottest.Assert(t, err, nil)
   121  	gobottest.Refute(t, a.pins, (map[string]gobot.PWMPinner)(nil))
   122  	gobottest.Assert(t, len(a.pins), 0)
   123  }
   124  
   125  func TestPWMPinsFinalize(t *testing.T) {
   126  	// arrange
   127  	sys := system.NewAccesser()
   128  	fs := sys.UseMockFilesystem(pwmMockPaths)
   129  	a := NewPWMPinsAdaptor(sys, testPWMPinTranslator)
   130  	// assert that finalize before connect is working
   131  	gobottest.Assert(t, a.Finalize(), nil)
   132  	// arrange
   133  	gobottest.Assert(t, a.Connect(), nil)
   134  	gobottest.Assert(t, a.PwmWrite("33", 1), nil)
   135  	gobottest.Assert(t, len(a.pins), 1)
   136  	// act
   137  	err := a.Finalize()
   138  	// assert
   139  	gobottest.Assert(t, err, nil)
   140  	gobottest.Assert(t, len(a.pins), 0)
   141  	// assert that finalize after finalize is working
   142  	gobottest.Assert(t, a.Finalize(), nil)
   143  	// arrange missing sysfs file
   144  	gobottest.Assert(t, a.Connect(), nil)
   145  	gobottest.Assert(t, a.PwmWrite("33", 2), nil)
   146  	delete(fs.Files, pwmUnexportPath)
   147  	err = a.Finalize()
   148  	gobottest.Assert(t, strings.Contains(err.Error(), pwmUnexportPath+": no such file"), true)
   149  	// arrange write error
   150  	gobottest.Assert(t, a.Connect(), nil)
   151  	gobottest.Assert(t, a.PwmWrite("33", 2), nil)
   152  	fs.WithWriteError = true
   153  	err = a.Finalize()
   154  	gobottest.Assert(t, strings.Contains(err.Error(), "write error"), true)
   155  }
   156  
   157  func TestPWMPinsReConnect(t *testing.T) {
   158  	// arrange
   159  	a, _ := initTestPWMPinsAdaptorWithMockedFilesystem(pwmMockPaths)
   160  	gobottest.Assert(t, a.PwmWrite("33", 1), nil)
   161  	gobottest.Assert(t, len(a.pins), 1)
   162  	gobottest.Assert(t, a.Finalize(), nil)
   163  	// act
   164  	err := a.Connect()
   165  	// assert
   166  	gobottest.Assert(t, err, nil)
   167  	gobottest.Refute(t, a.pins, nil)
   168  	gobottest.Assert(t, len(a.pins), 0)
   169  }
   170  
   171  func TestPwmWrite(t *testing.T) {
   172  	a, fs := initTestPWMPinsAdaptorWithMockedFilesystem(pwmMockPaths)
   173  
   174  	err := a.PwmWrite("33", 100)
   175  	gobottest.Assert(t, err, nil)
   176  
   177  	gobottest.Assert(t, fs.Files[pwmExportPath].Contents, "44")
   178  	gobottest.Assert(t, fs.Files[pwmEnablePath].Contents, "1")
   179  	gobottest.Assert(t, fs.Files[pwmPeriodPath].Contents, fmt.Sprintf("%d", a.periodDefault))
   180  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "3921568")
   181  	gobottest.Assert(t, fs.Files[pwmPolarityPath].Contents, "normal")
   182  
   183  	err = a.PwmWrite("notexist", 42)
   184  	gobottest.Assert(t, err.Error(), "'notexist' is not a valid id of a PWM pin")
   185  
   186  	fs.WithWriteError = true
   187  	err = a.PwmWrite("33", 100)
   188  	gobottest.Assert(t, strings.Contains(err.Error(), "write error"), true)
   189  	fs.WithWriteError = false
   190  
   191  	fs.WithReadError = true
   192  	err = a.PwmWrite("33", 100)
   193  	gobottest.Assert(t, strings.Contains(err.Error(), "read error"), true)
   194  }
   195  
   196  func TestServoWrite(t *testing.T) {
   197  	a, fs := initTestPWMPinsAdaptorWithMockedFilesystem(pwmMockPaths)
   198  
   199  	err := a.ServoWrite("33", 0)
   200  	gobottest.Assert(t, fs.Files[pwmExportPath].Contents, "44")
   201  	gobottest.Assert(t, fs.Files[pwmEnablePath].Contents, "1")
   202  	gobottest.Assert(t, fs.Files[pwmPeriodPath].Contents, fmt.Sprintf("%d", a.periodDefault))
   203  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "500000")
   204  	gobottest.Assert(t, fs.Files[pwmPolarityPath].Contents, "normal")
   205  	gobottest.Assert(t, err, nil)
   206  
   207  	err = a.ServoWrite("33", 180)
   208  	gobottest.Assert(t, err, nil)
   209  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "2000000")
   210  
   211  	err = a.ServoWrite("notexist", 42)
   212  	gobottest.Assert(t, err.Error(), "'notexist' is not a valid id of a PWM pin")
   213  
   214  	fs.WithWriteError = true
   215  	err = a.ServoWrite("33", 100)
   216  	gobottest.Assert(t, strings.Contains(err.Error(), "write error"), true)
   217  	fs.WithWriteError = false
   218  
   219  	fs.WithReadError = true
   220  	err = a.ServoWrite("33", 100)
   221  	gobottest.Assert(t, strings.Contains(err.Error(), "read error"), true)
   222  }
   223  
   224  func TestSetPeriod(t *testing.T) {
   225  	// arrange
   226  	a, fs := initTestPWMPinsAdaptorWithMockedFilesystem(pwmMockPaths)
   227  	newPeriod := uint32(2550000)
   228  	// act
   229  	err := a.SetPeriod("33", newPeriod)
   230  	// assert
   231  	gobottest.Assert(t, err, nil)
   232  	gobottest.Assert(t, fs.Files[pwmExportPath].Contents, "44")
   233  	gobottest.Assert(t, fs.Files[pwmEnablePath].Contents, "1")
   234  	gobottest.Assert(t, fs.Files[pwmPeriodPath].Contents, fmt.Sprintf("%d", newPeriod))
   235  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, "0")
   236  	gobottest.Assert(t, fs.Files[pwmPolarityPath].Contents, "normal")
   237  
   238  	// arrange test for automatic adjustment of duty cycle to lower value
   239  	err = a.PwmWrite("33", 127) // 127 is a little bit smaller than 50% of period
   240  	gobottest.Assert(t, err, nil)
   241  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, fmt.Sprintf("%d", 1270000))
   242  	newPeriod = newPeriod / 10
   243  
   244  	// act
   245  	err = a.SetPeriod("33", newPeriod)
   246  
   247  	// assert
   248  	gobottest.Assert(t, err, nil)
   249  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, fmt.Sprintf("%d", 127000))
   250  
   251  	// arrange test for automatic adjustment of duty cycle to higher value
   252  	newPeriod = newPeriod * 20
   253  
   254  	// act
   255  	err = a.SetPeriod("33", newPeriod)
   256  
   257  	// assert
   258  	gobottest.Assert(t, err, nil)
   259  	gobottest.Assert(t, fs.Files[pwmDutyCyclePath].Contents, fmt.Sprintf("%d", 2540000))
   260  
   261  	// act
   262  	err = a.SetPeriod("not_exist", newPeriod)
   263  	// assert
   264  	gobottest.Assert(t, err.Error(), "'not_exist' is not a valid id of a PWM pin")
   265  }
   266  
   267  func Test_PWMPin(t *testing.T) {
   268  	translateErr := "translator_error"
   269  	translator := func(string) (string, int, error) { return pwmDir, 44, nil }
   270  	var tests = map[string]struct {
   271  		mockPaths []string
   272  		period    string
   273  		translate func(string) (string, int, error)
   274  		pin       string
   275  		wantErr   string
   276  	}{
   277  		"pin_ok": {
   278  			mockPaths: []string{pwmExportPath, pwmEnablePath, pwmPeriodPath, pwmDutyCyclePath, pwmPolarityPath},
   279  			translate: translator,
   280  			pin:       "33",
   281  		},
   282  		"init_export_error": {
   283  			mockPaths: []string{},
   284  			translate: translator,
   285  			pin:       "33",
   286  			wantErr:   "Export() failed for id 44 with  : /sys/devices/platform/ff680020.pwm/pwm/pwmchip3/export: no such file",
   287  		},
   288  		"init_setenabled_error": {
   289  			mockPaths: []string{pwmExportPath, pwmPeriodPath},
   290  			period:    "1000",
   291  			translate: translator,
   292  			pin:       "33",
   293  			wantErr:   "SetEnabled(false) failed for id 44 with  : /sys/devices/platform/ff680020.pwm/pwm/pwmchip3/pwm44/enable: no such file",
   294  		},
   295  		"init_setperiod_dutycycle_no_error": {
   296  			mockPaths: []string{pwmExportPath, pwmEnablePath, pwmPeriodPath, pwmPolarityPath},
   297  			translate: translator,
   298  			pin:       "33",
   299  		},
   300  		"init_setperiod_error": {
   301  			mockPaths: []string{pwmExportPath, pwmEnablePath},
   302  			translate: translator,
   303  			pin:       "33",
   304  			wantErr:   "SetPeriod(10000000) failed for id 44 with  : /sys/devices/platform/ff680020.pwm/pwm/pwmchip3/pwm44/period: no such file",
   305  		},
   306  		"init_setpolarity_error": {
   307  			mockPaths: []string{pwmExportPath, pwmEnablePath, pwmPeriodPath, pwmDutyCyclePath},
   308  			translate: translator,
   309  			pin:       "33",
   310  			wantErr:   "SetPolarity(normal) failed for id 44 with  : /sys/devices/platform/ff680020.pwm/pwm/pwmchip3/pwm44/polarity: no such file",
   311  		},
   312  		"translate_error": {
   313  			translate: func(string) (string, int, error) { return "", -1, fmt.Errorf(translateErr) },
   314  			wantErr:   translateErr,
   315  		},
   316  	}
   317  	for name, tc := range tests {
   318  		t.Run(name, func(t *testing.T) {
   319  			// arrange
   320  			sys := system.NewAccesser()
   321  			fs := sys.UseMockFilesystem(tc.mockPaths)
   322  			if tc.period != "" {
   323  				fs.Files[pwmPeriodPath].Contents = tc.period
   324  			}
   325  			a := NewPWMPinsAdaptor(sys, tc.translate)
   326  			if err := a.Connect(); err != nil {
   327  				panic(err)
   328  			}
   329  			// act
   330  			got, err := a.PWMPin(tc.pin)
   331  			// assert
   332  			if tc.wantErr == "" {
   333  				gobottest.Assert(t, err, nil)
   334  				gobottest.Refute(t, got, nil)
   335  			} else {
   336  				gobottest.Assert(t, strings.Contains(err.Error(), tc.wantErr), true)
   337  				gobottest.Assert(t, got, nil)
   338  			}
   339  
   340  		})
   341  	}
   342  }
   343  
   344  func TestPWMPinConcurrency(t *testing.T) {
   345  	oldProcs := runtime.GOMAXPROCS(0)
   346  	runtime.GOMAXPROCS(8)
   347  	defer runtime.GOMAXPROCS(oldProcs)
   348  
   349  	translate := func(pin string) (string, int, error) { line, err := strconv.Atoi(pin); return "", line, err }
   350  	sys := system.NewAccesser()
   351  
   352  	for retry := 0; retry < 20; retry++ {
   353  
   354  		a := NewPWMPinsAdaptor(sys, translate)
   355  		a.Connect()
   356  		var wg sync.WaitGroup
   357  
   358  		for i := 0; i < 20; i++ {
   359  			wg.Add(1)
   360  			pinAsString := strconv.Itoa(i)
   361  			go func(pin string) {
   362  				defer wg.Done()
   363  				a.PWMPin(pin)
   364  			}(pinAsString)
   365  		}
   366  
   367  		wg.Wait()
   368  	}
   369  }