Introduces driver for Sensirion SHT-4X Temperature/Humidity Sensors.

Move CRC8 logic into common package for resuability.
pull/109/head
George Sexton 12 months ago
parent f009056300
commit 8a85f99127

@ -0,0 +1,24 @@
// Copyright 2025 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
// Package common contains functions used across multiple packages. For
// example, a CRC8 calculation
package common
// CRC8 calculates the 8-bit CRC of the byte slice parameter and returns the
// calculated value. CRC bytes are used in sensors from TI and Sensirion.
func CRC8(bytes []byte) byte {
var crc byte = 0xff
for _, val := range bytes {
crc ^= val
for range 8 {
if (crc & 0x80) == 0 {
crc <<= 1
} else {
crc = (byte)((crc << 1) ^ 0x31)
}
}
}
return crc
}

@ -0,0 +1,24 @@
// Copyright 2025 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package common
import "testing"
func TestCRC8(t *testing.T) {
var tests = []struct {
bytes []byte
result byte
}{
{bytes: []byte{0xbe, 0xef}, result: 0x92},
{bytes: []byte{0x01, 0xa4}, result: 0x4d},
{bytes: []byte{0xab, 0xcd}, result: 0x6f},
}
for _, test := range tests {
res := CRC8(test.bytes)
if res != test.result {
t.Errorf("CRC8(%#v)!=0x%d received 0x%d", test.bytes, test.result, res)
}
}
}

@ -21,6 +21,7 @@ import (
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/i2c"
"periph.io/x/conn/v3/physic"
"periph.io/x/devices/v3/common"
)
type SampleRate uint16
@ -203,21 +204,6 @@ func countToHumidity(bytes []byte) physic.RelativeHumidity {
return physic.RelativeHumidity(f * float64(physic.PercentRH))
}
func crc8(bytes []byte) byte {
var crc byte = 0xff
for _, val := range bytes {
crc ^= val
for range 8 {
if (crc & 0x80) == 0 {
crc <<= 1
} else {
crc = (byte)((crc << 1) ^ 0x31)
}
}
}
return crc
}
// Halt shuts down the device. If a SenseContinuous operation is in progress,
// its aborted. Implements conn.Resource
func (dev *Dev) Halt() error {
@ -251,7 +237,7 @@ func (dev *Dev) Sense(env *physic.Env) error {
if err := dev.d.Tx(read, res); err != nil {
return fmt.Errorf("hdc302x: %w", err)
}
if crc8(res[:2]) != res[2] || crc8(res[3:5]) != res[5] {
if common.CRC8(res[:2]) != res[2] || common.CRC8(res[3:5]) != res[5] {
return errInvalidCRC
}
env.Temperature = countToTemperature(res)
@ -320,7 +306,7 @@ func (dev *Dev) readSerialNumber() int64 {
// this is a 6 byte value read in 3 parts
for range 3 {
err := dev.d.Tx(cmd, r)
if err != nil || (crc8(r[:2]) != r[2]) {
if err != nil || (common.CRC8(r[:2]) != r[2]) {
return result
}
result = result<<16 | (int64(r[0])<<8 | int64(r[1]))
@ -358,7 +344,7 @@ func (dev *Dev) readAlertValues(cfg *Configuration) error {
if err != nil {
return err
}
if crc8(r[:2]) != r[2] {
if common.CRC8(r[:2]) != r[2] {
return errInvalidCRC
}
wValue := uint16(r[0])<<8 | uint16(r[1])
@ -381,7 +367,7 @@ func (dev *Dev) readOffsets(cfg *Configuration) error {
if err := dev.d.Tx(readSetOffsets, r); err != nil {
return fmt.Errorf("hdc302x: %w", err)
}
if crc8(r[:2]) != r[2] {
if common.CRC8(r[:2]) != r[2] {
return errInvalidCRC
}
@ -424,7 +410,7 @@ func (dev *Dev) ReadStatus() (StatusWord, error) {
if err := dev.d.Tx(readStatus, r); err != nil {
return 0, err
}
if crc8(r[:2]) != r[2] {
if common.CRC8(r[:2]) != r[2] {
return 0, errInvalidCRC
}
_ = dev.d.Tx(clearStatus, nil)
@ -463,7 +449,7 @@ func (dev *Dev) setOffsets(cfg *Configuration) error {
computeTemperatureOffsetByte(cfg.TemperatureOffset),
0,
}
w[4] = crc8(w[2:4])
w[4] = common.CRC8(w[2:4])
return dev.d.Tx(w, nil)
}
@ -553,7 +539,7 @@ func (dev *Dev) setThresholds(typeAlert bool, tp *ThresholdPair) error {
wval := uint16(0)
wval = (humBits & 0xfe00) | tempBits>>7
w := []byte{cmds[pair][ix][0], cmds[pair][ix][1], byte(wval >> 8), byte(wval & 0xff), 0}
w[4] = crc8(w[2:4])
w[4] = common.CRC8(w[2:4])
err := dev.d.Tx(w, nil)
if err != nil {
return err
@ -609,7 +595,7 @@ func (dev *Dev) SetHeater(powerLevel HeaterPower) error {
byte((powerLevel >> 8) & 0xff),
byte(powerLevel & 0xff),
0}
setValue[4] = crc8(setValue[2:4])
setValue[4] = common.CRC8(setValue[2:4])
err := dev.d.Tx(setValue, nil)
if err != nil {
return err

@ -200,22 +200,6 @@ func shutdown(t *testing.T) {
}
}
func TestCRC(t *testing.T) {
var tests = []struct {
bytes []byte
result byte
}{
{bytes: []byte{0xbe, 0xef}, result: 0x92},
{bytes: []byte{0xab, 0xcd}, result: 0x6f},
}
for _, test := range tests {
res := crc8(test.bytes)
if res != test.result {
t.Errorf("crc8(%#v)!=0x%d receieved 0x%d", test.bytes, test.result, res)
}
}
}
// TestConversions tests the various temperature/humidity functions
// for correct operation.
func TestConversions(t *testing.T) {

@ -12,6 +12,7 @@ import (
"periph.io/x/conn/v3/i2c"
"periph.io/x/conn/v3/physic"
"periph.io/x/devices/v3/common"
)
// PPM=Parts Per Million. Units of measure for CO2 concentration.
@ -402,22 +403,6 @@ func (d *Dev) Reset(mode ResetMode) error {
return err
}
func calcCRC(bytes []byte) byte {
polynomial := byte(0x31)
crc := byte(0xff)
for ix := range len(bytes) {
crc ^= bytes[ix]
for crc_bit := byte(8); crc_bit > 0; crc_bit-- {
if (crc & 0x80) == 0x80 {
crc = (crc << 1) ^ polynomial
} else {
crc = (crc << 1)
}
}
}
return crc
}
// makeWriteData converts the slice of word values into byte values with the
// CRC following.
func makeWriteData(data []uint16) []byte {
@ -425,7 +410,7 @@ func makeWriteData(data []uint16) []byte {
for ix, val := range data {
bytes[ix*3] = byte((val >> 8) & 0xff)
bytes[ix*3+1] = byte(val & 0xff)
bytes[ix*3+2] = calcCRC(bytes[ix*3 : ix*3+2])
bytes[ix*3+2] = common.CRC8(bytes[ix*3 : ix*3+2])
}
return bytes
}
@ -464,7 +449,7 @@ func (d *Dev) sendCommand(cmd command, writeData []uint16) ([]uint16, error) {
// verify the CRC as we go.
result := make([]uint16, cmd.responseSize/3)
for ix := range len(result) {
crc := calcCRC(r[ix*3 : ix*3+2])
crc := common.CRC8(r[ix*3 : ix*3+2])
if r[ix*3+2] != crc {
return nil, fmt.Errorf("scd4x cmd 0x%x: invalid crc", cmd.cmdWord)
}

@ -154,22 +154,6 @@ func shutdown(t *testing.T) {
}
}
func TestCRC(t *testing.T) {
tests := []struct {
bytes []byte
crc byte
}{
{bytes: []byte{0xbe, 0xef}, crc: 0x92},
{bytes: []byte{0x01, 0xa4}, crc: 0x4d},
}
for _, test := range tests {
res := calcCRC(test.bytes)
if res != test.crc {
t.Error(fmt.Errorf("crc calculation error bytes: %#v, result: 0x%x expected: 0x%x", test.bytes, res, test.crc))
}
}
}
func TestCountToTemperature(t *testing.T) {
tests := []struct {
count uint16

@ -0,0 +1,44 @@
// Copyright 2025 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package sht4x_test
import (
"log"
"time"
"periph.io/x/conn/v3/i2c/i2creg"
"periph.io/x/conn/v3/physic"
"periph.io/x/devices/v3/sht4x"
"periph.io/x/host/v3"
)
// Example shows creating an SHT-4X sensor and reading from it.
func Example() {
if _, err := host.Init(); err != nil {
log.Fatal("Error calling host.init()")
}
bus, err := i2creg.Open("")
if err != nil {
log.Fatal(err)
}
defer bus.Close()
dev, err := sht4x.New(bus, sht4x.DefaultAddress)
if err != nil {
log.Fatal(err)
}
env := &physic.Env{}
for range 10 {
err = dev.Sense(env)
if err != nil {
log.Println(err)
} else {
log.Printf("Temperature: %s Humidity: %s\n", env.Temperature, env.Humidity)
}
time.Sleep(time.Second)
}
}

@ -0,0 +1,328 @@
// Copyright 2025 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
// sht4x is a package for interfacing with the Sensirion SHT-40, SHT-41, and
// SHT-45 sensors.
//
// # Datasheet
//
// https://sensirion.com/media/documents/33FD6951/67EB9032/HT_DS_Datasheet_SHT4x_5.pdf
//
// # Temperature Accuracy
//
// SHT-40 & SHT-41
//
// Typical accuracy: ±0.2°C
//
// Response time τ₆₃% ≈2s
//
// SHT-45
//
// Typical accuracy: ±0.1°C
//
// Response time τ₆₃% ≈2s
//
// # Humidity Accuracy
//
// SHT-40 (Baseclass)
//
// Typical accuracy at 25°C:±1.8%RH
//
// Maximum accuracy (at 25°C): up to ±3.5%RH
//
// SHT-41 (Intermediateclass)
//
// Typical accuracy at 25°C:±1.8%RH
//
// Maximum accuracy (at 25°C): up to ±2.5%RH
//
// SHT-45 (Highaccuracyclass)
//
// Typical accuracy at 25°C:±1.0%RH
//
// Maximum accuracy (at 25°C): up to ≈±1.75%RH
//
// All three share a resolution of 0.01%RH, a response time τ₆₃% ≈4s, and longterm drift <0.2%RH/year .
//
// All devices have a resolution of 0.01°C and specified range 40…+125°C .
package sht4x
import (
"errors"
"fmt"
"sync"
"time"
"periph.io/x/conn/v3"
"periph.io/x/conn/v3/i2c"
"periph.io/x/conn/v3/physic"
"periph.io/x/devices/v3/common"
)
// HeaterPower represents a type for the heater power setting.
type HeaterPower int
// HeaterDuration represents a duration for turning the heater on.
type HeaterDuration time.Duration
const (
// Power settings for the heater element.
Power20mW HeaterPower = iota
Power110mW
Power200mW
// Durations that you can turn the heater on for.
Duration100ms HeaterDuration = HeaterDuration(time.Duration(100 * time.Millisecond))
Duration1s HeaterDuration = HeaterDuration(time.Second)
// Default I2C Address
DefaultAddress i2c.Addr = 0x44
)
const (
// byte commands for device.
cmdHeater200mW1s byte = 0x39
cmdHeater200mW100ms byte = 0x32
cmdHeater110mW1s byte = 0x2f
cmdHeater110mW100ms byte = 0x24
cmdHeater20mW1s byte = 0x1e
cmdHeater20mW100ms byte = 0x15
cmdSoftReset byte = 0x94
// Read at highest precision and repeatability
cmdMeasure byte = 0xfd
cmdReadSerialNumber byte = 0x89
countDivisor = float64(65535)
minTemperature = -40*physic.Kelvin + physic.ZeroCelsius
maxTemperature = 125*physic.Kelvin + physic.ZeroCelsius
minRH = 0 * physic.PercentRH
maxRH = 100 * physic.PercentRH
minSampleDuration = 10 * time.Millisecond
)
// Dev represents a SHT-4X series temperature/humidity sensor
type Dev struct {
d *i2c.Dev
shutdown chan struct{}
mu sync.Mutex
}
func New(bus i2c.Bus, addr i2c.Addr) (*Dev, error) {
dev := &Dev{d: &i2c.Dev{Bus: bus, Addr: uint16(addr)}}
return dev, nil
}
// If you try to read immediately after a write with this device, you'll get an
// io error. This just wraps the write and adds a delay before attempting the
// read.
func (dev *Dev) txWithDelay(w, r *[]byte, delay time.Duration) (err error) {
if w != nil {
err = dev.d.Tx(*w, nil)
if err != nil {
err = fmt.Errorf("sht4x: error transmitting %w", err)
return
}
}
time.Sleep(delay)
if r != nil {
err = dev.d.Tx(nil, *r)
if err != nil {
err = fmt.Errorf("sht4x: error reading %w", err)
}
// All calls that return bytes return the same format. 2 bytes
// of data, a CRC, 2 bytes of data, and
// a CRC. Verify them
if common.CRC8((*r)[:2]) != (*r)[2] {
err = errors.New("sht4x: bytes[:2] read crc error")
}
if err == nil && common.CRC8((*r)[3:5]) != (*r)[5] {
err = errors.New("sht4x: bytes[3:5] read crc error")
}
}
return
}
// convert the count to a temperature value.
func countToTemp(count uint16) physic.Temperature {
// T=-45+175*(count/countDivisor)
val := physic.Temperature(float64(physic.Kelvin)*(-45.0+175.0*(float64(count)/countDivisor))) + physic.ZeroCelsius
if val < minTemperature {
val = minTemperature
} else if val > maxTemperature {
val = maxTemperature
}
return val
}
func countToHumidity(count uint16) physic.RelativeHumidity {
// RH=-6 + 125*(count/countDivisor)
val := physic.RelativeHumidity((-6.0 + 125.0*(float64(count)/countDivisor)) * float64(physic.PercentRH))
if val < minRH {
val = minRH
} else if val > maxRH {
val = maxRH
}
return val
}
// Precision returns the smallest change in readings the device can produce.
// Implements physic.SenseEnv.
func (dev *Dev) Precision(e *physic.Env) {
e.Temperature = physic.Kelvin / 100
e.Humidity = physic.PercentRH / 100
e.Pressure = 0
}
// Halt shuts down the device and terminates a SenseContinuous
// command if running. Implements conn.Resource
func (dev *Dev) Halt() error {
dev.mu.Lock()
defer dev.mu.Unlock()
if dev.shutdown != nil {
close(dev.shutdown)
}
return nil
}
// Reset issues a soft-reset to the device
func (dev *Dev) Reset() error {
dev.mu.Lock()
defer dev.mu.Unlock()
err := dev.d.Tx([]byte{cmdSoftReset}, nil)
if err != nil {
err = fmt.Errorf("sht4x: error resetting %w", err)
}
time.Sleep(2 * time.Millisecond)
return err
}
// Sense reads temperature and humidity from the device.
func (dev *Dev) Sense(e *physic.Env) error {
e.Pressure = 0
r := make([]byte, 6)
w := []byte{cmdMeasure}
err := dev.txWithDelay(&w, &r, 10*time.Millisecond)
if err != nil {
e.Temperature = minTemperature
e.Humidity = minRH
return fmt.Errorf("sht4x: error reading device %w", err)
}
e.Temperature = countToTemp(uint16(r[0])<<8 | uint16(r[1]))
e.Humidity = countToHumidity(uint16(r[3])<<8 | uint16(r[4]))
return nil
}
// SenseContinuous continuously reads from the device and sends the output
// to the returned channel. To terminate the read, call Dev.Halt()
func (dev *Dev) SenseContinuous(duration time.Duration) (<-chan physic.Env, error) {
if dev.shutdown != nil {
return nil, errors.New("sht4x: SenseContinuous already running")
}
if duration < minSampleDuration {
return nil, errors.New("sht4x: sample interval is < device sample rate")
}
dev.shutdown = make(chan struct{})
ch := make(chan (physic.Env), 16)
go func(ch chan<- physic.Env) {
ticker := time.NewTicker(duration)
defer ticker.Stop()
defer close(ch)
for {
select {
case <-dev.shutdown:
dev.mu.Lock()
defer dev.mu.Unlock()
dev.shutdown = nil
return
case <-ticker.C:
env := physic.Env{}
if err := dev.Sense(&env); err == nil {
ch <- env
}
}
}
}(ch)
return ch, nil
}
// SerialNumber returns the device serial number set at the factory.
func (dev *Dev) SerialNumber() (uint32, error) {
r := make([]byte, 6)
w := []byte{cmdReadSerialNumber}
dev.mu.Lock()
defer dev.mu.Unlock()
err := dev.txWithDelay(&w, &r, 10*time.Millisecond)
if err != nil {
return 0, err
}
result := uint32(r[0])<<24 | uint32(r[1])<<16 | uint32(r[3])<<8 | uint32(r[4])
return result, nil
}
// SetHeater enables the sensor's heater. You can specify the power level, and
// the duration. After duration has passed, the heater will be turned off
// automatically. Enabling the heater can allow operation in condensing
// environments.
//
// powerLevel is one of the HeaterPower constants, and duration is one of the
// heaterDuration constants, either 100ms, or 1000ms.
//
// Returns the temperature and humidity after the period has completed. Refer to
// section 4.9 of the datasheet.
func (dev *Dev) SetHeater(powerLevel HeaterPower, duration HeaterDuration) (physic.Env, error) {
env := physic.Env{Temperature: minTemperature, Humidity: minRH}
var cmd byte
switch duration {
case Duration100ms:
switch powerLevel {
case Power20mW:
cmd = cmdHeater20mW100ms
case Power110mW:
cmd = cmdHeater110mW100ms
case Power200mW:
cmd = cmdHeater200mW100ms
default:
return env, errors.New("sht4x: invalid heater power")
}
case Duration1s:
switch powerLevel {
case Power20mW:
cmd = cmdHeater20mW1s
case Power110mW:
cmd = cmdHeater110mW1s
case Power200mW:
cmd = cmdHeater200mW1s
default:
return env, errors.New("sht4x: invalid heater power")
}
default:
return env, errors.New("sht4x: invalid heater duration")
}
r := make([]byte, 6)
w := []byte{cmd}
dev.mu.Lock()
defer dev.mu.Unlock()
err := dev.txWithDelay(&w, &r, time.Duration(duration)+10*time.Millisecond)
if err != nil {
return env, fmt.Errorf("sht4x: error setting heater %w", err)
}
env.Temperature = countToTemp(uint16(r[0])<<8 | uint16(r[1]))
env.Humidity = countToHumidity(uint16(r[3])<<8 | uint16(r[4]))
return env, nil
}
// String returns a string representation of the device.
func (dev *Dev) String() string {
return "sht4x"
}
var _ conn.Resource = &Dev{}
var _ physic.SenseEnv = &Dev{}

@ -0,0 +1,209 @@
// Copyright 2025 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package sht4x
import (
"math"
"sync"
"sync/atomic"
"testing"
"time"
"periph.io/x/conn/v3/i2c/i2ctest"
"periph.io/x/conn/v3/physic"
)
var liveDevice bool
func getDev(testName string) (*Dev, error) {
return New(&i2ctest.Playback{Ops: recordingData[testName], DontPanic: true}, DefaultAddress)
}
func TestBasic(t *testing.T) {
t.Logf("liveDevice=%t", liveDevice)
dev, err := getDev(t.Name())
if err != nil {
t.Fatal(err)
}
// Test String
s := dev.String()
if len(s) == 0 {
t.Error("string returned empty")
}
// Test Serial Number
sn, err := dev.SerialNumber()
if err == nil {
if sn == 0 {
t.Error("invalid serial number")
} else {
t.Logf("SerialNumber=0x%x", sn)
}
} else {
t.Error(err)
}
}
func TestCountToTemp(t *testing.T) {
temp := countToTemp(0)
if temp != minTemperature {
t.Errorf("invalid temperature %s. Expected -40", temp)
}
temp = countToTemp(0xffff)
if temp != maxTemperature {
t.Errorf("invalid temperature %s. Expected 125", temp)
}
temp = countToTemp(0x8000)
tTest := 42.5 + physic.ZeroCelsius.Celsius()
diff := physic.Temperature(math.Abs(tTest-float64(temp.Celsius()))) * physic.Kelvin
if diff > 2*physic.MilliKelvin {
t.Errorf("invalid temperature expected %f. got %s diff=%s", tTest, temp, diff)
}
}
func TestCountToHumidity(t *testing.T) {
rh := countToHumidity(0)
if rh != minRH {
t.Errorf("received RH %s expected %s", rh, minRH)
}
rh = countToHumidity(0xffff)
if rh != maxRH {
t.Errorf("received RH %s expected %s", rh, maxRH)
}
rh = countToHumidity(0x8000)
expected := physic.RelativeHumidity(56.5 * float64(physic.PercentRH))
diff := rh - expected
if diff > 2*physic.MilliRH {
t.Errorf("received rh %s expected %s diff=%v", rh, expected, diff)
}
}
// Test turning on the heater at various power levels and durations.
func TestHeater(t *testing.T) {
dev, err := getDev(t.Name())
if err != nil {
t.Fatal(err)
}
// Test Invalid parameters
_, err = dev.SetHeater(Power20mW, HeaterDuration(10*time.Second))
if err == nil {
t.Error("SetHeater() invalid duration did not generate error.")
}
_, err = dev.SetHeater(HeaterPower(500), Duration100ms)
if err == nil {
t.Error("SetHeater() invalid power level did not generate error.")
}
initEnv := &physic.Env{}
// Iterate over the allowed durations
for _, duration := range []HeaterDuration{Duration100ms, Duration1s} {
// Iterate over the supported heater power levels
for _, power := range []HeaterPower{Power20mW, Power110mW, Power200mW} {
// Read the initial temperature at the test start.
err := dev.Sense(initEnv)
if err != nil {
t.Error(err)
continue
}
// Turn the heater on 3 times
var diffLast float64
for range 3 {
env, err := dev.SetHeater(power, duration)
if err != nil {
t.Error(err)
break
}
// Confirm that the difference between the initial temperature and
// the temperature after the heater was turned on is > 0
diff := env.Temperature.Celsius() - initEnv.Temperature.Celsius()
t.Logf("initTemp=%s heaterTemp=%s, diff=%f", initEnv.Temperature, env.Temperature, diff)
if diff <= 0 || diff <= diffLast {
t.Errorf("heater error power=%d, Duration=%v diff=%f expected > 0", power, duration, diff)
}
diffLast = diff
}
if liveDevice {
// Give the thermometer core time to cool off
time.Sleep(10 * time.Second)
}
}
}
}
func TestReset(t *testing.T) {
dev, err := getDev(t.Name())
if err != nil {
t.Fatal(err)
}
err = dev.Reset()
if err != nil {
t.Error(err)
}
}
func TestSense(t *testing.T) {
dev, err := getDev(t.Name())
if err != nil {
t.Fatal(err)
}
env := &physic.Env{}
err = dev.Sense(env)
if err != nil {
t.Error(err)
}
t.Logf("env=%#v temperature=%s humidity=%s", *env, env.Temperature.String(), env.Humidity.String())
}
func TestSenseContinuous(t *testing.T) {
readCount := int32(10)
dev, err := getDev(t.Name())
if err != nil {
t.Fatal(err)
}
_, err = dev.SenseContinuous(time.Millisecond)
if err == nil {
t.Error("SenseContinuous() doesn't return an error on too short a duration.")
}
ch, err := dev.SenseContinuous(100 * time.Millisecond)
if err != nil {
t.Fatal(err)
}
_, err = dev.SenseContinuous(time.Second)
if err == nil {
t.Error("expected an error for attempting concurrent SenseContinuous")
}
counter := atomic.Int32{}
tEnd := time.Now().UnixMilli() + int64(readCount+2)*100
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
for {
time.Sleep(100 * time.Millisecond)
// Stay here until we get the expected number of reads, or the time
// has expired and when we do, Halt the SenseContinuous.
if counter.Load() >= readCount || time.Now().UnixMilli() >= tEnd {
t.Logf("calling halt!")
err := dev.Halt()
t.Logf("halt() returned")
if err != nil {
t.Error(err)
}
wg.Done()
return
}
}
}()
// Iterate over the channel until it's closed.
for e := range ch {
counter.Add(1)
t.Log(time.Now(), e, "count=", counter.Load())
}
if counter.Load() < readCount || counter.Load() > (readCount+1) {
t.Errorf("expected %d readings. received %d", readCount, counter.Load())
}
wg.Wait()
}

@ -0,0 +1,94 @@
// Copyright 2025 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package sht4x
import (
"periph.io/x/conn/v3/i2c/i2ctest"
)
// Auto-Generated by i2ctest.BusTest
var recordingData = map[string][]i2ctest.IO{
"TestBasic": {
{Addr: 0x44, W: []uint8{0x89}},
{Addr: 0x44, R: []uint8{0xd, 0x20, 0x47, 0x61, 0x1, 0x11}}},
"TestHeater": {
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x63, 0x36, 0x9a, 0x5a, 0x7c, 0xd}},
{Addr: 0x44, W: []uint8{0x15}},
{Addr: 0x44, R: []uint8{0x67, 0xa0, 0x86, 0x5a, 0xf9, 0x82}},
{Addr: 0x44, W: []uint8{0x15}},
{Addr: 0x44, R: []uint8{0x69, 0x28, 0x28, 0x5b, 0x27, 0x6d}},
{Addr: 0x44, W: []uint8{0x15}},
{Addr: 0x44, R: []uint8{0x6a, 0x26, 0x1a, 0x5b, 0x16, 0x99}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x63, 0x4a, 0x1f, 0x5a, 0x53, 0xa5}},
{Addr: 0x44, W: []uint8{0x24}},
{Addr: 0x44, R: []uint8{0x79, 0x7c, 0xfc, 0x5c, 0x84, 0x6c}},
{Addr: 0x44, W: []uint8{0x24}},
{Addr: 0x44, R: []uint8{0x81, 0x14, 0xd1, 0x5c, 0x64, 0xad}},
{Addr: 0x44, W: []uint8{0x24}},
{Addr: 0x44, R: []uint8{0x85, 0xd2, 0xb3, 0x5a, 0x5d, 0xba}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x63, 0x7f, 0x2f, 0x5b, 0x84, 0xc2}},
{Addr: 0x44, W: []uint8{0x32}},
{Addr: 0x44, R: []uint8{0x89, 0x3c, 0xd9, 0x5e, 0xae, 0xe8}},
{Addr: 0x44, W: []uint8{0x32}},
{Addr: 0x44, R: []uint8{0x95, 0xe0, 0x7a, 0x5c, 0xbe, 0x72}},
{Addr: 0x44, W: []uint8{0x32}},
{Addr: 0x44, R: []uint8{0x9d, 0xaa, 0xab, 0x55, 0xc0, 0x56}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x63, 0xbb, 0xac, 0x57, 0xed, 0x45}},
{Addr: 0x44, W: []uint8{0x1e}},
{Addr: 0x44, R: []uint8{0x6d, 0x4a, 0x72, 0x55, 0x53, 0x3c}},
{Addr: 0x44, W: []uint8{0x1e}},
{Addr: 0x44, R: []uint8{0x6e, 0x1e, 0xe5, 0x4e, 0x61, 0xef}},
{Addr: 0x44, W: []uint8{0x1e}},
{Addr: 0x44, R: []uint8{0x6e, 0x7e, 0x5e, 0x49, 0x91, 0xc3}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x63, 0xc5, 0x4b, 0x57, 0xeb, 0xe3}},
{Addr: 0x44, W: []uint8{0x2f}},
{Addr: 0x44, R: []uint8{0x93, 0x28, 0xde, 0x3b, 0xa5, 0x20}},
{Addr: 0x44, W: []uint8{0x2f}},
{Addr: 0x44, R: []uint8{0x97, 0x12, 0x43, 0x25, 0x59, 0xdc}},
{Addr: 0x44, W: []uint8{0x2f}},
{Addr: 0x44, R: []uint8{0x99, 0x2, 0x6d, 0x1f, 0x1f, 0x1b}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x65, 0x48, 0x27, 0x51, 0x9e, 0xb4}},
{Addr: 0x44, W: []uint8{0x39}},
{Addr: 0x44, R: []uint8{0xb3, 0xb8, 0x3b, 0x21, 0xa1, 0x64}},
{Addr: 0x44, W: []uint8{0x39}},
{Addr: 0x44, R: []uint8{0xb9, 0xd0, 0xd7, 0x15, 0x79, 0xe8}},
{Addr: 0x44, W: []uint8{0x39}},
{Addr: 0x44, R: []uint8{0xbc, 0xb8, 0xa2, 0x14, 0x4b, 0xbb}}},
"TestReset": {
{Addr: 0x44, W: []uint8{0x94}}},
"TestSense": {
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xf4, 0xc8, 0x4c, 0x9b, 0x6f}}},
"TestSenseContinuous": {
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xee, 0x50, 0x4c, 0xbf, 0x2d}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xda, 0x51, 0x4c, 0xeb, 0x97}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xdb, 0x60, 0x4c, 0xf6, 0x98}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xda, 0x51, 0x4d, 0x24, 0xa}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xd4, 0x4e, 0x4d, 0x3f, 0xa3}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xca, 0x12, 0x4d, 0x50, 0x36}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xba, 0xea, 0x4d, 0x71, 0x81}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xb6, 0x97, 0x4d, 0x99, 0xf9}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xb3, 0x62, 0x4d, 0xb0, 0xf7}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0xb3, 0x62, 0x4d, 0xc3, 0x5c}},
{Addr: 0x44, W: []uint8{0xfd}},
{Addr: 0x44, R: []uint8{0x66, 0x9e, 0xa8, 0x4d, 0xce, 0x10}}},
}
Loading…
Cancel
Save