1
0
mirror of https://github.com/hybridgroup/gobot.git synced 2025-04-26 13:48:49 +08:00

BUGFIX/Improvement: th02 i2c driver - wrong register usage for heater

This commit is contained in:
Thomas Kohler 2022-10-02 15:57:05 +02:00 committed by GitHub
commit b616a1aadd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 531 additions and 325 deletions

View File

@ -179,8 +179,8 @@ func newI2cTestAdaptor() *i2cTestAdaptor {
i2cReadImpl: func(b []byte) (int, error) {
return len(b), nil
},
i2cWriteImpl: func([]byte) (int, error) {
return 0, nil
i2cWriteImpl: func(b []byte) (int, error) {
return len(b), nil
},
}
}

View File

@ -24,38 +24,42 @@ package i2c
import (
"fmt"
"log"
"time"
"gobot.io/x/gobot"
)
const (
// TH02Address is the default address of device
TH02Address = 0x40
//TH02ConfigReg is the configuration register
TH02ConfigReg = 0x03
th02Debug = false
th02DefaultAddress = 0x40
)
//Accuracy constants for the TH02 devices
const (
TH02HighAccuracy = 0 //High Accuracy
TH02LowAccuracy = 1 //Lower Accuracy
th02Reg_Status = 0x00
th02Reg_DataMSB = 0x01
th02Reg_DataLSB = 0x02
th02Reg_Config = 0x03
th02Reg_ID = 0x11
th02Status_ReadyBit = 0x01 // D0 is /RDY
th02Config_StartBit = 0x01 // D0 is START
th02Config_HeatBit = 0x02 // D1 is HEAT
th02Config_TempBit = 0x10 // D4 is TEMP (if not set read humidity)
th02Config_FastBit = 0x20 // D5 is FAST (if set use 18 ms, but lower accuracy T: 13 bit, H: 11 bit)
)
//Accuracy constants for the TH02 devices (deprecated, use WithFastMode() instead)
const (
TH02HighAccuracy = 0 //High Accuracy (T: 14 bit, H: 12 bit), normal (35 ms)
TH02LowAccuracy = 1 //Lower Accuracy (T: 13 bit, H: 11 bit), fast (18 ms)
)
// TH02Driver is a Driver for a TH02 humidity and temperature sensor
type TH02Driver struct {
*Driver
Units string
name string
connector Connector
connection Connection
Config
addr byte
accuracy byte
heating bool
delay time.Duration
fastMode bool
}
// NewTH02Driver creates a new driver with specified i2c interface.
@ -71,16 +75,12 @@ type TH02Driver struct {
//
func NewTH02Driver(a Connector, options ...func(Config)) *TH02Driver {
s := &TH02Driver{
Driver: NewDriver(a, "TH02", th02DefaultAddress, options...),
Units: "C",
name: gobot.DefaultName("TH02"),
connector: a,
addr: TH02Address,
Config: NewConfig(),
heating: false,
fastMode: false,
}
s.SetAccuracy(1)
for _, option := range options {
option(s)
}
@ -88,87 +88,95 @@ func NewTH02Driver(a Connector, options ...func(Config)) *TH02Driver {
return s
}
// Name returns the name for this Driver
func (s *TH02Driver) Name() string { return s.name }
// SetName sets the name for this Driver
func (s *TH02Driver) SetName(n string) { s.name = n }
// Connection returns the connection for this Driver
func (s *TH02Driver) Connection() gobot.Connection { return s.connector.(gobot.Connection) }
// Start initializes the TH02
func (s *TH02Driver) Start() (err error) {
bus := s.GetBusOrDefault(s.connector.GetDefaultBus())
address := s.GetAddressOrDefault(int(s.addr))
s.connection, err = s.connector.GetConnection(address, bus)
return err
// WithTH02FastMode option sets the fast mode (leads to lower accuracy).
// Valid settings are <=0 (off), >0 (on).
func WithTH02FastMode(val int) func(Config) {
return func(c Config) {
d, ok := c.(*TH02Driver)
if ok {
d.fastMode = (val > 0)
} else if th02Debug {
log.Printf("Trying to set fast mode for non-TH02Driver %v", c)
}
}
}
// Halt returns true if devices is halted successfully
func (s *TH02Driver) Halt() (err error) { return }
// SetAddress sets the address of the device (deprecated, use WithAddress() instead)
func (s *TH02Driver) SetAddress(address int) {
WithAddress(address)(s)
}
// SetAddress sets the address of the device
func (s *TH02Driver) SetAddress(address int) { s.addr = byte(address) }
// Accuracy returns the accuracy of the sampling
func (s *TH02Driver) Accuracy() byte { return s.accuracy }
// SetAccuracy sets the accuracy of the sampling. It will only be used on the next
// measurment request. Invalid value will use the default of High
func (s *TH02Driver) SetAccuracy(a byte) {
if a == TH02LowAccuracy {
s.accuracy = a
} else {
s.accuracy = TH02HighAccuracy
// Accuracy returns the accuracy of the sampling (deprecated, use FastMode() instead)
func (s *TH02Driver) Accuracy() byte {
if s.fastMode {
return TH02LowAccuracy
}
return TH02HighAccuracy
}
// SetAccuracy sets the accuracy of the sampling. (deprecated, use WithFastMode() instead)
// It will only be used on the next measurement request. Invalid value will use the default of High
func (s *TH02Driver) SetAccuracy(a byte) {
s.fastMode = (a == TH02LowAccuracy)
}
// SerialNumber returns the serial number of the chip
func (s *TH02Driver) SerialNumber() (sn uint32, err error) {
ret, err := s.readRegister(0x11)
return uint32(ret) >> 4, err
func (s *TH02Driver) SerialNumber() (uint8, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
ret, err := s.connection.ReadByteData(th02Reg_ID)
return uint8(ret) >> 4, err
}
// Heater returns true if the heater is enabled
func (s *TH02Driver) Heater() (status bool, err error) {
st, err := s.readRegister(0x11)
return (0x02 & st) == 0x02, err
// FastMode returns true if the fast mode is enabled in the device
func (s *TH02Driver) FastMode() (bool, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
cfg, err := s.connection.ReadByteData(th02Reg_Config)
return (th02Config_FastBit & cfg) == th02Config_FastBit, err
}
func (s *TH02Driver) applysettings(base byte) byte {
if s.accuracy == TH02LowAccuracy {
base = base & 0xd5
} else {
base = base | 0x20
}
if s.heating {
base = base & 0xfd
} else {
base = base | 0x02
}
base = base | 0x01 //set the "sample" bit
return base
// SetHeater sets the heater of the device to the given state.
func (s *TH02Driver) SetHeater(state bool) error {
s.mutex.Lock()
defer s.mutex.Unlock()
s.heating = state
return s.connection.WriteByteData(th02Reg_Config, s.createConfig(false, false))
}
// Heater returns true if the heater is enabled in the device
func (s *TH02Driver) Heater() (bool, error) {
s.mutex.Lock()
defer s.mutex.Unlock()
cfg, err := s.connection.ReadByteData(th02Reg_Config)
return (th02Config_HeatBit & cfg) == th02Config_HeatBit, err
}
// Sample returns the temperature in celsius and relative humidity for one sample
func (s *TH02Driver) Sample() (temperature float32, relhumidity float32, _ error) {
s.mutex.Lock()
defer s.mutex.Unlock()
if err := s.writeRegister(TH02ConfigReg, s.applysettings(0x10)); err != nil {
// read humidity
if err := s.connection.WriteByteData(th02Reg_Config, s.createConfig(true, false)); err != nil {
return 0, 0, err
}
rawrh, err := s.readData()
rawrh, err := s.waitAndReadData()
if err != nil {
return 0, 0, err
}
relhumidity = float32(rawrh>>4)/16.0 - 24.0
if err := s.writeRegister(TH02ConfigReg, s.applysettings(0x00)); err != nil {
// read temperature
if err := s.connection.WriteByteData(th02Reg_Config, s.createConfig(true, true)); err != nil {
return 0, relhumidity, err
}
rawt, err := s.readData()
rawt, err := s.waitAndReadData()
if err != nil {
return 0, relhumidity, err
}
@ -183,27 +191,38 @@ func (s *TH02Driver) Sample() (temperature float32, relhumidity float32, _ error
}
//writeRegister writes the value to the register.
func (s *TH02Driver) writeRegister(reg, value byte) error {
_, err := s.connection.Write([]byte{reg, value})
return err
func (s *TH02Driver) createConfig(measurement bool, readTemp bool) byte {
cfg := byte(0x00)
if measurement {
cfg = cfg | th02Config_StartBit
if readTemp {
cfg = cfg | th02Config_TempBit
}
if s.fastMode {
cfg = cfg | th02Config_FastBit
}
}
if s.heating {
cfg = cfg | th02Config_HeatBit
}
return cfg
}
//readRegister returns the value of a single regusterm and a non-nil error on problem
func (s *TH02Driver) readRegister(reg byte) (byte, error) {
if _, err := s.connection.Write([]byte{reg}); err != nil {
func (s *TH02Driver) waitAndReadData() (uint16, error) {
if err := s.waitForReady(nil); err != nil {
return 0, err
}
rcvd := make([]byte, 1)
_, err := s.connection.Read(rcvd)
return rcvd[0], err
rcvd := make([]byte, 2)
err := s.connection.ReadBlockData(th02Reg_DataMSB, rcvd)
if err != nil {
return 0, err
}
return uint16(rcvd[0])<<8 + uint16(rcvd[1]), nil
}
/*waitForReady blocks for up to the passed duration (which defaults to 50mS if nil)
until the ~RDY bit is cleared, meanign a sample has been fully sampled and is ready for reading.
This is greedy.
*/
// waitForReady blocks for up to the passed duration (which defaults to 50mS if nil)
// until the ~RDY bit is cleared, meaning a sample has been fully sampled and is ready for reading.
func (s *TH02Driver) waitForReady(dur *time.Duration) error {
wait := 100 * time.Millisecond
if dur != nil {
@ -215,27 +234,9 @@ func (s *TH02Driver) waitForReady(dur *time.Duration) error {
return fmt.Errorf("timeout on \\RDY")
}
//yes, i am eating the error.
if reg, _ := s.readRegister(0x00); reg == 0 {
if reg, err := s.connection.ReadByteData(th02Reg_Status); (reg == 0) && (err == nil) {
return nil
}
time.Sleep(wait / 10)
}
}
/*readData fetches the data from the data 'registers'*/
func (s *TH02Driver) readData() (uint16, error) {
if err := s.waitForReady(nil); err != nil {
return 0, err
}
if n, err := s.connection.Write([]byte{0x01}); err != nil || n != 1 {
return 0, fmt.Errorf("n=%d not 1, or err = %v", n, err)
}
rcvd := make([]byte, 3)
n, err := s.connection.Read(rcvd)
if err != nil || n != 3 {
return 0, fmt.Errorf("n=%d not 3, or err = %v", n, err)
}
return uint16(rcvd[1])<<8 + uint16(rcvd[2]), nil
}

View File

@ -1,7 +1,6 @@
package i2c
import (
"errors"
"fmt"
"testing"
"time"
@ -12,176 +11,419 @@ import (
var _ gobot.Driver = (*TH02Driver)(nil)
// // --------- HELPERS
func initTestTH02Driver() *SHT3xDriver {
driver, _ := initTestSHT3xDriverWithStubbedAdaptor()
return driver
}
func initTestTH02DriverWithStubbedAdaptor() (*TH02Driver, *i2cTestAdaptor) {
func initTH02WithStubbedAdaptor() (*TH02Driver, *i2cTestAdaptor) {
adaptor := newI2cTestAdaptor()
return NewTH02Driver(adaptor), adaptor
driver := NewTH02Driver(adaptor)
if err := driver.Start(); err != nil {
panic(err)
}
return driver, adaptor
}
// --------- TESTS
func TestNewTH02Driver(t *testing.T) {
i2cd := newI2cTestAdaptor()
defer i2cd.Close()
// Does it return a pointer to an instance of SHT3xDriver?
var iface interface{} = NewTH02Driver(i2cd)
_, ok := iface.(*TH02Driver)
var id interface{} = NewTH02Driver(newI2cTestAdaptor())
d, ok := id.(*TH02Driver)
if !ok {
t.Errorf("NewTH02Driver() should have returned a *NewTH02Driver")
}
b := NewTH02Driver(i2cd, func(Config) {})
gobottest.Refute(t, b.Connection(), nil)
//cover some basically useless protions the Interface demands
if name := b.Name(); name != b.name {
t.Errorf("Didnt return the proper name. Got %q wanted %q", name, b.name)
}
if b.SetName("42"); b.name != "42" {
t.Errorf("yikes - didnt set name.")
}
gobottest.Refute(t, d.Driver, nil)
}
func TestTH02Driver_Accuracy(t *testing.T) {
i2cd := newI2cTestAdaptor()
defer i2cd.Close()
b := NewTH02Driver(i2cd)
func TestNewTH02DriverOptions(t *testing.T) {
// This is a general test, that options are applied in constructor by using the common options.
// Further tests for options can also be done by call of "WithOption(val)(d)".
d := NewTH02Driver(newI2cTestAdaptor(), WithBus(2), WithAddress(0x42))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
gobottest.Assert(t, d.GetAddressOrDefault(0x33), 0x42)
}
if b.SetAddress(0x42); b.addr != 0x42 {
t.Error("Didnt set address as expected")
}
func TestTH02SetAccuracy(t *testing.T) {
a := newI2cTestAdaptor()
b := NewTH02Driver(a)
if b.SetAccuracy(0x42); b.accuracy != TH02HighAccuracy {
if b.SetAccuracy(0x42); b.Accuracy() != TH02HighAccuracy {
t.Error("Setting an invalid accuracy should resolve to TH02HighAccuracy")
}
if b.SetAccuracy(TH02LowAccuracy); b.accuracy != TH02LowAccuracy {
if b.SetAccuracy(TH02LowAccuracy); b.Accuracy() != TH02LowAccuracy {
t.Error("Expected setting low accuracy to actually set to low accuracy")
}
if acc := b.Accuracy(); acc != TH02LowAccuracy {
t.Errorf("Accuract() didnt return what was expected")
t.Errorf("Accuracy() didn't return what was expected")
}
}
func TestTH022DriverStart(t *testing.T) {
b, _ := initTestTH02DriverWithStubbedAdaptor()
gobottest.Assert(t, b.Start(), nil)
func TestTH02WithFastMode(t *testing.T) {
var tests = map[string]struct {
value int
want bool
}{
"fast_on_for >0": {value: 1, want: true},
"fast_off_for =0": {value: 0, want: false},
"fast_off_for <0": {value: -1, want: false},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d := NewTH02Driver(newI2cTestAdaptor())
// act
WithTH02FastMode(tc.value)(d)
// assert
gobottest.Assert(t, d.fastMode, tc.want)
})
}
}
func TestTH02StartConnectError(t *testing.T) {
d, adaptor := initTestTH02DriverWithStubbedAdaptor()
adaptor.Testi2cConnectErr(true)
gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection"))
func TestTH02FastMode(t *testing.T) {
// sequence to read the fast mode status
// * write config register address (0x03)
// * read register content
// * if sixth bit (D5) is set, the fast mode is configured on, otherwise off
var tests = map[string]struct {
read uint8
want bool
}{
"fast on": {read: 0x20, want: true},
"fast off": {read: ^uint8(0x20), want: false},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, a := initTH02WithStubbedAdaptor()
a.i2cReadImpl = func(b []byte) (int, error) {
b[0] = tc.read
return len(b), nil
}
// act
got, err := d.FastMode()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(0x03))
gobottest.Assert(t, got, tc.want)
})
}
}
func TestTH02DriverHalt(t *testing.T) {
sht3x := initTestTH02Driver()
gobottest.Assert(t, sht3x.Halt(), nil)
func TestTH02SetHeater(t *testing.T) {
// sequence to set the heater status
// * set the local heater state
// * write config register address (0x03)
// * prepare config value by set/reset the heater bit (0x02, D1)
// * write the config value
var tests = map[string]struct {
heater bool
want uint8
}{
"heater on": {heater: true, want: 0x02},
"heater off": {heater: false, want: 0x00},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, a := initTH02WithStubbedAdaptor()
// act
err := d.SetHeater(tc.heater)
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, d.heating, tc.heater)
gobottest.Assert(t, len(a.written), 2)
gobottest.Assert(t, a.written[0], uint8(0x03))
gobottest.Assert(t, a.written[1], tc.want)
})
}
}
func TestTH02DriverOptions(t *testing.T) {
d := NewTH02Driver(newI2cTestAdaptor(), WithBus(2))
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
d.Halt()
func TestTH02Heater(t *testing.T) {
// sequence to read the heater status
// * write config register address (0x03)
// * read register content
// * if second bit (D1) is set, the heater is configured on, otherwise off
var tests = map[string]struct {
read uint8
want bool
}{
"heater on": {read: 0x02, want: true},
"heater off": {read: ^uint8(0x02), want: false},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, a := initTH02WithStubbedAdaptor()
a.i2cReadImpl = func(b []byte) (int, error) {
b[0] = tc.read
return len(b), nil
}
// act
got, err := d.Heater()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(0x03))
gobottest.Assert(t, got, tc.want)
})
}
}
func TestTH02Driver_ReadData(t *testing.T) {
d, i2cd := initTestTH02DriverWithStubbedAdaptor()
gobottest.Assert(t, d.Start(), nil)
func TestTH02SerialNumber(t *testing.T) {
// sequence to read SN
// * write identification register address (0x11)
// * read register content
// * use the higher nibble of byte
type x struct {
rd, wr func([]byte) (int, error)
// arrange
d, a := initTH02WithStubbedAdaptor()
a.i2cReadImpl = func(b []byte) (int, error) {
b[0] = 0x4F
return len(b), nil
}
want := uint8(0x04)
// act
sn, err := d.SerialNumber()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, len(a.written), 1)
gobottest.Assert(t, a.written[0], uint8(0x11))
gobottest.Assert(t, sn, want)
}
func TestTH02Sample(t *testing.T) {
// sequence to read values
// * write config register address (0x03)
// * prepare config bits (START, HEAT, TEMP, FAST)
// * write config register with config
// * write status register address (0x00)
// * read until value is "0" (means ready)
// * write data register MSB address (0x01)
// * read 2 bytes little-endian (MSB, LSB)
// * shift and scale
// RH: 4 bits shift right, RH[%]=RH/16-24
// T: 2 bits shift right, T[°C]=T/32-50
// test table according to data sheet page 15, 17
// operating range of the temperature sensor is -40..85 °C (F-grade 0..70 °C)
var tests = map[string]struct {
hData uint16
tData uint16
wantRH float32
wantT float32
}{
"RH 0, T -40": {
hData: 0x0180, wantRH: 0.0,
tData: 0x0140, wantT: -40.0,
},
"RH 10, T -20": {
hData: 0x0220, wantRH: 10.0,
tData: 0x03C0, wantT: -20.0,
},
"RH 20, T -10": {
hData: 0x02C0, wantRH: 20.0,
tData: 0x0500, wantT: -10.0,
},
"RH 30, T 0": {
hData: 0x0360, wantRH: 30.0,
tData: 0x0640, wantT: 0.0,
},
"RH 40, T 10": {
hData: 0x0400, wantRH: 40.0,
tData: 0x0780, wantT: 10.0,
},
"RH 50, T 20": {
hData: 0x04A0, wantRH: 50.0,
tData: 0x08C0, wantT: 20.0,
},
"RH 60, T 30": {
hData: 0x0540, wantRH: 60.0,
tData: 0x0A00, wantT: 30.0,
},
"RH 70, T 40": {
hData: 0x05E0, wantRH: 70.0,
tData: 0x0B40, wantT: 40.0,
},
"RH 80, T 50": {
hData: 0x0680, wantRH: 80.0,
tData: 0x0C80, wantT: 50.0,
},
"RH 90, T 60": {
hData: 0x0720, wantRH: 90.0,
tData: 0x0DC0, wantT: 60.0,
},
"RH 100, T 70": {
hData: 0x07C0, wantRH: 100.0,
tData: 0x0F00, wantT: 70.0,
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
d, a := initTH02WithStubbedAdaptor()
var reg uint8
var regVal uint8
a.i2cWriteImpl = func(b []byte) (int, error) {
reg = b[0]
if len(b) == 2 {
regVal = b[1]
}
return len(b), nil
}
a.i2cReadImpl = func(b []byte) (int, error) {
switch reg {
case 0x00:
// status
b[0] = 0
case 0x01:
// data register MSB
var data uint16
if (regVal & 0x10) == 0x10 {
// temperature
data = tc.tData << 2 // data sheet values are after shift 2 bits
} else {
// humidity
data = tc.hData << 4 // data sheet values are after shift 4 bits
}
b[0] = byte(data >> 8) // first read MSB from register 0x01
b[1] = byte(data & 0xFF) // second read LSB from register 0x02
default:
gobottest.Assert(t, fmt.Sprintf("unexpected register %d", reg), "only register 0 and 1 expected")
return 0, nil
}
return len(b), nil
}
// act
temp, rh, err := d.Sample()
// assert
gobottest.Assert(t, err, nil)
gobottest.Assert(t, rh, float32(tc.wantRH))
gobottest.Assert(t, temp, float32(tc.wantT))
})
}
}
func TestTH02_readData(t *testing.T) {
d, a := initTH02WithStubbedAdaptor()
var callCounter int
var tests = map[string]struct {
rd func([]byte) (int, error)
wr func([]byte) (int, error)
rtn uint16
errNil bool
}
tests := map[string]x{
"example RH": x{
wantErr error
}{
"example RH": {
rd: func(b []byte) (int, error) {
copy(b, []byte{0x00, 0x07, 0xC0})
return 3, nil
callCounter++
if callCounter == 1 {
// read for ready
b[0] = 0x00
} else {
copy(b, []byte{0x07, 0xC0})
}
return len(b), nil
},
wr: func([]byte) (int, error) {
return 1, nil
},
errNil: true,
rtn: 1984,
},
"example T": x{
"example T": {
rd: func(b []byte) (int, error) {
copy(b, []byte{0x00, 0x12, 0xC0})
return 3, nil
callCounter++
if callCounter == 1 {
// read for ready
b[0] = 0x00
} else {
copy(b, []byte{0x12, 0xC0})
}
return len(b), nil
},
wr: func([]byte) (int, error) {
return 1, nil
},
errNil: true,
rtn: 4800,
},
"timeout - no wait for ready": x{
"timeout - no wait for ready": {
rd: func(b []byte) (int, error) {
time.Sleep(200 * time.Millisecond)
copy(b, []byte{0x01})
return 1, fmt.Errorf("nope")
// simulate not ready
b[0] = 0x01
return len(b), nil
},
wr: func([]byte) (int, error) {
return 1, nil
},
errNil: false,
wantErr: fmt.Errorf("timeout on \\RDY"),
rtn: 0,
},
"unable to write status register": x{
"unable to write status register": {
rd: func(b []byte) (int, error) {
copy(b, []byte{0x00})
return 0, nil
callCounter++
if callCounter == 1 {
// read for ready
b[0] = 0x00
}
return len(b), nil
},
wr: func([]byte) (int, error) {
return 0, fmt.Errorf("Nope")
wr: func(b []byte) (int, error) {
return len(b), fmt.Errorf("an write error")
},
errNil: false,
wantErr: fmt.Errorf("timeout on \\RDY"),
rtn: 0,
},
"unable to read doesnt provide enought data": x{
"unable to write data register": {
rd: func(b []byte) (int, error) {
copy(b, []byte{0x00, 0x01})
return 2, nil
callCounter++
if callCounter == 1 {
// read for ready
b[0] = 0x00
}
return len(b), nil
},
wr: func([]byte) (int, error) {
return 1, nil
wr: func(b []byte) (int, error) {
if len(b) == 1 && b[0] == 0x00 {
// register of ready check
return len(b), nil
}
// data register
return len(b), fmt.Errorf("Nope")
},
errNil: false,
wantErr: fmt.Errorf("Nope"),
rtn: 0,
},
"unable to read doesn't provide enough data": {
rd: func(b []byte) (int, error) {
callCounter++
if callCounter == 1 {
// read for ready
b[0] = 0x00
} else {
b = []byte{0x01}
}
return len(b), nil
},
wantErr: fmt.Errorf("Read 1 bytes from device by i2c helpers, expected 2"),
rtn: 0,
},
}
for name, x := range tests {
t.Log("Running", name)
i2cd.i2cReadImpl = x.rd
i2cd.i2cWriteImpl = x.wr
got, err := d.readData()
gobottest.Assert(t, err == nil, x.errNil)
gobottest.Assert(t, got, x.rtn)
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// arrange
a.i2cReadImpl = tc.rd
if tc.wr != nil {
oldwr := a.i2cWriteImpl
a.i2cWriteImpl = tc.wr
defer func() { a.i2cWriteImpl = oldwr }()
}
callCounter = 0
// act
got, err := d.waitAndReadData()
// assert
gobottest.Assert(t, err, tc.wantErr)
gobottest.Assert(t, got, tc.rtn)
})
}
}
func TestTH02Driver_waitForReady(t *testing.T) {
d, i2cd := initTestTH02DriverWithStubbedAdaptor()
gobottest.Assert(t, d.Start(), nil)
func TestTH02_waitForReadyFailOnTimeout(t *testing.T) {
d, a := initTH02WithStubbedAdaptor()
i2cd.i2cReadImpl = func(b []byte) (int, error) {
a.i2cReadImpl = func(b []byte) (int, error) {
time.Sleep(50 * time.Millisecond)
copy(b, []byte{0x01, 0x00})
return 3, nil
}
i2cd.i2cWriteImpl = func([]byte) (int, error) {
return 1, nil
b[0] = 0x01
return len(b), nil
}
timeout := 10 * time.Microsecond
@ -190,93 +432,56 @@ func TestTH02Driver_waitForReady(t *testing.T) {
}
}
func TestTH02Driver_WriteRegister(t *testing.T) {
d, i2cd := initTestTH02DriverWithStubbedAdaptor()
gobottest.Assert(t, d.Start(), nil)
func TestTH02_waitForReadyFailOnReadError(t *testing.T) {
d, a := initTH02WithStubbedAdaptor()
i2cd.i2cWriteImpl = func([]byte) (int, error) {
return 1, nil
a.i2cReadImpl = func(b []byte) (int, error) {
time.Sleep(50 * time.Millisecond)
b[0] = 0x00
wrongLength := 2
return wrongLength, nil
}
if err := d.writeRegister(0x00, 0x00); err != nil {
t.Errorf("expected a nil error write")
timeout := 10 * time.Microsecond
if err := d.waitForReady(&timeout); err == nil {
t.Error("Expected a timeout error")
}
}
func TestTH02Driver_Heater(t *testing.T) {
d, i2cd := initTestTH02DriverWithStubbedAdaptor()
gobottest.Assert(t, d.Start(), nil)
i2cd.i2cReadImpl = func(b []byte) (int, error) {
copy(b, []byte{0xff})
return 1, nil
}
i2cd.i2cWriteImpl = func([]byte) (int, error) {
return 1, nil
}
on, err := d.Heater()
gobottest.Assert(t, on, true)
gobottest.Assert(t, err, nil)
}
func TestTH02Driver_SerialNumber(t *testing.T) {
d, i2cd := initTestTH02DriverWithStubbedAdaptor()
gobottest.Assert(t, d.Start(), nil)
i2cd.i2cReadImpl = func(b []byte) (int, error) {
copy(b, []byte{0x42})
return 1, nil
}
i2cd.i2cWriteImpl = func([]byte) (int, error) {
return 1, nil
}
sn, err := d.SerialNumber()
gobottest.Assert(t, sn, uint32((0x42)>>4))
gobottest.Assert(t, err, nil)
}
func TestTH02Driver_ApplySettings(t *testing.T) {
func TestTH02_createConfig(t *testing.T) {
d := &TH02Driver{}
type x struct {
acc, base, out byte
var tests = map[string]struct {
meas bool
fast bool
readTemp bool
heating bool
want byte
}{
"meas, no fast, RH, no heating": {meas: true, fast: false, readTemp: false, heating: false, want: 0x01},
"meas, no fast, RH, heating": {meas: true, fast: false, readTemp: false, heating: true, want: 0x03},
"meas, no fast, TE, no heating": {meas: true, fast: false, readTemp: true, heating: false, want: 0x11},
"meas, no fast, TE, heating": {meas: true, fast: false, readTemp: true, heating: true, want: 0x13},
"meas, fast, RH, no heating": {meas: true, fast: true, readTemp: false, heating: false, want: 0x21},
"meas, fast, RH, heating": {meas: true, fast: true, readTemp: false, heating: true, want: 0x23},
"meas, fast, TE, no heating": {meas: true, fast: true, readTemp: true, heating: false, want: 0x31},
"meas, fast, TE, heating": {meas: true, fast: true, readTemp: true, heating: true, want: 0x33},
"no meas, no fast, RH, no heating": {meas: false, fast: false, readTemp: false, heating: false, want: 0x00},
"no meas, no fast, RH, heating": {meas: false, fast: false, readTemp: false, heating: true, want: 0x02},
"no meas, no fast, TE, no heating": {meas: false, fast: false, readTemp: true, heating: false, want: 0x00},
"no meas, no fast, TE, heating": {meas: false, fast: false, readTemp: true, heating: true, want: 0x02},
"no meas, fast, RH, no heating": {meas: false, fast: true, readTemp: false, heating: false, want: 0x00},
"no meas, fast, RH, heating": {meas: false, fast: true, readTemp: false, heating: true, want: 0x02},
"no meas, fast, TE, no heating": {meas: false, fast: true, readTemp: true, heating: false, want: 0x00},
"no meas, fast, TE, heating": {meas: false, fast: true, readTemp: true, heating: true, want: 0x02},
}
tests := map[string]x{
"low acc, heating": x{acc: TH02LowAccuracy, base: 0x00, heating: true, out: 0x01},
"high acc, no heating": x{acc: TH02HighAccuracy, base: 0x00, heating: false, out: 0x23},
}
for name, x := range tests {
t.Log(name)
d.accuracy = x.acc
d.heating = x.heating
got := d.applysettings(x.base)
gobottest.Assert(t, x.out, got)
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
d.fastMode = tc.fast
d.heating = tc.heating
got := d.createConfig(tc.meas, tc.readTemp)
gobottest.Assert(t, tc.want, got)
})
}
}
func TestTH02Driver_Sample(t *testing.T) {
d, i2cd := initTestTH02DriverWithStubbedAdaptor()
gobottest.Assert(t, d.Start(), nil)
i2cd.i2cReadImpl = func(b []byte) (int, error) {
copy(b, []byte{0x00, 0x00, 0x07, 0xC0})
return 4, nil
}
i2cd.i2cWriteImpl = func([]byte) (int, error) {
return 1, nil
}
temp, rh, _ := d.Sample()
gobottest.Assert(t, temp, float32(0))
gobottest.Assert(t, rh, float32(0))
}