gobot.io/x/gobot/v2@v2.1.0/system/i2c_device_test.go (about) 1 package system 2 3 import ( 4 "errors" 5 "os" 6 "syscall" 7 "testing" 8 "unsafe" 9 10 "gobot.io/x/gobot/v2" 11 "gobot.io/x/gobot/v2/gobottest" 12 ) 13 14 const dev = "/dev/i2c-1" 15 16 func getSyscallFuncImpl(errorMask byte) func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { 17 // bit 0: error on function query 18 // bit 1: error on set address 19 // bit 2: error on command 20 return func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) { 21 // function query 22 if (trap == syscall.SYS_IOCTL) && (a2 == I2C_FUNCS) { 23 if errorMask&0x01 == 0x01 { 24 return 0, 0, 1 25 } 26 27 var funcPtr *uint64 = (*uint64)(unsafe.Pointer(a3)) 28 *funcPtr = I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_READ_BYTE_DATA | 29 I2C_FUNC_SMBUS_READ_WORD_DATA | 30 I2C_FUNC_SMBUS_WRITE_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA | 31 I2C_FUNC_SMBUS_WRITE_WORD_DATA 32 } 33 // set address 34 if (trap == syscall.SYS_IOCTL) && (a2 == I2C_SLAVE) { 35 if errorMask&0x02 == 0x02 { 36 return 0, 0, 1 37 } 38 } 39 // command 40 if (trap == syscall.SYS_IOCTL) && (a2 == I2C_SMBUS) { 41 if errorMask&0x04 == 0x04 { 42 return 0, 0, 1 43 } 44 } 45 // Let all operations succeed 46 return 0, 0, 0 47 } 48 } 49 50 func initTestI2cDeviceWithMockedSys() (*i2cDevice, *mockSyscall) { 51 a := NewAccesser() 52 msc := a.UseMockSyscall() 53 a.UseMockFilesystem([]string{dev}) 54 55 d, err := a.NewI2cDevice(dev) 56 if err != nil { 57 panic(err) 58 } 59 60 return d, msc 61 } 62 63 func TestNewI2cDevice(t *testing.T) { 64 var tests = map[string]struct { 65 dev string 66 wantErr string 67 }{ 68 "ok": { 69 dev: dev, 70 }, 71 "empty": { 72 dev: "", 73 wantErr: "the given character device location is empty", 74 }, 75 } 76 for name, tc := range tests { 77 t.Run(name, func(t *testing.T) { 78 // arrange 79 a := NewAccesser() 80 // act 81 d, err := a.NewI2cDevice(tc.dev) 82 // assert 83 if tc.wantErr != "" { 84 gobottest.Assert(t, err.Error(), tc.wantErr) 85 gobottest.Assert(t, d, (*i2cDevice)(nil)) 86 } else { 87 var _ gobot.I2cSystemDevicer = d 88 gobottest.Assert(t, err, nil) 89 } 90 }) 91 } 92 } 93 94 func TestClose(t *testing.T) { 95 // arrange 96 d, _ := initTestI2cDeviceWithMockedSys() 97 // act & assert 98 gobottest.Assert(t, d.Close(), nil) 99 } 100 101 func TestWriteRead(t *testing.T) { 102 // arrange 103 d, _ := initTestI2cDeviceWithMockedSys() 104 wbuf := []byte{0x01, 0x02, 0x03} 105 rbuf := make([]byte, 4) 106 // act 107 wn, werr := d.Write(1, wbuf) 108 rn, rerr := d.Read(1, rbuf) 109 // assert 110 gobottest.Assert(t, werr, nil) 111 gobottest.Assert(t, rerr, nil) 112 gobottest.Assert(t, wn, len(wbuf)) 113 gobottest.Assert(t, rn, len(wbuf)) // will read only the written values 114 gobottest.Assert(t, wbuf, rbuf[:len(wbuf)]) 115 } 116 117 func TestReadByte(t *testing.T) { 118 var tests = map[string]struct { 119 funcs uint64 120 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 121 wantErr string 122 }{ 123 "read_byte_ok": { 124 funcs: I2C_FUNC_SMBUS_READ_BYTE, 125 }, 126 "error_syscall": { 127 funcs: I2C_FUNC_SMBUS_READ_BYTE, 128 syscallImpl: getSyscallFuncImpl(0x04), 129 wantErr: "SMBus access r/w: 1, command: 0, protocol: 1, address: 2 failed with syscall.Errno operation not permitted", 130 }, 131 "error_not_supported": { 132 wantErr: "SMBus read byte not supported", 133 }, 134 } 135 for name, tc := range tests { 136 t.Run(name, func(t *testing.T) { 137 // arrange 138 d, msc := initTestI2cDeviceWithMockedSys() 139 msc.Impl = tc.syscallImpl 140 d.funcs = tc.funcs 141 const want = byte(5) 142 msc.dataSlice = []byte{want} 143 // act 144 got, err := d.ReadByte(2) 145 // assert 146 if tc.wantErr != "" { 147 gobottest.Assert(t, err.Error(), tc.wantErr) 148 } else { 149 gobottest.Assert(t, err, nil) 150 gobottest.Assert(t, got, want) 151 gobottest.Assert(t, msc.lastFile, d.file) 152 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 153 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_READ)) 154 gobottest.Assert(t, msc.smbus.command, byte(0)) // register is set to 0 in that case 155 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_BYTE)) 156 } 157 }) 158 } 159 } 160 161 func TestReadByteData(t *testing.T) { 162 var tests = map[string]struct { 163 funcs uint64 164 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 165 wantErr string 166 }{ 167 "read_byte_data_ok": { 168 funcs: I2C_FUNC_SMBUS_READ_BYTE_DATA, 169 }, 170 "error_syscall": { 171 funcs: I2C_FUNC_SMBUS_READ_BYTE_DATA, 172 syscallImpl: getSyscallFuncImpl(0x04), 173 wantErr: "SMBus access r/w: 1, command: 1, protocol: 2, address: 3 failed with syscall.Errno operation not permitted", 174 }, 175 "error_not_supported": { 176 wantErr: "SMBus read byte data not supported", 177 }, 178 } 179 for name, tc := range tests { 180 t.Run(name, func(t *testing.T) { 181 // arrange 182 d, msc := initTestI2cDeviceWithMockedSys() 183 msc.Impl = tc.syscallImpl 184 d.funcs = tc.funcs 185 const ( 186 reg = byte(0x01) 187 want = byte(0x02) 188 ) 189 msc.dataSlice = []byte{want} 190 // act 191 got, err := d.ReadByteData(3, reg) 192 // assert 193 if tc.wantErr != "" { 194 gobottest.Assert(t, err.Error(), tc.wantErr) 195 } else { 196 gobottest.Assert(t, err, nil) 197 gobottest.Assert(t, got, want) 198 gobottest.Assert(t, msc.lastFile, d.file) 199 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 200 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_READ)) 201 gobottest.Assert(t, msc.smbus.command, reg) 202 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_BYTE_DATA)) 203 } 204 }) 205 } 206 } 207 208 func TestReadWordData(t *testing.T) { 209 var tests = map[string]struct { 210 funcs uint64 211 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 212 wantErr string 213 }{ 214 "read_word_data_ok": { 215 funcs: I2C_FUNC_SMBUS_READ_WORD_DATA, 216 }, 217 "error_syscall": { 218 funcs: I2C_FUNC_SMBUS_READ_WORD_DATA, 219 syscallImpl: getSyscallFuncImpl(0x04), 220 wantErr: "SMBus access r/w: 1, command: 2, protocol: 3, address: 4 failed with syscall.Errno operation not permitted", 221 }, 222 "error_not_supported": { 223 wantErr: "SMBus read word data not supported", 224 }, 225 } 226 for name, tc := range tests { 227 t.Run(name, func(t *testing.T) { 228 // arrange 229 d, msc := initTestI2cDeviceWithMockedSys() 230 msc.Impl = tc.syscallImpl 231 d.funcs = tc.funcs 232 const ( 233 reg = byte(0x02) 234 msbyte = byte(0xD4) 235 lsbyte = byte(0x31) 236 want = uint16(54321) 237 ) 238 // all common drivers read LSByte first 239 msc.dataSlice = []byte{lsbyte, msbyte} 240 // act 241 got, err := d.ReadWordData(4, reg) 242 // assert 243 if tc.wantErr != "" { 244 gobottest.Assert(t, err.Error(), tc.wantErr) 245 } else { 246 gobottest.Assert(t, err, nil) 247 gobottest.Assert(t, got, want) 248 gobottest.Assert(t, msc.lastFile, d.file) 249 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 250 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_READ)) 251 gobottest.Assert(t, msc.smbus.command, reg) 252 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_WORD_DATA)) 253 } 254 }) 255 } 256 } 257 258 func TestReadBlockData(t *testing.T) { 259 // arrange 260 const ( 261 reg = byte(0x03) 262 wantB0 = byte(11) 263 wantB1 = byte(22) 264 wantB2 = byte(33) 265 wantB3 = byte(44) 266 wantB4 = byte(55) 267 wantB5 = byte(66) 268 wantB6 = byte(77) 269 wantB7 = byte(88) 270 wantB8 = byte(99) 271 wantB9 = byte(111) 272 ) 273 var tests = map[string]struct { 274 funcs uint64 275 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 276 wantErr string 277 }{ 278 "read_block_data_ok": { 279 funcs: I2C_FUNC_SMBUS_READ_I2C_BLOCK, 280 }, 281 "error_syscall": { 282 funcs: I2C_FUNC_SMBUS_READ_I2C_BLOCK, 283 syscallImpl: getSyscallFuncImpl(0x04), 284 wantErr: "SMBus access r/w: 1, command: 3, protocol: 8, address: 5 failed with syscall.Errno operation not permitted", 285 }, 286 "error_from_used_fallback_if_not_supported": { 287 wantErr: "Read 1 bytes from device by sysfs, expected 10", 288 }, 289 } 290 for name, tc := range tests { 291 t.Run(name, func(t *testing.T) { 292 // arrange 293 d, msc := initTestI2cDeviceWithMockedSys() 294 msc.Impl = tc.syscallImpl 295 d.funcs = tc.funcs 296 msc.dataSlice = []byte{wantB0, wantB1, wantB2, wantB3, wantB4, wantB5, wantB6, wantB7, wantB8, wantB9} 297 buf := []byte{12, 23, 34, 45, 56, 67, 78, 89, 98, 87} 298 // act 299 err := d.ReadBlockData(5, reg, buf) 300 // assert 301 if tc.wantErr != "" { 302 gobottest.Assert(t, err.Error(), tc.wantErr) 303 } else { 304 gobottest.Assert(t, err, nil) 305 gobottest.Assert(t, buf, msc.dataSlice) 306 gobottest.Assert(t, msc.lastFile, d.file) 307 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 308 gobottest.Assert(t, msc.sliceSize, uint8(len(buf)+1)) 309 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_READ)) 310 gobottest.Assert(t, msc.smbus.command, reg) 311 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_I2C_BLOCK_DATA)) 312 } 313 }) 314 } 315 } 316 317 func TestWriteByte(t *testing.T) { 318 var tests = map[string]struct { 319 funcs uint64 320 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 321 wantErr string 322 }{ 323 "write_byte_ok": { 324 funcs: I2C_FUNC_SMBUS_WRITE_BYTE, 325 }, 326 "error_syscall": { 327 funcs: I2C_FUNC_SMBUS_WRITE_BYTE, 328 syscallImpl: getSyscallFuncImpl(0x04), 329 wantErr: "SMBus access r/w: 0, command: 68, protocol: 1, address: 6 failed with syscall.Errno operation not permitted", 330 }, 331 "error_not_supported": { 332 wantErr: "SMBus write byte not supported", 333 }, 334 } 335 for name, tc := range tests { 336 t.Run(name, func(t *testing.T) { 337 // arrange 338 d, msc := initTestI2cDeviceWithMockedSys() 339 msc.Impl = tc.syscallImpl 340 d.funcs = tc.funcs 341 const val = byte(0x44) 342 // act 343 err := d.WriteByte(6, val) 344 // assert 345 if tc.wantErr != "" { 346 gobottest.Assert(t, err.Error(), tc.wantErr) 347 } else { 348 gobottest.Assert(t, err, nil) 349 gobottest.Assert(t, msc.lastFile, d.file) 350 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 351 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_WRITE)) 352 gobottest.Assert(t, msc.smbus.command, val) // in byte write, the register/command is used for the value 353 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_BYTE)) 354 } 355 }) 356 } 357 } 358 359 func TestWriteByteData(t *testing.T) { 360 var tests = map[string]struct { 361 funcs uint64 362 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 363 wantErr string 364 }{ 365 "write_byte_data_ok": { 366 funcs: I2C_FUNC_SMBUS_WRITE_BYTE_DATA, 367 }, 368 "error_syscall": { 369 funcs: I2C_FUNC_SMBUS_WRITE_BYTE_DATA, 370 syscallImpl: getSyscallFuncImpl(0x04), 371 wantErr: "SMBus access r/w: 0, command: 4, protocol: 2, address: 7 failed with syscall.Errno operation not permitted", 372 }, 373 "error_not_supported": { 374 wantErr: "SMBus write byte data not supported", 375 }, 376 } 377 for name, tc := range tests { 378 t.Run(name, func(t *testing.T) { 379 // arrange 380 d, msc := initTestI2cDeviceWithMockedSys() 381 msc.Impl = tc.syscallImpl 382 d.funcs = tc.funcs 383 const ( 384 reg = byte(0x04) 385 val = byte(0x55) 386 ) 387 // act 388 err := d.WriteByteData(7, reg, val) 389 // assert 390 if tc.wantErr != "" { 391 gobottest.Assert(t, err.Error(), tc.wantErr) 392 } else { 393 gobottest.Assert(t, err, nil) 394 gobottest.Assert(t, msc.lastFile, d.file) 395 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 396 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_WRITE)) 397 gobottest.Assert(t, msc.smbus.command, reg) 398 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_BYTE_DATA)) 399 gobottest.Assert(t, len(msc.dataSlice), 1) 400 gobottest.Assert(t, msc.dataSlice[0], val) 401 } 402 }) 403 } 404 } 405 406 func TestWriteWordData(t *testing.T) { 407 var tests = map[string]struct { 408 funcs uint64 409 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 410 wantErr string 411 }{ 412 "write_word_data_ok": { 413 funcs: I2C_FUNC_SMBUS_WRITE_WORD_DATA, 414 }, 415 "error_syscall": { 416 funcs: I2C_FUNC_SMBUS_WRITE_WORD_DATA, 417 syscallImpl: getSyscallFuncImpl(0x04), 418 wantErr: "SMBus access r/w: 0, command: 5, protocol: 3, address: 8 failed with syscall.Errno operation not permitted", 419 }, 420 "error_not_supported": { 421 wantErr: "SMBus write word data not supported", 422 }, 423 } 424 for name, tc := range tests { 425 t.Run(name, func(t *testing.T) { 426 // arrange 427 d, msc := initTestI2cDeviceWithMockedSys() 428 msc.Impl = tc.syscallImpl 429 d.funcs = tc.funcs 430 const ( 431 reg = byte(0x05) 432 val = uint16(54321) 433 wantLSByte = byte(0x31) 434 wantMSByte = byte(0xD4) 435 ) 436 // act 437 err := d.WriteWordData(8, reg, val) 438 // assert 439 if tc.wantErr != "" { 440 gobottest.Assert(t, err.Error(), tc.wantErr) 441 } else { 442 gobottest.Assert(t, err, nil) 443 gobottest.Assert(t, msc.lastFile, d.file) 444 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 445 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_WRITE)) 446 gobottest.Assert(t, msc.smbus.command, reg) 447 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_WORD_DATA)) 448 gobottest.Assert(t, len(msc.dataSlice), 2) 449 // all common drivers write LSByte first 450 gobottest.Assert(t, msc.dataSlice[0], wantLSByte) 451 gobottest.Assert(t, msc.dataSlice[1], wantMSByte) 452 } 453 }) 454 } 455 } 456 457 func TestWriteBlockData(t *testing.T) { 458 // arrange 459 const ( 460 reg = byte(0x06) 461 b0 = byte(0x09) 462 b1 = byte(0x11) 463 b2 = byte(0x22) 464 b3 = byte(0x33) 465 b4 = byte(0x44) 466 b5 = byte(0x55) 467 b6 = byte(0x66) 468 b7 = byte(0x77) 469 b8 = byte(0x88) 470 b9 = byte(0x99) 471 ) 472 var tests = map[string]struct { 473 funcs uint64 474 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 475 wantErr string 476 }{ 477 "write_block_data_ok": { 478 funcs: I2C_FUNC_SMBUS_WRITE_I2C_BLOCK, 479 }, 480 "error_syscall": { 481 funcs: I2C_FUNC_SMBUS_WRITE_I2C_BLOCK, 482 syscallImpl: getSyscallFuncImpl(0x04), 483 wantErr: "SMBus access r/w: 0, command: 6, protocol: 8, address: 9 failed with syscall.Errno operation not permitted", 484 }, 485 } 486 for name, tc := range tests { 487 t.Run(name, func(t *testing.T) { 488 // arrange 489 d, msc := initTestI2cDeviceWithMockedSys() 490 msc.Impl = tc.syscallImpl 491 d.funcs = tc.funcs 492 data := []byte{b0, b1, b2, b3, b4, b5, b6, b7, b8, b9} 493 // act 494 err := d.WriteBlockData(9, reg, data) 495 // assert 496 if tc.wantErr != "" { 497 gobottest.Assert(t, err.Error(), tc.wantErr) 498 } else { 499 gobottest.Assert(t, err, nil) 500 gobottest.Assert(t, msc.lastFile, d.file) 501 gobottest.Assert(t, msc.lastSignal, uintptr(I2C_SMBUS)) 502 gobottest.Assert(t, msc.sliceSize, uint8(len(data)+1)) // including size element 503 gobottest.Assert(t, msc.smbus.readWrite, byte(I2C_SMBUS_WRITE)) 504 gobottest.Assert(t, msc.smbus.command, reg) 505 gobottest.Assert(t, msc.smbus.protocol, uint32(I2C_SMBUS_I2C_BLOCK_DATA)) 506 gobottest.Assert(t, msc.dataSlice[0], uint8(len(data))) // data size 507 gobottest.Assert(t, msc.dataSlice[1:], data) 508 } 509 }) 510 } 511 } 512 513 func TestWriteBlockDataTooMuch(t *testing.T) { 514 // arrange 515 d, _ := initTestI2cDeviceWithMockedSys() 516 // act 517 err := d.WriteBlockData(10, 0x01, make([]byte, 33)) 518 // assert 519 gobottest.Assert(t, err, errors.New("Writing blocks larger than 32 bytes (33) not supported")) 520 } 521 522 func Test_setAddress(t *testing.T) { 523 // arrange 524 d, msc := initTestI2cDeviceWithMockedSys() 525 // act 526 err := d.setAddress(0xff) 527 // assert 528 gobottest.Assert(t, err, nil) 529 gobottest.Assert(t, msc.devAddress, uintptr(0xff)) 530 } 531 532 func Test_queryFunctionality(t *testing.T) { 533 var tests = map[string]struct { 534 requested uint64 535 dev string 536 syscallImpl func(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err syscall.Errno) 537 wantErr string 538 wantFile bool 539 wantFuncs uint64 540 }{ 541 "ok": { 542 requested: I2C_FUNC_SMBUS_READ_BYTE, 543 dev: dev, 544 syscallImpl: getSyscallFuncImpl(0x00), 545 wantFile: true, 546 wantFuncs: 0x7E0000, 547 }, 548 "dev_null_error": { 549 dev: os.DevNull, 550 syscallImpl: getSyscallFuncImpl(0x00), 551 wantErr: " : /dev/null: no such file", 552 }, 553 "query_funcs_error": { 554 dev: dev, 555 syscallImpl: getSyscallFuncImpl(0x01), 556 wantErr: "Querying functionality failed with syscall.Errno operation not permitted", 557 wantFile: true, 558 }, 559 } 560 for name, tc := range tests { 561 t.Run(name, func(t *testing.T) { 562 // arrange 563 d, msc := initTestI2cDeviceWithMockedSys() 564 d.location = tc.dev 565 msc.Impl = tc.syscallImpl 566 // act 567 err := d.queryFunctionality(tc.requested, "test"+name) 568 // assert 569 if tc.wantErr != "" { 570 gobottest.Assert(t, err.Error(), tc.wantErr) 571 } else { 572 gobottest.Assert(t, err, nil) 573 } 574 if tc.wantFile { 575 gobottest.Refute(t, d.file, nil) 576 } else { 577 gobottest.Assert(t, d.file, (*MockFile)(nil)) 578 } 579 gobottest.Assert(t, d.funcs, tc.wantFuncs) 580 }) 581 } 582 }