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 }