gobot.io/x/gobot/v2@v2.1.0/drivers/i2c/mcp23017_driver_test.go (about)

     1  package i2c
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  	"testing"
     7  
     8  	"gobot.io/x/gobot/v2"
     9  	"gobot.io/x/gobot/v2/gobottest"
    10  )
    11  
    12  // this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver
    13  // and tests all implementations, so no further tests needed here for gobot.Driver interface
    14  var _ gobot.Driver = (*MCP23017Driver)(nil)
    15  
    16  var pinValPort = map[string]interface{}{
    17  	"pin":  uint8(7),
    18  	"val":  uint8(0),
    19  	"port": "A",
    20  }
    21  
    22  var pinPort = map[string]interface{}{
    23  	"pin":  uint8(7),
    24  	"port": "A",
    25  }
    26  
    27  func initTestMCP23017(b uint8) (driver *MCP23017Driver) {
    28  	// create the driver without starting it
    29  	a := newI2cTestAdaptor()
    30  	d := NewMCP23017Driver(a, WithMCP23017Bank(b))
    31  	return d
    32  }
    33  
    34  func initTestMCP23017WithStubbedAdaptor(b uint8) (*MCP23017Driver, *i2cTestAdaptor) {
    35  	// create the driver, ready to use for tests
    36  	a := newI2cTestAdaptor()
    37  	d := NewMCP23017Driver(a, WithMCP23017Bank(b))
    38  	d.Start()
    39  	return d, a
    40  }
    41  
    42  func TestNewMCP23017Driver(t *testing.T) {
    43  	var di interface{} = NewMCP23017Driver(newI2cTestAdaptor())
    44  	d, ok := di.(*MCP23017Driver)
    45  	if !ok {
    46  		t.Errorf("NewMCP23017Driver() should have returned a *MCP23017Driver")
    47  	}
    48  	gobottest.Refute(t, d.Driver, nil)
    49  	gobottest.Assert(t, strings.HasPrefix(d.Name(), "MCP23017"), true)
    50  	gobottest.Assert(t, d.defaultAddress, 0x20)
    51  	gobottest.Refute(t, d.mcpConf, nil)
    52  	gobottest.Refute(t, d.mcpBehav, nil)
    53  }
    54  
    55  func TestWithMCP23017Bank(t *testing.T) {
    56  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Bank(1))
    57  	gobottest.Assert(t, b.mcpConf.bank, uint8(1))
    58  }
    59  
    60  func TestWithMCP23017Mirror(t *testing.T) {
    61  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Mirror(1))
    62  	gobottest.Assert(t, b.mcpConf.mirror, uint8(1))
    63  }
    64  
    65  func TestWithMCP23017Seqop(t *testing.T) {
    66  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Seqop(1))
    67  	gobottest.Assert(t, b.mcpConf.seqop, uint8(1))
    68  }
    69  
    70  func TestWithMCP23017Disslw(t *testing.T) {
    71  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Disslw(1))
    72  	gobottest.Assert(t, b.mcpConf.disslw, uint8(1))
    73  }
    74  
    75  func TestWithMCP23017Haen(t *testing.T) {
    76  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Haen(1))
    77  	gobottest.Assert(t, b.mcpConf.haen, uint8(1))
    78  }
    79  
    80  func TestWithMCP23017Odr(t *testing.T) {
    81  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Odr(1))
    82  	gobottest.Assert(t, b.mcpConf.odr, uint8(1))
    83  }
    84  
    85  func TestWithMCP23017Intpol(t *testing.T) {
    86  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017Intpol(1))
    87  	gobottest.Assert(t, b.mcpConf.intpol, uint8(1))
    88  }
    89  
    90  func TestWithMCP23017ForceRefresh(t *testing.T) {
    91  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017ForceRefresh(1))
    92  	gobottest.Assert(t, b.mcpBehav.forceRefresh, true)
    93  }
    94  
    95  func TestWithMCP23017AutoIODirOff(t *testing.T) {
    96  	b := NewMCP23017Driver(newI2cTestAdaptor(), WithMCP23017AutoIODirOff(1))
    97  	gobottest.Assert(t, b.mcpBehav.autoIODirOff, true)
    98  }
    99  
   100  func TestMCP23017CommandsWriteGPIO(t *testing.T) {
   101  	// arrange
   102  	d, _ := initTestMCP23017WithStubbedAdaptor(0)
   103  	// act
   104  	result := d.Command("WriteGPIO")(pinValPort)
   105  	// assert
   106  	gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
   107  }
   108  
   109  func TestMCP23017CommandsReadGPIO(t *testing.T) {
   110  	// arrange
   111  	d, _ := initTestMCP23017WithStubbedAdaptor(0)
   112  	// act
   113  	result := d.Command("ReadGPIO")(pinPort)
   114  	// assert
   115  	gobottest.Assert(t, result.(map[string]interface{})["err"], nil)
   116  }
   117  
   118  func TestMCP23017WriteGPIO(t *testing.T) {
   119  	// sequence to write (we force the refresh by preset with inverse bit state):
   120  	// * read current state of IODIR (write reg, read val) => see also SetPinMode()
   121  	// * set IODIR of pin to input (manipulate val, write reg, write val) => see also SetPinMode()
   122  	// * read current state of OLAT (write reg, read val)
   123  	// * write OLAT (manipulate val, write reg, write val)
   124  	// arrange
   125  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   126  	for bitState := 0; bitState <= 1; bitState++ {
   127  		a.written = []byte{} // reset writes of Start() and former test
   128  		// arrange some values
   129  		testPort := "A"
   130  		testPin := uint8(7)
   131  		wantReg1 := uint8(0x00)             // IODIRA
   132  		wantReg2 := uint8(0x14)             // OLATA
   133  		returnRead := []uint8{0xFF, 0xFF}   // emulate all IO's are inputs, emulate bit is on
   134  		wantReg1Val := returnRead[0] & 0x7F // IODIRA: bit 7 reset, all other untouched
   135  		wantReg2Val := returnRead[1] & 0x7F // OLATA: bit 7 reset, all other untouched
   136  		if bitState == 1 {
   137  			returnRead[1] = 0x7F               // emulate bit is off
   138  			wantReg2Val = returnRead[1] | 0x80 // OLATA: bit 7 set, all other untouched
   139  		}
   140  		// arrange reads
   141  		numCallsRead := 0
   142  		a.i2cReadImpl = func(b []byte) (int, error) {
   143  			numCallsRead++
   144  			b[len(b)-1] = returnRead[numCallsRead-1]
   145  			return len(b), nil
   146  		}
   147  		// act
   148  		err := d.WriteGPIO(testPin, testPort, uint8(bitState))
   149  		// assert
   150  		gobottest.Assert(t, err, nil)
   151  		gobottest.Assert(t, len(a.written), 6)
   152  		gobottest.Assert(t, a.written[0], wantReg1)
   153  		gobottest.Assert(t, a.written[1], wantReg1)
   154  		gobottest.Assert(t, a.written[2], wantReg1Val)
   155  		gobottest.Assert(t, a.written[3], wantReg2)
   156  		gobottest.Assert(t, a.written[4], wantReg2)
   157  		gobottest.Assert(t, a.written[5], wantReg2Val)
   158  		gobottest.Assert(t, numCallsRead, 2)
   159  	}
   160  }
   161  
   162  func TestMCP23017WriteGPIONoRefresh(t *testing.T) {
   163  	// sequence to write with take advantage of refresh optimization (see forceRefresh):
   164  	// * read current state of IODIR (write reg, read val) => by SetPinMode()
   165  	// * read current state of OLAT (write reg, read val)
   166  	// arrange
   167  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   168  	for bitState := 0; bitState <= 1; bitState++ {
   169  		a.written = []byte{} // reset writes of Start() and former test
   170  		// arrange some values
   171  		testPort := "B"
   172  		testPin := uint8(3)
   173  		wantReg1 := uint8(0x01)           // IODIRB
   174  		wantReg2 := uint8(0x15)           // OLATB
   175  		returnRead := []uint8{0xF7, 0xF7} // emulate all IO's are inputs except pin 3, emulate bit is already off
   176  		if bitState == 1 {
   177  			returnRead[1] = 0x08 // emulate bit is already on
   178  		}
   179  		// arrange reads
   180  		numCallsRead := 0
   181  		a.i2cReadImpl = func(b []byte) (int, error) {
   182  			numCallsRead++
   183  			b[len(b)-1] = returnRead[numCallsRead-1]
   184  			return len(b), nil
   185  		}
   186  		// act
   187  		err := d.WriteGPIO(testPin, testPort, uint8(bitState))
   188  		// assert
   189  		gobottest.Assert(t, err, nil)
   190  		gobottest.Assert(t, len(a.written), 2)
   191  		gobottest.Assert(t, a.written[0], wantReg1)
   192  		gobottest.Assert(t, a.written[1], wantReg2)
   193  		gobottest.Assert(t, numCallsRead, 2)
   194  	}
   195  }
   196  
   197  func TestMCP23017WriteGPIONoAutoDir(t *testing.T) {
   198  	// sequence to write with suppressed automatic setting of IODIR:
   199  	// * read current state of OLAT (write reg, read val)
   200  	// * write OLAT (manipulate val, write reg, write val)
   201  	// arrange
   202  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   203  	d.mcpBehav.autoIODirOff = true
   204  	for bitState := 0; bitState <= 1; bitState++ {
   205  		a.written = []byte{} // reset writes of Start() and former test
   206  		// arrange some values
   207  		testPort := "A"
   208  		testPin := uint8(7)
   209  		wantReg := uint8(0x14)          // OLATA
   210  		returnRead := uint8(0xFF)       // emulate bit is on
   211  		wantRegVal := returnRead & 0x7F // OLATA: bit 7 reset, all other untouched
   212  		if bitState == 1 {
   213  			returnRead = 0x7F              // emulate bit is off
   214  			wantRegVal = returnRead | 0x80 // OLATA: bit 7 set, all other untouched
   215  		}
   216  		// arrange reads
   217  		numCallsRead := 0
   218  		a.i2cReadImpl = func(b []byte) (int, error) {
   219  			numCallsRead++
   220  			b[len(b)-1] = returnRead
   221  			return len(b), nil
   222  		}
   223  		// act
   224  		err := d.WriteGPIO(testPin, testPort, uint8(bitState))
   225  		// assert
   226  		gobottest.Assert(t, err, nil)
   227  		gobottest.Assert(t, len(a.written), 3)
   228  		gobottest.Assert(t, a.written[0], wantReg)
   229  		gobottest.Assert(t, a.written[1], wantReg)
   230  		gobottest.Assert(t, a.written[2], wantRegVal)
   231  		gobottest.Assert(t, numCallsRead, 1)
   232  	}
   233  }
   234  
   235  func TestMCP23017CommandsWriteGPIOErrIODIR(t *testing.T) {
   236  	// arrange
   237  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   238  	a.i2cWriteImpl = func([]byte) (int, error) {
   239  		return 0, errors.New("write error")
   240  	}
   241  	// act
   242  	err := d.WriteGPIO(7, "A", 0)
   243  	// assert
   244  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=0): write error"))
   245  }
   246  
   247  func TestMCP23017CommandsWriteGPIOErrOLAT(t *testing.T) {
   248  	// arrange
   249  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   250  	numCalls := 1
   251  	a.i2cWriteImpl = func([]byte) (int, error) {
   252  		if numCalls == 2 {
   253  			return 0, errors.New("write error")
   254  		}
   255  		numCalls++
   256  		return 0, nil
   257  	}
   258  	// act
   259  	err := d.WriteGPIO(7, "A", 0)
   260  	// assert
   261  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=20): write error"))
   262  }
   263  
   264  func TestMCP23017ReadGPIO(t *testing.T) {
   265  	// sequence to read:
   266  	// * read current state of IODIR (write reg, read val) => see also SetPinMode()
   267  	// * set IODIR of pin to input (manipulate val, write reg, write val) => see also SetPinMode()
   268  	// * read GPIO (write reg, read val)
   269  	// arrange
   270  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   271  	for bitState := 0; bitState <= 1; bitState++ {
   272  		a.written = []byte{} // reset writes of Start() and former test
   273  		// arrange some values
   274  		testPort := "A"
   275  		testPin := uint8(7)
   276  		wantReg1 := uint8(0x00)             // IODIRA
   277  		wantReg2 := uint8(0x12)             // GPIOA
   278  		returnRead := []uint8{0x00, 0x7F}   // emulate all IO's are outputs, emulate bit is off
   279  		wantReg1Val := returnRead[0] | 0x80 // IODIRA: bit 7 set, all other untouched
   280  		if bitState == 1 {
   281  			returnRead[1] = 0xFF // emulate bit is set
   282  		}
   283  		// arrange reads
   284  		numCallsRead := 0
   285  		a.i2cReadImpl = func(b []byte) (int, error) {
   286  			numCallsRead++
   287  			b[len(b)-1] = returnRead[numCallsRead-1]
   288  			return len(b), nil
   289  		}
   290  		// act
   291  		val, err := d.ReadGPIO(testPin, testPort)
   292  		// assert
   293  		gobottest.Assert(t, err, nil)
   294  		gobottest.Assert(t, numCallsRead, 2)
   295  		gobottest.Assert(t, len(a.written), 4)
   296  		gobottest.Assert(t, a.written[0], wantReg1)
   297  		gobottest.Assert(t, a.written[1], wantReg1)
   298  		gobottest.Assert(t, a.written[2], wantReg1Val)
   299  		gobottest.Assert(t, a.written[3], wantReg2)
   300  		gobottest.Assert(t, val, uint8(bitState))
   301  	}
   302  }
   303  
   304  func TestMCP23017ReadGPIONoRefresh(t *testing.T) {
   305  	// sequence to read with take advantage of refresh optimization (see forceRefresh):
   306  	// * read current state of IODIR (write reg, read val) => by SetPinMode()
   307  	// * read GPIO (write reg, read val)
   308  	// arrange
   309  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   310  	for bitState := 0; bitState <= 1; bitState++ {
   311  		a.written = []byte{} // reset writes of Start() and former test
   312  		// arrange some values
   313  		testPort := "A"
   314  		testPin := uint8(7)
   315  		wantReg1 := uint8(0x00)           // IODIRA
   316  		wantReg2 := uint8(0x12)           // GPIOA
   317  		returnRead := []uint8{0x80, 0x7F} // emulate all IO's are outputs except pin 7, emulate bit is off
   318  		if bitState == 1 {
   319  			returnRead[1] = 0xFF // emulate bit is set
   320  		}
   321  		// arrange reads
   322  		numCallsRead := 0
   323  		a.i2cReadImpl = func(b []byte) (int, error) {
   324  			numCallsRead++
   325  			b[len(b)-1] = returnRead[numCallsRead-1]
   326  			return len(b), nil
   327  		}
   328  		// act
   329  		val, err := d.ReadGPIO(testPin, testPort)
   330  		// assert
   331  		gobottest.Assert(t, err, nil)
   332  		gobottest.Assert(t, numCallsRead, 2)
   333  		gobottest.Assert(t, len(a.written), 2)
   334  		gobottest.Assert(t, a.written[0], wantReg1)
   335  		gobottest.Assert(t, a.written[1], wantReg2)
   336  		gobottest.Assert(t, val, uint8(bitState))
   337  	}
   338  }
   339  
   340  func TestMCP23017ReadGPIONoAutoDir(t *testing.T) {
   341  	// sequence to read with suppressed automatic setting of IODIR:
   342  	// * read GPIO (write reg, read val)
   343  	// arrange
   344  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   345  	d.mcpBehav.autoIODirOff = true
   346  	for bitState := 0; bitState <= 1; bitState++ {
   347  		a.written = []byte{} // reset writes of Start() and former test
   348  		// arrange some values
   349  		testPort := "A"
   350  		testPin := uint8(7)
   351  		wantReg2 := uint8(0x12)   // GPIOA
   352  		returnRead := uint8(0x7F) // emulate bit is off
   353  		if bitState == 1 {
   354  			returnRead = 0xFF // emulate bit is set
   355  		}
   356  		// arrange reads
   357  		numCallsRead := 0
   358  		a.i2cReadImpl = func(b []byte) (int, error) {
   359  			numCallsRead++
   360  			b[len(b)-1] = returnRead
   361  			return len(b), nil
   362  		}
   363  		// act
   364  		val, err := d.ReadGPIO(testPin, testPort)
   365  		// assert
   366  		gobottest.Assert(t, err, nil)
   367  		gobottest.Assert(t, numCallsRead, 1)
   368  		gobottest.Assert(t, len(a.written), 1)
   369  		gobottest.Assert(t, a.written[0], wantReg2)
   370  		gobottest.Assert(t, val, uint8(bitState))
   371  	}
   372  }
   373  
   374  func TestMCP23017ReadGPIOErr(t *testing.T) {
   375  	// arrange
   376  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   377  	// arrange reads
   378  	a.i2cReadImpl = func(b []byte) (int, error) {
   379  		return len(b), errors.New("read error")
   380  	}
   381  	// act
   382  	_, err := d.ReadGPIO(7, "A")
   383  	// assert
   384  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=0): read error"))
   385  }
   386  
   387  func TestMCP23017SetPinMode(t *testing.T) {
   388  	// sequence for setting pin direction:
   389  	// * read current state of IODIR (write reg, read val)
   390  	// * set IODIR of pin to input or output (manipulate val, write reg, write val)
   391  	// TODO: can be optimized by not writing, when value is already fine
   392  	// arrange
   393  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   394  	for bitState := 0; bitState <= 1; bitState++ {
   395  		a.written = []byte{} // reset writes of Start() and former test
   396  		// arrange some values
   397  		testPort := "A"
   398  		testPin := uint8(7)
   399  		wantReg := uint8(0x00)          // IODIRA
   400  		returnRead := uint8(0xFF)       // emulate all ports are inputs
   401  		wantRegVal := returnRead & 0x7F // bit 7 reset, all other untouched
   402  		if bitState == 1 {
   403  			returnRead = 0x00              // emulate all ports are outputs
   404  			wantRegVal = returnRead | 0x80 // bit 7 set, all other untouched
   405  		}
   406  		// arrange reads
   407  		numCallsRead := 0
   408  		a.i2cReadImpl = func(b []byte) (int, error) {
   409  			numCallsRead++
   410  			b[len(b)-1] = returnRead
   411  			return len(b), nil
   412  		}
   413  		// act
   414  		err := d.SetPinMode(testPin, testPort, uint8(bitState))
   415  		// assert
   416  		gobottest.Assert(t, err, nil)
   417  		gobottest.Assert(t, len(a.written), 3)
   418  		gobottest.Assert(t, a.written[0], wantReg)
   419  		gobottest.Assert(t, a.written[1], wantReg)
   420  		gobottest.Assert(t, a.written[2], wantRegVal)
   421  		gobottest.Assert(t, numCallsRead, 1)
   422  	}
   423  }
   424  
   425  func TestMCP23017SetPinModeErr(t *testing.T) {
   426  	// arrange
   427  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   428  	a.i2cWriteImpl = func([]byte) (int, error) {
   429  		return 0, errors.New("write error")
   430  	}
   431  	// act
   432  	err := d.SetPinMode(7, "A", 0)
   433  	// assert
   434  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=0): write error"))
   435  }
   436  
   437  func TestMCP23017SetPullUp(t *testing.T) {
   438  	// sequence for setting input pin pull up:
   439  	// * read current state of GPPU (write reg, read val)
   440  	// * set GPPU of pin to target state (manipulate val, write reg, write val)
   441  	// TODO: can be optimized by not writing, when value is already fine
   442  	// arrange
   443  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   444  	for bitState := 0; bitState <= 1; bitState++ {
   445  		a.written = []byte{} // reset writes of Start()
   446  		// arrange some values
   447  		testPort := "A"
   448  		wantReg := uint8(0x0C) // GPPUA
   449  		testPin := uint8(5)
   450  		returnRead := uint8(0xFF)       // emulate all I's with pull up
   451  		wantSetVal := returnRead & 0xDF // bit 5 cleared, all other unchanged
   452  		if bitState == 1 {
   453  			returnRead = uint8(0x00)       // emulate all I's without pull up
   454  			wantSetVal = returnRead | 0x20 // bit 5 set, all other unchanged
   455  		}
   456  		// arrange reads
   457  		numCallsRead := 0
   458  		a.i2cReadImpl = func(b []byte) (int, error) {
   459  			numCallsRead++
   460  			b[len(b)-1] = returnRead
   461  			return len(b), nil
   462  		}
   463  		// act
   464  		err := d.SetPullUp(testPin, testPort, uint8(bitState))
   465  		// assert
   466  		gobottest.Assert(t, err, nil)
   467  		gobottest.Assert(t, len(a.written), 3)
   468  		gobottest.Assert(t, a.written[0], wantReg)
   469  		gobottest.Assert(t, a.written[1], wantReg)
   470  		gobottest.Assert(t, a.written[2], wantSetVal)
   471  		gobottest.Assert(t, numCallsRead, 1)
   472  	}
   473  }
   474  
   475  func TestMCP23017SetPullUpErr(t *testing.T) {
   476  	// arrange
   477  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   478  	a.i2cWriteImpl = func([]byte) (int, error) {
   479  		return 0, errors.New("write error")
   480  	}
   481  	// act
   482  	err := d.SetPullUp(7, "A", 0)
   483  	// assert
   484  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=12): write error"))
   485  }
   486  
   487  func TestMCP23017SetGPIOPolarity(t *testing.T) {
   488  	// sequence for setting input pin polarity:
   489  	// * read current state of IPOL (write reg, read val)
   490  	// * set IPOL of pin to target state (manipulate val, write reg, write val)
   491  	// TODO: can be optimized by not writing, when value is already fine
   492  	// arrange
   493  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   494  	for bitState := 0; bitState <= 1; bitState++ {
   495  		a.written = []byte{} // reset writes of Start()
   496  		// arrange some values
   497  		testPort := "B"
   498  		wantReg := uint8(0x03) // IPOLB
   499  		testPin := uint8(6)
   500  		returnRead := uint8(0xFF)       // emulate all I's negotiated
   501  		wantSetVal := returnRead & 0xBF // bit 6 cleared, all other unchanged
   502  		if bitState == 1 {
   503  			returnRead = uint8(0x00)       // emulate all I's not negotiated
   504  			wantSetVal = returnRead | 0x40 // bit 6 set, all other unchanged
   505  		}
   506  		// arrange reads
   507  		numCallsRead := 0
   508  		a.i2cReadImpl = func(b []byte) (int, error) {
   509  			numCallsRead++
   510  			b[len(b)-1] = returnRead
   511  			return len(b), nil
   512  		}
   513  		// act
   514  		err := d.SetGPIOPolarity(testPin, testPort, uint8(bitState))
   515  		// assert
   516  		gobottest.Assert(t, err, nil)
   517  		gobottest.Assert(t, len(a.written), 3)
   518  		gobottest.Assert(t, a.written[0], wantReg)
   519  		gobottest.Assert(t, a.written[1], wantReg)
   520  		gobottest.Assert(t, a.written[2], wantSetVal)
   521  		gobottest.Assert(t, numCallsRead, 1)
   522  	}
   523  }
   524  
   525  func TestMCP23017SetGPIOPolarityErr(t *testing.T) {
   526  	// arrange
   527  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   528  	a.i2cWriteImpl = func([]byte) (int, error) {
   529  		return 0, errors.New("write error")
   530  	}
   531  	// act
   532  	err := d.SetGPIOPolarity(7, "A", 0)
   533  	// assert
   534  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=2): write error"))
   535  }
   536  
   537  func TestMCP23017_write(t *testing.T) {
   538  	// clear bit
   539  	d, _ := initTestMCP23017WithStubbedAdaptor(0)
   540  	port := d.getPort("A")
   541  	err := d.write(port.IODIR, uint8(7), 0)
   542  	gobottest.Assert(t, err, nil)
   543  
   544  	// set bit
   545  	d, _ = initTestMCP23017WithStubbedAdaptor(0)
   546  	port = d.getPort("B")
   547  	err = d.write(port.IODIR, uint8(7), 1)
   548  	gobottest.Assert(t, err, nil)
   549  
   550  	// write error
   551  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   552  	a.i2cWriteImpl = func([]byte) (int, error) {
   553  		return 0, errors.New("write error")
   554  	}
   555  	err = d.write(port.IODIR, uint8(7), 0)
   556  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=1): write error"))
   557  
   558  	// read error
   559  	d, a = initTestMCP23017WithStubbedAdaptor(0)
   560  	a.i2cReadImpl = func(b []byte) (int, error) {
   561  		return len(b), errors.New("read error")
   562  	}
   563  	err = d.write(port.IODIR, uint8(7), 0)
   564  	gobottest.Assert(t, err, errors.New("MCP write-read: MCP write-ReadByteData(reg=1): read error"))
   565  	a.i2cReadImpl = func(b []byte) (int, error) {
   566  		return len(b), nil
   567  	}
   568  	err = d.write(port.IODIR, uint8(7), 1)
   569  	gobottest.Assert(t, err, nil)
   570  }
   571  
   572  func TestMCP23017_read(t *testing.T) {
   573  	// read
   574  	d, a := initTestMCP23017WithStubbedAdaptor(0)
   575  	port := d.getPort("A")
   576  	a.i2cReadImpl = func(b []byte) (int, error) {
   577  		copy(b, []byte{255})
   578  		return 1, nil
   579  	}
   580  	val, _ := d.read(port.IODIR)
   581  	gobottest.Assert(t, val, uint8(255))
   582  
   583  	// read error
   584  	d, a = initTestMCP23017WithStubbedAdaptor(0)
   585  	a.i2cReadImpl = func(b []byte) (int, error) {
   586  		return len(b), errors.New("read error")
   587  	}
   588  
   589  	val, err := d.read(port.IODIR)
   590  	gobottest.Assert(t, val, uint8(0))
   591  	gobottest.Assert(t, err, errors.New("MCP write-ReadByteData(reg=0): read error"))
   592  
   593  	// read
   594  	d, a = initTestMCP23017WithStubbedAdaptor(0)
   595  	port = d.getPort("A")
   596  	a.i2cReadImpl = func(b []byte) (int, error) {
   597  		copy(b, []byte{255})
   598  		return 1, nil
   599  	}
   600  	val, _ = d.read(port.IODIR)
   601  	gobottest.Assert(t, val, uint8(255))
   602  }
   603  
   604  func TestMCP23017_getPort(t *testing.T) {
   605  	// port A
   606  	d := initTestMCP23017(0)
   607  	expectedPort := mcp23017GetBank(0).portA
   608  	actualPort := d.getPort("A")
   609  	gobottest.Assert(t, expectedPort, actualPort)
   610  
   611  	// port B
   612  	d = initTestMCP23017(0)
   613  	expectedPort = mcp23017GetBank(0).portB
   614  	actualPort = d.getPort("B")
   615  	gobottest.Assert(t, expectedPort, actualPort)
   616  
   617  	// default
   618  	d = initTestMCP23017(0)
   619  	expectedPort = mcp23017GetBank(0).portA
   620  	actualPort = d.getPort("")
   621  	gobottest.Assert(t, expectedPort, actualPort)
   622  
   623  	// port A bank 1
   624  	d = initTestMCP23017(1)
   625  	expectedPort = mcp23017GetBank(1).portA
   626  	actualPort = d.getPort("")
   627  	gobottest.Assert(t, expectedPort, actualPort)
   628  }