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:
commit
b616a1aadd
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
Units string
|
||||
name string
|
||||
connector Connector
|
||||
connection Connection
|
||||
Config
|
||||
addr byte
|
||||
accuracy byte
|
||||
*Driver
|
||||
Units string
|
||||
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{
|
||||
Units: "C",
|
||||
name: gobot.DefaultName("TH02"),
|
||||
connector: a,
|
||||
addr: TH02Address,
|
||||
Config: NewConfig(),
|
||||
heating: false,
|
||||
Driver: NewDriver(a, "TH02", th02DefaultAddress, options...),
|
||||
Units: "C",
|
||||
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
|
||||
|
||||
}
|
||||
|
@ -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 TestTH02StartConnectError(t *testing.T) {
|
||||
d, adaptor := initTestTH02DriverWithStubbedAdaptor()
|
||||
adaptor.Testi2cConnectErr(true)
|
||||
gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection"))
|
||||
}
|
||||
|
||||
func TestTH02DriverHalt(t *testing.T) {
|
||||
sht3x := initTestTH02Driver()
|
||||
gobottest.Assert(t, sht3x.Halt(), nil)
|
||||
}
|
||||
|
||||
func TestTH02DriverOptions(t *testing.T) {
|
||||
d := NewTH02Driver(newI2cTestAdaptor(), WithBus(2))
|
||||
gobottest.Assert(t, d.GetBusOrDefault(1), 2)
|
||||
d.Halt()
|
||||
}
|
||||
|
||||
func TestTH02Driver_ReadData(t *testing.T) {
|
||||
d, i2cd := initTestTH02DriverWithStubbedAdaptor()
|
||||
gobottest.Assert(t, d.Start(), nil)
|
||||
|
||||
type x struct {
|
||||
rd, wr func([]byte) (int, error)
|
||||
rtn uint16
|
||||
errNil bool
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
tests := map[string]x{
|
||||
"example RH": x{
|
||||
rd: func(b []byte) (int, error) {
|
||||
copy(b, []byte{0x00, 0x07, 0xC0})
|
||||
return 3, nil
|
||||
},
|
||||
wr: func([]byte) (int, error) {
|
||||
return 1, nil
|
||||
},
|
||||
errNil: true,
|
||||
rtn: 1984,
|
||||
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 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 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 TestTH02SerialNumber(t *testing.T) {
|
||||
// sequence to read SN
|
||||
// * write identification register address (0x11)
|
||||
// * read register content
|
||||
// * use the higher nibble of byte
|
||||
|
||||
// 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,
|
||||
},
|
||||
"example T": x{
|
||||
rd: func(b []byte) (int, error) {
|
||||
copy(b, []byte{0x00, 0x12, 0xC0})
|
||||
return 3, nil
|
||||
},
|
||||
wr: func([]byte) (int, error) {
|
||||
return 1, nil
|
||||
},
|
||||
errNil: true,
|
||||
rtn: 4800,
|
||||
"RH 10, T -20": {
|
||||
hData: 0x0220, wantRH: 10.0,
|
||||
tData: 0x03C0, wantT: -20.0,
|
||||
},
|
||||
"timeout - no wait for ready": x{
|
||||
"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
|
||||
wantErr error
|
||||
}{
|
||||
"example RH": {
|
||||
rd: func(b []byte) (int, error) {
|
||||
callCounter++
|
||||
if callCounter == 1 {
|
||||
// read for ready
|
||||
b[0] = 0x00
|
||||
} else {
|
||||
copy(b, []byte{0x07, 0xC0})
|
||||
}
|
||||
return len(b), nil
|
||||
},
|
||||
rtn: 1984,
|
||||
},
|
||||
"example T": {
|
||||
rd: func(b []byte) (int, error) {
|
||||
callCounter++
|
||||
if callCounter == 1 {
|
||||
// read for ready
|
||||
b[0] = 0x00
|
||||
} else {
|
||||
copy(b, []byte{0x12, 0xC0})
|
||||
}
|
||||
return len(b), nil
|
||||
},
|
||||
rtn: 4800,
|
||||
},
|
||||
"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,
|
||||
rtn: 0,
|
||||
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,
|
||||
rtn: 0,
|
||||
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,
|
||||
rtn: 0,
|
||||
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
|
||||
heating bool
|
||||
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))
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user