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

     1  package i2c
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  	"time"
     8  
     9  	"gobot.io/x/gobot/v2"
    10  	"gobot.io/x/gobot/v2/gobottest"
    11  )
    12  
    13  // this ensures that the implementation is based on i2c.Driver, which implements the gobot.Driver
    14  // and tests all implementations, so no further tests needed here for gobot.Driver interface
    15  var _ gobot.Driver = (*TH02Driver)(nil)
    16  
    17  func initTestTH02DriverWithStubbedAdaptor() (*TH02Driver, *i2cTestAdaptor) {
    18  	adaptor := newI2cTestAdaptor()
    19  	driver := NewTH02Driver(adaptor)
    20  	if err := driver.Start(); err != nil {
    21  		panic(err)
    22  	}
    23  	return driver, adaptor
    24  }
    25  
    26  func TestNewTH02Driver(t *testing.T) {
    27  	var di interface{} = NewTH02Driver(newI2cTestAdaptor())
    28  	d, ok := di.(*TH02Driver)
    29  	if !ok {
    30  		t.Errorf("NewTH02Driver() should have returned a *NewTH02Driver")
    31  	}
    32  	gobottest.Refute(t, d.Driver, nil)
    33  	gobottest.Assert(t, strings.HasPrefix(d.Name(), "TH02"), true)
    34  	gobottest.Assert(t, d.defaultAddress, 0x40)
    35  }
    36  
    37  func TestTH02Options(t *testing.T) {
    38  	// This is a general test, that options are applied in constructor by using the common options.
    39  	// Further tests for options can also be done by call of "WithOption(val)(d)".
    40  	d := NewTH02Driver(newI2cTestAdaptor(), WithBus(2), WithAddress(0x42))
    41  	gobottest.Assert(t, d.GetBusOrDefault(1), 2)
    42  	gobottest.Assert(t, d.GetAddressOrDefault(0x33), 0x42)
    43  }
    44  
    45  func TestTH02SetAccuracy(t *testing.T) {
    46  	b := NewTH02Driver(newI2cTestAdaptor())
    47  
    48  	if b.SetAccuracy(0x42); b.Accuracy() != TH02HighAccuracy {
    49  		t.Error("Setting an invalid accuracy should resolve to TH02HighAccuracy")
    50  	}
    51  
    52  	if b.SetAccuracy(TH02LowAccuracy); b.Accuracy() != TH02LowAccuracy {
    53  		t.Error("Expected setting low accuracy to actually set to low accuracy")
    54  	}
    55  
    56  	if acc := b.Accuracy(); acc != TH02LowAccuracy {
    57  		t.Errorf("Accuracy() didn't return what was expected")
    58  	}
    59  }
    60  
    61  func TestTH02WithFastMode(t *testing.T) {
    62  	var tests = map[string]struct {
    63  		value int
    64  		want  bool
    65  	}{
    66  		"fast_on_for >0":  {value: 1, want: true},
    67  		"fast_off_for =0": {value: 0, want: false},
    68  		"fast_off_for <0": {value: -1, want: false},
    69  	}
    70  	for name, tc := range tests {
    71  		t.Run(name, func(t *testing.T) {
    72  			// arrange
    73  			d := NewTH02Driver(newI2cTestAdaptor())
    74  			// act
    75  			WithTH02FastMode(tc.value)(d)
    76  			// assert
    77  			gobottest.Assert(t, d.fastMode, tc.want)
    78  		})
    79  	}
    80  }
    81  
    82  func TestTH02FastMode(t *testing.T) {
    83  	// sequence to read the fast mode status
    84  	// * write config register address (0x03)
    85  	// * read register content
    86  	// * if sixth bit (D5) is set, the fast mode is configured on, otherwise off
    87  	var tests = map[string]struct {
    88  		read uint8
    89  		want bool
    90  	}{
    91  		"fast on":  {read: 0x20, want: true},
    92  		"fast off": {read: ^uint8(0x20), want: false},
    93  	}
    94  	for name, tc := range tests {
    95  		t.Run(name, func(t *testing.T) {
    96  			// arrange
    97  			d, a := initTestTH02DriverWithStubbedAdaptor()
    98  			a.i2cReadImpl = func(b []byte) (int, error) {
    99  				b[0] = tc.read
   100  				return len(b), nil
   101  			}
   102  			// act
   103  			got, err := d.FastMode()
   104  			// assert
   105  			gobottest.Assert(t, err, nil)
   106  			gobottest.Assert(t, len(a.written), 1)
   107  			gobottest.Assert(t, a.written[0], uint8(0x03))
   108  			gobottest.Assert(t, got, tc.want)
   109  		})
   110  	}
   111  }
   112  
   113  func TestTH02SetHeater(t *testing.T) {
   114  	// sequence to set the heater status
   115  	// * set the local heater state
   116  	// * write config register address (0x03)
   117  	// * prepare config value by set/reset the heater bit (0x02, D1)
   118  	// * write the config value
   119  	var tests = map[string]struct {
   120  		heater bool
   121  		want   uint8
   122  	}{
   123  		"heater on":  {heater: true, want: 0x02},
   124  		"heater off": {heater: false, want: 0x00},
   125  	}
   126  	for name, tc := range tests {
   127  		t.Run(name, func(t *testing.T) {
   128  			// arrange
   129  			d, a := initTestTH02DriverWithStubbedAdaptor()
   130  			// act
   131  			err := d.SetHeater(tc.heater)
   132  			// assert
   133  			gobottest.Assert(t, err, nil)
   134  			gobottest.Assert(t, d.heating, tc.heater)
   135  			gobottest.Assert(t, len(a.written), 2)
   136  			gobottest.Assert(t, a.written[0], uint8(0x03))
   137  			gobottest.Assert(t, a.written[1], tc.want)
   138  		})
   139  	}
   140  }
   141  
   142  func TestTH02Heater(t *testing.T) {
   143  	// sequence to read the heater status
   144  	// * write config register address (0x03)
   145  	// * read register content
   146  	// * if second bit (D1) is set, the heater is configured on, otherwise off
   147  	var tests = map[string]struct {
   148  		read uint8
   149  		want bool
   150  	}{
   151  		"heater on":  {read: 0x02, want: true},
   152  		"heater off": {read: ^uint8(0x02), want: false},
   153  	}
   154  	for name, tc := range tests {
   155  		t.Run(name, func(t *testing.T) {
   156  			// arrange
   157  			d, a := initTestTH02DriverWithStubbedAdaptor()
   158  			a.i2cReadImpl = func(b []byte) (int, error) {
   159  				b[0] = tc.read
   160  				return len(b), nil
   161  			}
   162  			// act
   163  			got, err := d.Heater()
   164  			// assert
   165  			gobottest.Assert(t, err, nil)
   166  			gobottest.Assert(t, len(a.written), 1)
   167  			gobottest.Assert(t, a.written[0], uint8(0x03))
   168  			gobottest.Assert(t, got, tc.want)
   169  		})
   170  	}
   171  }
   172  
   173  func TestTH02SerialNumber(t *testing.T) {
   174  	// sequence to read SN
   175  	// * write identification register address (0x11)
   176  	// * read register content
   177  	// * use the higher nibble of byte
   178  
   179  	// arrange
   180  	d, a := initTestTH02DriverWithStubbedAdaptor()
   181  	a.i2cReadImpl = func(b []byte) (int, error) {
   182  		b[0] = 0x4F
   183  		return len(b), nil
   184  	}
   185  	want := uint8(0x04)
   186  	// act
   187  	sn, err := d.SerialNumber()
   188  	// assert
   189  	gobottest.Assert(t, err, nil)
   190  	gobottest.Assert(t, len(a.written), 1)
   191  	gobottest.Assert(t, a.written[0], uint8(0x11))
   192  	gobottest.Assert(t, sn, want)
   193  }
   194  
   195  func TestTH02Sample(t *testing.T) {
   196  	// sequence to read values
   197  	// * write config register address (0x03)
   198  	// * prepare config bits (START, HEAT, TEMP, FAST)
   199  	// * write config register with config
   200  	// * write status register address (0x00)
   201  	// * read until value is "0" (means ready)
   202  	// * write data register MSB address (0x01)
   203  	// * read 2 bytes little-endian (MSB, LSB)
   204  	// * shift and scale
   205  	//    RH: 4 bits shift right, RH[%]=RH/16-24
   206  	//    T:  2 bits shift right, T[°C]=T/32-50
   207  
   208  	// test table according to data sheet page 15, 17
   209  	// operating range of the temperature sensor is -40..85 °C (F-grade 0..70 °C)
   210  	var tests = map[string]struct {
   211  		hData  uint16
   212  		tData  uint16
   213  		wantRH float32
   214  		wantT  float32
   215  	}{
   216  		"RH 0, T -40": {
   217  			hData: 0x0180, wantRH: 0.0,
   218  			tData: 0x0140, wantT: -40.0,
   219  		},
   220  		"RH 10, T -20": {
   221  			hData: 0x0220, wantRH: 10.0,
   222  			tData: 0x03C0, wantT: -20.0,
   223  		},
   224  		"RH 20, T -10": {
   225  			hData: 0x02C0, wantRH: 20.0,
   226  			tData: 0x0500, wantT: -10.0,
   227  		},
   228  		"RH 30, T 0": {
   229  			hData: 0x0360, wantRH: 30.0,
   230  			tData: 0x0640, wantT: 0.0,
   231  		},
   232  		"RH 40, T 10": {
   233  			hData: 0x0400, wantRH: 40.0,
   234  			tData: 0x0780, wantT: 10.0,
   235  		},
   236  		"RH 50, T 20": {
   237  			hData: 0x04A0, wantRH: 50.0,
   238  			tData: 0x08C0, wantT: 20.0,
   239  		},
   240  		"RH 60, T 30": {
   241  			hData: 0x0540, wantRH: 60.0,
   242  			tData: 0x0A00, wantT: 30.0,
   243  		},
   244  		"RH 70, T 40": {
   245  			hData: 0x05E0, wantRH: 70.0,
   246  			tData: 0x0B40, wantT: 40.0,
   247  		},
   248  		"RH 80, T 50": {
   249  			hData: 0x0680, wantRH: 80.0,
   250  			tData: 0x0C80, wantT: 50.0,
   251  		},
   252  		"RH 90, T 60": {
   253  			hData: 0x0720, wantRH: 90.0,
   254  			tData: 0x0DC0, wantT: 60.0,
   255  		},
   256  		"RH 100, T 70": {
   257  			hData: 0x07C0, wantRH: 100.0,
   258  			tData: 0x0F00, wantT: 70.0,
   259  		},
   260  	}
   261  	for name, tc := range tests {
   262  		t.Run(name, func(t *testing.T) {
   263  			// arrange
   264  			d, a := initTestTH02DriverWithStubbedAdaptor()
   265  			var reg uint8
   266  			var regVal uint8
   267  			a.i2cWriteImpl = func(b []byte) (int, error) {
   268  				reg = b[0]
   269  				if len(b) == 2 {
   270  					regVal = b[1]
   271  				}
   272  				return len(b), nil
   273  			}
   274  			a.i2cReadImpl = func(b []byte) (int, error) {
   275  				switch reg {
   276  				case 0x00:
   277  					// status
   278  					b[0] = 0
   279  				case 0x01:
   280  					// data register MSB
   281  					var data uint16
   282  					if (regVal & 0x10) == 0x10 {
   283  						// temperature
   284  						data = tc.tData << 2 // data sheet values are after shift 2 bits
   285  					} else {
   286  						// humidity
   287  						data = tc.hData << 4 // data sheet values are after shift 4 bits
   288  					}
   289  					b[0] = byte(data >> 8)   // first read MSB from register 0x01
   290  					b[1] = byte(data & 0xFF) // second read LSB from register 0x02
   291  				default:
   292  					gobottest.Assert(t, fmt.Sprintf("unexpected register %d", reg), "only register 0 and 1 expected")
   293  					return 0, nil
   294  				}
   295  				return len(b), nil
   296  			}
   297  			// act
   298  			temp, rh, err := d.Sample()
   299  			// assert
   300  			gobottest.Assert(t, err, nil)
   301  			gobottest.Assert(t, rh, float32(tc.wantRH))
   302  			gobottest.Assert(t, temp, float32(tc.wantT))
   303  		})
   304  	}
   305  }
   306  
   307  func TestTH02_readData(t *testing.T) {
   308  	d, a := initTestTH02DriverWithStubbedAdaptor()
   309  
   310  	var callCounter int
   311  
   312  	var tests = map[string]struct {
   313  		rd      func([]byte) (int, error)
   314  		wr      func([]byte) (int, error)
   315  		rtn     uint16
   316  		wantErr error
   317  	}{
   318  		"example RH": {
   319  			rd: func(b []byte) (int, error) {
   320  				callCounter++
   321  				if callCounter == 1 {
   322  					// read for ready
   323  					b[0] = 0x00
   324  				} else {
   325  					copy(b, []byte{0x07, 0xC0})
   326  				}
   327  				return len(b), nil
   328  			},
   329  			rtn: 1984,
   330  		},
   331  		"example T": {
   332  			rd: func(b []byte) (int, error) {
   333  				callCounter++
   334  				if callCounter == 1 {
   335  					// read for ready
   336  					b[0] = 0x00
   337  				} else {
   338  					copy(b, []byte{0x12, 0xC0})
   339  				}
   340  				return len(b), nil
   341  			},
   342  			rtn: 4800,
   343  		},
   344  		"timeout - no wait for ready": {
   345  			rd: func(b []byte) (int, error) {
   346  				time.Sleep(200 * time.Millisecond)
   347  				// simulate not ready
   348  				b[0] = 0x01
   349  				return len(b), nil
   350  			},
   351  			wantErr: fmt.Errorf("timeout on \\RDY"),
   352  			rtn:     0,
   353  		},
   354  		"unable to write status register": {
   355  			rd: func(b []byte) (int, error) {
   356  				callCounter++
   357  				if callCounter == 1 {
   358  					// read for ready
   359  					b[0] = 0x00
   360  				}
   361  				return len(b), nil
   362  			},
   363  			wr: func(b []byte) (int, error) {
   364  				return len(b), fmt.Errorf("an write error")
   365  			},
   366  			wantErr: fmt.Errorf("timeout on \\RDY"),
   367  			rtn:     0,
   368  		},
   369  		"unable to write data register": {
   370  			rd: func(b []byte) (int, error) {
   371  				callCounter++
   372  				if callCounter == 1 {
   373  					// read for ready
   374  					b[0] = 0x00
   375  				}
   376  				return len(b), nil
   377  			},
   378  			wr: func(b []byte) (int, error) {
   379  				if len(b) == 1 && b[0] == 0x00 {
   380  					// register of ready check
   381  					return len(b), nil
   382  				}
   383  				// data register
   384  				return len(b), fmt.Errorf("Nope")
   385  			},
   386  			wantErr: fmt.Errorf("Nope"),
   387  			rtn:     0,
   388  		},
   389  		"unable to read doesn't provide enough data": {
   390  			rd: func(b []byte) (int, error) {
   391  				callCounter++
   392  				if callCounter == 1 {
   393  					// read for ready
   394  					b[0] = 0x00
   395  				} else {
   396  					b = []byte{0x01}
   397  				}
   398  				return len(b), nil
   399  			},
   400  			wantErr: fmt.Errorf("Read 1 bytes from device by i2c helpers, expected 2"),
   401  			rtn:     0,
   402  		},
   403  	}
   404  
   405  	for name, tc := range tests {
   406  		t.Run(name, func(t *testing.T) {
   407  			// arrange
   408  			a.i2cReadImpl = tc.rd
   409  			if tc.wr != nil {
   410  				oldwr := a.i2cWriteImpl
   411  				a.i2cWriteImpl = tc.wr
   412  				defer func() { a.i2cWriteImpl = oldwr }()
   413  			}
   414  			callCounter = 0
   415  			// act
   416  			got, err := d.waitAndReadData()
   417  			// assert
   418  			gobottest.Assert(t, err, tc.wantErr)
   419  			gobottest.Assert(t, got, tc.rtn)
   420  		})
   421  	}
   422  }
   423  
   424  func TestTH02_waitForReadyFailOnTimeout(t *testing.T) {
   425  	d, a := initTestTH02DriverWithStubbedAdaptor()
   426  
   427  	a.i2cReadImpl = func(b []byte) (int, error) {
   428  		time.Sleep(50 * time.Millisecond)
   429  		b[0] = 0x01
   430  		return len(b), nil
   431  	}
   432  
   433  	timeout := 10 * time.Microsecond
   434  	if err := d.waitForReady(&timeout); err == nil {
   435  		t.Error("Expected a timeout error")
   436  	}
   437  }
   438  
   439  func TestTH02_waitForReadyFailOnReadError(t *testing.T) {
   440  	d, a := initTestTH02DriverWithStubbedAdaptor()
   441  
   442  	a.i2cReadImpl = func(b []byte) (int, error) {
   443  		time.Sleep(50 * time.Millisecond)
   444  		b[0] = 0x00
   445  		wrongLength := 2
   446  		return wrongLength, nil
   447  	}
   448  
   449  	timeout := 10 * time.Microsecond
   450  	if err := d.waitForReady(&timeout); err == nil {
   451  		t.Error("Expected a timeout error")
   452  	}
   453  }
   454  
   455  func TestTH02_createConfig(t *testing.T) {
   456  	d := &TH02Driver{}
   457  
   458  	var tests = map[string]struct {
   459  		meas     bool
   460  		fast     bool
   461  		readTemp bool
   462  		heating  bool
   463  		want     byte
   464  	}{
   465  		"meas, no fast, RH, no heating":    {meas: true, fast: false, readTemp: false, heating: false, want: 0x01},
   466  		"meas, no fast, RH, heating":       {meas: true, fast: false, readTemp: false, heating: true, want: 0x03},
   467  		"meas, no fast, TE, no heating":    {meas: true, fast: false, readTemp: true, heating: false, want: 0x11},
   468  		"meas, no fast, TE, heating":       {meas: true, fast: false, readTemp: true, heating: true, want: 0x13},
   469  		"meas, fast, RH, no heating":       {meas: true, fast: true, readTemp: false, heating: false, want: 0x21},
   470  		"meas, fast, RH, heating":          {meas: true, fast: true, readTemp: false, heating: true, want: 0x23},
   471  		"meas, fast, TE, no heating":       {meas: true, fast: true, readTemp: true, heating: false, want: 0x31},
   472  		"meas, fast, TE, heating":          {meas: true, fast: true, readTemp: true, heating: true, want: 0x33},
   473  		"no meas, no fast, RH, no heating": {meas: false, fast: false, readTemp: false, heating: false, want: 0x00},
   474  		"no meas, no fast, RH, heating":    {meas: false, fast: false, readTemp: false, heating: true, want: 0x02},
   475  		"no meas, no fast, TE, no heating": {meas: false, fast: false, readTemp: true, heating: false, want: 0x00},
   476  		"no meas, no fast, TE, heating":    {meas: false, fast: false, readTemp: true, heating: true, want: 0x02},
   477  		"no meas, fast, RH, no heating":    {meas: false, fast: true, readTemp: false, heating: false, want: 0x00},
   478  		"no meas, fast, RH, heating":       {meas: false, fast: true, readTemp: false, heating: true, want: 0x02},
   479  		"no meas, fast, TE, no heating":    {meas: false, fast: true, readTemp: true, heating: false, want: 0x00},
   480  		"no meas, fast, TE, heating":       {meas: false, fast: true, readTemp: true, heating: true, want: 0x02},
   481  	}
   482  
   483  	for name, tc := range tests {
   484  		t.Run(name, func(t *testing.T) {
   485  			d.fastMode = tc.fast
   486  			d.heating = tc.heating
   487  			got := d.createConfig(tc.meas, tc.readTemp)
   488  			gobottest.Assert(t, tc.want, got)
   489  		})
   490  	}
   491  }