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 }